OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 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 import gyp | |
6 import gyp.common | |
7 import gyp.SCons as SCons | |
8 import os.path | |
9 import pprint | |
10 import re | |
11 import subprocess | |
12 | |
13 | |
14 # TODO: remove when we delete the last WriteList() call in this module | |
15 WriteList = SCons.WriteList | |
16 | |
17 | |
18 generator_default_variables = { | |
19 'EXECUTABLE_PREFIX': '', | |
20 'EXECUTABLE_SUFFIX': '', | |
21 'STATIC_LIB_PREFIX': '${LIBPREFIX}', | |
22 'SHARED_LIB_PREFIX': '${SHLIBPREFIX}', | |
23 'STATIC_LIB_SUFFIX': '${LIBSUFFIX}', | |
24 'SHARED_LIB_SUFFIX': '${SHLIBSUFFIX}', | |
25 'INTERMEDIATE_DIR': '${INTERMEDIATE_DIR}', | |
26 'SHARED_INTERMEDIATE_DIR': '${SHARED_INTERMEDIATE_DIR}', | |
27 'OS': 'linux', | |
28 'PRODUCT_DIR': '$TOP_BUILDDIR', | |
29 'SHARED_LIB_DIR': '$LIB_DIR', | |
30 'LIB_DIR': '$LIB_DIR', | |
31 'RULE_INPUT_ROOT': '${SOURCE.filebase}', | |
32 'RULE_INPUT_DIRNAME': '${SOURCE.dir}', | |
33 'RULE_INPUT_EXT': '${SOURCE.suffix}', | |
34 'RULE_INPUT_NAME': '${SOURCE.file}', | |
35 'RULE_INPUT_PATH': '${SOURCE.abspath}', | |
36 'CONFIGURATION_NAME': '${CONFIG_NAME}', | |
37 } | |
38 | |
39 # Tell GYP how to process the input for us. | |
40 generator_handles_variants = True | |
41 generator_wants_absolute_build_file_paths = True | |
42 | |
43 | |
44 def FixPath(path, prefix): | |
45 if not os.path.isabs(path) and not path[0] == '$': | |
46 path = prefix + path | |
47 return path | |
48 | |
49 | |
50 header = """\ | |
51 # This file is generated; do not edit. | |
52 """ | |
53 | |
54 | |
55 _alias_template = """ | |
56 if GetOption('verbose'): | |
57 _action = Action([%(action)s]) | |
58 else: | |
59 _action = Action([%(action)s], %(message)s) | |
60 _outputs = env.Alias( | |
61 ['_%(target_name)s_action'], | |
62 %(inputs)s, | |
63 _action | |
64 ) | |
65 env.AlwaysBuild(_outputs) | |
66 """ | |
67 | |
68 _run_as_template = """ | |
69 if GetOption('verbose'): | |
70 _action = Action([%(action)s]) | |
71 else: | |
72 _action = Action([%(action)s], %(message)s) | |
73 """ | |
74 | |
75 _run_as_template_suffix = """ | |
76 _run_as_target = env.Alias('run_%(target_name)s', target_files, _action) | |
77 env.Requires(_run_as_target, [ | |
78 Alias('%(target_name)s'), | |
79 ]) | |
80 env.AlwaysBuild(_run_as_target) | |
81 """ | |
82 | |
83 _command_template = """ | |
84 if GetOption('verbose'): | |
85 _action = Action([%(action)s]) | |
86 else: | |
87 _action = Action([%(action)s], %(message)s) | |
88 _outputs = env.Command( | |
89 %(outputs)s, | |
90 %(inputs)s, | |
91 _action | |
92 ) | |
93 """ | |
94 | |
95 # This is copied from the default SCons action, updated to handle symlinks. | |
96 _copy_action_template = """ | |
97 import shutil | |
98 import SCons.Action | |
99 | |
100 def _copy_files_or_dirs_or_symlinks(dest, src): | |
101 SCons.Node.FS.invalidate_node_memos(dest) | |
102 if SCons.Util.is_List(src) and os.path.isdir(dest): | |
103 for file in src: | |
104 shutil.copy2(file, dest) | |
105 return 0 | |
106 elif os.path.islink(src): | |
107 linkto = os.readlink(src) | |
108 os.symlink(linkto, dest) | |
109 return 0 | |
110 elif os.path.isfile(src): | |
111 return shutil.copy2(src, dest) | |
112 else: | |
113 return shutil.copytree(src, dest, 1) | |
114 | |
115 def _copy_files_or_dirs_or_symlinks_str(dest, src): | |
116 return 'Copying %s to %s ...' % (src, dest) | |
117 | |
118 GYPCopy = SCons.Action.ActionFactory(_copy_files_or_dirs_or_symlinks, | |
119 _copy_files_or_dirs_or_symlinks_str, | |
120 convert=str) | |
121 """ | |
122 | |
123 _rule_template = """ | |
124 %(name)s_additional_inputs = %(inputs)s | |
125 %(name)s_outputs = %(outputs)s | |
126 def %(name)s_emitter(target, source, env): | |
127 return (%(name)s_outputs, source + %(name)s_additional_inputs) | |
128 if GetOption('verbose'): | |
129 %(name)s_action = Action([%(action)s]) | |
130 else: | |
131 %(name)s_action = Action([%(action)s], %(message)s) | |
132 env['BUILDERS']['%(name)s'] = Builder(action=%(name)s_action, | |
133 emitter=%(name)s_emitter) | |
134 | |
135 _outputs = [] | |
136 _processed_input_files = [] | |
137 for infile in input_files: | |
138 if (type(infile) == type('') | |
139 and not os.path.isabs(infile) | |
140 and not infile[0] == '$'): | |
141 infile = %(src_dir)r + infile | |
142 if str(infile).endswith('.%(extension)s'): | |
143 _generated = env.%(name)s(infile) | |
144 env.Precious(_generated) | |
145 _outputs.append(_generated) | |
146 %(process_outputs_as_sources_line)s | |
147 else: | |
148 _processed_input_files.append(infile) | |
149 prerequisites.extend(_outputs) | |
150 input_files = _processed_input_files | |
151 """ | |
152 | |
153 _spawn_hack = """ | |
154 import re | |
155 import SCons.Platform.posix | |
156 needs_shell = re.compile('["\\'><!^&]') | |
157 def gyp_spawn(sh, escape, cmd, args, env): | |
158 def strip_scons_quotes(arg): | |
159 if arg[0] == '"' and arg[-1] == '"': | |
160 return arg[1:-1] | |
161 return arg | |
162 stripped_args = [strip_scons_quotes(a) for a in args] | |
163 if needs_shell.search(' '.join(stripped_args)): | |
164 return SCons.Platform.posix.exec_spawnvpe([sh, '-c', ' '.join(args)], env) | |
165 else: | |
166 return SCons.Platform.posix.exec_spawnvpe(stripped_args, env) | |
167 """ | |
168 | |
169 | |
170 def EscapeShellArgument(s): | |
171 """Quotes an argument so that it will be interpreted literally by a POSIX | |
172 shell. Taken from | |
173 http://stackoverflow.com/questions/35817/whats-the-best-way-to-escape-ossys
tem-calls-in-python | |
174 """ | |
175 return "'" + s.replace("'", "'\\''") + "'" | |
176 | |
177 | |
178 def InvertNaiveSConsQuoting(s): | |
179 """SCons tries to "help" with quoting by naively putting double-quotes around | |
180 command-line arguments containing space or tab, which is broken for all | |
181 but trivial cases, so we undo it. (See quote_spaces() in Subst.py)""" | |
182 if ' ' in s or '\t' in s: | |
183 # Then SCons will put double-quotes around this, so add our own quotes | |
184 # to close its quotes at the beginning and end. | |
185 s = '"' + s + '"' | |
186 return s | |
187 | |
188 | |
189 def EscapeSConsVariableExpansion(s): | |
190 """SCons has its own variable expansion syntax using $. We must escape it for | |
191 strings to be interpreted literally. For some reason this requires four | |
192 dollar signs, not two, even without the shell involved.""" | |
193 return s.replace('$', '$$$$') | |
194 | |
195 | |
196 def EscapeCppDefine(s): | |
197 """Escapes a CPP define so that it will reach the compiler unaltered.""" | |
198 s = EscapeShellArgument(s) | |
199 s = InvertNaiveSConsQuoting(s) | |
200 s = EscapeSConsVariableExpansion(s) | |
201 return s | |
202 | |
203 | |
204 def GenerateConfig(fp, config, indent='', src_dir=''): | |
205 """ | |
206 Generates SCons dictionary items for a gyp configuration. | |
207 | |
208 This provides the main translation between the (lower-case) gyp settings | |
209 keywords and the (upper-case) SCons construction variables. | |
210 """ | |
211 var_mapping = { | |
212 'ASFLAGS' : 'asflags', | |
213 'CCFLAGS' : 'cflags', | |
214 'CFLAGS' : 'cflags_c', | |
215 'CXXFLAGS' : 'cflags_cc', | |
216 'CPPDEFINES' : 'defines', | |
217 'CPPPATH' : 'include_dirs', | |
218 # Add the ldflags value to $LINKFLAGS, but not $SHLINKFLAGS. | |
219 # SCons defines $SHLINKFLAGS to incorporate $LINKFLAGS, so | |
220 # listing both here would case 'ldflags' to get appended to | |
221 # both, and then have it show up twice on the command line. | |
222 'LINKFLAGS' : 'ldflags', | |
223 } | |
224 postamble='\n%s],\n' % indent | |
225 for scons_var in sorted(var_mapping.keys()): | |
226 gyp_var = var_mapping[scons_var] | |
227 value = config.get(gyp_var) | |
228 if value: | |
229 if gyp_var in ('defines',): | |
230 value = [EscapeCppDefine(v) for v in value] | |
231 if gyp_var in ('include_dirs',): | |
232 if src_dir and not src_dir.endswith('/'): | |
233 src_dir += '/' | |
234 result = [] | |
235 for v in value: | |
236 v = FixPath(v, src_dir) | |
237 # Force SCons to evaluate the CPPPATH directories at | |
238 # SConscript-read time, so delayed evaluation of $SRC_DIR | |
239 # doesn't point it to the --generator-output= directory. | |
240 result.append('env.Dir(%r)' % v) | |
241 value = result | |
242 else: | |
243 value = map(repr, value) | |
244 WriteList(fp, | |
245 value, | |
246 prefix=indent, | |
247 preamble='%s%s = [\n ' % (indent, scons_var), | |
248 postamble=postamble) | |
249 | |
250 | |
251 def GenerateSConscript(output_filename, spec, build_file, build_file_data): | |
252 """ | |
253 Generates a SConscript file for a specific target. | |
254 | |
255 This generates a SConscript file suitable for building any or all of | |
256 the target's configurations. | |
257 | |
258 A SConscript file may be called multiple times to generate targets for | |
259 multiple configurations. Consequently, it needs to be ready to build | |
260 the target for any requested configuration, and therefore contains | |
261 information about the settings for all configurations (generated into | |
262 the SConscript file at gyp configuration time) as well as logic for | |
263 selecting (at SCons build time) the specific configuration being built. | |
264 | |
265 The general outline of a generated SConscript file is: | |
266 | |
267 -- Header | |
268 | |
269 -- Import 'env'. This contains a $CONFIG_NAME construction | |
270 variable that specifies what configuration to build | |
271 (e.g. Debug, Release). | |
272 | |
273 -- Configurations. This is a dictionary with settings for | |
274 the different configurations (Debug, Release) under which this | |
275 target can be built. The values in the dictionary are themselves | |
276 dictionaries specifying what construction variables should added | |
277 to the local copy of the imported construction environment | |
278 (Append), should be removed (FilterOut), and should outright | |
279 replace the imported values (Replace). | |
280 | |
281 -- Clone the imported construction environment and update | |
282 with the proper configuration settings. | |
283 | |
284 -- Initialize the lists of the targets' input files and prerequisites. | |
285 | |
286 -- Target-specific actions and rules. These come after the | |
287 input file and prerequisite initializations because the | |
288 outputs of the actions and rules may affect the input file | |
289 list (process_outputs_as_sources) and get added to the list of | |
290 prerequisites (so that they're guaranteed to be executed before | |
291 building the target). | |
292 | |
293 -- Call the Builder for the target itself. | |
294 | |
295 -- Arrange for any copies to be made into installation directories. | |
296 | |
297 -- Set up the {name} Alias (phony Node) for the target as the | |
298 primary handle for building all of the target's pieces. | |
299 | |
300 -- Use env.Require() to make sure the prerequisites (explicitly | |
301 specified, but also including the actions and rules) are built | |
302 before the target itself. | |
303 | |
304 -- Return the {name} Alias to the calling SConstruct file | |
305 so it can be added to the list of default targets. | |
306 """ | |
307 scons_target = SCons.Target(spec) | |
308 | |
309 gyp_dir = os.path.dirname(output_filename) | |
310 if not gyp_dir: | |
311 gyp_dir = '.' | |
312 gyp_dir = os.path.abspath(gyp_dir) | |
313 | |
314 output_dir = os.path.dirname(output_filename) | |
315 src_dir = build_file_data['_DEPTH'] | |
316 src_dir_rel = gyp.common.RelativePath(src_dir, output_dir) | |
317 subdir = gyp.common.RelativePath(os.path.dirname(build_file), src_dir) | |
318 src_subdir = '$SRC_DIR/' + subdir | |
319 src_subdir_ = src_subdir + '/' | |
320 | |
321 component_name = os.path.splitext(os.path.basename(build_file))[0] | |
322 target_name = spec['target_name'] | |
323 | |
324 if not os.path.exists(gyp_dir): | |
325 os.makedirs(gyp_dir) | |
326 fp = open(output_filename, 'w') | |
327 fp.write(header) | |
328 | |
329 fp.write('\nimport os\n') | |
330 fp.write('\nImport("env")\n') | |
331 | |
332 # | |
333 fp.write('\n') | |
334 fp.write('env = env.Clone(COMPONENT_NAME=%s,\n' % repr(component_name)) | |
335 fp.write(' TARGET_NAME=%s)\n' % repr(target_name)) | |
336 | |
337 # | |
338 for config in spec['configurations'].itervalues(): | |
339 if config.get('scons_line_length'): | |
340 fp.write(_spawn_hack) | |
341 break | |
342 | |
343 # | |
344 indent = ' ' * 12 | |
345 fp.write('\n') | |
346 fp.write('configurations = {\n') | |
347 for config_name, config in spec['configurations'].iteritems(): | |
348 fp.write(' \'%s\' : {\n' % config_name) | |
349 | |
350 fp.write(' \'Append\' : dict(\n') | |
351 GenerateConfig(fp, config, indent, src_subdir) | |
352 libraries = spec.get('libraries') | |
353 if libraries: | |
354 WriteList(fp, | |
355 map(repr, libraries), | |
356 prefix=indent, | |
357 preamble='%sLIBS = [\n ' % indent, | |
358 postamble='\n%s],\n' % indent) | |
359 fp.write(' ),\n') | |
360 | |
361 fp.write(' \'FilterOut\' : dict(\n' ) | |
362 for key, var in config.get('scons_remove', {}).iteritems(): | |
363 fp.write(' %s = %s,\n' % (key, repr(var))) | |
364 fp.write(' ),\n') | |
365 | |
366 fp.write(' \'Replace\' : dict(\n' ) | |
367 scons_settings = config.get('scons_variable_settings', {}) | |
368 for key in sorted(scons_settings.keys()): | |
369 val = pprint.pformat(scons_settings[key]) | |
370 fp.write(' %s = %s,\n' % (key, val)) | |
371 if 'c++' in spec.get('link_languages', []): | |
372 fp.write(' %s = %s,\n' % ('LINK', repr('$CXX'))) | |
373 if config.get('scons_line_length'): | |
374 fp.write(' SPAWN = gyp_spawn,\n') | |
375 fp.write(' ),\n') | |
376 | |
377 fp.write(' \'ImportExternal\' : [\n' ) | |
378 for var in config.get('scons_import_variables', []): | |
379 fp.write(' %s,\n' % repr(var)) | |
380 fp.write(' ],\n') | |
381 | |
382 fp.write(' \'PropagateExternal\' : [\n' ) | |
383 for var in config.get('scons_propagate_variables', []): | |
384 fp.write(' %s,\n' % repr(var)) | |
385 fp.write(' ],\n') | |
386 | |
387 fp.write(' },\n') | |
388 fp.write('}\n') | |
389 | |
390 fp.write('\n' | |
391 'config = configurations[env[\'CONFIG_NAME\']]\n' | |
392 'env.Append(**config[\'Append\'])\n' | |
393 'env.FilterOut(**config[\'FilterOut\'])\n' | |
394 'env.Replace(**config[\'Replace\'])\n') | |
395 | |
396 fp.write('\n' | |
397 '# Scons forces -fPIC for SHCCFLAGS on some platforms.\n' | |
398 '# Disable that so we can control it from cflags in gyp.\n' | |
399 '# Note that Scons itself is inconsistent with its -fPIC\n' | |
400 '# setting. SHCCFLAGS forces -fPIC, and SHCFLAGS does not.\n' | |
401 '# This will make SHCCFLAGS consistent with SHCFLAGS.\n' | |
402 'env[\'SHCCFLAGS\'] = [\'$CCFLAGS\']\n') | |
403 | |
404 fp.write('\n' | |
405 'for _var in config[\'ImportExternal\']:\n' | |
406 ' if _var in ARGUMENTS:\n' | |
407 ' env[_var] = ARGUMENTS[_var]\n' | |
408 ' elif _var in os.environ:\n' | |
409 ' env[_var] = os.environ[_var]\n' | |
410 'for _var in config[\'PropagateExternal\']:\n' | |
411 ' if _var in ARGUMENTS:\n' | |
412 ' env[_var] = ARGUMENTS[_var]\n' | |
413 ' elif _var in os.environ:\n' | |
414 ' env[\'ENV\'][_var] = os.environ[_var]\n') | |
415 | |
416 fp.write('\n' | |
417 "env['ENV']['LD_LIBRARY_PATH'] = env.subst('$LIB_DIR')\n") | |
418 | |
419 # | |
420 #fp.write("\nif env.has_key('CPPPATH'):\n") | |
421 #fp.write(" env['CPPPATH'] = map(env.Dir, env['CPPPATH'])\n") | |
422 | |
423 variants = spec.get('variants', {}) | |
424 for setting in sorted(variants.keys()): | |
425 if_fmt = 'if ARGUMENTS.get(%s) not in (None, \'0\'):\n' | |
426 fp.write('\n') | |
427 fp.write(if_fmt % repr(setting.upper())) | |
428 fp.write(' env.AppendUnique(\n') | |
429 GenerateConfig(fp, variants[setting], indent, src_subdir) | |
430 fp.write(' )\n') | |
431 | |
432 # | |
433 scons_target.write_input_files(fp) | |
434 | |
435 fp.write('\n') | |
436 fp.write('target_files = []\n') | |
437 prerequisites = spec.get('scons_prerequisites', []) | |
438 fp.write('prerequisites = %s\n' % pprint.pformat(prerequisites)) | |
439 | |
440 actions = spec.get('actions', []) | |
441 for action in actions: | |
442 a = ['cd', src_subdir, '&&'] + action['action'] | |
443 message = action.get('message') | |
444 if message: | |
445 message = repr(message) | |
446 inputs = [FixPath(f, src_subdir_) for f in action.get('inputs', [])] | |
447 outputs = [FixPath(f, src_subdir_) for f in action.get('outputs', [])] | |
448 if outputs: | |
449 template = _command_template | |
450 else: | |
451 template = _alias_template | |
452 fp.write(template % { | |
453 'inputs' : pprint.pformat(inputs), | |
454 'outputs' : pprint.pformat(outputs), | |
455 'action' : pprint.pformat(a), | |
456 'message' : message, | |
457 'target_name': target_name, | |
458 }) | |
459 if int(action.get('process_outputs_as_sources', 0)): | |
460 fp.write('input_files.extend(_outputs)\n') | |
461 fp.write('prerequisites.extend(_outputs)\n') | |
462 fp.write('target_files.extend(_outputs)\n') | |
463 | |
464 rules = spec.get('rules', []) | |
465 for rule in rules: | |
466 name = re.sub('[^a-zA-Z0-9_]', '_', rule['rule_name']) | |
467 message = rule.get('message') | |
468 if message: | |
469 message = repr(message) | |
470 if int(rule.get('process_outputs_as_sources', 0)): | |
471 poas_line = '_processed_input_files.extend(_generated)' | |
472 else: | |
473 poas_line = '_processed_input_files.append(infile)' | |
474 inputs = [FixPath(f, src_subdir_) for f in rule.get('inputs', [])] | |
475 outputs = [FixPath(f, src_subdir_) for f in rule.get('outputs', [])] | |
476 # Skip a rule with no action and no inputs. | |
477 if 'action' not in rule and not rule.get('rule_sources', []): | |
478 continue | |
479 a = ['cd', src_subdir, '&&'] + rule['action'] | |
480 fp.write(_rule_template % { | |
481 'inputs' : pprint.pformat(inputs), | |
482 'outputs' : pprint.pformat(outputs), | |
483 'action' : pprint.pformat(a), | |
484 'extension' : rule['extension'], | |
485 'name' : name, | |
486 'message' : message, | |
487 'process_outputs_as_sources_line' : poas_line, | |
488 'src_dir' : src_subdir_, | |
489 }) | |
490 | |
491 scons_target.write_target(fp, src_subdir) | |
492 | |
493 copies = spec.get('copies', []) | |
494 if copies: | |
495 fp.write(_copy_action_template) | |
496 for copy in copies: | |
497 destdir = None | |
498 files = None | |
499 try: | |
500 destdir = copy['destination'] | |
501 except KeyError, e: | |
502 gyp.common.ExceptionAppend( | |
503 e, | |
504 "Required 'destination' key missing for 'copies' in %s." % build_file) | |
505 raise | |
506 try: | |
507 files = copy['files'] | |
508 except KeyError, e: | |
509 gyp.common.ExceptionAppend( | |
510 e, "Required 'files' key missing for 'copies' in %s." % build_file) | |
511 raise | |
512 if not files: | |
513 # TODO: should probably add a (suppressible) warning; | |
514 # a null file list may be unintentional. | |
515 continue | |
516 if not destdir: | |
517 raise Exception( | |
518 "Required 'destination' key is empty for 'copies' in %s." % build_file) | |
519 | |
520 fmt = ('\n' | |
521 '_outputs = env.Command(%s,\n' | |
522 ' %s,\n' | |
523 ' GYPCopy(\'$TARGET\', \'$SOURCE\'))\n') | |
524 for f in copy['files']: | |
525 # Remove trailing separators so basename() acts like Unix basename and | |
526 # always returns the last element, whether a file or dir. Without this, | |
527 # only the contents, not the directory itself, are copied (and nothing | |
528 # might be copied if dest already exists, since scons thinks nothing needs | |
529 # to be done). | |
530 dest = os.path.join(destdir, os.path.basename(f.rstrip(os.sep))) | |
531 f = FixPath(f, src_subdir_) | |
532 dest = FixPath(dest, src_subdir_) | |
533 fp.write(fmt % (repr(dest), repr(f))) | |
534 fp.write('target_files.extend(_outputs)\n') | |
535 | |
536 run_as = spec.get('run_as') | |
537 if run_as: | |
538 action = run_as.get('action', []) | |
539 working_directory = run_as.get('working_directory') | |
540 if not working_directory: | |
541 working_directory = gyp_dir | |
542 else: | |
543 if not os.path.isabs(working_directory): | |
544 working_directory = os.path.normpath(os.path.join(gyp_dir, | |
545 working_directory)) | |
546 if run_as.get('environment'): | |
547 for (key, val) in run_as.get('environment').iteritems(): | |
548 action = ['%s="%s"' % (key, val)] + action | |
549 action = ['cd', '"%s"' % working_directory, '&&'] + action | |
550 fp.write(_run_as_template % { | |
551 'action' : pprint.pformat(action), | |
552 'message' : run_as.get('message', ''), | |
553 }) | |
554 | |
555 fmt = "\ngyp_target = env.Alias('%s', target_files)\n" | |
556 fp.write(fmt % target_name) | |
557 | |
558 dependencies = spec.get('scons_dependencies', []) | |
559 if dependencies: | |
560 WriteList(fp, dependencies, preamble='dependencies = [\n ', | |
561 postamble='\n]\n') | |
562 fp.write('env.Requires(target_files, dependencies)\n') | |
563 fp.write('env.Requires(gyp_target, dependencies)\n') | |
564 fp.write('for prerequisite in prerequisites:\n') | |
565 fp.write(' env.Requires(prerequisite, dependencies)\n') | |
566 fp.write('env.Requires(gyp_target, prerequisites)\n') | |
567 | |
568 if run_as: | |
569 fp.write(_run_as_template_suffix % { | |
570 'target_name': target_name, | |
571 }) | |
572 | |
573 fp.write('Return("gyp_target")\n') | |
574 | |
575 fp.close() | |
576 | |
577 | |
578 ############################################################################# | |
579 # TEMPLATE BEGIN | |
580 | |
581 _wrapper_template = """\ | |
582 | |
583 __doc__ = ''' | |
584 Wrapper configuration for building this entire "solution," | |
585 including all the specific targets in various *.scons files. | |
586 ''' | |
587 | |
588 import os | |
589 import sys | |
590 | |
591 import SCons.Environment | |
592 import SCons.Util | |
593 | |
594 def GetProcessorCount(): | |
595 ''' | |
596 Detects the number of CPUs on the system. Adapted form: | |
597 http://codeliberates.blogspot.com/2008/05/detecting-cpuscores-in-python.html | |
598 ''' | |
599 # Linux, Unix and Mac OS X: | |
600 if hasattr(os, 'sysconf'): | |
601 if os.sysconf_names.has_key('SC_NPROCESSORS_ONLN'): | |
602 # Linux and Unix or Mac OS X with python >= 2.5: | |
603 return os.sysconf('SC_NPROCESSORS_ONLN') | |
604 else: # Mac OS X with Python < 2.5: | |
605 return int(os.popen2("sysctl -n hw.ncpu")[1].read()) | |
606 # Windows: | |
607 if os.environ.has_key('NUMBER_OF_PROCESSORS'): | |
608 return max(int(os.environ.get('NUMBER_OF_PROCESSORS', '1')), 1) | |
609 return 1 # Default | |
610 | |
611 # Support PROGRESS= to show progress in different ways. | |
612 p = ARGUMENTS.get('PROGRESS') | |
613 if p == 'spinner': | |
614 Progress(['/\\r', '|\\r', '\\\\\\r', '-\\r'], | |
615 interval=5, | |
616 file=open('/dev/tty', 'w')) | |
617 elif p == 'name': | |
618 Progress('$TARGET\\r', overwrite=True, file=open('/dev/tty', 'w')) | |
619 | |
620 # Set the default -j value based on the number of processors. | |
621 SetOption('num_jobs', GetProcessorCount() + 1) | |
622 | |
623 # Have SCons use its cached dependency information. | |
624 SetOption('implicit_cache', 1) | |
625 | |
626 # Only re-calculate MD5 checksums if a timestamp has changed. | |
627 Decider('MD5-timestamp') | |
628 | |
629 # Since we set the -j value by default, suppress SCons warnings about being | |
630 # unable to support parallel build on versions of Python with no threading. | |
631 default_warnings = ['no-no-parallel-support'] | |
632 SetOption('warn', default_warnings + GetOption('warn')) | |
633 | |
634 AddOption('--mode', nargs=1, dest='conf_list', default=[], | |
635 action='append', help='Configuration to build.') | |
636 | |
637 AddOption('--verbose', dest='verbose', default=False, | |
638 action='store_true', help='Verbose command-line output.') | |
639 | |
640 | |
641 # | |
642 sconscript_file_map = %(sconscript_files)s | |
643 | |
644 class LoadTarget: | |
645 ''' | |
646 Class for deciding if a given target sconscript is to be included | |
647 based on a list of included target names, optionally prefixed with '-' | |
648 to exclude a target name. | |
649 ''' | |
650 def __init__(self, load): | |
651 ''' | |
652 Initialize a class with a list of names for possible loading. | |
653 | |
654 Arguments: | |
655 load: list of elements in the LOAD= specification | |
656 ''' | |
657 self.included = set([c for c in load if not c.startswith('-')]) | |
658 self.excluded = set([c[1:] for c in load if c.startswith('-')]) | |
659 | |
660 if not self.included: | |
661 self.included = set(['all']) | |
662 | |
663 def __call__(self, target): | |
664 ''' | |
665 Returns True if the specified target's sconscript file should be | |
666 loaded, based on the initialized included and excluded lists. | |
667 ''' | |
668 return (target in self.included or | |
669 ('all' in self.included and not target in self.excluded)) | |
670 | |
671 if 'LOAD' in ARGUMENTS: | |
672 load = ARGUMENTS['LOAD'].split(',') | |
673 else: | |
674 load = [] | |
675 load_target = LoadTarget(load) | |
676 | |
677 sconscript_files = [] | |
678 for target, sconscript in sconscript_file_map.iteritems(): | |
679 if load_target(target): | |
680 sconscript_files.append(sconscript) | |
681 | |
682 | |
683 target_alias_list= [] | |
684 | |
685 conf_list = GetOption('conf_list') | |
686 if conf_list: | |
687 # In case the same --mode= value was specified multiple times. | |
688 conf_list = list(set(conf_list)) | |
689 else: | |
690 conf_list = [%(default_configuration)r] | |
691 | |
692 sconsbuild_dir = Dir(%(sconsbuild_dir)s) | |
693 | |
694 | |
695 def FilterOut(self, **kw): | |
696 kw = SCons.Environment.copy_non_reserved_keywords(kw) | |
697 for key, val in kw.items(): | |
698 envval = self.get(key, None) | |
699 if envval is None: | |
700 # No existing variable in the environment, so nothing to delete. | |
701 continue | |
702 | |
703 for vremove in val: | |
704 # Use while not if, so we can handle duplicates. | |
705 while vremove in envval: | |
706 envval.remove(vremove) | |
707 | |
708 self[key] = envval | |
709 | |
710 # TODO(sgk): SCons.Environment.Append() has much more logic to deal | |
711 # with various types of values. We should handle all those cases in here | |
712 # too. (If variable is a dict, etc.) | |
713 | |
714 | |
715 non_compilable_suffixes = { | |
716 'LINUX' : set([ | |
717 '.bdic', | |
718 '.css', | |
719 '.dat', | |
720 '.fragment', | |
721 '.gperf', | |
722 '.h', | |
723 '.hh', | |
724 '.hpp', | |
725 '.html', | |
726 '.hxx', | |
727 '.idl', | |
728 '.in', | |
729 '.in0', | |
730 '.in1', | |
731 '.js', | |
732 '.mk', | |
733 '.rc', | |
734 '.sigs', | |
735 '', | |
736 ]), | |
737 'WINDOWS' : set([ | |
738 '.h', | |
739 '.hh', | |
740 '.hpp', | |
741 '.dat', | |
742 '.idl', | |
743 '.in', | |
744 '.in0', | |
745 '.in1', | |
746 ]), | |
747 } | |
748 | |
749 def compilable(env, file): | |
750 base, ext = os.path.splitext(str(file)) | |
751 if ext in non_compilable_suffixes[env['TARGET_PLATFORM']]: | |
752 return False | |
753 return True | |
754 | |
755 def compilable_files(env, sources): | |
756 return [x for x in sources if compilable(env, x)] | |
757 | |
758 def GypProgram(env, target, source, *args, **kw): | |
759 source = compilable_files(env, source) | |
760 result = env.Program(target, source, *args, **kw) | |
761 if env.get('INCREMENTAL'): | |
762 env.Precious(result) | |
763 return result | |
764 | |
765 def GypTestProgram(env, target, source, *args, **kw): | |
766 source = compilable_files(env, source) | |
767 result = env.Program(target, source, *args, **kw) | |
768 if env.get('INCREMENTAL'): | |
769 env.Precious(*result) | |
770 return result | |
771 | |
772 def GypLibrary(env, target, source, *args, **kw): | |
773 source = compilable_files(env, source) | |
774 result = env.Library(target, source, *args, **kw) | |
775 return result | |
776 | |
777 def GypLoadableModule(env, target, source, *args, **kw): | |
778 source = compilable_files(env, source) | |
779 result = env.LoadableModule(target, source, *args, **kw) | |
780 return result | |
781 | |
782 def GypStaticLibrary(env, target, source, *args, **kw): | |
783 source = compilable_files(env, source) | |
784 result = env.StaticLibrary(target, source, *args, **kw) | |
785 return result | |
786 | |
787 def GypSharedLibrary(env, target, source, *args, **kw): | |
788 source = compilable_files(env, source) | |
789 result = env.SharedLibrary(target, source, *args, **kw) | |
790 if env.get('INCREMENTAL'): | |
791 env.Precious(result) | |
792 return result | |
793 | |
794 def add_gyp_methods(env): | |
795 env.AddMethod(GypProgram) | |
796 env.AddMethod(GypTestProgram) | |
797 env.AddMethod(GypLibrary) | |
798 env.AddMethod(GypLoadableModule) | |
799 env.AddMethod(GypStaticLibrary) | |
800 env.AddMethod(GypSharedLibrary) | |
801 | |
802 env.AddMethod(FilterOut) | |
803 | |
804 env.AddMethod(compilable) | |
805 | |
806 | |
807 base_env = Environment( | |
808 tools = %(scons_tools)s, | |
809 INTERMEDIATE_DIR='$OBJ_DIR/${COMPONENT_NAME}/_${TARGET_NAME}_intermediate', | |
810 LIB_DIR='$TOP_BUILDDIR/lib', | |
811 OBJ_DIR='$TOP_BUILDDIR/obj', | |
812 SCONSBUILD_DIR=sconsbuild_dir.abspath, | |
813 SHARED_INTERMEDIATE_DIR='$OBJ_DIR/_global_intermediate', | |
814 SRC_DIR=Dir(%(src_dir)r), | |
815 TARGET_PLATFORM='LINUX', | |
816 TOP_BUILDDIR='$SCONSBUILD_DIR/$CONFIG_NAME', | |
817 LIBPATH=['$LIB_DIR'], | |
818 ) | |
819 | |
820 if not GetOption('verbose'): | |
821 base_env.SetDefault( | |
822 ARCOMSTR='Creating library $TARGET', | |
823 ASCOMSTR='Assembling $TARGET', | |
824 CCCOMSTR='Compiling $TARGET', | |
825 CONCATSOURCECOMSTR='ConcatSource $TARGET', | |
826 CXXCOMSTR='Compiling $TARGET', | |
827 LDMODULECOMSTR='Building loadable module $TARGET', | |
828 LINKCOMSTR='Linking $TARGET', | |
829 MANIFESTCOMSTR='Updating manifest for $TARGET', | |
830 MIDLCOMSTR='Compiling IDL $TARGET', | |
831 PCHCOMSTR='Precompiling $TARGET', | |
832 RANLIBCOMSTR='Indexing $TARGET', | |
833 RCCOMSTR='Compiling resource $TARGET', | |
834 SHCCCOMSTR='Compiling $TARGET', | |
835 SHCXXCOMSTR='Compiling $TARGET', | |
836 SHLINKCOMSTR='Linking $TARGET', | |
837 SHMANIFESTCOMSTR='Updating manifest for $TARGET', | |
838 ) | |
839 | |
840 add_gyp_methods(base_env) | |
841 | |
842 for conf in conf_list: | |
843 env = base_env.Clone(CONFIG_NAME=conf) | |
844 SConsignFile(env.File('$TOP_BUILDDIR/.sconsign').abspath) | |
845 for sconscript in sconscript_files: | |
846 target_alias = env.SConscript(sconscript, exports=['env']) | |
847 if target_alias: | |
848 target_alias_list.extend(target_alias) | |
849 | |
850 Default(Alias('all', target_alias_list)) | |
851 | |
852 help_fmt = ''' | |
853 Usage: hammer [SCONS_OPTIONS] [VARIABLES] [TARGET] ... | |
854 | |
855 Local command-line build options: | |
856 --mode=CONFIG Configuration to build: | |
857 --mode=Debug [default] | |
858 --mode=Release | |
859 --verbose Print actual executed command lines. | |
860 | |
861 Supported command-line build variables: | |
862 LOAD=[module,...] Comma-separated list of components to load in the | |
863 dependency graph ('-' prefix excludes) | |
864 PROGRESS=type Display a progress indicator: | |
865 name: print each evaluated target name | |
866 spinner: print a spinner every 5 targets | |
867 | |
868 The following TARGET names can also be used as LOAD= module names: | |
869 | |
870 %%s | |
871 ''' | |
872 | |
873 if GetOption('help'): | |
874 def columnar_text(items, width=78, indent=2, sep=2): | |
875 result = [] | |
876 colwidth = max(map(len, items)) + sep | |
877 cols = (width - indent) / colwidth | |
878 if cols < 1: | |
879 cols = 1 | |
880 rows = (len(items) + cols - 1) / cols | |
881 indent = '%%*s' %% (indent, '') | |
882 sep = indent | |
883 for row in xrange(0, rows): | |
884 result.append(sep) | |
885 for i in xrange(row, len(items), rows): | |
886 result.append('%%-*s' %% (colwidth, items[i])) | |
887 sep = '\\n' + indent | |
888 result.append('\\n') | |
889 return ''.join(result) | |
890 | |
891 load_list = set(sconscript_file_map.keys()) | |
892 target_aliases = set(map(str, target_alias_list)) | |
893 | |
894 common = load_list and target_aliases | |
895 load_only = load_list - common | |
896 target_only = target_aliases - common | |
897 help_text = [help_fmt %% columnar_text(sorted(list(common)))] | |
898 if target_only: | |
899 fmt = "The following are additional TARGET names:\\n\\n%%s\\n" | |
900 help_text.append(fmt %% columnar_text(sorted(list(target_only)))) | |
901 if load_only: | |
902 fmt = "The following are additional LOAD= module names:\\n\\n%%s\\n" | |
903 help_text.append(fmt %% columnar_text(sorted(list(load_only)))) | |
904 Help(''.join(help_text)) | |
905 """ | |
906 | |
907 # TEMPLATE END | |
908 ############################################################################# | |
909 | |
910 | |
911 def GenerateSConscriptWrapper(build_file, build_file_data, name, | |
912 output_filename, sconscript_files, | |
913 default_configuration): | |
914 """ | |
915 Generates the "wrapper" SConscript file (analogous to the Visual Studio | |
916 solution) that calls all the individual target SConscript files. | |
917 """ | |
918 output_dir = os.path.dirname(output_filename) | |
919 src_dir = build_file_data['_DEPTH'] | |
920 src_dir_rel = gyp.common.RelativePath(src_dir, output_dir) | |
921 if not src_dir_rel: | |
922 src_dir_rel = '.' | |
923 scons_settings = build_file_data.get('scons_settings', {}) | |
924 sconsbuild_dir = scons_settings.get('sconsbuild_dir', '#') | |
925 scons_tools = scons_settings.get('tools', ['default']) | |
926 | |
927 sconscript_file_lines = ['dict('] | |
928 for target in sorted(sconscript_files.keys()): | |
929 sconscript = sconscript_files[target] | |
930 sconscript_file_lines.append(' %s = %r,' % (target, sconscript)) | |
931 sconscript_file_lines.append(')') | |
932 | |
933 fp = open(output_filename, 'w') | |
934 fp.write(header) | |
935 fp.write(_wrapper_template % { | |
936 'default_configuration' : default_configuration, | |
937 'name' : name, | |
938 'scons_tools' : repr(scons_tools), | |
939 'sconsbuild_dir' : repr(sconsbuild_dir), | |
940 'sconscript_files' : '\n'.join(sconscript_file_lines), | |
941 'src_dir' : src_dir_rel, | |
942 }) | |
943 fp.close() | |
944 | |
945 # Generate the SConstruct file that invokes the wrapper SConscript. | |
946 dir, fname = os.path.split(output_filename) | |
947 SConstruct = os.path.join(dir, 'SConstruct') | |
948 fp = open(SConstruct, 'w') | |
949 fp.write(header) | |
950 fp.write('SConscript(%s)\n' % repr(fname)) | |
951 fp.close() | |
952 | |
953 | |
954 def TargetFilename(target, build_file=None, output_suffix=''): | |
955 """Returns the .scons file name for the specified target. | |
956 """ | |
957 if build_file is None: | |
958 build_file, target = gyp.common.ParseQualifiedTarget(target)[:2] | |
959 output_file = os.path.join(os.path.dirname(build_file), | |
960 target + output_suffix + '.scons') | |
961 return output_file | |
962 | |
963 | |
964 def PerformBuild(data, configurations, params): | |
965 options = params['options'] | |
966 | |
967 # Due to the way we test gyp on the chromium typbots | |
968 # we need to look for 'scons.py' as well as the more common 'scons' | |
969 # TODO(sbc): update the trybots to have a more normal install | |
970 # of scons. | |
971 scons = 'scons' | |
972 paths = os.environ['PATH'].split(os.pathsep) | |
973 for scons_name in ['scons', 'scons.py']: | |
974 for path in paths: | |
975 test_scons = os.path.join(path, scons_name) | |
976 print 'looking for: %s' % test_scons | |
977 if os.path.exists(test_scons): | |
978 print "found scons: %s" % scons | |
979 scons = test_scons | |
980 break | |
981 | |
982 for config in configurations: | |
983 arguments = [scons, '-C', options.toplevel_dir, '--mode=%s' % config] | |
984 print "Building [%s]: %s" % (config, arguments) | |
985 subprocess.check_call(arguments) | |
986 | |
987 | |
988 def GenerateOutput(target_list, target_dicts, data, params): | |
989 """ | |
990 Generates all the output files for the specified targets. | |
991 """ | |
992 options = params['options'] | |
993 | |
994 if options.generator_output: | |
995 def output_path(filename): | |
996 return filename.replace(params['cwd'], options.generator_output) | |
997 else: | |
998 def output_path(filename): | |
999 return filename | |
1000 | |
1001 default_configuration = None | |
1002 | |
1003 for qualified_target in target_list: | |
1004 spec = target_dicts[qualified_target] | |
1005 if spec['toolset'] != 'target': | |
1006 raise Exception( | |
1007 'Multiple toolsets not supported in scons build (target %s)' % | |
1008 qualified_target) | |
1009 scons_target = SCons.Target(spec) | |
1010 if scons_target.is_ignored: | |
1011 continue | |
1012 | |
1013 # TODO: assumes the default_configuration of the first target | |
1014 # non-Default target is the correct default for all targets. | |
1015 # Need a better model for handle variation between targets. | |
1016 if (not default_configuration and | |
1017 spec['default_configuration'] != 'Default'): | |
1018 default_configuration = spec['default_configuration'] | |
1019 | |
1020 build_file, target = gyp.common.ParseQualifiedTarget(qualified_target)[:2] | |
1021 output_file = TargetFilename(target, build_file, options.suffix) | |
1022 if options.generator_output: | |
1023 output_file = output_path(output_file) | |
1024 | |
1025 if not spec.has_key('libraries'): | |
1026 spec['libraries'] = [] | |
1027 | |
1028 # Add dependent static library targets to the 'libraries' value. | |
1029 deps = spec.get('dependencies', []) | |
1030 spec['scons_dependencies'] = [] | |
1031 for d in deps: | |
1032 td = target_dicts[d] | |
1033 target_name = td['target_name'] | |
1034 spec['scons_dependencies'].append("Alias('%s')" % target_name) | |
1035 if td['type'] in ('static_library', 'shared_library'): | |
1036 libname = td.get('product_name', target_name) | |
1037 spec['libraries'].append('lib' + libname) | |
1038 if td['type'] == 'loadable_module': | |
1039 prereqs = spec.get('scons_prerequisites', []) | |
1040 # TODO: parameterize with <(SHARED_LIBRARY_*) variables? | |
1041 td_target = SCons.Target(td) | |
1042 td_target.target_prefix = '${SHLIBPREFIX}' | |
1043 td_target.target_suffix = '${SHLIBSUFFIX}' | |
1044 | |
1045 GenerateSConscript(output_file, spec, build_file, data[build_file]) | |
1046 | |
1047 if not default_configuration: | |
1048 default_configuration = 'Default' | |
1049 | |
1050 for build_file in sorted(data.keys()): | |
1051 path, ext = os.path.splitext(build_file) | |
1052 if ext != '.gyp': | |
1053 continue | |
1054 output_dir, basename = os.path.split(path) | |
1055 output_filename = path + '_main' + options.suffix + '.scons' | |
1056 | |
1057 all_targets = gyp.common.AllTargets(target_list, target_dicts, build_file) | |
1058 sconscript_files = {} | |
1059 for t in all_targets: | |
1060 scons_target = SCons.Target(target_dicts[t]) | |
1061 if scons_target.is_ignored: | |
1062 continue | |
1063 bf, target = gyp.common.ParseQualifiedTarget(t)[:2] | |
1064 target_filename = TargetFilename(target, bf, options.suffix) | |
1065 tpath = gyp.common.RelativePath(target_filename, output_dir) | |
1066 sconscript_files[target] = tpath | |
1067 | |
1068 output_filename = output_path(output_filename) | |
1069 if sconscript_files: | |
1070 GenerateSConscriptWrapper(build_file, data[build_file], basename, | |
1071 output_filename, sconscript_files, | |
1072 default_configuration) | |
OLD | NEW |