diff options
Diffstat (limited to 'tools/gyp/pylib/gyp/generator/ninja.py')
-rw-r--r-- | tools/gyp/pylib/gyp/generator/ninja.py | 867 |
1 files changed, 604 insertions, 263 deletions
diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py index c6bceaf38..23fb9b8af 100644 --- a/tools/gyp/pylib/gyp/generator/ninja.py +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -4,6 +4,7 @@ import copy import hashlib +import json import multiprocessing import os.path import re @@ -15,6 +16,7 @@ import gyp.common import gyp.msvs_emulation import gyp.MSVSUtil as MSVSUtil import gyp.xcode_emulation +from cStringIO import StringIO from gyp.common import GetEnvironFallback import gyp.ninja_syntax as ninja_syntax @@ -56,6 +58,7 @@ generator_default_variables = { generator_additional_non_configuration_keys = [] generator_additional_path_sections = [] generator_extra_sources_for_rules = [] +generator_filelist_paths = None # TODO: figure out how to not build extra host objects in the non-cross-compile # case when this is enabled, and enable unconditionally. @@ -97,6 +100,12 @@ def Define(d, flavor): return QuoteShellArgument(ninja_syntax.escape('-D' + d), flavor) +def AddArch(output, arch): + """Adds an arch string to an output path.""" + output, extension = os.path.splitext(output) + return '%s.%s%s' % (output, arch, extension) + + class Target: """Target represents the paths used within a single gyp target. @@ -203,7 +212,8 @@ class Target: class NinjaWriter: def __init__(self, qualified_target, target_outputs, base_dir, build_dir, - output_file, flavor, toplevel_dir=None): + output_file, toplevel_build, output_file_name, flavor, + toplevel_dir=None): """ base_dir: path from source root to directory containing this gyp file, by gyp semantics, all input paths are relative to this @@ -216,6 +226,9 @@ class NinjaWriter: self.base_dir = base_dir self.build_dir = build_dir self.ninja = ninja_syntax.Writer(output_file) + self.toplevel_build = toplevel_build + self.output_file_name = output_file_name + self.flavor = flavor self.abs_build_dir = None if toplevel_dir is not None: @@ -293,7 +306,7 @@ class NinjaWriter: expanded = os.path.normpath(expanded) return expanded if '$|' in path: - path = self.ExpandSpecial(path) + path = self.ExpandSpecial(path) assert '$' not in path, path return os.path.normpath(os.path.join(self.build_to_base, path)) @@ -345,8 +358,11 @@ class NinjaWriter: self.ninja.newline() return targets[0] - def WriteSpec(self, spec, config_name, generator_flags, - case_sensitive_filesystem): + def _SubninjaNameForArch(self, arch): + output_file_base = os.path.splitext(self.output_file_name)[0] + return '%s.%s.ninja' % (output_file_base, arch) + + 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. @@ -360,6 +376,9 @@ class NinjaWriter: self.target = Target(spec['type']) self.is_standalone_static_library = bool( spec.get('standalone_static_library', 0)) + # Track if this target contains any C++ files, to decide if gcc or g++ + # should be used for linking. + self.uses_cpp = False self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec) self.xcode_settings = self.msvs_settings = None @@ -370,6 +389,18 @@ class NinjaWriter: generator_flags) arch = self.msvs_settings.GetArch(config_name) self.ninja.variable('arch', self.win_env[arch]) + self.ninja.variable('cc', '$cl_' + arch) + self.ninja.variable('cxx', '$cl_' + arch) + + if self.flavor == 'mac': + self.archs = self.xcode_settings.GetActiveArchs(config_name) + if len(self.archs) > 1: + self.arch_subninjas = dict( + (arch, ninja_syntax.Writer( + OpenOutput(os.path.join(self.toplevel_build, + self._SubninjaNameForArch(arch)), + 'w'))) + for arch in self.archs) # Compute predepends for all rules. # actions_depends is the dependencies this target depends on before running @@ -411,8 +442,14 @@ class NinjaWriter: # Write out the compilation steps, if any. link_deps = [] - sources = spec.get('sources', []) + extra_sources + sources = extra_sources + spec.get('sources', []) if sources: + if self.flavor == 'mac' and len(self.archs) > 1: + # Write subninja file containing compile and link commands scoped to + # a single arch if a fat binary is being built. + for arch in self.archs: + self.ninja.subninja(self._SubninjaNameForArch(arch)) + pch = None if self.flavor == 'win': gyp.msvs_emulation.VerifyMissingSources( @@ -425,17 +462,24 @@ class NinjaWriter: self.xcode_settings, self.GypPathToNinja, lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang)) link_deps = self.WriteSources( - config_name, config, sources, compile_depends_stamp, pch, - case_sensitive_filesystem, spec) + self.ninja, config_name, config, sources, compile_depends_stamp, pch, + spec) # 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)] + obj_outputs = [f for f in sources if f.endswith(self.obj_ext)] + if obj_outputs: + if self.flavor != 'mac' or len(self.archs) == 1: + link_deps += [self.GypPathToNinja(o) for o in obj_outputs] + else: + print "Warning: Actions/rules writing object files don't work with " \ + "multiarch targets, dropping. (target %s)" % spec['target_name'] + 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 + is_empty_bundle = not link_deps and not mac_bundle_depends if link_deps or self.target.actions_stamp or actions_depends: output = self.WriteTarget(spec, config_name, config, link_deps, self.target.actions_stamp or actions_depends) @@ -444,7 +488,7 @@ class NinjaWriter: # Bundle all of the above together, if needed. if self.is_mac_bundle: - output = self.WriteMacBundle(spec, mac_bundle_depends) + output = self.WriteMacBundle(spec, mac_bundle_depends, is_empty_bundle) if not output: return None @@ -491,6 +535,10 @@ class NinjaWriter: """Write out the Actions, Rules, and Copies steps. Return a path representing the outputs of these steps.""" outputs = [] + if self.is_mac_bundle: + mac_bundle_resources = spec.get('mac_bundle_resources', [])[:] + else: + mac_bundle_resources = [] extra_mac_bundle_resources = [] if 'actions' in spec: @@ -498,6 +546,7 @@ class NinjaWriter: extra_mac_bundle_resources) if 'rules' in spec: outputs += self.WriteRules(spec['rules'], extra_sources, prebuild, + mac_bundle_resources, extra_mac_bundle_resources) if 'copies' in spec: outputs += self.WriteCopies(spec['copies'], prebuild, mac_bundle_depends) @@ -508,9 +557,8 @@ class NinjaWriter: stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs) if self.is_mac_bundle: - mac_bundle_resources = spec.get('mac_bundle_resources', []) + \ - extra_mac_bundle_resources - self.WriteMacBundleResources(mac_bundle_resources, mac_bundle_depends) + self.WriteMacBundleResources( + extra_mac_bundle_resources + mac_bundle_resources, mac_bundle_depends) self.WriteMacInfoPlist(mac_bundle_depends) return stamp @@ -567,7 +615,7 @@ class NinjaWriter: return all_outputs def WriteRules(self, rules, extra_sources, prebuild, - extra_mac_bundle_resources): + mac_bundle_resources, extra_mac_bundle_resources): env = self.GetSortedXcodeEnv() all_outputs = [] for rule in rules: @@ -607,6 +655,7 @@ class NinjaWriter: # For each source file, write an edge that generates all the outputs. for source in rule.get('rule_sources', []): + source = os.path.normpath(source) dirname, basename = os.path.split(source) root, ext = os.path.splitext(basename) @@ -620,15 +669,27 @@ class NinjaWriter: if int(rule.get('process_outputs_as_sources', False)): extra_sources += outputs - if int(rule.get('process_outputs_as_mac_bundle_resources', False)): + + was_mac_bundle_resource = source in mac_bundle_resources + if was_mac_bundle_resource or \ + int(rule.get('process_outputs_as_mac_bundle_resources', False)): extra_mac_bundle_resources += outputs + # Note: This is n_resources * n_outputs_in_rule. Put to-be-removed + # items in a set and remove them all in a single pass if this becomes + # a performance issue. + if was_mac_bundle_resource: + mac_bundle_resources.remove(source) extra_bindings = [] for var in needed_variables: if var == 'root': extra_bindings.append(('root', cygwin_munge(root))) elif var == 'dirname': - extra_bindings.append(('dirname', cygwin_munge(dirname))) + # '$dirname' 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. + dirname_expanded = self.ExpandSpecial(dirname, self.base_to_build) + extra_bindings.append(('dirname', cygwin_munge(dirname_expanded))) 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 @@ -682,8 +743,9 @@ class NinjaWriter: def WriteMacBundleResources(self, resources, bundle_depends): """Writes ninja edges for 'mac_bundle_resources'.""" for output, res in gyp.xcode_emulation.GetMacBundleResources( - self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), + generator_default_variables['PRODUCT_DIR'], self.xcode_settings, map(self.GypPathToNinja, resources)): + output = self.ExpandSpecial(output) self.ninja.build(output, 'mac_tool', res, variables=[('mactool_cmd', 'copy-bundle-resource')]) bundle_depends.append(output) @@ -691,38 +753,56 @@ class NinjaWriter: def WriteMacInfoPlist(self, bundle_depends): """Write build rules for bundle Info.plist files.""" info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist( - self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), + generator_default_variables['PRODUCT_DIR'], self.xcode_settings, self.GypPathToNinja) if not info_plist: return + out = self.ExpandSpecial(out) if defines: # Create an intermediate file to store preprocessed results. intermediate_plist = self.GypPathToUniqueOutput( os.path.basename(info_plist)) defines = ' '.join([Define(d, self.flavor) for d in defines]) - info_plist = self.ninja.build(intermediate_plist, 'infoplist', info_plist, - variables=[('defines',defines)]) + info_plist = self.ninja.build( + intermediate_plist, 'preprocess_infoplist', info_plist, + variables=[('defines',defines)]) env = self.GetSortedXcodeEnv(additional_settings=extra_env) env = self.ComputeExportEnvString(env) - self.ninja.build(out, 'mac_tool', info_plist, - variables=[('mactool_cmd', 'copy-info-plist'), - ('env', env)]) + keys = self.xcode_settings.GetExtraPlistItems(self.config_name) + keys = QuoteShellArgument(json.dumps(keys), self.flavor) + self.ninja.build(out, 'copy_infoplist', info_plist, + variables=[('env', env), ('keys', keys)]) bundle_depends.append(out) - def WriteSources(self, config_name, config, sources, predepends, - precompiled_header, case_sensitive_filesystem, spec): + def WriteSources(self, ninja_file, config_name, config, sources, predepends, + precompiled_header, spec): """Write build rules to compile all of |sources|.""" if self.toolset == 'host': self.ninja.variable('ar', '$ar_host') self.ninja.variable('cc', '$cc_host') self.ninja.variable('cxx', '$cxx_host') self.ninja.variable('ld', '$ld_host') + self.ninja.variable('ldxx', '$ldxx_host') + + if self.flavor != 'mac' or len(self.archs) == 1: + return self.WriteSourcesForArch( + self.ninja, config_name, config, sources, predepends, + precompiled_header, spec) + else: + return dict((arch, self.WriteSourcesForArch( + self.arch_subninjas[arch], config_name, config, sources, predepends, + precompiled_header, spec, arch=arch)) + for arch in self.archs) + + def WriteSourcesForArch(self, ninja_file, config_name, config, sources, + predepends, precompiled_header, spec, arch=None): + """Write build rules to compile all of |sources|.""" extra_defines = [] if self.flavor == 'mac': - cflags = self.xcode_settings.GetCflags(config_name) + cflags = self.xcode_settings.GetCflags(config_name, arch=arch) cflags_c = self.xcode_settings.GetCflagsC(config_name) cflags_cc = self.xcode_settings.GetCflagsCC(config_name) cflags_objc = ['$cflags_c'] + \ @@ -742,56 +822,70 @@ class NinjaWriter: obj += '.' + self.toolset pdbpath = os.path.normpath(os.path.join(obj, self.base_dir, self.name + '.pdb')) - self.WriteVariableList('pdbname', [pdbpath]) - self.WriteVariableList('pchprefix', [self.name]) + self.WriteVariableList(ninja_file, 'pdbname', [pdbpath]) + self.WriteVariableList(ninja_file, 'pchprefix', [self.name]) else: cflags = config.get('cflags', []) cflags_c = config.get('cflags_c', []) cflags_cc = config.get('cflags_cc', []) + # Respect environment variables related to build, but target-specific + # flags can still override them. + if self.toolset == 'target': + cflags_c = (os.environ.get('CPPFLAGS', '').split() + + os.environ.get('CFLAGS', '').split() + cflags_c) + cflags_cc = (os.environ.get('CPPFLAGS', '').split() + + os.environ.get('CXXFLAGS', '').split() + cflags_cc) + defines = config.get('defines', []) + extra_defines - self.WriteVariableList('defines', [Define(d, self.flavor) for d in defines]) + self.WriteVariableList(ninja_file, 'defines', + [Define(d, self.flavor) for d in defines]) if self.flavor == 'win': - self.WriteVariableList('rcflags', + self.WriteVariableList(ninja_file, 'rcflags', [QuoteShellArgument(self.ExpandSpecial(f), self.flavor) for f in self.msvs_settings.GetRcflags(config_name, self.GypPathToNinja)]) include_dirs = config.get('include_dirs', []) + env = self.GetSortedXcodeEnv() if self.flavor == 'win': + env = self.msvs_settings.GetVSMacroEnv('$!PRODUCT_DIR', + config=config_name) include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs, config_name) - self.WriteVariableList('includes', - [QuoteShellArgument('-I' + self.GypPathToNinja(i), self.flavor) + self.WriteVariableList(ninja_file, 'includes', + [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor) for i in include_dirs]) - pch_commands = precompiled_header.GetPchBuildCommands() + pch_commands = precompiled_header.GetPchBuildCommands(arch) if self.flavor == 'mac': - self.WriteVariableList('cflags_pch_c', - [precompiled_header.GetInclude('c')]) - self.WriteVariableList('cflags_pch_cc', - [precompiled_header.GetInclude('cc')]) - self.WriteVariableList('cflags_pch_objc', - [precompiled_header.GetInclude('m')]) - self.WriteVariableList('cflags_pch_objcc', - [precompiled_header.GetInclude('mm')]) - - self.WriteVariableList('cflags', map(self.ExpandSpecial, cflags)) - self.WriteVariableList('cflags_c', map(self.ExpandSpecial, cflags_c)) - self.WriteVariableList('cflags_cc', map(self.ExpandSpecial, cflags_cc)) + # Most targets use no precompiled headers, so only write these if needed. + for ext, var in [('c', 'cflags_pch_c'), ('cc', 'cflags_pch_cc'), + ('m', 'cflags_pch_objc'), ('mm', 'cflags_pch_objcc')]: + include = precompiled_header.GetInclude(ext, arch) + if include: ninja_file.variable(var, include) + + self.WriteVariableList(ninja_file, 'cflags', + map(self.ExpandSpecial, cflags)) + self.WriteVariableList(ninja_file, 'cflags_c', + map(self.ExpandSpecial, cflags_c)) + self.WriteVariableList(ninja_file, 'cflags_cc', + map(self.ExpandSpecial, cflags_cc)) if self.flavor == 'mac': - self.WriteVariableList('cflags_objc', map(self.ExpandSpecial, - cflags_objc)) - self.WriteVariableList('cflags_objcc', map(self.ExpandSpecial, - cflags_objcc)) - self.ninja.newline() + self.WriteVariableList(ninja_file, 'cflags_objc', + map(self.ExpandSpecial, cflags_objc)) + self.WriteVariableList(ninja_file, 'cflags_objcc', + map(self.ExpandSpecial, cflags_objcc)) + ninja_file.newline() outputs = [] + has_rc_source = False 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' + self.uses_cpp = True elif ext == 'c' or (ext == 'S' and self.flavor != 'win'): command = 'cc' elif ext == 's' and self.flavor != 'win': # Doesn't generate .o.d files. @@ -808,37 +902,41 @@ class NinjaWriter: command = 'objc' elif self.flavor == 'mac' and ext == 'mm': command = 'objcxx' + self.uses_cpp = True elif self.flavor == 'win' and ext == 'rc': command = 'rc' obj_ext = '.res' + has_rc_source = True else: # Ignore unhandled extensions. continue input = self.GypPathToNinja(source) output = self.GypPathToUniqueOutput(filename + obj_ext) - # Ninja's depfile handling gets confused when the case of a filename - # changes on a case-insensitive file system. To work around that, always - # convert .o filenames to lowercase on such file systems. See - # https://github.com/martine/ninja/issues/402 for details. - if not case_sensitive_filesystem: - output = output.lower() - implicit = precompiled_header.GetObjDependencies([input], [output]) + if arch is not None: + output = AddArch(output, arch) + implicit = precompiled_header.GetObjDependencies([input], [output], arch) variables = [] if self.flavor == 'win': variables, output, implicit = precompiled_header.GetFlagsModifications( input, output, implicit, command, cflags_c, cflags_cc, self.ExpandSpecial) - self.ninja.build(output, command, input, + ninja_file.build(output, command, input, implicit=[gch for _, _, gch in implicit], order_only=predepends, variables=variables) outputs.append(output) - self.WritePchTargets(pch_commands) + if has_rc_source: + resource_include_dirs = config.get('resource_include_dirs', include_dirs) + self.WriteVariableList(ninja_file, 'resource_includes', + [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor) + for i in resource_include_dirs]) - self.ninja.newline() + self.WritePchTargets(ninja_file, pch_commands) + + ninja_file.newline() return outputs - def WritePchTargets(self, pch_commands): + def WritePchTargets(self, ninja_file, pch_commands): """Writes ninja rules to compile prefix headers.""" if not pch_commands: return @@ -853,16 +951,34 @@ class NinjaWriter: map = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', } cmd = map.get(lang) - self.ninja.build(gch, cmd, input, variables=[(var_name, lang_flag)]) + ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)]) def WriteLink(self, spec, config_name, config, link_deps): """Write out a link step. Fills out target.binary. """ - + if self.flavor != 'mac' or len(self.archs) == 1: + return self.WriteLinkForArch( + self.ninja, spec, config_name, config, link_deps) + else: + output = self.ComputeOutput(spec) + inputs = [self.WriteLinkForArch(self.arch_subninjas[arch], spec, + config_name, config, link_deps[arch], + arch=arch) + for arch in self.archs] + extra_bindings = [] + if not self.is_mac_bundle: + self.AppendPostbuildVariable(extra_bindings, spec, output, output) + self.ninja.build(output, 'lipo', inputs, variables=extra_bindings) + return output + + def WriteLinkForArch(self, ninja_file, spec, config_name, config, + link_deps, arch=None): + """Write out a link step. Fills out target.binary. """ command = { 'executable': 'link', 'loadable_module': 'solink_module', 'shared_library': 'solink', }[spec['type']] + command_suffix = '' implicit_deps = set() solibs = set() @@ -879,61 +995,92 @@ class NinjaWriter: continue linkable = target.Linkable() if linkable: + new_deps = [] if (self.flavor == 'win' and target.component_objs and self.msvs_settings.IsUseLibraryDependencyInputs(config_name)): - extra_link_deps |= set(target.component_objs) + new_deps = target.component_objs elif self.flavor == 'win' and target.import_lib: - extra_link_deps.add(target.import_lib) + new_deps = [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) + new_deps = [target.binary] + for new_dep in new_deps: + if new_dep not in extra_link_deps: + extra_link_deps.add(new_dep) + link_deps.append(new_dep) final_output = target.FinalOutput() if not linkable or final_output != target.binary: implicit_deps.add(final_output) - link_deps.extend(list(extra_link_deps)) - extra_bindings = [] - if self.is_mac_bundle: - output = self.ComputeMacBundleBinaryOutput() - else: - output = self.ComputeOutput(spec) - extra_bindings.append(('postbuilds', - self.GetPostbuildCommand(spec, output, output))) + if self.uses_cpp and self.flavor != 'win': + extra_bindings.append(('ld', '$ldxx')) + + output = self.ComputeOutput(spec, arch) + if arch is None and not self.is_mac_bundle: + self.AppendPostbuildVariable(extra_bindings, spec, output, output) is_executable = spec['type'] == 'executable' + # The ldflags config key is not used on mac or win. On those platforms + # linker flags are set via xcode_settings and msvs_settings, respectively. + env_ldflags = os.environ.get('LDFLAGS', '').split() if self.flavor == 'mac': ldflags = self.xcode_settings.GetLdflags(config_name, self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']), - self.GypPathToNinja) + self.GypPathToNinja, arch) + ldflags = env_ldflags + ldflags elif self.flavor == 'win': manifest_name = self.GypPathToUniqueOutput( self.ComputeOutputFileName(spec)) ldflags, manifest_files = self.msvs_settings.GetLdflags(config_name, self.GypPathToNinja, self.ExpandSpecial, manifest_name, is_executable) - self.WriteVariableList('manifests', manifest_files) + ldflags = env_ldflags + ldflags + self.WriteVariableList(ninja_file, 'manifests', manifest_files) + command_suffix = _GetWinLinkRuleNameSuffix( + self.msvs_settings.IsEmbedManifest(config_name), + self.msvs_settings.IsLinkIncremental(config_name)) + def_file = self.msvs_settings.GetDefFile(self.GypPathToNinja) + if def_file: + implicit_deps.add(def_file) else: - ldflags = config.get('ldflags', []) + # Respect environment variables related to build, but target-specific + # flags can still override them. + ldflags = env_ldflags + config.get('ldflags', []) if is_executable and len(solibs): - ldflags.append('-Wl,-rpath=\$$ORIGIN/lib/') - ldflags.append('-Wl,-rpath-link=lib/') - self.WriteVariableList('ldflags', - gyp.common.uniquer(map(self.ExpandSpecial, - ldflags))) + rpath = 'lib/' + if self.toolset != 'target': + rpath += self.toolset + ldflags.append('-Wl,-rpath=\$$ORIGIN/%s' % rpath) + ldflags.append('-Wl,-rpath-link=%s' % rpath) + self.WriteVariableList(ninja_file, 'ldflags', + gyp.common.uniquer(map(self.ExpandSpecial, ldflags))) + + library_dirs = config.get('library_dirs', []) + if self.flavor == 'win': + library_dirs = [self.msvs_settings.ConvertVSMacros(l, config_name) + for l in library_dirs] + library_dirs = ['/LIBPATH:' + QuoteShellArgument(self.GypPathToNinja(l), + self.flavor) + for l in library_dirs] + else: + library_dirs = [QuoteShellArgument('-L' + self.GypPathToNinja(l), + self.flavor) + for l in library_dirs] libraries = gyp.common.uniquer(map(self.ExpandSpecial, spec.get('libraries', []))) if self.flavor == 'mac': - libraries = self.xcode_settings.AdjustLibraries(libraries) + libraries = self.xcode_settings.AdjustLibraries(libraries, config_name) elif self.flavor == 'win': libraries = self.msvs_settings.AdjustLibraries(libraries) - self.WriteVariableList('libs', libraries) - self.target.binary = output + self.WriteVariableList(ninja_file, 'libs', library_dirs + libraries) + + linked_binary = output if command in ('solink', 'solink_module'): extra_bindings.append(('soname', os.path.split(output)[1])) @@ -946,56 +1093,80 @@ class NinjaWriter: extra_bindings.append(('implibflag', '/IMPLIB:%s' % self.target.import_lib)) output = [output, self.target.import_lib] - else: + elif not self.is_mac_bundle: output = [output, output + '.TOC'] + else: + command = command + '_notoc' if len(solibs): extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs))) - self.ninja.build(output, command, link_deps, + ninja_file.build(output, command + command_suffix, link_deps, implicit=list(implicit_deps), variables=extra_bindings) + return linked_binary def WriteTarget(self, spec, config_name, config, link_deps, compile_deps): - if spec['type'] == 'none': + extra_link_deps = any(self.target_outputs.get(dep).Linkable() + for dep in spec.get('dependencies', []) + if dep in self.target_outputs) + if spec['type'] == 'none' or (not link_deps and not extra_link_deps): # TODO(evan): don't call this function for 'none' target types, as # it doesn't do anything, and we fake out a 'binary' with a stamp file. self.target.binary = compile_deps + self.target.type = 'none' elif spec['type'] == 'static_library': self.target.binary = self.ComputeOutput(spec) - variables = [] - postbuild = self.GetPostbuildCommand( - spec, self.target.binary, self.target.binary) - if postbuild: - variables.append(('postbuilds', postbuild)) - if self.xcode_settings: - variables.append(('libtool_flags', - self.xcode_settings.GetLibtoolflags(config_name))) - if (self.flavor not in ('mac', 'win') and not + if (self.flavor not in ('mac', 'openbsd', 'win') and not self.is_standalone_static_library): self.ninja.build(self.target.binary, 'alink_thin', link_deps, - order_only=compile_deps, variables=variables) + order_only=compile_deps) else: + variables = [] + if self.xcode_settings: + libtool_flags = self.xcode_settings.GetLibtoolflags(config_name) + if libtool_flags: + variables.append(('libtool_flags', libtool_flags)) if self.msvs_settings: libflags = self.msvs_settings.GetLibFlags(config_name, self.GypPathToNinja) variables.append(('libflags', libflags)) - self.ninja.build(self.target.binary, 'alink', link_deps, - order_only=compile_deps, variables=variables) + + if self.flavor != 'mac' or len(self.archs) == 1: + self.AppendPostbuildVariable(variables, spec, + self.target.binary, self.target.binary) + self.ninja.build(self.target.binary, 'alink', link_deps, + order_only=compile_deps, variables=variables) + else: + inputs = [] + for arch in self.archs: + output = self.ComputeOutput(spec, arch) + self.arch_subninjas[arch].build(output, 'alink', link_deps[arch], + order_only=compile_deps, + variables=variables) + inputs.append(output) + # TODO: It's not clear if libtool_flags should be passed to the alink + # call that combines single-arch .a files into a fat .a file. + self.AppendPostbuildVariable(variables, spec, + self.target.binary, self.target.binary) + self.ninja.build(self.target.binary, 'alink', inputs, + # FIXME: test proving order_only=compile_deps isn't + # needed. + variables=variables) else: - self.WriteLink(spec, config_name, config, link_deps) + self.target.binary = self.WriteLink(spec, config_name, config, link_deps) return self.target.binary - def WriteMacBundle(self, spec, mac_bundle_depends): + def WriteMacBundle(self, spec, mac_bundle_depends, is_empty): assert self.is_mac_bundle package_framework = spec['type'] in ('shared_library', 'loadable_module') output = self.ComputeMacBundleOutput() - postbuild = self.GetPostbuildCommand(spec, output, self.target.binary, - is_command_start=not package_framework) + if is_empty: + output += '.stamp' variables = [] - if postbuild: - variables.append(('postbuilds', postbuild)) - if package_framework: + self.AppendPostbuildVariable(variables, spec, output, self.target.binary, + is_command_start=not package_framework) + if package_framework and not is_empty: variables.append(('version', self.xcode_settings.GetFrameworkVersion())) self.ninja.build(output, 'package_framework', mac_bundle_depends, variables=variables) @@ -1025,23 +1196,30 @@ class NinjaWriter: 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): + def AppendPostbuildVariable(self, variables, spec, output, binary, + is_command_start=False): + """Adds a 'postbuild' variable if there is a postbuild for |output|.""" + postbuild = self.GetPostbuildCommand(spec, output, binary, is_command_start) + if postbuild: + variables.append(('postbuilds', postbuild)) + + def GetPostbuildCommand(self, spec, output, output_binary, is_command_start): """Returns a shell command that runs all the postbuilds, and removes |output| if any of them fails. If |is_command_start| is False, then the returned string will start with ' && '.""" if not self.xcode_settings or spec['type'] == 'none' or not output: return '' output = QuoteShellArgument(output, self.flavor) - target_postbuilds = self.xcode_settings.GetTargetPostbuilds( - 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 output_binary is not None: + postbuilds = self.xcode_settings.AddImplicitPostbuilds( + 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), + postbuilds, quiet=True) + if not postbuilds: return '' # Postbuilds expect to be run in the gyp file's directory, so insert an @@ -1074,14 +1252,9 @@ class NinjaWriter: def ComputeMacBundleOutput(self): """Return the 'output' (full output path) to a bundle output directory.""" assert self.is_mac_bundle - path = self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']) - return os.path.join(path, self.xcode_settings.GetWrapperName()) - - def ComputeMacBundleBinaryOutput(self): - """Return the 'output' (full output path) to the binary in a bundle.""" - assert self.is_mac_bundle - path = self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']) - return os.path.join(path, self.xcode_settings.GetExecutablePath()) + path = generator_default_variables['PRODUCT_DIR'] + return self.ExpandSpecial( + os.path.join(path, self.xcode_settings.GetWrapperName())) def ComputeOutputFileName(self, spec, type=None): """Compute the filename of the final output for the current target.""" @@ -1133,12 +1306,9 @@ class NinjaWriter: else: raise Exception('Unhandled output type %s' % type) - def ComputeOutput(self, spec, type=None): + def ComputeOutput(self, spec, arch=None): """Compute the path for the final output of the spec.""" - assert not self.is_mac_bundle or type - - if not type: - type = spec['type'] + type = spec['type'] if self.flavor == 'win': override = self.msvs_settings.GetOutputName(self.config_name, @@ -1146,13 +1316,13 @@ class NinjaWriter: if override: return override - if self.flavor == 'mac' and type in ( + if arch is None and self.flavor == 'mac' and type in ( 'static_library', 'executable', 'shared_library', 'loadable_module'): filename = self.xcode_settings.GetExecutablePath() else: filename = self.ComputeOutputFileName(spec, type) - if 'product_dir' in spec: + if arch is None and 'product_dir' in spec: path = os.path.join(spec['product_dir'], filename) return self.ExpandSpecial(path) @@ -1164,7 +1334,14 @@ class NinjaWriter: elif self.flavor == 'win' and self.toolset == 'target': type_in_output_root += ['shared_library'] - if type in type_in_output_root or self.is_standalone_static_library: + if arch is not None: + # Make sure partial executables don't end up in a bundle or the regular + # output directory. + archdir = 'arch' + if self.toolset != 'target': + archdir = os.path.join('arch', '%s' % self.toolset) + return os.path.join(archdir, AddArch(filename, arch)) + elif type in type_in_output_root or self.is_standalone_static_library: return filename elif type == 'shared_library': libdir = 'lib' @@ -1174,11 +1351,11 @@ class NinjaWriter: else: return self.GypPathToUniqueOutput(filename, qualified=False) - def WriteVariableList(self, var, values): + def WriteVariableList(self, ninja_file, var, values): assert not isinstance(values, str) if values is None: values = [] - self.ninja.variable(var, ' '.join(values)) + ninja_file.variable(var, ' '.join(values)) def WriteNewNinjaRule(self, name, args, description, is_cygwin, env): """Write out a new ninja "rule" statement for a given command. @@ -1275,7 +1452,6 @@ def CalculateVariables(default_variables, params): 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. @@ -1285,19 +1461,7 @@ def CalculateVariables(default_variables, params): 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 + gyp.msvs_emulation.CalculateCommonVariables(default_variables, params) else: operating_system = flavor if flavor == 'android': @@ -1309,6 +1473,32 @@ def CalculateVariables(default_variables, params): default_variables.setdefault('LIB_DIR', os.path.join('$!PRODUCT_DIR', 'obj')) +def ComputeOutputDir(params): + """Returns the path from the toplevel_dir to the build output directory.""" + # generator_dir: relative path from pwd to where make puts build files. + # Makes migrating from make to ninja easier, ninja doesn't put anything here. + generator_dir = os.path.relpath(params['options'].generator_output or '.') + + # output_dir: relative path from generator_dir to the build directory. + output_dir = params.get('generator_flags', {}).get('output_dir', 'out') + + # Relative path from source root to our output files. e.g. "out" + return os.path.normpath(os.path.join(generator_dir, output_dir)) + + +def CalculateGeneratorInputInfo(params): + """Called by __init__ to initialize generator values based on params.""" + # E.g. "out/gypfiles" + toplevel = params['options'].toplevel_dir + qualified_out_dir = os.path.normpath(os.path.join( + toplevel, ComputeOutputDir(params), 'gypfiles')) + + global generator_filelist_paths + generator_filelist_paths = { + 'toplevel': toplevel, + 'qualified_out_dir': qualified_out_dir, + } + def OpenOutput(path, mode='w'): """Open |path| for writing, creating directories if necessary.""" @@ -1326,32 +1516,149 @@ def CommandWithWrapper(cmd, wrappers, prog): return prog +def GetDefaultConcurrentLinks(): + """Returns a best-guess for a number of concurrent links.""" + if sys.platform in ('win32', 'cygwin'): + import ctypes + + class MEMORYSTATUSEX(ctypes.Structure): + _fields_ = [ + ("dwLength", ctypes.c_ulong), + ("dwMemoryLoad", ctypes.c_ulong), + ("ullTotalPhys", ctypes.c_ulonglong), + ("ullAvailPhys", ctypes.c_ulonglong), + ("ullTotalPageFile", ctypes.c_ulonglong), + ("ullAvailPageFile", ctypes.c_ulonglong), + ("ullTotalVirtual", ctypes.c_ulonglong), + ("ullAvailVirtual", ctypes.c_ulonglong), + ("sullAvailExtendedVirtual", ctypes.c_ulonglong), + ] + + stat = MEMORYSTATUSEX() + stat.dwLength = ctypes.sizeof(stat) + ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)) + + mem_limit = max(1, stat.ullTotalPhys / (4 * (2 ** 30))) # total / 4GB + hard_cap = max(1, int(os.getenv('GYP_LINK_CONCURRENCY_MAX', 2**32))) + return min(mem_limit, hard_cap) + elif sys.platform.startswith('linux'): + with open("/proc/meminfo") as meminfo: + memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB') + for line in meminfo: + match = memtotal_re.match(line) + if not match: + continue + # Allow 8Gb per link on Linux because Gold is quite memory hungry + return max(1, int(match.group(1)) / (8 * (2 ** 20))) + return 1 + elif sys.platform == 'darwin': + try: + avail_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize'])) + # A static library debug build of Chromium's unit_tests takes ~2.7GB, so + # 4GB per ld process allows for some more bloat. + return max(1, avail_bytes / (4 * (2 ** 30))) # total / 4GB + except: + return 1 + else: + # TODO(scottmg): Implement this for other platforms. + return 1 + + +def _GetWinLinkRuleNameSuffix(embed_manifest, link_incremental): + """Returns the suffix used to select an appropriate linking rule depending on + whether the manifest embedding and/or incremental linking is enabled.""" + suffix = '' + if embed_manifest: + suffix += '_embed' + if link_incremental: + suffix += '_inc' + return suffix + + +def _AddWinLinkRules(master_ninja, embed_manifest, link_incremental): + """Adds link rules for Windows platform to |master_ninja|.""" + def FullLinkCommand(ldcmd, out, binary_type): + cmd = ('cmd /c %(ldcmd)s' + ' && %(python)s gyp-win-tool manifest-wrapper $arch' + ' cmd /c if exist %(out)s.manifest del %(out)s.manifest' + ' && %(python)s gyp-win-tool manifest-wrapper $arch' + ' $mt -nologo -manifest $manifests') + if embed_manifest and not link_incremental: + # Embed manifest into a binary. If incremental linking is enabled, + # embedding is postponed to the re-linking stage (see below). + cmd += ' -outputresource:%(out)s;%(resname)s' + else: + # Save manifest as an external file. + cmd += ' -out:%(out)s.manifest' + if link_incremental: + # There is no point in generating separate rule for the case when + # incremental linking is enabled, but manifest embedding is disabled. + # In that case the basic rule should be used (e.g. 'link'). + # See also implementation of _GetWinLinkRuleNameSuffix(). + assert embed_manifest + # Make .rc file out of manifest, compile it to .res file and re-link. + cmd += (' && %(python)s gyp-win-tool manifest-to-rc $arch' + ' %(out)s.manifest %(out)s.manifest.rc %(resname)s' + ' && %(python)s gyp-win-tool rc-wrapper $arch $rc' + ' %(out)s.manifest.rc' + ' && %(ldcmd)s %(out)s.manifest.res') + resource_name = { + 'exe': '1', + 'dll': '2', + }[binary_type] + return cmd % {'python': sys.executable, + 'out': out, + 'ldcmd': ldcmd, + 'resname': resource_name} + + rule_name_suffix = _GetWinLinkRuleNameSuffix(embed_manifest, link_incremental) + dlldesc = 'LINK%s(DLL) $dll' % rule_name_suffix.upper() + dllcmd = ('%s gyp-win-tool link-wrapper $arch ' + '$ld /nologo $implibflag /DLL /OUT:$dll ' + '/PDB:$dll.pdb @$dll.rsp' % sys.executable) + dllcmd = FullLinkCommand(dllcmd, '$dll', 'dll') + master_ninja.rule('solink' + rule_name_suffix, + description=dlldesc, command=dllcmd, + rspfile='$dll.rsp', + rspfile_content='$libs $in_newline $ldflags', + restat=True, + pool='link_pool') + master_ninja.rule('solink_module' + rule_name_suffix, + description=dlldesc, command=dllcmd, + rspfile='$dll.rsp', + rspfile_content='$libs $in_newline $ldflags', + restat=True, + pool='link_pool') + # Note that ldflags goes at the end so that it has the option of + # overriding default settings earlier in the command line. + exe_cmd = ('%s gyp-win-tool link-wrapper $arch ' + '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp' % + sys.executable) + exe_cmd = FullLinkCommand(exe_cmd, '$out', 'exe') + master_ninja.rule('link' + rule_name_suffix, + description='LINK%s $out' % rule_name_suffix.upper(), + command=exe_cmd, + rspfile='$out.rsp', + rspfile_content='$in_newline $libs $ldflags', + pool='link_pool') + + def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name): options = params['options'] flavor = gyp.common.GetFlavor(params) generator_flags = params.get('generator_flags', {}) - # generator_dir: relative path from pwd to where make puts build files. - # Makes migrating from make to ninja easier, ninja doesn't put anything here. - generator_dir = os.path.relpath(params['options'].generator_output or '.') - - # output_dir: relative path from generator_dir to the build directory. - output_dir = generator_flags.get('output_dir', 'out') - # build_dir: relative path from source root to our output files. # e.g. "out/Debug" - build_dir = os.path.normpath(os.path.join(generator_dir, - output_dir, - config_name)) + build_dir = os.path.normpath( + os.path.join(ComputeOutputDir(params), config_name)) toplevel_build = os.path.join(options.toplevel_dir, build_dir) master_ninja = ninja_syntax.Writer( OpenOutput(os.path.join(toplevel_build, 'build.ninja')), width=120) - case_sensitive_filesystem = not os.path.exists( - os.path.join(toplevel_build, 'BUILD.NINJA')) # Put build-time support tools in out/{config_name}. gyp.common.CopyTool(flavor, toplevel_build) @@ -1364,17 +1671,20 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # 'CC_host'/'CXX_host' enviroment variable, cc_host/cxx_host should be set # to cc/cxx. if flavor == 'win': + # Overridden by local arch choice in the use_deps case. + # Chromium's ffmpeg c99conv.py currently looks for a 'cc =' line in + # build.ninja so needs something valid here. http://crbug.com/233985 cc = 'cl.exe' cxx = 'cl.exe' ld = 'link.exe' - gyp.msvs_emulation.GenerateEnvironmentFiles( - toplevel_build, generator_flags, OpenOutput) ld_host = '$ld' else: cc = 'gcc' cxx = 'g++' - ld = '$cxx' - ld_host = '$cxx_host' + ld = '$cc' + ldxx = '$cxx' + ld_host = '$cc_host' + ldxx_host = '$cxx_host' cc_host = None cxx_host = None @@ -1385,40 +1695,40 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, make_global_settings = data[build_file].get('make_global_settings', []) build_to_root = gyp.common.InvertRelativePath(build_dir, options.toplevel_dir) - flock = 'flock' - if flavor == 'mac': - flock = './gyp-mac-tool flock' wrappers = {} - if flavor != 'win': - wrappers['LINK'] = flock + ' linker.lock' for key, value in make_global_settings: if key == 'CC': cc = os.path.join(build_to_root, value) if key == 'CXX': cxx = os.path.join(build_to_root, value) - if key == 'LD': - ld = os.path.join(build_to_root, value) if key == 'CC.host': cc_host = os.path.join(build_to_root, value) cc_host_global_setting = value if key == 'CXX.host': cxx_host = os.path.join(build_to_root, value) cxx_host_global_setting = value - if key == 'LD.host': - ld_host = os.path.join(build_to_root, value) if key.endswith('_wrapper'): wrappers[key[:-len('_wrapper')]] = os.path.join(build_to_root, value) + # Support wrappers from environment variables too. + for key, value in os.environ.iteritems(): + if key.lower().endswith('_wrapper'): + key_prefix = key[:-len('_wrapper')] + key_prefix = re.sub(r'\.HOST$', '.host', key_prefix) + wrappers[key_prefix] = os.path.join(build_to_root, value) + + if flavor == 'win': + cl_paths = gyp.msvs_emulation.GenerateEnvironmentFiles( + toplevel_build, generator_flags, OpenOutput) + for arch, path in cl_paths.iteritems(): + master_ninja.variable( + 'cl_' + arch, CommandWithWrapper('CC', wrappers, + QuoteShellArgument(path, flavor))) + cc = GetEnvironFallback(['CC_target', 'CC'], cc) master_ninja.variable('cc', CommandWithWrapper('CC', wrappers, cc)) cxx = GetEnvironFallback(['CXX_target', 'CXX'], cxx) master_ninja.variable('cxx', CommandWithWrapper('CXX', wrappers, cxx)) - ld = GetEnvironFallback(['LD_target', 'LD'], ld) - - if not cc_host: - cc_host = cc - if not cxx_host: - cxx_host = cxx if flavor == 'win': master_ninja.variable('ld', ld) @@ -1427,41 +1737,54 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, master_ninja.variable('rc', 'rc.exe') master_ninja.variable('asm', 'ml.exe') master_ninja.variable('mt', 'mt.exe') - master_ninja.variable('use_dep_database', '1') else: master_ninja.variable('ld', CommandWithWrapper('LINK', wrappers, ld)) + master_ninja.variable('ldxx', CommandWithWrapper('LINK', wrappers, ldxx)) master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], 'ar')) - master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], 'ar')) - cc_host = GetEnvironFallback(['CC_host'], cc_host) - cxx_host = GetEnvironFallback(['CXX_host'], cxx_host) - ld_host = GetEnvironFallback(['LD_host'], ld_host) - - # The environment variable could be used in 'make_global_settings', like - # ['CC.host', '$(CC)'] or ['CXX.host', '$(CXX)'], transform them here. - if '$(CC)' in cc_host and cc_host_global_setting: - cc_host = cc_host_global_setting.replace('$(CC)', cc) - if '$(CXX)' in cxx_host and cxx_host_global_setting: - cxx_host = cxx_host_global_setting.replace('$(CXX)', cxx) - master_ninja.variable('cc_host', - CommandWithWrapper('CC.host', wrappers, cc_host)) - master_ninja.variable('cxx_host', - CommandWithWrapper('CXX.host', wrappers, cxx_host)) - if flavor == 'win': - master_ninja.variable('ld_host', ld_host) - else: - master_ninja.variable('ld_host', CommandWithWrapper( - 'LINK', wrappers, ld_host)) + if generator_supports_multiple_toolsets: + if not cc_host: + cc_host = cc + if not cxx_host: + cxx_host = cxx + + master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], 'ar')) + cc_host = GetEnvironFallback(['CC_host'], cc_host) + cxx_host = GetEnvironFallback(['CXX_host'], cxx_host) + + # The environment variable could be used in 'make_global_settings', like + # ['CC.host', '$(CC)'] or ['CXX.host', '$(CXX)'], transform them here. + if '$(CC)' in cc_host and cc_host_global_setting: + cc_host = cc_host_global_setting.replace('$(CC)', cc) + if '$(CXX)' in cxx_host and cxx_host_global_setting: + cxx_host = cxx_host_global_setting.replace('$(CXX)', cxx) + master_ninja.variable('cc_host', + CommandWithWrapper('CC.host', wrappers, cc_host)) + master_ninja.variable('cxx_host', + CommandWithWrapper('CXX.host', wrappers, cxx_host)) + if flavor == 'win': + master_ninja.variable('ld_host', ld_host) + else: + master_ninja.variable('ld_host', CommandWithWrapper( + 'LINK', wrappers, ld_host)) + master_ninja.variable('ldxx_host', CommandWithWrapper( + 'LINK', wrappers, ldxx_host)) + + master_ninja.newline() + master_ninja.pool('link_pool', depth=GetDefaultConcurrentLinks()) master_ninja.newline() + deps = 'msvc' if flavor == 'win' else 'gcc' + if flavor != 'win': master_ninja.rule( 'cc', description='CC $out', command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c ' '$cflags_pch_c -c $in -o $out'), - depfile='$out.d') + depfile='$out.d', + deps=deps) master_ninja.rule( 'cc_s', description='CC $out', @@ -1472,13 +1795,14 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, description='CXX $out', command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc ' '$cflags_pch_cc -c $in -o $out'), - depfile='$out.d') + depfile='$out.d', + deps=deps) else: - cc_command = ('ninja -t msvc -o $out -e $arch ' + cc_command = ('ninja -t msvc -e $arch ' + '-- ' '$cc /nologo /showIncludes /FC ' '@$out.rsp /c $in /Fo$out /Fd$pdbname ') - cxx_command = ('ninja -t msvc -o $out -e $arch ' + cxx_command = ('ninja -t msvc -e $arch ' + '-- ' '$cxx /nologo /showIncludes /FC ' '@$out.rsp /c $in /Fo$out /Fd$pdbname ') @@ -1486,16 +1810,16 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, 'cc', description='CC $out', command=cc_command, - depfile='$out.d', rspfile='$out.rsp', - rspfile_content='$defines $includes $cflags $cflags_c') + rspfile_content='$defines $includes $cflags $cflags_c', + deps=deps) master_ninja.rule( 'cxx', description='CXX $out', command=cxx_command, - depfile='$out.d', rspfile='$out.rsp', - rspfile_content='$defines $includes $cflags $cflags_cc') + rspfile_content='$defines $includes $cflags $cflags_cc', + deps=deps) master_ninja.rule( 'idl', description='IDL $in', @@ -1507,7 +1831,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, 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' % + '$arch $rc $defines $resource_includes $rcflags /fo$out $in' % sys.executable)) master_ninja.rule( 'asm', @@ -1549,18 +1873,22 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, restat=True, command=(mtime_preserving_solink_base % { 'suffix': '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive ' - '$libs'})) + '$libs'}), + pool='link_pool') master_ninja.rule( 'solink_module', description='SOLINK(module) $lib', restat=True, command=(mtime_preserving_solink_base % { - 'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group $libs'})) + 'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group ' + '$libs'}), + pool='link_pool') master_ninja.rule( 'link', description='LINK $out', command=('$ld $ldflags -o $out ' - '-Wl,--start-group $in $solibs -Wl,--end-group $libs')) + '-Wl,--start-group $in $solibs -Wl,--end-group $libs'), + pool='link_pool') elif flavor == 'win': master_ninja.rule( 'alink', @@ -1570,51 +1898,27 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, sys.executable), rspfile='$out.rsp', rspfile_content='$in_newline $libflags') - dlldesc = 'LINK(DLL) $dll' - dllcmd = ('%s gyp-win-tool link-wrapper $arch ' - '$ld /nologo $implibflag /DLL /OUT:$dll ' - '/PDB:$dll.pdb @$dll.rsp' % sys.executable) - dllcmd += (' && %s gyp-win-tool manifest-wrapper $arch ' - 'cmd /c if exist $dll.manifest del $dll.manifest' % - sys.executable) - dllcmd += (' && %s gyp-win-tool manifest-wrapper $arch ' - '$mt -nologo -manifest $manifests -out:$dll.manifest' % - 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=('%s gyp-win-tool link-wrapper $arch ' - '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp && ' - '%s gyp-win-tool manifest-wrapper $arch ' - 'cmd /c if exist $out.manifest del $out.manifest && ' - '%s gyp-win-tool manifest-wrapper $arch ' - '$mt -nologo -manifest $manifests -out:$out.manifest' % - (sys.executable, sys.executable, sys.executable)), - rspfile='$out.rsp', - rspfile_content='$in_newline $libs $ldflags') + _AddWinLinkRules(master_ninja, embed_manifest=True, link_incremental=True) + _AddWinLinkRules(master_ninja, embed_manifest=True, link_incremental=False) + _AddWinLinkRules(master_ninja, embed_manifest=False, link_incremental=False) + # Do not generate rules for embed_manifest=False and link_incremental=True + # because in that case rules for (False, False) should be used (see + # implementation of _GetWinLinkRuleNameSuffix()). else: master_ninja.rule( 'objc', description='OBJC $out', command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_objc ' '$cflags_pch_objc -c $in -o $out'), - depfile='$out.d') + depfile='$out.d', + deps=deps) master_ninja.rule( 'objcxx', description='OBJCXX $out', command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_objcc ' '$cflags_pch_objcc -c $in -o $out'), - depfile='$out.d') + depfile='$out.d', + deps=deps) master_ninja.rule( 'alink', description='LIBTOOL-STATIC $out, POSTBUILDS', @@ -1622,9 +1926,14 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, './gyp-mac-tool filter-libtool libtool $libtool_flags ' '-static -o $out $in' '$postbuilds') + master_ninja.rule( + 'lipo', + description='LIPO $out, POSTBUILDS', + command='rm -f $out && lipo -create $in -output $out$postbuilds') # Record the public interface of $lib in $lib.TOC. See the corresponding # comment in the posix section above for details. + solink_base = '$ld %(type)s $ldflags -o $lib %(suffix)s' mtime_preserving_solink_base = ( 'if [ ! -e $lib -o ! -e ${lib}.TOC ] || ' # Always force dependent targets to relink if this library @@ -1638,37 +1947,57 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, 'mv ${lib}.tmp ${lib}.TOC ; ' 'fi; ' 'fi' - % { 'solink': '$ld -shared $ldflags -o $lib %(suffix)s', + % { 'solink': solink_base, '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). + solink_suffix = '$in $solibs $libs$postbuilds' master_ninja.rule( 'solink', description='SOLINK $lib, POSTBUILDS', restat=True, - command=(mtime_preserving_solink_base % { - 'suffix': '$in $solibs $libs$postbuilds'})) + command=mtime_preserving_solink_base % {'suffix': solink_suffix, + 'type': '-shared'}, + pool='link_pool') + master_ninja.rule( + 'solink_notoc', + description='SOLINK $lib, POSTBUILDS', + restat=True, + command=solink_base % {'suffix':solink_suffix, 'type': '-shared'}, + pool='link_pool') + + solink_module_suffix = '$in $solibs $libs$postbuilds' master_ninja.rule( 'solink_module', description='SOLINK(module) $lib, POSTBUILDS', restat=True, - command=(mtime_preserving_solink_base % { - 'suffix': '$in $solibs $libs$postbuilds'})) + command=mtime_preserving_solink_base % {'suffix': solink_module_suffix, + 'type': '-bundle'}, + pool='link_pool') + master_ninja.rule( + 'solink_module_notoc', + description='SOLINK(module) $lib, POSTBUILDS', + restat=True, + command=solink_base % {'suffix': solink_module_suffix, 'type': '-bundle'}, + pool='link_pool') master_ninja.rule( 'link', description='LINK $out, POSTBUILDS', command=('$ld $ldflags -o $out ' - '$in $solibs $libs$postbuilds')) + '$in $solibs $libs$postbuilds'), + pool='link_pool') master_ninja.rule( - 'infoplist', - description='INFOPLIST $out', + 'preprocess_infoplist', + description='PREPROCESS INFOPLIST $out', command=('$cc -E -P -Wno-trigraphs -x c $defines $in -o $out && ' 'plutil -convert xml1 $out $out')) master_ninja.rule( + 'copy_infoplist', + description='COPY INFOPLIST $in', + command='$env ./gyp-mac-tool copy-info-plist $in $out $keys') + master_ninja.rule( 'mac_tool', description='MACTOOL $mactool_cmd $in', command='$env ./gyp-mac-tool $mactool_cmd $in $out') @@ -1710,6 +2039,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, # 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 = \ @@ -1731,14 +2061,21 @@ 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(toplevel_build) + ninja_output = StringIO() writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir, - OpenOutput(os.path.join(toplevel_build, output_file)), + ninja_output, + toplevel_build, output_file, flavor, toplevel_dir=options.toplevel_dir) - master_ninja.subninja(output_file) - target = writer.WriteSpec( - spec, config_name, generator_flags, case_sensitive_filesystem) + target = writer.WriteSpec(spec, config_name, generator_flags) + + if ninja_output.tell() > 0: + # Only create files for ninja files that actually have contents. + with OpenOutput(os.path.join(toplevel_build, output_file)) as ninja_file: + ninja_file.write(ninja_output.getvalue()) + ninja_output.close() + master_ninja.subninja(output_file) + if target: if name != target.FinalOutput() and spec['toolset'] == 'target': target_short_names.setdefault(name, []).append(target) @@ -1781,6 +2118,10 @@ def CallGenerateOutputForConfig(arglist): def GenerateOutput(target_list, target_dicts, data, params): + # Update target_dicts for iOS device builds. + target_dicts = gyp.xcode_emulation.CloneConfigurationForDeviceAndEmulator( + target_dicts) + user_config = params.get('generator_flags', {}).get('config', None) if gyp.common.GetFlavor(params) == 'win': target_list, target_dicts = MSVSUtil.ShardTargets(target_list, target_dicts) @@ -1799,7 +2140,7 @@ def GenerateOutput(target_list, target_dicts, data, params): for config_name in config_names: arglists.append( (target_list, target_dicts, data, params, config_name)) - pool.map(CallGenerateOutputForConfig, arglists) + pool.map(CallGenerateOutputForConfig, arglists) except KeyboardInterrupt, e: pool.terminate() raise e |