Home | History | Annotate | Download | only in generator
      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.
      4 
      5 """cmake output module
      6 
      7 This module is under development and should be considered experimental.
      8 
      9 This module produces cmake (2.8.8+) input as its output. One CMakeLists.txt is
     10 created for each configuration.
     11 
     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.
     19 
     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.
     25 
     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
     28 CMakeLists.txt file.
     29 """
     30 
     31 import multiprocessing
     32 import os
     33 import signal
     34 import string
     35 import subprocess
     36 import gyp.common
     37 
     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}',
     56 }
     57 
     58 FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}')
     59 
     60 generator_supports_multiple_toolsets = True
     61 generator_wants_static_library_dependencies_adjusted = True
     62 
     63 COMPILABLE_EXTENSIONS = {
     64   '.c': 'cc',
     65   '.cc': 'cxx',
     66   '.cpp': 'cxx',
     67   '.cxx': 'cxx',
     68   '.s': 's', # cc
     69   '.S': 's', # cc
     70 }
     71 
     72 
     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
     76 
     77 
     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))
     81 
     82 
     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)
     86 
     87 
     88 def Linkable(filename):
     89   """Return true if the file is linkable (should be on the link line)."""
     90   return filename.endswith('.o')
     91 
     92 
     93 def NormjoinPathForceCMakeSource(base_path, rel_path):
     94   """Resolves rel_path against base_path and returns the result.
     95 
     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
     99   CMakeLists.txt.
    100   """
    101   if os.path.isabs(rel_path):
    102     return rel_path
    103   if any([rel_path.startswith(var) for var in FULL_PATH_VARS]):
    104     return rel_path
    105   # TODO: do we need to check base_path for absolute variables as well?
    106   return os.path.join('${CMAKE_SOURCE_DIR}',
    107                       os.path.normpath(os.path.join(base_path, rel_path)))
    108 
    109 
    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.
    115   """
    116   if rel_path.startswith('$') and not rel_path.startswith('${configuration}'):
    117     return rel_path
    118   return os.path.normpath(os.path.join(base_path, rel_path))
    119 
    120 
    121 def CMakeStringEscape(a):
    122   """Escapes the string 'a' for use inside a CMake string.
    123 
    124   This means escaping
    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
    128 
    129   The following do not need to be escaped
    130   '#' when the lexer is in string state, this does not start a comment
    131 
    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
    136   """
    137   return a.replace('\\', '\\\\').replace(';', '\\;').replace('"', '\\"')
    138 
    139 
    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)
    146   output.write(' "')
    147   for value in values:
    148     output.write(CMakeStringEscape(value))
    149     output.write(sep)
    150   output.write('")\n')
    151 
    152 
    153 def SetFilesProperty(output, source_names, property_name, values, sep):
    154   """Given a set of source files, sets the given property on them."""
    155   output.write('set_source_files_properties(\n')
    156   for source_name in source_names:
    157     output.write('  ')
    158     output.write(source_name)
    159     output.write('\n')
    160   output.write(' PROPERTIES\n  ')
    161   output.write(property_name)
    162   output.write(' "')
    163   for value in values:
    164     output.write(CMakeStringEscape(value))
    165     output.write(sep)
    166   output.write('"\n)\n')
    167 
    168 
    169 def SetTargetProperty(output, target_name, property_name, values, sep=''):
    170   """Given a target, sets the given property."""
    171   output.write('set_target_properties(')
    172   output.write(target_name)
    173   output.write(' PROPERTIES ')
    174   output.write(property_name)
    175   output.write(' "')
    176   for value in values:
    177     output.write(CMakeStringEscape(value))
    178     output.write(sep)
    179   output.write('")\n')
    180 
    181 
    182 def SetVariable(output, variable_name, value):
    183   """Sets a CMake variable."""
    184   output.write('set(')
    185   output.write(variable_name)
    186   output.write(' "')
    187   output.write(CMakeStringEscape(value))
    188   output.write('")\n')
    189 
    190 
    191 def SetVariableList(output, variable_name, values):
    192   """Sets a CMake variable to a list."""
    193   if not values:
    194     return SetVariable(output, variable_name, "")
    195   if len(values) == 1:
    196     return SetVariable(output, variable_name, values[0])
    197   output.write('list(APPEND ')
    198   output.write(variable_name)
    199   output.write('\n  "')
    200   output.write('"\n  "'.join([CMakeStringEscape(value) for value in values]))
    201   output.write('")\n')
    202 
    203 
    204 def UnsetVariable(output, variable_name):
    205   """Unsets a CMake variable."""
    206   output.write('unset(')
    207   output.write(variable_name)
    208   output.write(')\n')
    209 
    210 
    211 def WriteVariable(output, variable_name, prepend=None):
    212   if prepend:
    213     output.write(prepend)
    214   output.write('${')
    215   output.write(variable_name)
    216   output.write('}')
    217 
    218 
    219 class CMakeTargetType:
    220   def __init__(self, command, modifier, property_modifier):
    221     self.command = command
    222     self.modifier = modifier
    223     self.property_modifier = property_modifier
    224 
    225 
    226 cmake_target_type_from_gyp_target_type = {
    227   'executable': CMakeTargetType('add_executable', None, 'RUNTIME'),
    228   'static_library': CMakeTargetType('add_library', 'STATIC', 'ARCHIVE'),
    229   'shared_library': CMakeTargetType('add_library', 'SHARED', 'LIBRARY'),
    230   'loadable_module': CMakeTargetType('add_library', 'MODULE', 'LIBRARY'),
    231   'none': CMakeTargetType('add_custom_target', 'SOURCES', None),
    232 }
    233 
    234 
    235 def StringToCMakeTargetName(a):
    236   """Converts the given string 'a' to a valid CMake target name.
    237 
    238   All invalid characters are replaced by '_'.
    239   Invalid for cmake: ' ', '/', '(', ')'
    240   Invalid for make: ':'
    241   Invalid for unknown reasons but cause failures: '.'
    242   """
    243   return a.translate(string.maketrans(' /():.', '______'))
    244 
    245 
    246 def WriteActions(target_name, actions, extra_sources, extra_deps,
    247                  path_to_gyp, output):
    248   """Write CMake for the 'actions' in the target.
    249 
    250   Args:
    251     target_name: the name of the CMake target being generated.
    252     actions: the Gyp 'actions' dict for this target.
    253     extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
    254     extra_deps: [<cmake_taget>] to append with generated targets.
    255     path_to_gyp: relative path from CMakeLists.txt being generated to
    256         the Gyp file in which the target being generated is defined.
    257   """
    258   for action in actions:
    259     action_name = StringToCMakeTargetName(action['action_name'])
    260     action_target_name = '%s__%s' % (target_name, action_name)
    261 
    262     inputs = action['inputs']
    263     inputs_name = action_target_name + '__input'
    264     SetVariableList(output, inputs_name,
    265         [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs])
    266 
    267     outputs = action['outputs']
    268     cmake_outputs = [NormjoinPathForceCMakeSource(path_to_gyp, out)
    269                      for out in outputs]
    270     outputs_name = action_target_name + '__output'
    271     SetVariableList(output, outputs_name, cmake_outputs)
    272 
    273     # Build up a list of outputs.
    274     # Collect the output dirs we'll need.
    275     dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir)
    276 
    277     if int(action.get('process_outputs_as_sources', False)):
    278       extra_sources.extend(zip(cmake_outputs, outputs))
    279 
    280     # add_custom_command
    281     output.write('add_custom_command(OUTPUT ')
    282     WriteVariable(output, outputs_name)
    283     output.write('\n')
    284 
    285     if len(dirs) > 0:
    286       for directory in dirs:
    287         output.write('  COMMAND ${CMAKE_COMMAND} -E make_directory ')
    288         output.write(directory)
    289         output.write('\n')
    290 
    291     output.write('  COMMAND ')
    292     output.write(gyp.common.EncodePOSIXShellList(action['action']))
    293     output.write('\n')
    294 
    295     output.write('  DEPENDS ')
    296     WriteVariable(output, inputs_name)
    297     output.write('\n')
    298 
    299     output.write('  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/')
    300     output.write(path_to_gyp)
    301     output.write('\n')
    302 
    303     output.write('  COMMENT ')
    304     if 'message' in action:
    305       output.write(action['message'])
    306     else:
    307       output.write(action_target_name)
    308     output.write('\n')
    309 
    310     output.write('  VERBATIM\n')
    311     output.write(')\n')
    312 
    313     # add_custom_target
    314     output.write('add_custom_target(')
    315     output.write(action_target_name)
    316     output.write('\n  DEPENDS ')
    317     WriteVariable(output, outputs_name)
    318     output.write('\n  SOURCES ')
    319     WriteVariable(output, inputs_name)
    320     output.write('\n)\n')
    321 
    322     extra_deps.append(action_target_name)
    323 
    324 
    325 def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
    326   if rel_path.startswith(("${RULE_INPUT_PATH}","${RULE_INPUT_DIRNAME}")):
    327     if any([rule_source.startswith(var) for var in FULL_PATH_VARS]):
    328       return rel_path
    329   return NormjoinPathForceCMakeSource(base_path, rel_path)
    330 
    331 
    332 def WriteRules(target_name, rules, extra_sources, extra_deps,
    333                path_to_gyp, output):
    334   """Write CMake for the 'rules' in the target.
    335 
    336   Args:
    337     target_name: the name of the CMake target being generated.
    338     actions: the Gyp 'actions' dict for this target.
    339     extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
    340     extra_deps: [<cmake_taget>] to append with generated targets.
    341     path_to_gyp: relative path from CMakeLists.txt being generated to
    342         the Gyp file in which the target being generated is defined.
    343   """
    344   for rule in rules:
    345     rule_name = StringToCMakeTargetName(target_name + '__' + rule['rule_name'])
    346 
    347     inputs = rule.get('inputs', [])
    348     inputs_name = rule_name + '__input'
    349     SetVariableList(output, inputs_name,
    350         [NormjoinPathForceCMakeSource(path_to_gyp, dep) for dep in inputs])
    351     outputs = rule['outputs']
    352     var_outputs = []
    353 
    354     for count, rule_source in enumerate(rule.get('rule_sources', [])):
    355       action_name = rule_name + '_' + str(count)
    356 
    357       rule_source_dirname, rule_source_basename = os.path.split(rule_source)
    358       rule_source_root, rule_source_ext = os.path.splitext(rule_source_basename)
    359 
    360       SetVariable(output, 'RULE_INPUT_PATH', rule_source)
    361       SetVariable(output, 'RULE_INPUT_DIRNAME', rule_source_dirname)
    362       SetVariable(output, 'RULE_INPUT_NAME', rule_source_basename)
    363       SetVariable(output, 'RULE_INPUT_ROOT', rule_source_root)
    364       SetVariable(output, 'RULE_INPUT_EXT', rule_source_ext)
    365 
    366       # Build up a list of outputs.
    367       # Collect the output dirs we'll need.
    368       dirs = set(dir for dir in (os.path.dirname(o) for o in outputs) if dir)
    369 
    370       # Create variables for the output, as 'local' variable will be unset.
    371       these_outputs = []
    372       for output_index, out in enumerate(outputs):
    373         output_name = action_name + '_' + str(output_index)
    374         SetVariable(output, output_name,
    375                      NormjoinRulePathForceCMakeSource(path_to_gyp, out,
    376                                                       rule_source))
    377         if int(rule.get('process_outputs_as_sources', False)):
    378           extra_sources.append(('${' + output_name + '}', out))
    379         these_outputs.append('${' + output_name + '}')
    380         var_outputs.append('${' + output_name + '}')
    381 
    382       # add_custom_command
    383       output.write('add_custom_command(OUTPUT\n')
    384       for out in these_outputs:
    385         output.write('  ')
    386         output.write(out)
    387         output.write('\n')
    388 
    389       for directory in dirs:
    390         output.write('  COMMAND ${CMAKE_COMMAND} -E make_directory ')
    391         output.write(directory)
    392         output.write('\n')
    393 
    394       output.write('  COMMAND ')
    395       output.write(gyp.common.EncodePOSIXShellList(rule['action']))
    396       output.write('\n')
    397 
    398       output.write('  DEPENDS ')
    399       WriteVariable(output, inputs_name)
    400       output.write(' ')
    401       output.write(NormjoinPath(path_to_gyp, rule_source))
    402       output.write('\n')
    403 
    404       # CMAKE_SOURCE_DIR is where the CMakeLists.txt lives.
    405       # The cwd is the current build directory.
    406       output.write('  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/')
    407       output.write(path_to_gyp)
    408       output.write('\n')
    409 
    410       output.write('  COMMENT ')
    411       if 'message' in rule:
    412         output.write(rule['message'])
    413       else:
    414         output.write(action_name)
    415       output.write('\n')
    416 
    417       output.write('  VERBATIM\n')
    418       output.write(')\n')
    419 
    420       UnsetVariable(output, 'RULE_INPUT_PATH')
    421       UnsetVariable(output, 'RULE_INPUT_DIRNAME')
    422       UnsetVariable(output, 'RULE_INPUT_NAME')
    423       UnsetVariable(output, 'RULE_INPUT_ROOT')
    424       UnsetVariable(output, 'RULE_INPUT_EXT')
    425 
    426     # add_custom_target
    427     output.write('add_custom_target(')
    428     output.write(rule_name)
    429     output.write(' DEPENDS\n')
    430     for out in var_outputs:
    431       output.write('  ')
    432       output.write(out)
    433       output.write('\n')
    434     output.write('SOURCES ')
    435     WriteVariable(output, inputs_name)
    436     output.write('\n')
    437     for rule_source in rule.get('rule_sources', []):
    438       output.write('  ')
    439       output.write(NormjoinPath(path_to_gyp, rule_source))
    440       output.write('\n')
    441     output.write(')\n')
    442 
    443     extra_deps.append(rule_name)
    444 
    445 
    446 def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output):
    447   """Write CMake for the 'copies' in the target.
    448 
    449   Args:
    450     target_name: the name of the CMake target being generated.
    451     actions: the Gyp 'actions' dict for this target.
    452     extra_deps: [<cmake_taget>] to append with generated targets.
    453     path_to_gyp: relative path from CMakeLists.txt being generated to
    454         the Gyp file in which the target being generated is defined.
    455   """
    456   copy_name = target_name + '__copies'
    457 
    458   # CMake gets upset with custom targets with OUTPUT which specify no output.
    459   have_copies = any(copy['files'] for copy in copies)
    460   if not have_copies:
    461     output.write('add_custom_target(')
    462     output.write(copy_name)
    463     output.write(')\n')
    464     extra_deps.append(copy_name)
    465     return
    466 
    467   class Copy:
    468     def __init__(self, ext, command):
    469       self.cmake_inputs = []
    470       self.cmake_outputs = []
    471       self.gyp_inputs = []
    472       self.gyp_outputs = []
    473       self.ext = ext
    474       self.inputs_name = None
    475       self.outputs_name = None
    476       self.command = command
    477 
    478   file_copy = Copy('', 'copy')
    479   dir_copy = Copy('_dirs', 'copy_directory')
    480 
    481   for copy in copies:
    482     files = copy['files']
    483     destination = copy['destination']
    484     for src in files:
    485       path = os.path.normpath(src)
    486       basename = os.path.split(path)[1]
    487       dst = os.path.join(destination, basename)
    488 
    489       copy = file_copy if os.path.basename(src) else dir_copy
    490 
    491       copy.cmake_inputs.append(NormjoinPath(path_to_gyp, src))
    492       copy.cmake_outputs.append(NormjoinPathForceCMakeSource(path_to_gyp, dst))
    493       copy.gyp_inputs.append(src)
    494       copy.gyp_outputs.append(dst)
    495 
    496   for copy in (file_copy, dir_copy):
    497     if copy.cmake_inputs:
    498       copy.inputs_name = copy_name + '__input' + copy.ext
    499       SetVariableList(output, copy.inputs_name, copy.cmake_inputs)
    500 
    501       copy.outputs_name = copy_name + '__output' + copy.ext
    502       SetVariableList(output, copy.outputs_name, copy.cmake_outputs)
    503 
    504   # add_custom_command
    505   output.write('add_custom_command(\n')
    506 
    507   output.write('OUTPUT')
    508   for copy in (file_copy, dir_copy):
    509     if copy.outputs_name:
    510       WriteVariable(output, copy.outputs_name, ' ')
    511   output.write('\n')
    512 
    513   for copy in (file_copy, dir_copy):
    514     for src, dst in zip(copy.gyp_inputs, copy.gyp_outputs):
    515       # 'cmake -E copy src dst' will create the 'dst' directory if needed.
    516       output.write('COMMAND ${CMAKE_COMMAND} -E %s ' % copy.command)
    517       output.write(src)
    518       output.write(' ')
    519       output.write(dst)
    520       output.write("\n")
    521 
    522   output.write('DEPENDS')
    523   for copy in (file_copy, dir_copy):
    524     if copy.inputs_name:
    525       WriteVariable(output, copy.inputs_name, ' ')
    526   output.write('\n')
    527 
    528   output.write('WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/')
    529   output.write(path_to_gyp)
    530   output.write('\n')
    531 
    532   output.write('COMMENT Copying for ')
    533   output.write(target_name)
    534   output.write('\n')
    535 
    536   output.write('VERBATIM\n')
    537   output.write(')\n')
    538 
    539   # add_custom_target
    540   output.write('add_custom_target(')
    541   output.write(copy_name)
    542   output.write('\n  DEPENDS')
    543   for copy in (file_copy, dir_copy):
    544     if copy.outputs_name:
    545       WriteVariable(output, copy.outputs_name, ' ')
    546   output.write('\n  SOURCES')
    547   if file_copy.inputs_name:
    548     WriteVariable(output, file_copy.inputs_name, ' ')
    549   output.write('\n)\n')
    550 
    551   extra_deps.append(copy_name)
    552 
    553 
    554 def CreateCMakeTargetBaseName(qualified_target):
    555   """This is the name we would like the target to have."""
    556   _, gyp_target_name, gyp_target_toolset = (
    557       gyp.common.ParseQualifiedTarget(qualified_target))
    558   cmake_target_base_name = gyp_target_name
    559   if gyp_target_toolset and gyp_target_toolset != 'target':
    560     cmake_target_base_name += '_' + gyp_target_toolset
    561   return StringToCMakeTargetName(cmake_target_base_name)
    562 
    563 
    564 def CreateCMakeTargetFullName(qualified_target):
    565   """An unambiguous name for the target."""
    566   gyp_file, gyp_target_name, gyp_target_toolset = (
    567       gyp.common.ParseQualifiedTarget(qualified_target))
    568   cmake_target_full_name = gyp_file + ':' + gyp_target_name
    569   if gyp_target_toolset and gyp_target_toolset != 'target':
    570     cmake_target_full_name += '_' + gyp_target_toolset
    571   return StringToCMakeTargetName(cmake_target_full_name)
    572 
    573 
    574 class CMakeNamer(object):
    575   """Converts Gyp target names into CMake target names.
    576 
    577   CMake requires that target names be globally unique. One way to ensure
    578   this is to fully qualify the names of the targets. Unfortunatly, this
    579   ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
    580   of just "chrome". If this generator were only interested in building, it
    581   would be possible to fully qualify all target names, then create
    582   unqualified target names which depend on all qualified targets which
    583   should have had that name. This is more or less what the 'make' generator
    584   does with aliases. However, one goal of this generator is to create CMake
    585   files for use with IDEs, and fully qualified names are not as user
    586   friendly.
    587 
    588   Since target name collision is rare, we do the above only when required.
    589 
    590   Toolset variants are always qualified from the base, as this is required for
    591   building. However, it also makes sense for an IDE, as it is possible for
    592   defines to be different.
    593   """
    594   def __init__(self, target_list):
    595     self.cmake_target_base_names_conficting = set()
    596 
    597     cmake_target_base_names_seen = set()
    598     for qualified_target in target_list:
    599       cmake_target_base_name = CreateCMakeTargetBaseName(qualified_target)
    600 
    601       if cmake_target_base_name not in cmake_target_base_names_seen:
    602         cmake_target_base_names_seen.add(cmake_target_base_name)
    603       else:
    604         self.cmake_target_base_names_conficting.add(cmake_target_base_name)
    605 
    606   def CreateCMakeTargetName(self, qualified_target):
    607     base_name = CreateCMakeTargetBaseName(qualified_target)
    608     if base_name in self.cmake_target_base_names_conficting:
    609       return CreateCMakeTargetFullName(qualified_target)
    610     return base_name
    611 
    612 
    613 def WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use,
    614                 options, generator_flags, all_qualified_targets, output):
    615 
    616   # The make generator does this always.
    617   # TODO: It would be nice to be able to tell CMake all dependencies.
    618   circular_libs = generator_flags.get('circular', True)
    619 
    620   if not generator_flags.get('standalone', False):
    621     output.write('\n#')
    622     output.write(qualified_target)
    623     output.write('\n')
    624 
    625   gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
    626   rel_gyp_file = gyp.common.RelativePath(gyp_file, options.toplevel_dir)
    627   rel_gyp_dir = os.path.dirname(rel_gyp_file)
    628 
    629   # Relative path from build dir to top dir.
    630   build_to_top = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir)
    631   # Relative path from build dir to gyp dir.
    632   build_to_gyp = os.path.join(build_to_top, rel_gyp_dir)
    633 
    634   path_from_cmakelists_to_gyp = build_to_gyp
    635 
    636   spec = target_dicts.get(qualified_target, {})
    637   config = spec.get('configurations', {}).get(config_to_use, {})
    638 
    639   target_name = spec.get('target_name', '<missing target name>')
    640   target_type = spec.get('type', '<missing target type>')
    641   target_toolset = spec.get('toolset')
    642 
    643   SetVariable(output, 'TARGET', target_name)
    644   SetVariable(output, 'TOOLSET', target_toolset)
    645 
    646   cmake_target_name = namer.CreateCMakeTargetName(qualified_target)
    647 
    648   extra_sources = []
    649   extra_deps = []
    650 
    651   # Actions must come first, since they can generate more OBJs for use below.
    652   if 'actions' in spec:
    653     WriteActions(cmake_target_name, spec['actions'], extra_sources, extra_deps,
    654                  path_from_cmakelists_to_gyp, output)
    655 
    656   # Rules must be early like actions.
    657   if 'rules' in spec:
    658     WriteRules(cmake_target_name, spec['rules'], extra_sources, extra_deps,
    659                path_from_cmakelists_to_gyp, output)
    660 
    661   # Copies
    662   if 'copies' in spec:
    663     WriteCopies(cmake_target_name, spec['copies'], extra_deps,
    664                 path_from_cmakelists_to_gyp, output)
    665 
    666   # Target and sources
    667   srcs = spec.get('sources', [])
    668 
    669   # Gyp separates the sheep from the goats based on file extensions.
    670   def partition(l, p):
    671     return reduce(lambda x, e: x[not p(e)].append(e) or x, l, ([], []))
    672   compilable_srcs, other_srcs = partition(srcs, Compilable)
    673 
    674   # CMake gets upset when executable targets provide no sources.
    675   if target_type == 'executable' and not compilable_srcs and not extra_sources:
    676     print ('Executable %s has no complilable sources, treating as "none".' %
    677                        target_name                                         )
    678     target_type = 'none'
    679 
    680   cmake_target_type = cmake_target_type_from_gyp_target_type.get(target_type)
    681   if cmake_target_type is None:
    682     print ('Target %s has unknown target type %s, skipping.' %
    683           (        target_name,               target_type  ) )
    684     return
    685 
    686   other_srcs_name = None
    687   if other_srcs:
    688     other_srcs_name = cmake_target_name + '__other_srcs'
    689     SetVariableList(output, other_srcs_name,
    690         [NormjoinPath(path_from_cmakelists_to_gyp, src) for src in other_srcs])
    691 
    692   # CMake is opposed to setting linker directories and considers the practice
    693   # of setting linker directories dangerous. Instead, it favors the use of
    694   # find_library and passing absolute paths to target_link_libraries.
    695   # However, CMake does provide the command link_directories, which adds
    696   # link directories to targets defined after it is called.
    697   # As a result, link_directories must come before the target definition.
    698   # CMake unfortunately has no means of removing entries from LINK_DIRECTORIES.
    699   library_dirs = config.get('library_dirs')
    700   if library_dirs is not None:
    701     output.write('link_directories(')
    702     for library_dir in library_dirs:
    703       output.write(' ')
    704       output.write(NormjoinPath(path_from_cmakelists_to_gyp, library_dir))
    705       output.write('\n')
    706     output.write(')\n')
    707 
    708   output.write(cmake_target_type.command)
    709   output.write('(')
    710   output.write(cmake_target_name)
    711 
    712   if cmake_target_type.modifier is not None:
    713     output.write(' ')
    714     output.write(cmake_target_type.modifier)
    715 
    716   if other_srcs_name:
    717     WriteVariable(output, other_srcs_name, ' ')
    718 
    719   output.write('\n')
    720 
    721   for src in compilable_srcs:
    722     output.write('  ')
    723     output.write(NormjoinPath(path_from_cmakelists_to_gyp, src))
    724     output.write('\n')
    725   for extra_source in extra_sources:
    726     output.write('  ')
    727     src, _ = extra_source
    728     output.write(NormjoinPath(path_from_cmakelists_to_gyp, src))
    729     output.write('\n')
    730 
    731   output.write(')\n')
    732 
    733   # Output name and location.
    734   if target_type != 'none':
    735     # Mark uncompiled sources as uncompiled.
    736     if other_srcs_name:
    737       output.write('set_source_files_properties(')
    738       WriteVariable(output, other_srcs_name, '')
    739       output.write(' PROPERTIES HEADER_FILE_ONLY "TRUE")\n')
    740 
    741     # Output directory
    742     target_output_directory = spec.get('product_dir')
    743     if target_output_directory is None:
    744       if target_type in ('executable', 'loadable_module'):
    745         target_output_directory = generator_default_variables['PRODUCT_DIR']
    746       elif target_type in ('shared_library'):
    747         target_output_directory = '${builddir}/lib.${TOOLSET}'
    748       elif spec.get('standalone_static_library', False):
    749         target_output_directory = generator_default_variables['PRODUCT_DIR']
    750       else:
    751         base_path = gyp.common.RelativePath(os.path.dirname(gyp_file),
    752                                             options.toplevel_dir)
    753         target_output_directory = '${obj}.${TOOLSET}'
    754         target_output_directory = (
    755             os.path.join(target_output_directory, base_path))
    756 
    757     cmake_target_output_directory = NormjoinPathForceCMakeSource(
    758                                         path_from_cmakelists_to_gyp,
    759                                         target_output_directory)
    760     SetTargetProperty(output,
    761         cmake_target_name,
    762         cmake_target_type.property_modifier + '_OUTPUT_DIRECTORY',
    763         cmake_target_output_directory)
    764 
    765     # Output name
    766     default_product_prefix = ''
    767     default_product_name = target_name
    768     default_product_ext = ''
    769     if target_type == 'static_library':
    770       static_library_prefix = generator_default_variables['STATIC_LIB_PREFIX']
    771       default_product_name = RemovePrefix(default_product_name,
    772                                           static_library_prefix)
    773       default_product_prefix = static_library_prefix
    774       default_product_ext = generator_default_variables['STATIC_LIB_SUFFIX']
    775 
    776     elif target_type in ('loadable_module', 'shared_library'):
    777       shared_library_prefix = generator_default_variables['SHARED_LIB_PREFIX']
    778       default_product_name = RemovePrefix(default_product_name,
    779                                           shared_library_prefix)
    780       default_product_prefix = shared_library_prefix
    781       default_product_ext = generator_default_variables['SHARED_LIB_SUFFIX']
    782 
    783     elif target_type != 'executable':
    784       print ('ERROR: What output file should be generated?',
    785               'type', target_type, 'target', target_name)
    786 
    787     product_prefix = spec.get('product_prefix', default_product_prefix)
    788     product_name = spec.get('product_name', default_product_name)
    789     product_ext = spec.get('product_extension')
    790     if product_ext:
    791       product_ext = '.' + product_ext
    792     else:
    793       product_ext = default_product_ext
    794 
    795     SetTargetProperty(output, cmake_target_name, 'PREFIX', product_prefix)
    796     SetTargetProperty(output, cmake_target_name,
    797                         cmake_target_type.property_modifier + '_OUTPUT_NAME',
    798                         product_name)
    799     SetTargetProperty(output, cmake_target_name, 'SUFFIX', product_ext)
    800 
    801     # Make the output of this target referenceable as a source.
    802     cmake_target_output_basename = product_prefix + product_name + product_ext
    803     cmake_target_output = os.path.join(cmake_target_output_directory,
    804                                        cmake_target_output_basename)
    805     SetFileProperty(output, cmake_target_output, 'GENERATED', ['TRUE'], '')
    806 
    807   # Let CMake know if the 'all' target should depend on this target.
    808   exclude_from_all = ('TRUE' if qualified_target not in all_qualified_targets
    809                              else 'FALSE')
    810   SetTargetProperty(output, cmake_target_name,
    811                       'EXCLUDE_FROM_ALL', exclude_from_all)
    812   for extra_target_name in extra_deps:
    813     SetTargetProperty(output, extra_target_name,
    814                         'EXCLUDE_FROM_ALL', exclude_from_all)
    815 
    816   # Includes
    817   includes = config.get('include_dirs')
    818   if includes:
    819     # This (target include directories) is what requires CMake 2.8.8
    820     includes_name = cmake_target_name + '__include_dirs'
    821     SetVariableList(output, includes_name,
    822         [NormjoinPathForceCMakeSource(path_from_cmakelists_to_gyp, include)
    823          for include in includes])
    824     output.write('set_property(TARGET ')
    825     output.write(cmake_target_name)
    826     output.write(' APPEND PROPERTY INCLUDE_DIRECTORIES ')
    827     WriteVariable(output, includes_name, '')
    828     output.write(')\n')
    829 
    830   # Defines
    831   defines = config.get('defines')
    832   if defines is not None:
    833     SetTargetProperty(output,
    834                         cmake_target_name,
    835                         'COMPILE_DEFINITIONS',
    836                         defines,
    837                         ';')
    838 
    839   # Compile Flags - http://www.cmake.org/Bug/view.php?id=6493
    840   # CMake currently does not have target C and CXX flags.
    841   # So, instead of doing...
    842 
    843   # cflags_c = config.get('cflags_c')
    844   # if cflags_c is not None:
    845   #   SetTargetProperty(output, cmake_target_name,
    846   #                       'C_COMPILE_FLAGS', cflags_c, ' ')
    847 
    848   # cflags_cc = config.get('cflags_cc')
    849   # if cflags_cc is not None:
    850   #   SetTargetProperty(output, cmake_target_name,
    851   #                       'CXX_COMPILE_FLAGS', cflags_cc, ' ')
    852 
    853   # Instead we must...
    854   s_sources = []
    855   c_sources = []
    856   cxx_sources = []
    857   for src in srcs:
    858     _, ext = os.path.splitext(src)
    859     src_type = COMPILABLE_EXTENSIONS.get(ext, None)
    860 
    861     if src_type == 's':
    862       s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src))
    863 
    864     if src_type == 'cc':
    865       c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src))
    866 
    867     if src_type == 'cxx':
    868       cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src))
    869 
    870   for extra_source in extra_sources:
    871     src, real_source = extra_source
    872     _, ext = os.path.splitext(real_source)
    873     src_type = COMPILABLE_EXTENSIONS.get(ext, None)
    874 
    875     if src_type == 's':
    876       s_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src))
    877 
    878     if src_type == 'cc':
    879       c_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src))
    880 
    881     if src_type == 'cxx':
    882       cxx_sources.append(NormjoinPath(path_from_cmakelists_to_gyp, src))
    883 
    884   cflags = config.get('cflags', [])
    885   cflags_c = config.get('cflags_c', [])
    886   cflags_cxx = config.get('cflags_cc', [])
    887   if c_sources and not (s_sources or cxx_sources):
    888     flags = []
    889     flags.extend(cflags)
    890     flags.extend(cflags_c)
    891     SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ')
    892 
    893   elif cxx_sources and not (s_sources or c_sources):
    894     flags = []
    895     flags.extend(cflags)
    896     flags.extend(cflags_cxx)
    897     SetTargetProperty(output, cmake_target_name, 'COMPILE_FLAGS', flags, ' ')
    898 
    899   else:
    900     if s_sources and cflags:
    901       SetFilesProperty(output, s_sources, 'COMPILE_FLAGS', cflags, ' ')
    902 
    903     if c_sources and (cflags or cflags_c):
    904       flags = []
    905       flags.extend(cflags)
    906       flags.extend(cflags_c)
    907       SetFilesProperty(output, c_sources, 'COMPILE_FLAGS', flags, ' ')
    908 
    909     if cxx_sources and (cflags or cflags_cxx):
    910       flags = []
    911       flags.extend(cflags)
    912       flags.extend(cflags_cxx)
    913       SetFilesProperty(output, cxx_sources, 'COMPILE_FLAGS', flags, ' ')
    914 
    915   # Have assembly link as c if there are no other files
    916   if not c_sources and not cxx_sources and s_sources:
    917     SetTargetProperty(output, cmake_target_name, 'LINKER_LANGUAGE', ['C'])
    918 
    919   # Linker flags
    920   ldflags = config.get('ldflags')
    921   if ldflags is not None:
    922     SetTargetProperty(output, cmake_target_name, 'LINK_FLAGS', ldflags, ' ')
    923 
    924   # Note on Dependencies and Libraries:
    925   # CMake wants to handle link order, resolving the link line up front.
    926   # Gyp does not retain or enforce specifying enough information to do so.
    927   # So do as other gyp generators and use --start-group and --end-group.
    928   # Give CMake as little information as possible so that it doesn't mess it up.
    929 
    930   # Dependencies
    931   rawDeps = spec.get('dependencies', [])
    932 
    933   static_deps = []
    934   shared_deps = []
    935   other_deps = []
    936   for rawDep in rawDeps:
    937     dep_cmake_name = namer.CreateCMakeTargetName(rawDep)
    938     dep_spec = target_dicts.get(rawDep, {})
    939     dep_target_type = dep_spec.get('type', None)
    940 
    941     if dep_target_type == 'static_library':
    942       static_deps.append(dep_cmake_name)
    943     elif dep_target_type ==  'shared_library':
    944       shared_deps.append(dep_cmake_name)
    945     else:
    946       other_deps.append(dep_cmake_name)
    947 
    948   # ensure all external dependencies are complete before internal dependencies
    949   # extra_deps currently only depend on their own deps, so otherwise run early
    950   if static_deps or shared_deps or other_deps:
    951     for extra_dep in extra_deps:
    952       output.write('add_dependencies(')
    953       output.write(extra_dep)
    954       output.write('\n')
    955       for deps in (static_deps, shared_deps, other_deps):
    956         for dep in gyp.common.uniquer(deps):
    957           output.write('  ')
    958           output.write(dep)
    959           output.write('\n')
    960       output.write(')\n')
    961 
    962   linkable = target_type in ('executable', 'loadable_module', 'shared_library')
    963   other_deps.extend(extra_deps)
    964   if other_deps or (not linkable and (static_deps or shared_deps)):
    965     output.write('add_dependencies(')
    966     output.write(cmake_target_name)
    967     output.write('\n')
    968     for dep in gyp.common.uniquer(other_deps):
    969       output.write('  ')
    970       output.write(dep)
    971       output.write('\n')
    972     if not linkable:
    973       for deps in (static_deps, shared_deps):
    974         for lib_dep in gyp.common.uniquer(deps):
    975           output.write('  ')
    976           output.write(lib_dep)
    977           output.write('\n')
    978     output.write(')\n')
    979 
    980   # Libraries
    981   if linkable:
    982     external_libs = [lib for lib in spec.get('libraries', []) if len(lib) > 0]
    983     if external_libs or static_deps or shared_deps:
    984       output.write('target_link_libraries(')
    985       output.write(cmake_target_name)
    986       output.write('\n')
    987       if static_deps:
    988         write_group = circular_libs and len(static_deps) > 1
    989         if write_group:
    990           output.write('-Wl,--start-group\n')
    991         for dep in gyp.common.uniquer(static_deps):
    992           output.write('  ')
    993           output.write(dep)
    994           output.write('\n')
    995         if write_group:
    996           output.write('-Wl,--end-group\n')
    997       if shared_deps:
    998         for dep in gyp.common.uniquer(shared_deps):
    999           output.write('  ')
   1000           output.write(dep)
   1001           output.write('\n')
   1002       if external_libs:
   1003         for lib in gyp.common.uniquer(external_libs):
   1004           output.write('  ')
   1005           output.write(lib)
   1006           output.write('\n')
   1007 
   1008       output.write(')\n')
   1009 
   1010   UnsetVariable(output, 'TOOLSET')
   1011   UnsetVariable(output, 'TARGET')
   1012 
   1013 
   1014 def GenerateOutputForConfig(target_list, target_dicts, data,
   1015                             params, config_to_use):
   1016   options = params['options']
   1017   generator_flags = params['generator_flags']
   1018 
   1019   # generator_dir: relative path from pwd to where make puts build files.
   1020   # Makes migrating from make to cmake easier, cmake doesn't put anything here.
   1021   # Each Gyp configuration creates a different CMakeLists.txt file
   1022   # to avoid incompatibilities between Gyp and CMake configurations.
   1023   generator_dir = os.path.relpath(options.generator_output or '.')
   1024 
   1025   # output_dir: relative path from generator_dir to the build directory.
   1026   output_dir = generator_flags.get('output_dir', 'out')
   1027 
   1028   # build_dir: relative path from source root to our output files.
   1029   # e.g. "out/Debug"
   1030   build_dir = os.path.normpath(os.path.join(generator_dir,
   1031                                             output_dir,
   1032                                             config_to_use))
   1033 
   1034   toplevel_build = os.path.join(options.toplevel_dir, build_dir)
   1035 
   1036   output_file = os.path.join(toplevel_build, 'CMakeLists.txt')
   1037   gyp.common.EnsureDirExists(output_file)
   1038 
   1039   output = open(output_file, 'w')
   1040   output.write('cmake_minimum_required(VERSION 2.8.8 FATAL_ERROR)\n')
   1041   output.write('cmake_policy(VERSION 2.8.8)\n')
   1042 
   1043   _, project_target, _ = gyp.common.ParseQualifiedTarget(target_list[-1])
   1044   output.write('project(')
   1045   output.write(project_target)
   1046   output.write(')\n')
   1047 
   1048   SetVariable(output, 'configuration', config_to_use)
   1049 
   1050   # The following appears to be as-yet undocumented.
   1051   # http://public.kitware.com/Bug/view.php?id=8392
   1052   output.write('enable_language(ASM)\n')
   1053   # ASM-ATT does not support .S files.
   1054   # output.write('enable_language(ASM-ATT)\n')
   1055 
   1056   SetVariable(output, 'builddir', '${CMAKE_BINARY_DIR}')
   1057   SetVariable(output, 'obj', '${builddir}/obj')
   1058   output.write('\n')
   1059 
   1060   # TODO: Undocumented/unsupported (the CMake Java generator depends on it).
   1061   # CMake by default names the object resulting from foo.c to be foo.c.o.
   1062   # Gyp traditionally names the object resulting from foo.c foo.o.
   1063   # This should be irrelevant, but some targets extract .o files from .a
   1064   # and depend on the name of the extracted .o files.
   1065   output.write('set(CMAKE_C_OUTPUT_EXTENSION_REPLACE 1)\n')
   1066   output.write('set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1)\n')
   1067   output.write('\n')
   1068 
   1069   namer = CMakeNamer(target_list)
   1070 
   1071   # The list of targets upon which the 'all' target should depend.
   1072   # CMake has it's own implicit 'all' target, one is not created explicitly.
   1073   all_qualified_targets = set()
   1074   for build_file in params['build_files']:
   1075     for qualified_target in gyp.common.AllTargets(target_list,
   1076                                                   target_dicts,
   1077                                                   os.path.normpath(build_file)):
   1078       all_qualified_targets.add(qualified_target)
   1079 
   1080   for qualified_target in target_list:
   1081     WriteTarget(namer, qualified_target, target_dicts, build_dir, config_to_use,
   1082                 options, generator_flags, all_qualified_targets, output)
   1083 
   1084   output.close()
   1085 
   1086 
   1087 def PerformBuild(data, configurations, params):
   1088   options = params['options']
   1089   generator_flags = params['generator_flags']
   1090 
   1091   # generator_dir: relative path from pwd to where make puts build files.
   1092   # Makes migrating from make to cmake easier, cmake doesn't put anything here.
   1093   generator_dir = os.path.relpath(options.generator_output or '.')
   1094 
   1095   # output_dir: relative path from generator_dir to the build directory.
   1096   output_dir = generator_flags.get('output_dir', 'out')
   1097 
   1098   for config_name in configurations:
   1099     # build_dir: relative path from source root to our output files.
   1100     # e.g. "out/Debug"
   1101     build_dir = os.path.normpath(os.path.join(generator_dir,
   1102                                               output_dir,
   1103                                               config_name))
   1104     arguments = ['cmake', '-G', 'Ninja']
   1105     print 'Generating [%s]: %s' % (config_name, arguments)
   1106     subprocess.check_call(arguments, cwd=build_dir)
   1107 
   1108     arguments = ['ninja', '-C', build_dir]
   1109     print 'Building [%s]: %s' % (config_name, arguments)
   1110     subprocess.check_call(arguments)
   1111 
   1112 
   1113 def CallGenerateOutputForConfig(arglist):
   1114   # Ignore the interrupt signal so that the parent process catches it and
   1115   # kills all multiprocessing children.
   1116   signal.signal(signal.SIGINT, signal.SIG_IGN)
   1117 
   1118   target_list, target_dicts, data, params, config_name = arglist
   1119   GenerateOutputForConfig(target_list, target_dicts, data, params, config_name)
   1120 
   1121 
   1122 def GenerateOutput(target_list, target_dicts, data, params):
   1123   user_config = params.get('generator_flags', {}).get('config', None)
   1124   if user_config:
   1125     GenerateOutputForConfig(target_list, target_dicts, data,
   1126                             params, user_config)
   1127   else:
   1128     config_names = target_dicts[target_list[0]]['configurations'].keys()
   1129     if params['parallel']:
   1130       try:
   1131         pool = multiprocessing.Pool(len(config_names))
   1132         arglists = []
   1133         for config_name in config_names:
   1134           arglists.append((target_list, target_dicts, data,
   1135                            params, config_name))
   1136           pool.map(CallGenerateOutputForConfig, arglists)
   1137       except KeyboardInterrupt, e:
   1138         pool.terminate()
   1139         raise e
   1140     else:
   1141       for config_name in config_names:
   1142         GenerateOutputForConfig(target_list, target_dicts, data,
   1143                                 params, config_name)
   1144