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