blob: a2b96291aa526aa5c93d1ecef687c70806872f53 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001# 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)