1
# Copyright (c) 2013 Google Inc. All rights reserved.
2
# Use of this source code is governed by a BSD-style license that can be
3
# found in the LICENSE file.
7
This module is under development and should be considered experimental.
9
This module produces cmake (2.8.8+) input as its output. One CMakeLists.txt is
10
created for each configuration.
12
This module's original purpose was to support editing in IDEs like KDevelop
13
which use CMake for project management. It is also possible to use CMake to
14
generate projects for other IDEs such as eclipse cdt and code::blocks. QtCreator
15
will convert the CMakeLists.txt to a code::blocks cbp for the editor to read,
16
but build using CMake. As a result QtCreator editor is unaware of compiler
17
defines. The generated CMakeLists.txt can also be used to build on Linux. There
18
is currently no support for building on platforms other than Linux.
20
The generated CMakeLists.txt should properly compile all projects. However,
21
there is a mismatch between gyp and cmake with regard to linking. All attempts
22
are made to work around this, but CMake sometimes sees -Wl,--start-group as a
23
library and incorrectly repeats it. As a result the output of this generator
24
should not be relied on for building.
26
When using with kdevelop, use version 4.4+. Previous versions of kdevelop will
27
not be able to find the header file directories described in the generated
31
import multiprocessing
38
generator_default_variables = {
39
'EXECUTABLE_PREFIX': '',
40
'EXECUTABLE_SUFFIX': '',
41
'STATIC_LIB_PREFIX': 'lib',
42
'STATIC_LIB_SUFFIX': '.a',
43
'SHARED_LIB_PREFIX': 'lib',
44
'SHARED_LIB_SUFFIX': '.so',
45
'SHARED_LIB_DIR': '${builddir}/lib.${TOOLSET}',
46
'LIB_DIR': '${obj}.${TOOLSET}',
47
'INTERMEDIATE_DIR': '${obj}.${TOOLSET}/${TARGET}/geni',
48
'SHARED_INTERMEDIATE_DIR': '${obj}/gen',
49
'PRODUCT_DIR': '${builddir}',
50
'RULE_INPUT_PATH': '${RULE_INPUT_PATH}',
51
'RULE_INPUT_DIRNAME': '${RULE_INPUT_DIRNAME}',
52
'RULE_INPUT_NAME': '${RULE_INPUT_NAME}',
53
'RULE_INPUT_ROOT': '${RULE_INPUT_ROOT}',
54
'RULE_INPUT_EXT': '${RULE_INPUT_EXT}',
55
'CONFIGURATION_NAME': '${configuration}',
58
FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}')
60
generator_supports_multiple_toolsets = True
61
generator_wants_static_library_dependencies_adjusted = True
63
COMPILABLE_EXTENSIONS = {
73
def RemovePrefix(a, prefix):
74
"""Returns 'a' without 'prefix' if it starts with 'prefix'."""
75
return a[len(prefix):] if a.startswith(prefix) else a
78
def CalculateVariables(default_variables, params):
79
"""Calculate additional variables for use in the build (called by gyp)."""
80
default_variables.setdefault('OS', gyp.common.GetFlavor(params))
83
def Compilable(filename):
84
"""Return true if the file is compilable (should be in OBJS)."""
85
return any(filename.endswith(e) for e in COMPILABLE_EXTENSIONS)
88
def Linkable(filename):
89
"""Return true if the file is linkable (should be on the link line)."""
90
return filename.endswith('.o')
93
def NormjoinPathForceCMakeSource(base_path, rel_path):
94
"""Resolves rel_path against base_path and returns the result.
96
If rel_path is an absolute path it is returned unchanged.
97
Otherwise it is resolved against base_path and normalized.
98
If the result is a relative path, it is forced to be relative to the
101
if os.path.isabs(rel_path):
103
if any([rel_path.startswith(var) for var in FULL_PATH_VARS]):
105
# TODO: do we need to check base_path for absolute variables as well?
106
return os.path.join('${CMAKE_SOURCE_DIR}',
107
os.path.normpath(os.path.join(base_path, rel_path)))
110
def NormjoinPath(base_path, rel_path):
111
"""Resolves rel_path against base_path and returns the result.
112
TODO: what is this really used for?
113
If rel_path begins with '$' it is returned unchanged.
114
Otherwise it is resolved against base_path if relative, then normalized.
116
if rel_path.startswith('$') and not rel_path.startswith('${configuration}'):
118
return os.path.normpath(os.path.join(base_path, rel_path))
121
def CMakeStringEscape(a):
122
"""Escapes the string 'a' for use inside a CMake string.
125
'\' otherwise it may be seen as modifying the next character
126
'"' otherwise it will end the string
127
';' otherwise the string becomes a list
129
The following do not need to be escaped
130
'#' when the lexer is in string state, this does not start a comment
132
The following are yet unknown
133
'$' generator variables (like ${obj}) must not be escaped,
134
but text $ should be escaped
135
what is wanted is to know which $ come from generator variables
137
return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"')
140
def SetFileProperty(output, source_name, property_name, values, sep):
141
"""Given a set of source file, sets the given property on them."""
142
output.write('set_source_files_properties(')
143
output.write(source_name)
144
output.write(' PROPERTIES ')
145
output.write(property_name)
148
output.write(CMakeStringEscape(value))
153
def SetFilesProperty(output, variable, property_name, values, sep):
154
"""Given a set of source files, sets the given property on them."""
155
output.write('set_source_files_properties(')
156
WriteVariable(output, variable)
157
output.write(' PROPERTIES ')
158
output.write(property_name)
161
output.write(CMakeStringEscape(value))
166
def SetTargetProperty(output, target_name, property_name, values, sep=''):
167
"""Given a target, sets the given property."""
168
output.write('set_target_properties(')
169
output.write(target_name)
170
output.write(' PROPERTIES ')
171
output.write(property_name)
174
output.write(CMakeStringEscape(value))
179
def SetVariable(output, variable_name, value):
180
"""Sets a CMake variable."""
182
output.write(variable_name)
184
output.write(CMakeStringEscape(value))
188
def SetVariableList(output, variable_name, values):
189
"""Sets a CMake variable to a list."""
191
return SetVariable(output, variable_name, "")
193
return SetVariable(output, variable_name, values[0])
194
output.write('list(APPEND ')
195
output.write(variable_name)
197
output.write('"\n "'.join([CMakeStringEscape(value) for value in values]))
201
def UnsetVariable(output, variable_name):
202
"""Unsets a CMake variable."""
203
output.write('unset(')
204
output.write(variable_name)
208
def WriteVariable(output, variable_name, prepend=None):
210
output.write(prepend)
212
output.write(variable_name)
216
class CMakeTargetType(object):
217
def __init__(self, command, modifier, property_modifier):
218
self.command = command
219
self.modifier = modifier
220
self.property_modifier = property_modifier
223
cmake_target_type_from_gyp_target_type = {
224
'executable': CMakeTargetType('add_executable', None, 'RUNTIME'),
225
'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE'),
226
'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY'),
227
'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY'),
228
'none': CMakeTargetType('add_custom_target', 'SOURCES', None),
232
def StringToCMakeTargetName(a):
233
"""Converts the given string 'a' to a valid CMake target name.
235
All invalid characters are replaced by '_'.
236
Invalid for cmake: ' ', '/', '(', ')', '"'
237
Invalid for make: ':'
238
Invalid for unknown reasons but cause failures: '.'
240
return a.translate(string.maketrans(' /():."', '_______'))
243
def WriteActions(target_name, actions, extra_sources, extra_deps,
244
path_to_gyp, output):
245
"""Write CMake for the 'actions' in the target.
248
target_name: the name of the CMake target being generated.
249
actions: the Gyp 'actions' dict for this target.
250
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
251
extra_deps: [<cmake_taget>] to append with generated targets.
252
path_to_gyp: relative path from CMakeLists.txt being generated to
253
the Gyp file in which the target being generated is defined.
255
for action in actions:
256
action_name = StringToCMakeTargetName(action['action_name'])
257
action_target_name = '%s__%s' % (target_name, action_name)
259
inputs = action['inputs']
260
inputs_name = action_target_name + '__input'
261
SetVariableList(output, inputs_name,
262
[NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs])
264
outputs = action['outputs']
265
cmake_outputs = [NormjoinPathForceCMakeSource(path_to_gyp, out)
267
outputs_name = action_target_name + '__output'
268
SetVariableList(output, outputs_name, cmake_outputs)
270
# Build up a list of outputs.
271
# Collect the output dirs we'll need.
272
dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir)
274
if int(action.get('process_outputs_as_sources', False)):
275
extra_sources.extend(zip(cmake_outputs, outputs))
278
output.write('add_custom_command(OUTPUT ')
279
WriteVariable(output, outputs_name)
283
for directory in dirs:
284
output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ')
285
output.write(directory)
288
output.write(' COMMAND ')
289
output.write(gyp.common.EncodePOSIXShellList(action['action']))
292
output.write(' DEPENDS ')
293
WriteVariable(output, inputs_name)
296
output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/')
297
output.write(path_to_gyp)
300
output.write(' COMMENT ')
301
if 'message' in action:
302
output.write(action['message'])
304
output.write(action_target_name)
307
output.write(' VERBATIM\n')
311
output.write('add_custom_target(')
312
output.write(action_target_name)
313
output.write('\n DEPENDS ')
314
WriteVariable(output, outputs_name)
315
output.write('\n SOURCES ')
316
WriteVariable(output, inputs_name)
317
output.write('\n)\n')
319
extra_deps.append(action_target_name)
322
def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
323
if rel_path.startswith(("${RULE_INPUT_PATH}","${RULE_INPUT_DIRNAME}")):
324
if any([rule_source.startswith(var) for var in FULL_PATH_VARS]):
326
return NormjoinPathForceCMakeSource(base_path, rel_path)
329
def WriteRules(target_name, rules, extra_sources, extra_deps,
330
path_to_gyp, output):
331
"""Write CMake for the 'rules' in the target.
334
target_name: the name of the CMake target being generated.
335
actions: the Gyp 'actions' dict for this target.
336
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
337
extra_deps: [<cmake_taget>] to append with generated targets.
338
path_to_gyp: relative path from CMakeLists.txt being generated to
339
the Gyp file in which the target being generated is defined.
342
rule_name = StringToCMakeTargetName(target_name + '__' + rule['rule_name'])
344
inputs = rule.get('inputs', [])
345
inputs_name = rule_name + '__input'
346
SetVariableList(output, inputs_name,
347
[NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs])
348
outputs = rule['outputs']
351
for count, rule_source in enumerate(rule.get('rule_sources', [])):
352
action_name = rule_name + '_' + str(count)
354
rule_source_dirname, rule_source_basename = os.path.split(rule_source)
355
rule_source_root, rule_source_ext = os.path.splitext(rule_source_basename)
357
SetVariable(output, 'RULE_INPUT_PATH', rule_source)
358
SetVariable(output, 'RULE_INPUT_DIRNAME', rule_source_dirname)
359
SetVariable(output, 'RULE_INPUT_NAME', rule_source_basename)
360
SetVariable(output, 'RULE_INPUT_ROOT', rule_source_root)
361
SetVariable(output, 'RULE_INPUT_EXT', rule_source_ext)
363
# Build up a list of outputs.
364
# Collect the output dirs we'll need.
365
dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir)
367
# Create variables for the output, as 'local' variable will be unset.
369
for output_index, out in enumerate(outputs):
370
output_name = action_name + '_' + str(output_index)
371
SetVariable(output, output_name,
372
NormjoinRulePathForceCMakeSource(path_to_gyp, out,
374
if int(rule.get('process_outputs_as_sources', False)):
375
extra_sources.append(('${' + output_name + '}', out))
376
these_outputs.append('${' + output_name + '}')
377
var_outputs.append('${' + output_name + '}')
380
output.write('add_custom_command(OUTPUT\n')
381
for out in these_outputs:
386
for directory in dirs:
387
output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ')
388
output.write(directory)
391
output.write(' COMMAND ')
392
output.write(gyp.common.EncodePOSIXShellList(rule['action']))
395
output.write(' DEPENDS ')
396
WriteVariable(output, inputs_name)
398
output.write(NormjoinPath(path_to_gyp, rule_source))
401
# CMAKE_SOURCE_DIR is where the CMakeLists.txt lives.
402
# The cwd is the current build directory.
403
output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/')
404
output.write(path_to_gyp)
407
output.write(' COMMENT ')
408
if 'message' in rule:
409
output.write(rule['message'])
411
output.write(action_name)
414
output.write(' VERBATIM\n')
417
UnsetVariable(output, 'RULE_INPUT_PATH')
418
UnsetVariable(output, 'RULE_INPUT_DIRNAME')
419
UnsetVariable(output, 'RULE_INPUT_NAME')
420
UnsetVariable(output, 'RULE_INPUT_ROOT')
421
UnsetVariable(output, 'RULE_INPUT_EXT')
424
output.write('add_custom_target(')
425
output.write(rule_name)
426
output.write(' DEPENDS\n')
427
for out in var_outputs:
431
output.write('SOURCES ')
432
WriteVariable(output, inputs_name)
434
for rule_source in rule.get('rule_sources', []):
436
output.write(NormjoinPath(path_to_gyp, rule_source))
440
extra_deps.append(rule_name)
443
def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output):
444
"""Write CMake for the 'copies' in the target.
447
target_name: the name of the CMake target being generated.
448
actions: the Gyp 'actions' dict for this target.
449
extra_deps: [<cmake_taget>] to append with generated targets.
450
path_to_gyp: relative path from CMakeLists.txt being generated to
451
the Gyp file in which the target being generated is defined.
453
copy_name = target_name + '__copies'
455
# CMake gets upset with custom targets with OUTPUT which specify no output.
456
have_copies = any(copy['files'] for copy in copies)
458
output.write('add_custom_target(')
459
output.write(copy_name)
461
extra_deps.append(copy_name)
465
def __init__(self, ext, command):
466
self.cmake_inputs = []
467
self.cmake_outputs = []
469
self.gyp_outputs = []
471
self.inputs_name = None
472
self.outputs_name = None
473
self.command = command
475
file_copy = Copy('', 'copy')
476
dir_copy = Copy('_dirs', 'copy_directory')
479
files = copy['files']
480
destination = copy['destination']
482
path = os.path.normpath(src)
483
basename = os.path.split(path)[1]
484
dst = os.path.join(destination, basename)
486
copy = file_copy if os.path.basename(src) else dir_copy
488
copy.cmake_inputs.append(NormjoinPathForceCMakeSource(path_to_gyp, src))
489
copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst))
490
copy.gyp_inputs.append(src)
491
copy.gyp_outputs.append(dst)
493
for copy in (file_copy, dir_copy):
494
if copy.cmake_inputs:
495
copy.inputs_name = copy_name + '__input' + copy.ext
496
SetVariableList(output, copy.inputs_name, copy.cmake_inputs)
498
copy.outputs_name = copy_name + '__output' + copy.ext
499
SetVariableList(output, copy.outputs_name, copy.cmake_outputs)
502
output.write('add_custom_command(\n')
504
output.write('OUTPUT')
505
for copy in (file_copy, dir_copy):
506
if copy.outputs_name:
507
WriteVariable(output, copy.outputs_name, ' ')
510
for copy in (file_copy, dir_copy):
511
for src, dst in zip(copy.gyp_inputs, copy.gyp_outputs):
512
# 'cmake -E copy src dst' will create the 'dst' directory if needed.
513
output.write('COMMAND ${CMAKE_COMMAND} -E %s ' % copy.command)
519
output.write('DEPENDS')
520
for copy in (file_copy, dir_copy):
522
WriteVariable(output, copy.inputs_name, ' ')
525
output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/')
526
output.write(path_to_gyp)
529
output.write('COMMENT Copying for ')
530
output.write(target_name)
533
output.write('VERBATIM\n')
537
output.write('add_custom_target(')
538
output.write(copy_name)
539
output.write('\n DEPENDS')
540
for copy in (file_copy, dir_copy):
541
if copy.outputs_name:
542
WriteVariable(output, copy.outputs_name, ' ')
543
output.write('\n SOURCES')
544
if file_copy.inputs_name:
545
WriteVariable(output, file_copy.inputs_name, ' ')
546
output.write('\n)\n')
548
extra_deps.append(copy_name)
551
def CreateCMakeTargetBaseName(qualified_target):
552
"""This is the name we would like the target to have."""
553
_, gyp_target_name, gyp_target_toolset = (
554
gyp.common.ParseQualifiedTarget(qualified_target))
555
cmake_target_base_name = gyp_target_name
556
if gyp_target_toolset and gyp_target_toolset != 'target':
557
cmake_target_base_name += '_' + gyp_target_toolset
558
return StringToCMakeTargetName(cmake_target_base_name)
561
def CreateCMakeTargetFullName(qualified_target):
562
"""An unambiguous name for the target."""
563
gyp_file, gyp_target_name, gyp_target_toolset = (
564
gyp.common.ParseQualifiedTarget(qualified_target))
565
cmake_target_full_name = gyp_file + ':' + gyp_target_name
566
if gyp_target_toolset and gyp_target_toolset != 'target':
567
cmake_target_full_name += '_' + gyp_target_toolset
568
return StringToCMakeTargetName(cmake_target_full_name)
571
class CMakeNamer(object):
572
"""Converts Gyp target names into CMake target names.
574
CMake requires that target names be globally unique. One way to ensure
575
this is to fully qualify the names of the targets. Unfortunatly, this
576
ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
577
of just "chrome". If this generator were only interested in building, it
578
would be possible to fully qualify all target names, then create
579
unqualified target names which depend on all qualified targets which
580
should have had that name. This is more or less what the 'make' generator
581
does with aliases. However, one goal of this generator is to create CMake
582
files for use with IDEs, and fully qualified names are not as user
585
Since target name collision is rare, we do the above only when required.
587
Toolset variants are always qualified from the base, as this is required for
588
building. However, it also makes sense for an IDE, as it is possible for
589
defines to be different.
591
def __init__(self, target_list):
592
self.cmake_target_base_names_conficting = set()
594
cmake_target_base_names_seen = set()
595
for qualified_target in target_list:
596
cmake_target_base_name = CreateCMakeTargetBaseName(qualified_target)
598
if cmake_target_base_name not in cmake_target_base_names_seen:
599
cmake_target_base_names_seen.add(cmake_target_base_name)
601
self.cmake_target_base_names_conficting.add(cmake_target_base_name)
603
def CreateCMakeTargetName(self, qualified_target):
604
base_name = CreateCMakeTargetBaseName(qualified_target)
605
if base_name in self.cmake_target_base_names_conficting:
606
return CreateCMakeTargetFullName(qualified_target)
610
def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use,
611
options, generator_flags, all_qualified_targets, output):
613
# The make generator does this always.
614
# TODO: It would be nice to be able to tell CMake all dependencies.
615
circular_libs = generator_flags.get('circular', True)
617
if not generator_flags.get('standalone', False):
619
output.write(qualified_target)
622
gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
623
rel_gyp_file = gyp.common.RelativePath(gyp_file, options.toplevel_dir)
624
rel_gyp_dir = os.path.dirname(rel_gyp_file)
626
# Relative path from build dir to top dir.
627
build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir)
628
# Relative path from build dir to gyp dir.
629
build_to_gyp = os.path.join(build_to_top, rel_gyp_dir)
631
path_from_cmakelists_to_gyp = build_to_gyp
633
spec = target_dicts.get(qualified_target, {})
634
config = spec.get('configurations', {}).get(config_to_use, {})
636
target_name = spec.get('target_name', '<missing target name>')
637
target_type = spec.get('type', '<missing target type>')
638
target_toolset = spec.get('toolset')
640
cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type)
641
if cmake_target_type is None:
642
print ('Target %s has unknown target type %s, skipping.' %
643
( target_name, target_type ) )
646
SetVariable(output, 'TARGET', target_name)
647
SetVariable(output, 'TOOLSET', target_toolset)
649
cmake_target_name = namer.CreateCMakeTargetName(qualified_target)
654
# Actions must come first, since they can generate more OBJs for use below.
655
if 'actions' in spec:
656
WriteActions(cmake_target_name, spec['actions'], extra_sources, extra_deps,
657
path_from_cmakelists_to_gyp, output)
659
# Rules must be early like actions.
661
WriteRules(cmake_target_name, spec['rules'], extra_sources, extra_deps,
662
path_from_cmakelists_to_gyp, output)
666
WriteCopies(cmake_target_name, spec['copies'], extra_deps,
667
path_from_cmakelists_to_gyp, output)
670
srcs = spec.get('sources', [])
672
# Gyp separates the sheep from the goats based on file extensions.
673
# A full separation is done here because of flag handing (see below).
677
linkable_sources = []
680
_, ext = os.path.splitext(src)
681
src_type = COMPILABLE_EXTENSIONS.get(ext, None)
682
src_norm_path = NormjoinPath(path_from_cmakelists_to_gyp, src);
685
s_sources.append(src_norm_path)
686
elif src_type == 'cc':
687
c_sources.append(src_norm_path)
688
elif src_type == 'cxx':
689
cxx_sources.append(src_norm_path)
691
linkable_sources.append(src_norm_path)
693
other_sources.append(src_norm_path)
695
for extra_source in extra_sources:
696
src, real_source = extra_source
697
_, ext = os.path.splitext(real_source)
698
src_type = COMPILABLE_EXTENSIONS.get(ext, None)
701
s_sources.append(src)
702
elif src_type == 'cc':
703
c_sources.append(src)
704
elif src_type == 'cxx':
705
cxx_sources.append(src)
707
linkable_sources.append(src)
709
other_sources.append(src)
711
s_sources_name = None
713
s_sources_name = cmake_target_name + '__asm_srcs'
714
SetVariableList(output, s_sources_name, s_sources)
716
c_sources_name = None
718
c_sources_name = cmake_target_name + '__c_srcs'
719
SetVariableList(output, c_sources_name, c_sources)
721
cxx_sources_name = None
723
cxx_sources_name = cmake_target_name + '__cxx_srcs'
724
SetVariableList(output, cxx_sources_name, cxx_sources)
726
linkable_sources_name = None
728
linkable_sources_name = cmake_target_name + '__linkable_srcs'
729
SetVariableList(output, linkable_sources_name, linkable_sources)
731
other_sources_name = None
733
other_sources_name = cmake_target_name + '__other_srcs'
734
SetVariableList(output, other_sources_name, other_sources)
736
# CMake gets upset when executable targets provide no sources.
737
# http://www.cmake.org/pipermail/cmake/2010-July/038461.html
738
dummy_sources_name = None
739
has_sources = (s_sources_name or
742
linkable_sources_name or
744
if target_type == 'executable' and not has_sources:
745
dummy_sources_name = cmake_target_name + '__dummy_srcs'
746
SetVariable(output, dummy_sources_name,
747
"${obj}.${TOOLSET}/${TARGET}/genc/dummy.c")
748
output.write('if(NOT EXISTS "')
749
WriteVariable(output, dummy_sources_name)
751
output.write(' file(WRITE "')
752
WriteVariable(output, dummy_sources_name)
753
output.write('" "")\n')
754
output.write("endif()\n")
757
# CMake is opposed to setting linker directories and considers the practice
758
# of setting linker directories dangerous. Instead, it favors the use of
759
# find_library and passing absolute paths to target_link_libraries.
760
# However, CMake does provide the command link_directories, which adds
761
# link directories to targets defined after it is called.
762
# As a result, link_directories must come before the target definition.
763
# CMake unfortunately has no means of removing entries from LINK_DIRECTORIES.
764
library_dirs = config.get('library_dirs')
765
if library_dirs is not None:
766
output.write('link_directories(')
767
for library_dir in library_dirs:
769
output.write(NormjoinPath(path_from_cmakelists_to_gyp, library_dir))
773
output.write(cmake_target_type.command)
775
output.write(cmake_target_name)
777
if cmake_target_type.modifier is not None:
779
output.write(cmake_target_type.modifier)
782
WriteVariable(output, s_sources_name, ' ')
784
WriteVariable(output, c_sources_name, ' ')
786
WriteVariable(output, cxx_sources_name, ' ')
787
if linkable_sources_name:
788
WriteVariable(output, linkable_sources_name, ' ')
789
if other_sources_name:
790
WriteVariable(output, other_sources_name, ' ')
791
if dummy_sources_name:
792
WriteVariable(output, dummy_sources_name, ' ')
796
# Let CMake know if the 'all' target should depend on this target.
797
exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets
799
SetTargetProperty(output, cmake_target_name,
800
'EXCLUDE_FROM_ALL', exclude_from_all)
801
for extra_target_name in extra_deps:
802
SetTargetProperty(output, extra_target_name,
803
'EXCLUDE_FROM_ALL', exclude_from_all)
805
# Output name and location.
806
if target_type != 'none':
807
# Link as 'C' if there are no other files
808
if not c_sources and not cxx_sources:
809
SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C'])
811
# Mark uncompiled sources as uncompiled.
812
if other_sources_name:
813
output.write('set_source_files_properties(')
814
WriteVariable(output, other_sources_name, '')
815
output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n')
817
# Mark object sources as linkable.
818
if linkable_sources_name:
819
output.write('set_source_files_properties(')
820
WriteVariable(output, other_sources_name, '')
821
output.write(' PROPERTIES EXTERNAL_OBJECT "TRUE")\n')
824
target_output_directory = spec.get('product_dir')
825
if target_output_directory is None:
826
if target_type in ('executable', 'loadable_module'):
827
target_output_directory = generator_default_variables['PRODUCT_DIR']
828
elif target_type == 'shared_library':
829
target_output_directory = '${builddir}/lib.${TOOLSET}'
830
elif spec.get('standalone_static_library', False):
831
target_output_directory = generator_default_variables['PRODUCT_DIR']
833
base_path = gyp.common.RelativePath(os.path.dirname(gyp_file),
834
options.toplevel_dir)
835
target_output_directory = '${obj}.${TOOLSET}'
836
target_output_directory = (
837
os.path.join(target_output_directory, base_path))
839
cmake_target_output_directory = NormjoinPathForceCMakeSource(
840
path_from_cmakelists_to_gyp,
841
target_output_directory)
842
SetTargetProperty(output,
844
cmake_target_type.property_modifier + '_OUTPUT_DIRECTORY',
845
cmake_target_output_directory)
848
default_product_prefix = ''
849
default_product_name = target_name
850
default_product_ext = ''
851
if target_type == 'static_library':
852
static_library_prefix = generator_default_variables['STATIC_LIB_PREFIX']
853
default_product_name = RemovePrefix(default_product_name,
854
static_library_prefix)
855
default_product_prefix = static_library_prefix
856
default_product_ext = generator_default_variables['STATIC_LIB_SUFFIX']
858
elif target_type in ('loadable_module', 'shared_library'):
859
shared_library_prefix = generator_default_variables['SHARED_LIB_PREFIX']
860
default_product_name = RemovePrefix(default_product_name,
861
shared_library_prefix)
862
default_product_prefix = shared_library_prefix
863
default_product_ext = generator_default_variables['SHARED_LIB_SUFFIX']
865
elif target_type != 'executable':
866
print ('ERROR: What output file should be generated?',
867
'type', target_type, 'target', target_name)
869
product_prefix = spec.get('product_prefix', default_product_prefix)
870
product_name = spec.get('product_name', default_product_name)
871
product_ext = spec.get('product_extension')
873
product_ext = '.' + product_ext
875
product_ext = default_product_ext
877
SetTargetProperty(output, cmake_target_name, 'PREFIX', product_prefix)
878
SetTargetProperty(output, cmake_target_name,
879
cmake_target_type.property_modifier + '_OUTPUT_NAME',
881
SetTargetProperty(output, cmake_target_name, 'SUFFIX', product_ext)
883
# Make the output of this target referenceable as a source.
884
cmake_target_output_basename = product_prefix + product_name + product_ext
885
cmake_target_output = os.path.join(cmake_target_output_directory,
886
cmake_target_output_basename)
887
SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '')
890
includes = config.get('include_dirs')
892
# This (target include directories) is what requires CMake 2.8.8
893
includes_name = cmake_target_name + '__include_dirs'
894
SetVariableList(output, includes_name,
895
[NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include)
896
for include in includes])
897
output.write('set_property(TARGET ')
898
output.write(cmake_target_name)
899
output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ')
900
WriteVariable(output, includes_name, '')
904
defines = config.get('defines')
905
if defines is not None:
906
SetTargetProperty(output,
908
'COMPILE_DEFINITIONS',
912
# Compile Flags - http://www.cmake.org/Bug/view.php?id=6493
913
# CMake currently does not have target C and CXX flags.
914
# So, instead of doing...
916
# cflags_c = config.get('cflags_c')
917
# if cflags_c is not None:
918
# SetTargetProperty(output, cmake_target_name,
919
# 'C_COMPILE_FLAGS', cflags_c, ' ')
921
# cflags_cc = config.get('cflags_cc')
922
# if cflags_cc is not None:
923
# SetTargetProperty(output, cmake_target_name,
924
# 'CXX_COMPILE_FLAGS', cflags_cc, ' ')
927
cflags = config.get('cflags', [])
928
cflags_c = config.get('cflags_c', [])
929
cflags_cxx = config.get('cflags_cc', [])
930
if (not cflags_c or not c_sources) and (not cflags_cxx or not cxx_sources):
931
SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', cflags, ' ')
933
elif c_sources and not (s_sources or cxx_sources):
936
flags.extend(cflags_c)
937
SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ')
939
elif cxx_sources and not (s_sources or c_sources):
942
flags.extend(cflags_cxx)
943
SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ')
946
# TODO: This is broken, one cannot generally set properties on files,
947
# as other targets may require different properties on the same files.
948
if s_sources and cflags:
949
SetFilesProperty(output, s_sources_name, 'COMPILE_FLAGS', cflags, ' ')
951
if c_sources and (cflags or cflags_c):
954
flags.extend(cflags_c)
955
SetFilesProperty(output, c_sources_name, 'COMPILE_FLAGS', flags, ' ')
957
if cxx_sources and (cflags or cflags_cxx):
960
flags.extend(cflags_cxx)
961
SetFilesProperty(output, cxx_sources_name, 'COMPILE_FLAGS', flags, ' ')
964
ldflags = config.get('ldflags')
965
if ldflags is not None:
966
SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ')
968
# Note on Dependencies and Libraries:
969
# CMake wants to handle link order, resolving the link line up front.
970
# Gyp does not retain or enforce specifying enough information to do so.
971
# So do as other gyp generators and use --start-group and --end-group.
972
# Give CMake as little information as possible so that it doesn't mess it up.
975
rawDeps = spec.get('dependencies', [])
980
for rawDep in rawDeps:
981
dep_cmake_name = namer.CreateCMakeTargetName(rawDep)
982
dep_spec = target_dicts.get(rawDep, {})
983
dep_target_type = dep_spec.get('type', None)
985
if dep_target_type == 'static_library':
986
static_deps.append(dep_cmake_name)
987
elif dep_target_type == 'shared_library':
988
shared_deps.append(dep_cmake_name)
990
other_deps.append(dep_cmake_name)
992
# ensure all external dependencies are complete before internal dependencies
993
# extra_deps currently only depend on their own deps, so otherwise run early
994
if static_deps or shared_deps or other_deps:
995
for extra_dep in extra_deps:
996
output.write('add_dependencies(')
997
output.write(extra_dep)
999
for deps in (static_deps, shared_deps, other_deps):
1000
for dep in gyp.common.uniquer(deps):
1006
linkable = target_type in ('executable', 'loadable_module', 'shared_library')
1007
other_deps.extend(extra_deps)
1008
if other_deps or (not linkable and (static_deps or shared_deps)):
1009
output.write('add_dependencies(')
1010
output.write(cmake_target_name)
1012
for dep in gyp.common.uniquer(other_deps):
1017
for deps in (static_deps, shared_deps):
1018
for lib_dep in gyp.common.uniquer(deps):
1020
output.write(lib_dep)
1026
external_libs = [lib for lib in spec.get('libraries', []) if len(lib) > 0]
1027
if external_libs or static_deps or shared_deps:
1028
output.write('target_link_libraries(')
1029
output.write(cmake_target_name)
1032
write_group = circular_libs and len(static_deps) > 1
1034
output.write('-Wl,--start-group\n')
1035
for dep in gyp.common.uniquer(static_deps):
1040
output.write('-Wl,--end-group\n')
1042
for dep in gyp.common.uniquer(shared_deps):
1047
for lib in gyp.common.uniquer(external_libs):
1054
UnsetVariable(output, 'TOOLSET')
1055
UnsetVariable(output, 'TARGET')
1058
def GenerateOutputForConfig(target_list, target_dicts, data,
1059
params, config_to_use):
1060
options = params['options']
1061
generator_flags = params['generator_flags']
1063
# generator_dir: relative path from pwd to where make puts build files.
1064
# Makes migrating from make to cmake easier, cmake doesn't put anything here.
1065
# Each Gyp configuration creates a different CMakeLists.txt file
1066
# to avoid incompatibilities between Gyp and CMake configurations.
1067
generator_dir = os.path.relpath(options.generator_output or '.')
1069
# output_dir: relative path from generator_dir to the build directory.
1070
output_dir = generator_flags.get('output_dir', 'out')
1072
# build_dir: relative path from source root to our output files.
1074
build_dir = os.path.normpath(os.path.join(generator_dir,
1078
toplevel_build = os.path.join(options.toplevel_dir, build_dir)
1080
output_file = os.path.join(toplevel_build, 'CMakeLists.txt')
1081
gyp.common.EnsureDirExists(output_file)
1083
output = open(output_file, 'w')
1084
output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n')
1085
output.write('cmake_policy(VERSION 2.8.8)\n')
1087
gyp_file, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1])
1088
output.write('project(')
1089
output.write(project_target)
1092
SetVariable(output, 'configuration', config_to_use)
1098
make_global_settings = data[gyp_file].get('make_global_settings', [])
1099
build_to_top = gyp.common.InvertRelativePath(build_dir,
1100
options.toplevel_dir)
1101
for key, value in make_global_settings:
1103
ar = os.path.join(build_to_top, value)
1105
cc = os.path.join(build_to_top, value)
1107
cxx = os.path.join(build_to_top, value)
1109
ar = gyp.common.GetEnvironFallback(['AR_target', 'AR'], ar)
1110
cc = gyp.common.GetEnvironFallback(['CC_target', 'CC'], cc)
1111
cxx = gyp.common.GetEnvironFallback(['CXX_target', 'CXX'], cxx)
1114
SetVariable(output, 'CMAKE_AR', ar)
1116
SetVariable(output, 'CMAKE_C_COMPILER', cc)
1118
SetVariable(output, 'CMAKE_CXX_COMPILER', cxx)
1120
# The following appears to be as-yet undocumented.
1121
# http://public.kitware.com/Bug/view.php?id=8392
1122
output.write('enable_language(ASM)\n')
1123
# ASM-ATT does not support .S files.
1124
# output.write('enable_language(ASM-ATT)\n')
1127
SetVariable(output, 'CMAKE_ASM_COMPILER', cc)
1129
SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}')
1130
SetVariable(output, 'obj', '${builddir}/obj')
1133
# TODO: Undocumented/unsupported (the CMake Java generator depends on it).
1134
# CMake by default names the object resulting from foo.c to be foo.c.o.
1135
# Gyp traditionally names the object resulting from foo.c foo.o.
1136
# This should be irrelevant, but some targets extract .o files from .a
1137
# and depend on the name of the extracted .o files.
1138
output.write('set(CMAKE_C_OUTPUT_EXTENSION_REPLACE 1)\n')
1139
output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n')
1142
# Force ninja to use rsp files. Otherwise link and ar lines can get too long,
1143
# resulting in 'Argument list too long' errors.
1144
output.write('set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1)\n')
1147
namer = CMakeNamer(target_list)
1149
# The list of targets upon which the 'all' target should depend.
1150
# CMake has it's own implicit 'all' target, one is not created explicitly.
1151
all_qualified_targets = set()
1152
for build_file in params['build_files']:
1153
for qualified_target in gyp.common.AllTargets(target_list,
1155
os.path.normpath(build_file)):
1156
all_qualified_targets.add(qualified_target)
1158
for qualified_target in target_list:
1159
WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use,
1160
options, generator_flags, all_qualified_targets, output)
1165
def PerformBuild(data, configurations, params):
1166
options = params['options']
1167
generator_flags = params['generator_flags']
1169
# generator_dir: relative path from pwd to where make puts build files.
1170
# Makes migrating from make to cmake easier, cmake doesn't put anything here.
1171
generator_dir = os.path.relpath(options.generator_output or '.')
1173
# output_dir: relative path from generator_dir to the build directory.
1174
output_dir = generator_flags.get('output_dir', 'out')
1176
for config_name in configurations:
1177
# build_dir: relative path from source root to our output files.
1179
build_dir = os.path.normpath(os.path.join(generator_dir,
1182
arguments = ['cmake', '-G', 'Ninja']
1183
print 'Generating [%s]: %s' % (config_name, arguments)
1184
subprocess.check_call(arguments, cwd=build_dir)
1186
arguments = ['ninja', '-C', build_dir]
1187
print 'Building [%s]: %s' % (config_name, arguments)
1188
subprocess.check_call(arguments)
1191
def CallGenerateOutputForConfig(arglist):
1192
# Ignore the interrupt signal so that the parent process catches it and
1193
# kills all multiprocessing children.
1194
signal.signal(signal.SIGINT, signal.SIG_IGN)
1196
target_list, target_dicts, data, params, config_name = arglist
1197
GenerateOutputForConfig(target_list, target_dicts, data, params, config_name)
1200
def GenerateOutput(target_list, target_dicts, data, params):
1201
user_config = params.get('generator_flags', {}).get('config', None)
1203
GenerateOutputForConfig(target_list, target_dicts, data,
1204
params, user_config)
1206
config_names = target_dicts[target_list[0]]['configurations'].keys()
1207
if params['parallel']:
1209
pool = multiprocessing.Pool(len(config_names))
1211
for config_name in config_names:
1212
arglists.append((target_list, target_dicts, data,
1213
params, config_name))
1214
pool.map(CallGenerateOutputForConfig, arglists)
1215
except KeyboardInterrupt, e:
1219
for config_name in config_names:
1220
GenerateOutputForConfig(target_list, target_dicts, data,
1221
params, config_name)