OLD | NEW |
---|---|
(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) | |
OLD | NEW |