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