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