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_CURRENT_LIST_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_CURRENT_LIST_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_CURRENT_LIST_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_CURRENT_LIST_DIR is where the CMakeLists.txt lives.
402 # The cwd is the current build directory.
403 output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_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_CURRENT_LIST_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_CURRENT_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)