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