Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: pylib/gyp/generator/cmake.py

Issue 12314014: CMake generator. (Closed) Base URL: http://gyp.googlecode.com/svn/trunk/
Patch Set: Address comments. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « gyptest.py ('k') | test/additional-targets/gyptest-additional.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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)
OLDNEW
« no previous file with comments | « gyptest.py ('k') | test/additional-targets/gyptest-additional.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698