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