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