The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / npm / node_modules / node-gyp / gyp / pylib / gyp / generator / ninja.py @ d46ebffb

History | View | Annotate | Download (71.5 KB)

1
# Copyright (c) 2012 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 copy
6
import hashlib
7
import multiprocessing
8
import os.path
9
import re
10
import signal
11
import subprocess
12
import sys
13
import gyp
14
import gyp.common
15
import gyp.msvs_emulation
16
import gyp.MSVSVersion
17
import gyp.xcode_emulation
18

    
19
from gyp.common import GetEnvironFallback
20
import gyp.ninja_syntax as ninja_syntax
21

    
22
generator_default_variables = {
23
  'EXECUTABLE_PREFIX': '',
24
  'EXECUTABLE_SUFFIX': '',
25
  'STATIC_LIB_PREFIX': 'lib',
26
  'STATIC_LIB_SUFFIX': '.a',
27
  'SHARED_LIB_PREFIX': 'lib',
28

    
29
  # Gyp expects the following variables to be expandable by the build
30
  # system to the appropriate locations.  Ninja prefers paths to be
31
  # known at gyp time.  To resolve this, introduce special
32
  # variables starting with $! and $| (which begin with a $ so gyp knows it
33
  # should be treated specially, but is otherwise an invalid
34
  # ninja/shell variable) that are passed to gyp here but expanded
35
  # before writing out into the target .ninja files; see
36
  # ExpandSpecial.
37
  # $! is used for variables that represent a path and that can only appear at
38
  # the start of a string, while $| is used for variables that can appear
39
  # anywhere in a string.
40
  'INTERMEDIATE_DIR': '$!INTERMEDIATE_DIR',
41
  'SHARED_INTERMEDIATE_DIR': '$!PRODUCT_DIR/gen',
42
  'PRODUCT_DIR': '$!PRODUCT_DIR',
43
  'CONFIGURATION_NAME': '$|CONFIGURATION_NAME',
44

    
45
  # Special variables that may be used by gyp 'rule' targets.
46
  # We generate definitions for these variables on the fly when processing a
47
  # rule.
48
  'RULE_INPUT_ROOT': '${root}',
49
  'RULE_INPUT_DIRNAME': '${dirname}',
50
  'RULE_INPUT_PATH': '${source}',
51
  'RULE_INPUT_EXT': '${ext}',
52
  'RULE_INPUT_NAME': '${name}',
53
}
54

    
55
# Placates pylint.
56
generator_additional_non_configuration_keys = []
57
generator_additional_path_sections = []
58
generator_extra_sources_for_rules = []
59

    
60
# TODO: figure out how to not build extra host objects in the non-cross-compile
61
# case when this is enabled, and enable unconditionally.
62
generator_supports_multiple_toolsets = (
63
  os.environ.get('GYP_CROSSCOMPILE') or
64
  os.environ.get('AR_host') or
65
  os.environ.get('CC_host') or
66
  os.environ.get('CXX_host') or
67
  os.environ.get('AR_target') or
68
  os.environ.get('CC_target') or
69
  os.environ.get('CXX_target'))
70

    
71

    
72
def StripPrefix(arg, prefix):
73
  if arg.startswith(prefix):
74
    return arg[len(prefix):]
75
  return arg
76

    
77

    
78
def QuoteShellArgument(arg, flavor):
79
  """Quote a string such that it will be interpreted as a single argument
80
  by the shell."""
81
  # Rather than attempting to enumerate the bad shell characters, just
82
  # whitelist common OK ones and quote anything else.
83
  if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
84
    return arg  # No quoting necessary.
85
  if flavor == 'win':
86
    return gyp.msvs_emulation.QuoteForRspFile(arg)
87
  return "'" + arg.replace("'", "'" + '"\'"' + "'")  + "'"
88

    
89

    
90
def Define(d, flavor):
91
  """Takes a preprocessor define and returns a -D parameter that's ninja- and
92
  shell-escaped."""
93
  if flavor == 'win':
94
    # cl.exe replaces literal # characters with = in preprocesor definitions for
95
    # some reason. Octal-encode to work around that.
96
    d = d.replace('#', '\\%03o' % ord('#'))
97
  return QuoteShellArgument(ninja_syntax.escape('-D' + d), flavor)
98

    
99

    
100
def InvertRelativePath(path):
101
  """Given a relative path like foo/bar, return the inverse relative path:
102
  the path from the relative path back to the origin dir.
103

104
  E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
105
  should always produce the empty string."""
106

    
107
  if not path:
108
    return path
109
  # Only need to handle relative paths into subdirectories for now.
110
  assert '..' not in path, path
111
  depth = len(path.split(os.path.sep))
112
  return os.path.sep.join(['..'] * depth)
113

    
114

    
115
class Target:
116
  """Target represents the paths used within a single gyp target.
117

118
  Conceptually, building a single target A is a series of steps:
119

120
  1) actions/rules/copies  generates source/resources/etc.
121
  2) compiles              generates .o files
122
  3) link                  generates a binary (library/executable)
123
  4) bundle                merges the above in a mac bundle
124

125
  (Any of these steps can be optional.)
126

127
  From a build ordering perspective, a dependent target B could just
128
  depend on the last output of this series of steps.
129

130
  But some dependent commands sometimes need to reach inside the box.
131
  For example, when linking B it needs to get the path to the static
132
  library generated by A.
133

134
  This object stores those paths.  To keep things simple, member
135
  variables only store concrete paths to single files, while methods
136
  compute derived values like "the last output of the target".
137
  """
138
  def __init__(self, type):
139
    # Gyp type ("static_library", etc.) of this target.
140
    self.type = type
141
    # File representing whether any input dependencies necessary for
142
    # dependent actions have completed.
143
    self.preaction_stamp = None
144
    # File representing whether any input dependencies necessary for
145
    # dependent compiles have completed.
146
    self.precompile_stamp = None
147
    # File representing the completion of actions/rules/copies, if any.
148
    self.actions_stamp = None
149
    # Path to the output of the link step, if any.
150
    self.binary = None
151
    # Path to the file representing the completion of building the bundle,
152
    # if any.
153
    self.bundle = None
154
    # On Windows, incremental linking requires linking against all the .objs
155
    # that compose a .lib (rather than the .lib itself). That list is stored
156
    # here.
157
    self.component_objs = None
158
    # Windows only. The import .lib is the output of a build step, but
159
    # because dependents only link against the lib (not both the lib and the
160
    # dll) we keep track of the import library here.
161
    self.import_lib = None
162

    
163
  def Linkable(self):
164
    """Return true if this is a target that can be linked against."""
165
    return self.type in ('static_library', 'shared_library')
166

    
167
  def UsesToc(self, flavor):
168
    """Return true if the target should produce a restat rule based on a TOC
169
    file."""
170
    # For bundles, the .TOC should be produced for the binary, not for
171
    # FinalOutput(). But the naive approach would put the TOC file into the
172
    # bundle, so don't do this for bundles for now.
173
    if flavor == 'win' or self.bundle:
174
      return False
175
    return self.type in ('shared_library', 'loadable_module')
176

    
177
  def PreActionInput(self, flavor):
178
    """Return the path, if any, that should be used as a dependency of
179
    any dependent action step."""
180
    if self.UsesToc(flavor):
181
      return self.FinalOutput() + '.TOC'
182
    return self.FinalOutput() or self.preaction_stamp
183

    
184
  def PreCompileInput(self):
185
    """Return the path, if any, that should be used as a dependency of
186
    any dependent compile step."""
187
    return self.actions_stamp or self.precompile_stamp
188

    
189
  def FinalOutput(self):
190
    """Return the last output of the target, which depends on all prior
191
    steps."""
192
    return self.bundle or self.binary or self.actions_stamp
193

    
194

    
195
# A small discourse on paths as used within the Ninja build:
196
# All files we produce (both at gyp and at build time) appear in the
197
# build directory (e.g. out/Debug).
198
#
199
# Paths within a given .gyp file are always relative to the directory
200
# containing the .gyp file.  Call these "gyp paths".  This includes
201
# sources as well as the starting directory a given gyp rule/action
202
# expects to be run from.  We call the path from the source root to
203
# the gyp file the "base directory" within the per-.gyp-file
204
# NinjaWriter code.
205
#
206
# All paths as written into the .ninja files are relative to the build
207
# directory.  Call these paths "ninja paths".
208
#
209
# We translate between these two notions of paths with two helper
210
# functions:
211
#
212
# - GypPathToNinja translates a gyp path (i.e. relative to the .gyp file)
213
#   into the equivalent ninja path.
214
#
215
# - GypPathToUniqueOutput translates a gyp path into a ninja path to write
216
#   an output file; the result can be namespaced such that it is unique
217
#   to the input file name as well as the output target name.
218

    
219
class NinjaWriter:
220
  def __init__(self, qualified_target, target_outputs, base_dir, build_dir,
221
               output_file, flavor, abs_build_dir=None):
222
    """
223
    base_dir: path from source root to directory containing this gyp file,
224
              by gyp semantics, all input paths are relative to this
225
    build_dir: path from source root to build output
226
    abs_build_dir: absolute path to the build directory
227
    """
228

    
229
    self.qualified_target = qualified_target
230
    self.target_outputs = target_outputs
231
    self.base_dir = base_dir
232
    self.build_dir = build_dir
233
    self.ninja = ninja_syntax.Writer(output_file)
234
    self.flavor = flavor
235
    self.abs_build_dir = abs_build_dir
236
    self.obj_ext = '.obj' if flavor == 'win' else '.o'
237
    if flavor == 'win':
238
      # See docstring of msvs_emulation.GenerateEnvironmentFiles().
239
      self.win_env = {}
240
      for arch in ('x86', 'x64'):
241
        self.win_env[arch] = 'environment.' + arch
242

    
243
    # Relative path from build output dir to base dir.
244
    self.build_to_base = os.path.join(InvertRelativePath(build_dir), base_dir)
245
    # Relative path from base dir to build dir.
246
    self.base_to_build = os.path.join(InvertRelativePath(base_dir), build_dir)
247

    
248
  def ExpandSpecial(self, path, product_dir=None):
249
    """Expand specials like $!PRODUCT_DIR in |path|.
250

251
    If |product_dir| is None, assumes the cwd is already the product
252
    dir.  Otherwise, |product_dir| is the relative path to the product
253
    dir.
254
    """
255

    
256
    PRODUCT_DIR = '$!PRODUCT_DIR'
257
    if PRODUCT_DIR in path:
258
      if product_dir:
259
        path = path.replace(PRODUCT_DIR, product_dir)
260
      else:
261
        path = path.replace(PRODUCT_DIR + '/', '')
262
        path = path.replace(PRODUCT_DIR + '\\', '')
263
        path = path.replace(PRODUCT_DIR, '.')
264

    
265
    INTERMEDIATE_DIR = '$!INTERMEDIATE_DIR'
266
    if INTERMEDIATE_DIR in path:
267
      int_dir = self.GypPathToUniqueOutput('gen')
268
      # GypPathToUniqueOutput generates a path relative to the product dir,
269
      # so insert product_dir in front if it is provided.
270
      path = path.replace(INTERMEDIATE_DIR,
271
                          os.path.join(product_dir or '', int_dir))
272

    
273
    CONFIGURATION_NAME = '$|CONFIGURATION_NAME'
274
    path = path.replace(CONFIGURATION_NAME, self.config_name)
275

    
276
    return path
277

    
278
  def ExpandRuleVariables(self, path, root, dirname, source, ext, name):
279
    if self.flavor == 'win':
280
      path = self.msvs_settings.ConvertVSMacros(
281
          path, config=self.config_name)
282
    path = path.replace(generator_default_variables['RULE_INPUT_ROOT'], root)
283
    path = path.replace(generator_default_variables['RULE_INPUT_DIRNAME'],
284
                        dirname)
285
    path = path.replace(generator_default_variables['RULE_INPUT_PATH'], source)
286
    path = path.replace(generator_default_variables['RULE_INPUT_EXT'], ext)
287
    path = path.replace(generator_default_variables['RULE_INPUT_NAME'], name)
288
    return path
289

    
290
  def GypPathToNinja(self, path, env=None):
291
    """Translate a gyp path to a ninja path, optionally expanding environment
292
    variable references in |path| with |env|.
293

294
    See the above discourse on path conversions."""
295
    if env:
296
      if self.flavor == 'mac':
297
        path = gyp.xcode_emulation.ExpandEnvVars(path, env)
298
      elif self.flavor == 'win':
299
        path = gyp.msvs_emulation.ExpandMacros(path, env)
300
    if path.startswith('$!'):
301
      expanded = self.ExpandSpecial(path)
302
      if self.flavor == 'win':
303
        expanded = os.path.normpath(expanded)
304
      return expanded
305
    if '$|' in path:
306
      path =  self.ExpandSpecial(path)
307
    assert '$' not in path, path
308
    return os.path.normpath(os.path.join(self.build_to_base, path))
309

    
310
  def GypPathToUniqueOutput(self, path, qualified=True):
311
    """Translate a gyp path to a ninja path for writing output.
312

313
    If qualified is True, qualify the resulting filename with the name
314
    of the target.  This is necessary when e.g. compiling the same
315
    path twice for two separate output targets.
316

317
    See the above discourse on path conversions."""
318

    
319
    path = self.ExpandSpecial(path)
320
    assert not path.startswith('$'), path
321

    
322
    # Translate the path following this scheme:
323
    #   Input: foo/bar.gyp, target targ, references baz/out.o
324
    #   Output: obj/foo/baz/targ.out.o (if qualified)
325
    #           obj/foo/baz/out.o (otherwise)
326
    #     (and obj.host instead of obj for cross-compiles)
327
    #
328
    # Why this scheme and not some other one?
329
    # 1) for a given input, you can compute all derived outputs by matching
330
    #    its path, even if the input is brought via a gyp file with '..'.
331
    # 2) simple files like libraries and stamps have a simple filename.
332

    
333
    obj = 'obj'
334
    if self.toolset != 'target':
335
      obj += '.' + self.toolset
336

    
337
    path_dir, path_basename = os.path.split(path)
338
    if qualified:
339
      path_basename = self.name + '.' + path_basename
340
    return os.path.normpath(os.path.join(obj, self.base_dir, path_dir,
341
                                         path_basename))
342

    
343
  def WriteCollapsedDependencies(self, name, targets):
344
    """Given a list of targets, return a path for a single file
345
    representing the result of building all the targets or None.
346

347
    Uses a stamp file if necessary."""
348

    
349
    assert targets == filter(None, targets), targets
350
    if len(targets) == 0:
351
      return None
352
    if len(targets) > 1:
353
      stamp = self.GypPathToUniqueOutput(name + '.stamp')
354
      targets = self.ninja.build(stamp, 'stamp', targets)
355
      self.ninja.newline()
356
    return targets[0]
357

    
358
  def WriteSpec(self, spec, config_name, generator_flags,
359
      case_sensitive_filesystem):
360
    """The main entry point for NinjaWriter: write the build rules for a spec.
361

362
    Returns a Target object, which represents the output paths for this spec.
363
    Returns None if there are no outputs (e.g. a settings-only 'none' type
364
    target)."""
365

    
366
    self.config_name = config_name
367
    self.name = spec['target_name']
368
    self.toolset = spec['toolset']
369
    config = spec['configurations'][config_name]
370
    self.target = Target(spec['type'])
371
    self.is_standalone_static_library = bool(
372
        spec.get('standalone_static_library', 0))
373

    
374
    self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec)
375
    self.xcode_settings = self.msvs_settings = None
376
    if self.flavor == 'mac':
377
      self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec)
378
    if self.flavor == 'win':
379
      self.msvs_settings = gyp.msvs_emulation.MsvsSettings(spec,
380
                                                           generator_flags)
381
      target_platform = self.msvs_settings.GetTargetPlatform(config_name)
382
      self.ninja.variable('arch', self.win_env[target_platform])
383

    
384
    # Compute predepends for all rules.
385
    # actions_depends is the dependencies this target depends on before running
386
    # any of its action/rule/copy steps.
387
    # compile_depends is the dependencies this target depends on before running
388
    # any of its compile steps.
389
    actions_depends = []
390
    compile_depends = []
391
    # TODO(evan): it is rather confusing which things are lists and which
392
    # are strings.  Fix these.
393
    if 'dependencies' in spec:
394
      for dep in spec['dependencies']:
395
        if dep in self.target_outputs:
396
          target = self.target_outputs[dep]
397
          actions_depends.append(target.PreActionInput(self.flavor))
398
          compile_depends.append(target.PreCompileInput())
399
      actions_depends = filter(None, actions_depends)
400
      compile_depends = filter(None, compile_depends)
401
      actions_depends = self.WriteCollapsedDependencies('actions_depends',
402
                                                        actions_depends)
403
      compile_depends = self.WriteCollapsedDependencies('compile_depends',
404
                                                        compile_depends)
405
      self.target.preaction_stamp = actions_depends
406
      self.target.precompile_stamp = compile_depends
407

    
408
    # Write out actions, rules, and copies.  These must happen before we
409
    # compile any sources, so compute a list of predependencies for sources
410
    # while we do it.
411
    extra_sources = []
412
    mac_bundle_depends = []
413
    self.target.actions_stamp = self.WriteActionsRulesCopies(
414
        spec, extra_sources, actions_depends, mac_bundle_depends)
415

    
416
    # If we have actions/rules/copies, we depend directly on those, but
417
    # otherwise we depend on dependent target's actions/rules/copies etc.
418
    # We never need to explicitly depend on previous target's link steps,
419
    # because no compile ever depends on them.
420
    compile_depends_stamp = (self.target.actions_stamp or compile_depends)
421

    
422
    # Write out the compilation steps, if any.
423
    link_deps = []
424
    sources = spec.get('sources', []) + extra_sources
425
    if sources:
426
      pch = None
427
      if self.flavor == 'win':
428
        gyp.msvs_emulation.VerifyMissingSources(
429
            sources, self.abs_build_dir, generator_flags, self.GypPathToNinja)
430
        pch = gyp.msvs_emulation.PrecompiledHeader(
431
            self.msvs_settings, config_name, self.GypPathToNinja)
432
      else:
433
        pch = gyp.xcode_emulation.MacPrefixHeader(
434
            self.xcode_settings, self.GypPathToNinja,
435
            lambda path, lang: self.GypPathToUniqueOutput(path + '-' + lang))
436
      link_deps = self.WriteSources(
437
          config_name, config, sources, compile_depends_stamp, pch,
438
          case_sensitive_filesystem)
439
      # Some actions/rules output 'sources' that are already object files.
440
      link_deps += [self.GypPathToNinja(f)
441
          for f in sources if f.endswith(self.obj_ext)]
442

    
443
    if self.flavor == 'win' and self.target.type == 'static_library':
444
      self.target.component_objs = link_deps
445

    
446
    # Write out a link step, if needed.
447
    output = None
448
    if link_deps or self.target.actions_stamp or actions_depends:
449
      output = self.WriteTarget(spec, config_name, config, link_deps,
450
                                self.target.actions_stamp or actions_depends)
451
      if self.is_mac_bundle:
452
        mac_bundle_depends.append(output)
453

    
454
    # Bundle all of the above together, if needed.
455
    if self.is_mac_bundle:
456
      output = self.WriteMacBundle(spec, mac_bundle_depends)
457

    
458
    if not output:
459
      return None
460

    
461
    assert self.target.FinalOutput(), output
462
    return self.target
463

    
464
  def _WinIdlRule(self, source, prebuild, outputs):
465
    """Handle the implicit VS .idl rule for one source file. Fills |outputs|
466
    with files that are generated."""
467
    outdir, output, vars, flags = self.msvs_settings.GetIdlBuildData(
468
        source, self.config_name)
469
    outdir = self.GypPathToNinja(outdir)
470
    def fix_path(path, rel=None):
471
      path = os.path.join(outdir, path)
472
      dirname, basename = os.path.split(source)
473
      root, ext = os.path.splitext(basename)
474
      path = self.ExpandRuleVariables(
475
          path, root, dirname, source, ext, basename)
476
      if rel:
477
        path = os.path.relpath(path, rel)
478
      return path
479
    vars = [(name, fix_path(value, outdir)) for name, value in vars]
480
    output = [fix_path(p) for p in output]
481
    vars.append(('outdir', outdir))
482
    vars.append(('idlflags', flags))
483
    input = self.GypPathToNinja(source)
484
    self.ninja.build(output, 'idl', input,
485
        variables=vars, order_only=prebuild)
486
    outputs.extend(output)
487

    
488
  def WriteWinIdlFiles(self, spec, prebuild):
489
    """Writes rules to match MSVS's implicit idl handling."""
490
    assert self.flavor == 'win'
491
    if self.msvs_settings.HasExplicitIdlRules(spec):
492
      return []
493
    outputs = []
494
    for source in filter(lambda x: x.endswith('.idl'), spec['sources']):
495
      self._WinIdlRule(source, prebuild, outputs)
496
    return outputs
497

    
498
  def WriteActionsRulesCopies(self, spec, extra_sources, prebuild,
499
                              mac_bundle_depends):
500
    """Write out the Actions, Rules, and Copies steps.  Return a path
501
    representing the outputs of these steps."""
502
    outputs = []
503
    extra_mac_bundle_resources = []
504

    
505
    if 'actions' in spec:
506
      outputs += self.WriteActions(spec['actions'], extra_sources, prebuild,
507
                                   extra_mac_bundle_resources)
508
    if 'rules' in spec:
509
      outputs += self.WriteRules(spec['rules'], extra_sources, prebuild,
510
                                 extra_mac_bundle_resources)
511
    if 'copies' in spec:
512
      outputs += self.WriteCopies(spec['copies'], prebuild)
513

    
514
    if 'sources' in spec and self.flavor == 'win':
515
      outputs += self.WriteWinIdlFiles(spec, prebuild)
516

    
517
    stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs)
518

    
519
    if self.is_mac_bundle:
520
      mac_bundle_resources = spec.get('mac_bundle_resources', []) + \
521
                             extra_mac_bundle_resources
522
      self.WriteMacBundleResources(mac_bundle_resources, mac_bundle_depends)
523
      self.WriteMacInfoPlist(mac_bundle_depends)
524

    
525
    return stamp
526

    
527
  def GenerateDescription(self, verb, message, fallback):
528
    """Generate and return a description of a build step.
529

530
    |verb| is the short summary, e.g. ACTION or RULE.
531
    |message| is a hand-written description, or None if not available.
532
    |fallback| is the gyp-level name of the step, usable as a fallback.
533
    """
534
    if self.toolset != 'target':
535
      verb += '(%s)' % self.toolset
536
    if message:
537
      return '%s %s' % (verb, self.ExpandSpecial(message))
538
    else:
539
      return '%s %s: %s' % (verb, self.name, fallback)
540

    
541
  def WriteActions(self, actions, extra_sources, prebuild,
542
                   extra_mac_bundle_resources):
543
    # Actions cd into the base directory.
544
    env = self.GetSortedXcodeEnv()
545
    if self.flavor == 'win':
546
      env = self.msvs_settings.GetVSMacroEnv(
547
          '$!PRODUCT_DIR', config=self.config_name)
548
    all_outputs = []
549
    for action in actions:
550
      # First write out a rule for the action.
551
      name = '%s_%s' % (action['action_name'],
552
                        hashlib.md5(self.qualified_target).hexdigest())
553
      description = self.GenerateDescription('ACTION',
554
                                             action.get('message', None),
555
                                             name)
556
      is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(action)
557
                   if self.flavor == 'win' else False)
558
      args = action['action']
559
      rule_name, _ = self.WriteNewNinjaRule(name, args, description,
560
                                            is_cygwin, env=env)
561

    
562
      inputs = [self.GypPathToNinja(i, env) for i in action['inputs']]
563
      if int(action.get('process_outputs_as_sources', False)):
564
        extra_sources += action['outputs']
565
      if int(action.get('process_outputs_as_mac_bundle_resources', False)):
566
        extra_mac_bundle_resources += action['outputs']
567
      outputs = [self.GypPathToNinja(o, env) for o in action['outputs']]
568

    
569
      # Then write out an edge using the rule.
570
      self.ninja.build(outputs, rule_name, inputs,
571
                       order_only=prebuild)
572
      all_outputs += outputs
573

    
574
      self.ninja.newline()
575

    
576
    return all_outputs
577

    
578
  def WriteRules(self, rules, extra_sources, prebuild,
579
                 extra_mac_bundle_resources):
580
    env = self.GetSortedXcodeEnv()
581
    all_outputs = []
582
    for rule in rules:
583
      # First write out a rule for the rule action.
584
      name = '%s_%s' % (rule['rule_name'],
585
                        hashlib.md5(self.qualified_target).hexdigest())
586
      # Skip a rule with no action and no inputs.
587
      if 'action' not in rule and not rule.get('rule_sources', []):
588
        continue
589
      args = rule['action']
590
      description = self.GenerateDescription(
591
          'RULE',
592
          rule.get('message', None),
593
          ('%s ' + generator_default_variables['RULE_INPUT_PATH']) % name)
594
      is_cygwin = (self.msvs_settings.IsRuleRunUnderCygwin(rule)
595
                   if self.flavor == 'win' else False)
596
      rule_name, args = self.WriteNewNinjaRule(
597
          name, args, description, is_cygwin, env=env)
598

    
599
      # TODO: if the command references the outputs directly, we should
600
      # simplify it to just use $out.
601

    
602
      # Rules can potentially make use of some special variables which
603
      # must vary per source file.
604
      # Compute the list of variables we'll need to provide.
605
      special_locals = ('source', 'root', 'dirname', 'ext', 'name')
606
      needed_variables = set(['source'])
607
      for argument in args:
608
        for var in special_locals:
609
          if ('${%s}' % var) in argument:
610
            needed_variables.add(var)
611

    
612
      def cygwin_munge(path):
613
        if is_cygwin:
614
          return path.replace('\\', '/')
615
        return path
616

    
617
      # For each source file, write an edge that generates all the outputs.
618
      for source in rule.get('rule_sources', []):
619
        dirname, basename = os.path.split(source)
620
        root, ext = os.path.splitext(basename)
621

    
622
        # Gather the list of inputs and outputs, expanding $vars if possible.
623
        outputs = [self.ExpandRuleVariables(o, root, dirname,
624
                                            source, ext, basename)
625
                   for o in rule['outputs']]
626
        inputs = [self.ExpandRuleVariables(i, root, dirname,
627
                                           source, ext, basename)
628
                  for i in rule.get('inputs', [])]
629

    
630
        if int(rule.get('process_outputs_as_sources', False)):
631
          extra_sources += outputs
632
        if int(rule.get('process_outputs_as_mac_bundle_resources', False)):
633
          extra_mac_bundle_resources += outputs
634

    
635
        extra_bindings = []
636
        for var in needed_variables:
637
          if var == 'root':
638
            extra_bindings.append(('root', cygwin_munge(root)))
639
          elif var == 'dirname':
640
            extra_bindings.append(('dirname', cygwin_munge(dirname)))
641
          elif var == 'source':
642
            # '$source' is a parameter to the rule action, which means
643
            # it shouldn't be converted to a Ninja path.  But we don't
644
            # want $!PRODUCT_DIR in there either.
645
            source_expanded = self.ExpandSpecial(source, self.base_to_build)
646
            extra_bindings.append(('source', cygwin_munge(source_expanded)))
647
          elif var == 'ext':
648
            extra_bindings.append(('ext', ext))
649
          elif var == 'name':
650
            extra_bindings.append(('name', cygwin_munge(basename)))
651
          else:
652
            assert var == None, repr(var)
653

    
654
        inputs = [self.GypPathToNinja(i, env) for i in inputs]
655
        outputs = [self.GypPathToNinja(o, env) for o in outputs]
656
        extra_bindings.append(('unique_name',
657
            hashlib.md5(outputs[0]).hexdigest()))
658
        self.ninja.build(outputs, rule_name, self.GypPathToNinja(source),
659
                         implicit=inputs,
660
                         order_only=prebuild,
661
                         variables=extra_bindings)
662

    
663
        all_outputs.extend(outputs)
664

    
665
    return all_outputs
666

    
667
  def WriteCopies(self, copies, prebuild):
668
    outputs = []
669
    env = self.GetSortedXcodeEnv()
670
    for copy in copies:
671
      for path in copy['files']:
672
        # Normalize the path so trailing slashes don't confuse us.
673
        path = os.path.normpath(path)
674
        basename = os.path.split(path)[1]
675
        src = self.GypPathToNinja(path, env)
676
        dst = self.GypPathToNinja(os.path.join(copy['destination'], basename),
677
                                  env)
678
        outputs += self.ninja.build(dst, 'copy', src, order_only=prebuild)
679

    
680
    return outputs
681

    
682
  def WriteMacBundleResources(self, resources, bundle_depends):
683
    """Writes ninja edges for 'mac_bundle_resources'."""
684
    for output, res in gyp.xcode_emulation.GetMacBundleResources(
685
        self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']),
686
        self.xcode_settings, map(self.GypPathToNinja, resources)):
687
      self.ninja.build(output, 'mac_tool', res,
688
                       variables=[('mactool_cmd', 'copy-bundle-resource')])
689
      bundle_depends.append(output)
690

    
691
  def WriteMacInfoPlist(self, bundle_depends):
692
    """Write build rules for bundle Info.plist files."""
693
    info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist(
694
        self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']),
695
        self.xcode_settings, self.GypPathToNinja)
696
    if not info_plist:
697
      return
698
    if defines:
699
      # Create an intermediate file to store preprocessed results.
700
      intermediate_plist = self.GypPathToUniqueOutput(
701
          os.path.basename(info_plist))
702
      defines = ' '.join([Define(d, self.flavor) for d in defines])
703
      info_plist = self.ninja.build(intermediate_plist, 'infoplist', info_plist,
704
                                    variables=[('defines',defines)])
705

    
706
    env = self.GetSortedXcodeEnv(additional_settings=extra_env)
707
    env = self.ComputeExportEnvString(env)
708

    
709
    self.ninja.build(out, 'mac_tool', info_plist,
710
                     variables=[('mactool_cmd', 'copy-info-plist'),
711
                                ('env', env)])
712
    bundle_depends.append(out)
713

    
714
  def WriteSources(self, config_name, config, sources, predepends,
715
                   precompiled_header, case_sensitive_filesystem):
716
    """Write build rules to compile all of |sources|."""
717
    if self.toolset == 'host':
718
      self.ninja.variable('ar', '$ar_host')
719
      self.ninja.variable('cc', '$cc_host')
720
      self.ninja.variable('cxx', '$cxx_host')
721
      self.ninja.variable('ld', '$ld_host')
722

    
723
    extra_defines = []
724
    if self.flavor == 'mac':
725
      cflags = self.xcode_settings.GetCflags(config_name)
726
      cflags_c = self.xcode_settings.GetCflagsC(config_name)
727
      cflags_cc = self.xcode_settings.GetCflagsCC(config_name)
728
      cflags_objc = ['$cflags_c'] + \
729
                    self.xcode_settings.GetCflagsObjC(config_name)
730
      cflags_objcc = ['$cflags_cc'] + \
731
                     self.xcode_settings.GetCflagsObjCC(config_name)
732
    elif self.flavor == 'win':
733
      cflags = self.msvs_settings.GetCflags(config_name)
734
      cflags_c = self.msvs_settings.GetCflagsC(config_name)
735
      cflags_cc = self.msvs_settings.GetCflagsCC(config_name)
736
      extra_defines = self.msvs_settings.GetComputedDefines(config_name)
737
      self.WriteVariableList('pdbname', [self.name + '.pdb'])
738
      self.WriteVariableList('pchprefix', [self.name])
739
    else:
740
      cflags = config.get('cflags', [])
741
      cflags_c = config.get('cflags_c', [])
742
      cflags_cc = config.get('cflags_cc', [])
743

    
744
    defines = config.get('defines', []) + extra_defines
745
    self.WriteVariableList('defines', [Define(d, self.flavor) for d in defines])
746
    if self.flavor == 'win':
747
      self.WriteVariableList('rcflags',
748
          [QuoteShellArgument(self.ExpandSpecial(f), self.flavor)
749
           for f in self.msvs_settings.GetRcflags(config_name,
750
                                                  self.GypPathToNinja)])
751

    
752
    include_dirs = config.get('include_dirs', [])
753
    if self.flavor == 'win':
754
      include_dirs = self.msvs_settings.AdjustIncludeDirs(include_dirs,
755
                                                          config_name)
756
    self.WriteVariableList('includes',
757
        [QuoteShellArgument('-I' + self.GypPathToNinja(i), self.flavor)
758
         for i in include_dirs])
759

    
760
    pch_commands = precompiled_header.GetPchBuildCommands()
761
    if self.flavor == 'mac':
762
      self.WriteVariableList('cflags_pch_c',
763
                             [precompiled_header.GetInclude('c')])
764
      self.WriteVariableList('cflags_pch_cc',
765
                             [precompiled_header.GetInclude('cc')])
766
      self.WriteVariableList('cflags_pch_objc',
767
                             [precompiled_header.GetInclude('m')])
768
      self.WriteVariableList('cflags_pch_objcc',
769
                             [precompiled_header.GetInclude('mm')])
770

    
771
    self.WriteVariableList('cflags', map(self.ExpandSpecial, cflags))
772
    self.WriteVariableList('cflags_c', map(self.ExpandSpecial, cflags_c))
773
    self.WriteVariableList('cflags_cc', map(self.ExpandSpecial, cflags_cc))
774
    if self.flavor == 'mac':
775
      self.WriteVariableList('cflags_objc', map(self.ExpandSpecial,
776
                                                cflags_objc))
777
      self.WriteVariableList('cflags_objcc', map(self.ExpandSpecial,
778
                                                 cflags_objcc))
779
    self.ninja.newline()
780
    outputs = []
781
    for source in sources:
782
      filename, ext = os.path.splitext(source)
783
      ext = ext[1:]
784
      obj_ext = self.obj_ext
785
      if ext in ('cc', 'cpp', 'cxx'):
786
        command = 'cxx'
787
      elif ext == 'c' or (ext == 'S' and self.flavor != 'win'):
788
        command = 'cc'
789
      elif ext == 's' and self.flavor != 'win':  # Doesn't generate .o.d files.
790
        command = 'cc_s'
791
      elif (self.flavor == 'win' and ext == 'asm' and
792
            self.msvs_settings.GetTargetPlatform(config_name) == 'Win32'):
793
        # Asm files only get auto assembled for x86 (not x64).
794
        command = 'asm'
795
        # Add the _asm suffix as msvs is capable of handling .cc and
796
        # .asm files of the same name without collision.
797
        obj_ext = '_asm.obj'
798
      elif self.flavor == 'mac' and ext == 'm':
799
        command = 'objc'
800
      elif self.flavor == 'mac' and ext == 'mm':
801
        command = 'objcxx'
802
      elif self.flavor == 'win' and ext == 'rc':
803
        command = 'rc'
804
        obj_ext = '.res'
805
      else:
806
        # Ignore unhandled extensions.
807
        continue
808
      input = self.GypPathToNinja(source)
809
      output = self.GypPathToUniqueOutput(filename + obj_ext)
810
      # Ninja's depfile handling gets confused when the case of a filename
811
      # changes on a case-insensitive file system. To work around that, always
812
      # convert .o filenames to lowercase on such file systems. See
813
      # https://github.com/martine/ninja/issues/402 for details.
814
      if not case_sensitive_filesystem:
815
        output = output.lower()
816
      implicit = precompiled_header.GetObjDependencies([input], [output])
817
      self.ninja.build(output, command, input,
818
                       implicit=[gch for _, _, gch in implicit],
819
                       order_only=predepends)
820
      outputs.append(output)
821

    
822
    self.WritePchTargets(pch_commands)
823

    
824
    self.ninja.newline()
825
    return outputs
826

    
827
  def WritePchTargets(self, pch_commands):
828
    """Writes ninja rules to compile prefix headers."""
829
    if not pch_commands:
830
      return
831

    
832
    for gch, lang_flag, lang, input in pch_commands:
833
      var_name = {
834
        'c': 'cflags_pch_c',
835
        'cc': 'cflags_pch_cc',
836
        'm': 'cflags_pch_objc',
837
        'mm': 'cflags_pch_objcc',
838
      }[lang]
839

    
840
      map = { 'c': 'cc', 'cc': 'cxx', 'm': 'objc', 'mm': 'objcxx', }
841
      if self.flavor == 'win':
842
        map.update({'c': 'cc_pch', 'cc': 'cxx_pch'})
843
      cmd = map.get(lang)
844
      self.ninja.build(gch, cmd, input, variables=[(var_name, lang_flag)])
845

    
846
  def WriteLink(self, spec, config_name, config, link_deps):
847
    """Write out a link step. Fills out target.binary. """
848

    
849
    command = {
850
      'executable':      'link',
851
      'loadable_module': 'solink_module',
852
      'shared_library':  'solink',
853
    }[spec['type']]
854

    
855
    implicit_deps = set()
856
    solibs = set()
857

    
858
    if 'dependencies' in spec:
859
      # Two kinds of dependencies:
860
      # - Linkable dependencies (like a .a or a .so): add them to the link line.
861
      # - Non-linkable dependencies (like a rule that generates a file
862
      #   and writes a stamp file): add them to implicit_deps
863
      extra_link_deps = set()
864
      for dep in spec['dependencies']:
865
        target = self.target_outputs.get(dep)
866
        if not target:
867
          continue
868
        linkable = target.Linkable()
869
        if linkable:
870
          if (self.flavor == 'win' and
871
              target.component_objs and
872
              self.msvs_settings.IsUseLibraryDependencyInputs(config_name)):
873
            extra_link_deps |= set(target.component_objs)
874
          elif self.flavor == 'win' and target.import_lib:
875
            extra_link_deps.add(target.import_lib)
876
          elif target.UsesToc(self.flavor):
877
            solibs.add(target.binary)
878
            implicit_deps.add(target.binary + '.TOC')
879
          else:
880
            extra_link_deps.add(target.binary)
881

    
882
        final_output = target.FinalOutput()
883
        if not linkable or final_output != target.binary:
884
          implicit_deps.add(final_output)
885

    
886
      link_deps.extend(list(extra_link_deps))
887

    
888
    extra_bindings = []
889
    if self.is_mac_bundle:
890
      output = self.ComputeMacBundleBinaryOutput()
891
    else:
892
      output = self.ComputeOutput(spec)
893
      extra_bindings.append(('postbuilds',
894
                             self.GetPostbuildCommand(spec, output, output)))
895

    
896
    if self.flavor == 'mac':
897
      ldflags = self.xcode_settings.GetLdflags(config_name,
898
          self.ExpandSpecial(generator_default_variables['PRODUCT_DIR']),
899
          self.GypPathToNinja)
900
    elif self.flavor == 'win':
901
      libflags = self.msvs_settings.GetLibFlags(config_name,
902
                                                self.GypPathToNinja)
903
      self.WriteVariableList(
904
          'libflags', gyp.common.uniquer(map(self.ExpandSpecial, libflags)))
905
      is_executable = spec['type'] == 'executable'
906
      manifest_name = self.GypPathToUniqueOutput(
907
          self.ComputeOutputFileName(spec))
908
      ldflags, manifest_files = self.msvs_settings.GetLdflags(config_name,
909
          self.GypPathToNinja, self.ExpandSpecial, manifest_name, is_executable)
910
      self.WriteVariableList('manifests', manifest_files)
911
    else:
912
      ldflags = config.get('ldflags', [])
913
    self.WriteVariableList('ldflags',
914
                           gyp.common.uniquer(map(self.ExpandSpecial,
915
                                                  ldflags)))
916

    
917
    libraries = gyp.common.uniquer(map(self.ExpandSpecial,
918
                                       spec.get('libraries', [])))
919
    if self.flavor == 'mac':
920
      libraries = self.xcode_settings.AdjustLibraries(libraries)
921
    elif self.flavor == 'win':
922
      libraries = self.msvs_settings.AdjustLibraries(libraries)
923
    self.WriteVariableList('libs', libraries)
924

    
925
    self.target.binary = output
926

    
927
    if command in ('solink', 'solink_module'):
928
      extra_bindings.append(('soname', os.path.split(output)[1]))
929
      extra_bindings.append(('lib',
930
                            gyp.common.EncodePOSIXShellArgument(output)))
931
      if self.flavor == 'win':
932
        extra_bindings.append(('dll', output))
933
        if '/NOENTRY' not in ldflags:
934
          self.target.import_lib = output + '.lib'
935
          extra_bindings.append(('implibflag',
936
                                 '/IMPLIB:%s' % self.target.import_lib))
937
          output = [output, self.target.import_lib]
938
      else:
939
        output = [output, output + '.TOC']
940

    
941
    if len(solibs):
942
      extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs)))
943

    
944
    self.ninja.build(output, command, link_deps,
945
                     implicit=list(implicit_deps),
946
                     variables=extra_bindings)
947

    
948
  def WriteTarget(self, spec, config_name, config, link_deps, compile_deps):
949
    if spec['type'] == 'none':
950
      # TODO(evan): don't call this function for 'none' target types, as
951
      # it doesn't do anything, and we fake out a 'binary' with a stamp file.
952
      self.target.binary = compile_deps
953
    elif spec['type'] == 'static_library':
954
      self.target.binary = self.ComputeOutput(spec)
955
      variables = []
956
      postbuild = self.GetPostbuildCommand(
957
          spec, self.target.binary, self.target.binary)
958
      if postbuild:
959
        variables.append(('postbuilds', postbuild))
960
      if self.xcode_settings:
961
        variables.append(('libtool_flags',
962
                          self.xcode_settings.GetLibtoolflags(config_name)))
963
      if (self.flavor not in ('mac', 'win') and not
964
          self.is_standalone_static_library):
965
        self.ninja.build(self.target.binary, 'alink_thin', link_deps,
966
                         order_only=compile_deps, variables=variables)
967
      else:
968
        self.ninja.build(self.target.binary, 'alink', link_deps,
969
                         order_only=compile_deps, variables=variables)
970
    else:
971
      self.WriteLink(spec, config_name, config, link_deps)
972
    return self.target.binary
973

    
974
  def WriteMacBundle(self, spec, mac_bundle_depends):
975
    assert self.is_mac_bundle
976
    package_framework = spec['type'] in ('shared_library', 'loadable_module')
977
    output = self.ComputeMacBundleOutput()
978
    postbuild = self.GetPostbuildCommand(spec, output, self.target.binary,
979
                                         is_command_start=not package_framework)
980
    variables = []
981
    if postbuild:
982
      variables.append(('postbuilds', postbuild))
983
    if package_framework:
984
      variables.append(('version', self.xcode_settings.GetFrameworkVersion()))
985
      self.ninja.build(output, 'package_framework', mac_bundle_depends,
986
                       variables=variables)
987
    else:
988
      self.ninja.build(output, 'stamp', mac_bundle_depends,
989
                       variables=variables)
990
    self.target.bundle = output
991
    return output
992

    
993
  def GetSortedXcodeEnv(self, additional_settings=None):
994
    """Returns the variables Xcode would set for build steps."""
995
    assert self.abs_build_dir
996
    abs_build_dir = self.abs_build_dir
997
    return gyp.xcode_emulation.GetSortedXcodeEnv(
998
        self.xcode_settings, abs_build_dir,
999
        os.path.join(abs_build_dir, self.build_to_base), self.config_name,
1000
        additional_settings)
1001

    
1002
  def GetSortedXcodePostbuildEnv(self):
1003
    """Returns the variables Xcode would set for postbuild steps."""
1004
    postbuild_settings = {}
1005
    # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack.
1006
    # TODO(thakis): It would be nice to have some general mechanism instead.
1007
    strip_save_file = self.xcode_settings.GetPerTargetSetting(
1008
        'CHROMIUM_STRIP_SAVE_FILE')
1009
    if strip_save_file:
1010
      postbuild_settings['CHROMIUM_STRIP_SAVE_FILE'] = strip_save_file
1011
    return self.GetSortedXcodeEnv(additional_settings=postbuild_settings)
1012

    
1013
  def GetPostbuildCommand(self, spec, output, output_binary,
1014
                          is_command_start=False):
1015
    """Returns a shell command that runs all the postbuilds, and removes
1016
    |output| if any of them fails. If |is_command_start| is False, then the
1017
    returned string will start with ' && '."""
1018
    if not self.xcode_settings or spec['type'] == 'none' or not output:
1019
      return ''
1020
    output = QuoteShellArgument(output, self.flavor)
1021
    target_postbuilds = self.xcode_settings.GetTargetPostbuilds(
1022
        self.config_name,
1023
        os.path.normpath(os.path.join(self.base_to_build, output)),
1024
        QuoteShellArgument(
1025
            os.path.normpath(os.path.join(self.base_to_build, output_binary)),
1026
            self.flavor),
1027
        quiet=True)
1028
    postbuilds = gyp.xcode_emulation.GetSpecPostbuildCommands(spec, quiet=True)
1029
    postbuilds = target_postbuilds + postbuilds
1030
    if not postbuilds:
1031
      return ''
1032
    # Postbuilds expect to be run in the gyp file's directory, so insert an
1033
    # implicit postbuild to cd to there.
1034
    postbuilds.insert(0, gyp.common.EncodePOSIXShellList(
1035
        ['cd', self.build_to_base]))
1036
    env = self.ComputeExportEnvString(self.GetSortedXcodePostbuildEnv())
1037
    # G will be non-null if any postbuild fails. Run all postbuilds in a
1038
    # subshell.
1039
    commands = env + ' (F=0; ' + \
1040
        ' '.join([ninja_syntax.escape(command) + ' || F=$$?;'
1041
                                 for command in postbuilds])
1042
    command_string = (commands + ' exit $$F); G=$$?; '
1043
                      # Remove the final output if any postbuild failed.
1044
                      '((exit $$G) || rm -rf %s) ' % output + '&& exit $$G)')
1045
    if is_command_start:
1046
      return '(' + command_string + ' && '
1047
    else:
1048
      return '$ && (' + command_string
1049

    
1050
  def ComputeExportEnvString(self, env):
1051
    """Given an environment, returns a string looking like
1052
        'export FOO=foo; export BAR="${FOO} bar;'
1053
    that exports |env| to the shell."""
1054
    export_str = []
1055
    for k, v in env:
1056
      export_str.append('export %s=%s;' %
1057
          (k, ninja_syntax.escape(gyp.common.EncodePOSIXShellArgument(v))))
1058
    return ' '.join(export_str)
1059

    
1060
  def ComputeMacBundleOutput(self):
1061
    """Return the 'output' (full output path) to a bundle output directory."""
1062
    assert self.is_mac_bundle
1063
    path = self.ExpandSpecial(generator_default_variables['PRODUCT_DIR'])
1064
    return os.path.join(path, self.xcode_settings.GetWrapperName())
1065

    
1066
  def ComputeMacBundleBinaryOutput(self):
1067
    """Return the 'output' (full output path) to the binary in a bundle."""
1068
    assert self.is_mac_bundle
1069
    path = self.ExpandSpecial(generator_default_variables['PRODUCT_DIR'])
1070
    return os.path.join(path, self.xcode_settings.GetExecutablePath())
1071

    
1072
  def ComputeOutputFileName(self, spec, type=None):
1073
    """Compute the filename of the final output for the current target."""
1074
    if not type:
1075
      type = spec['type']
1076

    
1077
    default_variables = copy.copy(generator_default_variables)
1078
    CalculateVariables(default_variables, {'flavor': self.flavor})
1079

    
1080
    # Compute filename prefix: the product prefix, or a default for
1081
    # the product type.
1082
    DEFAULT_PREFIX = {
1083
      'loadable_module': default_variables['SHARED_LIB_PREFIX'],
1084
      'shared_library': default_variables['SHARED_LIB_PREFIX'],
1085
      'static_library': default_variables['STATIC_LIB_PREFIX'],
1086
      'executable': default_variables['EXECUTABLE_PREFIX'],
1087
      }
1088
    prefix = spec.get('product_prefix', DEFAULT_PREFIX.get(type, ''))
1089

    
1090
    # Compute filename extension: the product extension, or a default
1091
    # for the product type.
1092
    DEFAULT_EXTENSION = {
1093
        'loadable_module': default_variables['SHARED_LIB_SUFFIX'],
1094
        'shared_library': default_variables['SHARED_LIB_SUFFIX'],
1095
        'static_library': default_variables['STATIC_LIB_SUFFIX'],
1096
        'executable': default_variables['EXECUTABLE_SUFFIX'],
1097
      }
1098
    extension = spec.get('product_extension')
1099
    if extension:
1100
      extension = '.' + extension
1101
    else:
1102
      extension = DEFAULT_EXTENSION.get(type, '')
1103

    
1104
    if 'product_name' in spec:
1105
      # If we were given an explicit name, use that.
1106
      target = spec['product_name']
1107
    else:
1108
      # Otherwise, derive a name from the target name.
1109
      target = spec['target_name']
1110
      if prefix == 'lib':
1111
        # Snip out an extra 'lib' from libs if appropriate.
1112
        target = StripPrefix(target, 'lib')
1113

    
1114
    if type in ('static_library', 'loadable_module', 'shared_library',
1115
                        'executable'):
1116
      return '%s%s%s' % (prefix, target, extension)
1117
    elif type == 'none':
1118
      return '%s.stamp' % target
1119
    else:
1120
      raise Exception('Unhandled output type %s' % type)
1121

    
1122
  def ComputeOutput(self, spec, type=None):
1123
    """Compute the path for the final output of the spec."""
1124
    assert not self.is_mac_bundle or type
1125

    
1126
    if not type:
1127
      type = spec['type']
1128

    
1129
    if self.flavor == 'win':
1130
      override = self.msvs_settings.GetOutputName(self.config_name,
1131
                                                  self.ExpandSpecial)
1132
      if override:
1133
        return override
1134

    
1135
    if self.flavor == 'mac' and type in (
1136
        'static_library', 'executable', 'shared_library', 'loadable_module'):
1137
      filename = self.xcode_settings.GetExecutablePath()
1138
    else:
1139
      filename = self.ComputeOutputFileName(spec, type)
1140

    
1141
    if 'product_dir' in spec:
1142
      path = os.path.join(spec['product_dir'], filename)
1143
      return self.ExpandSpecial(path)
1144

    
1145
    # Some products go into the output root, libraries go into shared library
1146
    # dir, and everything else goes into the normal place.
1147
    type_in_output_root = ['executable', 'loadable_module']
1148
    if self.flavor == 'mac' and self.toolset == 'target':
1149
      type_in_output_root += ['shared_library', 'static_library']
1150
    elif self.flavor == 'win' and self.toolset == 'target':
1151
      type_in_output_root += ['shared_library']
1152

    
1153
    if type in type_in_output_root or self.is_standalone_static_library:
1154
      return filename
1155
    elif type == 'shared_library':
1156
      libdir = 'lib'
1157
      if self.toolset != 'target':
1158
        libdir = os.path.join('lib', '%s' % self.toolset)
1159
      return os.path.join(libdir, filename)
1160
    else:
1161
      return self.GypPathToUniqueOutput(filename, qualified=False)
1162

    
1163
  def WriteVariableList(self, var, values):
1164
    assert not isinstance(values, str)
1165
    if values is None:
1166
      values = []
1167
    self.ninja.variable(var, ' '.join(values))
1168

    
1169
  def WriteNewNinjaRule(self, name, args, description, is_cygwin, env):
1170
    """Write out a new ninja "rule" statement for a given command.
1171

1172
    Returns the name of the new rule, and a copy of |args| with variables
1173
    expanded."""
1174

    
1175
    if self.flavor == 'win':
1176
      args = [self.msvs_settings.ConvertVSMacros(
1177
                  arg, self.base_to_build, config=self.config_name)
1178
              for arg in args]
1179
      description = self.msvs_settings.ConvertVSMacros(
1180
          description, config=self.config_name)
1181
    elif self.flavor == 'mac':
1182
      # |env| is an empty list on non-mac.
1183
      args = [gyp.xcode_emulation.ExpandEnvVars(arg, env) for arg in args]
1184
      description = gyp.xcode_emulation.ExpandEnvVars(description, env)
1185

    
1186
    # TODO: we shouldn't need to qualify names; we do it because
1187
    # currently the ninja rule namespace is global, but it really
1188
    # should be scoped to the subninja.
1189
    rule_name = self.name
1190
    if self.toolset == 'target':
1191
      rule_name += '.' + self.toolset
1192
    rule_name += '.' + name
1193
    rule_name = re.sub('[^a-zA-Z0-9_]', '_', rule_name)
1194

    
1195
    # Remove variable references, but not if they refer to the magic rule
1196
    # variables.  This is not quite right, as it also protects these for
1197
    # actions, not just for rules where they are valid. Good enough.
1198
    protect = [ '${root}', '${dirname}', '${source}', '${ext}', '${name}' ]
1199
    protect = '(?!' + '|'.join(map(re.escape, protect)) + ')'
1200
    description = re.sub(protect + r'\$', '_', description)
1201

    
1202
    # gyp dictates that commands are run from the base directory.
1203
    # cd into the directory before running, and adjust paths in
1204
    # the arguments to point to the proper locations.
1205
    rspfile = None
1206
    rspfile_content = None
1207
    args = [self.ExpandSpecial(arg, self.base_to_build) for arg in args]
1208
    if self.flavor == 'win':
1209
      rspfile = rule_name + '.$unique_name.rsp'
1210
      # The cygwin case handles this inside the bash sub-shell.
1211
      run_in = '' if is_cygwin else ' ' + self.build_to_base
1212
      if is_cygwin:
1213
        rspfile_content = self.msvs_settings.BuildCygwinBashCommandLine(
1214
            args, self.build_to_base)
1215
      else:
1216
        rspfile_content = gyp.msvs_emulation.EncodeRspFileList(args)
1217
      command = ('%s gyp-win-tool action-wrapper $arch ' % sys.executable +
1218
                 rspfile + run_in)
1219
    else:
1220
      env = self.ComputeExportEnvString(env)
1221
      command = gyp.common.EncodePOSIXShellList(args)
1222
      command = 'cd %s; ' % self.build_to_base + env + command
1223

    
1224
    # GYP rules/actions express being no-ops by not touching their outputs.
1225
    # Avoid executing downstream dependencies in this case by specifying
1226
    # restat=1 to ninja.
1227
    self.ninja.rule(rule_name, command, description, restat=True,
1228
                    rspfile=rspfile, rspfile_content=rspfile_content)
1229
    self.ninja.newline()
1230

    
1231
    return rule_name, args
1232

    
1233

    
1234
def CalculateVariables(default_variables, params):
1235
  """Calculate additional variables for use in the build (called by gyp)."""
1236
  global generator_additional_non_configuration_keys
1237
  global generator_additional_path_sections
1238
  flavor = gyp.common.GetFlavor(params)
1239
  if flavor == 'mac':
1240
    default_variables.setdefault('OS', 'mac')
1241
    default_variables.setdefault('SHARED_LIB_SUFFIX', '.dylib')
1242
    default_variables.setdefault('SHARED_LIB_DIR',
1243
                                 generator_default_variables['PRODUCT_DIR'])
1244
    default_variables.setdefault('LIB_DIR',
1245
                                 generator_default_variables['PRODUCT_DIR'])
1246

    
1247
    # Copy additional generator configuration data from Xcode, which is shared
1248
    # by the Mac Ninja generator.
1249
    import gyp.generator.xcode as xcode_generator
1250
    generator_additional_non_configuration_keys = getattr(xcode_generator,
1251
        'generator_additional_non_configuration_keys', [])
1252
    generator_additional_path_sections = getattr(xcode_generator,
1253
        'generator_additional_path_sections', [])
1254
    global generator_extra_sources_for_rules
1255
    generator_extra_sources_for_rules = getattr(xcode_generator,
1256
        'generator_extra_sources_for_rules', [])
1257
  elif flavor == 'win':
1258
    default_variables.setdefault('OS', 'win')
1259
    default_variables['EXECUTABLE_SUFFIX'] = '.exe'
1260
    default_variables['STATIC_LIB_PREFIX'] = ''
1261
    default_variables['STATIC_LIB_SUFFIX'] = '.lib'
1262
    default_variables['SHARED_LIB_PREFIX'] = ''
1263
    default_variables['SHARED_LIB_SUFFIX'] = '.dll'
1264
    generator_flags = params.get('generator_flags', {})
1265

    
1266
    # Copy additional generator configuration data from VS, which is shared
1267
    # by the Windows Ninja generator.
1268
    import gyp.generator.msvs as msvs_generator
1269
    generator_additional_non_configuration_keys = getattr(msvs_generator,
1270
        'generator_additional_non_configuration_keys', [])
1271
    generator_additional_path_sections = getattr(msvs_generator,
1272
        'generator_additional_path_sections', [])
1273

    
1274
    # Set a variable so conditions can be based on msvs_version.
1275
    msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
1276
    default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1277

    
1278
    # To determine processor word size on Windows, in addition to checking
1279
    # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1280
    # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
1281
    # contains the actual word size of the system when running thru WOW64).
1282
    if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
1283
        '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
1284
      default_variables['MSVS_OS_BITS'] = 64
1285
    else:
1286
      default_variables['MSVS_OS_BITS'] = 32
1287
  else:
1288
    operating_system = flavor
1289
    if flavor == 'android':
1290
      operating_system = 'linux'  # Keep this legacy behavior for now.
1291
    default_variables.setdefault('OS', operating_system)
1292
    default_variables.setdefault('SHARED_LIB_SUFFIX', '.so')
1293
    default_variables.setdefault('SHARED_LIB_DIR',
1294
                                 os.path.join('$!PRODUCT_DIR', 'lib'))
1295
    default_variables.setdefault('LIB_DIR',
1296
                                 os.path.join('$!PRODUCT_DIR', 'obj'))
1297

    
1298

    
1299
def OpenOutput(path, mode='w'):
1300
  """Open |path| for writing, creating directories if necessary."""
1301
  try:
1302
    os.makedirs(os.path.dirname(path))
1303
  except OSError:
1304
    pass
1305
  return open(path, mode)
1306

    
1307

    
1308
def GenerateOutputForConfig(target_list, target_dicts, data, params,
1309
                            config_name):
1310
  options = params['options']
1311
  flavor = gyp.common.GetFlavor(params)
1312
  generator_flags = params.get('generator_flags', {})
1313

    
1314
  # generator_dir: relative path from pwd to where make puts build files.
1315
  # Makes migrating from make to ninja easier, ninja doesn't put anything here.
1316
  generator_dir = os.path.relpath(params['options'].generator_output or '.')
1317

    
1318
  # output_dir: relative path from generator_dir to the build directory.
1319
  output_dir = generator_flags.get('output_dir', 'out')
1320

    
1321
  # build_dir: relative path from source root to our output files.
1322
  # e.g. "out/Debug"
1323
  build_dir = os.path.normpath(os.path.join(generator_dir,
1324
                                            output_dir,
1325
                                            config_name))
1326

    
1327
  toplevel_build = os.path.join(options.toplevel_dir, build_dir)
1328

    
1329
  master_ninja = ninja_syntax.Writer(
1330
      OpenOutput(os.path.join(toplevel_build, 'build.ninja')),
1331
      width=120)
1332
  case_sensitive_filesystem = not os.path.exists(
1333
      os.path.join(toplevel_build, 'BUILD.NINJA'))
1334

    
1335
  # Put build-time support tools in out/{config_name}.
1336
  gyp.common.CopyTool(flavor, toplevel_build)
1337

    
1338
  # Grab make settings for CC/CXX.
1339
  # The rules are
1340
  # - The priority from low to high is gcc/g++, the 'make_global_settings' in
1341
  #   gyp, the environment variable.
1342
  # - If there is no 'make_global_settings' for CC.host/CXX.host or
1343
  #   'CC_host'/'CXX_host' enviroment variable, cc_host/cxx_host should be set
1344
  #   to cc/cxx.
1345
  if flavor == 'win':
1346
    cc = 'cl.exe'
1347
    cxx = 'cl.exe'
1348
    ld = 'link.exe'
1349
    gyp.msvs_emulation.GenerateEnvironmentFiles(
1350
        toplevel_build, generator_flags, OpenOutput)
1351
    ld_host = '$ld'
1352
  else:
1353
    cc = 'gcc'
1354
    cxx = 'g++'
1355
    ld = '$cxx'
1356
    ld_host = '$cxx_host'
1357

    
1358
  cc_host = None
1359
  cxx_host = None
1360
  cc_host_global_setting = None
1361
  cxx_host_global_setting = None
1362

    
1363
  build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
1364
  make_global_settings = data[build_file].get('make_global_settings', [])
1365
  build_to_root = InvertRelativePath(build_dir)
1366
  for key, value in make_global_settings:
1367
    if key == 'CC':
1368
      cc = os.path.join(build_to_root, value)
1369
    if key == 'CXX':
1370
      cxx = os.path.join(build_to_root, value)
1371
    if key == 'LD':
1372
      ld = os.path.join(build_to_root, value)
1373
    if key == 'CC.host':
1374
      cc_host = os.path.join(build_to_root, value)
1375
      cc_host_global_setting = value
1376
    if key == 'CXX.host':
1377
      cxx_host = os.path.join(build_to_root, value)
1378
      cxx_host_global_setting = value
1379
    if key == 'LD.host':
1380
      ld_host = os.path.join(build_to_root, value)
1381

    
1382
  flock = 'flock'
1383
  if flavor == 'mac':
1384
    flock = './gyp-mac-tool flock'
1385
  cc = GetEnvironFallback(['CC_target', 'CC'], cc)
1386
  master_ninja.variable('cc', cc)
1387
  cxx = GetEnvironFallback(['CXX_target', 'CXX'], cxx)
1388
  master_ninja.variable('cxx', cxx)
1389
  ld = GetEnvironFallback(['LD_target', 'LD'], ld)
1390

    
1391
  if not cc_host:
1392
    cc_host = cc
1393
  if not cxx_host:
1394
    cxx_host = cxx
1395

    
1396
  if flavor == 'win':
1397
    master_ninja.variable('ld', ld)
1398
    master_ninja.variable('idl', 'midl.exe')
1399
    master_ninja.variable('ar', 'lib.exe')
1400
    master_ninja.variable('rc', 'rc.exe')
1401
    master_ninja.variable('asm', 'ml.exe')
1402
    master_ninja.variable('mt', 'mt.exe')
1403
    master_ninja.variable('use_dep_database', '1')
1404
  else:
1405
    master_ninja.variable('ld', flock + ' linker.lock ' + ld)
1406
    master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], 'ar'))
1407

    
1408
  master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], 'ar'))
1409
  cc_host = GetEnvironFallback(['CC_host'], cc_host)
1410
  cxx_host = GetEnvironFallback(['CXX_host'], cxx_host)
1411
  ld_host = GetEnvironFallback(['LD_host'], ld_host)
1412

    
1413
  # The environment variable could be used in 'make_global_settings', like
1414
  # ['CC.host', '$(CC)'] or ['CXX.host', '$(CXX)'], transform them here.
1415
  if '$(CC)' in cc_host and cc_host_global_setting:
1416
    cc_host = cc_host_global_setting.replace('$(CC)', cc)
1417
  if '$(CXX)' in cxx_host and cxx_host_global_setting:
1418
    cxx_host = cxx_host_global_setting.replace('$(CXX)', cxx)
1419
  master_ninja.variable('cc_host', cc_host)
1420
  master_ninja.variable('cxx_host', cxx_host)
1421
  if flavor == 'win':
1422
    master_ninja.variable('ld_host', ld_host)
1423
  else:
1424
    master_ninja.variable('ld_host', flock + ' linker.lock ' + ld_host)
1425

    
1426
  master_ninja.newline()
1427

    
1428
  if flavor != 'win':
1429
    master_ninja.rule(
1430
      'cc',
1431
      description='CC $out',
1432
      command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_c '
1433
              '$cflags_pch_c -c $in -o $out'),
1434
      depfile='$out.d')
1435
    master_ninja.rule(
1436
      'cc_s',
1437
      description='CC $out',
1438
      command=('$cc $defines $includes $cflags $cflags_c '
1439
              '$cflags_pch_c -c $in -o $out'))
1440
    master_ninja.rule(
1441
      'cxx',
1442
      description='CXX $out',
1443
      command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_cc '
1444
              '$cflags_pch_cc -c $in -o $out'),
1445
      depfile='$out.d')
1446
  else:
1447
    # Template for compile commands mostly shared between compiling files
1448
    # and generating PCH. In the case of PCH, the "output" is specified by /Fp
1449
    # rather than /Fo (for object files), but we still need to specify an /Fo
1450
    # when compiling PCH.
1451
    cc_template = ('ninja -t msvc -r . -o $out -e $arch '
1452
                   '-- '
1453
                   '$cc /nologo /showIncludes /FC '
1454
                   '@$out.rsp '
1455
                   '$cflags_pch_c /c $in %(outspec)s /Fd$pdbname ')
1456
    cxx_template = ('ninja -t msvc -r . -o $out -e $arch '
1457
                    '-- '
1458
                    '$cxx /nologo /showIncludes /FC '
1459
                    '@$out.rsp '
1460
                    '$cflags_pch_cc /c $in %(outspec)s $pchobj /Fd$pdbname ')
1461
    master_ninja.rule(
1462
      'cc',
1463
      description='CC $out',
1464
      command=cc_template % {'outspec': '/Fo$out'},
1465
      depfile='$out.d',
1466
      rspfile='$out.rsp',
1467
      rspfile_content='$defines $includes $cflags $cflags_c')
1468
    master_ninja.rule(
1469
      'cc_pch',
1470
      description='CC PCH $out',
1471
      command=cc_template % {'outspec': '/Fp$out /Fo$out.obj'},
1472
      depfile='$out.d',
1473
      rspfile='$out.rsp',
1474
      rspfile_content='$defines $includes $cflags $cflags_c')
1475
    master_ninja.rule(
1476
      'cxx',
1477
      description='CXX $out',
1478
      command=cxx_template % {'outspec': '/Fo$out'},
1479
      depfile='$out.d',
1480
      rspfile='$out.rsp',
1481
      rspfile_content='$defines $includes $cflags $cflags_cc')
1482
    master_ninja.rule(
1483
      'cxx_pch',
1484
      description='CXX PCH $out',
1485
      command=cxx_template % {'outspec': '/Fp$out /Fo$out.obj'},
1486
      depfile='$out.d',
1487
      rspfile='$out.rsp',
1488
      rspfile_content='$defines $includes $cflags $cflags_cc')
1489
    master_ninja.rule(
1490
      'idl',
1491
      description='IDL $in',
1492
      command=('%s gyp-win-tool midl-wrapper $arch $outdir '
1493
               '$tlb $h $dlldata $iid $proxy $in '
1494
               '$idlflags' % sys.executable))
1495
    master_ninja.rule(
1496
      'rc',
1497
      description='RC $in',
1498
      # Note: $in must be last otherwise rc.exe complains.
1499
      command=('%s gyp-win-tool rc-wrapper '
1500
               '$arch $rc $defines $includes $rcflags /fo$out $in' %
1501
               sys.executable))
1502
    master_ninja.rule(
1503
      'asm',
1504
      description='ASM $in',
1505
      command=('%s gyp-win-tool asm-wrapper '
1506
               '$arch $asm $defines $includes /c /Fo $out $in' %
1507
               sys.executable))
1508

    
1509
  if flavor != 'mac' and flavor != 'win':
1510
    master_ninja.rule(
1511
      'alink',
1512
      description='AR $out',
1513
      command='rm -f $out && $ar rcs $out $in')
1514
    master_ninja.rule(
1515
      'alink_thin',
1516
      description='AR $out',
1517
      command='rm -f $out && $ar rcsT $out $in')
1518

    
1519
    # This allows targets that only need to depend on $lib's API to declare an
1520
    # order-only dependency on $lib.TOC and avoid relinking such downstream
1521
    # dependencies when $lib changes only in non-public ways.
1522
    # The resulting string leaves an uninterpolated %{suffix} which
1523
    # is used in the final substitution below.
1524
    mtime_preserving_solink_base = (
1525
        'if [ ! -e $lib -o ! -e ${lib}.TOC ]; then '
1526
        '%(solink)s && %(extract_toc)s > ${lib}.TOC; else '
1527
        '%(solink)s && %(extract_toc)s > ${lib}.tmp && '
1528
        'if ! cmp -s ${lib}.tmp ${lib}.TOC; then mv ${lib}.tmp ${lib}.TOC ; '
1529
        'fi; fi'
1530
        % { 'solink':
1531
              '$ld -shared $ldflags -o $lib -Wl,-soname=$soname %(suffix)s',
1532
            'extract_toc':
1533
              ('{ readelf -d ${lib} | grep SONAME ; '
1534
               'nm -gD -f p ${lib} | cut -f1-2 -d\' \'; }')})
1535

    
1536
    master_ninja.rule(
1537
      'solink',
1538
      description='SOLINK $lib',
1539
      restat=True,
1540
      command=(mtime_preserving_solink_base % {
1541
          'suffix': '-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive '
1542
          '$libs'}))
1543
    master_ninja.rule(
1544
      'solink_module',
1545
      description='SOLINK(module) $lib',
1546
      restat=True,
1547
      command=(mtime_preserving_solink_base % {
1548
          'suffix': '-Wl,--start-group $in $solibs -Wl,--end-group $libs'}))
1549
    master_ninja.rule(
1550
      'link',
1551
      description='LINK $out',
1552
      command=('$ld $ldflags -o $out -Wl,-rpath=\$$ORIGIN/lib '
1553
               '-Wl,--start-group $in $solibs -Wl,--end-group $libs'))
1554
  elif flavor == 'win':
1555
    master_ninja.rule(
1556
        'alink',
1557
        description='LIB $out',
1558
        command=('%s gyp-win-tool link-wrapper $arch '
1559
                 '$ar /nologo /ignore:4221 /OUT:$out @$out.rsp' %
1560
                 sys.executable),
1561
        rspfile='$out.rsp',
1562
        rspfile_content='$in_newline $libflags')
1563
    dlldesc = 'LINK(DLL) $dll'
1564
    dllcmd = ('%s gyp-win-tool link-wrapper $arch '
1565
              '$ld /nologo $implibflag /DLL /OUT:$dll '
1566
              '/PDB:$dll.pdb @$dll.rsp' % sys.executable)
1567
    dllcmd += (' && %s gyp-win-tool manifest-wrapper $arch '
1568
               '$mt -nologo -manifest $manifests -out:$dll.manifest' %
1569
               sys.executable)
1570
    master_ninja.rule('solink', description=dlldesc, command=dllcmd,
1571
                      rspfile='$dll.rsp',
1572
                      rspfile_content='$libs $in_newline $ldflags',
1573
                      restat=True)
1574
    master_ninja.rule('solink_module', description=dlldesc, command=dllcmd,
1575
                      rspfile='$dll.rsp',
1576
                      rspfile_content='$libs $in_newline $ldflags',
1577
                      restat=True)
1578
    # Note that ldflags goes at the end so that it has the option of
1579
    # overriding default settings earlier in the command line.
1580
    master_ninja.rule(
1581
        'link',
1582
        description='LINK $out',
1583
        command=('%s gyp-win-tool link-wrapper $arch '
1584
                 '$ld /nologo /OUT:$out /PDB:$out.pdb @$out.rsp && '
1585
                 '%s gyp-win-tool manifest-wrapper $arch '
1586
                 '$mt -nologo -manifest $manifests -out:$out.manifest' %
1587
                 (sys.executable, sys.executable)),
1588
        rspfile='$out.rsp',
1589
        rspfile_content='$in_newline $libs $ldflags')
1590
  else:
1591
    master_ninja.rule(
1592
      'objc',
1593
      description='OBJC $out',
1594
      command=('$cc -MMD -MF $out.d $defines $includes $cflags $cflags_objc '
1595
               '$cflags_pch_objc -c $in -o $out'),
1596
      depfile='$out.d')
1597
    master_ninja.rule(
1598
      'objcxx',
1599
      description='OBJCXX $out',
1600
      command=('$cxx -MMD -MF $out.d $defines $includes $cflags $cflags_objcc '
1601
               '$cflags_pch_objcc -c $in -o $out'),
1602
      depfile='$out.d')
1603
    master_ninja.rule(
1604
      'alink',
1605
      description='LIBTOOL-STATIC $out, POSTBUILDS',
1606
      command='rm -f $out && '
1607
              './gyp-mac-tool filter-libtool libtool $libtool_flags '
1608
              '-static -o $out $in'
1609
              '$postbuilds')
1610

    
1611
    # Record the public interface of $lib in $lib.TOC. See the corresponding
1612
    # comment in the posix section above for details.
1613
    mtime_preserving_solink_base = (
1614
        'if [ ! -e $lib -o ! -e ${lib}.TOC ] || '
1615
             # Always force dependent targets to relink if this library
1616
             # reexports something. Handling this correctly would require
1617
             # recursive TOC dumping but this is rare in practice, so punt.
1618
             'otool -l $lib | grep -q LC_REEXPORT_DYLIB ; then '
1619
          '%(solink)s && %(extract_toc)s > ${lib}.TOC; '
1620
        'else '
1621
          '%(solink)s && %(extract_toc)s > ${lib}.tmp && '
1622
          'if ! cmp -s ${lib}.tmp ${lib}.TOC; then '
1623
            'mv ${lib}.tmp ${lib}.TOC ; '
1624
          'fi; '
1625
        'fi'
1626
        % { 'solink': '$ld -shared $ldflags -o $lib %(suffix)s',
1627
            'extract_toc':
1628
              '{ otool -l $lib | grep LC_ID_DYLIB -A 5; '
1629
              'nm -gP $lib | cut -f1-2 -d\' \' | grep -v U$$; true; }'})
1630

    
1631
    # TODO(thakis): The solink_module rule is likely wrong. Xcode seems to pass
1632
    # -bundle -single_module here (for osmesa.so).
1633
    master_ninja.rule(
1634
      'solink',
1635
      description='SOLINK $lib, POSTBUILDS',
1636
      restat=True,
1637
      command=(mtime_preserving_solink_base % {
1638
          'suffix': '$in $solibs $libs$postbuilds'}))
1639
    master_ninja.rule(
1640
      'solink_module',
1641
      description='SOLINK(module) $lib, POSTBUILDS',
1642
      restat=True,
1643
      command=(mtime_preserving_solink_base % {
1644
          'suffix': '$in $solibs $libs$postbuilds'}))
1645

    
1646
    master_ninja.rule(
1647
      'link',
1648
      description='LINK $out, POSTBUILDS',
1649
      command=('$ld $ldflags -o $out '
1650
               '$in $solibs $libs$postbuilds'))
1651
    master_ninja.rule(
1652
      'infoplist',
1653
      description='INFOPLIST $out',
1654
      command=('$cc -E -P -Wno-trigraphs -x c $defines $in -o $out && '
1655
               'plutil -convert xml1 $out $out'))
1656
    master_ninja.rule(
1657
      'mac_tool',
1658
      description='MACTOOL $mactool_cmd $in',
1659
      command='$env ./gyp-mac-tool $mactool_cmd $in $out')
1660
    master_ninja.rule(
1661
      'package_framework',
1662
      description='PACKAGE FRAMEWORK $out, POSTBUILDS',
1663
      command='./gyp-mac-tool package-framework $out $version$postbuilds '
1664
              '&& touch $out')
1665
  if flavor == 'win':
1666
    master_ninja.rule(
1667
      'stamp',
1668
      description='STAMP $out',
1669
      command='%s gyp-win-tool stamp $out' % sys.executable)
1670
    master_ninja.rule(
1671
      'copy',
1672
      description='COPY $in $out',
1673
      command='%s gyp-win-tool recursive-mirror $in $out' % sys.executable)
1674
  else:
1675
    master_ninja.rule(
1676
      'stamp',
1677
      description='STAMP $out',
1678
      command='${postbuilds}touch $out')
1679
    master_ninja.rule(
1680
      'copy',
1681
      description='COPY $in $out',
1682
      command='rm -rf $out && cp -af $in $out')
1683
  master_ninja.newline()
1684

    
1685
  all_targets = set()
1686
  for build_file in params['build_files']:
1687
    for target in gyp.common.AllTargets(target_list,
1688
                                        target_dicts,
1689
                                        os.path.normpath(build_file)):
1690
      all_targets.add(target)
1691
  all_outputs = set()
1692

    
1693
  # target_outputs is a map from qualified target name to a Target object.
1694
  target_outputs = {}
1695
  # target_short_names is a map from target short name to a list of Target
1696
  # objects.
1697
  target_short_names = {}
1698
  for qualified_target in target_list:
1699
    # qualified_target is like: third_party/icu/icu.gyp:icui18n#target
1700
    build_file, name, toolset = \
1701
        gyp.common.ParseQualifiedTarget(qualified_target)
1702

    
1703
    this_make_global_settings = data[build_file].get('make_global_settings', [])
1704
    assert make_global_settings == this_make_global_settings, (
1705
        "make_global_settings needs to be the same for all targets.")
1706

    
1707
    spec = target_dicts[qualified_target]
1708
    if flavor == 'mac':
1709
      gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec)
1710

    
1711
    build_file = gyp.common.RelativePath(build_file, options.toplevel_dir)
1712

    
1713
    base_path = os.path.dirname(build_file)
1714
    obj = 'obj'
1715
    if toolset != 'target':
1716
      obj += '.' + toolset
1717
    output_file = os.path.join(obj, base_path, name + '.ninja')
1718

    
1719
    abs_build_dir = os.path.abspath(toplevel_build)
1720
    writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir,
1721
                         OpenOutput(os.path.join(toplevel_build, output_file)),
1722
                         flavor, abs_build_dir=abs_build_dir)
1723
    master_ninja.subninja(output_file)
1724

    
1725
    target = writer.WriteSpec(
1726
        spec, config_name, generator_flags, case_sensitive_filesystem)
1727
    if target:
1728
      if name != target.FinalOutput() and spec['toolset'] == 'target':
1729
        target_short_names.setdefault(name, []).append(target)
1730
      target_outputs[qualified_target] = target
1731
      if qualified_target in all_targets:
1732
        all_outputs.add(target.FinalOutput())
1733

    
1734
  if target_short_names:
1735
    # Write a short name to build this target.  This benefits both the
1736
    # "build chrome" case as well as the gyp tests, which expect to be
1737
    # able to run actions and build libraries by their short name.
1738
    master_ninja.newline()
1739
    master_ninja.comment('Short names for targets.')
1740
    for short_name in target_short_names:
1741
      master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in
1742
                                               target_short_names[short_name]])
1743

    
1744
  if all_outputs:
1745
    master_ninja.newline()
1746
    master_ninja.build('all', 'phony', list(all_outputs))
1747
    master_ninja.default(generator_flags.get('default_target', 'all'))
1748

    
1749

    
1750
def PerformBuild(data, configurations, params):
1751
  options = params['options']
1752
  for config in configurations:
1753
    builddir = os.path.join(options.toplevel_dir, 'out', config)
1754
    arguments = ['ninja', '-C', builddir]
1755
    print 'Building [%s]: %s' % (config, arguments)
1756
    subprocess.check_call(arguments)
1757

    
1758

    
1759
def CallGenerateOutputForConfig(arglist):
1760
  # Ignore the interrupt signal so that the parent process catches it and
1761
  # kills all multiprocessing children.
1762
  signal.signal(signal.SIGINT, signal.SIG_IGN)
1763

    
1764
  (target_list, target_dicts, data, params, config_name) = arglist
1765
  GenerateOutputForConfig(target_list, target_dicts, data, params, config_name)
1766

    
1767

    
1768
def GenerateOutput(target_list, target_dicts, data, params):
1769
  user_config = params.get('generator_flags', {}).get('config', None)
1770
  if user_config:
1771
    GenerateOutputForConfig(target_list, target_dicts, data, params,
1772
                            user_config)
1773
  else:
1774
    config_names = target_dicts[target_list[0]]['configurations'].keys()
1775
    if params['parallel']:
1776
      try:
1777
        pool = multiprocessing.Pool(len(config_names))
1778
        arglists = []
1779
        for config_name in config_names:
1780
          arglists.append(
1781
              (target_list, target_dicts, data, params, config_name))
1782
          pool.map(CallGenerateOutputForConfig, arglists)
1783
      except KeyboardInterrupt, e:
1784
        pool.terminate()
1785
        raise e
1786
    else:
1787
      for config_name in config_names:
1788
        GenerateOutputForConfig(target_list, target_dicts, data, params,
1789
                                config_name)