Index: pylib/gyp/generator/cmake.py |
=================================================================== |
--- pylib/gyp/generator/cmake.py (revision 0) |
+++ pylib/gyp/generator/cmake.py (working copy) |
@@ -0,0 +1,1150 @@ |
+# Copyright (c) 2013 Google Inc. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""cmake output module |
+ |
+This module is under development and should be considered experimental. |
+ |
+This module produces cmake (2.8.8+) input as its output. One CMakeLists.txt is |
+created for each configuration. |
+ |
+This module's original purpose was to support editing in IDEs like KDevelop |
+which use CMake for project management. It is also possible to use CMake to |
+generate projects for other IDEs such as eclipse cdt and code::blocks. QtCreator |
+will convert the CMakeLists.txt to a code::blocks cbp for the editor to read, |
+but build using CMake. As a result QtCreator editor is unaware of compiler |
+defines. The generated CMakeLists.txt can also be used to build on Linux. There |
+is currently no support for building on platforms other than Linux. |
+ |
+The generated CMakeLists.txt should properly compile all projects. However, |
+there is a mismatch between gyp and cmake with regard to linking. All attempts |
+are made to work around this, but CMake sometimes sees -Wl,--start-group as a |
+library and incorrectly repeats it. As a result the output of this generator |
+should not be relied on for building. |
+ |
+When using with kdevelop, use version 4.4+. Previous versions of kdevelop will |
+not be able to find the header file directories described in the generated |
+CMakeLists.txt file. |
+""" |
+ |
+import multiprocessing |
+import os |
+import signal |
+import string |
+import subprocess |
+import gyp.common |
+ |
+generator_default_variables = { |
+ 'EXECUTABLE_PREFIX': '', |
+ 'EXECUTABLE_SUFFIX': '', |
+ 'STATIC_LIB_PREFIX': 'lib', |
+ 'STATIC_LIB_SUFFIX': '.a', |
+ 'SHARED_LIB_PREFIX': 'lib', |
+ 'SHARED_LIB_SUFFIX': '.so', |
+ 'SHARED_LIB_DIR': '${builddir}/lib.${TOOLSET}', |
+ 'LIB_DIR': '${obj}.${TOOLSET}', |
+ 'INTERMEDIATE_DIR': '${obj}.${TOOLSET}/${TARGET}/geni', |
+ 'SHARED_INTERMEDIATE_DIR': '${obj}/gen', |
+ 'PRODUCT_DIR': '${builddir}', |
+ 'RULE_INPUT_PATH': '${RULE_INPUT_PATH}', |
+ 'RULE_INPUT_DIRNAME': '${RULE_INPUT_DIRNAME}', |
+ 'RULE_INPUT_NAME': '${RULE_INPUT_NAME}', |
+ 'RULE_INPUT_ROOT': '${RULE_INPUT_ROOT}', |
+ 'RULE_INPUT_EXT': '${RULE_INPUT_EXT}', |
+ 'CONFIGURATION_NAME': '${configuration}', |
+} |
+ |
+FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}') |
+ |
+generator_supports_multiple_toolsets = True |
+generator_wants_static_library_dependencies_adjusted = True |
+ |
+COMPILABLE_EXTENSIONS = { |
+ '.c': 'cc', |
+ '.cc': 'cxx', |
+ '.cpp': 'cxx', |
+ '.cxx': 'cxx', |
+ '.s': 's', # cc |
+ '.S': 's', # cc |
+} |
+ |
+ |
+def RemovePrefix(a, prefix): |
+ """Returns 'a' without 'prefix' if it starts with 'prefix'.""" |
+ return a[len(prefix):] if a.startswith(prefix) else a |
+ |
+ |
+def CalculateVariables(default_variables, params): |
+ """Calculate additional variables for use in the build (called by gyp).""" |
+ default_variables.setdefault('OS', gyp.common.GetFlavor(params)) |
+ |
+ |
+def Compilable(filename): |
+ """Return true if the file is compilable (should be in OBJS).""" |
+ return any(filename.endswith(e) for e in COMPILABLE_EXTENSIONS) |
+ |
+ |
+def Linkable(filename): |
+ """Return true if the file is linkable (should be on the link line).""" |
+ return filename.endswith('.o') |
+ |
+ |
+def NormjoinPathForceCMakeSource(base_path, rel_path): |
+ """Resolves rel_path against base_path and returns the result. |
+ |
+ If rel_path is an absolute path it is returned unchanged. |
+ Otherwise it is resolved against base_path and normalized. |
+ If the result is a relative path, it is forced to be relative to the |
+ CMakeLists.txt. |
+ """ |
+ if os.path.isabs(rel_path): |
+ return rel_path |
+ if any([rel_path.startswith(var) for var in FULL_PATH_VARS]): |
+ return rel_path |
+ # TODO: do we need to check base_path for absolute variables as well? |
+ return os.path.join('${CMAKE_SOURCE_DIR}', |
+ os.path.normpath(os.path.join(base_path, rel_path))) |
+ |
+ |
+def NormjoinPath(base_path, rel_path): |
+ """Resolves rel_path against base_path and returns the result. |
+ TODO: what is this really used for? |
+ If rel_path begins with '$' it is returned unchanged. |
+ Otherwise it is resolved against base_path if relative, then normalized. |
+ """ |
+ if rel_path.startswith('$') and not rel_path.startswith('${configuration}'): |
+ return rel_path |
+ return os.path.normpath(os.path.join(base_path, rel_path)) |
+ |
+ |
+def EnsureDirectoryExists(path): |
+ """Python version of 'mkdir -p'.""" |
+ dirPath = os.path.dirname(path) |
+ if dirPath and not os.path.exists(dirPath): |
+ os.makedirs(dirPath) |
+ |
+ |
+def CMakeStringEscape(a): |
+ """Escapes the string 'a' for use inside a CMake string. |
+ |
+ This means escaping |
+ '\' otherwise it may be seen as modifying the next character |
+ '"' otherwise it will end the string |
+ ';' otherwise the string becomes a list |
+ |
+ The following do not need to be escaped |
+ '#' when the lexer is in string state, this does not start a comment |
+ |
+ The following are yet unknown |
+ '$' generator variables (like ${obj}) must not be escaped, |
+ but text $ should be escaped |
+ what is wanted is to know which $ come from generator variables |
+ """ |
+ return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"') |
+ |
+ |
+def SetFileProperty(output, source_name, property_name, values, sep): |
+ """Given a set of source file, sets the given property on them.""" |
+ output.write('set_source_files_properties(') |
+ output.write(source_name) |
+ output.write(' PROPERTIES ') |
+ output.write(property_name) |
+ output.write(' "') |
+ for value in values: |
+ output.write(CMakeStringEscape(value)) |
+ output.write(sep) |
+ output.write('")\n') |
+ |
+ |
+def SetFilesProperty(output, source_names, property_name, values, sep): |
+ """Given a set of source files, sets the given property on them.""" |
+ output.write('set_source_files_properties(\n') |
+ for source_name in source_names: |
+ output.write(' ') |
+ output.write(source_name) |
+ output.write('\n') |
+ output.write(' PROPERTIES\n ') |
+ output.write(property_name) |
+ output.write(' "') |
+ for value in values: |
+ output.write(CMakeStringEscape(value)) |
+ output.write(sep) |
+ output.write('"\n)\n') |
+ |
+ |
+def SetTargetProperty(output, target_name, property_name, values, sep=''): |
+ """Given a target, sets the given property.""" |
+ output.write('set_target_properties(') |
+ output.write(target_name) |
+ output.write(' PROPERTIES ') |
+ output.write(property_name) |
+ output.write(' "') |
+ for value in values: |
+ output.write(CMakeStringEscape(value)) |
+ output.write(sep) |
+ output.write('")\n') |
+ |
+ |
+def SetVariable(output, variable_name, value): |
+ """Sets a CMake variable.""" |
+ output.write('set(') |
+ output.write(variable_name) |
+ output.write(' "') |
+ output.write(CMakeStringEscape(value)) |
+ output.write('")\n') |
+ |
+ |
+def SetVariableList(output, variable_name, values): |
+ """Sets a CMake variable to a list.""" |
+ if not values: |
+ return SetVariable(output, variable_name, "") |
+ if len(values) == 1: |
+ return SetVariable(output, variable_name, values[0]) |
+ output.write('list(APPEND ') |
+ output.write(variable_name) |
+ output.write('\n "') |
+ output.write('"\n "'.join([CMakeStringEscape(value) for value in values])) |
+ output.write('")\n') |
+ |
+ |
+def UnsetVariable(output, variable_name): |
+ """Unsets a CMake variable.""" |
+ output.write('unset(') |
+ output.write(variable_name) |
+ output.write(')\n') |
+ |
+ |
+def WriteVariable(output, variable_name, prepend=None): |
+ if prepend: |
+ output.write(prepend) |
+ output.write('${') |
+ output.write(variable_name) |
+ output.write('}') |
+ |
+ |
+class CMakeTargetType: |
+ def __init__(self, command, modifier, property_modifier): |
+ self.command = command |
+ self.modifier = modifier |
+ self.property_modifier = property_modifier |
+ |
+ |
+cmake_target_type_from_gyp_target_type = { |
+ 'executable': CMakeTargetType('add_executable', None, 'RUNTIME'), |
+ 'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE'), |
+ 'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY'), |
+ 'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY'), |
+ 'none': CMakeTargetType('add_custom_target', 'SOURCES', None), |
+} |
+ |
+ |
+def StringToCMakeTargetName(a): |
+ """Converts the given string 'a' to a valid CMake target name. |
+ |
+ All invalid characters are replaced by '_'. |
+ Invalid for cmake: ' ', '/', '(', ')' |
+ Invalid for make: ':' |
+ Invalid for unknown reasons but cause failures: '.' |
+ """ |
+ return a.translate(string.maketrans(' /():.', '______')) |
+ |
+ |
+def WriteActions(target_name, actions, extra_sources, extra_deps, |
+ path_to_gyp, output): |
+ """Write CMake for the 'actions' in the target. |
+ |
+ Args: |
+ target_name: the name of the CMake target being generated. |
+ actions: the Gyp 'actions' dict for this target. |
+ extra_sources: [(<cmake_src>, <src>)] to append with generated source files. |
+ extra_deps: [<cmake_taget>] to append with generated targets. |
+ path_to_gyp: relative path from CMakeLists.txt being generated to |
+ the Gyp file in which the target being generated is defined. |
+ """ |
+ for action in actions: |
+ action_name = StringToCMakeTargetName(action['action_name']) |
+ action_target_name = '%s__%s' % (target_name, action_name) |
+ |
+ inputs = action['inputs'] |
+ inputs_name = action_target_name + '__input' |
+ SetVariableList(output, inputs_name, |
+ [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs]) |
+ |
+ outputs = action['outputs'] |
+ cmake_outputs = [NormjoinPathForceCMakeSource(path_to_gyp, out) |
+ for out in outputs] |
+ outputs_name = action_target_name + '__output' |
+ SetVariableList(output, outputs_name, cmake_outputs) |
+ |
+ # Build up a list of outputs. |
+ # Collect the output dirs we'll need. |
+ dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir) |
+ |
+ if int(action.get('process_outputs_as_sources', False)): |
+ extra_sources.extend(zip(cmake_outputs, outputs)) |
+ |
+ # add_custom_command |
+ output.write('add_custom_command(OUTPUT ') |
+ WriteVariable(output, outputs_name) |
+ output.write('\n') |
+ |
+ if len(dirs) > 0: |
+ for directory in dirs: |
+ output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ') |
+ output.write(directory) |
+ output.write('\n') |
+ |
+ output.write(' COMMAND ') |
+ output.write(gyp.common.EncodePOSIXShellList(action['action'])) |
+ output.write('\n') |
+ |
+ output.write(' DEPENDS ') |
+ WriteVariable(output, inputs_name) |
+ output.write('\n') |
+ |
+ output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') |
+ output.write(path_to_gyp) |
+ output.write('\n') |
+ |
+ output.write(' COMMENT ') |
+ if 'message' in action: |
+ output.write(action['message']) |
+ else: |
+ output.write(action_target_name) |
+ output.write('\n') |
+ |
+ output.write(' VERBATIM\n') |
+ output.write(')\n') |
+ |
+ # add_custom_target |
+ output.write('add_custom_target(') |
+ output.write(action_target_name) |
+ output.write('\n DEPENDS ') |
+ WriteVariable(output, outputs_name) |
+ output.write('\n SOURCES ') |
+ WriteVariable(output, inputs_name) |
+ output.write('\n)\n') |
+ |
+ extra_deps.append(action_target_name) |
+ |
+ |
+def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source): |
+ if rel_path.startswith(("${RULE_INPUT_PATH}","${RULE_INPUT_DIRNAME}")): |
+ if any([rule_source.startswith(var) for var in FULL_PATH_VARS]): |
+ return rel_path |
+ return NormjoinPathForceCMakeSource(base_path, rel_path) |
+ |
+ |
+def WriteRules(target_name, rules, extra_sources, extra_deps, |
+ path_to_gyp, output): |
+ """Write CMake for the 'rules' in the target. |
+ |
+ Args: |
+ target_name: the name of the CMake target being generated. |
+ actions: the Gyp 'actions' dict for this target. |
+ extra_sources: [(<cmake_src>, <src>)] to append with generated source files. |
+ extra_deps: [<cmake_taget>] to append with generated targets. |
+ path_to_gyp: relative path from CMakeLists.txt being generated to |
+ the Gyp file in which the target being generated is defined. |
+ """ |
+ for rule in rules: |
+ rule_name = StringToCMakeTargetName(target_name + '__' + rule['rule_name']) |
+ |
+ inputs = rule.get('inputs', []) |
+ inputs_name = rule_name + '__input' |
+ SetVariableList(output, inputs_name, |
+ [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs]) |
+ outputs = rule['outputs'] |
+ var_outputs = [] |
+ |
+ for count, rule_source in enumerate(rule.get('rule_sources', [])): |
+ action_name = rule_name + '_' + str(count) |
+ |
+ rule_source_dirname, rule_source_basename = os.path.split(rule_source) |
+ rule_source_root, rule_source_ext = os.path.splitext(rule_source_basename) |
+ |
+ SetVariable(output, 'RULE_INPUT_PATH', rule_source) |
+ SetVariable(output, 'RULE_INPUT_DIRNAME', rule_source_dirname) |
+ SetVariable(output, 'RULE_INPUT_NAME', rule_source_basename) |
+ SetVariable(output, 'RULE_INPUT_ROOT', rule_source_root) |
+ SetVariable(output, 'RULE_INPUT_EXT', rule_source_ext) |
+ |
+ # Build up a list of outputs. |
+ # Collect the output dirs we'll need. |
+ dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir) |
+ |
+ # Create variables for the output, as 'local' variable will be unset. |
+ these_outputs = [] |
+ for output_index, out in enumerate(outputs): |
+ output_name = action_name + '_' + str(output_index) |
+ SetVariable(output, output_name, |
+ NormjoinRulePathForceCMakeSource(path_to_gyp, out, |
+ rule_source)) |
+ if int(rule.get('process_outputs_as_sources', False)): |
+ extra_sources.append(('${' + output_name + '}', out)) |
+ these_outputs.append('${' + output_name + '}') |
+ var_outputs.append('${' + output_name + '}') |
+ |
+ # add_custom_command |
+ output.write('add_custom_command(OUTPUT\n') |
+ for out in these_outputs: |
+ output.write(' ') |
+ output.write(out) |
+ output.write('\n') |
+ |
+ for directory in dirs: |
+ output.write(' COMMAND ${CMAKE_COMMAND} -E make_directory ') |
+ output.write(directory) |
+ output.write('\n') |
+ |
+ output.write(' COMMAND ') |
+ output.write(gyp.common.EncodePOSIXShellList(rule['action'])) |
+ output.write('\n') |
+ |
+ output.write(' DEPENDS ') |
+ WriteVariable(output, inputs_name) |
+ output.write(' ') |
+ output.write(NormjoinPath(path_to_gyp, rule_source)) |
+ output.write('\n') |
+ |
+ # CMAKE_SOURCE_DIR is where the CMakeLists.txt lives. |
+ # The cwd is the current build directory. |
+ output.write(' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') |
+ output.write(path_to_gyp) |
+ output.write('\n') |
+ |
+ output.write(' COMMENT ') |
+ if 'message' in rule: |
+ output.write(rule['message']) |
+ else: |
+ output.write(action_name) |
+ output.write('\n') |
+ |
+ output.write(' VERBATIM\n') |
+ output.write(')\n') |
+ |
+ UnsetVariable(output, 'RULE_INPUT_PATH') |
+ UnsetVariable(output, 'RULE_INPUT_DIRNAME') |
+ UnsetVariable(output, 'RULE_INPUT_NAME') |
+ UnsetVariable(output, 'RULE_INPUT_ROOT') |
+ UnsetVariable(output, 'RULE_INPUT_EXT') |
+ |
+ # add_custom_target |
+ output.write('add_custom_target(') |
+ output.write(rule_name) |
+ output.write(' DEPENDS\n') |
+ for out in var_outputs: |
+ output.write(' ') |
+ output.write(out) |
+ output.write('\n') |
+ output.write('SOURCES ') |
+ WriteVariable(output, inputs_name) |
+ output.write('\n') |
+ for rule_source in rule.get('rule_sources', []): |
+ output.write(' ') |
+ output.write(NormjoinPath(path_to_gyp, rule_source)) |
+ output.write('\n') |
+ output.write(')\n') |
+ |
+ extra_deps.append(rule_name) |
+ |
+ |
+def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output): |
+ """Write CMake for the 'copies' in the target. |
+ |
+ Args: |
+ target_name: the name of the CMake target being generated. |
+ actions: the Gyp 'actions' dict for this target. |
+ extra_deps: [<cmake_taget>] to append with generated targets. |
+ path_to_gyp: relative path from CMakeLists.txt being generated to |
+ the Gyp file in which the target being generated is defined. |
+ """ |
+ copy_name = target_name + '__copies' |
+ |
+ # CMake gets upset with custom targets with OUTPUT which specify no output. |
+ have_copies = any(copy['files'] for copy in copies) |
+ if not have_copies: |
+ output.write('add_custom_target(') |
+ output.write(copy_name) |
+ output.write(')\n') |
+ extra_deps.append(copy_name) |
+ return |
+ |
+ class Copy: |
+ def __init__(self, ext, command): |
+ self.cmake_inputs = [] |
+ self.cmake_outputs = [] |
+ self.gyp_inputs = [] |
+ self.gyp_outputs = [] |
+ self.ext = ext |
+ self.inputs_name = None |
+ self.outputs_name = None |
+ self.command = command |
+ |
+ file_copy = Copy('', 'copy') |
+ dir_copy = Copy('_dirs', 'copy_directory') |
+ |
+ for copy in copies: |
+ files = copy['files'] |
+ destination = copy['destination'] |
+ for src in files: |
+ path = os.path.normpath(src) |
+ basename = os.path.split(path)[1] |
+ dst = os.path.join(destination, basename) |
+ |
+ copy = file_copy if os.path.basename(src) else dir_copy |
+ |
+ copy.cmake_inputs.append(NormjoinPath(path_to_gyp, src)) |
+ copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst)) |
+ copy.gyp_inputs.append(src) |
+ copy.gyp_outputs.append(dst) |
+ |
+ for copy in (file_copy, dir_copy): |
+ if copy.cmake_inputs: |
+ copy.inputs_name = copy_name + '__input' + copy.ext |
+ SetVariableList(output, copy.inputs_name, copy.cmake_inputs) |
+ |
+ copy.outputs_name = copy_name + '__output' + copy.ext |
+ SetVariableList(output, copy.outputs_name, copy.cmake_outputs) |
+ |
+ # add_custom_command |
+ output.write('add_custom_command(\n') |
+ |
+ output.write('OUTPUT') |
+ for copy in (file_copy, dir_copy): |
+ if copy.outputs_name: |
+ WriteVariable(output, copy.outputs_name, ' ') |
+ output.write('\n') |
+ |
+ for copy in (file_copy, dir_copy): |
+ for src, dst in zip(copy.gyp_inputs, copy.gyp_outputs): |
+ # 'cmake -E copy src dst' will create the 'dst' directory if needed. |
+ output.write('COMMAND ${CMAKE_COMMAND} -E %s ' % copy.command) |
+ output.write(src) |
+ output.write(' ') |
+ output.write(dst) |
+ output.write("\n") |
+ |
+ output.write('DEPENDS') |
+ for copy in (file_copy, dir_copy): |
+ if copy.inputs_name: |
+ WriteVariable(output, copy.inputs_name, ' ') |
+ output.write('\n') |
+ |
+ output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/') |
+ output.write(path_to_gyp) |
+ output.write('\n') |
+ |
+ output.write('COMMENT Copying for ') |
+ output.write(target_name) |
+ output.write('\n') |
+ |
+ output.write('VERBATIM\n') |
+ output.write(')\n') |
+ |
+ # add_custom_target |
+ output.write('add_custom_target(') |
+ output.write(copy_name) |
+ output.write('\n DEPENDS') |
+ for copy in (file_copy, dir_copy): |
+ if copy.outputs_name: |
+ WriteVariable(output, copy.outputs_name, ' ') |
+ output.write('\n SOURCES') |
+ if file_copy.inputs_name: |
+ WriteVariable(output, file_copy.inputs_name, ' ') |
+ output.write('\n)\n') |
+ |
+ extra_deps.append(copy_name) |
+ |
+ |
+def CreateCMakeTargetBaseName(qualified_target): |
+ """This is the name we would like the target to have.""" |
+ _, gyp_target_name, gyp_target_toolset = ( |
+ gyp.common.ParseQualifiedTarget(qualified_target)) |
+ cmake_target_base_name = gyp_target_name |
+ if gyp_target_toolset and gyp_target_toolset != 'target': |
+ cmake_target_base_name += '_' + gyp_target_toolset |
+ return StringToCMakeTargetName(cmake_target_base_name) |
+ |
+ |
+def CreateCMakeTargetFullName(qualified_target): |
+ """An unambiguous name for the target.""" |
+ gyp_file, gyp_target_name, gyp_target_toolset = ( |
+ gyp.common.ParseQualifiedTarget(qualified_target)) |
+ cmake_target_full_name = gyp_file + ':' + gyp_target_name |
+ if gyp_target_toolset and gyp_target_toolset != 'target': |
+ cmake_target_full_name += '_' + gyp_target_toolset |
+ return StringToCMakeTargetName(cmake_target_full_name) |
+ |
+ |
+class CMakeNamer(object): |
+ """Converts Gyp target names into CMake target names. |
+ |
+ CMake requires that target names be globally unique. One way to ensure |
+ this is to fully qualify the names of the targets. Unfortunatly, this |
+ ends up with all targets looking like "chrome_chrome_gyp_chrome" instead |
+ of just "chrome". If this generator were only interested in building, it |
+ would be possible to fully qualify all target names, then create |
+ unqualified target names which depend on all qualified targets which |
+ should have had that name. This is more or less what the 'make' generator |
+ does with aliases. However, one goal of this generator is to create CMake |
+ files for use with IDEs, and fully qualified names are not as user |
+ friendly. |
+ |
+ Since target name collision is rare, we do the above only when required. |
+ |
+ Toolset variants are always qualified from the base, as this is required for |
+ building. However, it also makes sense for an IDE, as it is possible for |
+ defines to be different. |
+ """ |
+ def __init__(self, target_list): |
+ self.cmake_target_base_names_conficting = set() |
+ |
+ cmake_target_base_names_seen = set() |
+ for qualified_target in target_list: |
+ cmake_target_base_name = CreateCMakeTargetBaseName(qualified_target) |
+ |
+ if cmake_target_base_name not in cmake_target_base_names_seen: |
+ cmake_target_base_names_seen.add(cmake_target_base_name) |
+ else: |
+ self.cmake_target_base_names_conficting.add(cmake_target_base_name) |
+ |
+ def CreateCMakeTargetName(self, qualified_target): |
+ base_name = CreateCMakeTargetBaseName(qualified_target) |
+ if base_name in self.cmake_target_base_names_conficting: |
+ return CreateCMakeTargetFullName(qualified_target) |
+ return base_name |
+ |
+ |
+def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, |
+ options, generator_flags, all_qualified_targets, output): |
+ |
+ # The make generator does this always. |
+ # TODO: It would be nice to be able to tell CMake all dependencies. |
+ circular_libs = generator_flags.get('circular', True) |
+ |
+ if not generator_flags.get('standalone', False): |
+ output.write('\n#') |
+ output.write(qualified_target) |
+ output.write('\n') |
+ |
+ gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) |
+ rel_gyp_file = gyp.common.RelativePath(gyp_file, options.toplevel_dir) |
+ rel_gyp_dir = os.path.dirname(rel_gyp_file) |
+ |
+ # Relative path from build dir to top dir. |
+ build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir) |
+ # Relative path from build dir to gyp dir. |
+ build_to_gyp = os.path.join(build_to_top, rel_gyp_dir) |
+ |
+ path_from_cmakelists_to_gyp = build_to_gyp |
+ |
+ spec = target_dicts.get(qualified_target, {}) |
+ config = spec.get('configurations', {}).get(config_to_use, {}) |
+ |
+ target_name = spec.get('target_name', '<missing target name>') |
+ target_type = spec.get('type', '<missing target type>') |
+ target_toolset = spec.get('toolset') |
+ |
+ SetVariable(output, 'TARGET', target_name) |
+ SetVariable(output, 'TOOLSET', target_toolset) |
+ |
+ cmake_target_name = namer.CreateCMakeTargetName(qualified_target) |
+ |
+ extra_sources = [] |
+ extra_deps = [] |
+ |
+ # Actions must come first, since they can generate more OBJs for use below. |
+ if 'actions' in spec: |
+ WriteActions(cmake_target_name, spec['actions'], extra_sources, extra_deps, |
+ path_from_cmakelists_to_gyp, output) |
+ |
+ # Rules must be early like actions. |
+ if 'rules' in spec: |
+ WriteRules(cmake_target_name, spec['rules'], extra_sources, extra_deps, |
+ path_from_cmakelists_to_gyp, output) |
+ |
+ # Copies |
+ if 'copies' in spec: |
+ WriteCopies(cmake_target_name, spec['copies'], extra_deps, |
+ path_from_cmakelists_to_gyp, output) |
+ |
+ # Target and sources |
+ srcs = spec.get('sources', []) |
+ |
+ # Gyp separates the sheep from the goats based on file extensions. |
+ def partition(l, p): |
+ return reduce(lambda x, e: x[not p(e)].append(e) or x, l, ([], [])) |
+ compilable_srcs, other_srcs = partition(srcs, Compilable) |
+ |
+ # CMake gets upset when executable targets provide no sources. |
+ if target_type == 'executable' and not compilable_srcs and not extra_sources: |
+ print ('Executable %s has no complilable sources, treating as "none".' % |
+ target_name ) |
+ target_type = 'none' |
+ |
+ cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type) |
+ if cmake_target_type is None: |
+ print ('Target %s has unknown target type %s, skipping.' % |
+ ( target_name, target_type ) ) |
+ return |
+ |
+ other_srcs_name = None |
+ if other_srcs: |
+ other_srcs_name = cmake_target_name + '__other_srcs' |
+ SetVariableList(output, other_srcs_name, |
+ [NormjoinPath(path_from_cmakelists_to_gyp, src) for src in other_srcs]) |
+ |
+ # CMake is opposed to setting linker directories and considers the practice |
+ # of setting linker directories dangerous. Instead, it favors the use of |
+ # find_library and passing absolute paths to target_link_libraries. |
+ # However, CMake does provide the command link_directories, which adds |
+ # link directories to targets defined after it is called. |
+ # As a result, link_directories must come before the target definition. |
+ # CMake unfortunately has no means of removing entries from LINK_DIRECTORIES. |
+ library_dirs = config.get('library_dirs') |
+ if library_dirs is not None: |
+ output.write('link_directories(') |
+ for library_dir in library_dirs: |
+ output.write(' ') |
+ output.write(NormjoinPath(path_from_cmakelists_to_gyp, library_dir)) |
+ output.write('\n') |
+ output.write(')\n') |
+ |
+ output.write(cmake_target_type.command) |
+ output.write('(') |
+ output.write(cmake_target_name) |
+ |
+ if cmake_target_type.modifier is not None: |
+ output.write(' ') |
+ output.write(cmake_target_type.modifier) |
+ |
+ if other_srcs_name: |
+ WriteVariable(output, other_srcs_name, ' ') |
+ |
+ output.write('\n') |
+ |
+ for src in compilable_srcs: |
+ output.write(' ') |
+ output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ output.write('\n') |
+ for extra_source in extra_sources: |
+ output.write(' ') |
+ src, _ = extra_source |
+ output.write(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ output.write('\n') |
+ |
+ output.write(')\n') |
+ |
+ # Output name and location. |
+ if target_type != 'none': |
+ # Mark uncompiled sources as uncompiled. |
+ if other_srcs_name: |
+ output.write('set_source_files_properties(') |
+ WriteVariable(output, other_srcs_name, '') |
+ output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n') |
+ |
+ # Output directory |
+ target_output_directory = spec.get('product_dir') |
+ if target_output_directory is None: |
+ if target_type in ('executable', 'loadable_module'): |
+ target_output_directory = generator_default_variables['PRODUCT_DIR'] |
+ elif target_type in ('shared_library'): |
+ target_output_directory = '${builddir}/lib.${TOOLSET}' |
+ elif spec.get('standalone_static_library', False): |
+ target_output_directory = generator_default_variables['PRODUCT_DIR'] |
+ else: |
+ base_path = gyp.common.RelativePath(os.path.dirname(gyp_file), |
+ options.toplevel_dir) |
+ target_output_directory = '${obj}.${TOOLSET}' |
+ target_output_directory = ( |
+ os.path.join(target_output_directory, base_path)) |
+ |
+ cmake_target_output_directory = NormjoinPathForceCMakeSource( |
+ path_from_cmakelists_to_gyp, |
+ target_output_directory) |
+ SetTargetProperty(output, |
+ cmake_target_name, |
+ cmake_target_type.property_modifier + '_OUTPUT_DIRECTORY', |
+ cmake_target_output_directory) |
+ |
+ # Output name |
+ default_product_prefix = '' |
+ default_product_name = target_name |
+ default_product_ext = '' |
+ if target_type == 'static_library': |
+ static_library_prefix = generator_default_variables['STATIC_LIB_PREFIX'] |
+ default_product_name = RemovePrefix(default_product_name, |
+ static_library_prefix) |
+ default_product_prefix = static_library_prefix |
+ default_product_ext = generator_default_variables['STATIC_LIB_SUFFIX'] |
+ |
+ elif target_type in ('loadable_module', 'shared_library'): |
+ shared_library_prefix = generator_default_variables['SHARED_LIB_PREFIX'] |
+ default_product_name = RemovePrefix(default_product_name, |
+ shared_library_prefix) |
+ default_product_prefix = shared_library_prefix |
+ default_product_ext = generator_default_variables['SHARED_LIB_SUFFIX'] |
+ |
+ elif target_type != 'executable': |
+ print ('ERROR: What output file should be generated?', |
+ 'type', target_type, 'target', target_name) |
+ |
+ product_prefix = spec.get('product_prefix', default_product_prefix) |
+ product_name = spec.get('product_name', default_product_name) |
+ product_ext = spec.get('product_extension') |
+ if product_ext: |
+ product_ext = '.' + product_ext |
+ else: |
+ product_ext = default_product_ext |
+ |
+ SetTargetProperty(output, cmake_target_name, 'PREFIX', product_prefix) |
+ SetTargetProperty(output, cmake_target_name, |
+ cmake_target_type.property_modifier + '_OUTPUT_NAME', |
+ product_name) |
+ SetTargetProperty(output, cmake_target_name, 'SUFFIX', product_ext) |
+ |
+ # Make the output of this target referenceable as a source. |
+ cmake_target_output_basename = product_prefix + product_name + product_ext |
+ cmake_target_output = os.path.join(cmake_target_output_directory, |
+ cmake_target_output_basename) |
+ SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '') |
+ |
+ # Let CMake know if the 'all' target should depend on this target. |
+ exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets |
+ else 'FALSE') |
+ SetTargetProperty(output, cmake_target_name, |
+ 'EXCLUDE_FROM_ALL', exclude_from_all) |
+ for extra_target_name in extra_deps: |
+ SetTargetProperty(output, extra_target_name, |
+ 'EXCLUDE_FROM_ALL', exclude_from_all) |
+ |
+ # Includes |
+ includes = config.get('include_dirs') |
+ if includes: |
+ # This (target include directories) is what requires CMake 2.8.8 |
+ includes_name = cmake_target_name + '__include_dirs' |
+ SetVariableList(output, includes_name, |
+ [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include) |
+ for include in includes]) |
+ output.write('set_property(TARGET ') |
+ output.write(cmake_target_name) |
+ output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ') |
+ WriteVariable(output, includes_name, '') |
+ output.write(')\n') |
+ |
+ # Defines |
+ defines = config.get('defines') |
+ if defines is not None: |
+ SetTargetProperty(output, |
+ cmake_target_name, |
+ 'COMPILE_DEFINITIONS', |
+ defines, |
+ ';') |
+ |
+ # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493 |
+ # CMake currently does not have target C and CXX flags. |
+ # So, instead of doing... |
+ |
+ # cflags_c = config.get('cflags_c') |
+ # if cflags_c is not None: |
+ # SetTargetProperty(output, cmake_target_name, |
+ # 'C_COMPILE_FLAGS', cflags_c, ' ') |
+ |
+ # cflags_cc = config.get('cflags_cc') |
+ # if cflags_cc is not None: |
+ # SetTargetProperty(output, cmake_target_name, |
+ # 'CXX_COMPILE_FLAGS', cflags_cc, ' ') |
+ |
+ # Instead we must... |
+ s_sources = [] |
+ c_sources = [] |
+ cxx_sources = [] |
+ for src in srcs: |
+ _, ext = os.path.splitext(src) |
+ src_type = COMPILABLE_EXTENSIONS.get(ext, None) |
+ |
+ if src_type == 's': |
+ s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ |
+ if src_type == 'cc': |
+ c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ |
+ if src_type == 'cxx': |
+ cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ |
+ for extra_source in extra_sources: |
+ src, real_source = extra_source |
+ _, ext = os.path.splitext(real_source) |
+ src_type = COMPILABLE_EXTENSIONS.get(ext, None) |
+ |
+ if src_type == 's': |
+ s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ |
+ if src_type == 'cc': |
+ c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ |
+ if src_type == 'cxx': |
+ cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src)) |
+ |
+ cflags = config.get('cflags', []) |
+ cflags_c = config.get('cflags_c', []) |
+ cflags_cxx = config.get('cflags_cc', []) |
+ if c_sources and not (s_sources or cxx_sources): |
+ flags = [] |
+ flags.extend(cflags) |
+ flags.extend(cflags_c) |
+ SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') |
+ |
+ elif cxx_sources and not (s_sources or c_sources): |
+ flags = [] |
+ flags.extend(cflags) |
+ flags.extend(cflags_cxx) |
+ SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ') |
+ |
+ else: |
+ if s_sources and cflags: |
+ SetFilesProperty(output, s_sources, 'COMPILE_FLAGS', cflags, ' ') |
+ |
+ if c_sources and (cflags or cflags_c): |
+ flags = [] |
+ flags.extend(cflags) |
+ flags.extend(cflags_c) |
+ SetFilesProperty(output, c_sources, 'COMPILE_FLAGS', flags, ' ') |
+ |
+ if cxx_sources and (cflags or cflags_cxx): |
+ flags = [] |
+ flags.extend(cflags) |
+ flags.extend(cflags_cxx) |
+ SetFilesProperty(output, cxx_sources, 'COMPILE_FLAGS', flags, ' ') |
+ |
+ # Have assembly link as c if there are no other files |
+ if not c_sources and not cxx_sources and s_sources: |
+ SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) |
+ |
+ # Linker flags |
+ ldflags = config.get('ldflags') |
+ if ldflags is not None: |
+ SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ') |
+ |
+ # Note on Dependencies and Libraries: |
+ # CMake wants to handle link order, resolving the link line up front. |
+ # Gyp does not retain or enforce specifying enough information to do so. |
+ # So do as other gyp generators and use --start-group and --end-group. |
+ # Give CMake as little information as possible so that it doesn't mess it up. |
+ |
+ # Dependencies |
+ rawDeps = spec.get('dependencies', []) |
+ |
+ static_deps = [] |
+ shared_deps = [] |
+ other_deps = [] |
+ for rawDep in rawDeps: |
+ dep_cmake_name = namer.CreateCMakeTargetName(rawDep) |
+ dep_spec = target_dicts.get(rawDep, {}) |
+ dep_target_type = dep_spec.get('type', None) |
+ |
+ if dep_target_type == 'static_library': |
+ static_deps.append(dep_cmake_name) |
+ elif dep_target_type == 'shared_library': |
+ shared_deps.append(dep_cmake_name) |
+ else: |
+ other_deps.append(dep_cmake_name) |
+ |
+ # ensure all external dependencies are complete before internal dependencies |
+ # extra_deps currently only depend on their own deps, so otherwise run early |
+ if static_deps or shared_deps or other_deps: |
+ for extra_dep in extra_deps: |
+ output.write('add_dependencies(') |
+ output.write(extra_dep) |
+ output.write('\n') |
+ for deps in (static_deps, shared_deps, other_deps): |
+ for dep in gyp.common.uniquer(deps): |
+ output.write(' ') |
+ output.write(dep) |
+ output.write('\n') |
+ output.write(')\n') |
+ |
+ linkable = target_type in ('executable', 'loadable_module', 'shared_library') |
+ other_deps.extend(extra_deps) |
+ if other_deps or (not linkable and (static_deps or shared_deps)): |
+ output.write('add_dependencies(') |
+ output.write(cmake_target_name) |
+ output.write('\n') |
+ for dep in gyp.common.uniquer(other_deps): |
+ output.write(' ') |
+ output.write(dep) |
+ output.write('\n') |
+ if not linkable: |
+ for deps in (static_deps, shared_deps): |
+ for lib_dep in gyp.common.uniquer(deps): |
+ output.write(' ') |
+ output.write(lib_dep) |
+ output.write('\n') |
+ output.write(')\n') |
+ |
+ # Libraries |
+ if linkable: |
+ external_libs = [lib for lib in spec.get('libraries', []) if len(lib) > 0] |
+ if external_libs or static_deps or shared_deps: |
+ output.write('target_link_libraries(') |
+ output.write(cmake_target_name) |
+ output.write('\n') |
+ if static_deps: |
+ write_group = circular_libs and len(static_deps) > 1 |
+ if write_group: |
+ output.write('-Wl,--start-group\n') |
+ for dep in gyp.common.uniquer(static_deps): |
+ output.write(' ') |
+ output.write(dep) |
+ output.write('\n') |
+ if write_group: |
+ output.write('-Wl,--end-group\n') |
+ if shared_deps: |
+ for dep in gyp.common.uniquer(shared_deps): |
+ output.write(' ') |
+ output.write(dep) |
+ output.write('\n') |
+ if external_libs: |
+ for lib in gyp.common.uniquer(external_libs): |
+ output.write(' ') |
+ output.write(lib) |
+ output.write('\n') |
+ |
+ output.write(')\n') |
+ |
+ UnsetVariable(output, 'TOOLSET') |
+ UnsetVariable(output, 'TARGET') |
+ |
+ |
+def GenerateOutputForConfig(target_list, target_dicts, data, |
+ params, config_to_use): |
+ options = params['options'] |
+ generator_flags = params['generator_flags'] |
+ |
+ # generator_dir: relative path from pwd to where make puts build files. |
+ # Makes migrating from make to cmake easier, cmake doesn't put anything here. |
+ # Each Gyp configuration creates a different CMakeLists.txt file |
+ # to avoid incompatibilities between Gyp and CMake configurations. |
+ generator_dir = os.path.relpath(options.generator_output or '.') |
+ |
+ # output_dir: relative path from generator_dir to the build directory. |
+ output_dir = generator_flags.get('output_dir', 'out') |
+ |
+ # build_dir: relative path from source root to our output files. |
+ # e.g. "out/Debug" |
+ build_dir = os.path.normpath(os.path.join(generator_dir, |
+ output_dir, |
+ config_to_use)) |
+ |
+ toplevel_build = os.path.join(options.toplevel_dir, build_dir) |
+ |
+ output_file = os.path.join(toplevel_build, 'CMakeLists.txt') |
+ EnsureDirectoryExists(output_file) |
+ |
+ output = open(output_file, 'w') |
+ output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n') |
+ output.write('cmake_policy(VERSION 2.8.8)\n') |
+ |
+ _, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) |
+ output.write('project(') |
+ output.write(project_target) |
+ output.write(')\n') |
+ |
+ SetVariable(output, 'configuration', config_to_use) |
+ |
+ # The following appears to be as-yet undocumented. |
+ # http://public.kitware.com/Bug/view.php?id=8392 |
+ output.write('enable_language(ASM)\n') |
+ # ASM-ATT does not support .S files. |
+ # output.write('enable_language(ASM-ATT)\n') |
+ |
+ SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}') |
+ SetVariable(output, 'obj', '${builddir}/obj') |
+ output.write('\n') |
+ |
+ # TODO: Undocumented/unsupported (the CMake Java generator depends on it). |
+ # CMake by default names the object resulting from foo.c to be foo.c.o. |
+ # Gyp traditionally names the object resulting from foo.c foo.o. |
+ # This should be irrelevant, but some targets extract .o files from .a |
+ # and depend on the name of the extracted .o files. |
+ output.write('set(CMAKE_C_OUTPUT_EXTENSION_REPLACE 1)\n') |
+ output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n') |
+ output.write('\n') |
+ |
+ namer = CMakeNamer(target_list) |
+ |
+ # The list of targets upon which the 'all' target should depend. |
+ # CMake has it's own implicit 'all' target, one is not created explicitly. |
+ all_qualified_targets = set() |
+ for build_file in params['build_files']: |
+ for qualified_target in gyp.common.AllTargets(target_list, |
+ target_dicts, |
+ os.path.normpath(build_file)): |
+ all_qualified_targets.add(qualified_target) |
+ |
+ for qualified_target in target_list: |
+ WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use, |
+ options, generator_flags, all_qualified_targets, output) |
+ |
+ output.close() |
+ |
+ |
+def PerformBuild(data, configurations, params): |
+ options = params['options'] |
+ generator_flags = params['generator_flags'] |
+ |
+ # generator_dir: relative path from pwd to where make puts build files. |
+ # Makes migrating from make to cmake easier, cmake doesn't put anything here. |
+ generator_dir = os.path.relpath(options.generator_output or '.') |
+ |
+ # output_dir: relative path from generator_dir to the build directory. |
+ output_dir = generator_flags.get('output_dir', 'out') |
+ |
+ for config_name in configurations: |
+ # build_dir: relative path from source root to our output files. |
+ # e.g. "out/Debug" |
+ build_dir = os.path.normpath(os.path.join(generator_dir, |
+ output_dir, |
+ config_name)) |
+ arguments = ['cmake', '-G', 'Ninja'] |
+ print 'Generating [%s]: %s' % (config_name, arguments) |
+ subprocess.check_call(arguments, cwd=build_dir) |
+ |
+ arguments = ['ninja', '-C', build_dir] |
+ print 'Building [%s]: %s' % (config_name, arguments) |
+ subprocess.check_call(arguments) |
+ |
+ |
+def CallGenerateOutputForConfig(arglist): |
+ # Ignore the interrupt signal so that the parent process catches it and |
+ # kills all multiprocessing children. |
+ signal.signal(signal.SIGINT, signal.SIG_IGN) |
+ |
+ target_list, target_dicts, data, params, config_name = arglist |
+ GenerateOutputForConfig(target_list, target_dicts, data, params, config_name) |
+ |
+ |
+def GenerateOutput(target_list, target_dicts, data, params): |
+ user_config = params.get('generator_flags', {}).get('config', None) |
+ if user_config: |
+ GenerateOutputForConfig(target_list, target_dicts, data, |
+ params, user_config) |
+ else: |
+ config_names = target_dicts[target_list[0]]['configurations'].keys() |
+ if params['parallel']: |
+ try: |
+ pool = multiprocessing.Pool(len(config_names)) |
+ arglists = [] |
+ for config_name in config_names: |
+ arglists.append((target_list, target_dicts, data, |
+ params, config_name)) |
+ pool.map(CallGenerateOutputForConfig, arglists) |
+ except KeyboardInterrupt, e: |
+ pool.terminate() |
+ raise e |
+ else: |
+ for config_name in config_names: |
+ GenerateOutputForConfig(target_list, target_dicts, data, |
+ params, config_name) |