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