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