diff options
author | Ben Noordhuis <info@bnoordhuis.nl> | 2012-07-03 20:56:06 +0200 |
---|---|---|
committer | Ben Noordhuis <info@bnoordhuis.nl> | 2012-07-03 20:56:35 +0200 |
commit | fc4e12b8f1d6e3ea010d575504011deee2a97b16 (patch) | |
tree | b7ef6352a6c36c26c5446c1a803d9d1bf462d163 /tools/gyp/pylib/gyp/generator/ninja.py | |
parent | 5da78905a6c82aa40f79ae8ff1a74a53bc89a588 (diff) | |
download | android-node-v8-fc4e12b8f1d6e3ea010d575504011deee2a97b16.tar.gz android-node-v8-fc4e12b8f1d6e3ea010d575504011deee2a97b16.tar.bz2 android-node-v8-fc4e12b8f1d6e3ea010d575504011deee2a97b16.zip |
tools: update gyp to r1426
Diffstat (limited to 'tools/gyp/pylib/gyp/generator/ninja.py')
-rw-r--r-- | tools/gyp/pylib/gyp/generator/ninja.py | 715 |
1 files changed, 548 insertions, 167 deletions
diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py index 8d6c6f52d1..6f6fc5bb64 100644 --- a/tools/gyp/pylib/gyp/generator/ninja.py +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -5,8 +5,11 @@ import copy import gyp import gyp.common +import gyp.msvs_emulation +import gyp.MSVSVersion import gyp.system_test import gyp.xcode_emulation +import hashlib import os.path import re import subprocess @@ -43,11 +46,16 @@ generator_default_variables = { 'RULE_INPUT_NAME': '${name}', } -# TODO: enable cross compiling once we figure out: -# - how to not build extra host objects in the non-cross-compile case. -# - how to decide what the host compiler is (should not just be $cc). -# - need ld_host as well. -generator_supports_multiple_toolsets = False +# Placates pylint. +generator_additional_non_configuration_keys = [] +generator_additional_path_sections = [] +generator_extra_sources_for_rules = [] + +# TODO: figure out how to not build extra host objects in the non-cross-compile +# case when this is enabled, and enable unconditionally. +generator_supports_multiple_toolsets = ( + os.environ.get('AR_target') or os.environ.get('CC_target') or + os.environ.get('CXX_target')) def StripPrefix(arg, prefix): @@ -56,16 +64,28 @@ def StripPrefix(arg, prefix): return arg -def QuoteShellArgument(arg): +def QuoteShellArgument(arg, flavor): """Quote a string such that it will be interpreted as a single argument by the shell.""" # Rather than attempting to enumerate the bad shell characters, just # whitelist common OK ones and quote anything else. - if re.match(r'^[a-zA-Z0-9_=-]+$', arg): + if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg): return arg # No quoting necessary. + if flavor == 'win': + return gyp.msvs_emulation.QuoteForRspFile(arg) return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'" +def Define(d, flavor): + """Takes a preprocessor define and returns a -D parameter that's ninja- and + shell-escaped.""" + if flavor == 'win': + # cl.exe replaces literal # characters with = in preprocesor definitions for + # some reason. Octal-encode to work around that. + d = d.replace('#', '\\%03o' % ord('#')) + return QuoteShellArgument(ninja_syntax.escape('-D' + d), flavor) + + def InvertRelativePath(path): """Given a relative path like foo/bar, return the inverse relative path: the path from the relative path back to the origin dir. @@ -120,14 +140,34 @@ class Target: # Path to the file representing the completion of building the bundle, # if any. self.bundle = None + # On Windows, incremental linking requires linking against all the .objs + # that compose a .lib (rather than the .lib itself). That list is stored + # here. + self.component_objs = None + # Windows only. The import .lib is the output of a build step, but + # because dependents only link against the lib (not both the lib and the + # dll) we keep track of the import library here. + self.import_lib = None def Linkable(self): """Return true if this is a target that can be linked against.""" return self.type in ('static_library', 'shared_library') - def PreActionInput(self): + def UsesToc(self, flavor): + """Return true if the target should produce a restat rule based on a TOC + file.""" + # For bundles, the .TOC should be produced for the binary, not for + # FinalOutput(). But the naive approach would put the TOC file into the + # bundle, so don't do this for bundles for now. + if flavor == 'win' or self.bundle: + return False + return self.type in ('shared_library', 'loadable_module') + + def PreActionInput(self, flavor): """Return the path, if any, that should be used as a dependency of any dependent action step.""" + if self.UsesToc(flavor): + return self.FinalOutput() + '.TOC' return self.FinalOutput() or self.preaction_stamp def PreCompileInput(self): @@ -162,12 +202,12 @@ class Target: # into the equivalent ninja path. # # - GypPathToUniqueOutput translates a gyp path into a ninja path to write -# an output file; the result can be namespaced such that is unique +# an output file; the result can be namespaced such that it is unique # to the input file name as well as the output target name. class NinjaWriter: - def __init__(self, target_outputs, base_dir, build_dir, output_file, flavor, - abs_build_dir=None): + def __init__(self, qualified_target, target_outputs, base_dir, build_dir, + output_file, flavor, abs_build_dir=None): """ base_dir: path from source root to directory containing this gyp file, by gyp semantics, all input paths are relative to this @@ -175,6 +215,7 @@ class NinjaWriter: abs_build_dir: absolute path to the build directory """ + self.qualified_target = qualified_target self.target_outputs = target_outputs self.base_dir = base_dir self.build_dir = build_dir @@ -182,6 +223,11 @@ class NinjaWriter: self.flavor = flavor self.abs_build_dir = abs_build_dir self.obj_ext = '.obj' if flavor == 'win' else '.o' + if flavor == 'win': + # See docstring of msvs_emulation.GenerateEnvironmentFiles(). + self.win_env = {} + for arch in ('x86', 'x64'): + self.win_env[arch] = 'environment.' + arch # Relative path from build output dir to base dir. self.build_to_base = os.path.join(InvertRelativePath(build_dir), base_dir) @@ -212,10 +258,12 @@ class NinjaWriter: # so insert product_dir in front if it is provided. path = path.replace(INTERMEDIATE_DIR, os.path.join(product_dir or '', int_dir)) - - return os.path.normpath(path) + return path def ExpandRuleVariables(self, path, root, dirname, source, ext, name): + if self.flavor == 'win': + path = self.msvs_settings.ConvertVSMacros( + path, config=self.config_name) path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root) path = path.replace(generator_default_variables['RULE_INPUT_DIRNAME'], dirname) @@ -230,9 +278,15 @@ class NinjaWriter: See the above discourse on path conversions.""" if env: - path = gyp.xcode_emulation.ExpandEnvVars(path, env) + if self.flavor == 'mac': + path = gyp.xcode_emulation.ExpandEnvVars(path, env) + elif self.flavor == 'win': + path = gyp.msvs_emulation.ExpandMacros(path, env) if path.startswith('$!'): - return self.ExpandSpecial(path) + expanded = self.ExpandSpecial(path) + if self.flavor == 'win': + expanded = os.path.normpath(expanded) + return expanded assert '$' not in path, path return os.path.normpath(os.path.join(self.build_to_base, path)) @@ -284,7 +338,7 @@ class NinjaWriter: self.ninja.newline() return targets[0] - def WriteSpec(self, spec, config_name): + def WriteSpec(self, spec, config_name, generator_flags): """The main entry point for NinjaWriter: write the build rules for a spec. Returns a Target object, which represents the output paths for this spec. @@ -298,10 +352,14 @@ class NinjaWriter: self.target = Target(spec['type']) self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec) + self.xcode_settings = self.msvs_settings = None if self.flavor == 'mac': self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec) - else: - self.xcode_settings = None + if self.flavor == 'win': + self.msvs_settings = gyp.msvs_emulation.MsvsSettings(spec, + generator_flags) + target_platform = self.msvs_settings.GetTargetPlatform(config_name) + self.ninja.variable('arch', self.win_env[target_platform]) # Compute predepends for all rules. # actions_depends is the dependencies this target depends on before running @@ -316,7 +374,7 @@ class NinjaWriter: for dep in spec['dependencies']: if dep in self.target_outputs: target = self.target_outputs[dep] - actions_depends.append(target.PreActionInput()) + actions_depends.append(target.PreActionInput(self.flavor)) compile_depends.append(target.PreCompileInput()) actions_depends = filter(None, actions_depends) compile_depends = filter(None, compile_depends) @@ -345,15 +403,23 @@ class NinjaWriter: link_deps = [] sources = spec.get('sources', []) + extra_sources if sources: + pch = None + if self.flavor == 'win': + pch = gyp.msvs_emulation.PrecompiledHeader( + self.msvs_settings, config_name, self.GypPathToNinja) + else: + pch = gyp.xcode_emulation.MacPrefixHeader( + self.xcode_settings, self.GypPathToNinja, + lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang)) link_deps = self.WriteSources( - config_name, config, sources, compile_depends_stamp, - gyp.xcode_emulation.MacPrefixHeader( - self.xcode_settings, self.GypPathToNinja, - lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang))) + config_name, config, sources, compile_depends_stamp, pch) # Some actions/rules output 'sources' that are already object files. link_deps += [self.GypPathToNinja(f) for f in sources if f.endswith(self.obj_ext)] + if self.flavor == 'win' and self.target.type == 'static_library': + self.target.component_objs = link_deps + # Write out a link step, if needed. output = None if link_deps or self.target.actions_stamp or actions_depends: @@ -369,15 +435,43 @@ class NinjaWriter: if not output: return None - if self.name != output and self.toolset == 'target': - # Write a short name to build this target. This benefits both the - # "build chrome" case as well as the gyp tests, which expect to be - # able to run actions and build libraries by their short name. - self.ninja.build(self.name, 'phony', output) - assert self.target.FinalOutput(), output return self.target + def _WinIdlRule(self, source, prebuild, outputs): + """Handle the implicit VS .idl rule for one source file. Fills |outputs| + with files that are generated.""" + outdir, output, vars, flags = self.msvs_settings.GetIdlBuildData( + source, self.config_name) + outdir = self.GypPathToNinja(outdir) + def fix_path(path, rel=None): + path = os.path.join(outdir, path) + dirname, basename = os.path.split(source) + root, ext = os.path.splitext(basename) + path = self.ExpandRuleVariables( + path, root, dirname, source, ext, basename) + if rel: + path = os.path.relpath(path, rel) + return path + vars = [(name, fix_path(value, outdir)) for name, value in vars] + output = [fix_path(p) for p in output] + vars.append(('outdir', outdir)) + vars.append(('idlflags', flags)) + input = self.GypPathToNinja(source) + self.ninja.build(output, 'idl', input, + variables=vars, order_only=prebuild) + outputs.extend(output) + + def WriteWinIdlFiles(self, spec, prebuild): + """Writes rules to match MSVS's implicit idl handling.""" + assert self.flavor == 'win' + if self.msvs_settings.HasExplicitIdlRules(spec): + return [] + outputs = [] + for source in filter(lambda x: x.endswith('.idl'), spec['sources']): + self._WinIdlRule(source, prebuild, outputs) + return outputs + def WriteActionsRulesCopies(self, spec, extra_sources, prebuild, mac_bundle_depends): """Write out the Actions, Rules, and Copies steps. Return a path @@ -394,6 +488,9 @@ class NinjaWriter: if 'copies' in spec: outputs += self.WriteCopies(spec['copies'], prebuild) + if 'sources' in spec and self.flavor == 'win': + outputs += self.WriteWinIdlFiles(spec, prebuild) + stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs) if self.is_mac_bundle: @@ -421,16 +518,26 @@ class NinjaWriter: def WriteActions(self, actions, extra_sources, prebuild, extra_mac_bundle_resources): # Actions cd into the base directory. - env = self.GetXcodeEnv() + env = self.GetSortedXcodeEnv() + if self.flavor == 'win': + env = self.msvs_settings.GetVSMacroEnv( + '$!PRODUCT_DIR', config=self.config_name) all_outputs = [] for action in actions: # First write out a rule for the action. - name = re.sub(r'[ {}$]', '_', action['action_name']) + name = '%s_%s' % (action['action_name'], + hashlib.md5(self.qualified_target).hexdigest()) description = self.GenerateDescription('ACTION', action.get('message', None), name) - rule_name = self.WriteNewNinjaRule(name, action['action'], description, - env=env) + is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(action) + if self.flavor == 'win' else False) + args = action['action'] + args = [self.msvs_settings.ConvertVSMacros( + arg, self.base_to_build, config=self.config_name) + for arg in args] if self.flavor == 'win' else args + rule_name = self.WriteNewNinjaRule(name, args, description, + is_cygwin, env=env) inputs = [self.GypPathToNinja(i, env) for i in action['inputs']] if int(action.get('process_outputs_as_sources', False)): @@ -453,13 +560,22 @@ class NinjaWriter: all_outputs = [] for rule in rules: # First write out a rule for the rule action. - name = rule['rule_name'] + name = '%s_%s' % (rule['rule_name'], + hashlib.md5(self.qualified_target).hexdigest()) + # Skip a rule with no action and no inputs. + if 'action' not in rule and not rule.get('rule_sources', []): + continue args = rule['action'] description = self.GenerateDescription( 'RULE', rule.get('message', None), ('%s ' + generator_default_variables['RULE_INPUT_PATH']) % name) - rule_name = self.WriteNewNinjaRule(name, args, description) + is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(rule) + if self.flavor == 'win' else False) + args = [self.msvs_settings.ConvertVSMacros( + arg, self.base_to_build, config=self.config_name) + for arg in args] if self.flavor == 'win' else args + rule_name = self.WriteNewNinjaRule(name, args, description, is_cygwin) # TODO: if the command references the outputs directly, we should # simplify it to just use $out. @@ -474,16 +590,23 @@ class NinjaWriter: if ('${%s}' % var) in argument: needed_variables.add(var) + def cygwin_munge(path): + if is_cygwin: + return path.replace('\\', '/') + return path + # For each source file, write an edge that generates all the outputs. for source in rule.get('rule_sources', []): dirname, basename = os.path.split(source) root, ext = os.path.splitext(basename) - # Gather the list of outputs, expanding $vars if possible. - outputs = [] - for output in rule['outputs']: - outputs.append(self.ExpandRuleVariables(output, root, dirname, - source, ext, basename)) + # Gather the list of inputs and outputs, expanding $vars if possible. + outputs = [self.ExpandRuleVariables(o, root, dirname, + source, ext, basename) + for o in rule['outputs']] + inputs = [self.ExpandRuleVariables(i, root, dirname, + source, ext, basename) + for i in rule.get('inputs', [])] if int(rule.get('process_outputs_as_sources', False)): extra_sources += outputs @@ -493,24 +616,26 @@ class NinjaWriter: extra_bindings = [] for var in needed_variables: if var == 'root': - extra_bindings.append(('root', root)) + extra_bindings.append(('root', cygwin_munge(root))) elif var == 'dirname': - extra_bindings.append(('dirname', dirname)) + extra_bindings.append(('dirname', cygwin_munge(dirname))) elif var == 'source': # '$source' is a parameter to the rule action, which means # it shouldn't be converted to a Ninja path. But we don't # want $!PRODUCT_DIR in there either. source_expanded = self.ExpandSpecial(source, self.base_to_build) - extra_bindings.append(('source', source_expanded)) + extra_bindings.append(('source', cygwin_munge(source_expanded))) elif var == 'ext': extra_bindings.append(('ext', ext)) elif var == 'name': - extra_bindings.append(('name', basename)) + extra_bindings.append(('name', cygwin_munge(basename))) else: assert var == None, repr(var) - inputs = map(self.GypPathToNinja, rule.get('inputs', [])) + inputs = map(self.GypPathToNinja, inputs) outputs = map(self.GypPathToNinja, outputs) + extra_bindings.append(('unique_name', + re.sub('[^a-zA-Z0-9_]', '_', outputs[0]))) self.ninja.build(outputs, rule_name, self.GypPathToNinja(source), implicit=inputs, order_only=prebuild, @@ -522,7 +647,7 @@ class NinjaWriter: def WriteCopies(self, copies, prebuild): outputs = [] - env = self.GetXcodeEnv() + env = self.GetSortedXcodeEnv() for copy in copies: for path in copy['files']: # Normalize the path so trailing slashes don't confuse us. @@ -555,12 +680,11 @@ class NinjaWriter: # Create an intermediate file to store preprocessed results. intermediate_plist = self.GypPathToUniqueOutput( os.path.basename(info_plist)) - defines = ' '.join( - [QuoteShellArgument(ninja_syntax.escape('-D' + d)) for d in defines]) + defines = ' '.join([Define(d, self.flavor) for d in defines]) info_plist = self.ninja.build(intermediate_plist, 'infoplist', info_plist, variables=[('defines',defines)]) - env = self.GetXcodeEnv(additional_settings=extra_env) + env = self.GetSortedXcodeEnv(additional_settings=extra_env) env = self.ComputeExportEnvString(env) self.ninja.build(out, 'mac_tool', info_plist, @@ -571,10 +695,13 @@ class NinjaWriter: def WriteSources(self, config_name, config, sources, predepends, precompiled_header): """Write build rules to compile all of |sources|.""" - if self.toolset == 'host': - self.ninja.variable('cc', '$cc_host') - self.ninja.variable('cxx', '$cxx_host') + if self.toolset == 'target': + self.ninja.variable('ar', '$ar_target') + self.ninja.variable('cc', '$cc_target') + self.ninja.variable('cxx', '$cxx_target') + self.ninja.variable('ld', '$ld_target') + extra_defines = [] if self.flavor == 'mac': cflags = self.xcode_settings.GetCflags(config_name) cflags_c = self.xcode_settings.GetCflagsC(config_name) @@ -583,19 +710,35 @@ class NinjaWriter: self.xcode_settings.GetCflagsObjC(config_name) cflags_objcc = ['$cflags_cc'] + \ self.xcode_settings.GetCflagsObjCC(config_name) + elif self.flavor == 'win': + cflags = self.msvs_settings.GetCflags(config_name) + cflags_c = self.msvs_settings.GetCflagsC(config_name) + cflags_cc = self.msvs_settings.GetCflagsCC(config_name) + extra_defines = self.msvs_settings.GetComputedDefines(config_name) + self.WriteVariableList('pdbname', [self.name + '.pdb']) + self.WriteVariableList('pchprefix', [self.name]) else: cflags = config.get('cflags', []) cflags_c = config.get('cflags_c', []) cflags_cc = config.get('cflags_cc', []) - self.WriteVariableList('defines', - [QuoteShellArgument(ninja_syntax.escape('-D' + d)) - for d in config.get('defines', [])]) + defines = config.get('defines', []) + extra_defines + self.WriteVariableList('defines', [Define(d, self.flavor) for d in defines]) + if self.flavor == 'win': + self.WriteVariableList('rcflags', + [QuoteShellArgument(self.ExpandSpecial(f), self.flavor) + for f in self.msvs_settings.GetRcflags(config_name, + self.GypPathToNinja)]) + + include_dirs = config.get('include_dirs', []) + if self.flavor == 'win': + include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs, + config_name) self.WriteVariableList('includes', - ['-I' + self.GypPathToNinja(i) - for i in config.get('include_dirs', [])]) + [QuoteShellArgument('-I' + self.GypPathToNinja(i), self.flavor) + for i in include_dirs]) - pch_commands = precompiled_header.GetGchBuildCommands() + pch_commands = precompiled_header.GetPchBuildCommands() if self.flavor == 'mac': self.WriteVariableList('cflags_pch_c', [precompiled_header.GetInclude('c')]) @@ -619,19 +762,30 @@ class NinjaWriter: for source in sources: filename, ext = os.path.splitext(source) ext = ext[1:] + obj_ext = self.obj_ext if ext in ('cc', 'cpp', 'cxx'): command = 'cxx' - elif ext in ('c', 's', 'S'): + elif ext == 'c' or (ext in ('s', 'S') and self.flavor != 'win'): command = 'cc' + elif (self.flavor == 'win' and ext == 'asm' and + self.msvs_settings.GetTargetPlatform(config_name) == 'Win32'): + # Asm files only get auto assembled for x86 (not x64). + command = 'asm' + # Add the _asm suffix as msvs is capable of handling .cc and + # .asm files of the same name without collision. + obj_ext = '_asm.obj' elif self.flavor == 'mac' and ext == 'm': command = 'objc' elif self.flavor == 'mac' and ext == 'mm': command = 'objcxx' + elif self.flavor == 'win' and ext == 'rc': + command = 'rc' + obj_ext = '.res' else: - # TODO: should we assert here on unexpected extensions? + # Ignore unhandled extensions. continue input = self.GypPathToNinja(source) - output = self.GypPathToUniqueOutput(filename + self.obj_ext) + output = self.GypPathToUniqueOutput(filename + obj_ext) implicit = precompiled_header.GetObjDependencies([input], [output]) self.ninja.build(output, command, input, implicit=[gch for _, _, gch in implicit], @@ -656,12 +810,14 @@ class NinjaWriter: 'mm': 'cflags_pch_objcc', }[lang] - cmd = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', }.get(lang) + map = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', } + if self.flavor == 'win': + map.update({'c': 'cc_pch', 'cc': 'cxx_pch'}) + cmd = map.get(lang) self.ninja.build(gch, cmd, input, variables=[(var_name, lang_flag)]) - def WriteLink(self, spec, config_name, config, link_deps): - """Write out a link step. Returns the path to the output.""" + """Write out a link step. Fills out target.binary. """ command = { 'executable': 'link', @@ -670,6 +826,7 @@ class NinjaWriter: }[spec['type']] implicit_deps = set() + solibs = set() if 'dependencies' in spec: # Two kinds of dependencies: @@ -683,7 +840,17 @@ class NinjaWriter: continue linkable = target.Linkable() if linkable: - extra_link_deps.add(target.binary) + if (self.flavor == 'win' and + target.component_objs and + self.msvs_settings.IsUseLibraryDependencyInputs(config_name)): + extra_link_deps |= set(target.component_objs) + elif self.flavor == 'win' and target.import_lib: + extra_link_deps.add(target.import_lib) + elif target.UsesToc(self.flavor): + solibs.add(target.binary) + implicit_deps.add(target.binary + '.TOC') + else: + extra_link_deps.add(target.binary) final_output = target.FinalOutput() if not linkable or final_output != target.binary: @@ -703,6 +870,13 @@ class NinjaWriter: ldflags = self.xcode_settings.GetLdflags(config_name, self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), self.GypPathToNinja) + elif self.flavor == 'win': + libflags = self.msvs_settings.GetLibFlags(config_name, + self.GypPathToNinja) + self.WriteVariableList( + 'libflags', gyp.common.uniquer(map(self.ExpandSpecial, libflags))) + ldflags = self.msvs_settings.GetLdflags(config_name, + self.GypPathToNinja, self.ExpandSpecial) else: ldflags = config.get('ldflags', []) self.WriteVariableList('ldflags', @@ -713,15 +887,30 @@ class NinjaWriter: spec.get('libraries', []))) if self.flavor == 'mac': libraries = self.xcode_settings.AdjustLibraries(libraries) + elif self.flavor == 'win': + libraries = self.msvs_settings.AdjustLibraries(libraries) self.WriteVariableList('libs', libraries) + self.target.binary = output + if command in ('solink', 'solink_module'): extra_bindings.append(('soname', os.path.split(output)[1])) + extra_bindings.append(('lib', + gyp.common.EncodePOSIXShellArgument(output))) + if self.flavor == 'win': + self.target.import_lib = output + '.lib' + extra_bindings.append(('dll', output)) + extra_bindings.append(('implib', self.target.import_lib)) + output = [output, self.target.import_lib] + else: + output = [output, output + '.TOC'] + + if len(solibs): + extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs))) self.ninja.build(output, command, link_deps, implicit=list(implicit_deps), variables=extra_bindings) - return output def WriteTarget(self, spec, config_name, config, link_deps, compile_deps): if spec['type'] == 'none': @@ -735,7 +924,7 @@ class NinjaWriter: variables=[('postbuilds', self.GetPostbuildCommand( spec, self.target.binary, self.target.binary))]) else: - self.target.binary = self.WriteLink(spec, config_name, config, link_deps) + self.WriteLink(spec, config_name, config, link_deps) return self.target.binary def WriteMacBundle(self, spec, mac_bundle_depends): @@ -757,16 +946,16 @@ class NinjaWriter: self.target.bundle = output return output - def GetXcodeEnv(self, additional_settings=None): + def GetSortedXcodeEnv(self, additional_settings=None): """Returns the variables Xcode would set for build steps.""" assert self.abs_build_dir abs_build_dir = self.abs_build_dir - return gyp.xcode_emulation.GetXcodeEnv( + return gyp.xcode_emulation.GetSortedXcodeEnv( self.xcode_settings, abs_build_dir, os.path.join(abs_build_dir, self.build_to_base), self.config_name, additional_settings) - def GetXcodePostbuildEnv(self): + def GetSortedXcodePostbuildEnv(self): """Returns the variables Xcode would set for postbuild steps.""" postbuild_settings = {} # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack. @@ -774,9 +963,8 @@ class NinjaWriter: strip_save_file = self.xcode_settings.GetPerTargetSetting( 'CHROMIUM_STRIP_SAVE_FILE') if strip_save_file: - postbuild_settings['CHROMIUM_STRIP_SAVE_FILE'] = self.GypPathToNinja( - strip_save_file) - return self.GetXcodeEnv(additional_settings=postbuild_settings) + postbuild_settings['CHROMIUM_STRIP_SAVE_FILE'] = strip_save_file + return self.GetSortedXcodeEnv(additional_settings=postbuild_settings) def GetPostbuildCommand(self, spec, output, output_binary, is_command_start=False): @@ -785,20 +973,31 @@ class NinjaWriter: returned string will start with ' && '.""" if not self.xcode_settings or spec['type'] == 'none' or not output: return '' - output = QuoteShellArgument(output) + output = QuoteShellArgument(output, self.flavor) target_postbuilds = self.xcode_settings.GetTargetPostbuilds( - self.config_name, output, QuoteShellArgument(output_binary), quiet=True) - postbuilds = gyp.xcode_emulation.GetSpecPostbuildCommands( - spec, self.GypPathToNinja, quiet=True) + self.config_name, + os.path.normpath(os.path.join(self.base_to_build, output)), + QuoteShellArgument( + os.path.normpath(os.path.join(self.base_to_build, output_binary)), + self.flavor), + quiet=True) + postbuilds = gyp.xcode_emulation.GetSpecPostbuildCommands(spec, quiet=True) postbuilds = target_postbuilds + postbuilds if not postbuilds: return '' - env = self.ComputeExportEnvString(self.GetXcodePostbuildEnv()) - commands = env + ' F=0; ' + \ + # Postbuilds expect to be run in the gyp file's directory, so insert an + # implicit postbuild to cd to there. + postbuilds.insert(0, gyp.common.EncodePOSIXShellList( + ['cd', self.build_to_base])) + env = self.ComputeExportEnvString(self.GetSortedXcodePostbuildEnv()) + # G will be non-null if any postbuild fails. Run all postbuilds in a + # subshell. + commands = env + ' (F=0; ' + \ ' '.join([ninja_syntax.escape(command) + ' || F=$$?;' for command in postbuilds]) - command_string = env + commands + ' ((exit $$F) || rm -rf %s) ' % output + \ - '&& exit $$F)' + command_string = (commands + ' exit $$F); G=$$?; ' + # Remove the final output if any postbuild failed. + '((exit $$G) || rm -rf %s) ' % output + '&& exit $$G)') if is_command_start: return '(' + command_string + ' && ' else: @@ -809,9 +1008,9 @@ class NinjaWriter: 'export FOO=foo; export BAR="${FOO} bar;' that exports |env| to the shell.""" export_str = [] - for k in gyp.xcode_emulation.TopologicallySortedEnvVarKeys(env): + for k, v in env: export_str.append('export %s=%s;' % - (k, ninja_syntax.escape(gyp.common.EncodePOSIXShellArgument(env[k])))) + (k, ninja_syntax.escape(gyp.common.EncodePOSIXShellArgument(v)))) return ' '.join(export_str) def ComputeMacBundleOutput(self): @@ -874,7 +1073,7 @@ class NinjaWriter: elif type == 'none': return '%s.stamp' % target else: - raise 'Unhandled output type', type + raise Exception('Unhandled output type %s' % type) def ComputeOutput(self, spec, type=None): """Compute the path for the final output of the spec.""" @@ -883,6 +1082,12 @@ class NinjaWriter: if not type: type = spec['type'] + if self.flavor == 'win': + override = self.msvs_settings.GetOutputName(self.config_name, + self.ExpandSpecial) + if override: + return override + if self.flavor == 'mac' and type in ( 'static_library', 'executable', 'shared_library', 'loadable_module'): filename = self.xcode_settings.GetExecutablePath() @@ -898,6 +1103,8 @@ class NinjaWriter: type_in_output_root = ['executable', 'loadable_module'] if self.flavor == 'mac' and self.toolset == 'target': type_in_output_root += ['shared_library', 'static_library'] + elif self.flavor == 'win' and self.toolset == 'target': + type_in_output_root += ['shared_library'] if type in type_in_output_root: return filename @@ -910,11 +1117,12 @@ class NinjaWriter: return self.GypPathToUniqueOutput(filename, qualified=False) def WriteVariableList(self, var, values): + assert not isinstance(values, str) if values is None: values = [] self.ninja.variable(var, ' '.join(values)) - def WriteNewNinjaRule(self, name, args, description, env={}): + def WriteNewNinjaRule(self, name, args, description, is_cygwin, env={}): """Write out a new ninja "rule" statement for a given command. Returns the name of the new rule.""" @@ -926,36 +1134,45 @@ class NinjaWriter: if self.toolset == 'target': rule_name += '.' + self.toolset rule_name += '.' + name - rule_name = rule_name.replace(' ', '_') + rule_name = re.sub('[^a-zA-Z0-9_]', '_', rule_name) args = args[:] + if self.flavor == 'win': + description = self.msvs_settings.ConvertVSMacros( + description, config=self.config_name) + # gyp dictates that commands are run from the base directory. # cd into the directory before running, and adjust paths in # the arguments to point to the proper locations. - if self.flavor == 'win': - cd = 'cmd /s /c "cd %s && ' % self.build_to_base - else: - cd = 'cd %s; ' % self.build_to_base + rspfile = None + rspfile_content = None args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args] - env = self.ComputeExportEnvString(env) if self.flavor == 'win': - # TODO(scottmg): Really don't want encourage cygwin, but I'm not sure - # how much sh is depended upon. For now, double quote args to make most - # things work. - command = args[0] + ' "' + '" "'.join(args[1:]) + '""' + rspfile = rule_name + '.$unique_name.rsp' + # The cygwin case handles this inside the bash sub-shell. + run_in = '' if is_cygwin else ' ' + self.build_to_base + if is_cygwin: + rspfile_content = self.msvs_settings.BuildCygwinBashCommandLine( + args, self.build_to_base) + else: + rspfile_content = gyp.msvs_emulation.EncodeRspFileList(args) + command = ('%s gyp-win-tool action-wrapper $arch ' % sys.executable + + rspfile + run_in) else: + env = self.ComputeExportEnvString(env) command = gyp.common.EncodePOSIXShellList(args) - if env: - # If an environment is passed in, variables in the command should be - # read from it, instead of from ninja's internal variables. - command = ninja_syntax.escape(command) + if env: + # If an environment is passed in, variables in the command should be + # read from it, instead of from ninja's internal variables. + command = ninja_syntax.escape(command) + command = 'cd %s; ' % self.build_to_base + env + command - command = cd + env + command # GYP rules/actions express being no-ops by not touching their outputs. # Avoid executing downstream dependencies in this case by specifying # restat=1 to ninja. - self.ninja.rule(rule_name, command, description, restat=True) + self.ninja.rule(rule_name, command, description, restat=True, + rspfile=rspfile, rspfile_content=rspfile_content) self.ninja.newline() return rule_name @@ -963,6 +1180,8 @@ class NinjaWriter: def CalculateVariables(default_variables, params): """Calculate additional variables for use in the build (called by gyp).""" + global generator_additional_non_configuration_keys + global generator_additional_path_sections cc_target = os.environ.get('CC.target', os.environ.get('CC', 'cc')) flavor = gyp.common.GetFlavor(params) if flavor == 'mac': @@ -976,22 +1195,43 @@ def CalculateVariables(default_variables, params): # Copy additional generator configuration data from Xcode, which is shared # by the Mac Ninja generator. import gyp.generator.xcode as xcode_generator - global generator_additional_non_configuration_keys generator_additional_non_configuration_keys = getattr(xcode_generator, 'generator_additional_non_configuration_keys', []) - global generator_additional_path_sections generator_additional_path_sections = getattr(xcode_generator, 'generator_additional_path_sections', []) global generator_extra_sources_for_rules generator_extra_sources_for_rules = getattr(xcode_generator, 'generator_extra_sources_for_rules', []) elif flavor == 'win': - default_variables['OS'] = 'win' + default_variables.setdefault('OS', 'win') default_variables['EXECUTABLE_SUFFIX'] = '.exe' default_variables['STATIC_LIB_PREFIX'] = '' default_variables['STATIC_LIB_SUFFIX'] = '.lib' default_variables['SHARED_LIB_PREFIX'] = '' default_variables['SHARED_LIB_SUFFIX'] = '.dll' + generator_flags = params.get('generator_flags', {}) + + # Copy additional generator configuration data from VS, which is shared + # by the Windows Ninja generator. + import gyp.generator.msvs as msvs_generator + generator_additional_non_configuration_keys = getattr(msvs_generator, + 'generator_additional_non_configuration_keys', []) + generator_additional_path_sections = getattr(msvs_generator, + 'generator_additional_path_sections', []) + + # Set a variable so conditions can be based on msvs_version. + msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags) + default_variables['MSVS_VERSION'] = msvs_version.ShortName() + + # To determine processor word size on Windows, in addition to checking + # PROCESSOR_ARCHITECTURE (which reflects the word size of the current + # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which + # contains the actual word size of the system when running thru WOW64). + if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or + '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')): + default_variables['MSVS_OS_BITS'] = 64 + else: + default_variables['MSVS_OS_BITS'] = 32 else: operating_system = flavor if flavor == 'android': @@ -1000,16 +1240,17 @@ def CalculateVariables(default_variables, params): default_variables.setdefault('SHARED_LIB_SUFFIX', '.so') default_variables.setdefault('SHARED_LIB_DIR', os.path.join('$!PRODUCT_DIR', 'lib')) - default_variables.setdefault('LIB_DIR', '') + default_variables.setdefault('LIB_DIR', + os.path.join('$!PRODUCT_DIR', 'obj')) -def OpenOutput(path): +def OpenOutput(path, mode='w'): """Open |path| for writing, creating directories if necessary.""" try: os.makedirs(os.path.dirname(path)) except OSError: pass - return open(path, 'w') + return open(path, mode) def GenerateOutputForConfig(target_list, target_dicts, data, params, @@ -1023,16 +1264,20 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, build_dir = os.path.join(generator_flags.get('output_dir', 'out'), config_name) + toplevel_build = os.path.join(options.toplevel_dir, build_dir) + master_ninja = ninja_syntax.Writer( - OpenOutput(os.path.join(options.toplevel_dir, build_dir, 'build.ninja')), + OpenOutput(os.path.join(toplevel_build, 'build.ninja')), width=120) # Put build-time support tools in out/{config_name}. - gyp.common.CopyTool(flavor, os.path.join(options.toplevel_dir, build_dir)) + gyp.common.CopyTool(flavor, toplevel_build) # Grab make settings for CC/CXX. if flavor == 'win': - cc = cxx = 'cl' + cc = cxx = 'cl.exe' + gyp.msvs_emulation.GenerateEnvironmentFiles( + toplevel_build, generator_flags, OpenOutput) else: cc, cxx = 'gcc', 'g++' build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) @@ -1048,11 +1293,23 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, master_ninja.variable('cc', os.environ.get('CC', cc)) master_ninja.variable('cxx', os.environ.get('CXX', cxx)) if flavor == 'win': - master_ninja.variable('ld', 'link') + master_ninja.variable('ld', 'link.exe') + master_ninja.variable('idl', 'midl.exe') + master_ninja.variable('ar', 'lib.exe') + master_ninja.variable('rc', 'rc.exe') + master_ninja.variable('asm', 'ml.exe') else: master_ninja.variable('ld', flock + ' linker.lock $cxx') - master_ninja.variable('cc_host', '$cc') - master_ninja.variable('cxx_host', '$cxx') + master_ninja.variable('ar', os.environ.get('AR', 'ar')) + + master_ninja.variable('ar_target', os.environ.get('AR_target', '$ar')) + master_ninja.variable('cc_target', os.environ.get('CC_target', '$cc')) + master_ninja.variable('cxx_target', os.environ.get('CXX_target', '$cxx')) + if flavor == 'win': + master_ninja.variable('ld_target', os.environ.get('LD_target', '$ld')) + else: + master_ninja.variable('ld_target', flock + ' linker.lock $cxx_target') + if flavor == 'mac': master_ninja.variable('mac_tool', os.path.join('.', 'gyp-mac-tool')) master_ninja.newline() @@ -1071,62 +1328,142 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, '$cflags_pch_cc -c $in -o $out'), depfile='$out.d') else: - # TODO(scottmg): Requires deplist branch of ninja for now (for - # /showIncludes handling). + # TODO(scottmg): Requires fork of ninja for dependency and linking + # support: https://github.com/sgraham/ninja + # Template for compile commands mostly shared between compiling files + # and generating PCH. In the case of PCH, the "output" is specified by /Fp + # rather than /Fo (for object files), but we still need to specify an /Fo + # when compiling PCH. + cc_template = ('ninja-deplist-helper -r . -q -f cl -o $out.dl -e $arch ' + '--command ' + '$cc /nologo /showIncludes /FC ' + '@$out.rsp ' + '$cflags_pch_c /c $in %(outspec)s /Fd$pdbname ') + cxx_template = ('ninja-deplist-helper -r . -q -f cl -o $out.dl -e $arch ' + '--command ' + '$cxx /nologo /showIncludes /FC ' + '@$out.rsp ' + '$cflags_pch_cc /c $in %(outspec)s $pchobj /Fd$pdbname ') master_ninja.rule( 'cc', description='CC $out', - command=('cmd /c $cc /nologo /showIncludes ' - '$defines $includes $cflags $cflags_c ' - '$cflags_pch_c /c $in /Fo$out ' - '| ninja-deplist-helper -f cl -o $out.dl'), - deplist='$out.dl') + command=cc_template % {'outspec': '/Fo$out'}, + deplist='$out.dl', + rspfile='$out.rsp', + rspfile_content='$defines $includes $cflags $cflags_c') + master_ninja.rule( + 'cc_pch', + description='CC PCH $out', + command=cc_template % {'outspec': '/Fp$out /Fo$out.obj'}, + deplist='$out.dl', + rspfile='$out.rsp', + rspfile_content='$defines $includes $cflags $cflags_c') master_ninja.rule( 'cxx', description='CXX $out', - command=('cmd /c $cxx /nologo /showIncludes ' - '$defines $includes $cflags $cflags_cc ' - '$cflags_pch_cc /c $in /Fo$out ' - '| ninja-deplist-helper -f cl -o $out.dl'), - deplist='$out.dl') + command=cxx_template % {'outspec': '/Fo$out'}, + deplist='$out.dl', + rspfile='$out.rsp', + rspfile_content='$defines $includes $cflags $cflags_cc') + master_ninja.rule( + 'cxx_pch', + description='CXX PCH $out', + command=cxx_template % {'outspec': '/Fp$out /Fo$out.obj'}, + deplist='$out.dl', + rspfile='$out.rsp', + rspfile_content='$defines $includes $cflags $cflags_cc') + master_ninja.rule( + 'idl', + description='IDL $in', + command=('%s gyp-win-tool midl-wrapper $arch $outdir ' + '$tlb $h $dlldata $iid $proxy $in ' + '$idlflags' % sys.executable)) + master_ninja.rule( + 'rc', + description='RC $in', + # Note: $in must be last otherwise rc.exe complains. + command=('%s gyp-win-tool rc-wrapper ' + '$arch $rc $defines $includes $rcflags /fo$out $in' % + sys.executable)) + master_ninja.rule( + 'asm', + description='ASM $in', + command=('%s gyp-win-tool asm-wrapper ' + '$arch $asm $defines $includes /c /Fo $out $in' % + sys.executable)) if flavor != 'mac' and flavor != 'win': master_ninja.rule( 'alink', description='AR $out', - command='rm -f $out && ar rcsT $out $in') + command='rm -f $out && $ar rcsT $out $in') + + # This allows targets that only need to depend on $lib's API to declare an + # order-only dependency on $lib.TOC and avoid relinking such downstream + # dependencies when $lib changes only in non-public ways. + # The resulting string leaves an uninterpolated %{suffix} which + # is used in the final substitution below. + mtime_preserving_solink_base = ( + 'if [ ! -e $lib -o ! -e ${lib}.TOC ]; then ' + '%(solink)s && %(extract_toc)s > ${lib}.TOC; else ' + '%(solink)s && %(extract_toc)s > ${lib}.tmp && ' + 'if ! cmp -s ${lib}.tmp ${lib}.TOC; then mv ${lib}.tmp ${lib}.TOC ; ' + 'fi; fi' + % { 'solink': + '$ld -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s', + 'extract_toc': + ('{ readelf -d ${lib} | grep SONAME ; ' + 'nm -gD -f p ${lib} | cut -f1-2 -d\' \'; }')}) + master_ninja.rule( 'solink', - description='SOLINK $out', - command=('$ld -shared $ldflags -o $out -Wl,-soname=$soname ' - '-Wl,--whole-archive $in -Wl,--no-whole-archive $libs')) + description='SOLINK $lib', + restat=True, + command=(mtime_preserving_solink_base % { + 'suffix': '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive ' + '$libs'})) master_ninja.rule( 'solink_module', - description='SOLINK(module) $out', - command=('$ld -shared $ldflags -o $out -Wl,-soname=$soname ' - '-Wl,--start-group $in -Wl,--end-group $libs')) + description='SOLINK(module) $lib', + restat=True, + command=(mtime_preserving_solink_base % { + 'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group $libs'})) master_ninja.rule( 'link', description='LINK $out', command=('$ld $ldflags -o $out -Wl,-rpath=\$$ORIGIN/lib ' - '-Wl,--start-group $in -Wl,--end-group $libs')) + '-Wl,--start-group $in $solibs -Wl,--end-group $libs')) elif flavor == 'win': master_ninja.rule( - 'alink', - description='AR $out', - command='lib /nologo /OUT:$out $in') - master_ninja.rule( - 'solink', - description='SOLINK $out', - command=('$ld /nologo /DLL $ldflags /OUT:$out $in $libs')) - master_ninja.rule( - 'solink_module', - description='SOLINK(module) $out', - command=('$ld /nologo /DLL $ldflags /OUT:$out $in $libs')) + 'alink', + description='LIB $out', + command=('%s gyp-win-tool link-wrapper $arch ' + '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' % + sys.executable), + rspfile='$out.rsp', + rspfile_content='$in_newline $libflags') + dlldesc = 'LINK(DLL) $dll' + dllcmd = ('%s gyp-win-tool link-wrapper $arch ' + '$ld /nologo /IMPLIB:$implib /DLL /OUT:$dll ' + '/PDB:$dll.pdb @$dll.rsp' % sys.executable) + master_ninja.rule('solink', description=dlldesc, command=dllcmd, + rspfile='$dll.rsp', + rspfile_content='$libs $in_newline $ldflags', + restat=True) + master_ninja.rule('solink_module', description=dlldesc, command=dllcmd, + rspfile='$dll.rsp', + rspfile_content='$libs $in_newline $ldflags', + restat=True) + # Note that ldflags goes at the end so that it has the option of + # overriding default settings earlier in the command line. master_ninja.rule( - 'link', - description='LINK $out', - command=('$ld /nologo $ldflags /OUT:$out $in $libs')) + 'link', + description='LINK $out', + command=('%s gyp-win-tool link-wrapper $arch ' + '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp' % + sys.executable), + rspfile='$out.rsp', + rspfile_content='$in_newline $libs $ldflags') else: master_ninja.rule( 'objc', @@ -1146,23 +1483,47 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, command='rm -f $out && ' './gyp-mac-tool filter-libtool libtool -static -o $out $in' '$postbuilds') + + # Record the public interface of $lib in $lib.TOC. See the corresponding + # comment in the posix section above for details. + mtime_preserving_solink_base = ( + 'if [ ! -e $lib -o ! -e ${lib}.TOC ] || ' + # Always force dependent targets to relink if this library + # reexports something. Handling this correctly would require + # recursive TOC dumping but this is rare in practice, so punt. + 'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then ' + '%(solink)s && %(extract_toc)s > ${lib}.TOC; ' + 'else ' + '%(solink)s && %(extract_toc)s > ${lib}.tmp && ' + 'if ! cmp -s ${lib}.tmp ${lib}.TOC; then ' + 'mv ${lib}.tmp ${lib}.TOC ; ' + 'fi; ' + 'fi' + % { 'solink': '$ld -shared $ldflags -o $lib %(suffix)s', + 'extract_toc': + '{ otool -l $lib | grep LC_ID_DYLIB -A 5; ' + 'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'}) + # TODO(thakis): The solink_module rule is likely wrong. Xcode seems to pass # -bundle -single_module here (for osmesa.so). master_ninja.rule( 'solink', - description='SOLINK $out, POSTBUILDS', - command=('$ld -shared $ldflags -o $out ' - '$in $libs$postbuilds')) + description='SOLINK $lib, POSTBUILDS', + restat=True, + command=(mtime_preserving_solink_base % { + 'suffix': '$in $solibs $libs$postbuilds'})) master_ninja.rule( 'solink_module', - description='SOLINK(module) $out, POSTBUILDS', - command=('$ld -shared $ldflags -o $out ' - '$in $libs$postbuilds')) + description='SOLINK(module) $lib, POSTBUILDS', + restat=True, + command=(mtime_preserving_solink_base % { + 'suffix': '$in $solibs $libs$postbuilds'})) + master_ninja.rule( 'link', description='LINK $out, POSTBUILDS', command=('$ld $ldflags -o $out ' - '$in $libs$postbuilds')) + '$in $solibs $libs$postbuilds')) master_ninja.rule( 'infoplist', description='INFOPLIST $out', @@ -1177,18 +1538,21 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, description='PACKAGE FRAMEWORK $out, POSTBUILDS', command='$mac_tool package-framework $out $version$postbuilds ' '&& touch $out') - master_ninja.rule( - 'stamp', - description='STAMP $out', - command='${postbuilds}touch $out') if flavor == 'win': - # TODO(scottmg): Copy fallback? + master_ninja.rule( + 'stamp', + description='STAMP $out', + command='%s gyp-win-tool stamp $out' % sys.executable) master_ninja.rule( 'copy', description='COPY $in $out', - command='cmd /c mklink /h $out $in >nul || mklink /h /j $out $in >nul') + command='%s gyp-win-tool recursive-mirror $in $out' % sys.executable) else: master_ninja.rule( + 'stamp', + description='STAMP $out', + command='${postbuilds}touch $out') + master_ninja.rule( 'copy', description='COPY $in $out', command='ln -f $in $out 2>/dev/null || (rm -rf $out && cp -af $in $out)') @@ -1196,12 +1560,17 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, all_targets = set() for build_file in params['build_files']: - for target in gyp.common.AllTargets(target_list, target_dicts, build_file): + for target in gyp.common.AllTargets(target_list, + target_dicts, + os.path.normpath(build_file)): all_targets.add(target) all_outputs = set() # target_outputs is a map from qualified target name to a Target object. target_outputs = {} + # target_short_names is a map from target short name to a list of Target + # objects. + target_short_names = {} for qualified_target in target_list: # qualified_target is like: third_party/icu/icu.gyp:icui18n#target build_file, name, toolset = \ @@ -1223,22 +1592,34 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, obj += '.' + toolset output_file = os.path.join(obj, base_path, name + '.ninja') - abs_build_dir=os.path.abspath(os.path.join(options.toplevel_dir, build_dir)) - writer = NinjaWriter(target_outputs, base_path, build_dir, - OpenOutput(os.path.join(options.toplevel_dir, - build_dir, - output_file)), + abs_build_dir = os.path.abspath(toplevel_build) + writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir, + OpenOutput(os.path.join(toplevel_build, output_file)), flavor, abs_build_dir=abs_build_dir) master_ninja.subninja(output_file) - target = writer.WriteSpec(spec, config_name) + target = writer.WriteSpec(spec, config_name, generator_flags) if target: + if name != target.FinalOutput() and spec['toolset'] == 'target': + target_short_names.setdefault(name, []).append(target) target_outputs[qualified_target] = target if qualified_target in all_targets: all_outputs.add(target.FinalOutput()) + if target_short_names: + # Write a short name to build this target. This benefits both the + # "build chrome" case as well as the gyp tests, which expect to be + # able to run actions and build libraries by their short name. + master_ninja.newline() + master_ninja.comment('Short names for targets.') + for short_name in target_short_names: + master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in + target_short_names[short_name]]) + if all_outputs: + master_ninja.newline() master_ninja.build('all', 'phony', list(all_outputs)) + master_ninja.default('all') def GenerateOutput(target_list, target_dicts, data, params): |