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

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

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

Powered by Google App Engine
This is Rietveld 408576698