| # 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_CURRENT_LIST_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_CURRENT_LIST_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 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, variable, property_name, values, sep): |
| """Given a set of source files, sets the given property on them.""" |
| output.write('set_source_files_properties(') |
| WriteVariable(output, variable) |
| output.write(' PROPERTIES ') |
| output.write(property_name) |
| output.write(' "') |
| for value in values: |
| output.write(CMakeStringEscape(value)) |
| output.write(sep) |
| output.write('")\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(object): |
| 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_CURRENT_LIST_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_CURRENT_LIST_DIR is where the CMakeLists.txt lives. |
| # The cwd is the current build directory. |
| output.write(' WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_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(object): |
| 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(NormjoinPathForceCMakeSource(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_CURRENT_LIST_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') |
| |
| 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 |
| |
| 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. |
| # A full separation is done here because of flag handing (see below). |
| s_sources = [] |
| c_sources = [] |
| cxx_sources = [] |
| linkable_sources = [] |
| other_sources = [] |
| for src in srcs: |
| _, ext = os.path.splitext(src) |
| src_type = COMPILABLE_EXTENSIONS.get(ext, None) |
| src_norm_path = NormjoinPath(path_from_cmakelists_to_gyp, src); |
| |
| if src_type == 's': |
| s_sources.append(src_norm_path) |
| elif src_type == 'cc': |
| c_sources.append(src_norm_path) |
| elif src_type == 'cxx': |
| cxx_sources.append(src_norm_path) |
| elif Linkable(ext): |
| linkable_sources.append(src_norm_path) |
| else: |
| other_sources.append(src_norm_path) |
| |
| 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(src) |
| elif src_type == 'cc': |
| c_sources.append(src) |
| elif src_type == 'cxx': |
| cxx_sources.append(src) |
| elif Linkable(ext): |
| linkable_sources.append(src) |
| else: |
| other_sources.append(src) |
| |
| s_sources_name = None |
| if s_sources: |
| s_sources_name = cmake_target_name + '__asm_srcs' |
| SetVariableList(output, s_sources_name, s_sources) |
| |
| c_sources_name = None |
| if c_sources: |
| c_sources_name = cmake_target_name + '__c_srcs' |
| SetVariableList(output, c_sources_name, c_sources) |
| |
| cxx_sources_name = None |
| if cxx_sources: |
| cxx_sources_name = cmake_target_name + '__cxx_srcs' |
| SetVariableList(output, cxx_sources_name, cxx_sources) |
| |
| linkable_sources_name = None |
| if linkable_sources: |
| linkable_sources_name = cmake_target_name + '__linkable_srcs' |
| SetVariableList(output, linkable_sources_name, linkable_sources) |
| |
| other_sources_name = None |
| if other_sources: |
| other_sources_name = cmake_target_name + '__other_srcs' |
| SetVariableList(output, other_sources_name, other_sources) |
| |
| # CMake gets upset when executable targets provide no sources. |
| # http://www.cmake.org/pipermail/cmake/2010-July/038461.html |
| dummy_sources_name = None |
| has_sources = (s_sources_name or |
| c_sources_name or |
| cxx_sources_name or |
| linkable_sources_name or |
| other_sources_name) |
| if target_type == 'executable' and not has_sources: |
| dummy_sources_name = cmake_target_name + '__dummy_srcs' |
| SetVariable(output, dummy_sources_name, |
| "${obj}.${TOOLSET}/${TARGET}/genc/dummy.c") |
| output.write('if(NOT EXISTS "') |
| WriteVariable(output, dummy_sources_name) |
| output.write('")\n') |
| output.write(' file(WRITE "') |
| WriteVariable(output, dummy_sources_name) |
| output.write('" "")\n') |
| output.write("endif()\n") |
| |
| |
| # 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 s_sources_name: |
| WriteVariable(output, s_sources_name, ' ') |
| if c_sources_name: |
| WriteVariable(output, c_sources_name, ' ') |
| if cxx_sources_name: |
| WriteVariable(output, cxx_sources_name, ' ') |
| if linkable_sources_name: |
| WriteVariable(output, linkable_sources_name, ' ') |
| if other_sources_name: |
| WriteVariable(output, other_sources_name, ' ') |
| if dummy_sources_name: |
| WriteVariable(output, dummy_sources_name, ' ') |
| |
| output.write(')\n') |
| |
| # 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) |
| |
| # Output name and location. |
| if target_type != 'none': |
| # Link as 'C' if there are no other files |
| if not c_sources and not cxx_sources: |
| SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C']) |
| |
| # Mark uncompiled sources as uncompiled. |
| if other_sources_name: |
| output.write('set_source_files_properties(') |
| WriteVariable(output, other_sources_name, '') |
| output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n') |
| |
| # Mark object sources as linkable. |
| if linkable_sources_name: |
| output.write('set_source_files_properties(') |
| WriteVariable(output, other_sources_name, '') |
| output.write(' PROPERTIES EXTERNAL_OBJECT "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 == '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'], '') |
| |
| # 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... |
| cflags = config.get('cflags', []) |
| cflags_c = config.get('cflags_c', []) |
| cflags_cxx = config.get('cflags_cc', []) |
| if (not cflags_c or not c_sources) and (not cflags_cxx or not cxx_sources): |
| SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', cflags, ' ') |
| |
| elif 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: |
| # TODO: This is broken, one cannot generally set properties on files, |
| # as other targets may require different properties on the same files. |
| if s_sources and cflags: |
| SetFilesProperty(output, s_sources_name, 'COMPILE_FLAGS', cflags, ' ') |
| |
| if c_sources and (cflags or cflags_c): |
| flags = [] |
| flags.extend(cflags) |
| flags.extend(cflags_c) |
| SetFilesProperty(output, c_sources_name, 'COMPILE_FLAGS', flags, ' ') |
| |
| if cxx_sources and (cflags or cflags_cxx): |
| flags = [] |
| flags.extend(cflags) |
| flags.extend(cflags_cxx) |
| SetFilesProperty(output, cxx_sources_name, 'COMPILE_FLAGS', flags, ' ') |
| |
| # 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') |
| gyp.common.EnsureDirExists(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') |
| |
| gyp_file, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1]) |
| output.write('project(') |
| output.write(project_target) |
| output.write(')\n') |
| |
| SetVariable(output, 'configuration', config_to_use) |
| |
| ar = None |
| cc = None |
| cxx = None |
| |
| make_global_settings = data[gyp_file].get('make_global_settings', []) |
| build_to_top = gyp.common.InvertRelativePath(build_dir, |
| options.toplevel_dir) |
| for key, value in make_global_settings: |
| if key == 'AR': |
| ar = os.path.join(build_to_top, value) |
| if key == 'CC': |
| cc = os.path.join(build_to_top, value) |
| if key == 'CXX': |
| cxx = os.path.join(build_to_top, value) |
| |
| ar = gyp.common.GetEnvironFallback(['AR_target', 'AR'], ar) |
| cc = gyp.common.GetEnvironFallback(['CC_target', 'CC'], cc) |
| cxx = gyp.common.GetEnvironFallback(['CXX_target', 'CXX'], cxx) |
| |
| if ar: |
| SetVariable(output, 'CMAKE_AR', ar) |
| if cc: |
| SetVariable(output, 'CMAKE_C_COMPILER', cc) |
| if cxx: |
| SetVariable(output, 'CMAKE_CXX_COMPILER', cxx) |
| |
| # 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') |
| |
| if cc: |
| SetVariable(output, 'CMAKE_ASM_COMPILER', cc) |
| |
| SetVariable(output, 'builddir', '${CMAKE_CURRENT_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') |
| |
| # Force ninja to use rsp files. Otherwise link and ar lines can get too long, |
| # resulting in 'Argument list too long' errors. |
| output.write('set(CMAKE_NINJA_FORCE_RESPONSE_FILE 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) |