• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7This module is under development and should be considered experimental.
8
9This module produces cmake (2.8.8+) input as its output. One CMakeLists.txt is
10created for each configuration.
11
12This module's original purpose was to support editing in IDEs like KDevelop
13which use CMake for project management. It is also possible to use CMake to
14generate projects for other IDEs such as eclipse cdt and code::blocks. QtCreator
15will convert the CMakeLists.txt to a code::blocks cbp for the editor to read,
16but build using CMake. As a result QtCreator editor is unaware of compiler
17defines. The generated CMakeLists.txt can also be used to build on Linux. There
18is currently no support for building on platforms other than Linux.
19
20The generated CMakeLists.txt should properly compile all projects. However,
21there is a mismatch between gyp and cmake with regard to linking. All attempts
22are made to work around this, but CMake sometimes sees -Wl,--start-group as a
23library and incorrectly repeats it. As a result the output of this generator
24should not be relied on for building.
25
26When using with kdevelop, use version 4.4+. Previous versions of kdevelop will
27not be able to find the header file directories described in the generated
28CMakeLists.txt file.
29"""
30
31import multiprocessing
32import os
33import signal
34import string
35import subprocess
36import gyp.common
37
38generator_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
58FULL_PATH_VARS = ('${CMAKE_SOURCE_DIR}', '${builddir}', '${obj}')
59
60generator_supports_multiple_toolsets = True
61generator_wants_static_library_dependencies_adjusted = True
62
63COMPILABLE_EXTENSIONS = {
64  '.c': 'cc',
65  '.cc': 'cxx',
66  '.cpp': 'cxx',
67  '.cxx': 'cxx',
68  '.s': 's', # cc
69  '.S': 's', # cc
70}
71
72
73def 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
78def 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
83def 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
88def Linkable(filename):
89  """Return true if the file is linkable (should be on the link line)."""
90  return filename.endswith('.o')
91
92
93def 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
110def 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
121def 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
140def 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
153def 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
169def 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
182def 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
191def 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
204def UnsetVariable(output, variable_name):
205  """Unsets a CMake variable."""
206  output.write('unset(')
207  output.write(variable_name)
208  output.write(')\n')
209
210
211def 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
219class 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
226cmake_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
235def 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
246def 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
325def 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
332def 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
446def 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
554def 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
564def 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
574class 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
613def 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
1014def 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
1087def 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
1113def 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
1122def 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