Completed
Branch rebuild (e7a2d2)
by Glenn
08:26
created

NinjaWriter.WriteRules()   F

Complexity

Conditions 30

Size

Total Lines 116

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 116
c 0
b 0
f 0
rs 2
cc 30

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like NinjaWriter.WriteRules() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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