Chromium Code Reviews| Index: pylib/gyp/generator/ninja.py |
| diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..afc9e761ca2de474bbb468a14f1a30a2d2da836b |
| --- /dev/null |
| +++ b/pylib/gyp/generator/ninja.py |
| @@ -0,0 +1,613 @@ |
| +#!/usr/bin/python |
| + |
| +# Copyright (c) 2010 Google Inc. All rights reserved. |
|
Nico
2011/08/19 19:07:48
2011
Evan Martin
2011/08/19 20:26:43
Heh, shows how long I've been sitting on this patc
|
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +import gyp |
| +import gyp.common |
| +import gyp.system_test |
| +import os.path |
| +import pprint |
| +import subprocess |
| +import sys |
| + |
| +import gyp.ninja_syntax as ninja_syntax |
| + |
| +generator_default_variables = { |
| + 'OS': 'linux', |
| + |
| + 'EXECUTABLE_PREFIX': '', |
| + 'EXECUTABLE_SUFFIX': '', |
| + 'STATIC_LIB_PREFIX': '', |
| + 'STATIC_LIB_SUFFIX': '.a', |
| + 'SHARED_LIB_PREFIX': 'lib', |
| + 'SHARED_LIB_SUFFIX': '.so', |
| + # TODO: intermediate dir should *not* be shared between different targets. |
| + # Unfortunately, whatever we provide here gets written into many different |
| + # places within the gyp spec so it's difficult to make it target-specific. |
| + # Apparently we've made it this far with one global path for the make build |
| + # we're safe for now. |
| + 'INTERMEDIATE_DIR': '$b/geni', |
| + 'SHARED_INTERMEDIATE_DIR': '$b/gen', |
| + 'PRODUCT_DIR': '$b', |
| + 'SHARED_LIB_DIR': '$b/lib', |
| + 'LIB_DIR': '$b', |
| + |
| + # Special variables that may be used by gyp 'rule' targets. |
| + # We generate definitions for these variables on the fly when processing a |
| + # rule. |
| + 'RULE_INPUT_ROOT': '$root', |
| + 'RULE_INPUT_PATH': '$source', |
| + 'RULE_INPUT_EXT': '$ext', |
| + 'RULE_INPUT_NAME': '$name', |
| +} |
| + |
| +NINJA_BASE = """\ |
| +builddir = %(builddir)s |
| +# Short alias for builddir. |
| +b = %(builddir)s |
| + |
| +cc = %(cc)s |
| +cxx = %(cxx)s |
| + |
| +rule cc |
| + depfile = $out.d |
| + description = CC $out |
| + command = $cc -MMD -MF $out.d $defines $includes $cflags $cflags_cc $ |
|
Nico
2011/08/19 19:07:48
I think the inconsistency of calling the flags cfl
|
| + -c $in -o $out |
| + |
| +rule cxx |
| + depfile = $out.d |
| + description = CXX $out |
| + command = $cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cxx $ |
| + -c $in -o $out |
| + |
| +rule alink |
| + description = AR $out |
| + command = rm -f $out && ar rcsT $out $in |
| + |
| +rule solink |
| + description = SOLINK $out |
| + command = g++ -Wl,--threads -Wl,--thread-count=4 $ |
| + -shared $ldflags -o $out -Wl,-soname=$soname $ |
| + -Wl,--whole-archive $in -Wl,--no-whole-archive $libs |
| + |
| +rule link |
| + description = LINK $out |
| + command = g++ -Wl,--threads -Wl,--thread-count=4 $ |
| + $ldflags -o $out -Wl,-rpath=\$$ORIGIN/lib $ |
| + -Wl,--start-group $in -Wl,--end-group $libs |
| + |
| +rule stamp |
| + description = STAMP $out |
| + command = touch $out |
| + |
| +rule copy |
| + description = COPY $in $out |
| + command = ln -f $in $out 2>/dev/null || cp -af $in $out |
| + |
| +""" |
| + |
| + |
| +def StripPrefix(arg, prefix): |
| + if arg.startswith(prefix): |
| + return arg[len(prefix):] |
| + return arg |
| + |
| + |
| +def QuoteShellArgument(arg): |
| + return "'" + arg.replace("'", "'" + '"\'"' + "'") + "'" |
| + |
| + |
| +def MaybeQuoteShellArgument(arg): |
| + if '"' in arg or ' ' in arg: |
| + return QuoteShellArgument(arg) |
| + return arg |
| + |
| + |
| +# A small discourse on paths as used within the Ninja build: |
| +# |
| +# Paths within a given .gyp file are always relative to the directory |
| +# containing the .gyp file. Call these "gyp paths". This includes |
| +# sources as well as the starting directory a given gyp rule/action |
| +# expects to be run from. We call this directory "base_dir" within |
| +# the per-.gyp-file NinjaWriter code. |
| +# |
| +# All paths as written into the .ninja files are relative to root of |
| +# the tree. Call these paths "ninja paths". We set up the ninja |
| +# variable "$b" to be the path to the root of the build output, |
| +# e.g. out/Debug/. All files we produce (both at gyp and at build |
| +# time) appear in that output directory. |
| +# |
| +# We translate between these two notions of paths with two helper |
| +# functions: |
| +# |
| +# - ExpandGypPath translates a gyp path (i.e. relative to the .gyp file) |
| +# into the equivalent ninja path. |
|
Nico
2011/08/19 19:07:48
Why not NinjaFromGypPath() and NinjaOutputFromGypP
Evan Martin
2011/08/19 20:26:43
I renamed them GypPathToNinja and GypPathToUniqueO
|
| +# |
| +# - UniqueOutputPath translates a gyp path into a ninja path to write |
| +# an output file; the result can be namespaced such that is unique |
| +# to the input file name as well as the output target name. |
| + |
| +class NinjaWriter: |
| + def __init__(self, target_outputs, base_dir, output_file): |
| + self.target_outputs = target_outputs |
| + # The root-relative path to the source .gyp file; by gyp |
| + # semantics, all input paths are relative to this. |
| + self.base_dir = base_dir |
| + self.ninja = ninja_syntax.Writer(output_file) |
| + |
| + def ExpandGypPath(self, path): |
| + """Translate a gyp path to a ninja path. |
| + |
| + See the above discourse on path conversions.""" |
|
Nico
2011/08/19 19:07:48
nit: It's a bit weird to have a docstring refer to
|
| + if path.startswith('$'): |
| + # If the path contains a reference to a ninja variable, we know |
| + # it's already relative to the source root. |
| + return path |
| + return os.path.normpath(os.path.join(self.base_dir, path)) |
| + |
| + def UniqueOutputPath(self, path, qualified=False): |
| + """Translate a gyp path to a ninja path for writing output. |
| + |
| + If qualified is True, qualify the resulting filename with the name |
| + of the target. This is necessary when e.g. compiling the same |
| + path twice for two separate output targets. |
| + |
| + See the above discourse on path conversions.""" |
| + |
| + # It may seem strange to discard components of the path, but we are just |
| + # attempting to produce a known-unique output filename; we don't want to |
| + # reuse any global directory. |
|
Nico
2011/08/19 19:07:48
assert not generator_default_variables['INTERMEDIA
|
| + path = StripPrefix(path, |
| + generator_default_variables['SHARED_INTERMEDIATE_DIR']) |
| + path = StripPrefix(path, |
| + generator_default_variables['INTERMEDIATE_DIR']) |
| + path = StripPrefix(path, '/') |
| + assert not path.startswith('$') |
| + path_dir, path_filename = os.path.split(path) |
|
Nico
2011/08/19 19:07:48
Usually, filename = dirname + basename, so I'd cal
|
| + if qualified: |
| + path_filename = self.name + '.' + path_filename |
| + return os.path.normpath(os.path.join('$b/obj', self.base_dir, path_dir, |
| + path_filename)) |
| + |
| + def StampPath(self, name): |
|
Nico
2011/08/19 19:07:48
(If you rename the two functions above, this would
Evan Martin
2011/08/19 20:26:43
I renamed the other functions, but since stamps ne
|
| + """Return a path for a stamp file with a particular name. |
| + |
| + Stamp files are used to collapse a dependency on a bunch of files |
| + into a single file.""" |
| + return self.UniqueOutputPath(name + '.stamp', qualified=True) |
| + |
| + def WriteSpec(self, spec, config): |
| + """The main entry point for NinjaWriter: write the build rules for a spec. |
| + |
| + Returns the path to the build output, or None.""" |
| + |
| + if spec['type'] == 'settings': |
| + # TODO: 'settings' is not actually part of gyp; it was |
| + # accidentally introduced somehow into just the Linux build files. |
| + return None |
| + |
| + self.name = spec['target_name'] |
| + |
| + # Compute predepends for all rules. |
| + # prebuild is the dependencies this target depends on before |
| + # running any of its internal steps. |
| + prebuild = [] |
| + if 'dependencies' in spec: |
| + deps = [self.target_outputs.get(dep, (None, False)) |
| + for dep in spec['dependencies']] |
| + prebuild_deps = [x for x, _ in deps if x] |
|
Nico
2011/08/19 19:07:48
Is this the same as `filter(None, deps.keys())`? I
Evan Martin
2011/08/19 20:26:43
Unfortunately, deps is a list of tuples.
I rewrot
|
| + if prebuild_deps: |
| + prebuild = [self.StampPath('predepends')] |
| + self.ninja.build(prebuild, 'stamp', prebuild_deps) |
| + self.ninja.newline() |
| + |
| + # Write out actions, rules, and copies. These must happen before we |
| + # compile any sources, so compute a list of predependencies for sources |
| + # while we do it. |
| + extra_sources = [] |
| + sources_predepends = self.WriteActionsRulesCopies(spec, extra_sources, |
| + prebuild) |
| + |
| + # Write out the compilation steps, if any. |
| + link_deps = [] |
| + sources = spec.get('sources', []) + extra_sources |
| + if sources: |
| + link_deps = self.WriteSources(config, sources, |
| + sources_predepends or prebuild) |
| + # Some actions/rules output 'sources' that are already object files. |
| + link_deps += [f for f in sources if f.endswith('.o')] |
| + |
| + # The final output of our target depends on the last output of the |
| + # above steps. |
| + final_deps = link_deps or sources_predepends or prebuild |
|
Nico
2011/08/19 19:07:48
`sources_predepends or prebuild` is repeated here
Evan Martin
2011/08/19 20:26:43
I think I had originally used a variable here and
|
| + if final_deps: |
| + return self.WriteTarget(spec, config, final_deps) |
| + |
| + def WriteActionsRulesCopies(self, spec, extra_sources, prebuild): |
| + """Write out the Actions, Rules, and Copies steps. Return any outputs |
| + of these steps (or a stamp file if there are lots of outputs).""" |
| + outputs = [] |
| + |
| + if 'actions' in spec: |
| + outputs += self.WriteActions(spec['actions'], extra_sources, prebuild) |
| + if 'rules' in spec: |
| + outputs += self.WriteRules(spec['rules'], extra_sources, prebuild) |
| + if 'copies' in spec: |
| + outputs += self.WriteCopies(spec['copies'], prebuild) |
| + |
| + # To simplify downstream build edges, ensure we generate a single |
| + # stamp file that represents the results of all of the above. |
| + if len(outputs) > 1: |
| + stamp = self.StampPath('actions_rules_copies') |
| + outputs = self.ninja.build(stamp, 'stamp', outputs) |
| + |
| + return outputs |
| + |
| + def WriteActions(self, actions, extra_sources, prebuild): |
| + all_outputs = [] |
| + for action in actions: |
| + # First write out a rule for the action. |
| + name = action['action_name'] |
| + if 'message' in action: |
| + description = 'ACTION ' + action['message'] |
| + else: |
| + description = 'ACTION %s: %s' % (self.name, action['action_name']) |
| + rule_name = self.WriteNewNinjaRule(name, action['action'], description) |
| + |
| + inputs = [self.ExpandGypPath(i) for i in action['inputs']] |
| + if int(action.get('process_outputs_as_sources', False)): |
| + extra_sources += action['outputs'] |
| + outputs = [self.ExpandGypPath(o) for o in action['outputs']] |
| + |
| + # Then write out an edge using the rule. |
| + self.ninja.build(outputs, rule_name, inputs, |
| + order_only=prebuild) |
| + all_outputs += outputs |
| + |
| + self.ninja.newline() |
| + |
| + return all_outputs |
| + |
| + def WriteRules(self, rules, extra_sources, prebuild): |
| + all_outputs = [] |
| + for rule in rules: |
| + # First write out a rule for the rule action. |
| + name = rule['rule_name'] |
| + args = rule['action'] |
| + if 'message' in rule: |
| + description = 'RULE ' + rule['message'] |
| + else: |
| + description = 'RULE %s: %s $source' % (self.name, name) |
| + rule_name = self.WriteNewNinjaRule(name, args, description) |
| + |
| + # TODO: if the command references the outputs directly, we should |
| + # simplify it to just use $out. |
| + |
| + # Rules can potentially make use of some special variables which |
| + # must vary per source file. |
| + # Compute the list of variables we'll need to provide. |
| + special_locals = ('source', 'root', 'ext', 'name') |
| + needed_variables = set(['source']) |
| + for argument in args: |
| + for var in special_locals: |
| + if '$' + var in argument: |
| + needed_variables.add(var) |
| + |
| + # For each source file, write an edge that generates all the outputs. |
| + for source in rule.get('rule_sources', []): |
| + basename = os.path.basename(source) |
| + root, ext = os.path.splitext(basename) |
| + source = self.ExpandGypPath(source) |
| + |
| + outputs = [] |
| + for output in rule['outputs']: |
| + outputs.append(output.replace('$root', root)) |
| + |
| + extra_bindings = [] |
| + for var in needed_variables: |
| + if var == 'root': |
| + extra_bindings.append(('root', root)) |
| + elif var == 'source': |
| + extra_bindings.append(('source', source)) |
| + elif var == 'ext': |
| + extra_bindings.append(('ext', ext)) |
| + elif var == 'name': |
| + extra_bindings.append(('name', basename)) |
| + else: |
| + assert var == None, repr(var) |
| + |
| + inputs = map(self.ExpandGypPath, rule.get('inputs', [])) |
| + self.ninja.build(outputs, rule_name, source, |
| + implicit=inputs, |
| + order_only=prebuild, |
| + variables=extra_bindings) |
| + |
| + if int(rule.get('process_outputs_as_sources', False)): |
| + extra_sources += outputs |
| + |
| + all_outputs.extend(outputs) |
| + |
| + return all_outputs |
| + |
| + def WriteCopies(self, copies, prebuild): |
| + outputs = [] |
| + for copy in copies: |
| + for path in copy['files']: |
| + # Normalize the path so trailing slashes don't confuse us. |
| + path = os.path.normpath(path) |
| + filename = os.path.split(path)[1] |
|
Nico
2011/08/19 19:07:48
(if you say 'basename' above, do so here too)
|
| + src = self.ExpandGypPath(path) |
| + dst = self.ExpandGypPath(os.path.join(copy['destination'], filename)) |
| + self.ninja.build(dst, 'copy', src, |
| + order_only=prebuild) |
| + outputs.append(dst) |
| + |
| + return outputs |
| + |
| + def WriteSources(self, config, sources, predepends): |
| + """Write build rules to compile all of |sources|.""" |
| + self.WriteVariableList('defines', |
| + ['-D' + MaybeQuoteShellArgument(ninja_syntax.escape(d)) |
| + for d in config.get('defines', [])]) |
| + self.WriteVariableList('includes', |
| + ['-I' + self.ExpandGypPath(i) |
| + for i in config.get('include_dirs', [])]) |
| + self.WriteVariableList('cflags', config.get('cflags')) |
| + self.WriteVariableList('cflags_cc', config.get('cflags_c')) |
| + self.WriteVariableList('cflags_cxx', config.get('cflags_cc')) |
| + self.ninja.newline() |
| + outputs = [] |
| + for source in sources: |
| + filename, ext = os.path.splitext(source) |
| + ext = ext[1:] |
| + if ext in ('cc', 'cpp', 'cxx'): |
| + command = 'cxx' |
| + elif ext in ('c', 's', 'S'): |
| + command = 'cc' |
| + else: |
| + # if ext in ('h', 'hxx'): |
|
Nico
2011/08/19 19:07:48
is this a todo? something else?
|
| + # elif ext in ('re', 'gperf', 'grd', ): |
| + continue |
| + input = self.ExpandGypPath(source) |
| + output = self.UniqueOutputPath(filename + '.o', qualified=True) |
| + self.ninja.build(output, command, input, |
| + order_only=predepends) |
| + outputs.append(output) |
| + self.ninja.newline() |
| + return outputs |
| + |
| + def WriteTarget(self, spec, config, final_deps): |
| + output = self.ComputeOutput(spec) |
| + |
| + output_uses_linker = spec['type'] in ('executable', 'loadable_module', |
| + 'shared_library') |
| + |
| + implicit_deps = set() |
| + if 'dependencies' in spec: |
| + # Two kinds of dependencies: |
| + # - Linkable dependencies (like a .a or a .so): add them to the link line. |
| + # - Non-linkable dependencies (like a rule that generates a |
| + # file and writes a stamp file): add them to implicit_deps |
|
Nico
2011/08/19 19:07:48
nit break: before the "and" instead of before the
|
| + if output_uses_linker: |
| + extra_deps = set() |
| + for dep in spec['dependencies']: |
| + input, linkable = self.target_outputs.get(dep, (None, False)) |
| + if not input: |
| + continue |
| + if linkable: |
| + extra_deps.add(input) |
| + else: |
| + # XXX Chrome-specific HACK. Chrome runs this lastchange rule on |
| + # every build, but we don't want to rebuild when it runs. |
| + if 'lastchange.stamp' not in input: |
| + implicit_deps.add(input) |
| + final_deps.extend(list(extra_deps)) |
| + command_map = { |
| + 'executable': 'link', |
| + 'static_library': 'alink', |
| + 'loadable_module': 'solink', |
| + 'shared_library': 'solink', |
| + 'none': 'stamp', |
| + } |
| + command = command_map[spec['type']] |
| + |
| + if output_uses_linker: |
| + self.WriteVariableList('ldflags', |
| + gyp.common.uniquer(config.get('ldflags', []))) |
| + self.WriteVariableList('libs', |
| + gyp.common.uniquer(spec.get('libraries', []))) |
| + |
| + extra_bindings = [] |
| + if command == 'solink': |
| + extra_bindings.append(('soname', os.path.split(output)[1])) |
| + |
| + self.ninja.build(output, command, final_deps, |
| + implicit=list(implicit_deps), |
| + variables=extra_bindings) |
| + |
| + # 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) |
| + |
| + return output |
| + |
| + def ComputeOutputFileName(self, spec): |
| + # Compute filename prefix: the product prefix, or a default for |
| + # the product type. |
| + DEFAULT_PREFIX = { |
| + 'loadable_module': 'lib', |
| + 'shared_library': 'lib', |
| + } |
| + prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(spec['type'], '')) |
| + |
| + # Compute filename extension: the product extension, or a default |
| + # for the product type. |
| + DEFAULT_EXTENSION = { |
| + 'static_library': 'a', |
| + 'loadable_module': 'so', |
| + 'shared_library': 'so', |
| + } |
| + extension = spec.get('product_extension', |
| + DEFAULT_EXTENSION.get(spec['type'], '')) |
| + if extension: |
| + extension = '.' + extension |
| + |
| + if 'product_name' in spec: |
| + # If we were given an explicit name, use that. |
| + target = spec['product_name'] |
| + else: |
| + # Otherwise, derive a name from the target name. |
| + target = spec['target_name'] |
| + if prefix == 'lib': |
| + # Snip out an extra 'lib' from libs if appropriate. |
| + target = StripPrefix(target, 'lib') |
| + |
| + if spec['type'] in ('static_library', 'loadable_module', 'shared_library', |
| + 'executable'): |
| + return '%s%s%s' % (prefix, target, extension) |
| + elif spec['type'] == 'none': |
| + return '%s.stamp' % target |
| + elif spec['type'] == 'settings': |
| + return None |
| + else: |
| + raise 'Unhandled output type', spec['type'] |
| + |
| + def ComputeOutput(self, spec): |
| + filename = self.ComputeOutputFileName(spec) |
| + |
| + if 'product_dir' in spec: |
| + path = os.path.join(spec['product_dir'], filename) |
| + print 'pdir', path |
| + return path |
| + |
| + # Executables and loadable modules go into the output root, |
| + # libraries go into shared library dir, and everything else |
| + # goes into the normal place. |
| + if spec['type'] in ('executable', 'loadable_module'): |
| + return os.path.join('$b/', filename) |
| + elif spec['type'] == 'shared_library': |
| + return os.path.join('$b/lib', filename) |
| + else: |
| + return self.UniqueOutputPath(filename) |
| + |
| + def WriteVariableList(self, var, values): |
| + if values is None: |
| + values = [] |
| + self.ninja.variable(var, ' '.join(values)) |
| + |
| + def WriteNewNinjaRule(self, name, args, description): |
| + """Write out a new ninja "rule" statement for a given command. |
| + |
| + Returns the name of the new rule.""" |
| + |
| + # TODO: we shouldn't need to qualify names; we do it because |
| + # currently the ninja rule namespace is global, but it really |
| + # should be scoped to the subninja. |
| + rule_name = ('%s.%s' % (self.name, name)).replace(' ', '_') |
| + |
| + cd = '' |
| + args = args[:] |
| + if self.base_dir: |
| + # gyp dictates that commands are run from the base directory. |
| + # cd into the directory before running, and adjust all paths in |
| + # the arguments point to the proper locations. |
| + cd = 'cd %s; ' % self.base_dir |
| + cdup = '../' * len(self.base_dir.split('/')) |
| + for i, arg in enumerate(args): |
| + arg = arg.replace('$b', cdup + '$b') |
| + arg = arg.replace('$source', cdup + '$source') |
| + args[i] = arg |
| + |
| + command = cd + gyp.common.EncodePOSIXShellList(args) |
| + self.ninja.rule(rule_name, command, description) |
| + self.ninja.newline() |
| + |
| + return rule_name |
| + |
| + |
| +def CalculateVariables(default_variables, params): |
| + """Calculate additional variables for use in the build (called by gyp).""" |
| + cc_target = os.environ.get('CC.target', os.environ.get('CC', 'cc')) |
| + default_variables['LINKER_SUPPORTS_ICF'] = \ |
| + gyp.system_test.TestLinkerSupportsICF(cc_command=cc_target) |
| + |
| + |
| +def OpenOutput(path): |
| + """Open |path| for writing, creating directories if necessary.""" |
| + try: |
| + os.makedirs(os.path.dirname(path)) |
| + except OSError: |
| + pass |
| + return open(path, 'w') |
| + |
| + |
| +def GenerateOutput(target_list, target_dicts, data, params): |
| + options = params['options'] |
| + generator_flags = params.get('generator_flags', {}) |
| + |
| + if options.generator_output: |
| + raise NotImplementedError, "--generator_output not implemented for ninja" |
| + |
| + config_name = generator_flags.get('config', None) |
| + if config_name is None: |
| + # Guess which config we want to use: pick the first one from the |
| + # first target. |
| + config_name = target_dicts[target_list[0]]['default_configuration'] |
| + |
| + # builddir: relative path from source root to our output files. |
| + # e.g. "out/Debug" |
| + builddir = os.path.join(generator_flags.get('output_dir', 'out'), config_name) |
| + |
| + master_ninja = OpenOutput(os.path.join(options.toplevel_dir, builddir, |
| + 'build.ninja')) |
| + master_ninja.write(NINJA_BASE % { |
| + 'builddir': builddir, |
| + 'cc': os.environ.get('CC', 'gcc'), |
| + 'cxx': os.environ.get('CXX', 'g++'), |
| + }) |
| + |
| + all_targets = set() |
| + for build_file in params['build_files']: |
| + for target in gyp.common.AllTargets(target_list, target_dicts, build_file): |
| + all_targets.add(target) |
| + all_outputs = set() |
| + |
| + subninjas = set() |
| + target_outputs = {} |
| + for qualified_target in target_list: |
| + # qualified_target is like: third_party/icu/icu.gyp:icui18n#target |
| + build_file, target, _ = gyp.common.ParseQualifiedTarget(qualified_target) |
| + |
| + # TODO: what is options.depth and how is it different than |
| + # options.toplevel_dir? |
| + build_file = gyp.common.RelativePath(build_file, options.depth) |
| + |
| + base_path = os.path.dirname(build_file) |
| + output_file = os.path.join(builddir, 'obj', base_path, target + '.ninja') |
| + spec = target_dicts[qualified_target] |
| + config = spec['configurations'][config_name] |
| + |
| + writer = NinjaWriter(target_outputs, base_path, |
| + OpenOutput(os.path.join(options.toplevel_dir, |
| + output_file))) |
| + subninjas.add(output_file) |
| + |
| + output = writer.WriteSpec(spec, config) |
| + if output: |
| + linkable = spec['type'] in ('static_library', 'shared_library') |
| + target_outputs[qualified_target] = (output, linkable) |
| + |
| + if qualified_target in all_targets: |
| + all_outputs.add(output) |
| + |
| + for ninja in subninjas: |
| + print >>master_ninja, 'subninja', ninja |
| + |
| + if all_outputs: |
| + print >>master_ninja, 'build all: phony ||' + ' '.join(all_outputs) |
| + |
| + master_ninja.close() |