Index: pylib/gyp/generator/scons.py |
=================================================================== |
--- pylib/gyp/generator/scons.py (revision 1611) |
+++ pylib/gyp/generator/scons.py (working copy) |
@@ -1,1072 +0,0 @@ |
-# Copyright (c) 2012 Google Inc. All rights reserved. |
-# Use of this source code is governed by a BSD-style license that can be |
-# found in the LICENSE file. |
- |
-import gyp |
-import gyp.common |
-import gyp.SCons as SCons |
-import os.path |
-import pprint |
-import re |
-import subprocess |
- |
- |
-# TODO: remove when we delete the last WriteList() call in this module |
-WriteList = SCons.WriteList |
- |
- |
-generator_default_variables = { |
- 'EXECUTABLE_PREFIX': '', |
- 'EXECUTABLE_SUFFIX': '', |
- 'STATIC_LIB_PREFIX': '${LIBPREFIX}', |
- 'SHARED_LIB_PREFIX': '${SHLIBPREFIX}', |
- 'STATIC_LIB_SUFFIX': '${LIBSUFFIX}', |
- 'SHARED_LIB_SUFFIX': '${SHLIBSUFFIX}', |
- 'INTERMEDIATE_DIR': '${INTERMEDIATE_DIR}', |
- 'SHARED_INTERMEDIATE_DIR': '${SHARED_INTERMEDIATE_DIR}', |
- 'OS': 'linux', |
- 'PRODUCT_DIR': '$TOP_BUILDDIR', |
- 'SHARED_LIB_DIR': '$LIB_DIR', |
- 'LIB_DIR': '$LIB_DIR', |
- 'RULE_INPUT_ROOT': '${SOURCE.filebase}', |
- 'RULE_INPUT_DIRNAME': '${SOURCE.dir}', |
- 'RULE_INPUT_EXT': '${SOURCE.suffix}', |
- 'RULE_INPUT_NAME': '${SOURCE.file}', |
- 'RULE_INPUT_PATH': '${SOURCE.abspath}', |
- 'CONFIGURATION_NAME': '${CONFIG_NAME}', |
-} |
- |
-# Tell GYP how to process the input for us. |
-generator_handles_variants = True |
-generator_wants_absolute_build_file_paths = True |
- |
- |
-def FixPath(path, prefix): |
- if not os.path.isabs(path) and not path[0] == '$': |
- path = prefix + path |
- return path |
- |
- |
-header = """\ |
-# This file is generated; do not edit. |
-""" |
- |
- |
-_alias_template = """ |
-if GetOption('verbose'): |
- _action = Action([%(action)s]) |
-else: |
- _action = Action([%(action)s], %(message)s) |
-_outputs = env.Alias( |
- ['_%(target_name)s_action'], |
- %(inputs)s, |
- _action |
-) |
-env.AlwaysBuild(_outputs) |
-""" |
- |
-_run_as_template = """ |
-if GetOption('verbose'): |
- _action = Action([%(action)s]) |
-else: |
- _action = Action([%(action)s], %(message)s) |
-""" |
- |
-_run_as_template_suffix = """ |
-_run_as_target = env.Alias('run_%(target_name)s', target_files, _action) |
-env.Requires(_run_as_target, [ |
- Alias('%(target_name)s'), |
-]) |
-env.AlwaysBuild(_run_as_target) |
-""" |
- |
-_command_template = """ |
-if GetOption('verbose'): |
- _action = Action([%(action)s]) |
-else: |
- _action = Action([%(action)s], %(message)s) |
-_outputs = env.Command( |
- %(outputs)s, |
- %(inputs)s, |
- _action |
-) |
-""" |
- |
-# This is copied from the default SCons action, updated to handle symlinks. |
-_copy_action_template = """ |
-import shutil |
-import SCons.Action |
- |
-def _copy_files_or_dirs_or_symlinks(dest, src): |
- SCons.Node.FS.invalidate_node_memos(dest) |
- if SCons.Util.is_List(src) and os.path.isdir(dest): |
- for file in src: |
- shutil.copy2(file, dest) |
- return 0 |
- elif os.path.islink(src): |
- linkto = os.readlink(src) |
- os.symlink(linkto, dest) |
- return 0 |
- elif os.path.isfile(src): |
- return shutil.copy2(src, dest) |
- else: |
- return shutil.copytree(src, dest, 1) |
- |
-def _copy_files_or_dirs_or_symlinks_str(dest, src): |
- return 'Copying %s to %s ...' % (src, dest) |
- |
-GYPCopy = SCons.Action.ActionFactory(_copy_files_or_dirs_or_symlinks, |
- _copy_files_or_dirs_or_symlinks_str, |
- convert=str) |
-""" |
- |
-_rule_template = """ |
-%(name)s_additional_inputs = %(inputs)s |
-%(name)s_outputs = %(outputs)s |
-def %(name)s_emitter(target, source, env): |
- return (%(name)s_outputs, source + %(name)s_additional_inputs) |
-if GetOption('verbose'): |
- %(name)s_action = Action([%(action)s]) |
-else: |
- %(name)s_action = Action([%(action)s], %(message)s) |
-env['BUILDERS']['%(name)s'] = Builder(action=%(name)s_action, |
- emitter=%(name)s_emitter) |
- |
-_outputs = [] |
-_processed_input_files = [] |
-for infile in input_files: |
- if (type(infile) == type('') |
- and not os.path.isabs(infile) |
- and not infile[0] == '$'): |
- infile = %(src_dir)r + infile |
- if str(infile).endswith('.%(extension)s'): |
- _generated = env.%(name)s(infile) |
- env.Precious(_generated) |
- _outputs.append(_generated) |
- %(process_outputs_as_sources_line)s |
- else: |
- _processed_input_files.append(infile) |
-prerequisites.extend(_outputs) |
-input_files = _processed_input_files |
-""" |
- |
-_spawn_hack = """ |
-import re |
-import SCons.Platform.posix |
-needs_shell = re.compile('["\\'><!^&]') |
-def gyp_spawn(sh, escape, cmd, args, env): |
- def strip_scons_quotes(arg): |
- if arg[0] == '"' and arg[-1] == '"': |
- return arg[1:-1] |
- return arg |
- stripped_args = [strip_scons_quotes(a) for a in args] |
- if needs_shell.search(' '.join(stripped_args)): |
- return SCons.Platform.posix.exec_spawnvpe([sh, '-c', ' '.join(args)], env) |
- else: |
- return SCons.Platform.posix.exec_spawnvpe(stripped_args, env) |
-""" |
- |
- |
-def EscapeShellArgument(s): |
- """Quotes an argument so that it will be interpreted literally by a POSIX |
- shell. Taken from |
- http://stackoverflow.com/questions/35817/whats-the-best-way-to-escape-ossystem-calls-in-python |
- """ |
- return "'" + s.replace("'", "'\\''") + "'" |
- |
- |
-def InvertNaiveSConsQuoting(s): |
- """SCons tries to "help" with quoting by naively putting double-quotes around |
- command-line arguments containing space or tab, which is broken for all |
- but trivial cases, so we undo it. (See quote_spaces() in Subst.py)""" |
- if ' ' in s or '\t' in s: |
- # Then SCons will put double-quotes around this, so add our own quotes |
- # to close its quotes at the beginning and end. |
- s = '"' + s + '"' |
- return s |
- |
- |
-def EscapeSConsVariableExpansion(s): |
- """SCons has its own variable expansion syntax using $. We must escape it for |
- strings to be interpreted literally. For some reason this requires four |
- dollar signs, not two, even without the shell involved.""" |
- return s.replace('$', '$$$$') |
- |
- |
-def EscapeCppDefine(s): |
- """Escapes a CPP define so that it will reach the compiler unaltered.""" |
- s = EscapeShellArgument(s) |
- s = InvertNaiveSConsQuoting(s) |
- s = EscapeSConsVariableExpansion(s) |
- return s |
- |
- |
-def GenerateConfig(fp, config, indent='', src_dir=''): |
- """ |
- Generates SCons dictionary items for a gyp configuration. |
- |
- This provides the main translation between the (lower-case) gyp settings |
- keywords and the (upper-case) SCons construction variables. |
- """ |
- var_mapping = { |
- 'ASFLAGS' : 'asflags', |
- 'CCFLAGS' : 'cflags', |
- 'CFLAGS' : 'cflags_c', |
- 'CXXFLAGS' : 'cflags_cc', |
- 'CPPDEFINES' : 'defines', |
- 'CPPPATH' : 'include_dirs', |
- # Add the ldflags value to $LINKFLAGS, but not $SHLINKFLAGS. |
- # SCons defines $SHLINKFLAGS to incorporate $LINKFLAGS, so |
- # listing both here would case 'ldflags' to get appended to |
- # both, and then have it show up twice on the command line. |
- 'LINKFLAGS' : 'ldflags', |
- } |
- postamble='\n%s],\n' % indent |
- for scons_var in sorted(var_mapping.keys()): |
- gyp_var = var_mapping[scons_var] |
- value = config.get(gyp_var) |
- if value: |
- if gyp_var in ('defines',): |
- value = [EscapeCppDefine(v) for v in value] |
- if gyp_var in ('include_dirs',): |
- if src_dir and not src_dir.endswith('/'): |
- src_dir += '/' |
- result = [] |
- for v in value: |
- v = FixPath(v, src_dir) |
- # Force SCons to evaluate the CPPPATH directories at |
- # SConscript-read time, so delayed evaluation of $SRC_DIR |
- # doesn't point it to the --generator-output= directory. |
- result.append('env.Dir(%r)' % v) |
- value = result |
- else: |
- value = map(repr, value) |
- WriteList(fp, |
- value, |
- prefix=indent, |
- preamble='%s%s = [\n ' % (indent, scons_var), |
- postamble=postamble) |
- |
- |
-def GenerateSConscript(output_filename, spec, build_file, build_file_data): |
- """ |
- Generates a SConscript file for a specific target. |
- |
- This generates a SConscript file suitable for building any or all of |
- the target's configurations. |
- |
- A SConscript file may be called multiple times to generate targets for |
- multiple configurations. Consequently, it needs to be ready to build |
- the target for any requested configuration, and therefore contains |
- information about the settings for all configurations (generated into |
- the SConscript file at gyp configuration time) as well as logic for |
- selecting (at SCons build time) the specific configuration being built. |
- |
- The general outline of a generated SConscript file is: |
- |
- -- Header |
- |
- -- Import 'env'. This contains a $CONFIG_NAME construction |
- variable that specifies what configuration to build |
- (e.g. Debug, Release). |
- |
- -- Configurations. This is a dictionary with settings for |
- the different configurations (Debug, Release) under which this |
- target can be built. The values in the dictionary are themselves |
- dictionaries specifying what construction variables should added |
- to the local copy of the imported construction environment |
- (Append), should be removed (FilterOut), and should outright |
- replace the imported values (Replace). |
- |
- -- Clone the imported construction environment and update |
- with the proper configuration settings. |
- |
- -- Initialize the lists of the targets' input files and prerequisites. |
- |
- -- Target-specific actions and rules. These come after the |
- input file and prerequisite initializations because the |
- outputs of the actions and rules may affect the input file |
- list (process_outputs_as_sources) and get added to the list of |
- prerequisites (so that they're guaranteed to be executed before |
- building the target). |
- |
- -- Call the Builder for the target itself. |
- |
- -- Arrange for any copies to be made into installation directories. |
- |
- -- Set up the {name} Alias (phony Node) for the target as the |
- primary handle for building all of the target's pieces. |
- |
- -- Use env.Require() to make sure the prerequisites (explicitly |
- specified, but also including the actions and rules) are built |
- before the target itself. |
- |
- -- Return the {name} Alias to the calling SConstruct file |
- so it can be added to the list of default targets. |
- """ |
- scons_target = SCons.Target(spec) |
- |
- gyp_dir = os.path.dirname(output_filename) |
- if not gyp_dir: |
- gyp_dir = '.' |
- gyp_dir = os.path.abspath(gyp_dir) |
- |
- output_dir = os.path.dirname(output_filename) |
- src_dir = build_file_data['_DEPTH'] |
- src_dir_rel = gyp.common.RelativePath(src_dir, output_dir) |
- subdir = gyp.common.RelativePath(os.path.dirname(build_file), src_dir) |
- src_subdir = '$SRC_DIR/' + subdir |
- src_subdir_ = src_subdir + '/' |
- |
- component_name = os.path.splitext(os.path.basename(build_file))[0] |
- target_name = spec['target_name'] |
- |
- if not os.path.exists(gyp_dir): |
- os.makedirs(gyp_dir) |
- fp = open(output_filename, 'w') |
- fp.write(header) |
- |
- fp.write('\nimport os\n') |
- fp.write('\nImport("env")\n') |
- |
- # |
- fp.write('\n') |
- fp.write('env = env.Clone(COMPONENT_NAME=%s,\n' % repr(component_name)) |
- fp.write(' TARGET_NAME=%s)\n' % repr(target_name)) |
- |
- # |
- for config in spec['configurations'].itervalues(): |
- if config.get('scons_line_length'): |
- fp.write(_spawn_hack) |
- break |
- |
- # |
- indent = ' ' * 12 |
- fp.write('\n') |
- fp.write('configurations = {\n') |
- for config_name, config in spec['configurations'].iteritems(): |
- fp.write(' \'%s\' : {\n' % config_name) |
- |
- fp.write(' \'Append\' : dict(\n') |
- GenerateConfig(fp, config, indent, src_subdir) |
- libraries = spec.get('libraries') |
- if libraries: |
- WriteList(fp, |
- map(repr, libraries), |
- prefix=indent, |
- preamble='%sLIBS = [\n ' % indent, |
- postamble='\n%s],\n' % indent) |
- fp.write(' ),\n') |
- |
- fp.write(' \'FilterOut\' : dict(\n' ) |
- for key, var in config.get('scons_remove', {}).iteritems(): |
- fp.write(' %s = %s,\n' % (key, repr(var))) |
- fp.write(' ),\n') |
- |
- fp.write(' \'Replace\' : dict(\n' ) |
- scons_settings = config.get('scons_variable_settings', {}) |
- for key in sorted(scons_settings.keys()): |
- val = pprint.pformat(scons_settings[key]) |
- fp.write(' %s = %s,\n' % (key, val)) |
- if 'c++' in spec.get('link_languages', []): |
- fp.write(' %s = %s,\n' % ('LINK', repr('$CXX'))) |
- if config.get('scons_line_length'): |
- fp.write(' SPAWN = gyp_spawn,\n') |
- fp.write(' ),\n') |
- |
- fp.write(' \'ImportExternal\' : [\n' ) |
- for var in config.get('scons_import_variables', []): |
- fp.write(' %s,\n' % repr(var)) |
- fp.write(' ],\n') |
- |
- fp.write(' \'PropagateExternal\' : [\n' ) |
- for var in config.get('scons_propagate_variables', []): |
- fp.write(' %s,\n' % repr(var)) |
- fp.write(' ],\n') |
- |
- fp.write(' },\n') |
- fp.write('}\n') |
- |
- fp.write('\n' |
- 'config = configurations[env[\'CONFIG_NAME\']]\n' |
- 'env.Append(**config[\'Append\'])\n' |
- 'env.FilterOut(**config[\'FilterOut\'])\n' |
- 'env.Replace(**config[\'Replace\'])\n') |
- |
- fp.write('\n' |
- '# Scons forces -fPIC for SHCCFLAGS on some platforms.\n' |
- '# Disable that so we can control it from cflags in gyp.\n' |
- '# Note that Scons itself is inconsistent with its -fPIC\n' |
- '# setting. SHCCFLAGS forces -fPIC, and SHCFLAGS does not.\n' |
- '# This will make SHCCFLAGS consistent with SHCFLAGS.\n' |
- 'env[\'SHCCFLAGS\'] = [\'$CCFLAGS\']\n') |
- |
- fp.write('\n' |
- 'for _var in config[\'ImportExternal\']:\n' |
- ' if _var in ARGUMENTS:\n' |
- ' env[_var] = ARGUMENTS[_var]\n' |
- ' elif _var in os.environ:\n' |
- ' env[_var] = os.environ[_var]\n' |
- 'for _var in config[\'PropagateExternal\']:\n' |
- ' if _var in ARGUMENTS:\n' |
- ' env[_var] = ARGUMENTS[_var]\n' |
- ' elif _var in os.environ:\n' |
- ' env[\'ENV\'][_var] = os.environ[_var]\n') |
- |
- fp.write('\n' |
- "env['ENV']['LD_LIBRARY_PATH'] = env.subst('$LIB_DIR')\n") |
- |
- # |
- #fp.write("\nif env.has_key('CPPPATH'):\n") |
- #fp.write(" env['CPPPATH'] = map(env.Dir, env['CPPPATH'])\n") |
- |
- variants = spec.get('variants', {}) |
- for setting in sorted(variants.keys()): |
- if_fmt = 'if ARGUMENTS.get(%s) not in (None, \'0\'):\n' |
- fp.write('\n') |
- fp.write(if_fmt % repr(setting.upper())) |
- fp.write(' env.AppendUnique(\n') |
- GenerateConfig(fp, variants[setting], indent, src_subdir) |
- fp.write(' )\n') |
- |
- # |
- scons_target.write_input_files(fp) |
- |
- fp.write('\n') |
- fp.write('target_files = []\n') |
- prerequisites = spec.get('scons_prerequisites', []) |
- fp.write('prerequisites = %s\n' % pprint.pformat(prerequisites)) |
- |
- actions = spec.get('actions', []) |
- for action in actions: |
- a = ['cd', src_subdir, '&&'] + action['action'] |
- message = action.get('message') |
- if message: |
- message = repr(message) |
- inputs = [FixPath(f, src_subdir_) for f in action.get('inputs', [])] |
- outputs = [FixPath(f, src_subdir_) for f in action.get('outputs', [])] |
- if outputs: |
- template = _command_template |
- else: |
- template = _alias_template |
- fp.write(template % { |
- 'inputs' : pprint.pformat(inputs), |
- 'outputs' : pprint.pformat(outputs), |
- 'action' : pprint.pformat(a), |
- 'message' : message, |
- 'target_name': target_name, |
- }) |
- if int(action.get('process_outputs_as_sources', 0)): |
- fp.write('input_files.extend(_outputs)\n') |
- fp.write('prerequisites.extend(_outputs)\n') |
- fp.write('target_files.extend(_outputs)\n') |
- |
- rules = spec.get('rules', []) |
- for rule in rules: |
- name = re.sub('[^a-zA-Z0-9_]', '_', rule['rule_name']) |
- message = rule.get('message') |
- if message: |
- message = repr(message) |
- if int(rule.get('process_outputs_as_sources', 0)): |
- poas_line = '_processed_input_files.extend(_generated)' |
- else: |
- poas_line = '_processed_input_files.append(infile)' |
- inputs = [FixPath(f, src_subdir_) for f in rule.get('inputs', [])] |
- outputs = [FixPath(f, src_subdir_) for f in rule.get('outputs', [])] |
- # Skip a rule with no action and no inputs. |
- if 'action' not in rule and not rule.get('rule_sources', []): |
- continue |
- a = ['cd', src_subdir, '&&'] + rule['action'] |
- fp.write(_rule_template % { |
- 'inputs' : pprint.pformat(inputs), |
- 'outputs' : pprint.pformat(outputs), |
- 'action' : pprint.pformat(a), |
- 'extension' : rule['extension'], |
- 'name' : name, |
- 'message' : message, |
- 'process_outputs_as_sources_line' : poas_line, |
- 'src_dir' : src_subdir_, |
- }) |
- |
- scons_target.write_target(fp, src_subdir) |
- |
- copies = spec.get('copies', []) |
- if copies: |
- fp.write(_copy_action_template) |
- for copy in copies: |
- destdir = None |
- files = None |
- try: |
- destdir = copy['destination'] |
- except KeyError, e: |
- gyp.common.ExceptionAppend( |
- e, |
- "Required 'destination' key missing for 'copies' in %s." % build_file) |
- raise |
- try: |
- files = copy['files'] |
- except KeyError, e: |
- gyp.common.ExceptionAppend( |
- e, "Required 'files' key missing for 'copies' in %s." % build_file) |
- raise |
- if not files: |
- # TODO: should probably add a (suppressible) warning; |
- # a null file list may be unintentional. |
- continue |
- if not destdir: |
- raise Exception( |
- "Required 'destination' key is empty for 'copies' in %s." % build_file) |
- |
- fmt = ('\n' |
- '_outputs = env.Command(%s,\n' |
- ' %s,\n' |
- ' GYPCopy(\'$TARGET\', \'$SOURCE\'))\n') |
- for f in copy['files']: |
- # Remove trailing separators so basename() acts like Unix basename and |
- # always returns the last element, whether a file or dir. Without this, |
- # only the contents, not the directory itself, are copied (and nothing |
- # might be copied if dest already exists, since scons thinks nothing needs |
- # to be done). |
- dest = os.path.join(destdir, os.path.basename(f.rstrip(os.sep))) |
- f = FixPath(f, src_subdir_) |
- dest = FixPath(dest, src_subdir_) |
- fp.write(fmt % (repr(dest), repr(f))) |
- fp.write('target_files.extend(_outputs)\n') |
- |
- run_as = spec.get('run_as') |
- if run_as: |
- action = run_as.get('action', []) |
- working_directory = run_as.get('working_directory') |
- if not working_directory: |
- working_directory = gyp_dir |
- else: |
- if not os.path.isabs(working_directory): |
- working_directory = os.path.normpath(os.path.join(gyp_dir, |
- working_directory)) |
- if run_as.get('environment'): |
- for (key, val) in run_as.get('environment').iteritems(): |
- action = ['%s="%s"' % (key, val)] + action |
- action = ['cd', '"%s"' % working_directory, '&&'] + action |
- fp.write(_run_as_template % { |
- 'action' : pprint.pformat(action), |
- 'message' : run_as.get('message', ''), |
- }) |
- |
- fmt = "\ngyp_target = env.Alias('%s', target_files)\n" |
- fp.write(fmt % target_name) |
- |
- dependencies = spec.get('scons_dependencies', []) |
- if dependencies: |
- WriteList(fp, dependencies, preamble='dependencies = [\n ', |
- postamble='\n]\n') |
- fp.write('env.Requires(target_files, dependencies)\n') |
- fp.write('env.Requires(gyp_target, dependencies)\n') |
- fp.write('for prerequisite in prerequisites:\n') |
- fp.write(' env.Requires(prerequisite, dependencies)\n') |
- fp.write('env.Requires(gyp_target, prerequisites)\n') |
- |
- if run_as: |
- fp.write(_run_as_template_suffix % { |
- 'target_name': target_name, |
- }) |
- |
- fp.write('Return("gyp_target")\n') |
- |
- fp.close() |
- |
- |
-############################################################################# |
-# TEMPLATE BEGIN |
- |
-_wrapper_template = """\ |
- |
-__doc__ = ''' |
-Wrapper configuration for building this entire "solution," |
-including all the specific targets in various *.scons files. |
-''' |
- |
-import os |
-import sys |
- |
-import SCons.Environment |
-import SCons.Util |
- |
-def GetProcessorCount(): |
- ''' |
- Detects the number of CPUs on the system. Adapted form: |
- http://codeliberates.blogspot.com/2008/05/detecting-cpuscores-in-python.html |
- ''' |
- # Linux, Unix and Mac OS X: |
- if hasattr(os, 'sysconf'): |
- if os.sysconf_names.has_key('SC_NPROCESSORS_ONLN'): |
- # Linux and Unix or Mac OS X with python >= 2.5: |
- return os.sysconf('SC_NPROCESSORS_ONLN') |
- else: # Mac OS X with Python < 2.5: |
- return int(os.popen2("sysctl -n hw.ncpu")[1].read()) |
- # Windows: |
- if os.environ.has_key('NUMBER_OF_PROCESSORS'): |
- return max(int(os.environ.get('NUMBER_OF_PROCESSORS', '1')), 1) |
- return 1 # Default |
- |
-# Support PROGRESS= to show progress in different ways. |
-p = ARGUMENTS.get('PROGRESS') |
-if p == 'spinner': |
- Progress(['/\\r', '|\\r', '\\\\\\r', '-\\r'], |
- interval=5, |
- file=open('/dev/tty', 'w')) |
-elif p == 'name': |
- Progress('$TARGET\\r', overwrite=True, file=open('/dev/tty', 'w')) |
- |
-# Set the default -j value based on the number of processors. |
-SetOption('num_jobs', GetProcessorCount() + 1) |
- |
-# Have SCons use its cached dependency information. |
-SetOption('implicit_cache', 1) |
- |
-# Only re-calculate MD5 checksums if a timestamp has changed. |
-Decider('MD5-timestamp') |
- |
-# Since we set the -j value by default, suppress SCons warnings about being |
-# unable to support parallel build on versions of Python with no threading. |
-default_warnings = ['no-no-parallel-support'] |
-SetOption('warn', default_warnings + GetOption('warn')) |
- |
-AddOption('--mode', nargs=1, dest='conf_list', default=[], |
- action='append', help='Configuration to build.') |
- |
-AddOption('--verbose', dest='verbose', default=False, |
- action='store_true', help='Verbose command-line output.') |
- |
- |
-# |
-sconscript_file_map = %(sconscript_files)s |
- |
-class LoadTarget: |
- ''' |
- Class for deciding if a given target sconscript is to be included |
- based on a list of included target names, optionally prefixed with '-' |
- to exclude a target name. |
- ''' |
- def __init__(self, load): |
- ''' |
- Initialize a class with a list of names for possible loading. |
- |
- Arguments: |
- load: list of elements in the LOAD= specification |
- ''' |
- self.included = set([c for c in load if not c.startswith('-')]) |
- self.excluded = set([c[1:] for c in load if c.startswith('-')]) |
- |
- if not self.included: |
- self.included = set(['all']) |
- |
- def __call__(self, target): |
- ''' |
- Returns True if the specified target's sconscript file should be |
- loaded, based on the initialized included and excluded lists. |
- ''' |
- return (target in self.included or |
- ('all' in self.included and not target in self.excluded)) |
- |
-if 'LOAD' in ARGUMENTS: |
- load = ARGUMENTS['LOAD'].split(',') |
-else: |
- load = [] |
-load_target = LoadTarget(load) |
- |
-sconscript_files = [] |
-for target, sconscript in sconscript_file_map.iteritems(): |
- if load_target(target): |
- sconscript_files.append(sconscript) |
- |
- |
-target_alias_list= [] |
- |
-conf_list = GetOption('conf_list') |
-if conf_list: |
- # In case the same --mode= value was specified multiple times. |
- conf_list = list(set(conf_list)) |
-else: |
- conf_list = [%(default_configuration)r] |
- |
-sconsbuild_dir = Dir(%(sconsbuild_dir)s) |
- |
- |
-def FilterOut(self, **kw): |
- kw = SCons.Environment.copy_non_reserved_keywords(kw) |
- for key, val in kw.items(): |
- envval = self.get(key, None) |
- if envval is None: |
- # No existing variable in the environment, so nothing to delete. |
- continue |
- |
- for vremove in val: |
- # Use while not if, so we can handle duplicates. |
- while vremove in envval: |
- envval.remove(vremove) |
- |
- self[key] = envval |
- |
- # TODO(sgk): SCons.Environment.Append() has much more logic to deal |
- # with various types of values. We should handle all those cases in here |
- # too. (If variable is a dict, etc.) |
- |
- |
-non_compilable_suffixes = { |
- 'LINUX' : set([ |
- '.bdic', |
- '.css', |
- '.dat', |
- '.fragment', |
- '.gperf', |
- '.h', |
- '.hh', |
- '.hpp', |
- '.html', |
- '.hxx', |
- '.idl', |
- '.in', |
- '.in0', |
- '.in1', |
- '.js', |
- '.mk', |
- '.rc', |
- '.sigs', |
- '', |
- ]), |
- 'WINDOWS' : set([ |
- '.h', |
- '.hh', |
- '.hpp', |
- '.dat', |
- '.idl', |
- '.in', |
- '.in0', |
- '.in1', |
- ]), |
-} |
- |
-def compilable(env, file): |
- base, ext = os.path.splitext(str(file)) |
- if ext in non_compilable_suffixes[env['TARGET_PLATFORM']]: |
- return False |
- return True |
- |
-def compilable_files(env, sources): |
- return [x for x in sources if compilable(env, x)] |
- |
-def GypProgram(env, target, source, *args, **kw): |
- source = compilable_files(env, source) |
- result = env.Program(target, source, *args, **kw) |
- if env.get('INCREMENTAL'): |
- env.Precious(result) |
- return result |
- |
-def GypTestProgram(env, target, source, *args, **kw): |
- source = compilable_files(env, source) |
- result = env.Program(target, source, *args, **kw) |
- if env.get('INCREMENTAL'): |
- env.Precious(*result) |
- return result |
- |
-def GypLibrary(env, target, source, *args, **kw): |
- source = compilable_files(env, source) |
- result = env.Library(target, source, *args, **kw) |
- return result |
- |
-def GypLoadableModule(env, target, source, *args, **kw): |
- source = compilable_files(env, source) |
- result = env.LoadableModule(target, source, *args, **kw) |
- return result |
- |
-def GypStaticLibrary(env, target, source, *args, **kw): |
- source = compilable_files(env, source) |
- result = env.StaticLibrary(target, source, *args, **kw) |
- return result |
- |
-def GypSharedLibrary(env, target, source, *args, **kw): |
- source = compilable_files(env, source) |
- result = env.SharedLibrary(target, source, *args, **kw) |
- if env.get('INCREMENTAL'): |
- env.Precious(result) |
- return result |
- |
-def add_gyp_methods(env): |
- env.AddMethod(GypProgram) |
- env.AddMethod(GypTestProgram) |
- env.AddMethod(GypLibrary) |
- env.AddMethod(GypLoadableModule) |
- env.AddMethod(GypStaticLibrary) |
- env.AddMethod(GypSharedLibrary) |
- |
- env.AddMethod(FilterOut) |
- |
- env.AddMethod(compilable) |
- |
- |
-base_env = Environment( |
- tools = %(scons_tools)s, |
- INTERMEDIATE_DIR='$OBJ_DIR/${COMPONENT_NAME}/_${TARGET_NAME}_intermediate', |
- LIB_DIR='$TOP_BUILDDIR/lib', |
- OBJ_DIR='$TOP_BUILDDIR/obj', |
- SCONSBUILD_DIR=sconsbuild_dir.abspath, |
- SHARED_INTERMEDIATE_DIR='$OBJ_DIR/_global_intermediate', |
- SRC_DIR=Dir(%(src_dir)r), |
- TARGET_PLATFORM='LINUX', |
- TOP_BUILDDIR='$SCONSBUILD_DIR/$CONFIG_NAME', |
- LIBPATH=['$LIB_DIR'], |
-) |
- |
-if not GetOption('verbose'): |
- base_env.SetDefault( |
- ARCOMSTR='Creating library $TARGET', |
- ASCOMSTR='Assembling $TARGET', |
- CCCOMSTR='Compiling $TARGET', |
- CONCATSOURCECOMSTR='ConcatSource $TARGET', |
- CXXCOMSTR='Compiling $TARGET', |
- LDMODULECOMSTR='Building loadable module $TARGET', |
- LINKCOMSTR='Linking $TARGET', |
- MANIFESTCOMSTR='Updating manifest for $TARGET', |
- MIDLCOMSTR='Compiling IDL $TARGET', |
- PCHCOMSTR='Precompiling $TARGET', |
- RANLIBCOMSTR='Indexing $TARGET', |
- RCCOMSTR='Compiling resource $TARGET', |
- SHCCCOMSTR='Compiling $TARGET', |
- SHCXXCOMSTR='Compiling $TARGET', |
- SHLINKCOMSTR='Linking $TARGET', |
- SHMANIFESTCOMSTR='Updating manifest for $TARGET', |
- ) |
- |
-add_gyp_methods(base_env) |
- |
-for conf in conf_list: |
- env = base_env.Clone(CONFIG_NAME=conf) |
- SConsignFile(env.File('$TOP_BUILDDIR/.sconsign').abspath) |
- for sconscript in sconscript_files: |
- target_alias = env.SConscript(sconscript, exports=['env']) |
- if target_alias: |
- target_alias_list.extend(target_alias) |
- |
-Default(Alias('all', target_alias_list)) |
- |
-help_fmt = ''' |
-Usage: hammer [SCONS_OPTIONS] [VARIABLES] [TARGET] ... |
- |
-Local command-line build options: |
- --mode=CONFIG Configuration to build: |
- --mode=Debug [default] |
- --mode=Release |
- --verbose Print actual executed command lines. |
- |
-Supported command-line build variables: |
- LOAD=[module,...] Comma-separated list of components to load in the |
- dependency graph ('-' prefix excludes) |
- PROGRESS=type Display a progress indicator: |
- name: print each evaluated target name |
- spinner: print a spinner every 5 targets |
- |
-The following TARGET names can also be used as LOAD= module names: |
- |
-%%s |
-''' |
- |
-if GetOption('help'): |
- def columnar_text(items, width=78, indent=2, sep=2): |
- result = [] |
- colwidth = max(map(len, items)) + sep |
- cols = (width - indent) / colwidth |
- if cols < 1: |
- cols = 1 |
- rows = (len(items) + cols - 1) / cols |
- indent = '%%*s' %% (indent, '') |
- sep = indent |
- for row in xrange(0, rows): |
- result.append(sep) |
- for i in xrange(row, len(items), rows): |
- result.append('%%-*s' %% (colwidth, items[i])) |
- sep = '\\n' + indent |
- result.append('\\n') |
- return ''.join(result) |
- |
- load_list = set(sconscript_file_map.keys()) |
- target_aliases = set(map(str, target_alias_list)) |
- |
- common = load_list and target_aliases |
- load_only = load_list - common |
- target_only = target_aliases - common |
- help_text = [help_fmt %% columnar_text(sorted(list(common)))] |
- if target_only: |
- fmt = "The following are additional TARGET names:\\n\\n%%s\\n" |
- help_text.append(fmt %% columnar_text(sorted(list(target_only)))) |
- if load_only: |
- fmt = "The following are additional LOAD= module names:\\n\\n%%s\\n" |
- help_text.append(fmt %% columnar_text(sorted(list(load_only)))) |
- Help(''.join(help_text)) |
-""" |
- |
-# TEMPLATE END |
-############################################################################# |
- |
- |
-def GenerateSConscriptWrapper(build_file, build_file_data, name, |
- output_filename, sconscript_files, |
- default_configuration): |
- """ |
- Generates the "wrapper" SConscript file (analogous to the Visual Studio |
- solution) that calls all the individual target SConscript files. |
- """ |
- output_dir = os.path.dirname(output_filename) |
- src_dir = build_file_data['_DEPTH'] |
- src_dir_rel = gyp.common.RelativePath(src_dir, output_dir) |
- if not src_dir_rel: |
- src_dir_rel = '.' |
- scons_settings = build_file_data.get('scons_settings', {}) |
- sconsbuild_dir = scons_settings.get('sconsbuild_dir', '#') |
- scons_tools = scons_settings.get('tools', ['default']) |
- |
- sconscript_file_lines = ['dict('] |
- for target in sorted(sconscript_files.keys()): |
- sconscript = sconscript_files[target] |
- sconscript_file_lines.append(' %s = %r,' % (target, sconscript)) |
- sconscript_file_lines.append(')') |
- |
- fp = open(output_filename, 'w') |
- fp.write(header) |
- fp.write(_wrapper_template % { |
- 'default_configuration' : default_configuration, |
- 'name' : name, |
- 'scons_tools' : repr(scons_tools), |
- 'sconsbuild_dir' : repr(sconsbuild_dir), |
- 'sconscript_files' : '\n'.join(sconscript_file_lines), |
- 'src_dir' : src_dir_rel, |
- }) |
- fp.close() |
- |
- # Generate the SConstruct file that invokes the wrapper SConscript. |
- dir, fname = os.path.split(output_filename) |
- SConstruct = os.path.join(dir, 'SConstruct') |
- fp = open(SConstruct, 'w') |
- fp.write(header) |
- fp.write('SConscript(%s)\n' % repr(fname)) |
- fp.close() |
- |
- |
-def TargetFilename(target, build_file=None, output_suffix=''): |
- """Returns the .scons file name for the specified target. |
- """ |
- if build_file is None: |
- build_file, target = gyp.common.ParseQualifiedTarget(target)[:2] |
- output_file = os.path.join(os.path.dirname(build_file), |
- target + output_suffix + '.scons') |
- return output_file |
- |
- |
-def PerformBuild(data, configurations, params): |
- options = params['options'] |
- |
- # Due to the way we test gyp on the chromium typbots |
- # we need to look for 'scons.py' as well as the more common 'scons' |
- # TODO(sbc): update the trybots to have a more normal install |
- # of scons. |
- scons = 'scons' |
- paths = os.environ['PATH'].split(os.pathsep) |
- for scons_name in ['scons', 'scons.py']: |
- for path in paths: |
- test_scons = os.path.join(path, scons_name) |
- print 'looking for: %s' % test_scons |
- if os.path.exists(test_scons): |
- print "found scons: %s" % scons |
- scons = test_scons |
- break |
- |
- for config in configurations: |
- arguments = [scons, '-C', options.toplevel_dir, '--mode=%s' % config] |
- print "Building [%s]: %s" % (config, arguments) |
- subprocess.check_call(arguments) |
- |
- |
-def GenerateOutput(target_list, target_dicts, data, params): |
- """ |
- Generates all the output files for the specified targets. |
- """ |
- options = params['options'] |
- |
- if options.generator_output: |
- def output_path(filename): |
- return filename.replace(params['cwd'], options.generator_output) |
- else: |
- def output_path(filename): |
- return filename |
- |
- default_configuration = None |
- |
- for qualified_target in target_list: |
- spec = target_dicts[qualified_target] |
- if spec['toolset'] != 'target': |
- raise Exception( |
- 'Multiple toolsets not supported in scons build (target %s)' % |
- qualified_target) |
- scons_target = SCons.Target(spec) |
- if scons_target.is_ignored: |
- continue |
- |
- # TODO: assumes the default_configuration of the first target |
- # non-Default target is the correct default for all targets. |
- # Need a better model for handle variation between targets. |
- if (not default_configuration and |
- spec['default_configuration'] != 'Default'): |
- default_configuration = spec['default_configuration'] |
- |
- build_file, target = gyp.common.ParseQualifiedTarget(qualified_target)[:2] |
- output_file = TargetFilename(target, build_file, options.suffix) |
- if options.generator_output: |
- output_file = output_path(output_file) |
- |
- if not spec.has_key('libraries'): |
- spec['libraries'] = [] |
- |
- # Add dependent static library targets to the 'libraries' value. |
- deps = spec.get('dependencies', []) |
- spec['scons_dependencies'] = [] |
- for d in deps: |
- td = target_dicts[d] |
- target_name = td['target_name'] |
- spec['scons_dependencies'].append("Alias('%s')" % target_name) |
- if td['type'] in ('static_library', 'shared_library'): |
- libname = td.get('product_name', target_name) |
- spec['libraries'].append('lib' + libname) |
- if td['type'] == 'loadable_module': |
- prereqs = spec.get('scons_prerequisites', []) |
- # TODO: parameterize with <(SHARED_LIBRARY_*) variables? |
- td_target = SCons.Target(td) |
- td_target.target_prefix = '${SHLIBPREFIX}' |
- td_target.target_suffix = '${SHLIBSUFFIX}' |
- |
- GenerateSConscript(output_file, spec, build_file, data[build_file]) |
- |
- if not default_configuration: |
- default_configuration = 'Default' |
- |
- for build_file in sorted(data.keys()): |
- path, ext = os.path.splitext(build_file) |
- if ext != '.gyp': |
- continue |
- output_dir, basename = os.path.split(path) |
- output_filename = path + '_main' + options.suffix + '.scons' |
- |
- all_targets = gyp.common.AllTargets(target_list, target_dicts, build_file) |
- sconscript_files = {} |
- for t in all_targets: |
- scons_target = SCons.Target(target_dicts[t]) |
- if scons_target.is_ignored: |
- continue |
- bf, target = gyp.common.ParseQualifiedTarget(t)[:2] |
- target_filename = TargetFilename(target, bf, options.suffix) |
- tpath = gyp.common.RelativePath(target_filename, output_dir) |
- sconscript_files[target] = tpath |
- |
- output_filename = output_path(output_filename) |
- if sconscript_files: |
- GenerateSConscriptWrapper(build_file, data[build_file], basename, |
- output_filename, sconscript_files, |
- default_configuration) |