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