| 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)
|
|
|