1from __future__ import print_function
2# Copyright (c) 2013 Google Inc. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import collections
7import copy
8import hashlib
9import json
10import multiprocessing
11import os.path
12import re
13import signal
14import subprocess
15import sys
16import gyp
17import gyp.common
18from gyp.common import OrderedSet
19import gyp.msvs_emulation
20import gyp.MSVSUtil as MSVSUtil
21import gyp.xcode_emulation
22try:
23  from cStringIO import StringIO
24except ImportError:
25  from io import StringIO
26
27from gyp.common import GetEnvironFallback
28import gyp.ninja_syntax as ninja_syntax
29
30generator_default_variables = {
31  'EXECUTABLE_PREFIX': '',
32  'EXECUTABLE_SUFFIX': '',
33  'STATIC_LIB_PREFIX': 'lib',
34  'STATIC_LIB_SUFFIX': '.a',
35  'SHARED_LIB_PREFIX': 'lib',
36
37  # Gyp expects the following variables to be expandable by the build
38  # system to the appropriate locations.  Ninja prefers paths to be
39  # known at gyp time.  To resolve this, introduce special
40  # variables starting with $! and $| (which begin with a $ so gyp knows it
41  # should be treated specially, but is otherwise an invalid
42  # ninja/shell variable) that are passed to gyp here but expanded
43  # before writing out into the target .ninja files; see
44  # ExpandSpecial.
45  # $! is used for variables that represent a path and that can only appear at
46  # the start of a string, while $| is used for variables that can appear
47  # anywhere in a string.
48  'INTERMEDIATE_DIR': '$!INTERMEDIATE_DIR',
49  'SHARED_INTERMEDIATE_DIR': '$!PRODUCT_DIR/gen',
50  'PRODUCT_DIR': '$!PRODUCT_DIR',
51  'CONFIGURATION_NAME': '$|CONFIGURATION_NAME',
52
53  # Special variables that may be used by gyp 'rule' targets.
54  # We generate definitions for these variables on the fly when processing a
55  # rule.
56  'RULE_INPUT_ROOT': '${root}',
57  'RULE_INPUT_DIRNAME': '${dirname}',
58  'RULE_INPUT_PATH': '${source}',
59  'RULE_INPUT_EXT': '${ext}',
60  'RULE_INPUT_NAME': '${name}',
61}
62
63# Placates pylint.
64generator_additional_non_configuration_keys = []
65generator_additional_path_sections = []
66generator_extra_sources_for_rules = []
67generator_filelist_paths = None
68
69generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
70
71def StripPrefix(arg, prefix):
72  if arg.startswith(prefix):
73    return arg[len(prefix):]
74  return arg
75
76
77def QuoteShellArgument(arg, flavor):
78  """Quote a string such that it will be interpreted as a single argument
79  by the shell."""
80  # Rather than attempting to enumerate the bad shell characters, just
81  # whitelist common OK ones and quote anything else.
82  if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
83    return arg  # No quoting necessary.
84  if flavor == 'win':
85    return gyp.msvs_emulation.QuoteForRspFile(arg)
86  return "'" + arg.replace("'", "'" + '"\'"' + "'")  + "'"
87
88
89def Define(d, flavor):
90  """Takes a preprocessor define and returns a -D parameter that's ninja- and
91  shell-escaped."""
92  if flavor == 'win':
93    # cl.exe replaces literal # characters with = in preprocesor definitions for
94    # some reason. Octal-encode to work around that.
95    d = d.replace('#', '\\%03o' % ord('#'))
96  return QuoteShellArgument(ninja_syntax.escape('-D' + d), flavor)
97
98
99def AddArch(output, arch):
100  """Adds an arch string to an output path."""
101  output, extension = os.path.splitext(output)
102  return '%s.%s%s' % (output, arch, extension)
103
104
105class Target(object):
106  """Target represents the paths used within a single gyp target.
107
108  Conceptually, building a single target A is a series of steps:
109
110  1) actions/rules/copies  generates source/resources/etc.
111  2) compiles              generates .o files
112  3) link                  generates a binary (library/executable)
113  4) bundle                merges the above in a mac bundle
114
115  (Any of these steps can be optional.)
116
117  From a build ordering perspective, a dependent target B could just
118  depend on the last output of this series of steps.
119
120  But some dependent commands sometimes need to reach inside the box.
121  For example, when linking B it needs to get the path to the static
122  library generated by A.
123
124  This object stores those paths.  To keep things simple, member
125  variables only store concrete paths to single files, while methods
126  compute derived values like "the last output of the target".
127  """
128  def __init__(self, type):
129    # Gyp type ("static_library", etc.) of this target.
130    self.type = type
131    # File representing whether any input dependencies necessary for
132    # dependent actions have completed.
133    self.preaction_stamp = None
134    # File representing whether any input dependencies necessary for
135    # dependent compiles have completed.
136    self.precompile_stamp = None
137    # File representing the completion of actions/rules/copies, if any.
138    self.actions_stamp = None
139    # Path to the output of the link step, if any.
140    self.binary = None
141    # Path to the file representing the completion of building the bundle,
142    # if any.
143    self.bundle = None
144    # On Windows, incremental linking requires linking against all the .objs
145    # that compose a .lib (rather than the .lib itself). That list is stored
146    # here. In this case, we also need to save the compile_deps for the target,
147    # so that the target that directly depends on the .objs can also depend
148    # on those.
149    self.component_objs = None
150    self.compile_deps = None
151    # Windows only. The import .lib is the output of a build step, but
152    # because dependents only link against the lib (not both the lib and the
153    # dll) we keep track of the import library here.
154    self.import_lib = None
155
156  def Linkable(self):
157    """Return true if this is a target that can be linked against."""
158    return self.type in ('static_library', 'shared_library')
159
160  def UsesToc(self, flavor):
161    """Return true if the target should produce a restat rule based on a TOC
162    file."""
163    # For bundles, the .TOC should be produced for the binary, not for
164    # FinalOutput(). But the naive approach would put the TOC file into the
165    # bundle, so don't do this for bundles for now.
166    if flavor == 'win' or self.bundle:
167      return False
168    return self.type in ('shared_library', 'loadable_module')
169
170  def PreActionInput(self, flavor):
171    """Return the path, if any, that should be used as a dependency of
172    any dependent action step."""
173    if self.UsesToc(flavor):
174      return self.FinalOutput() + '.TOC'
175    return self.FinalOutput() or self.preaction_stamp
176
177  def PreCompileInput(self):
178    """Return the path, if any, that should be used as a dependency of
179    any dependent compile step."""
180    return self.actions_stamp or self.precompile_stamp
181
182  def FinalOutput(self):
183    """Return the last output of the target, which depends on all prior
184    steps."""
185    return self.bundle or self.binary or self.actions_stamp
186
187
188# A small discourse on paths as used within the Ninja build:
189# All files we produce (both at gyp and at build time) appear in the
190# build directory (e.g. out/Debug).
191#
192# Paths within a given .gyp file are always relative to the directory
193# containing the .gyp file.  Call these "gyp paths".  This includes
194# sources as well as the starting directory a given gyp rule/action
195# expects to be run from.  We call the path from the source root to
196# the gyp file the "base directory" within the per-.gyp-file
197# NinjaWriter code.
198#
199# All paths as written into the .ninja files are relative to the build
200# directory.  Call these paths "ninja paths".
201#
202# We translate between these two notions of paths with two helper
203# functions:
204#
205# - GypPathToNinja translates a gyp path (i.e. relative to the .gyp file)
206#   into the equivalent ninja path.
207#
208# - GypPathToUniqueOutput translates a gyp path into a ninja path to write
209#   an output file; the result can be namespaced such that it is unique
210#   to the input file name as well as the output target name.
211
212class NinjaWriter(object):
213  def __init__(self, hash_for_rules, target_outputs, base_dir, build_dir,
214               output_file, toplevel_build, output_file_name, flavor,
215               toplevel_dir=None):
216    """
217    base_dir: path from source root to directory containing this gyp file,
218              by gyp semantics, all input paths are relative to this
219    build_dir: path from source root to build output
220    toplevel_dir: path to the toplevel directory
221    """
222
223    self.hash_for_rules = hash_for_rules
224    self.target_outputs = target_outputs
225    self.base_dir = base_dir
226    self.build_dir = build_dir
227    self.ninja = ninja_syntax.Writer(output_file)
228    self.toplevel_build = toplevel_build
229    self.output_file_name = output_file_name
230
231    self.flavor = flavor
232    self.abs_build_dir = None
233    if toplevel_dir is not None:
234      self.abs_build_dir = os.path.abspath(os.path.join(toplevel_dir,
235                                                        build_dir))
236    self.obj_ext = '.obj' if flavor == 'win' else '.o'
237    if flavor == 'win':
238      # See docstring of msvs_emulation.GenerateEnvironmentFiles().
239      self.win_env = {}
240      for arch in ('x86', 'x64'):
241        self.win_env[arch] = 'environment.' + arch
242
243    # Relative path from build output dir to base dir.
244    build_to_top = gyp.common.InvertRelativePath(build_dir, toplevel_dir)
245    self.build_to_base = os.path.join(build_to_top, base_dir)
246    # Relative path from base dir to build dir.
247    base_to_top = gyp.common.InvertRelativePath(base_dir, toplevel_dir)
248    self.base_to_build = os.path.join(base_to_top, build_dir)
249
250  def ExpandSpecial(self, path, product_dir=None):
251    """Expand specials like $!PRODUCT_DIR in |path|.
252
253    If |product_dir| is None, assumes the cwd is already the product
254    dir.  Otherwise, |product_dir| is the relative path to the product
255    dir.
256    """
257
258    PRODUCT_DIR = '$!PRODUCT_DIR'
259    if PRODUCT_DIR in path:
260      if product_dir:
261        path = path.replace(PRODUCT_DIR, product_dir)
262      else:
263        path = path.replace(PRODUCT_DIR + '/', '')
264        path = path.replace(PRODUCT_DIR + '\\', '')
265        path = path.replace(PRODUCT_DIR, '.')
266
267    INTERMEDIATE_DIR = '$!INTERMEDIATE_DIR'
268    if INTERMEDIATE_DIR in path:
269      int_dir = self.GypPathToUniqueOutput('gen')
270      # GypPathToUniqueOutput generates a path relative to the product dir,
271      # so insert product_dir in front if it is provided.
272      path = path.replace(INTERMEDIATE_DIR,
273                          os.path.join(product_dir or '', int_dir))
274
275    CONFIGURATION_NAME = '$|CONFIGURATION_NAME'
276    path = path.replace(CONFIGURATION_NAME, self.config_name)
277
278    return path
279
280  def ExpandRuleVariables(self, path, root, dirname, source, ext, name):
281    if self.flavor == 'win':
282      path = self.msvs_settings.ConvertVSMacros(
283          path, config=self.config_name)
284    path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root)
285    path = path.replace(generator_default_variables['RULE_INPUT_DIRNAME'],
286                        dirname)
287    path = path.replace(generator_default_variables['RULE_INPUT_PATH'], source)
288    path = path.replace(generator_default_variables['RULE_INPUT_EXT'], ext)
289    path = path.replace(generator_default_variables['RULE_INPUT_NAME'], name)
290    return path
291
292  def GypPathToNinja(self, path, env=None):
293    """Translate a gyp path to a ninja path, optionally expanding environment
294    variable references in |path| with |env|.
295
296    See the above discourse on path conversions."""
297    if env:
298      if self.flavor == 'mac':
299        path = gyp.xcode_emulation.ExpandEnvVars(path, env)
300      elif self.flavor == 'win':
301        path = gyp.msvs_emulation.ExpandMacros(path, env)
302    if path.startswith('$!'):
303      expanded = self.ExpandSpecial(path)
304      if self.flavor == 'win':
305        expanded = os.path.normpath(expanded)
306      return expanded
307    if '$|' in path:
308      path = self.ExpandSpecial(path)
309    assert '$' not in path, path
310    return os.path.normpath(os.path.join(self.build_to_base, path))
311
312  def GypPathToUniqueOutput(self, path, qualified=True):
313    """Translate a gyp path to a ninja path for writing output.
314
315    If qualified is True, qualify the resulting filename with the name
316    of the target.  This is necessary when e.g. compiling the same
317    path twice for two separate output targets.
318
319    See the above discourse on path conversions."""
320
321    path = self.ExpandSpecial(path)
322    assert not path.startswith('$'), path
323
324    # Translate the path following this scheme:
325    #   Input: foo/bar.gyp, target targ, references baz/out.o
326    #   Output: obj/foo/baz/targ.out.o (if qualified)
327    #           obj/foo/baz/out.o (otherwise)
328    #     (and obj.host instead of obj for cross-compiles)
329    #
330    # Why this scheme and not some other one?
331    # 1) for a given input, you can compute all derived outputs by matching
332    #    its path, even if the input is brought via a gyp file with '..'.
333    # 2) simple files like libraries and stamps have a simple filename.
334
335    obj = 'obj'
336    if self.toolset != 'target':
337      obj += '.' + self.toolset
338
339    path_dir, path_basename = os.path.split(path)
340    assert not os.path.isabs(path_dir), (
341        "'%s' can not be absolute path (see crbug.com/462153)." % path_dir)
342
343    if qualified:
344      path_basename = self.name + '.' + path_basename
345    return os.path.normpath(os.path.join(obj, self.base_dir, path_dir,
346                                         path_basename))
347
348  def WriteCollapsedDependencies(self, name, targets, order_only=None):
349    """Given a list of targets, return a path for a single file
350    representing the result of building all the targets or None.
351
352    Uses a stamp file if necessary."""
353
354    assert targets == filter(None, targets), targets
355    if len(targets) == 0:
356      assert not order_only
357      return None
358    if len(targets) > 1 or order_only:
359      stamp = self.GypPathToUniqueOutput(name + '.stamp')
360      targets = self.ninja.build(stamp, 'stamp', targets, order_only=order_only)
361      self.ninja.newline()
362    return targets[0]
363
364  def _SubninjaNameForArch(self, arch):
365    output_file_base = os.path.splitext(self.output_file_name)[0]
366    return '%s.%s.ninja' % (output_file_base, arch)
367
368  def WriteSpec(self, spec, config_name, generator_flags):
369    """The main entry point for NinjaWriter: write the build rules for a spec.
370
371    Returns a Target object, which represents the output paths for this spec.
372    Returns None if there are no outputs (e.g. a settings-only 'none' type
373    target)."""
374
375    self.config_name = config_name
376    self.name = spec['target_name']
377    self.toolset = spec['toolset']
378    config = spec['configurations'][config_name]
379    self.target = Target(spec['type'])
380    self.is_standalone_static_library = bool(
381        spec.get('standalone_static_library', 0))
382    # Track if this target contains any C++ files, to decide if gcc or g++
383    # should be used for linking.
384    self.uses_cpp = False
385
386    self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec)
387    self.xcode_settings = self.msvs_settings = None
388    if self.flavor == 'mac':
389      self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec)
390    if self.flavor == 'win':
391      self.msvs_settings = gyp.msvs_emulation.MsvsSettings(spec,
392                                                           generator_flags)
393      arch = self.msvs_settings.GetArch(config_name)
394      self.ninja.variable('arch', self.win_env[arch])
395      self.ninja.variable('cc', '$cl_' + arch)
396      self.ninja.variable('cxx', '$cl_' + arch)
397      self.ninja.variable('cc_host', '$cl_' + arch)
398      self.ninja.variable('cxx_host', '$cl_' + arch)
399      self.ninja.variable('asm', '$ml_' + arch)
400
401    if self.flavor == 'mac':
402      self.archs = self.xcode_settings.GetActiveArchs(config_name)
403      if len(self.archs) > 1:
404        self.arch_subninjas = dict(
405            (arch, ninja_syntax.Writer(
406                OpenOutput(os.path.join(self.toplevel_build,
407                                        self._SubninjaNameForArch(arch)),
408                           'w')))
409            for arch in self.archs)
410
411    # Compute predepends for all rules.
412    # actions_depends is the dependencies this target depends on before running
413    # any of its action/rule/copy steps.
414    # compile_depends is the dependencies this target depends on before running
415    # any of its compile steps.
416    actions_depends = []
417    compile_depends = []
418    # TODO(evan): it is rather confusing which things are lists and which
419    # are strings.  Fix these.
420    if 'dependencies' in spec:
421      for dep in spec['dependencies']:
422        if dep in self.target_outputs:
423          target = self.target_outputs[dep]
424          actions_depends.append(target.PreActionInput(self.flavor))
425          compile_depends.append(target.PreCompileInput())
426      actions_depends = filter(None, actions_depends)
427      compile_depends = filter(None, compile_depends)
428      actions_depends = self.WriteCollapsedDependencies('actions_depends',
429                                                        actions_depends)
430      compile_depends = self.WriteCollapsedDependencies('compile_depends',
431                                                        compile_depends)
432      self.target.preaction_stamp = actions_depends
433      self.target.precompile_stamp = compile_depends
434
435    # Write out actions, rules, and copies.  These must happen before we
436    # compile any sources, so compute a list of predependencies for sources
437    # while we do it.
438    extra_sources = []
439    mac_bundle_depends = []
440    self.target.actions_stamp = self.WriteActionsRulesCopies(
441        spec, extra_sources, actions_depends, mac_bundle_depends)
442
443    # If we have actions/rules/copies, we depend directly on those, but
444    # otherwise we depend on dependent target's actions/rules/copies etc.
445    # We never need to explicitly depend on previous target's link steps,
446    # because no compile ever depends on them.
447    compile_depends_stamp = (self.target.actions_stamp or compile_depends)
448
449    # Write out the compilation steps, if any.
450    link_deps = []
451    sources = extra_sources + spec.get('sources', [])
452    if sources:
453      if self.flavor == 'mac' and len(self.archs) > 1:
454        # Write subninja file containing compile and link commands scoped to
455        # a single arch if a fat binary is being built.
456        for arch in self.archs:
457          self.ninja.subninja(self._SubninjaNameForArch(arch))
458
459      pch = None
460      if self.flavor == 'win':
461        gyp.msvs_emulation.VerifyMissingSources(
462            sources, self.abs_build_dir, generator_flags, self.GypPathToNinja)
463        pch = gyp.msvs_emulation.PrecompiledHeader(
464            self.msvs_settings, config_name, self.GypPathToNinja,
465            self.GypPathToUniqueOutput, self.obj_ext)
466      else:
467        pch = gyp.xcode_emulation.MacPrefixHeader(
468            self.xcode_settings, self.GypPathToNinja,
469            lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang))
470      link_deps = self.WriteSources(
471          self.ninja, config_name, config, sources, compile_depends_stamp, pch,
472          spec)
473      # Some actions/rules output 'sources' that are already object files.
474      obj_outputs = [f for f in sources if f.endswith(self.obj_ext)]
475      if obj_outputs:
476        if self.flavor != 'mac' or len(self.archs) == 1:
477          link_deps += [self.GypPathToNinja(o) for o in obj_outputs]
478        else:
479          print("Warning: Actions/rules writing object files don't work with " \
480                "multiarch targets, dropping. (target %s)" % spec['target_name'])
481    elif self.flavor == 'mac' and len(self.archs) > 1:
482      link_deps = collections.defaultdict(list)
483
484    compile_deps = self.target.actions_stamp or actions_depends
485    if self.flavor == 'win' and self.target.type == 'static_library':
486      self.target.component_objs = link_deps
487      self.target.compile_deps = compile_deps
488
489    # Write out a link step, if needed.
490    output = None
491    is_empty_bundle = not link_deps and not mac_bundle_depends
492    if link_deps or self.target.actions_stamp or actions_depends:
493      output = self.WriteTarget(spec, config_name, config, link_deps,
494                                compile_deps)
495      if self.is_mac_bundle:
496        mac_bundle_depends.append(output)
497
498    # Bundle all of the above together, if needed.
499    if self.is_mac_bundle:
500      output = self.WriteMacBundle(spec, mac_bundle_depends, is_empty_bundle)
501
502    if not output:
503      return None
504
505    assert self.target.FinalOutput(), output
506    return self.target
507
508  def _WinIdlRule(self, source, prebuild, outputs):
509    """Handle the implicit VS .idl rule for one source file. Fills |outputs|
510    with files that are generated."""
511    outdir, output, vars, flags = self.msvs_settings.GetIdlBuildData(
512        source, self.config_name)
513    outdir = self.GypPathToNinja(outdir)
514    def fix_path(path, rel=None):
515      path = os.path.join(outdir, path)
516      dirname, basename = os.path.split(source)
517      root, ext = os.path.splitext(basename)
518      path = self.ExpandRuleVariables(
519          path, root, dirname, source, ext, basename)
520      if rel:
521        path = os.path.relpath(path, rel)
522      return path
523    vars = [(name, fix_path(value, outdir)) for name, value in vars]
524    output = [fix_path(p) for p in output]
525    vars.append(('outdir', outdir))
526    vars.append(('idlflags', flags))
527    input = self.GypPathToNinja(source)
528    self.ninja.build(output, 'idl', input,
529        variables=vars, order_only=prebuild)
530    outputs.extend(output)
531
532  def WriteWinIdlFiles(self, spec, prebuild):
533    """Writes rules to match MSVS's implicit idl handling."""
534    assert self.flavor == 'win'
535    if self.msvs_settings.HasExplicitIdlRulesOrActions(spec):
536      return []
537    outputs = []
538    for source in filter(lambda x: x.endswith('.idl'), spec['sources']):
539      self._WinIdlRule(source, prebuild, outputs)
540    return outputs
541
542  def WriteActionsRulesCopies(self, spec, extra_sources, prebuild,
543                              mac_bundle_depends):
544    """Write out the Actions, Rules, and Copies steps.  Return a path
545    representing the outputs of these steps."""
546    outputs = []
547    if self.is_mac_bundle:
548      mac_bundle_resources = spec.get('mac_bundle_resources', [])[:]
549    else:
550      mac_bundle_resources = []
551    extra_mac_bundle_resources = []
552
553    if 'actions' in spec:
554      outputs += self.WriteActions(spec['actions'], extra_sources, prebuild,
555                                   extra_mac_bundle_resources)
556    if 'rules' in spec:
557      outputs += self.WriteRules(spec['rules'], extra_sources, prebuild,
558                                 mac_bundle_resources,
559                                 extra_mac_bundle_resources)
560    if 'copies' in spec:
561      outputs += self.WriteCopies(spec['copies'], prebuild, mac_bundle_depends)
562
563    if 'sources' in spec and self.flavor == 'win':
564      outputs += self.WriteWinIdlFiles(spec, prebuild)
565
566    stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs)
567
568    if self.is_mac_bundle:
569      xcassets = self.WriteMacBundleResources(
570          extra_mac_bundle_resources + mac_bundle_resources, mac_bundle_depends)
571      partial_info_plist = self.WriteMacXCassets(xcassets, mac_bundle_depends)
572      self.WriteMacInfoPlist(partial_info_plist, mac_bundle_depends)
573
574    return stamp
575
576  def GenerateDescription(self, verb, message, fallback):
577    """Generate and return a description of a build step.
578
579    |verb| is the short summary, e.g. ACTION or RULE.
580    |message| is a hand-written description, or None if not available.
581    |fallback| is the gyp-level name of the step, usable as a fallback.
582    """
583    if self.toolset != 'target':
584      verb += '(%s)' % self.toolset
585    if message:
586      return '%s %s' % (verb, self.ExpandSpecial(message))
587    else:
588      return '%s %s: %s' % (verb, self.name, fallback)
589
590  def WriteActions(self, actions, extra_sources, prebuild,
591                   extra_mac_bundle_resources):
592    # Actions cd into the base directory.
593    env = self.GetToolchainEnv()
594    all_outputs = []
595    for action in actions:
596      # First write out a rule for the action.
597      name = '%s_%s' % (action['action_name'], self.hash_for_rules)
598      description = self.GenerateDescription('ACTION',
599                                             action.get('message', None),
600                                             name)
601      is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(action)
602                   if self.flavor == 'win' else False)
603      args = action['action']
604      depfile = action.get('depfile', None)
605      if depfile:
606        depfile = self.ExpandSpecial(depfile, self.base_to_build)
607      pool = 'console' if int(action.get('ninja_use_console', 0)) else None
608      rule_name, _ = self.WriteNewNinjaRule(name, args, description,
609                                            is_cygwin, env, pool,
610                                            depfile=depfile)
611
612      inputs = [self.GypPathToNinja(i, env) for i in action['inputs']]
613      if int(action.get('process_outputs_as_sources', False)):
614        extra_sources += action['outputs']
615      if int(action.get('process_outputs_as_mac_bundle_resources', False)):
616        extra_mac_bundle_resources += action['outputs']
617      outputs = [self.GypPathToNinja(o, env) for o in action['outputs']]
618
619      # Then write out an edge using the rule.
620      self.ninja.build(outputs, rule_name, inputs,
621                       order_only=prebuild)
622      all_outputs += outputs
623
624      self.ninja.newline()
625
626    return all_outputs
627
628  def WriteRules(self, rules, extra_sources, prebuild,
629                 mac_bundle_resources, extra_mac_bundle_resources):
630    env = self.GetToolchainEnv()
631    all_outputs = []
632    for rule in rules:
633      # Skip a rule with no action and no inputs.
634      if 'action' not in rule and not rule.get('rule_sources', []):
635        continue
636
637      # First write out a rule for the rule action.
638      name = '%s_%s' % (rule['rule_name'], self.hash_for_rules)
639
640      args = rule['action']
641      description = self.GenerateDescription(
642          'RULE',
643          rule.get('message', None),
644          ('%s ' + generator_default_variables['RULE_INPUT_PATH']) % name)
645      is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(rule)
646                   if self.flavor == 'win' else False)
647      pool = 'console' if int(rule.get('ninja_use_console', 0)) else None
648      rule_name, args = self.WriteNewNinjaRule(
649          name, args, description, is_cygwin, env, pool)
650
651      # TODO: if the command references the outputs directly, we should
652      # simplify it to just use $out.
653
654      # Rules can potentially make use of some special variables which
655      # must vary per source file.
656      # Compute the list of variables we'll need to provide.
657      special_locals = ('source', 'root', 'dirname', 'ext', 'name')
658      needed_variables = set(['source'])
659      for argument in args:
660        for var in special_locals:
661          if '${%s}' % var in argument:
662            needed_variables.add(var)
663
664      def cygwin_munge(path):
665        # pylint: disable=cell-var-from-loop
666        if is_cygwin:
667          return path.replace('\\', '/')
668        return path
669
670      inputs = [self.GypPathToNinja(i, env) for i in rule.get('inputs', [])]
671
672      # If there are n source files matching the rule, and m additional rule
673      # inputs, then adding 'inputs' to each build edge written below will
674      # write m * n inputs. Collapsing reduces this to m + n.
675      sources = rule.get('rule_sources', [])
676      num_inputs = len(inputs)
677      if prebuild:
678        num_inputs += 1
679      if num_inputs > 2 and len(sources) > 2:
680        inputs = [self.WriteCollapsedDependencies(
681          rule['rule_name'], inputs, order_only=prebuild)]
682        prebuild = []
683
684      # For each source file, write an edge that generates all the outputs.
685      for source in sources:
686        source = os.path.normpath(source)
687        dirname, basename = os.path.split(source)
688        root, ext = os.path.splitext(basename)
689
690        # Gather the list of inputs and outputs, expanding $vars if possible.
691        outputs = [self.ExpandRuleVariables(o, root, dirname,
692                                            source, ext, basename)
693                   for o in rule['outputs']]
694
695        if int(rule.get('process_outputs_as_sources', False)):
696          extra_sources += outputs
697
698        was_mac_bundle_resource = source in mac_bundle_resources
699        if was_mac_bundle_resource or \
700            int(rule.get('process_outputs_as_mac_bundle_resources', False)):
701          extra_mac_bundle_resources += outputs
702          # Note: This is n_resources * n_outputs_in_rule.  Put to-be-removed
703          # items in a set and remove them all in a single pass if this becomes
704          # a performance issue.
705          if was_mac_bundle_resource:
706            mac_bundle_resources.remove(source)
707
708        extra_bindings = []
709        for var in needed_variables:
710          if var == 'root':
711            extra_bindings.append(('root', cygwin_munge(root)))
712          elif var == 'dirname':
713            # '$dirname' is a parameter to the rule action, which means
714            # it shouldn't be converted to a Ninja path.  But we don't
715            # want $!PRODUCT_DIR in there either.
716            dirname_expanded = self.ExpandSpecial(dirname, self.base_to_build)
717            extra_bindings.append(('dirname', cygwin_munge(dirname_expanded)))
718          elif var == 'source':
719            # '$source' is a parameter to the rule action, which means
720            # it shouldn't be converted to a Ninja path.  But we don't
721            # want $!PRODUCT_DIR in there either.
722            source_expanded = self.ExpandSpecial(source, self.base_to_build)
723            extra_bindings.append(('source', cygwin_munge(source_expanded)))
724          elif var == 'ext':
725            extra_bindings.append(('ext', ext))
726          elif var == 'name':
727            extra_bindings.append(('name', cygwin_munge(basename)))
728          else:
729            assert var is None, repr(var)
730
731        outputs = [self.GypPathToNinja(o, env) for o in outputs]
732        if self.flavor == 'win':
733          # WriteNewNinjaRule uses unique_name for creating an rsp file on win.
734          extra_bindings.append(('unique_name',
735              hashlib.md5(outputs[0]).hexdigest()))
736        self.ninja.build(outputs, rule_name, self.GypPathToNinja(source),
737                         implicit=inputs,
738                         order_only=prebuild,
739                         variables=extra_bindings)
740
741        all_outputs.extend(outputs)
742
743    return all_outputs
744
745  def WriteCopies(self, copies, prebuild, mac_bundle_depends):
746    outputs = []
747    env = self.GetToolchainEnv()
748    for copy in copies:
749      for path in copy['files']:
750        # Normalize the path so trailing slashes don't confuse us.
751        path = os.path.normpath(path)
752        basename = os.path.split(path)[1]
753        src = self.GypPathToNinja(path, env)
754        dst = self.GypPathToNinja(os.path.join(copy['destination'], basename),
755                                  env)
756        outputs += self.ninja.build(dst, 'copy', src, order_only=prebuild)
757        if self.is_mac_bundle:
758          # gyp has mac_bundle_resources to copy things into a bundle's
759          # Resources folder, but there's no built-in way to copy files to other
760          # places in the bundle. Hence, some targets use copies for this. Check
761          # if this file is copied into the current bundle, and if so add it to
762          # the bundle depends so that dependent targets get rebuilt if the copy
763          # input changes.
764          if dst.startswith(self.xcode_settings.GetBundleContentsFolderPath()):
765            mac_bundle_depends.append(dst)
766
767    return outputs
768
769  def WriteMacBundleResources(self, resources, bundle_depends):
770    """Writes ninja edges for 'mac_bundle_resources'."""
771    xcassets = []
772    for output, res in gyp.xcode_emulation.GetMacBundleResources(
773        generator_default_variables['PRODUCT_DIR'],
774        self.xcode_settings, map(self.GypPathToNinja, resources)):
775      output = self.ExpandSpecial(output)
776      if os.path.splitext(output)[-1] != '.xcassets':
777        isBinary = self.xcode_settings.IsBinaryOutputFormat(self.config_name)
778        self.ninja.build(output, 'mac_tool', res,
779                         variables=[('mactool_cmd', 'copy-bundle-resource'), \
780                                    ('binary', isBinary)])
781        bundle_depends.append(output)
782      else:
783        xcassets.append(res)
784    return xcassets
785
786  def WriteMacXCassets(self, xcassets, bundle_depends):
787    """Writes ninja edges for 'mac_bundle_resources' .xcassets files.
788
789    This add an invocation of 'actool' via the 'mac_tool.py' helper script.
790    It assumes that the assets catalogs define at least one imageset and
791    thus an Assets.car file will be generated in the application resources
792    directory. If this is not the case, then the build will probably be done
793    at each invocation of ninja."""
794    if not xcassets:
795      return
796
797    extra_arguments = {}
798    settings_to_arg = {
799        'XCASSETS_APP_ICON': 'app-icon',
800        'XCASSETS_LAUNCH_IMAGE': 'launch-image',
801    }
802    settings = self.xcode_settings.xcode_settings[self.config_name]
803    for settings_key, arg_name in settings_to_arg.items():
804      value = settings.get(settings_key)
805      if value:
806        extra_arguments[arg_name] = value
807
808    partial_info_plist = None
809    if extra_arguments:
810      partial_info_plist = self.GypPathToUniqueOutput(
811          'assetcatalog_generated_info.plist')
812      extra_arguments['output-partial-info-plist'] = partial_info_plist
813
814    outputs = []
815    outputs.append(
816        os.path.join(
817            self.xcode_settings.GetBundleResourceFolder(),
818            'Assets.car'))
819    if partial_info_plist:
820      outputs.append(partial_info_plist)
821
822    keys = QuoteShellArgument(json.dumps(extra_arguments), self.flavor)
823    extra_env = self.xcode_settings.GetPerTargetSettings()
824    env = self.GetSortedXcodeEnv(additional_settings=extra_env)
825    env = self.ComputeExportEnvString(env)
826
827    bundle_depends.extend(self.ninja.build(
828        outputs, 'compile_xcassets', xcassets,
829        variables=[('env', env), ('keys', keys)]))
830    return partial_info_plist
831
832  def WriteMacInfoPlist(self, partial_info_plist, bundle_depends):
833    """Write build rules for bundle Info.plist files."""
834    info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist(
835        generator_default_variables['PRODUCT_DIR'],
836        self.xcode_settings, self.GypPathToNinja)
837    if not info_plist:
838      return
839    out = self.ExpandSpecial(out)
840    if defines:
841      # Create an intermediate file to store preprocessed results.
842      intermediate_plist = self.GypPathToUniqueOutput(
843          os.path.basename(info_plist))
844      defines = ' '.join([Define(d, self.flavor) for d in defines])
845      info_plist = self.ninja.build(
846          intermediate_plist, 'preprocess_infoplist', info_plist,
847          variables=[('defines',defines)])
848
849    env = self.GetSortedXcodeEnv(additional_settings=extra_env)
850    env = self.ComputeExportEnvString(env)
851
852    if partial_info_plist:
853      intermediate_plist = self.GypPathToUniqueOutput('merged_info.plist')
854      info_plist = self.ninja.build(
855          intermediate_plist, 'merge_infoplist',
856          [partial_info_plist, info_plist])
857
858    keys = self.xcode_settings.GetExtraPlistItems(self.config_name)
859    keys = QuoteShellArgument(json.dumps(keys), self.flavor)
860    isBinary = self.xcode_settings.IsBinaryOutputFormat(self.config_name)
861    self.ninja.build(out, 'copy_infoplist', info_plist,
862                     variables=[('env', env), ('keys', keys),
863                                ('binary', isBinary)])
864    bundle_depends.append(out)
865
866  def WriteSources(self, ninja_file, config_name, config, sources, predepends,
867                   precompiled_header, spec):
868    """Write build rules to compile all of |sources|."""
869    if self.toolset == 'host':
870      self.ninja.variable('ar', '$ar_host')
871      self.ninja.variable('cc', '$cc_host')
872      self.ninja.variable('cxx', '$cxx_host')
873      self.ninja.variable('ld', '$ld_host')
874      self.ninja.variable('ldxx', '$ldxx_host')
875      self.ninja.variable('nm', '$nm_host')
876      self.ninja.variable('readelf', '$readelf_host')
877
878    if self.flavor != 'mac' or len(self.archs) == 1:
879      return self.WriteSourcesForArch(
880          self.ninja, config_name, config, sources, predepends,
881          precompiled_header, spec)
882    else:
883      return dict((arch, self.WriteSourcesForArch(
884            self.arch_subninjas[arch], config_name, config, sources, predepends,
885            precompiled_header, spec, arch=arch))
886          for arch in self.archs)
887
888  def WriteSourcesForArch(self, ninja_file, config_name, config, sources,
889                          predepends, precompiled_header, spec, arch=None):
890    """Write build rules to compile all of |sources|."""
891
892    extra_defines = []
893    if self.flavor == 'mac':
894      cflags = self.xcode_settings.GetCflags(config_name, arch=arch)
895      cflags_c = self.xcode_settings.GetCflagsC(config_name)
896      cflags_cc = self.xcode_settings.GetCflagsCC(config_name)
897      cflags_objc = ['$cflags_c'] + \
898                    self.xcode_settings.GetCflagsObjC(config_name)
899      cflags_objcc = ['$cflags_cc'] + \
900                     self.xcode_settings.GetCflagsObjCC(config_name)
901    elif self.flavor == 'win':
902      asmflags = self.msvs_settings.GetAsmflags(config_name)
903      cflags = self.msvs_settings.GetCflags(config_name)
904      cflags_c = self.msvs_settings.GetCflagsC(config_name)
905      cflags_cc = self.msvs_settings.GetCflagsCC(config_name)
906      extra_defines = self.msvs_settings.GetComputedDefines(config_name)
907      # See comment at cc_command for why there's two .pdb files.
908      pdbpath_c = pdbpath_cc = self.msvs_settings.GetCompilerPdbName(
909          config_name, self.ExpandSpecial)
910      if not pdbpath_c:
911        obj = 'obj'
912        if self.toolset != 'target':
913          obj += '.' + self.toolset
914        pdbpath = os.path.normpath(os.path.join(obj, self.base_dir, self.name))
915        pdbpath_c = pdbpath + '.c.pdb'
916        pdbpath_cc = pdbpath + '.cc.pdb'
917      self.WriteVariableList(ninja_file, 'pdbname_c', [pdbpath_c])
918      self.WriteVariableList(ninja_file, 'pdbname_cc', [pdbpath_cc])
919      self.WriteVariableList(ninja_file, 'pchprefix', [self.name])
920    else:
921      cflags = config.get('cflags', [])
922      cflags_c = config.get('cflags_c', [])
923      cflags_cc = config.get('cflags_cc', [])
924
925    # Respect environment variables related to build, but target-specific
926    # flags can still override them.
927    if self.toolset == 'target':
928      cflags_c = (os.environ.get('CPPFLAGS', '').split() +
929                  os.environ.get('CFLAGS', '').split() + cflags_c)
930      cflags_cc = (os.environ.get('CPPFLAGS', '').split() +
931                   os.environ.get('CXXFLAGS', '').split() + cflags_cc)
932    elif self.toolset == 'host':
933      cflags_c = (os.environ.get('CPPFLAGS_host', '').split() +
934                  os.environ.get('CFLAGS_host', '').split() + cflags_c)
935      cflags_cc = (os.environ.get('CPPFLAGS_host', '').split() +
936                   os.environ.get('CXXFLAGS_host', '').split() + cflags_cc)
937
938    defines = config.get('defines', []) + extra_defines
939    self.WriteVariableList(ninja_file, 'defines',
940                           [Define(d, self.flavor) for d in defines])
941    if self.flavor == 'win':
942      self.WriteVariableList(ninja_file, 'asmflags',
943                             map(self.ExpandSpecial, asmflags))
944      self.WriteVariableList(ninja_file, 'rcflags',
945          [QuoteShellArgument(self.ExpandSpecial(f), self.flavor)
946           for f in self.msvs_settings.GetRcflags(config_name,
947                                                  self.GypPathToNinja)])
948
949    include_dirs = config.get('include_dirs', [])
950
951    env = self.GetToolchainEnv()
952    if self.flavor == 'win':
953      include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs,
954                                                          config_name)
955    self.WriteVariableList(ninja_file, 'includes',
956        [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor)
957         for i in include_dirs])
958
959    if self.flavor == 'win':
960      midl_include_dirs = config.get('midl_include_dirs', [])
961      midl_include_dirs = self.msvs_settings.AdjustMidlIncludeDirs(
962          midl_include_dirs, config_name)
963      self.WriteVariableList(ninja_file, 'midl_includes',
964          [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor)
965           for i in midl_include_dirs])
966
967    pch_commands = precompiled_header.GetPchBuildCommands(arch)
968    if self.flavor == 'mac':
969      # Most targets use no precompiled headers, so only write these if needed.
970      for ext, var in [('c', 'cflags_pch_c'), ('cc', 'cflags_pch_cc'),
971                       ('m', 'cflags_pch_objc'), ('mm', 'cflags_pch_objcc')]:
972        include = precompiled_header.GetInclude(ext, arch)
973        if include: ninja_file.variable(var, include)
974
975    arflags = config.get('arflags', [])
976
977    self.WriteVariableList(ninja_file, 'cflags',
978                           map(self.ExpandSpecial, cflags))
979    self.WriteVariableList(ninja_file, 'cflags_c',
980                           map(self.ExpandSpecial, cflags_c))
981    self.WriteVariableList(ninja_file, 'cflags_cc',
982                           map(self.ExpandSpecial, cflags_cc))
983    if self.flavor == 'mac':
984      self.WriteVariableList(ninja_file, 'cflags_objc',
985                             map(self.ExpandSpecial, cflags_objc))
986      self.WriteVariableList(ninja_file, 'cflags_objcc',
987                             map(self.ExpandSpecial, cflags_objcc))
988    self.WriteVariableList(ninja_file, 'arflags',
989                           map(self.ExpandSpecial, arflags))
990    ninja_file.newline()
991    outputs = []
992    has_rc_source = False
993    for source in sources:
994      filename, ext = os.path.splitext(source)
995      ext = ext[1:]
996      obj_ext = self.obj_ext
997      if ext in ('cc', 'cpp', 'cxx'):
998        command = 'cxx'
999        self.uses_cpp = True
1000      elif ext == 'c' or (ext == 'S' and self.flavor != 'win'):
1001        command = 'cc'
1002      elif ext == 's' and self.flavor != 'win':  # Doesn't generate .o.d files.
1003        command = 'cc_s'
1004      elif (self.flavor == 'win' and ext == 'asm' and
1005            not self.msvs_settings.HasExplicitAsmRules(spec)):
1006        command = 'asm'
1007        # Add the _asm suffix as msvs is capable of handling .cc and
1008        # .asm files of the same name without collision.
1009        obj_ext = '_asm.obj'
1010      elif self.flavor == 'mac' and ext == 'm':
1011        command = 'objc'
1012      elif self.flavor == 'mac' and ext == 'mm':
1013        command = 'objcxx'
1014        self.uses_cpp = True
1015      elif self.flavor == 'win' and ext == 'rc':
1016        command = 'rc'
1017        obj_ext = '.res'
1018        has_rc_source = True
1019      else:
1020        # Ignore unhandled extensions.
1021        continue
1022      input = self.GypPathToNinja(source)
1023      output = self.GypPathToUniqueOutput(filename + obj_ext)
1024      if arch is not None:
1025        output = AddArch(output, arch)
1026      implicit = precompiled_header.GetObjDependencies([input], [output], arch)
1027      variables = []
1028      if self.flavor == 'win':
1029        variables, output, implicit = precompiled_header.GetFlagsModifications(
1030            input, output, implicit, command, cflags_c, cflags_cc,
1031            self.ExpandSpecial)
1032      ninja_file.build(output, command, input,
1033                       implicit=[gch for _, _, gch in implicit],
1034                       order_only=predepends, variables=variables)
1035      outputs.append(output)
1036
1037    if has_rc_source:
1038      resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1039      self.WriteVariableList(ninja_file, 'resource_includes',
1040          [QuoteShellArgument('-I' + self.GypPathToNinja(i, env), self.flavor)
1041           for i in resource_include_dirs])
1042
1043    self.WritePchTargets(ninja_file, pch_commands)
1044
1045    ninja_file.newline()
1046    return outputs
1047
1048  def WritePchTargets(self, ninja_file, pch_commands):
1049    """Writes ninja rules to compile prefix headers."""
1050    if not pch_commands:
1051      return
1052
1053    for gch, lang_flag, lang, input in pch_commands:
1054      var_name = {
1055        'c': 'cflags_pch_c',
1056        'cc': 'cflags_pch_cc',
1057        'm': 'cflags_pch_objc',
1058        'mm': 'cflags_pch_objcc',
1059      }[lang]
1060
1061      map = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', }
1062      cmd = map.get(lang)
1063      ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)])
1064
1065  def WriteLink(self, spec, config_name, config, link_deps):
1066    """Write out a link step. Fills out target.binary. """
1067    if self.flavor != 'mac' or len(self.archs) == 1:
1068      return self.WriteLinkForArch(
1069          self.ninja, spec, config_name, config, link_deps)
1070    else:
1071      output = self.ComputeOutput(spec)
1072      inputs = [self.WriteLinkForArch(self.arch_subninjas[arch], spec,
1073                                      config_name, config, link_deps[arch],
1074                                      arch=arch)
1075                for arch in self.archs]
1076      extra_bindings = []
1077      build_output = output
1078      if not self.is_mac_bundle:
1079        self.AppendPostbuildVariable(extra_bindings, spec, output, output)
1080
1081      # TODO(yyanagisawa): more work needed to fix:
1082      # https://code.google.com/p/gyp/issues/detail?id=411
1083      if (spec['type'] in ('shared_library', 'loadable_module') and
1084          not self.is_mac_bundle):
1085        extra_bindings.append(('lib', output))
1086        self.ninja.build([output, output + '.TOC'], 'solipo', inputs,
1087            variables=extra_bindings)
1088      else:
1089        self.ninja.build(build_output, 'lipo', inputs, variables=extra_bindings)
1090      return output
1091
1092  def WriteLinkForArch(self, ninja_file, spec, config_name, config,
1093                       link_deps, arch=None):
1094    """Write out a link step. Fills out target.binary. """
1095    command = {
1096      'executable':      'link',
1097      'loadable_module': 'solink_module',
1098      'shared_library':  'solink',
1099    }[spec['type']]
1100    command_suffix = ''
1101
1102    implicit_deps = set()
1103    solibs = set()
1104    order_deps = set()
1105
1106    if 'dependencies' in spec:
1107      # Two kinds of dependencies:
1108      # - Linkable dependencies (like a .a or a .so): add them to the link line.
1109      # - Non-linkable dependencies (like a rule that generates a file
1110      #   and writes a stamp file): add them to implicit_deps
1111      extra_link_deps = set()
1112      for dep in spec['dependencies']:
1113        target = self.target_outputs.get(dep)
1114        if not target:
1115          continue
1116        linkable = target.Linkable()
1117        if linkable:
1118          new_deps = []
1119          if (self.flavor == 'win' and
1120              target.component_objs and
1121              self.msvs_settings.IsUseLibraryDependencyInputs(config_name)):
1122            new_deps = target.component_objs
1123            if target.compile_deps:
1124              order_deps.add(target.compile_deps)
1125          elif self.flavor == 'win' and target.import_lib:
1126            new_deps = [target.import_lib]
1127          elif target.UsesToc(self.flavor):
1128            solibs.add(target.binary)
1129            implicit_deps.add(target.binary + '.TOC')
1130          else:
1131            new_deps = [target.binary]
1132          for new_dep in new_deps:
1133            if new_dep not in extra_link_deps:
1134              extra_link_deps.add(new_dep)
1135              link_deps.append(new_dep)
1136
1137        final_output = target.FinalOutput()
1138        if not linkable or final_output != target.binary:
1139          implicit_deps.add(final_output)
1140
1141    extra_bindings = []
1142    if self.uses_cpp and self.flavor != 'win':
1143      extra_bindings.append(('ld', '$ldxx'))
1144
1145    output = self.ComputeOutput(spec, arch)
1146    if arch is None and not self.is_mac_bundle:
1147      self.AppendPostbuildVariable(extra_bindings, spec, output, output)
1148
1149    is_executable = spec['type'] == 'executable'
1150    # The ldflags config key is not used on mac or win. On those platforms
1151    # linker flags are set via xcode_settings and msvs_settings, respectively.
1152    env_ldflags = os.environ.get('LDFLAGS', '').split()
1153    if self.flavor == 'mac':
1154      ldflags = self.xcode_settings.GetLdflags(config_name,
1155          self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']),
1156          self.GypPathToNinja, arch)
1157      ldflags = env_ldflags + ldflags
1158    elif self.flavor == 'win':
1159      manifest_base_name = self.GypPathToUniqueOutput(
1160          self.ComputeOutputFileName(spec))
1161      ldflags, intermediate_manifest, manifest_files = \
1162          self.msvs_settings.GetLdflags(config_name, self.GypPathToNinja,
1163                                        self.ExpandSpecial, manifest_base_name,
1164                                        output, is_executable,
1165                                        self.toplevel_build)
1166      ldflags = env_ldflags + ldflags
1167      self.WriteVariableList(ninja_file, 'manifests', manifest_files)
1168      implicit_deps = implicit_deps.union(manifest_files)
1169      if intermediate_manifest:
1170        self.WriteVariableList(
1171            ninja_file, 'intermediatemanifest', [intermediate_manifest])
1172      command_suffix = _GetWinLinkRuleNameSuffix(
1173          self.msvs_settings.IsEmbedManifest(config_name))
1174      def_file = self.msvs_settings.GetDefFile(self.GypPathToNinja)
1175      if def_file:
1176        implicit_deps.add(def_file)
1177    else:
1178      # Respect environment variables related to build, but target-specific
1179      # flags can still override them.
1180      ldflags = env_ldflags + config.get('ldflags', [])
1181      if is_executable and len(solibs):
1182        rpath = 'lib/'
1183        if self.toolset != 'target':
1184          rpath += self.toolset
1185        ldflags.append(r'-Wl,-rpath=\$$ORIGIN/%s' % rpath)
1186        ldflags.append('-Wl,-rpath-link=%s' % rpath)
1187    self.WriteVariableList(ninja_file, 'ldflags',
1188                           map(self.ExpandSpecial, ldflags))
1189
1190    library_dirs = config.get('library_dirs', [])
1191    if self.flavor == 'win':
1192      library_dirs = [self.msvs_settings.ConvertVSMacros(l, config_name)
1193                      for l in library_dirs]
1194      library_dirs = ['/LIBPATH:' + QuoteShellArgument(self.GypPathToNinja(l),
1195                                                       self.flavor)
1196                      for l in library_dirs]
1197    else:
1198      library_dirs = [QuoteShellArgument('-L' + self.GypPathToNinja(l),
1199                                         self.flavor)
1200                      for l in library_dirs]
1201
1202    libraries = gyp.common.uniquer(map(self.ExpandSpecial,
1203                                       spec.get('libraries', [])))
1204    if self.flavor == 'mac':
1205      libraries = self.xcode_settings.AdjustLibraries(libraries, config_name)
1206    elif self.flavor == 'win':
1207      libraries = self.msvs_settings.AdjustLibraries(libraries)
1208
1209    self.WriteVariableList(ninja_file, 'libs', library_dirs + libraries)
1210
1211    linked_binary = output
1212
1213    if command in ('solink', 'solink_module'):
1214      extra_bindings.append(('soname', os.path.split(output)[1]))
1215      extra_bindings.append(('lib',
1216                            gyp.common.EncodePOSIXShellArgument(output)))
1217      if self.flavor != 'win':
1218        link_file_list = output
1219        if self.is_mac_bundle:
1220          # 'Dependency Framework.framework/Versions/A/Dependency Framework' ->
1221          # 'Dependency Framework.framework.rsp'
1222          link_file_list = self.xcode_settings.GetWrapperName()
1223        if arch:
1224          link_file_list += '.' + arch
1225        link_file_list += '.rsp'
1226        # If an rspfile contains spaces, ninja surrounds the filename with
1227        # quotes around it and then passes it to open(), creating a file with
1228        # quotes in its name (and when looking for the rsp file, the name
1229        # makes it through bash which strips the quotes) :-/
1230        link_file_list = link_file_list.replace(' ', '_')
1231        extra_bindings.append(
1232          ('link_file_list',
1233            gyp.common.EncodePOSIXShellArgument(link_file_list)))
1234      if self.flavor == 'win':
1235        extra_bindings.append(('binary', output))
1236        if ('/NOENTRY' not in ldflags and
1237            not self.msvs_settings.GetNoImportLibrary(config_name)):
1238          self.target.import_lib = output + '.lib'
1239          extra_bindings.append(('implibflag',
1240                                 '/IMPLIB:%s' % self.target.import_lib))
1241          pdbname = self.msvs_settings.GetPDBName(
1242              config_name, self.ExpandSpecial, output + '.pdb')
1243          output = [output, self.target.import_lib]
1244          if pdbname:
1245            output.append(pdbname)
1246      elif not self.is_mac_bundle:
1247        output = [output, output + '.TOC']
1248      else:
1249        command = command + '_notoc'
1250    elif self.flavor == 'win':
1251      extra_bindings.append(('binary', output))
1252      pdbname = self.msvs_settings.GetPDBName(
1253          config_name, self.ExpandSpecial, output + '.pdb')
1254      if pdbname:
1255        output = [output, pdbname]
1256
1257
1258    if len(solibs):
1259      extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs)))
1260
1261    ninja_file.build(output, command + command_suffix, link_deps,
1262                     implicit=list(implicit_deps),
1263                     order_only=list(order_deps),
1264                     variables=extra_bindings)
1265    return linked_binary
1266
1267  def WriteTarget(self, spec, config_name, config, link_deps, compile_deps):
1268    extra_link_deps = any(self.target_outputs.get(dep).Linkable()
1269                          for dep in spec.get('dependencies', [])
1270                          if dep in self.target_outputs)
1271    if spec['type'] == 'none' or (not link_deps and not extra_link_deps):
1272      # TODO(evan): don't call this function for 'none' target types, as
1273      # it doesn't do anything, and we fake out a 'binary' with a stamp file.
1274      self.target.binary = compile_deps
1275      self.target.type = 'none'
1276    elif spec['type'] == 'static_library':
1277      self.target.binary = self.ComputeOutput(spec)
1278      if (self.flavor not in ('mac', 'openbsd', 'netbsd', 'win') and not
1279          self.is_standalone_static_library):
1280        self.ninja.build(self.target.binary, 'alink_thin', link_deps,
1281                         order_only=compile_deps)
1282      else:
1283        variables = []
1284        if self.xcode_settings:
1285          libtool_flags = self.xcode_settings.GetLibtoolflags(config_name)
1286          if libtool_flags:
1287            variables.append(('libtool_flags', libtool_flags))
1288        if self.msvs_settings:
1289          libflags = self.msvs_settings.GetLibFlags(config_name,
1290                                                    self.GypPathToNinja)
1291          variables.append(('libflags', libflags))
1292
1293        if self.flavor != 'mac' or len(self.archs) == 1:
1294          self.AppendPostbuildVariable(variables, spec,
1295                                       self.target.binary, self.target.binary)
1296          self.ninja.build(self.target.binary, 'alink', link_deps,
1297                           order_only=compile_deps, variables=variables)
1298        else:
1299          inputs = []
1300          for arch in self.archs:
1301            output = self.ComputeOutput(spec, arch)
1302            self.arch_subninjas[arch].build(output, 'alink', link_deps[arch],
1303                                            order_only=compile_deps,
1304                                            variables=variables)
1305            inputs.append(output)
1306          # TODO: It's not clear if libtool_flags should be passed to the alink
1307          # call that combines single-arch .a files into a fat .a file.
1308          self.AppendPostbuildVariable(variables, spec,
1309                                       self.target.binary, self.target.binary)
1310          self.ninja.build(self.target.binary, 'alink', inputs,
1311                           # FIXME: test proving order_only=compile_deps isn't
1312                           # needed.
1313                           variables=variables)
1314    else:
1315      self.target.binary = self.WriteLink(spec, config_name, config, link_deps)
1316    return self.target.binary
1317
1318  def WriteMacBundle(self, spec, mac_bundle_depends, is_empty):
1319    assert self.is_mac_bundle
1320    package_framework = spec['type'] in ('shared_library', 'loadable_module')
1321    output = self.ComputeMacBundleOutput()
1322    if is_empty:
1323      output += '.stamp'
1324    variables = []
1325    self.AppendPostbuildVariable(variables, spec, output, self.target.binary,
1326                                 is_command_start=not package_framework)
1327    if package_framework and not is_empty:
1328      variables.append(('version', self.xcode_settings.GetFrameworkVersion()))
1329      self.ninja.build(output, 'package_framework', mac_bundle_depends,
1330                       variables=variables)
1331    else:
1332      self.ninja.build(output, 'stamp', mac_bundle_depends,
1333                       variables=variables)
1334    self.target.bundle = output
1335    return output
1336
1337  def GetToolchainEnv(self, additional_settings=None):
1338    """Returns the variables toolchain would set for build steps."""
1339    env = self.GetSortedXcodeEnv(additional_settings=additional_settings)
1340    if self.flavor == 'win':
1341      env = self.GetMsvsToolchainEnv(
1342          additional_settings=additional_settings)
1343    return env
1344
1345  def GetMsvsToolchainEnv(self, additional_settings=None):
1346    """Returns the variables Visual Studio would set for build steps."""
1347    return self.msvs_settings.GetVSMacroEnv('$!PRODUCT_DIR',
1348                                             config=self.config_name)
1349
1350  def GetSortedXcodeEnv(self, additional_settings=None):
1351    """Returns the variables Xcode would set for build steps."""
1352    assert self.abs_build_dir
1353    abs_build_dir = self.abs_build_dir
1354    return gyp.xcode_emulation.GetSortedXcodeEnv(
1355        self.xcode_settings, abs_build_dir,
1356        os.path.join(abs_build_dir, self.build_to_base), self.config_name,
1357        additional_settings)
1358
1359  def GetSortedXcodePostbuildEnv(self):
1360    """Returns the variables Xcode would set for postbuild steps."""
1361    postbuild_settings = {}
1362    # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack.
1363    # TODO(thakis): It would be nice to have some general mechanism instead.
1364    strip_save_file = self.xcode_settings.GetPerTargetSetting(
1365        'CHROMIUM_STRIP_SAVE_FILE')
1366    if strip_save_file:
1367      postbuild_settings['CHROMIUM_STRIP_SAVE_FILE'] = strip_save_file
1368    return self.GetSortedXcodeEnv(additional_settings=postbuild_settings)
1369
1370  def AppendPostbuildVariable(self, variables, spec, output, binary,
1371                              is_command_start=False):
1372    """Adds a 'postbuild' variable if there is a postbuild for |output|."""
1373    postbuild = self.GetPostbuildCommand(spec, output, binary, is_command_start)
1374    if postbuild:
1375      variables.append(('postbuilds', postbuild))
1376
1377  def GetPostbuildCommand(self, spec, output, output_binary, is_command_start):
1378    """Returns a shell command that runs all the postbuilds, and removes
1379    |output| if any of them fails. If |is_command_start| is False, then the
1380    returned string will start with ' && '."""
1381    if not self.xcode_settings or spec['type'] == 'none' or not output:
1382      return ''
1383    output = QuoteShellArgument(output, self.flavor)
1384    postbuilds = gyp.xcode_emulation.GetSpecPostbuildCommands(spec, quiet=True)
1385    if output_binary is not None:
1386      postbuilds = self.xcode_settings.AddImplicitPostbuilds(
1387          self.config_name,
1388          os.path.normpath(os.path.join(self.base_to_build, output)),
1389          QuoteShellArgument(
1390              os.path.normpath(os.path.join(self.base_to_build, output_binary)),
1391              self.flavor),
1392          postbuilds, quiet=True)
1393
1394    if not postbuilds:
1395      return ''
1396    # Postbuilds expect to be run in the gyp file's directory, so insert an
1397    # implicit postbuild to cd to there.
1398    postbuilds.insert(0, gyp.common.EncodePOSIXShellList(
1399        ['cd', self.build_to_base]))
1400    env = self.ComputeExportEnvString(self.GetSortedXcodePostbuildEnv())
1401    # G will be non-null if any postbuild fails. Run all postbuilds in a
1402    # subshell.
1403    commands = env + ' (' + \
1404        ' && '.join([ninja_syntax.escape(command) for command in postbuilds])
1405    command_string = (commands + '); G=$$?; '
1406                      # Remove the final output if any postbuild failed.
1407                      '((exit $$G) || rm -rf %s) ' % output + '&& exit $$G)')
1408    if is_command_start:
1409      return '(' + command_string + ' && '
1410    else:
1411      return '$ && (' + command_string
1412
1413  def ComputeExportEnvString(self, env):
1414    """Given an environment, returns a string looking like
1415        'export FOO=foo; export BAR="${FOO} bar;'
1416    that exports |env| to the shell."""
1417    export_str = []
1418    for k, v in env:
1419      export_str.append('export %s=%s;' %
1420          (k, ninja_syntax.escape(gyp.common.EncodePOSIXShellArgument(v))))
1421    return ' '.join(export_str)
1422
1423  def ComputeMacBundleOutput(self):
1424    """Return the 'output' (full output path) to a bundle output directory."""
1425    assert self.is_mac_bundle
1426    path = generator_default_variables['PRODUCT_DIR']
1427    return self.ExpandSpecial(
1428        os.path.join(path, self.xcode_settings.GetWrapperName()))
1429
1430  def ComputeOutputFileName(self, spec, type=None):
1431    """Compute the filename of the final output for the current target."""
1432    if not type:
1433      type = spec['type']
1434
1435    default_variables = copy.copy(generator_default_variables)
1436    CalculateVariables(default_variables, {'flavor': self.flavor})
1437
1438    # Compute filename prefix: the product prefix, or a default for
1439    # the product type.
1440    DEFAULT_PREFIX = {
1441      'loadable_module': default_variables['SHARED_LIB_PREFIX'],
1442      'shared_library': default_variables['SHARED_LIB_PREFIX'],
1443      'static_library': default_variables['STATIC_LIB_PREFIX'],
1444      'executable': default_variables['EXECUTABLE_PREFIX'],
1445      }
1446    prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
1447
1448    # Compute filename extension: the product extension, or a default
1449    # for the product type.
1450    DEFAULT_EXTENSION = {
1451        'loadable_module': default_variables['SHARED_LIB_SUFFIX'],
1452        'shared_library': default_variables['SHARED_LIB_SUFFIX'],
1453        'static_library': default_variables['STATIC_LIB_SUFFIX'],
1454        'executable': default_variables['EXECUTABLE_SUFFIX'],
1455      }
1456    extension = spec.get('product_extension')
1457    if extension:
1458      extension = '.' + extension
1459    else:
1460      extension = DEFAULT_EXTENSION.get(type, '')
1461
1462    if 'product_name' in spec:
1463      # If we were given an explicit name, use that.
1464      target = spec['product_name']
1465    else:
1466      # Otherwise, derive a name from the target name.
1467      target = spec['target_name']
1468      if prefix == 'lib':
1469        # Snip out an extra 'lib' from libs if appropriate.
1470        target = StripPrefix(target, 'lib')
1471
1472    if type in ('static_library', 'loadable_module', 'shared_library',
1473                        'executable'):
1474      return '%s%s%s' % (prefix, target, extension)
1475    elif type == 'none':
1476      return '%s.stamp' % target
1477    else:
1478      raise Exception('Unhandled output type %s' % type)
1479
1480  def ComputeOutput(self, spec, arch=None):
1481    """Compute the path for the final output of the spec."""
1482    type = spec['type']
1483
1484    if self.flavor == 'win':
1485      override = self.msvs_settings.GetOutputName(self.config_name,
1486                                                  self.ExpandSpecial)
1487      if override:
1488        return override
1489
1490    if arch is None and self.flavor == 'mac' and type in (
1491        'static_library', 'executable', 'shared_library', 'loadable_module'):
1492      filename = self.xcode_settings.GetExecutablePath()
1493    else:
1494      filename = self.ComputeOutputFileName(spec, type)
1495
1496    if arch is None and 'product_dir' in spec:
1497      path = os.path.join(spec['product_dir'], filename)
1498      return self.ExpandSpecial(path)
1499
1500    # Some products go into the output root, libraries go into shared library
1501    # dir, and everything else goes into the normal place.
1502    type_in_output_root = ['executable', 'loadable_module']
1503    if self.flavor == 'mac' and self.toolset == 'target':
1504      type_in_output_root += ['shared_library', 'static_library']
1505    elif self.flavor == 'win' and self.toolset == 'target':
1506      type_in_output_root += ['shared_library']
1507
1508    if arch is not None:
1509      # Make sure partial executables don't end up in a bundle or the regular
1510      # output directory.
1511      archdir = 'arch'
1512      if self.toolset != 'target':
1513        archdir = os.path.join('arch', '%s' % self.toolset)
1514      return os.path.join(archdir, AddArch(filename, arch))
1515    elif type in type_in_output_root or self.is_standalone_static_library:
1516      return filename
1517    elif type == 'shared_library':
1518      libdir = 'lib'
1519      if self.toolset != 'target':
1520        libdir = os.path.join('lib', '%s' % self.toolset)
1521      return os.path.join(libdir, filename)
1522    else:
1523      return self.GypPathToUniqueOutput(filename, qualified=False)
1524
1525  def WriteVariableList(self, ninja_file, var, values):
1526    assert not isinstance(values, str)
1527    if values is None:
1528      values = []
1529    ninja_file.variable(var, ' '.join(values))
1530
1531  def WriteNewNinjaRule(self, name, args, description, is_cygwin, env, pool,
1532                        depfile=None):
1533    """Write out a new ninja "rule" statement for a given command.
1534
1535    Returns the name of the new rule, and a copy of |args| with variables
1536    expanded."""
1537
1538    if self.flavor == 'win':
1539      args = [self.msvs_settings.ConvertVSMacros(
1540                  arg, self.base_to_build, config=self.config_name)
1541              for arg in args]
1542      description = self.msvs_settings.ConvertVSMacros(
1543          description, config=self.config_name)
1544    elif self.flavor == 'mac':
1545      # |env| is an empty list on non-mac.
1546      args = [gyp.xcode_emulation.ExpandEnvVars(arg, env) for arg in args]
1547      description = gyp.xcode_emulation.ExpandEnvVars(description, env)
1548
1549    # TODO: we shouldn't need to qualify names; we do it because
1550    # currently the ninja rule namespace is global, but it really
1551    # should be scoped to the subninja.
1552    rule_name = self.name
1553    if self.toolset == 'target':
1554      rule_name += '.' + self.toolset
1555    rule_name += '.' + name
1556    rule_name = re.sub('[^a-zA-Z0-9_]', '_', rule_name)
1557
1558    # Remove variable references, but not if they refer to the magic rule
1559    # variables.  This is not quite right, as it also protects these for
1560    # actions, not just for rules where they are valid. Good enough.
1561    protect = [ '${root}', '${dirname}', '${source}', '${ext}', '${name}' ]
1562    protect = '(?!' + '|'.join(map(re.escape, protect)) + ')'
1563    description = re.sub(protect + r'\$', '_', description)
1564
1565    # gyp dictates that commands are run from the base directory.
1566    # cd into the directory before running, and adjust paths in
1567    # the arguments to point to the proper locations.
1568    rspfile = None
1569    rspfile_content = None
1570    args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args]
1571    if self.flavor == 'win':
1572      rspfile = rule_name + '.$unique_name.rsp'
1573      # The cygwin case handles this inside the bash sub-shell.
1574      run_in = '' if is_cygwin else ' ' + self.build_to_base
1575      if is_cygwin:
1576        rspfile_content = self.msvs_settings.BuildCygwinBashCommandLine(
1577            args, self.build_to_base)
1578      else:
1579        rspfile_content = gyp.msvs_emulation.EncodeRspFileList(args)
1580      command = ('%s gyp-win-tool action-wrapper $arch ' % sys.executable +
1581                 rspfile + run_in)
1582    else:
1583      env = self.ComputeExportEnvString(env)
1584      command = gyp.common.EncodePOSIXShellList(args)
1585      command = 'cd %s; ' % self.build_to_base + env + command
1586
1587    # GYP rules/actions express being no-ops by not touching their outputs.
1588    # Avoid executing downstream dependencies in this case by specifying
1589    # restat=1 to ninja.
1590    self.ninja.rule(rule_name, command, description, depfile=depfile,
1591                    restat=True, pool=pool,
1592                    rspfile=rspfile, rspfile_content=rspfile_content)
1593    self.ninja.newline()
1594
1595    return rule_name, args
1596
1597
1598def CalculateVariables(default_variables, params):
1599  """Calculate additional variables for use in the build (called by gyp)."""
1600  global generator_additional_non_configuration_keys
1601  global generator_additional_path_sections
1602  flavor = gyp.common.GetFlavor(params)
1603  if flavor == 'mac':
1604    default_variables.setdefault('OS', 'mac')
1605    default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib')
1606    default_variables.setdefault('SHARED_LIB_DIR',
1607                                 generator_default_variables['PRODUCT_DIR'])
1608    default_variables.setdefault('LIB_DIR',
1609                                 generator_default_variables['PRODUCT_DIR'])
1610
1611    # Copy additional generator configuration data from Xcode, which is shared
1612    # by the Mac Ninja generator.
1613    import gyp.generator.xcode as xcode_generator
1614    generator_additional_non_configuration_keys = getattr(xcode_generator,
1615        'generator_additional_non_configuration_keys', [])
1616    generator_additional_path_sections = getattr(xcode_generator,
1617        'generator_additional_path_sections', [])
1618    global generator_extra_sources_for_rules
1619    generator_extra_sources_for_rules = getattr(xcode_generator,
1620        'generator_extra_sources_for_rules', [])
1621  elif flavor == 'win':
1622    exts = gyp.MSVSUtil.TARGET_TYPE_EXT
1623    default_variables.setdefault('OS', 'win')
1624    default_variables['EXECUTABLE_SUFFIX'] = '.' + exts['executable']
1625    default_variables['STATIC_LIB_PREFIX'] = ''
1626    default_variables['STATIC_LIB_SUFFIX'] = '.' + exts['static_library']
1627    default_variables['SHARED_LIB_PREFIX'] = ''
1628    default_variables['SHARED_LIB_SUFFIX'] = '.' + exts['shared_library']
1629
1630    # Copy additional generator configuration data from VS, which is shared
1631    # by the Windows Ninja generator.
1632    import gyp.generator.msvs as msvs_generator
1633    generator_additional_non_configuration_keys = getattr(msvs_generator,
1634        'generator_additional_non_configuration_keys', [])
1635    generator_additional_path_sections = getattr(msvs_generator,
1636        'generator_additional_path_sections', [])
1637
1638    gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
1639  else:
1640    operating_system = flavor
1641    if flavor == 'android':
1642      operating_system = 'linux'  # Keep this legacy behavior for now.
1643    default_variables.setdefault('OS', operating_system)
1644    default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
1645    default_variables.setdefault('SHARED_LIB_DIR',
1646                                 os.path.join('$!PRODUCT_DIR', 'lib'))
1647    default_variables.setdefault('LIB_DIR',
1648                                 os.path.join('$!PRODUCT_DIR', 'obj'))
1649
1650def ComputeOutputDir(params):
1651  """Returns the path from the toplevel_dir to the build output directory."""
1652  # generator_dir: relative path from pwd to where make puts build files.
1653  # Makes migrating from make to ninja easier, ninja doesn't put anything here.
1654  generator_dir = os.path.relpath(params['options'].generator_output or '.')
1655
1656  # output_dir: relative path from generator_dir to the build directory.
1657  output_dir = params.get('generator_flags', {}).get('output_dir', 'out')
1658
1659  # Relative path from source root to our output files.  e.g. "out"
1660  return os.path.normpath(os.path.join(generator_dir, output_dir))
1661
1662
1663def CalculateGeneratorInputInfo(params):
1664  """Called by __init__ to initialize generator values based on params."""
1665  # E.g. "out/gypfiles"
1666  toplevel = params['options'].toplevel_dir
1667  qualified_out_dir = os.path.normpath(os.path.join(
1668      toplevel, ComputeOutputDir(params), 'gypfiles'))
1669
1670  global generator_filelist_paths
1671  generator_filelist_paths = {
1672      'toplevel': toplevel,
1673      'qualified_out_dir': qualified_out_dir,
1674  }
1675
1676
1677def OpenOutput(path, mode='w'):
1678  """Open |path| for writing, creating directories if necessary."""
1679  gyp.common.EnsureDirExists(path)
1680  return open(path, mode)
1681
1682
1683def CommandWithWrapper(cmd, wrappers, prog):
1684  wrapper = wrappers.get(cmd, '')
1685  if wrapper:
1686    return wrapper + ' ' + prog
1687  return prog
1688
1689
1690def GetDefaultConcurrentLinks():
1691  """Returns a best-guess for a number of concurrent links."""
1692  pool_size = int(os.environ.get('GYP_LINK_CONCURRENCY', 0))
1693  if pool_size:
1694    return pool_size
1695
1696  if sys.platform in ('win32', 'cygwin'):
1697    import ctypes
1698
1699    class MEMORYSTATUSEX(ctypes.Structure):
1700      _fields_ = [
1701        ("dwLength", ctypes.c_ulong),
1702        ("dwMemoryLoad", ctypes.c_ulong),
1703        ("ullTotalPhys", ctypes.c_ulonglong),
1704        ("ullAvailPhys", ctypes.c_ulonglong),
1705        ("ullTotalPageFile", ctypes.c_ulonglong),
1706        ("ullAvailPageFile", ctypes.c_ulonglong),
1707        ("ullTotalVirtual", ctypes.c_ulonglong),
1708        ("ullAvailVirtual", ctypes.c_ulonglong),
1709        ("sullAvailExtendedVirtual", ctypes.c_ulonglong),
1710      ]
1711
1712    stat = MEMORYSTATUSEX()
1713    stat.dwLength = ctypes.sizeof(stat)
1714    ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
1715
1716    # VS 2015 uses 20% more working set than VS 2013 and can consume all RAM
1717    # on a 64 GB machine.
1718    mem_limit = max(1, stat.ullTotalPhys / (5 * (2 ** 30)))  # total / 5GB
1719    hard_cap = max(1, int(os.environ.get('GYP_LINK_CONCURRENCY_MAX', 2**32)))
1720    return min(mem_limit, hard_cap)
1721  elif sys.platform.startswith('linux'):
1722    if os.path.exists("/proc/meminfo"):
1723      with open("/proc/meminfo") as meminfo:
1724        memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
1725        for line in meminfo:
1726          match = memtotal_re.match(line)
1727          if not match:
1728            continue
1729          # Allow 8Gb per link on Linux because Gold is quite memory hungry
1730          return max(1, int(match.group(1)) / (8 * (2 ** 20)))
1731    return 1
1732  elif sys.platform == 'darwin':
1733    try:
1734      avail_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
1735      # A static library debug build of Chromium's unit_tests takes ~2.7GB, so
1736      # 4GB per ld process allows for some more bloat.
1737      return max(1, avail_bytes / (4 * (2 ** 30)))  # total / 4GB
1738    except:
1739      return 1
1740  else:
1741    # TODO(scottmg): Implement this for other platforms.
1742    return 1
1743
1744
1745def _GetWinLinkRuleNameSuffix(embed_manifest):
1746  """Returns the suffix used to select an appropriate linking rule depending on
1747  whether the manifest embedding is enabled."""
1748  return '_embed' if embed_manifest else ''
1749
1750
1751def _AddWinLinkRules(master_ninja, embed_manifest):
1752  """Adds link rules for Windows platform to |master_ninja|."""
1753  def FullLinkCommand(ldcmd, out, binary_type):
1754    resource_name = {
1755      'exe': '1',
1756      'dll': '2',
1757    }[binary_type]
1758    return '%(python)s gyp-win-tool link-with-manifests $arch %(embed)s ' \
1759           '%(out)s "%(ldcmd)s" %(resname)s $mt $rc "$intermediatemanifest" ' \
1760           '$manifests' % {
1761               'python': sys.executable,
1762               'out': out,
1763               'ldcmd': ldcmd,
1764               'resname': resource_name,
1765               'embed': embed_manifest }
1766  rule_name_suffix = _GetWinLinkRuleNameSuffix(embed_manifest)
1767  use_separate_mspdbsrv = (
1768      int(os.environ.get('GYP_USE_SEPARATE_MSPDBSRV', '0')) != 0)
1769  dlldesc = 'LINK%s(DLL) $binary' % rule_name_suffix.upper()
1770  dllcmd = ('%s gyp-win-tool link-wrapper $arch %s '
1771            '$ld /nologo $implibflag /DLL /OUT:$binary '
1772            '@$binary.rsp' % (sys.executable, use_separate_mspdbsrv))
1773  dllcmd = FullLinkCommand(dllcmd, '$binary', 'dll')
1774  master_ninja.rule('solink' + rule_name_suffix,
1775                    description=dlldesc, command=dllcmd,
1776                    rspfile='$binary.rsp',
1777                    rspfile_content='$libs $in_newline $ldflags',
1778                    restat=True,
1779                    pool='link_pool')
1780  master_ninja.rule('solink_module' + rule_name_suffix,
1781                    description=dlldesc, command=dllcmd,
1782                    rspfile='$binary.rsp',
1783                    rspfile_content='$libs $in_newline $ldflags',
1784                    restat=True,
1785                    pool='link_pool')
1786  # Note that ldflags goes at the end so that it has the option of
1787  # overriding default settings earlier in the command line.
1788  exe_cmd = ('%s gyp-win-tool link-wrapper $arch %s '
1789             '$ld /nologo /OUT:$binary @$binary.rsp' %
1790              (sys.executable, use_separate_mspdbsrv))
1791  exe_cmd = FullLinkCommand(exe_cmd, '$binary', 'exe')
1792  master_ninja.rule('link' + rule_name_suffix,
1793                    description='LINK%s $binary' % rule_name_suffix.upper(),
1794                    command=exe_cmd,
1795                    rspfile='$binary.rsp',
1796                    rspfile_content='$in_newline $libs $ldflags',
1797                    pool='link_pool')
1798
1799
1800def GenerateOutputForConfig(target_list, target_dicts, data, params,
1801                            config_name):
1802  options = params['options']
1803  flavor = gyp.common.GetFlavor(params)
1804  generator_flags = params.get('generator_flags', {})
1805
1806  # build_dir: relative path from source root to our output files.
1807  # e.g. "out/Debug"
1808  build_dir = os.path.normpath(
1809      os.path.join(ComputeOutputDir(params), config_name))
1810
1811  toplevel_build = os.path.join(options.toplevel_dir, build_dir)
1812
1813  master_ninja_file = OpenOutput(os.path.join(toplevel_build, 'build.ninja'))
1814  master_ninja = ninja_syntax.Writer(master_ninja_file, width=120)
1815
1816  # Put build-time support tools in out/{config_name}.
1817  gyp.common.CopyTool(flavor, toplevel_build)
1818
1819  # Grab make settings for CC/CXX.
1820  # The rules are
1821  # - The priority from low to high is gcc/g++, the 'make_global_settings' in
1822  #   gyp, the environment variable.
1823  # - If there is no 'make_global_settings' for CC.host/CXX.host or
1824  #   'CC_host'/'CXX_host' environment variable, cc_host/cxx_host should be set
1825  #   to cc/cxx.
1826  if flavor == 'win':
1827    ar = 'lib.exe'
1828    # cc and cxx must be set to the correct architecture by overriding with one
1829    # of cl_x86 or cl_x64 below.
1830    cc = 'UNSET'
1831    cxx = 'UNSET'
1832    ld = 'link.exe'
1833    ld_host = '$ld'
1834  else:
1835    ar = 'ar'
1836    cc = 'cc'
1837    cxx = 'c++'
1838    ld = '$cc'
1839    ldxx = '$cxx'
1840    ld_host = '$cc_host'
1841    ldxx_host = '$cxx_host'
1842
1843  ar_host = 'ar'
1844  cc_host = None
1845  cxx_host = None
1846  cc_host_global_setting = None
1847  cxx_host_global_setting = None
1848  clang_cl = None
1849  nm = 'nm'
1850  nm_host = 'nm'
1851  readelf = 'readelf'
1852  readelf_host = 'readelf'
1853
1854  build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
1855  make_global_settings = data[build_file].get('make_global_settings', [])
1856  build_to_root = gyp.common.InvertRelativePath(build_dir,
1857                                                options.toplevel_dir)
1858  wrappers = {}
1859  for key, value in make_global_settings:
1860    if key == 'AR':
1861      ar = os.path.join(build_to_root, value)
1862    if key == 'AR.host':
1863      ar_host = os.path.join(build_to_root, value)
1864    if key == 'CC':
1865      cc = os.path.join(build_to_root, value)
1866      if cc.endswith('clang-cl'):
1867        clang_cl = cc
1868    if key == 'CXX':
1869      cxx = os.path.join(build_to_root, value)
1870    if key == 'CC.host':
1871      cc_host = os.path.join(build_to_root, value)
1872      cc_host_global_setting = value
1873    if key == 'CXX.host':
1874      cxx_host = os.path.join(build_to_root, value)
1875      cxx_host_global_setting = value
1876    if key == 'LD':
1877      ld = os.path.join(build_to_root, value)
1878    if key == 'LD.host':
1879      ld_host = os.path.join(build_to_root, value)
1880    if key == 'LDXX':
1881      ldxx = os.path.join(build_to_root, value)
1882    if key == 'LDXX.host':
1883      ldxx_host = os.path.join(build_to_root, value)
1884    if key == 'NM':
1885      nm = os.path.join(build_to_root, value)
1886    if key == 'NM.host':
1887      nm_host = os.path.join(build_to_root, value)
1888    if key == 'READELF':
1889      readelf = os.path.join(build_to_root, value)
1890    if key == 'READELF.host':
1891      readelf_host = os.path.join(build_to_root, value)
1892    if key.endswith('_wrapper'):
1893      wrappers[key[:-len('_wrapper')]] = os.path.join(build_to_root, value)
1894
1895  # Support wrappers from environment variables too.
1896  for key, value in os.environ.items():
1897    if key.lower().endswith('_wrapper'):
1898      key_prefix = key[:-len('_wrapper')]
1899      key_prefix = re.sub(r'\.HOST$', '.host', key_prefix)
1900      wrappers[key_prefix] = os.path.join(build_to_root, value)
1901
1902  if flavor == 'win':
1903    configs = [target_dicts[qualified_target]['configurations'][config_name]
1904               for qualified_target in target_list]
1905    shared_system_includes = None
1906    if not generator_flags.get('ninja_use_custom_environment_files', 0):
1907      shared_system_includes = \
1908          gyp.msvs_emulation.ExtractSharedMSVSSystemIncludes(
1909              configs, generator_flags)
1910    cl_paths = gyp.msvs_emulation.GenerateEnvironmentFiles(
1911        toplevel_build, generator_flags, shared_system_includes, OpenOutput)
1912    for arch, path in cl_paths.items():
1913      if clang_cl:
1914        # If we have selected clang-cl, use that instead.
1915        path = clang_cl
1916      command = CommandWithWrapper('CC', wrappers,
1917          QuoteShellArgument(path, 'win'))
1918      if clang_cl:
1919        # Use clang-cl to cross-compile for x86 or x86_64.
1920        command += (' -m32' if arch == 'x86' else ' -m64')
1921      master_ninja.variable('cl_' + arch, command)
1922
1923  cc = GetEnvironFallback(['CC_target', 'CC'], cc)
1924  master_ninja.variable('cc', CommandWithWrapper('CC', wrappers, cc))
1925  cxx = GetEnvironFallback(['CXX_target', 'CXX'], cxx)
1926  master_ninja.variable('cxx', CommandWithWrapper('CXX', wrappers, cxx))
1927
1928  if flavor == 'win':
1929    master_ninja.variable('ld', ld)
1930    master_ninja.variable('idl', 'midl.exe')
1931    master_ninja.variable('ar', ar)
1932    master_ninja.variable('rc', 'rc.exe')
1933    master_ninja.variable('ml_x86', 'ml.exe')
1934    master_ninja.variable('ml_x64', 'ml64.exe')
1935    master_ninja.variable('mt', 'mt.exe')
1936  else:
1937    master_ninja.variable('ld', CommandWithWrapper('LINK', wrappers, ld))
1938    master_ninja.variable('ldxx', CommandWithWrapper('LINK', wrappers, ldxx))
1939    master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], ar))
1940    if flavor != 'mac':
1941      # Mac does not use readelf/nm for .TOC generation, so avoiding polluting
1942      # the master ninja with extra unused variables.
1943      master_ninja.variable(
1944          'nm', GetEnvironFallback(['NM_target', 'NM'], nm))
1945      master_ninja.variable(
1946          'readelf', GetEnvironFallback(['READELF_target', 'READELF'], readelf))
1947
1948  if generator_supports_multiple_toolsets:
1949    if not cc_host:
1950      cc_host = cc
1951    if not cxx_host:
1952      cxx_host = cxx
1953
1954    master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], ar_host))
1955    master_ninja.variable('nm_host', GetEnvironFallback(['NM_host'], nm_host))
1956    master_ninja.variable('readelf_host',
1957                          GetEnvironFallback(['READELF_host'], readelf_host))
1958    cc_host = GetEnvironFallback(['CC_host'], cc_host)
1959    cxx_host = GetEnvironFallback(['CXX_host'], cxx_host)
1960
1961    # The environment variable could be used in 'make_global_settings', like
1962    # ['CC.host', '$(CC)'] or ['CXX.host', '$(CXX)'], transform them here.
1963    if '$(CC)' in cc_host and cc_host_global_setting:
1964      cc_host = cc_host_global_setting.replace('$(CC)', cc)
1965    if '$(CXX)' in cxx_host and cxx_host_global_setting:
1966      cxx_host = cxx_host_global_setting.replace('$(CXX)', cxx)
1967    master_ninja.variable('cc_host',
1968                          CommandWithWrapper('CC.host', wrappers, cc_host))
1969    master_ninja.variable('cxx_host',
1970                          CommandWithWrapper('CXX.host', wrappers, cxx_host))
1971    if flavor == 'win':
1972      master_ninja.variable('ld_host', ld_host)
1973      master_ninja.variable('ldxx_host', ldxx_host)
1974    else:
1975      master_ninja.variable('ld_host', CommandWithWrapper(
1976          'LINK', wrappers, ld_host))
1977      master_ninja.variable('ldxx_host', CommandWithWrapper(
1978          'LINK', wrappers, ldxx_host))
1979
1980  master_ninja.newline()
1981
1982  master_ninja.pool('link_pool', depth=GetDefaultConcurrentLinks())
1983  master_ninja.newline()
1984
1985  deps = 'msvc' if flavor == 'win' else 'gcc'
1986
1987  if flavor != 'win':
1988    master_ninja.rule(
1989      'cc',
1990      description='CC $out',
1991      command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c '
1992              '$cflags_pch_c -c $in -o $out'),
1993      depfile='$out.d',
1994      deps=deps)
1995    master_ninja.rule(
1996      'cc_s',
1997      description='CC $out',
1998      command=('$cc $defines $includes $cflags $cflags_c '
1999              '$cflags_pch_c -c $in -o $out'))
2000    master_ninja.rule(
2001      'cxx',
2002      description='CXX $out',
2003      command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc '
2004              '$cflags_pch_cc -c $in -o $out'),
2005      depfile='$out.d',
2006      deps=deps)
2007  else:
2008    # TODO(scottmg) Separate pdb names is a test to see if it works around
2009    # http://crbug.com/142362. It seems there's a race between the creation of
2010    # the .pdb by the precompiled header step for .cc and the compilation of
2011    # .c files. This should be handled by mspdbsrv, but rarely errors out with
2012    #   c1xx : fatal error C1033: cannot open program database
2013    # By making the rules target separate pdb files this might be avoided.
2014    cc_command = ('ninja -t msvc -e $arch ' +
2015                  '-- '
2016                  '$cc /nologo /showIncludes /FC '
2017                  '@$out.rsp /c $in /Fo$out /Fd$pdbname_c ')
2018    cxx_command = ('ninja -t msvc -e $arch ' +
2019                   '-- '
2020                   '$cxx /nologo /showIncludes /FC '
2021                   '@$out.rsp /c $in /Fo$out /Fd$pdbname_cc ')
2022    master_ninja.rule(
2023      'cc',
2024      description='CC $out',
2025      command=cc_command,
2026      rspfile='$out.rsp',
2027      rspfile_content='$defines $includes $cflags $cflags_c',
2028      deps=deps)
2029    master_ninja.rule(
2030      'cxx',
2031      description='CXX $out',
2032      command=cxx_command,
2033      rspfile='$out.rsp',
2034      rspfile_content='$defines $includes $cflags $cflags_cc',
2035      deps=deps)
2036    master_ninja.rule(
2037      'idl',
2038      description='IDL $in',
2039      command=('%s gyp-win-tool midl-wrapper $arch $outdir '
2040               '$tlb $h $dlldata $iid $proxy $in '
2041               '$midl_includes $idlflags' % sys.executable))
2042    master_ninja.rule(
2043      'rc',
2044      description='RC $in',
2045      # Note: $in must be last otherwise rc.exe complains.
2046      command=('%s gyp-win-tool rc-wrapper '
2047               '$arch $rc $defines $resource_includes $rcflags /fo$out $in' %
2048               sys.executable))
2049    master_ninja.rule(
2050      'asm',
2051      description='ASM $out',
2052      command=('%s gyp-win-tool asm-wrapper '
2053               '$arch $asm $defines $includes $asmflags /c /Fo $out $in' %
2054               sys.executable))
2055
2056  if flavor != 'mac' and flavor != 'win':
2057    master_ninja.rule(
2058      'alink',
2059      description='AR $out',
2060      command='rm -f $out && $ar rcs $arflags $out $in')
2061    master_ninja.rule(
2062      'alink_thin',
2063      description='AR $out',
2064      command='rm -f $out && $ar rcsT $arflags $out $in')
2065
2066    # This allows targets that only need to depend on $lib's API to declare an
2067    # order-only dependency on $lib.TOC and avoid relinking such downstream
2068    # dependencies when $lib changes only in non-public ways.
2069    # The resulting string leaves an uninterpolated %{suffix} which
2070    # is used in the final substitution below.
2071    mtime_preserving_solink_base = (
2072        'if [ ! -e $lib -o ! -e $lib.TOC ]; then '
2073        '%(solink)s && %(extract_toc)s > $lib.TOC; else '
2074        '%(solink)s && %(extract_toc)s > $lib.tmp && '
2075        'if ! cmp -s $lib.tmp $lib.TOC; then mv $lib.tmp $lib.TOC ; '
2076        'fi; fi'
2077        % { 'solink':
2078              '$ld -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s',
2079            'extract_toc':
2080              ('{ $readelf -d $lib | grep SONAME ; '
2081               '$nm -gD -f p $lib | cut -f1-2 -d\' \'; }')})
2082
2083    master_ninja.rule(
2084      'solink',
2085      description='SOLINK $lib',
2086      restat=True,
2087      command=mtime_preserving_solink_base % {'suffix': '@$link_file_list'},
2088      rspfile='$link_file_list',
2089      rspfile_content=
2090          '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive $libs',
2091      pool='link_pool')
2092    master_ninja.rule(
2093      'solink_module',
2094      description='SOLINK(module) $lib',
2095      restat=True,
2096      command=mtime_preserving_solink_base % {'suffix': '@$link_file_list'},
2097      rspfile='$link_file_list',
2098      rspfile_content='-Wl,--start-group $in $solibs $libs -Wl,--end-group',
2099      pool='link_pool')
2100    master_ninja.rule(
2101      'link',
2102      description='LINK $out',
2103      command=('$ld $ldflags -o $out '
2104               '-Wl,--start-group $in $solibs $libs -Wl,--end-group'),
2105      pool='link_pool')
2106  elif flavor == 'win':
2107    master_ninja.rule(
2108        'alink',
2109        description='LIB $out',
2110        command=('%s gyp-win-tool link-wrapper $arch False '
2111                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' %
2112                 sys.executable),
2113        rspfile='$out.rsp',
2114        rspfile_content='$in_newline $libflags')
2115    _AddWinLinkRules(master_ninja, embed_manifest=True)
2116    _AddWinLinkRules(master_ninja, embed_manifest=False)
2117  else:
2118    master_ninja.rule(
2119      'objc',
2120      description='OBJC $out',
2121      command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_objc '
2122               '$cflags_pch_objc -c $in -o $out'),
2123      depfile='$out.d',
2124      deps=deps)
2125    master_ninja.rule(
2126      'objcxx',
2127      description='OBJCXX $out',
2128      command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_objcc '
2129               '$cflags_pch_objcc -c $in -o $out'),
2130      depfile='$out.d',
2131      deps=deps)
2132    master_ninja.rule(
2133      'alink',
2134      description='LIBTOOL-STATIC $out, POSTBUILDS',
2135      command='rm -f $out && '
2136              './gyp-mac-tool filter-libtool libtool $libtool_flags '
2137              '-static -o $out $in'
2138              '$postbuilds')
2139    master_ninja.rule(
2140      'lipo',
2141      description='LIPO $out, POSTBUILDS',
2142      command='rm -f $out && lipo -create $in -output $out$postbuilds')
2143    master_ninja.rule(
2144      'solipo',
2145      description='SOLIPO $out, POSTBUILDS',
2146      command=(
2147          'rm -f $lib $lib.TOC && lipo -create $in -output $lib$postbuilds &&'
2148          '%(extract_toc)s > $lib.TOC'
2149          % { 'extract_toc':
2150                '{ otool -l $lib | grep LC_ID_DYLIB -A 5; '
2151                'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'}))
2152
2153
2154    # Record the public interface of $lib in $lib.TOC. See the corresponding
2155    # comment in the posix section above for details.
2156    solink_base = '$ld %(type)s $ldflags -o $lib %(suffix)s'
2157    mtime_preserving_solink_base = (
2158        'if [ ! -e $lib -o ! -e $lib.TOC ] || '
2159             # Always force dependent targets to relink if this library
2160             # reexports something. Handling this correctly would require
2161             # recursive TOC dumping but this is rare in practice, so punt.
2162             'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then '
2163          '%(solink)s && %(extract_toc)s > $lib.TOC; '
2164        'else '
2165          '%(solink)s && %(extract_toc)s > $lib.tmp && '
2166          'if ! cmp -s $lib.tmp $lib.TOC; then '
2167            'mv $lib.tmp $lib.TOC ; '
2168          'fi; '
2169        'fi'
2170        % { 'solink': solink_base,
2171            'extract_toc':
2172              '{ otool -l $lib | grep LC_ID_DYLIB -A 5; '
2173              'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'})
2174
2175
2176    solink_suffix = '@$link_file_list$postbuilds'
2177    master_ninja.rule(
2178      'solink',
2179      description='SOLINK $lib, POSTBUILDS',
2180      restat=True,
2181      command=mtime_preserving_solink_base % {'suffix': solink_suffix,
2182                                              'type': '-shared'},
2183      rspfile='$link_file_list',
2184      rspfile_content='$in $solibs $libs',
2185      pool='link_pool')
2186    master_ninja.rule(
2187      'solink_notoc',
2188      description='SOLINK $lib, POSTBUILDS',
2189      restat=True,
2190      command=solink_base % {'suffix':solink_suffix, 'type': '-shared'},
2191      rspfile='$link_file_list',
2192      rspfile_content='$in $solibs $libs',
2193      pool='link_pool')
2194
2195    master_ninja.rule(
2196      'solink_module',
2197      description='SOLINK(module) $lib, POSTBUILDS',
2198      restat=True,
2199      command=mtime_preserving_solink_base % {'suffix': solink_suffix,
2200                                              'type': '-bundle'},
2201      rspfile='$link_file_list',
2202      rspfile_content='$in $solibs $libs',
2203      pool='link_pool')
2204    master_ninja.rule(
2205      'solink_module_notoc',
2206      description='SOLINK(module) $lib, POSTBUILDS',
2207      restat=True,
2208      command=solink_base % {'suffix': solink_suffix, 'type': '-bundle'},
2209      rspfile='$link_file_list',
2210      rspfile_content='$in $solibs $libs',
2211      pool='link_pool')
2212
2213    master_ninja.rule(
2214      'link',
2215      description='LINK $out, POSTBUILDS',
2216      command=('$ld $ldflags -o $out '
2217               '$in $solibs $libs$postbuilds'),
2218      pool='link_pool')
2219    master_ninja.rule(
2220      'preprocess_infoplist',
2221      description='PREPROCESS INFOPLIST $out',
2222      command=('$cc -E -P -Wno-trigraphs -x c $defines $in -o $out && '
2223               'plutil -convert xml1 $out $out'))
2224    master_ninja.rule(
2225      'copy_infoplist',
2226      description='COPY INFOPLIST $in',
2227      command='$env ./gyp-mac-tool copy-info-plist $in $out $binary $keys')
2228    master_ninja.rule(
2229      'merge_infoplist',
2230      description='MERGE INFOPLISTS $in',
2231      command='$env ./gyp-mac-tool merge-info-plist $out $in')
2232    master_ninja.rule(
2233      'compile_xcassets',
2234      description='COMPILE XCASSETS $in',
2235      command='$env ./gyp-mac-tool compile-xcassets $keys $in')
2236    master_ninja.rule(
2237      'mac_tool',
2238      description='MACTOOL $mactool_cmd $in',
2239      command='$env ./gyp-mac-tool $mactool_cmd $in $out $binary')
2240    master_ninja.rule(
2241      'package_framework',
2242      description='PACKAGE FRAMEWORK $out, POSTBUILDS',
2243      command='./gyp-mac-tool package-framework $out $version$postbuilds '
2244              '&& touch $out')
2245  if flavor == 'win':
2246    master_ninja.rule(
2247      'stamp',
2248      description='STAMP $out',
2249      command='%s gyp-win-tool stamp $out' % sys.executable)
2250  else:
2251    master_ninja.rule(
2252      'stamp',
2253      description='STAMP $out',
2254      command='${postbuilds}touch $out')
2255  if flavor == 'win':
2256    master_ninja.rule(
2257      'copy',
2258      description='COPY $in $out',
2259      command='%s gyp-win-tool recursive-mirror $in $out' % sys.executable)
2260  elif flavor == 'zos':
2261    master_ninja.rule(
2262      'copy',
2263      description='COPY $in $out',
2264      command='rm -rf $out && cp -fRP $in $out')
2265  else:
2266    master_ninja.rule(
2267      'copy',
2268      description='COPY $in $out',
2269      command='rm -rf $out && cp -af $in $out')
2270  master_ninja.newline()
2271
2272  all_targets = set()
2273  for build_file in params['build_files']:
2274    for target in gyp.common.AllTargets(target_list,
2275                                        target_dicts,
2276                                        os.path.normpath(build_file)):
2277      all_targets.add(target)
2278  all_outputs = set()
2279
2280  # target_outputs is a map from qualified target name to a Target object.
2281  target_outputs = {}
2282  # target_short_names is a map from target short name to a list of Target
2283  # objects.
2284  target_short_names = {}
2285
2286  # short name of targets that were skipped because they didn't contain anything
2287  # interesting.
2288  # NOTE: there may be overlap between this an non_empty_target_names.
2289  empty_target_names = set()
2290
2291  # Set of non-empty short target names.
2292  # NOTE: there may be overlap between this an empty_target_names.
2293  non_empty_target_names = set()
2294
2295  for qualified_target in target_list:
2296    # qualified_target is like: third_party/icu/icu.gyp:icui18n#target
2297    build_file, name, toolset = \
2298        gyp.common.ParseQualifiedTarget(qualified_target)
2299
2300    this_make_global_settings = data[build_file].get('make_global_settings', [])
2301    assert make_global_settings == this_make_global_settings, (
2302        "make_global_settings needs to be the same for all targets. %s vs. %s" %
2303        (this_make_global_settings, make_global_settings))
2304
2305    spec = target_dicts[qualified_target]
2306    if flavor == 'mac':
2307      gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec)
2308
2309    # If build_file is a symlink, we must not follow it because there's a chance
2310    # it could point to a path above toplevel_dir, and we cannot correctly deal
2311    # with that case at the moment.
2312    build_file = gyp.common.RelativePath(build_file, options.toplevel_dir,
2313                                         False)
2314
2315    qualified_target_for_hash = gyp.common.QualifiedTarget(build_file, name,
2316                                                           toolset)
2317    hash_for_rules = hashlib.md5(qualified_target_for_hash).hexdigest()
2318
2319    base_path = os.path.dirname(build_file)
2320    obj = 'obj'
2321    if toolset != 'target':
2322      obj += '.' + toolset
2323    output_file = os.path.join(obj, base_path, name + '.ninja')
2324
2325    ninja_output = StringIO()
2326    writer = NinjaWriter(hash_for_rules, target_outputs, base_path, build_dir,
2327                         ninja_output,
2328                         toplevel_build, output_file,
2329                         flavor, toplevel_dir=options.toplevel_dir)
2330
2331    target = writer.WriteSpec(spec, config_name, generator_flags)
2332
2333    if ninja_output.tell() > 0:
2334      # Only create files for ninja files that actually have contents.
2335      with OpenOutput(os.path.join(toplevel_build, output_file)) as ninja_file:
2336        ninja_file.write(ninja_output.getvalue())
2337      ninja_output.close()
2338      master_ninja.subninja(output_file)
2339
2340    if target:
2341      if name != target.FinalOutput() and spec['toolset'] == 'target':
2342        target_short_names.setdefault(name, []).append(target)
2343      target_outputs[qualified_target] = target
2344      if qualified_target in all_targets:
2345        all_outputs.add(target.FinalOutput())
2346      non_empty_target_names.add(name)
2347    else:
2348      empty_target_names.add(name)
2349
2350  if target_short_names:
2351    # Write a short name to build this target.  This benefits both the
2352    # "build chrome" case as well as the gyp tests, which expect to be
2353    # able to run actions and build libraries by their short name.
2354    master_ninja.newline()
2355    master_ninja.comment('Short names for targets.')
2356    for short_name in target_short_names:
2357      master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in
2358                                               target_short_names[short_name]])
2359
2360  # Write phony targets for any empty targets that weren't written yet. As
2361  # short names are  not necessarily unique only do this for short names that
2362  # haven't already been output for another target.
2363  empty_target_names = empty_target_names - non_empty_target_names
2364  if empty_target_names:
2365    master_ninja.newline()
2366    master_ninja.comment('Empty targets (output for completeness).')
2367    for name in sorted(empty_target_names):
2368      master_ninja.build(name, 'phony')
2369
2370  if all_outputs:
2371    master_ninja.newline()
2372    master_ninja.build('all', 'phony', list(all_outputs))
2373    master_ninja.default(generator_flags.get('default_target', 'all'))
2374
2375  master_ninja_file.close()
2376
2377
2378def PerformBuild(data, configurations, params):
2379  options = params['options']
2380  for config in configurations:
2381    builddir = os.path.join(options.toplevel_dir, 'out', config)
2382    arguments = ['ninja', '-C', builddir]
2383    print('Building [%s]: %s' % (config, arguments))
2384    subprocess.check_call(arguments)
2385
2386
2387def CallGenerateOutputForConfig(arglist):
2388  # Ignore the interrupt signal so that the parent process catches it and
2389  # kills all multiprocessing children.
2390  signal.signal(signal.SIGINT, signal.SIG_IGN)
2391
2392  (target_list, target_dicts, data, params, config_name) = arglist
2393  GenerateOutputForConfig(target_list, target_dicts, data, params, config_name)
2394
2395
2396def GenerateOutput(target_list, target_dicts, data, params):
2397  # Update target_dicts for iOS device builds.
2398  target_dicts = gyp.xcode_emulation.CloneConfigurationForDeviceAndEmulator(
2399      target_dicts)
2400
2401  user_config = params.get('generator_flags', {}).get('config', None)
2402  if gyp.common.GetFlavor(params) == 'win':
2403    target_list, target_dicts = MSVSUtil.ShardTargets(target_list, target_dicts)
2404    target_list, target_dicts = MSVSUtil.InsertLargePdbShims(
2405        target_list, target_dicts, generator_default_variables)
2406
2407  if user_config:
2408    GenerateOutputForConfig(target_list, target_dicts, data, params,
2409                            user_config)
2410  else:
2411    config_names = target_dicts[target_list[0]]['configurations'].keys()
2412    if params['parallel']:
2413      try:
2414        pool = multiprocessing.Pool(len(config_names))
2415        arglists = []
2416        for config_name in config_names:
2417          arglists.append(
2418              (target_list, target_dicts, data, params, config_name))
2419        pool.map(CallGenerateOutputForConfig, arglists)
2420      except KeyboardInterrupt as e:
2421        pool.terminate()
2422        raise e
2423    else:
2424      for config_name in config_names:
2425        GenerateOutputForConfig(target_list, target_dicts, data, params,
2426                                config_name)
2427