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

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

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

Powered by Google App Engine
This is Rietveld 408576698