def GenerateOutput(target_list, target_dicts, data, params): """ Generates all the output structure 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)
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 structure 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['structure'] except KeyError, e: gyp.common.ExceptionAppend( e, "Required 'structure' key missing for 'copies' in %s." % build_file) raise