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