• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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# Notes:
6#
7# This is all roughly based on the Makefile system used by the Linux
8# kernel, but is a non-recursive make -- we put the entire dependency
9# graph in front of make and let it figure it out.
10#
11# The code below generates a separate .mk file for each target, but
12# all are sourced by the top-level Makefile.  This means that all
13# variables in .mk-files clobber one another.  Be careful to use :=
14# where appropriate for immediate evaluation, and similarly to watch
15# that you're not relying on a variable value to last between different
16# .mk files.
17#
18# TODOs:
19#
20# Global settings and utility functions are currently stuffed in the
21# toplevel Makefile.  It may make sense to generate some .mk files on
22# the side to keep the files readable.
23
24
25import os
26import re
27import subprocess
28import gyp
29import gyp.common
30import gyp.xcode_emulation
31from gyp.common import GetEnvironFallback
32
33import hashlib
34
35generator_default_variables = {
36    "EXECUTABLE_PREFIX": "",
37    "EXECUTABLE_SUFFIX": "",
38    "STATIC_LIB_PREFIX": "lib",
39    "SHARED_LIB_PREFIX": "lib",
40    "STATIC_LIB_SUFFIX": ".a",
41    "INTERMEDIATE_DIR": "$(obj).$(TOOLSET)/$(TARGET)/geni",
42    "SHARED_INTERMEDIATE_DIR": "$(obj)/gen",
43    "PRODUCT_DIR": "$(builddir)",
44    "RULE_INPUT_ROOT": "%(INPUT_ROOT)s",  # This gets expanded by Python.
45    "RULE_INPUT_DIRNAME": "%(INPUT_DIRNAME)s",  # This gets expanded by Python.
46    "RULE_INPUT_PATH": "$(abspath $<)",
47    "RULE_INPUT_EXT": "$(suffix $<)",
48    "RULE_INPUT_NAME": "$(notdir $<)",
49    "CONFIGURATION_NAME": "$(BUILDTYPE)",
50}
51
52# Make supports multiple toolsets
53generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
54
55# Request sorted dependencies in the order from dependents to dependencies.
56generator_wants_sorted_dependencies = False
57
58# Placates pylint.
59generator_additional_non_configuration_keys = []
60generator_additional_path_sections = []
61generator_extra_sources_for_rules = []
62generator_filelist_paths = None
63
64
65def CalculateVariables(default_variables, params):
66    """Calculate additional variables for use in the build (called by gyp)."""
67    flavor = gyp.common.GetFlavor(params)
68    if flavor == "mac":
69        default_variables.setdefault("OS", "mac")
70        default_variables.setdefault("SHARED_LIB_SUFFIX", ".dylib")
71        default_variables.setdefault(
72            "SHARED_LIB_DIR", generator_default_variables["PRODUCT_DIR"]
73        )
74        default_variables.setdefault(
75            "LIB_DIR", generator_default_variables["PRODUCT_DIR"]
76        )
77
78        # Copy additional generator configuration data from Xcode, which is shared
79        # by the Mac Make generator.
80        import gyp.generator.xcode as xcode_generator
81
82        global generator_additional_non_configuration_keys
83        generator_additional_non_configuration_keys = getattr(
84            xcode_generator, "generator_additional_non_configuration_keys", []
85        )
86        global generator_additional_path_sections
87        generator_additional_path_sections = getattr(
88            xcode_generator, "generator_additional_path_sections", []
89        )
90        global generator_extra_sources_for_rules
91        generator_extra_sources_for_rules = getattr(
92            xcode_generator, "generator_extra_sources_for_rules", []
93        )
94        COMPILABLE_EXTENSIONS.update({".m": "objc", ".mm": "objcxx"})
95    else:
96        operating_system = flavor
97        if flavor == "android":
98            operating_system = "linux"  # Keep this legacy behavior for now.
99        default_variables.setdefault("OS", operating_system)
100        if flavor == "aix":
101            default_variables.setdefault("SHARED_LIB_SUFFIX", ".a")
102        elif flavor == "zos":
103            default_variables.setdefault("SHARED_LIB_SUFFIX", ".x")
104        else:
105            default_variables.setdefault("SHARED_LIB_SUFFIX", ".so")
106        default_variables.setdefault("SHARED_LIB_DIR", "$(builddir)/lib.$(TOOLSET)")
107        default_variables.setdefault("LIB_DIR", "$(obj).$(TOOLSET)")
108
109
110def CalculateGeneratorInputInfo(params):
111    """Calculate the generator specific info that gets fed to input (called by
112    gyp)."""
113    generator_flags = params.get("generator_flags", {})
114    android_ndk_version = generator_flags.get("android_ndk_version", None)
115    # Android NDK requires a strict link order.
116    if android_ndk_version:
117        global generator_wants_sorted_dependencies
118        generator_wants_sorted_dependencies = True
119
120    output_dir = params["options"].generator_output or params["options"].toplevel_dir
121    builddir_name = generator_flags.get("output_dir", "out")
122    qualified_out_dir = os.path.normpath(
123        os.path.join(output_dir, builddir_name, "gypfiles")
124    )
125
126    global generator_filelist_paths
127    generator_filelist_paths = {
128        "toplevel": params["options"].toplevel_dir,
129        "qualified_out_dir": qualified_out_dir,
130    }
131
132
133# The .d checking code below uses these functions:
134# wildcard, sort, foreach, shell, wordlist
135# wildcard can handle spaces, the rest can't.
136# Since I could find no way to make foreach work with spaces in filenames
137# correctly, the .d files have spaces replaced with another character. The .d
138# file for
139#     Chromium\ Framework.framework/foo
140# is for example
141#     out/Release/.deps/out/Release/Chromium?Framework.framework/foo
142# This is the replacement character.
143SPACE_REPLACEMENT = "?"
144
145
146LINK_COMMANDS_LINUX = """\
147quiet_cmd_alink = AR($(TOOLSET)) $@
148cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^)
149
150quiet_cmd_alink_thin = AR($(TOOLSET)) $@
151cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crsT $@ $(filter %.o,$^)
152
153# Due to circular dependencies between libraries :(, we wrap the
154# special "figure out circular dependencies" flags around the entire
155# input list during linking.
156quiet_cmd_link = LINK($(TOOLSET)) $@
157cmd_link = $(LINK.$(TOOLSET)) -o $@ $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,--start-group $(LD_INPUTS) $(LIBS) -Wl,--end-group
158
159# Note: this does not handle spaces in paths
160define xargs
161  $(1) $(word 1,$(2))
162$(if $(word 2,$(2)),$(call xargs,$(1),$(wordlist 2,$(words $(2)),$(2))))
163endef
164
165define write-to-file
166  @: >$(1)
167$(call xargs,@printf "%s\\n" >>$(1),$(2))
168endef
169
170OBJ_FILE_LIST := ar-file-list
171
172define create_archive
173        rm -f $(1) $(1).$(OBJ_FILE_LIST); mkdir -p `dirname $(1)`
174        $(call write-to-file,$(1).$(OBJ_FILE_LIST),$(filter %.o,$(2)))
175        $(AR.$(TOOLSET)) crs $(1) @$(1).$(OBJ_FILE_LIST)
176endef
177
178define create_thin_archive
179        rm -f $(1) $(OBJ_FILE_LIST); mkdir -p `dirname $(1)`
180        $(call write-to-file,$(1).$(OBJ_FILE_LIST),$(filter %.o,$(2)))
181        $(AR.$(TOOLSET)) crsT $(1) @$(1).$(OBJ_FILE_LIST)
182endef
183
184# We support two kinds of shared objects (.so):
185# 1) shared_library, which is just bundling together many dependent libraries
186# into a link line.
187# 2) loadable_module, which is generating a module intended for dlopen().
188#
189# They differ only slightly:
190# In the former case, we want to package all dependent code into the .so.
191# In the latter case, we want to package just the API exposed by the
192# outermost module.
193# This means shared_library uses --whole-archive, while loadable_module doesn't.
194# (Note that --whole-archive is incompatible with the --start-group used in
195# normal linking.)
196
197# Other shared-object link notes:
198# - Set SONAME to the library filename so our binaries don't reference
199# the local, absolute paths used on the link command-line.
200quiet_cmd_solink = SOLINK($(TOOLSET)) $@
201cmd_solink = $(LINK.$(TOOLSET)) -o $@ -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -Wl,--whole-archive $(LD_INPUTS) -Wl,--no-whole-archive $(LIBS)
202
203quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
204cmd_solink_module = $(LINK.$(TOOLSET)) -o $@ -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -Wl,--start-group $(filter-out FORCE_DO_CMD, $^) -Wl,--end-group $(LIBS)
205"""  # noqa: E501
206
207LINK_COMMANDS_MAC = """\
208quiet_cmd_alink = LIBTOOL-STATIC $@
209cmd_alink = rm -f $@ && ./gyp-mac-tool filter-libtool libtool $(GYP_LIBTOOLFLAGS) -static -o $@ $(filter %.o,$^)
210
211quiet_cmd_link = LINK($(TOOLSET)) $@
212cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS)
213
214quiet_cmd_solink = SOLINK($(TOOLSET)) $@
215cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS)
216
217quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
218cmd_solink_module = $(LINK.$(TOOLSET)) -bundle $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
219"""  # noqa: E501
220
221LINK_COMMANDS_ANDROID = """\
222quiet_cmd_alink = AR($(TOOLSET)) $@
223cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^)
224
225quiet_cmd_alink_thin = AR($(TOOLSET)) $@
226cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crsT $@ $(filter %.o,$^)
227
228# Note: this does not handle spaces in paths
229define xargs
230  $(1) $(word 1,$(2))
231$(if $(word 2,$(2)),$(call xargs,$(1),$(wordlist 2,$(words $(2)),$(2))))
232endef
233
234define write-to-file
235  @: >$(1)
236$(call xargs,@printf "%s\\n" >>$(1),$(2))
237endef
238
239OBJ_FILE_LIST := ar-file-list
240
241define create_archive
242        rm -f $(1) $(1).$(OBJ_FILE_LIST); mkdir -p `dirname $(1)`
243        $(call write-to-file,$(1).$(OBJ_FILE_LIST),$(filter %.o,$(2)))
244        $(AR.$(TOOLSET)) crs $(1) @$(1).$(OBJ_FILE_LIST)
245endef
246
247define create_thin_archive
248        rm -f $(1) $(OBJ_FILE_LIST); mkdir -p `dirname $(1)`
249        $(call write-to-file,$(1).$(OBJ_FILE_LIST),$(filter %.o,$(2)))
250        $(AR.$(TOOLSET)) crsT $(1) @$(1).$(OBJ_FILE_LIST)
251endef
252
253# Due to circular dependencies between libraries :(, we wrap the
254# special "figure out circular dependencies" flags around the entire
255# input list during linking.
256quiet_cmd_link = LINK($(TOOLSET)) $@
257quiet_cmd_link_host = LINK($(TOOLSET)) $@
258cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ -Wl,--start-group $(LD_INPUTS) -Wl,--end-group $(LIBS)
259cmd_link_host = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ -Wl,--start-group $(LD_INPUTS) -Wl,--end-group $(LIBS)
260
261# Other shared-object link notes:
262# - Set SONAME to the library filename so our binaries don't reference
263# the local, absolute paths used on the link command-line.
264quiet_cmd_solink = SOLINK($(TOOLSET)) $@
265cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ -Wl,--whole-archive $(LD_INPUTS) -Wl,--no-whole-archive $(LIBS)
266
267quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
268cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ -Wl,--start-group $(filter-out FORCE_DO_CMD, $^) -Wl,--end-group $(LIBS)
269quiet_cmd_solink_module_host = SOLINK_MODULE($(TOOLSET)) $@
270cmd_solink_module_host = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
271"""  # noqa: E501
272
273
274LINK_COMMANDS_AIX = """\
275quiet_cmd_alink = AR($(TOOLSET)) $@
276cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) -X32_64 crs $@ $(filter %.o,$^)
277
278quiet_cmd_alink_thin = AR($(TOOLSET)) $@
279cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) -X32_64 crs $@ $(filter %.o,$^)
280
281quiet_cmd_link = LINK($(TOOLSET)) $@
282cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS)
283
284quiet_cmd_solink = SOLINK($(TOOLSET)) $@
285cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS)
286
287quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
288cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
289"""  # noqa: E501
290
291
292LINK_COMMANDS_OS400 = """\
293quiet_cmd_alink = AR($(TOOLSET)) $@
294cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) -X64 crs $@ $(filter %.o,$^)
295
296quiet_cmd_alink_thin = AR($(TOOLSET)) $@
297cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) -X64 crs $@ $(filter %.o,$^)
298
299quiet_cmd_link = LINK($(TOOLSET)) $@
300cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS)
301
302quiet_cmd_solink = SOLINK($(TOOLSET)) $@
303cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS)
304
305quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
306cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
307"""  # noqa: E501
308
309
310LINK_COMMANDS_OS390 = """\
311quiet_cmd_alink = AR($(TOOLSET)) $@
312cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^)
313
314quiet_cmd_alink_thin = AR($(TOOLSET)) $@
315cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crsT $@ $(filter %.o,$^)
316
317quiet_cmd_link = LINK($(TOOLSET)) $@
318cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(LD_INPUTS) $(LIBS)
319
320quiet_cmd_solink = SOLINK($(TOOLSET)) $@
321cmd_solink = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,DLL -o $(patsubst %.x,%.so,$@) $(LD_INPUTS) $(LIBS) && if [ -f $(notdir $@) ]; then /bin/cp $(notdir $@) $@; else true; fi
322
323quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
324cmd_solink_module = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
325"""  # noqa: E501
326
327
328# Header of toplevel Makefile.
329# This should go into the build tree, but it's easier to keep it here for now.
330SHARED_HEADER = (
331    """\
332# We borrow heavily from the kernel build setup, though we are simpler since
333# we don't have Kconfig tweaking settings on us.
334
335# The implicit make rules have it looking for RCS files, among other things.
336# We instead explicitly write all the rules we care about.
337# It's even quicker (saves ~200ms) to pass -r on the command line.
338MAKEFLAGS=-r
339
340# The source directory tree.
341srcdir := %(srcdir)s
342abs_srcdir := $(abspath $(srcdir))
343
344# The name of the builddir.
345builddir_name ?= %(builddir)s
346
347# The V=1 flag on command line makes us verbosely print command lines.
348ifdef V
349  quiet=
350else
351  quiet=quiet_
352endif
353
354# Specify BUILDTYPE=Release on the command line for a release build.
355BUILDTYPE ?= %(default_configuration)s
356
357# Directory all our build output goes into.
358# Note that this must be two directories beneath src/ for unit tests to pass,
359# as they reach into the src/ directory for data with relative paths.
360builddir ?= $(builddir_name)/$(BUILDTYPE)
361abs_builddir := $(abspath $(builddir))
362depsdir := $(builddir)/.deps
363
364# Object output directory.
365obj := $(builddir)/obj
366abs_obj := $(abspath $(obj))
367
368# We build up a list of every single one of the targets so we can slurp in the
369# generated dependency rule Makefiles in one pass.
370all_deps :=
371
372%(make_global_settings)s
373
374CC.target ?= %(CC.target)s
375CFLAGS.target ?= $(CPPFLAGS) $(CFLAGS)
376CXX.target ?= %(CXX.target)s
377CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS)
378LINK.target ?= %(LINK.target)s
379LDFLAGS.target ?= $(LDFLAGS)
380AR.target ?= $(AR)
381
382# C++ apps need to be linked with g++.
383LINK ?= $(CXX.target)
384
385# TODO(evan): move all cross-compilation logic to gyp-time so we don't need
386# to replicate this environment fallback in make as well.
387CC.host ?= %(CC.host)s
388CFLAGS.host ?= $(CPPFLAGS_host) $(CFLAGS_host)
389CXX.host ?= %(CXX.host)s
390CXXFLAGS.host ?= $(CPPFLAGS_host) $(CXXFLAGS_host)
391LINK.host ?= %(LINK.host)s
392LDFLAGS.host ?= $(LDFLAGS_host)
393AR.host ?= %(AR.host)s
394
395# Define a dir function that can handle spaces.
396# http://www.gnu.org/software/make/manual/make.html#Syntax-of-Functions
397# "leading spaces cannot appear in the text of the first argument as written.
398# These characters can be put into the argument value by variable substitution."
399empty :=
400space := $(empty) $(empty)
401
402# http://stackoverflow.com/questions/1189781/using-make-dir-or-notdir-on-a-path-with-spaces
403replace_spaces = $(subst $(space),"""
404    + SPACE_REPLACEMENT
405    + """,$1)
406unreplace_spaces = $(subst """
407    + SPACE_REPLACEMENT
408    + """,$(space),$1)
409dirx = $(call unreplace_spaces,$(dir $(call replace_spaces,$1)))
410
411# Flags to make gcc output dependency info.  Note that you need to be
412# careful here to use the flags that ccache and distcc can understand.
413# We write to a dep file on the side first and then rename at the end
414# so we can't end up with a broken dep file.
415depfile = $(depsdir)/$(call replace_spaces,$@).d
416DEPFLAGS = %(makedep_args)s -MF $(depfile).raw
417
418# We have to fixup the deps output in a few ways.
419# (1) the file output should mention the proper .o file.
420# ccache or distcc lose the path to the target, so we convert a rule of
421# the form:
422#   foobar.o: DEP1 DEP2
423# into
424#   path/to/foobar.o: DEP1 DEP2
425# (2) we want missing files not to cause us to fail to build.
426# We want to rewrite
427#   foobar.o: DEP1 DEP2 \\
428#               DEP3
429# to
430#   DEP1:
431#   DEP2:
432#   DEP3:
433# so if the files are missing, they're just considered phony rules.
434# We have to do some pretty insane escaping to get those backslashes
435# and dollar signs past make, the shell, and sed at the same time.
436# Doesn't work with spaces, but that's fine: .d files have spaces in
437# their names replaced with other characters."""
438    r"""
439define fixup_dep
440# The depfile may not exist if the input file didn't have any #includes.
441touch $(depfile).raw
442# Fixup path as in (1).
443sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)
444# Add extra rules as in (2).
445# We remove slashes and replace spaces with new lines;
446# remove blank lines;
447# delete the first line and append a colon to the remaining lines.
448sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\
449  grep -v '^$$'                             |\
450  sed -e 1d -e 's|$$|:|'                     \
451    >> $(depfile)
452rm $(depfile).raw
453endef
454"""
455    """
456# Command definitions:
457# - cmd_foo is the actual command to run;
458# - quiet_cmd_foo is the brief-output summary of the command.
459
460quiet_cmd_cc = CC($(TOOLSET)) $@
461cmd_cc = $(CC.$(TOOLSET)) -c -o $@ $< $(GYP_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET))
462
463quiet_cmd_cxx = CXX($(TOOLSET)) $@
464cmd_cxx = $(CXX.$(TOOLSET)) -c -o $@ $< $(GYP_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET))
465%(extra_commands)s
466quiet_cmd_touch = TOUCH $@
467cmd_touch = touch $@
468
469quiet_cmd_copy = COPY $@
470# send stderr to /dev/null to ignore messages when linking directories.
471cmd_copy = ln -f "$<" "$@" 2>/dev/null || (rm -rf "$@" && cp %(copy_archive_args)s "$<" "$@")
472
473quiet_cmd_symlink = SYMLINK $@
474cmd_symlink = ln -sf "$<" "$@"
475
476%(link_commands)s
477"""  # noqa: E501
478    r"""
479# Define an escape_quotes function to escape single quotes.
480# This allows us to handle quotes properly as long as we always use
481# use single quotes and escape_quotes.
482escape_quotes = $(subst ','\'',$(1))
483# This comment is here just to include a ' to unconfuse syntax highlighting.
484# Define an escape_vars function to escape '$' variable syntax.
485# This allows us to read/write command lines with shell variables (e.g.
486# $LD_LIBRARY_PATH), without triggering make substitution.
487escape_vars = $(subst $$,$$$$,$(1))
488# Helper that expands to a shell command to echo a string exactly as it is in
489# make. This uses printf instead of echo because printf's behaviour with respect
490# to escape sequences is more portable than echo's across different shells
491# (e.g., dash, bash).
492exact_echo = printf '%%s\n' '$(call escape_quotes,$(1))'
493"""
494    """
495# Helper to compare the command we're about to run against the command
496# we logged the last time we ran the command.  Produces an empty
497# string (false) when the commands match.
498# Tricky point: Make has no string-equality test function.
499# The kernel uses the following, but it seems like it would have false
500# positives, where one string reordered its arguments.
501#   arg_check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \\
502#                       $(filter-out $(cmd_$@), $(cmd_$(1))))
503# We instead substitute each for the empty string into the other, and
504# say they're equal if both substitutions produce the empty string.
505# .d files contain """
506    + SPACE_REPLACEMENT
507    + """ instead of spaces, take that into account.
508command_changed = $(or $(subst $(cmd_$(1)),,$(cmd_$(call replace_spaces,$@))),\\
509                       $(subst $(cmd_$(call replace_spaces,$@)),,$(cmd_$(1))))
510
511# Helper that is non-empty when a prerequisite changes.
512# Normally make does this implicitly, but we force rules to always run
513# so we can check their command lines.
514#   $? -- new prerequisites
515#   $| -- order-only dependencies
516prereq_changed = $(filter-out FORCE_DO_CMD,$(filter-out $|,$?))
517
518# Helper that executes all postbuilds until one fails.
519define do_postbuilds
520  @E=0;\\
521  for p in $(POSTBUILDS); do\\
522    eval $$p;\\
523    E=$$?;\\
524    if [ $$E -ne 0 ]; then\\
525      break;\\
526    fi;\\
527  done;\\
528  if [ $$E -ne 0 ]; then\\
529    rm -rf "$@";\\
530    exit $$E;\\
531  fi
532endef
533
534# do_cmd: run a command via the above cmd_foo names, if necessary.
535# Should always run for a given target to handle command-line changes.
536# Second argument, if non-zero, makes it do asm/C/C++ dependency munging.
537# Third argument, if non-zero, makes it do POSTBUILDS processing.
538# Note: We intentionally do NOT call dirx for depfile, since it contains """
539    + SPACE_REPLACEMENT
540    + """ for
541# spaces already and dirx strips the """
542    + SPACE_REPLACEMENT
543    + """ characters.
544define do_cmd
545$(if $(or $(command_changed),$(prereq_changed)),
546  @$(call exact_echo,  $($(quiet)cmd_$(1)))
547  @mkdir -p "$(call dirx,$@)" "$(dir $(depfile))"
548  $(if $(findstring flock,$(word %(flock_index)d,$(cmd_$1))),
549    @$(cmd_$(1))
550    @echo "  $(quiet_cmd_$(1)): Finished",
551    @$(cmd_$(1))
552  )
553  @$(call exact_echo,$(call escape_vars,cmd_$(call replace_spaces,$@) := $(cmd_$(1)))) > $(depfile)
554  @$(if $(2),$(fixup_dep))
555  $(if $(and $(3), $(POSTBUILDS)),
556    $(call do_postbuilds)
557  )
558)
559endef
560
561# Declare the "%(default_target)s" target first so it is the default,
562# even though we don't have the deps yet.
563.PHONY: %(default_target)s
564%(default_target)s:
565
566# make looks for ways to re-generate included makefiles, but in our case, we
567# don't have a direct way. Explicitly telling make that it has nothing to do
568# for them makes it go faster.
569%%.d: ;
570
571# Use FORCE_DO_CMD to force a target to run.  Should be coupled with
572# do_cmd.
573.PHONY: FORCE_DO_CMD
574FORCE_DO_CMD:
575
576"""  # noqa: E501
577)
578
579SHARED_HEADER_MAC_COMMANDS = """
580quiet_cmd_objc = CXX($(TOOLSET)) $@
581cmd_objc = $(CC.$(TOOLSET)) $(GYP_OBJCFLAGS) $(DEPFLAGS) -c -o $@ $<
582
583quiet_cmd_objcxx = CXX($(TOOLSET)) $@
584cmd_objcxx = $(CXX.$(TOOLSET)) $(GYP_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $<
585
586# Commands for precompiled header files.
587quiet_cmd_pch_c = CXX($(TOOLSET)) $@
588cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $<
589quiet_cmd_pch_cc = CXX($(TOOLSET)) $@
590cmd_pch_cc = $(CC.$(TOOLSET)) $(GYP_PCH_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $<
591quiet_cmd_pch_m = CXX($(TOOLSET)) $@
592cmd_pch_m = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCFLAGS) $(DEPFLAGS) -c -o $@ $<
593quiet_cmd_pch_mm = CXX($(TOOLSET)) $@
594cmd_pch_mm = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $<
595
596# gyp-mac-tool is written next to the root Makefile by gyp.
597# Use $(4) for the command, since $(2) and $(3) are used as flag by do_cmd
598# already.
599quiet_cmd_mac_tool = MACTOOL $(4) $<
600cmd_mac_tool = ./gyp-mac-tool $(4) $< "$@"
601
602quiet_cmd_mac_package_framework = PACKAGE FRAMEWORK $@
603cmd_mac_package_framework = ./gyp-mac-tool package-framework "$@" $(4)
604
605quiet_cmd_infoplist = INFOPLIST $@
606cmd_infoplist = $(CC.$(TOOLSET)) -E -P -Wno-trigraphs -x c $(INFOPLIST_DEFINES) "$<" -o "$@"
607"""  # noqa: E501
608
609
610def WriteRootHeaderSuffixRules(writer):
611    extensions = sorted(COMPILABLE_EXTENSIONS.keys(), key=str.lower)
612
613    writer.write("# Suffix rules, putting all outputs into $(obj).\n")
614    for ext in extensions:
615        writer.write("$(obj).$(TOOLSET)/%%.o: $(srcdir)/%%%s FORCE_DO_CMD\n" % ext)
616        writer.write("\t@$(call do_cmd,%s,1)\n" % COMPILABLE_EXTENSIONS[ext])
617
618    writer.write("\n# Try building from generated source, too.\n")
619    for ext in extensions:
620        writer.write(
621            "$(obj).$(TOOLSET)/%%.o: $(obj).$(TOOLSET)/%%%s FORCE_DO_CMD\n" % ext
622        )
623        writer.write("\t@$(call do_cmd,%s,1)\n" % COMPILABLE_EXTENSIONS[ext])
624    writer.write("\n")
625    for ext in extensions:
626        writer.write("$(obj).$(TOOLSET)/%%.o: $(obj)/%%%s FORCE_DO_CMD\n" % ext)
627        writer.write("\t@$(call do_cmd,%s,1)\n" % COMPILABLE_EXTENSIONS[ext])
628    writer.write("\n")
629
630
631SHARED_HEADER_SUFFIX_RULES_COMMENT1 = """\
632# Suffix rules, putting all outputs into $(obj).
633"""
634
635
636SHARED_HEADER_SUFFIX_RULES_COMMENT2 = """\
637# Try building from generated source, too.
638"""
639
640
641SHARED_FOOTER = """\
642# "all" is a concatenation of the "all" targets from all the included
643# sub-makefiles. This is just here to clarify.
644all:
645
646# Add in dependency-tracking rules.  $(all_deps) is the list of every single
647# target in our tree. Only consider the ones with .d (dependency) info:
648d_files := $(wildcard $(foreach f,$(all_deps),$(depsdir)/$(f).d))
649ifneq ($(d_files),)
650  include $(d_files)
651endif
652"""
653
654header = """\
655# This file is generated by gyp; do not edit.
656
657"""
658
659# Maps every compilable file extension to the do_cmd that compiles it.
660COMPILABLE_EXTENSIONS = {
661    ".c": "cc",
662    ".cc": "cxx",
663    ".cpp": "cxx",
664    ".cxx": "cxx",
665    ".s": "cc",
666    ".S": "cc",
667}
668
669
670def Compilable(filename):
671    """Return true if the file is compilable (should be in OBJS)."""
672    for res in (filename.endswith(e) for e in COMPILABLE_EXTENSIONS):
673        if res:
674            return True
675    return False
676
677
678def Linkable(filename):
679    """Return true if the file is linkable (should be on the link line)."""
680    return filename.endswith(".o")
681
682
683def Target(filename):
684    """Translate a compilable filename to its .o target."""
685    return os.path.splitext(filename)[0] + ".o"
686
687
688def EscapeShellArgument(s):
689    """Quotes an argument so that it will be interpreted literally by a POSIX
690    shell. Taken from
691    http://stackoverflow.com/questions/35817/whats-the-best-way-to-escape-ossystem-calls-in-python
692    """
693    return "'" + s.replace("'", "'\\''") + "'"
694
695
696def EscapeMakeVariableExpansion(s):
697    """Make has its own variable expansion syntax using $. We must escape it for
698    string to be interpreted literally."""
699    return s.replace("$", "$$")
700
701
702def EscapeCppDefine(s):
703    """Escapes a CPP define so that it will reach the compiler unaltered."""
704    s = EscapeShellArgument(s)
705    s = EscapeMakeVariableExpansion(s)
706    # '#' characters must be escaped even embedded in a string, else Make will
707    # treat it as the start of a comment.
708    return s.replace("#", r"\#")
709
710
711def QuoteIfNecessary(string):
712    """TODO: Should this ideally be replaced with one or more of the above
713    functions?"""
714    if '"' in string:
715        string = '"' + string.replace('"', '\\"') + '"'
716    return string
717
718
719def StringToMakefileVariable(string):
720    """Convert a string to a value that is acceptable as a make variable name."""
721    return re.sub("[^a-zA-Z0-9_]", "_", string)
722
723
724srcdir_prefix = ""
725
726
727def Sourceify(path):
728    """Convert a path to its source directory form."""
729    if "$(" in path:
730        return path
731    if os.path.isabs(path):
732        return path
733    return srcdir_prefix + path
734
735
736def QuoteSpaces(s, quote=r"\ "):
737    return s.replace(" ", quote)
738
739
740def SourceifyAndQuoteSpaces(path):
741    """Convert a path to its source directory form and quote spaces."""
742    return QuoteSpaces(Sourceify(path))
743
744
745# Map from qualified target to path to output.
746target_outputs = {}
747# Map from qualified target to any linkable output.  A subset
748# of target_outputs.  E.g. when mybinary depends on liba, we want to
749# include liba in the linker line; when otherbinary depends on
750# mybinary, we just want to build mybinary first.
751target_link_deps = {}
752
753
754class MakefileWriter:
755    """MakefileWriter packages up the writing of one target-specific foobar.mk.
756
757    Its only real entry point is Write(), and is mostly used for namespacing.
758    """
759
760    def __init__(self, generator_flags, flavor):
761        self.generator_flags = generator_flags
762        self.flavor = flavor
763
764        self.suffix_rules_srcdir = {}
765        self.suffix_rules_objdir1 = {}
766        self.suffix_rules_objdir2 = {}
767
768        # Generate suffix rules for all compilable extensions.
769        for ext in COMPILABLE_EXTENSIONS.keys():
770            # Suffix rules for source folder.
771            self.suffix_rules_srcdir.update(
772                {
773                    ext: (
774                        """\
775$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(srcdir)/%%%s FORCE_DO_CMD
776\t@$(call do_cmd,%s,1)
777"""
778                        % (ext, COMPILABLE_EXTENSIONS[ext])
779                    )
780                }
781            )
782
783            # Suffix rules for generated source files.
784            self.suffix_rules_objdir1.update(
785                {
786                    ext: (
787                        """\
788$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj).$(TOOLSET)/%%%s FORCE_DO_CMD
789\t@$(call do_cmd,%s,1)
790"""
791                        % (ext, COMPILABLE_EXTENSIONS[ext])
792                    )
793                }
794            )
795            self.suffix_rules_objdir2.update(
796                {
797                    ext: (
798                        """\
799$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
800\t@$(call do_cmd,%s,1)
801"""
802                        % (ext, COMPILABLE_EXTENSIONS[ext])
803                    )
804                }
805            )
806
807    def Write(
808        self, qualified_target, base_path, output_filename, spec, configs, part_of_all
809    ):
810        """The main entry point: writes a .mk file for a single target.
811
812        Arguments:
813          qualified_target: target we're generating
814          base_path: path relative to source root we're building in, used to resolve
815                     target-relative paths
816          output_filename: output .mk file name to write
817          spec, configs: gyp info
818          part_of_all: flag indicating this target is part of 'all'
819        """
820        gyp.common.EnsureDirExists(output_filename)
821
822        self.fp = open(output_filename, "w")
823
824        self.fp.write(header)
825
826        self.qualified_target = qualified_target
827        self.path = base_path
828        self.target = spec["target_name"]
829        self.type = spec["type"]
830        self.toolset = spec["toolset"]
831
832        self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec)
833        if self.flavor == "mac":
834            self.xcode_settings = gyp.xcode_emulation.XcodeSettings(spec)
835        else:
836            self.xcode_settings = None
837
838        deps, link_deps = self.ComputeDeps(spec)
839
840        # Some of the generation below can add extra output, sources, or
841        # link dependencies.  All of the out params of the functions that
842        # follow use names like extra_foo.
843        extra_outputs = []
844        extra_sources = []
845        extra_link_deps = []
846        extra_mac_bundle_resources = []
847        mac_bundle_deps = []
848
849        if self.is_mac_bundle:
850            self.output = self.ComputeMacBundleOutput(spec)
851            self.output_binary = self.ComputeMacBundleBinaryOutput(spec)
852        else:
853            self.output = self.output_binary = self.ComputeOutput(spec)
854
855        self.is_standalone_static_library = bool(
856            spec.get("standalone_static_library", 0)
857        )
858        self._INSTALLABLE_TARGETS = ("executable", "loadable_module", "shared_library")
859        if self.is_standalone_static_library or self.type in self._INSTALLABLE_TARGETS:
860            self.alias = os.path.basename(self.output)
861            install_path = self._InstallableTargetInstallPath()
862        else:
863            self.alias = self.output
864            install_path = self.output
865
866        self.WriteLn("TOOLSET := " + self.toolset)
867        self.WriteLn("TARGET := " + self.target)
868
869        # Actions must come first, since they can generate more OBJs for use below.
870        if "actions" in spec:
871            self.WriteActions(
872                spec["actions"],
873                extra_sources,
874                extra_outputs,
875                extra_mac_bundle_resources,
876                part_of_all,
877            )
878
879        # Rules must be early like actions.
880        if "rules" in spec:
881            self.WriteRules(
882                spec["rules"],
883                extra_sources,
884                extra_outputs,
885                extra_mac_bundle_resources,
886                part_of_all,
887            )
888
889        if "copies" in spec:
890            self.WriteCopies(spec["copies"], extra_outputs, part_of_all)
891
892        # Bundle resources.
893        if self.is_mac_bundle:
894            all_mac_bundle_resources = (
895                spec.get("mac_bundle_resources", []) + extra_mac_bundle_resources
896            )
897            self.WriteMacBundleResources(all_mac_bundle_resources, mac_bundle_deps)
898            self.WriteMacInfoPlist(mac_bundle_deps)
899
900        # Sources.
901        all_sources = spec.get("sources", []) + extra_sources
902        if all_sources:
903            self.WriteSources(
904                configs,
905                deps,
906                all_sources,
907                extra_outputs,
908                extra_link_deps,
909                part_of_all,
910                gyp.xcode_emulation.MacPrefixHeader(
911                    self.xcode_settings,
912                    lambda p: Sourceify(self.Absolutify(p)),
913                    self.Pchify,
914                ),
915            )
916            sources = [x for x in all_sources if Compilable(x)]
917            if sources:
918                self.WriteLn(SHARED_HEADER_SUFFIX_RULES_COMMENT1)
919                extensions = {os.path.splitext(s)[1] for s in sources}
920                for ext in extensions:
921                    if ext in self.suffix_rules_srcdir:
922                        self.WriteLn(self.suffix_rules_srcdir[ext])
923                self.WriteLn(SHARED_HEADER_SUFFIX_RULES_COMMENT2)
924                for ext in extensions:
925                    if ext in self.suffix_rules_objdir1:
926                        self.WriteLn(self.suffix_rules_objdir1[ext])
927                for ext in extensions:
928                    if ext in self.suffix_rules_objdir2:
929                        self.WriteLn(self.suffix_rules_objdir2[ext])
930                self.WriteLn("# End of this set of suffix rules")
931
932                # Add dependency from bundle to bundle binary.
933                if self.is_mac_bundle:
934                    mac_bundle_deps.append(self.output_binary)
935
936        self.WriteTarget(
937            spec,
938            configs,
939            deps,
940            extra_link_deps + link_deps,
941            mac_bundle_deps,
942            extra_outputs,
943            part_of_all,
944        )
945
946        # Update global list of target outputs, used in dependency tracking.
947        target_outputs[qualified_target] = install_path
948
949        # Update global list of link dependencies.
950        if self.type in ("static_library", "shared_library"):
951            target_link_deps[qualified_target] = self.output_binary
952
953        # Currently any versions have the same effect, but in future the behavior
954        # could be different.
955        if self.generator_flags.get("android_ndk_version", None):
956            self.WriteAndroidNdkModuleRule(self.target, all_sources, link_deps)
957
958        self.fp.close()
959
960    def WriteSubMake(self, output_filename, makefile_path, targets, build_dir):
961        """Write a "sub-project" Makefile.
962
963        This is a small, wrapper Makefile that calls the top-level Makefile to build
964        the targets from a single gyp file (i.e. a sub-project).
965
966        Arguments:
967          output_filename: sub-project Makefile name to write
968          makefile_path: path to the top-level Makefile
969          targets: list of "all" targets for this sub-project
970          build_dir: build output directory, relative to the sub-project
971        """
972        gyp.common.EnsureDirExists(output_filename)
973        self.fp = open(output_filename, "w")
974        self.fp.write(header)
975        # For consistency with other builders, put sub-project build output in the
976        # sub-project dir (see test/subdirectory/gyptest-subdir-all.py).
977        self.WriteLn(
978            "export builddir_name ?= %s"
979            % os.path.join(os.path.dirname(output_filename), build_dir)
980        )
981        self.WriteLn(".PHONY: all")
982        self.WriteLn("all:")
983        if makefile_path:
984            makefile_path = " -C " + makefile_path
985        self.WriteLn("\t$(MAKE){} {}".format(makefile_path, " ".join(targets)))
986        self.fp.close()
987
988    def WriteActions(
989        self,
990        actions,
991        extra_sources,
992        extra_outputs,
993        extra_mac_bundle_resources,
994        part_of_all,
995    ):
996        """Write Makefile code for any 'actions' from the gyp input.
997
998        extra_sources: a list that will be filled in with newly generated source
999                       files, if any
1000        extra_outputs: a list that will be filled in with any outputs of these
1001                       actions (used to make other pieces dependent on these
1002                       actions)
1003        part_of_all: flag indicating this target is part of 'all'
1004        """
1005        env = self.GetSortedXcodeEnv()
1006        for action in actions:
1007            name = StringToMakefileVariable(
1008                "{}_{}".format(self.qualified_target, action["action_name"])
1009            )
1010            self.WriteLn('### Rules for action "%s":' % action["action_name"])
1011            inputs = action["inputs"]
1012            outputs = action["outputs"]
1013
1014            # Build up a list of outputs.
1015            # Collect the output dirs we'll need.
1016            dirs = set()
1017            for out in outputs:
1018                dir = os.path.split(out)[0]
1019                if dir:
1020                    dirs.add(dir)
1021            if int(action.get("process_outputs_as_sources", False)):
1022                extra_sources += outputs
1023            if int(action.get("process_outputs_as_mac_bundle_resources", False)):
1024                extra_mac_bundle_resources += outputs
1025
1026            # Write the actual command.
1027            action_commands = action["action"]
1028            if self.flavor == "mac":
1029                action_commands = [
1030                    gyp.xcode_emulation.ExpandEnvVars(command, env)
1031                    for command in action_commands
1032                ]
1033            command = gyp.common.EncodePOSIXShellList(action_commands)
1034            if "message" in action:
1035                self.WriteLn(
1036                    "quiet_cmd_{} = ACTION {} $@".format(name, action["message"])
1037                )
1038            else:
1039                self.WriteLn(f"quiet_cmd_{name} = ACTION {name} $@")
1040            if len(dirs) > 0:
1041                command = "mkdir -p %s" % " ".join(dirs) + "; " + command
1042
1043            cd_action = "cd %s; " % Sourceify(self.path or ".")
1044
1045            # command and cd_action get written to a toplevel variable called
1046            # cmd_foo. Toplevel variables can't handle things that change per
1047            # makefile like $(TARGET), so hardcode the target.
1048            command = command.replace("$(TARGET)", self.target)
1049            cd_action = cd_action.replace("$(TARGET)", self.target)
1050
1051            # Set LD_LIBRARY_PATH in case the action runs an executable from this
1052            # build which links to shared libs from this build.
1053            # actions run on the host, so they should in theory only use host
1054            # libraries, but until everything is made cross-compile safe, also use
1055            # target libraries.
1056            # TODO(piman): when everything is cross-compile safe, remove lib.target
1057            if self.flavor == "zos" or self.flavor == "aix":
1058                self.WriteLn(
1059                    "cmd_%s = LIBPATH=$(builddir)/lib.host:"
1060                    "$(builddir)/lib.target:$$LIBPATH; "
1061                    "export LIBPATH; "
1062                    "%s%s" % (name, cd_action, command)
1063                )
1064            else:
1065                self.WriteLn(
1066                    "cmd_%s = LD_LIBRARY_PATH=$(builddir)/lib.host:"
1067                    "$(builddir)/lib.target:$$LD_LIBRARY_PATH; "
1068                    "export LD_LIBRARY_PATH; "
1069                    "%s%s" % (name, cd_action, command)
1070                )
1071            self.WriteLn()
1072            outputs = [self.Absolutify(o) for o in outputs]
1073            # The makefile rules are all relative to the top dir, but the gyp actions
1074            # are defined relative to their containing dir.  This replaces the obj
1075            # variable for the action rule with an absolute version so that the output
1076            # goes in the right place.
1077            # Only write the 'obj' and 'builddir' rules for the "primary" output (:1);
1078            # it's superfluous for the "extra outputs", and this avoids accidentally
1079            # writing duplicate dummy rules for those outputs.
1080            # Same for environment.
1081            self.WriteLn("%s: obj := $(abs_obj)" % QuoteSpaces(outputs[0]))
1082            self.WriteLn("%s: builddir := $(abs_builddir)" % QuoteSpaces(outputs[0]))
1083            self.WriteSortedXcodeEnv(outputs[0], self.GetSortedXcodeEnv())
1084
1085            for input in inputs:
1086                assert " " not in input, (
1087                    "Spaces in action input filenames not supported (%s)" % input
1088                )
1089            for output in outputs:
1090                assert " " not in output, (
1091                    "Spaces in action output filenames not supported (%s)" % output
1092                )
1093
1094            # See the comment in WriteCopies about expanding env vars.
1095            outputs = [gyp.xcode_emulation.ExpandEnvVars(o, env) for o in outputs]
1096            inputs = [gyp.xcode_emulation.ExpandEnvVars(i, env) for i in inputs]
1097
1098            self.WriteDoCmd(
1099                outputs,
1100                [Sourceify(self.Absolutify(i)) for i in inputs],
1101                part_of_all=part_of_all,
1102                command=name,
1103            )
1104
1105            # Stuff the outputs in a variable so we can refer to them later.
1106            outputs_variable = "action_%s_outputs" % name
1107            self.WriteLn("{} := {}".format(outputs_variable, " ".join(outputs)))
1108            extra_outputs.append("$(%s)" % outputs_variable)
1109            self.WriteLn()
1110
1111        self.WriteLn()
1112
1113    def WriteRules(
1114        self,
1115        rules,
1116        extra_sources,
1117        extra_outputs,
1118        extra_mac_bundle_resources,
1119        part_of_all,
1120    ):
1121        """Write Makefile code for any 'rules' from the gyp input.
1122
1123        extra_sources: a list that will be filled in with newly generated source
1124                       files, if any
1125        extra_outputs: a list that will be filled in with any outputs of these
1126                       rules (used to make other pieces dependent on these rules)
1127        part_of_all: flag indicating this target is part of 'all'
1128        """
1129        env = self.GetSortedXcodeEnv()
1130        for rule in rules:
1131            name = StringToMakefileVariable(
1132                "{}_{}".format(self.qualified_target, rule["rule_name"])
1133            )
1134            count = 0
1135            self.WriteLn("### Generated for rule %s:" % name)
1136
1137            all_outputs = []
1138
1139            for rule_source in rule.get("rule_sources", []):
1140                dirs = set()
1141                (rule_source_dirname, rule_source_basename) = os.path.split(rule_source)
1142                (rule_source_root, rule_source_ext) = os.path.splitext(
1143                    rule_source_basename
1144                )
1145
1146                outputs = [
1147                    self.ExpandInputRoot(out, rule_source_root, rule_source_dirname)
1148                    for out in rule["outputs"]
1149                ]
1150
1151                for out in outputs:
1152                    dir = os.path.dirname(out)
1153                    if dir:
1154                        dirs.add(dir)
1155                if int(rule.get("process_outputs_as_sources", False)):
1156                    extra_sources += outputs
1157                if int(rule.get("process_outputs_as_mac_bundle_resources", False)):
1158                    extra_mac_bundle_resources += outputs
1159                inputs = [
1160                    Sourceify(self.Absolutify(i))
1161                    for i in [rule_source] + rule.get("inputs", [])
1162                ]
1163                actions = ["$(call do_cmd,%s_%d)" % (name, count)]
1164
1165                if name == "resources_grit":
1166                    # HACK: This is ugly.  Grit intentionally doesn't touch the
1167                    # timestamp of its output file when the file doesn't change,
1168                    # which is fine in hash-based dependency systems like scons
1169                    # and forge, but not kosher in the make world.  After some
1170                    # discussion, hacking around it here seems like the least
1171                    # amount of pain.
1172                    actions += ["@touch --no-create $@"]
1173
1174                # See the comment in WriteCopies about expanding env vars.
1175                outputs = [gyp.xcode_emulation.ExpandEnvVars(o, env) for o in outputs]
1176                inputs = [gyp.xcode_emulation.ExpandEnvVars(i, env) for i in inputs]
1177
1178                outputs = [self.Absolutify(o) for o in outputs]
1179                all_outputs += outputs
1180                # Only write the 'obj' and 'builddir' rules for the "primary" output
1181                # (:1); it's superfluous for the "extra outputs", and this avoids
1182                # accidentally writing duplicate dummy rules for those outputs.
1183                self.WriteLn("%s: obj := $(abs_obj)" % outputs[0])
1184                self.WriteLn("%s: builddir := $(abs_builddir)" % outputs[0])
1185                self.WriteMakeRule(
1186                    outputs, inputs, actions, command="%s_%d" % (name, count)
1187                )
1188                # Spaces in rule filenames are not supported, but rule variables have
1189                # spaces in them (e.g. RULE_INPUT_PATH expands to '$(abspath $<)').
1190                # The spaces within the variables are valid, so remove the variables
1191                # before checking.
1192                variables_with_spaces = re.compile(r"\$\([^ ]* \$<\)")
1193                for output in outputs:
1194                    output = re.sub(variables_with_spaces, "", output)
1195                    assert " " not in output, (
1196                        "Spaces in rule filenames not yet supported (%s)" % output
1197                    )
1198                self.WriteLn("all_deps += %s" % " ".join(outputs))
1199
1200                action = [
1201                    self.ExpandInputRoot(ac, rule_source_root, rule_source_dirname)
1202                    for ac in rule["action"]
1203                ]
1204                mkdirs = ""
1205                if len(dirs) > 0:
1206                    mkdirs = "mkdir -p %s; " % " ".join(dirs)
1207                cd_action = "cd %s; " % Sourceify(self.path or ".")
1208
1209                # action, cd_action, and mkdirs get written to a toplevel variable
1210                # called cmd_foo. Toplevel variables can't handle things that change
1211                # per makefile like $(TARGET), so hardcode the target.
1212                if self.flavor == "mac":
1213                    action = [
1214                        gyp.xcode_emulation.ExpandEnvVars(command, env)
1215                        for command in action
1216                    ]
1217                action = gyp.common.EncodePOSIXShellList(action)
1218                action = action.replace("$(TARGET)", self.target)
1219                cd_action = cd_action.replace("$(TARGET)", self.target)
1220                mkdirs = mkdirs.replace("$(TARGET)", self.target)
1221
1222                # Set LD_LIBRARY_PATH in case the rule runs an executable from this
1223                # build which links to shared libs from this build.
1224                # rules run on the host, so they should in theory only use host
1225                # libraries, but until everything is made cross-compile safe, also use
1226                # target libraries.
1227                # TODO(piman): when everything is cross-compile safe, remove lib.target
1228                self.WriteLn(
1229                    "cmd_%(name)s_%(count)d = LD_LIBRARY_PATH="
1230                    "$(builddir)/lib.host:$(builddir)/lib.target:$$LD_LIBRARY_PATH; "
1231                    "export LD_LIBRARY_PATH; "
1232                    "%(cd_action)s%(mkdirs)s%(action)s"
1233                    % {
1234                        "action": action,
1235                        "cd_action": cd_action,
1236                        "count": count,
1237                        "mkdirs": mkdirs,
1238                        "name": name,
1239                    }
1240                )
1241                self.WriteLn(
1242                    "quiet_cmd_%(name)s_%(count)d = RULE %(name)s_%(count)d $@"
1243                    % {"count": count, "name": name}
1244                )
1245                self.WriteLn()
1246                count += 1
1247
1248            outputs_variable = "rule_%s_outputs" % name
1249            self.WriteList(all_outputs, outputs_variable)
1250            extra_outputs.append("$(%s)" % outputs_variable)
1251
1252            self.WriteLn("### Finished generating for rule: %s" % name)
1253            self.WriteLn()
1254        self.WriteLn("### Finished generating for all rules")
1255        self.WriteLn("")
1256
1257    def WriteCopies(self, copies, extra_outputs, part_of_all):
1258        """Write Makefile code for any 'copies' from the gyp input.
1259
1260        extra_outputs: a list that will be filled in with any outputs of this action
1261                       (used to make other pieces dependent on this action)
1262        part_of_all: flag indicating this target is part of 'all'
1263        """
1264        self.WriteLn("### Generated for copy rule.")
1265
1266        variable = StringToMakefileVariable(self.qualified_target + "_copies")
1267        outputs = []
1268        for copy in copies:
1269            for path in copy["files"]:
1270                # Absolutify() may call normpath, and will strip trailing slashes.
1271                path = Sourceify(self.Absolutify(path))
1272                filename = os.path.split(path)[1]
1273                output = Sourceify(
1274                    self.Absolutify(os.path.join(copy["destination"], filename))
1275                )
1276
1277                # If the output path has variables in it, which happens in practice for
1278                # 'copies', writing the environment as target-local doesn't work,
1279                # because the variables are already needed for the target name.
1280                # Copying the environment variables into global make variables doesn't
1281                # work either, because then the .d files will potentially contain spaces
1282                # after variable expansion, and .d file handling cannot handle spaces.
1283                # As a workaround, manually expand variables at gyp time. Since 'copies'
1284                # can't run scripts, there's no need to write the env then.
1285                # WriteDoCmd() will escape spaces for .d files.
1286                env = self.GetSortedXcodeEnv()
1287                output = gyp.xcode_emulation.ExpandEnvVars(output, env)
1288                path = gyp.xcode_emulation.ExpandEnvVars(path, env)
1289                self.WriteDoCmd([output], [path], "copy", part_of_all)
1290                outputs.append(output)
1291        self.WriteLn(
1292            "{} = {}".format(variable, " ".join(QuoteSpaces(o) for o in outputs))
1293        )
1294        extra_outputs.append("$(%s)" % variable)
1295        self.WriteLn()
1296
1297    def WriteMacBundleResources(self, resources, bundle_deps):
1298        """Writes Makefile code for 'mac_bundle_resources'."""
1299        self.WriteLn("### Generated for mac_bundle_resources")
1300
1301        for output, res in gyp.xcode_emulation.GetMacBundleResources(
1302            generator_default_variables["PRODUCT_DIR"],
1303            self.xcode_settings,
1304            [Sourceify(self.Absolutify(r)) for r in resources],
1305        ):
1306            _, ext = os.path.splitext(output)
1307            if ext != ".xcassets":
1308                # Make does not supports '.xcassets' emulation.
1309                self.WriteDoCmd(
1310                    [output], [res], "mac_tool,,,copy-bundle-resource", part_of_all=True
1311                )
1312                bundle_deps.append(output)
1313
1314    def WriteMacInfoPlist(self, bundle_deps):
1315        """Write Makefile code for bundle Info.plist files."""
1316        info_plist, out, defines, extra_env = gyp.xcode_emulation.GetMacInfoPlist(
1317            generator_default_variables["PRODUCT_DIR"],
1318            self.xcode_settings,
1319            lambda p: Sourceify(self.Absolutify(p)),
1320        )
1321        if not info_plist:
1322            return
1323        if defines:
1324            # Create an intermediate file to store preprocessed results.
1325            intermediate_plist = "$(obj).$(TOOLSET)/$(TARGET)/" + os.path.basename(
1326                info_plist
1327            )
1328            self.WriteList(
1329                defines,
1330                intermediate_plist + ": INFOPLIST_DEFINES",
1331                "-D",
1332                quoter=EscapeCppDefine,
1333            )
1334            self.WriteMakeRule(
1335                [intermediate_plist],
1336                [info_plist],
1337                [
1338                    "$(call do_cmd,infoplist)",
1339                    # "Convert" the plist so that any weird whitespace changes from the
1340                    # preprocessor do not affect the XML parser in mac_tool.
1341                    "@plutil -convert xml1 $@ $@",
1342                ],
1343            )
1344            info_plist = intermediate_plist
1345        # plists can contain envvars and substitute them into the file.
1346        self.WriteSortedXcodeEnv(
1347            out, self.GetSortedXcodeEnv(additional_settings=extra_env)
1348        )
1349        self.WriteDoCmd(
1350            [out], [info_plist], "mac_tool,,,copy-info-plist", part_of_all=True
1351        )
1352        bundle_deps.append(out)
1353
1354    def WriteSources(
1355        self,
1356        configs,
1357        deps,
1358        sources,
1359        extra_outputs,
1360        extra_link_deps,
1361        part_of_all,
1362        precompiled_header,
1363    ):
1364        """Write Makefile code for any 'sources' from the gyp input.
1365        These are source files necessary to build the current target.
1366
1367        configs, deps, sources: input from gyp.
1368        extra_outputs: a list of extra outputs this action should be dependent on;
1369                       used to serialize action/rules before compilation
1370        extra_link_deps: a list that will be filled in with any outputs of
1371                         compilation (to be used in link lines)
1372        part_of_all: flag indicating this target is part of 'all'
1373        """
1374
1375        # Write configuration-specific variables for CFLAGS, etc.
1376        for configname in sorted(configs.keys()):
1377            config = configs[configname]
1378            self.WriteList(
1379                config.get("defines"),
1380                "DEFS_%s" % configname,
1381                prefix="-D",
1382                quoter=EscapeCppDefine,
1383            )
1384
1385            if self.flavor == "mac":
1386                cflags = self.xcode_settings.GetCflags(
1387                    configname, arch=config.get("xcode_configuration_platform")
1388                )
1389                cflags_c = self.xcode_settings.GetCflagsC(configname)
1390                cflags_cc = self.xcode_settings.GetCflagsCC(configname)
1391                cflags_objc = self.xcode_settings.GetCflagsObjC(configname)
1392                cflags_objcc = self.xcode_settings.GetCflagsObjCC(configname)
1393            else:
1394                cflags = config.get("cflags")
1395                cflags_c = config.get("cflags_c")
1396                cflags_cc = config.get("cflags_cc")
1397
1398            self.WriteLn("# Flags passed to all source files.")
1399            self.WriteList(cflags, "CFLAGS_%s" % configname)
1400            self.WriteLn("# Flags passed to only C files.")
1401            self.WriteList(cflags_c, "CFLAGS_C_%s" % configname)
1402            self.WriteLn("# Flags passed to only C++ files.")
1403            self.WriteList(cflags_cc, "CFLAGS_CC_%s" % configname)
1404            if self.flavor == "mac":
1405                self.WriteLn("# Flags passed to only ObjC files.")
1406                self.WriteList(cflags_objc, "CFLAGS_OBJC_%s" % configname)
1407                self.WriteLn("# Flags passed to only ObjC++ files.")
1408                self.WriteList(cflags_objcc, "CFLAGS_OBJCC_%s" % configname)
1409            includes = config.get("include_dirs")
1410            if includes:
1411                includes = [Sourceify(self.Absolutify(i)) for i in includes]
1412            self.WriteList(includes, "INCS_%s" % configname, prefix="-I")
1413
1414        compilable = list(filter(Compilable, sources))
1415        objs = [self.Objectify(self.Absolutify(Target(c))) for c in compilable]
1416        self.WriteList(objs, "OBJS")
1417
1418        for obj in objs:
1419            assert " " not in obj, "Spaces in object filenames not supported (%s)" % obj
1420        self.WriteLn(
1421            "# Add to the list of files we specially track " "dependencies for."
1422        )
1423        self.WriteLn("all_deps += $(OBJS)")
1424        self.WriteLn()
1425
1426        # Make sure our dependencies are built first.
1427        if deps:
1428            self.WriteMakeRule(
1429                ["$(OBJS)"],
1430                deps,
1431                comment="Make sure our dependencies are built " "before any of us.",
1432                order_only=True,
1433            )
1434
1435        # Make sure the actions and rules run first.
1436        # If they generate any extra headers etc., the per-.o file dep tracking
1437        # will catch the proper rebuilds, so order only is still ok here.
1438        if extra_outputs:
1439            self.WriteMakeRule(
1440                ["$(OBJS)"],
1441                extra_outputs,
1442                comment="Make sure our actions/rules run " "before any of us.",
1443                order_only=True,
1444            )
1445
1446        pchdeps = precompiled_header.GetObjDependencies(compilable, objs)
1447        if pchdeps:
1448            self.WriteLn("# Dependencies from obj files to their precompiled headers")
1449            for source, obj, gch in pchdeps:
1450                self.WriteLn(f"{obj}: {gch}")
1451            self.WriteLn("# End precompiled header dependencies")
1452
1453        if objs:
1454            extra_link_deps.append("$(OBJS)")
1455            self.WriteLn(
1456                """\
1457# CFLAGS et al overrides must be target-local.
1458# See "Target-specific Variable Values" in the GNU Make manual."""
1459            )
1460            self.WriteLn("$(OBJS): TOOLSET := $(TOOLSET)")
1461            self.WriteLn(
1462                "$(OBJS): GYP_CFLAGS := "
1463                "$(DEFS_$(BUILDTYPE)) "
1464                "$(INCS_$(BUILDTYPE)) "
1465                "%s " % precompiled_header.GetInclude("c") + "$(CFLAGS_$(BUILDTYPE)) "
1466                "$(CFLAGS_C_$(BUILDTYPE))"
1467            )
1468            self.WriteLn(
1469                "$(OBJS): GYP_CXXFLAGS := "
1470                "$(DEFS_$(BUILDTYPE)) "
1471                "$(INCS_$(BUILDTYPE)) "
1472                "%s " % precompiled_header.GetInclude("cc") + "$(CFLAGS_$(BUILDTYPE)) "
1473                "$(CFLAGS_CC_$(BUILDTYPE))"
1474            )
1475            if self.flavor == "mac":
1476                self.WriteLn(
1477                    "$(OBJS): GYP_OBJCFLAGS := "
1478                    "$(DEFS_$(BUILDTYPE)) "
1479                    "$(INCS_$(BUILDTYPE)) "
1480                    "%s " % precompiled_header.GetInclude("m")
1481                    + "$(CFLAGS_$(BUILDTYPE)) "
1482                    "$(CFLAGS_C_$(BUILDTYPE)) "
1483                    "$(CFLAGS_OBJC_$(BUILDTYPE))"
1484                )
1485                self.WriteLn(
1486                    "$(OBJS): GYP_OBJCXXFLAGS := "
1487                    "$(DEFS_$(BUILDTYPE)) "
1488                    "$(INCS_$(BUILDTYPE)) "
1489                    "%s " % precompiled_header.GetInclude("mm")
1490                    + "$(CFLAGS_$(BUILDTYPE)) "
1491                    "$(CFLAGS_CC_$(BUILDTYPE)) "
1492                    "$(CFLAGS_OBJCC_$(BUILDTYPE))"
1493                )
1494
1495        self.WritePchTargets(precompiled_header.GetPchBuildCommands())
1496
1497        # If there are any object files in our input file list, link them into our
1498        # output.
1499        extra_link_deps += [source for source in sources if Linkable(source)]
1500
1501        self.WriteLn()
1502
1503    def WritePchTargets(self, pch_commands):
1504        """Writes make rules to compile prefix headers."""
1505        if not pch_commands:
1506            return
1507
1508        for gch, lang_flag, lang, input in pch_commands:
1509            extra_flags = {
1510                "c": "$(CFLAGS_C_$(BUILDTYPE))",
1511                "cc": "$(CFLAGS_CC_$(BUILDTYPE))",
1512                "m": "$(CFLAGS_C_$(BUILDTYPE)) $(CFLAGS_OBJC_$(BUILDTYPE))",
1513                "mm": "$(CFLAGS_CC_$(BUILDTYPE)) $(CFLAGS_OBJCC_$(BUILDTYPE))",
1514            }[lang]
1515            var_name = {
1516                "c": "GYP_PCH_CFLAGS",
1517                "cc": "GYP_PCH_CXXFLAGS",
1518                "m": "GYP_PCH_OBJCFLAGS",
1519                "mm": "GYP_PCH_OBJCXXFLAGS",
1520            }[lang]
1521            self.WriteLn(
1522                f"{gch}: {var_name} := {lang_flag} " + "$(DEFS_$(BUILDTYPE)) "
1523                "$(INCS_$(BUILDTYPE)) "
1524                "$(CFLAGS_$(BUILDTYPE)) " + extra_flags
1525            )
1526
1527            self.WriteLn(f"{gch}: {input} FORCE_DO_CMD")
1528            self.WriteLn("\t@$(call do_cmd,pch_%s,1)" % lang)
1529            self.WriteLn("")
1530            assert " " not in gch, "Spaces in gch filenames not supported (%s)" % gch
1531            self.WriteLn("all_deps += %s" % gch)
1532            self.WriteLn("")
1533
1534    def ComputeOutputBasename(self, spec):
1535        """Return the 'output basename' of a gyp spec.
1536
1537        E.g., the loadable module 'foobar' in directory 'baz' will produce
1538          'libfoobar.so'
1539        """
1540        assert not self.is_mac_bundle
1541
1542        if self.flavor == "mac" and self.type in (
1543            "static_library",
1544            "executable",
1545            "shared_library",
1546            "loadable_module",
1547        ):
1548            return self.xcode_settings.GetExecutablePath()
1549
1550        target = spec["target_name"]
1551        target_prefix = ""
1552        target_ext = ""
1553        if self.type == "static_library":
1554            if target[:3] == "lib":
1555                target = target[3:]
1556            target_prefix = "lib"
1557            target_ext = ".a"
1558        elif self.type in ("loadable_module", "shared_library"):
1559            if target[:3] == "lib":
1560                target = target[3:]
1561            target_prefix = "lib"
1562            if self.flavor == "aix":
1563                target_ext = ".a"
1564            elif self.flavor == "zos":
1565                target_ext = ".x"
1566            else:
1567                target_ext = ".so"
1568        elif self.type == "none":
1569            target = "%s.stamp" % target
1570        elif self.type != "executable":
1571            print(
1572                "ERROR: What output file should be generated?",
1573                "type",
1574                self.type,
1575                "target",
1576                target,
1577            )
1578
1579        target_prefix = spec.get("product_prefix", target_prefix)
1580        target = spec.get("product_name", target)
1581        product_ext = spec.get("product_extension")
1582        if product_ext:
1583            target_ext = "." + product_ext
1584
1585        return target_prefix + target + target_ext
1586
1587    def _InstallImmediately(self):
1588        return (
1589            self.toolset == "target"
1590            and self.flavor == "mac"
1591            and self.type
1592            in ("static_library", "executable", "shared_library", "loadable_module")
1593        )
1594
1595    def ComputeOutput(self, spec):
1596        """Return the 'output' (full output path) of a gyp spec.
1597
1598        E.g., the loadable module 'foobar' in directory 'baz' will produce
1599          '$(obj)/baz/libfoobar.so'
1600        """
1601        assert not self.is_mac_bundle
1602
1603        path = os.path.join("$(obj)." + self.toolset, self.path)
1604        if self.type == "executable" or self._InstallImmediately():
1605            path = "$(builddir)"
1606        path = spec.get("product_dir", path)
1607        return os.path.join(path, self.ComputeOutputBasename(spec))
1608
1609    def ComputeMacBundleOutput(self, spec):
1610        """Return the 'output' (full output path) to a bundle output directory."""
1611        assert self.is_mac_bundle
1612        path = generator_default_variables["PRODUCT_DIR"]
1613        return os.path.join(path, self.xcode_settings.GetWrapperName())
1614
1615    def ComputeMacBundleBinaryOutput(self, spec):
1616        """Return the 'output' (full output path) to the binary in a bundle."""
1617        path = generator_default_variables["PRODUCT_DIR"]
1618        return os.path.join(path, self.xcode_settings.GetExecutablePath())
1619
1620    def ComputeDeps(self, spec):
1621        """Compute the dependencies of a gyp spec.
1622
1623        Returns a tuple (deps, link_deps), where each is a list of
1624        filenames that will need to be put in front of make for either
1625        building (deps) or linking (link_deps).
1626        """
1627        deps = []
1628        link_deps = []
1629        if "dependencies" in spec:
1630            deps.extend(
1631                [
1632                    target_outputs[dep]
1633                    for dep in spec["dependencies"]
1634                    if target_outputs[dep]
1635                ]
1636            )
1637            for dep in spec["dependencies"]:
1638                if dep in target_link_deps:
1639                    link_deps.append(target_link_deps[dep])
1640            deps.extend(link_deps)
1641            # TODO: It seems we need to transitively link in libraries (e.g. -lfoo)?
1642            # This hack makes it work:
1643            # link_deps.extend(spec.get('libraries', []))
1644        return (gyp.common.uniquer(deps), gyp.common.uniquer(link_deps))
1645
1646    def GetSharedObjectFromSidedeck(self, sidedeck):
1647        """Return the shared object files based on sidedeck"""
1648        return re.sub(r"\.x$", ".so", sidedeck)
1649
1650    def GetUnversionedSidedeckFromSidedeck(self, sidedeck):
1651        """Return the shared object files based on sidedeck"""
1652        return re.sub(r"\.\d+\.x$", ".x", sidedeck)
1653
1654    def WriteDependencyOnExtraOutputs(self, target, extra_outputs):
1655        self.WriteMakeRule(
1656            [self.output_binary],
1657            extra_outputs,
1658            comment="Build our special outputs first.",
1659            order_only=True,
1660        )
1661
1662    def WriteTarget(
1663        self, spec, configs, deps, link_deps, bundle_deps, extra_outputs, part_of_all
1664    ):
1665        """Write Makefile code to produce the final target of the gyp spec.
1666
1667        spec, configs: input from gyp.
1668        deps, link_deps: dependency lists; see ComputeDeps()
1669        extra_outputs: any extra outputs that our target should depend on
1670        part_of_all: flag indicating this target is part of 'all'
1671        """
1672
1673        self.WriteLn("### Rules for final target.")
1674
1675        if extra_outputs:
1676            self.WriteDependencyOnExtraOutputs(self.output_binary, extra_outputs)
1677            self.WriteMakeRule(
1678                extra_outputs,
1679                deps,
1680                comment=("Preserve order dependency of " "special output on deps."),
1681                order_only=True,
1682            )
1683
1684        target_postbuilds = {}
1685        if self.type != "none":
1686            for configname in sorted(configs.keys()):
1687                config = configs[configname]
1688                if self.flavor == "mac":
1689                    ldflags = self.xcode_settings.GetLdflags(
1690                        configname,
1691                        generator_default_variables["PRODUCT_DIR"],
1692                        lambda p: Sourceify(self.Absolutify(p)),
1693                        arch=config.get("xcode_configuration_platform"),
1694                    )
1695
1696                    # TARGET_POSTBUILDS_$(BUILDTYPE) is added to postbuilds later on.
1697                    gyp_to_build = gyp.common.InvertRelativePath(self.path)
1698                    target_postbuild = self.xcode_settings.AddImplicitPostbuilds(
1699                        configname,
1700                        QuoteSpaces(
1701                            os.path.normpath(os.path.join(gyp_to_build, self.output))
1702                        ),
1703                        QuoteSpaces(
1704                            os.path.normpath(
1705                                os.path.join(gyp_to_build, self.output_binary)
1706                            )
1707                        ),
1708                    )
1709                    if target_postbuild:
1710                        target_postbuilds[configname] = target_postbuild
1711                else:
1712                    ldflags = config.get("ldflags", [])
1713                    # Compute an rpath for this output if needed.
1714                    if any(dep.endswith(".so") or ".so." in dep for dep in deps):
1715                        # We want to get the literal string "$ORIGIN"
1716                        # into the link command, so we need lots of escaping.
1717                        ldflags.append(r"-Wl,-rpath=\$$ORIGIN/")
1718                        ldflags.append(r"-Wl,-rpath-link=\$(builddir)/")
1719                library_dirs = config.get("library_dirs", [])
1720                ldflags += [("-L%s" % library_dir) for library_dir in library_dirs]
1721                self.WriteList(ldflags, "LDFLAGS_%s" % configname)
1722                if self.flavor == "mac":
1723                    self.WriteList(
1724                        self.xcode_settings.GetLibtoolflags(configname),
1725                        "LIBTOOLFLAGS_%s" % configname,
1726                    )
1727            libraries = spec.get("libraries")
1728            if libraries:
1729                # Remove duplicate entries
1730                libraries = gyp.common.uniquer(libraries)
1731                if self.flavor == "mac":
1732                    libraries = self.xcode_settings.AdjustLibraries(libraries)
1733            self.WriteList(libraries, "LIBS")
1734            self.WriteLn(
1735                "%s: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))"
1736                % QuoteSpaces(self.output_binary)
1737            )
1738            self.WriteLn("%s: LIBS := $(LIBS)" % QuoteSpaces(self.output_binary))
1739
1740            if self.flavor == "mac":
1741                self.WriteLn(
1742                    "%s: GYP_LIBTOOLFLAGS := $(LIBTOOLFLAGS_$(BUILDTYPE))"
1743                    % QuoteSpaces(self.output_binary)
1744                )
1745
1746        # Postbuild actions. Like actions, but implicitly depend on the target's
1747        # output.
1748        postbuilds = []
1749        if self.flavor == "mac":
1750            if target_postbuilds:
1751                postbuilds.append("$(TARGET_POSTBUILDS_$(BUILDTYPE))")
1752            postbuilds.extend(gyp.xcode_emulation.GetSpecPostbuildCommands(spec))
1753
1754        if postbuilds:
1755            # Envvars may be referenced by TARGET_POSTBUILDS_$(BUILDTYPE),
1756            # so we must output its definition first, since we declare variables
1757            # using ":=".
1758            self.WriteSortedXcodeEnv(self.output, self.GetSortedXcodePostbuildEnv())
1759
1760            for configname in target_postbuilds:
1761                self.WriteLn(
1762                    "%s: TARGET_POSTBUILDS_%s := %s"
1763                    % (
1764                        QuoteSpaces(self.output),
1765                        configname,
1766                        gyp.common.EncodePOSIXShellList(target_postbuilds[configname]),
1767                    )
1768                )
1769
1770            # Postbuilds expect to be run in the gyp file's directory, so insert an
1771            # implicit postbuild to cd to there.
1772            postbuilds.insert(0, gyp.common.EncodePOSIXShellList(["cd", self.path]))
1773            for i, postbuild in enumerate(postbuilds):
1774                if not postbuild.startswith("$"):
1775                    postbuilds[i] = EscapeShellArgument(postbuild)
1776            self.WriteLn("%s: builddir := $(abs_builddir)" % QuoteSpaces(self.output))
1777            self.WriteLn(
1778                "%s: POSTBUILDS := %s"
1779                % (QuoteSpaces(self.output), " ".join(postbuilds))
1780            )
1781
1782        # A bundle directory depends on its dependencies such as bundle resources
1783        # and bundle binary. When all dependencies have been built, the bundle
1784        # needs to be packaged.
1785        if self.is_mac_bundle:
1786            # If the framework doesn't contain a binary, then nothing depends
1787            # on the actions -- make the framework depend on them directly too.
1788            self.WriteDependencyOnExtraOutputs(self.output, extra_outputs)
1789
1790            # Bundle dependencies. Note that the code below adds actions to this
1791            # target, so if you move these two lines, move the lines below as well.
1792            self.WriteList([QuoteSpaces(dep) for dep in bundle_deps], "BUNDLE_DEPS")
1793            self.WriteLn("%s: $(BUNDLE_DEPS)" % QuoteSpaces(self.output))
1794
1795            # After the framework is built, package it. Needs to happen before
1796            # postbuilds, since postbuilds depend on this.
1797            if self.type in ("shared_library", "loadable_module"):
1798                self.WriteLn(
1799                    "\t@$(call do_cmd,mac_package_framework,,,%s)"
1800                    % self.xcode_settings.GetFrameworkVersion()
1801                )
1802
1803            # Bundle postbuilds can depend on the whole bundle, so run them after
1804            # the bundle is packaged, not already after the bundle binary is done.
1805            if postbuilds:
1806                self.WriteLn("\t@$(call do_postbuilds)")
1807            postbuilds = []  # Don't write postbuilds for target's output.
1808
1809            # Needed by test/mac/gyptest-rebuild.py.
1810            self.WriteLn("\t@true  # No-op, used by tests")
1811
1812            # Since this target depends on binary and resources which are in
1813            # nested subfolders, the framework directory will be older than
1814            # its dependencies usually. To prevent this rule from executing
1815            # on every build (expensive, especially with postbuilds), expliclity
1816            # update the time on the framework directory.
1817            self.WriteLn("\t@touch -c %s" % QuoteSpaces(self.output))
1818
1819        if postbuilds:
1820            assert not self.is_mac_bundle, (
1821                "Postbuilds for bundles should be done "
1822                "on the bundle, not the binary (target '%s')" % self.target
1823            )
1824            assert "product_dir" not in spec, (
1825                "Postbuilds do not work with " "custom product_dir"
1826            )
1827
1828        if self.type == "executable":
1829            self.WriteLn(
1830                "%s: LD_INPUTS := %s"
1831                % (
1832                    QuoteSpaces(self.output_binary),
1833                    " ".join(QuoteSpaces(dep) for dep in link_deps),
1834                )
1835            )
1836            if self.toolset == "host" and self.flavor == "android":
1837                self.WriteDoCmd(
1838                    [self.output_binary],
1839                    link_deps,
1840                    "link_host",
1841                    part_of_all,
1842                    postbuilds=postbuilds,
1843                )
1844            else:
1845                self.WriteDoCmd(
1846                    [self.output_binary],
1847                    link_deps,
1848                    "link",
1849                    part_of_all,
1850                    postbuilds=postbuilds,
1851                )
1852
1853        elif self.type == "static_library":
1854            for link_dep in link_deps:
1855                assert " " not in link_dep, (
1856                    "Spaces in alink input filenames not supported (%s)" % link_dep
1857                )
1858            if (
1859                self.flavor not in ("mac", "openbsd", "netbsd", "win")
1860                and not self.is_standalone_static_library
1861            ):
1862                if self.flavor in ("linux", "android"):
1863                    self.WriteMakeRule(
1864                        [self.output_binary],
1865                        link_deps,
1866                        actions=["$(call create_thin_archive,$@,$^)"],
1867                    )
1868                else:
1869                    self.WriteDoCmd(
1870                        [self.output_binary],
1871                        link_deps,
1872                        "alink_thin",
1873                        part_of_all,
1874                        postbuilds=postbuilds,
1875                    )
1876            else:
1877                if self.flavor in ("linux", "android"):
1878                    self.WriteMakeRule(
1879                        [self.output_binary],
1880                        link_deps,
1881                        actions=["$(call create_archive,$@,$^)"],
1882                    )
1883                else:
1884                    self.WriteDoCmd(
1885                        [self.output_binary],
1886                        link_deps,
1887                        "alink",
1888                        part_of_all,
1889                        postbuilds=postbuilds,
1890                    )
1891        elif self.type == "shared_library":
1892            self.WriteLn(
1893                "%s: LD_INPUTS := %s"
1894                % (
1895                    QuoteSpaces(self.output_binary),
1896                    " ".join(QuoteSpaces(dep) for dep in link_deps),
1897                )
1898            )
1899            self.WriteDoCmd(
1900                [self.output_binary],
1901                link_deps,
1902                "solink",
1903                part_of_all,
1904                postbuilds=postbuilds,
1905            )
1906            # z/OS has a .so target as well as a sidedeck .x target
1907            if self.flavor == "zos":
1908                self.WriteLn(
1909                    "%s: %s"
1910                    % (
1911                        QuoteSpaces(
1912                            self.GetSharedObjectFromSidedeck(self.output_binary)
1913                        ),
1914                        QuoteSpaces(self.output_binary),
1915                    )
1916                )
1917        elif self.type == "loadable_module":
1918            for link_dep in link_deps:
1919                assert " " not in link_dep, (
1920                    "Spaces in module input filenames not supported (%s)" % link_dep
1921                )
1922            if self.toolset == "host" and self.flavor == "android":
1923                self.WriteDoCmd(
1924                    [self.output_binary],
1925                    link_deps,
1926                    "solink_module_host",
1927                    part_of_all,
1928                    postbuilds=postbuilds,
1929                )
1930            else:
1931                self.WriteDoCmd(
1932                    [self.output_binary],
1933                    link_deps,
1934                    "solink_module",
1935                    part_of_all,
1936                    postbuilds=postbuilds,
1937                )
1938        elif self.type == "none":
1939            # Write a stamp line.
1940            self.WriteDoCmd(
1941                [self.output_binary], deps, "touch", part_of_all, postbuilds=postbuilds
1942            )
1943        else:
1944            print("WARNING: no output for", self.type, self.target)
1945
1946        # Add an alias for each target (if there are any outputs).
1947        # Installable target aliases are created below.
1948        if (self.output and self.output != self.target) and (
1949            self.type not in self._INSTALLABLE_TARGETS
1950        ):
1951            self.WriteMakeRule(
1952                [self.target], [self.output], comment="Add target alias", phony=True
1953            )
1954            if part_of_all:
1955                self.WriteMakeRule(
1956                    ["all"],
1957                    [self.target],
1958                    comment='Add target alias to "all" target.',
1959                    phony=True,
1960                )
1961
1962        # Add special-case rules for our installable targets.
1963        # 1) They need to install to the build dir or "product" dir.
1964        # 2) They get shortcuts for building (e.g. "make chrome").
1965        # 3) They are part of "make all".
1966        if self.type in self._INSTALLABLE_TARGETS or self.is_standalone_static_library:
1967            if self.type == "shared_library":
1968                file_desc = "shared library"
1969            elif self.type == "static_library":
1970                file_desc = "static library"
1971            else:
1972                file_desc = "executable"
1973            install_path = self._InstallableTargetInstallPath()
1974            installable_deps = []
1975            if self.flavor != "zos":
1976                installable_deps.append(self.output)
1977            if (
1978                self.flavor == "mac"
1979                and "product_dir" not in spec
1980                and self.toolset == "target"
1981            ):
1982                # On mac, products are created in install_path immediately.
1983                assert install_path == self.output, "{} != {}".format(
1984                    install_path,
1985                    self.output,
1986                )
1987
1988            # Point the target alias to the final binary output.
1989            self.WriteMakeRule(
1990                [self.target], [install_path], comment="Add target alias", phony=True
1991            )
1992            if install_path != self.output:
1993                assert not self.is_mac_bundle  # See comment a few lines above.
1994                self.WriteDoCmd(
1995                    [install_path],
1996                    [self.output],
1997                    "copy",
1998                    comment="Copy this to the %s output path." % file_desc,
1999                    part_of_all=part_of_all,
2000                )
2001                if self.flavor != "zos":
2002                    installable_deps.append(install_path)
2003            if self.flavor == "zos" and self.type == "shared_library":
2004                # lib.target/libnode.so has a dependency on $(obj).target/libnode.so
2005                self.WriteDoCmd(
2006                    [self.GetSharedObjectFromSidedeck(install_path)],
2007                    [self.GetSharedObjectFromSidedeck(self.output)],
2008                    "copy",
2009                    comment="Copy this to the %s output path." % file_desc,
2010                    part_of_all=part_of_all,
2011                )
2012                # Create a symlink of libnode.x to libnode.version.x
2013                self.WriteDoCmd(
2014                    [self.GetUnversionedSidedeckFromSidedeck(install_path)],
2015                    [install_path],
2016                    "symlink",
2017                    comment="Symlnk this to the %s output path." % file_desc,
2018                    part_of_all=part_of_all,
2019                )
2020                # Place libnode.version.so and libnode.x symlink in lib.target dir
2021                installable_deps.append(self.GetSharedObjectFromSidedeck(install_path))
2022                installable_deps.append(
2023                    self.GetUnversionedSidedeckFromSidedeck(install_path)
2024                )
2025            if self.output != self.alias and self.alias != self.target:
2026                self.WriteMakeRule(
2027                    [self.alias],
2028                    installable_deps,
2029                    comment="Short alias for building this %s." % file_desc,
2030                    phony=True,
2031                )
2032            if self.flavor == "zos" and self.type == "shared_library":
2033                # Make sure that .x symlink target is run
2034                self.WriteMakeRule(
2035                    ["all"],
2036                    [
2037                        self.GetUnversionedSidedeckFromSidedeck(install_path),
2038                        self.GetSharedObjectFromSidedeck(install_path),
2039                    ],
2040                    comment='Add %s to "all" target.' % file_desc,
2041                    phony=True,
2042                )
2043            elif part_of_all:
2044                self.WriteMakeRule(
2045                    ["all"],
2046                    [install_path],
2047                    comment='Add %s to "all" target.' % file_desc,
2048                    phony=True,
2049                )
2050
2051    def WriteList(self, value_list, variable=None, prefix="", quoter=QuoteIfNecessary):
2052        """Write a variable definition that is a list of values.
2053
2054        E.g. WriteList(['a','b'], 'foo', prefix='blah') writes out
2055             foo = blaha blahb
2056        but in a pretty-printed style.
2057        """
2058        values = ""
2059        if value_list:
2060            value_list = [quoter(prefix + value) for value in value_list]
2061            values = " \\\n\t" + " \\\n\t".join(value_list)
2062        self.fp.write(f"{variable} :={values}\n\n")
2063
2064    def WriteDoCmd(
2065        self, outputs, inputs, command, part_of_all, comment=None, postbuilds=False
2066    ):
2067        """Write a Makefile rule that uses do_cmd.
2068
2069        This makes the outputs dependent on the command line that was run,
2070        as well as support the V= make command line flag.
2071        """
2072        suffix = ""
2073        if postbuilds:
2074            assert "," not in command
2075            suffix = ",,1"  # Tell do_cmd to honor $POSTBUILDS
2076        self.WriteMakeRule(
2077            outputs,
2078            inputs,
2079            actions=[f"$(call do_cmd,{command}{suffix})"],
2080            comment=comment,
2081            command=command,
2082            force=True,
2083        )
2084        # Add our outputs to the list of targets we read depfiles from.
2085        # all_deps is only used for deps file reading, and for deps files we replace
2086        # spaces with ? because escaping doesn't work with make's $(sort) and
2087        # other functions.
2088        outputs = [QuoteSpaces(o, SPACE_REPLACEMENT) for o in outputs]
2089        self.WriteLn("all_deps += %s" % " ".join(outputs))
2090
2091    def WriteMakeRule(
2092        self,
2093        outputs,
2094        inputs,
2095        actions=None,
2096        comment=None,
2097        order_only=False,
2098        force=False,
2099        phony=False,
2100        command=None,
2101    ):
2102        """Write a Makefile rule, with some extra tricks.
2103
2104        outputs: a list of outputs for the rule (note: this is not directly
2105                 supported by make; see comments below)
2106        inputs: a list of inputs for the rule
2107        actions: a list of shell commands to run for the rule
2108        comment: a comment to put in the Makefile above the rule (also useful
2109                 for making this Python script's code self-documenting)
2110        order_only: if true, makes the dependency order-only
2111        force: if true, include FORCE_DO_CMD as an order-only dep
2112        phony: if true, the rule does not actually generate the named output, the
2113               output is just a name to run the rule
2114        command: (optional) command name to generate unambiguous labels
2115        """
2116        outputs = [QuoteSpaces(o) for o in outputs]
2117        inputs = [QuoteSpaces(i) for i in inputs]
2118
2119        if comment:
2120            self.WriteLn("# " + comment)
2121        if phony:
2122            self.WriteLn(".PHONY: " + " ".join(outputs))
2123        if actions:
2124            self.WriteLn("%s: TOOLSET := $(TOOLSET)" % outputs[0])
2125        force_append = " FORCE_DO_CMD" if force else ""
2126
2127        if order_only:
2128            # Order only rule: Just write a simple rule.
2129            # TODO(evanm): just make order_only a list of deps instead of this hack.
2130            self.WriteLn(
2131                "{}: | {}{}".format(" ".join(outputs), " ".join(inputs), force_append)
2132            )
2133        elif len(outputs) == 1:
2134            # Regular rule, one output: Just write a simple rule.
2135            self.WriteLn("{}: {}{}".format(outputs[0], " ".join(inputs), force_append))
2136        else:
2137            # Regular rule, more than one output: Multiple outputs are tricky in
2138            # make. We will write three rules:
2139            # - All outputs depend on an intermediate file.
2140            # - Make .INTERMEDIATE depend on the intermediate.
2141            # - The intermediate file depends on the inputs and executes the
2142            #   actual command.
2143            # - The intermediate recipe will 'touch' the intermediate file.
2144            # - The multi-output rule will have an do-nothing recipe.
2145
2146            # Hash the target name to avoid generating overlong filenames.
2147            cmddigest = hashlib.sha1(
2148                (command or self.target).encode("utf-8")
2149            ).hexdigest()
2150            intermediate = "%s.intermediate" % cmddigest
2151            self.WriteLn("{}: {}".format(" ".join(outputs), intermediate))
2152            self.WriteLn("\t%s" % "@:")
2153            self.WriteLn("{}: {}".format(".INTERMEDIATE", intermediate))
2154            self.WriteLn(
2155                "{}: {}{}".format(intermediate, " ".join(inputs), force_append)
2156            )
2157            actions.insert(0, "$(call do_cmd,touch)")
2158
2159        if actions:
2160            for action in actions:
2161                self.WriteLn("\t%s" % action)
2162        self.WriteLn()
2163
2164    def WriteAndroidNdkModuleRule(self, module_name, all_sources, link_deps):
2165        """Write a set of LOCAL_XXX definitions for Android NDK.
2166
2167        These variable definitions will be used by Android NDK but do nothing for
2168        non-Android applications.
2169
2170        Arguments:
2171          module_name: Android NDK module name, which must be unique among all
2172              module names.
2173          all_sources: A list of source files (will be filtered by Compilable).
2174          link_deps: A list of link dependencies, which must be sorted in
2175              the order from dependencies to dependents.
2176        """
2177        if self.type not in ("executable", "shared_library", "static_library"):
2178            return
2179
2180        self.WriteLn("# Variable definitions for Android applications")
2181        self.WriteLn("include $(CLEAR_VARS)")
2182        self.WriteLn("LOCAL_MODULE := " + module_name)
2183        self.WriteLn(
2184            "LOCAL_CFLAGS := $(CFLAGS_$(BUILDTYPE)) "
2185            "$(DEFS_$(BUILDTYPE)) "
2186            # LOCAL_CFLAGS is applied to both of C and C++.  There is
2187            # no way to specify $(CFLAGS_C_$(BUILDTYPE)) only for C
2188            # sources.
2189            "$(CFLAGS_C_$(BUILDTYPE)) "
2190            # $(INCS_$(BUILDTYPE)) includes the prefix '-I' while
2191            # LOCAL_C_INCLUDES does not expect it.  So put it in
2192            # LOCAL_CFLAGS.
2193            "$(INCS_$(BUILDTYPE))"
2194        )
2195        # LOCAL_CXXFLAGS is obsolete and LOCAL_CPPFLAGS is preferred.
2196        self.WriteLn("LOCAL_CPPFLAGS := $(CFLAGS_CC_$(BUILDTYPE))")
2197        self.WriteLn("LOCAL_C_INCLUDES :=")
2198        self.WriteLn("LOCAL_LDLIBS := $(LDFLAGS_$(BUILDTYPE)) $(LIBS)")
2199
2200        # Detect the C++ extension.
2201        cpp_ext = {".cc": 0, ".cpp": 0, ".cxx": 0}
2202        default_cpp_ext = ".cpp"
2203        for filename in all_sources:
2204            ext = os.path.splitext(filename)[1]
2205            if ext in cpp_ext:
2206                cpp_ext[ext] += 1
2207                if cpp_ext[ext] > cpp_ext[default_cpp_ext]:
2208                    default_cpp_ext = ext
2209        self.WriteLn("LOCAL_CPP_EXTENSION := " + default_cpp_ext)
2210
2211        self.WriteList(
2212            list(map(self.Absolutify, filter(Compilable, all_sources))),
2213            "LOCAL_SRC_FILES",
2214        )
2215
2216        # Filter out those which do not match prefix and suffix and produce
2217        # the resulting list without prefix and suffix.
2218        def DepsToModules(deps, prefix, suffix):
2219            modules = []
2220            for filepath in deps:
2221                filename = os.path.basename(filepath)
2222                if filename.startswith(prefix) and filename.endswith(suffix):
2223                    modules.append(filename[len(prefix) : -len(suffix)])
2224            return modules
2225
2226        # Retrieve the default value of 'SHARED_LIB_SUFFIX'
2227        params = {"flavor": "linux"}
2228        default_variables = {}
2229        CalculateVariables(default_variables, params)
2230
2231        self.WriteList(
2232            DepsToModules(
2233                link_deps,
2234                generator_default_variables["SHARED_LIB_PREFIX"],
2235                default_variables["SHARED_LIB_SUFFIX"],
2236            ),
2237            "LOCAL_SHARED_LIBRARIES",
2238        )
2239        self.WriteList(
2240            DepsToModules(
2241                link_deps,
2242                generator_default_variables["STATIC_LIB_PREFIX"],
2243                generator_default_variables["STATIC_LIB_SUFFIX"],
2244            ),
2245            "LOCAL_STATIC_LIBRARIES",
2246        )
2247
2248        if self.type == "executable":
2249            self.WriteLn("include $(BUILD_EXECUTABLE)")
2250        elif self.type == "shared_library":
2251            self.WriteLn("include $(BUILD_SHARED_LIBRARY)")
2252        elif self.type == "static_library":
2253            self.WriteLn("include $(BUILD_STATIC_LIBRARY)")
2254        self.WriteLn()
2255
2256    def WriteLn(self, text=""):
2257        self.fp.write(text + "\n")
2258
2259    def GetSortedXcodeEnv(self, additional_settings=None):
2260        return gyp.xcode_emulation.GetSortedXcodeEnv(
2261            self.xcode_settings,
2262            "$(abs_builddir)",
2263            os.path.join("$(abs_srcdir)", self.path),
2264            "$(BUILDTYPE)",
2265            additional_settings,
2266        )
2267
2268    def GetSortedXcodePostbuildEnv(self):
2269        # CHROMIUM_STRIP_SAVE_FILE is a chromium-specific hack.
2270        # TODO(thakis): It would be nice to have some general mechanism instead.
2271        strip_save_file = self.xcode_settings.GetPerTargetSetting(
2272            "CHROMIUM_STRIP_SAVE_FILE", ""
2273        )
2274        # Even if strip_save_file is empty, explicitly write it. Else a postbuild
2275        # might pick up an export from an earlier target.
2276        return self.GetSortedXcodeEnv(
2277            additional_settings={"CHROMIUM_STRIP_SAVE_FILE": strip_save_file}
2278        )
2279
2280    def WriteSortedXcodeEnv(self, target, env):
2281        for k, v in env:
2282            # For
2283            #  foo := a\ b
2284            # the escaped space does the right thing. For
2285            #  export foo := a\ b
2286            # it does not -- the backslash is written to the env as literal character.
2287            # So don't escape spaces in |env[k]|.
2288            self.WriteLn(f"{QuoteSpaces(target)}: export {k} := {v}")
2289
2290    def Objectify(self, path):
2291        """Convert a path to its output directory form."""
2292        if "$(" in path:
2293            path = path.replace("$(obj)/", "$(obj).%s/$(TARGET)/" % self.toolset)
2294        if "$(obj)" not in path:
2295            path = f"$(obj).{self.toolset}/$(TARGET)/{path}"
2296        return path
2297
2298    def Pchify(self, path, lang):
2299        """Convert a prefix header path to its output directory form."""
2300        path = self.Absolutify(path)
2301        if "$(" in path:
2302            path = path.replace(
2303                "$(obj)/", f"$(obj).{self.toolset}/$(TARGET)/pch-{lang}"
2304            )
2305            return path
2306        return f"$(obj).{self.toolset}/$(TARGET)/pch-{lang}/{path}"
2307
2308    def Absolutify(self, path):
2309        """Convert a subdirectory-relative path into a base-relative path.
2310        Skips over paths that contain variables."""
2311        if "$(" in path:
2312            # Don't call normpath in this case, as it might collapse the
2313            # path too aggressively if it features '..'. However it's still
2314            # important to strip trailing slashes.
2315            return path.rstrip("/")
2316        return os.path.normpath(os.path.join(self.path, path))
2317
2318    def ExpandInputRoot(self, template, expansion, dirname):
2319        if "%(INPUT_ROOT)s" not in template and "%(INPUT_DIRNAME)s" not in template:
2320            return template
2321        path = template % {
2322            "INPUT_ROOT": expansion,
2323            "INPUT_DIRNAME": dirname,
2324        }
2325        return path
2326
2327    def _InstallableTargetInstallPath(self):
2328        """Returns the location of the final output for an installable target."""
2329        # Functionality removed for all platforms to match Xcode and hoist
2330        # shared libraries into PRODUCT_DIR for users:
2331        # Xcode puts shared_library results into PRODUCT_DIR, and some gyp files
2332        # rely on this. Emulate this behavior for mac.
2333        # if self.type == "shared_library" and (
2334        #     self.flavor != "mac" or self.toolset != "target"
2335        # ):
2336        #    # Install all shared libs into a common directory (per toolset) for
2337        #    # convenient access with LD_LIBRARY_PATH.
2338        #    return "$(builddir)/lib.%s/%s" % (self.toolset, self.alias)
2339        if self.flavor == "zos" and self.type == "shared_library":
2340            return "$(builddir)/lib.%s/%s" % (self.toolset, self.alias)
2341
2342        return "$(builddir)/" + self.alias
2343
2344
2345def WriteAutoRegenerationRule(params, root_makefile, makefile_name, build_files):
2346    """Write the target to regenerate the Makefile."""
2347    options = params["options"]
2348    build_files_args = [
2349        gyp.common.RelativePath(filename, options.toplevel_dir)
2350        for filename in params["build_files_arg"]
2351    ]
2352
2353    gyp_binary = gyp.common.FixIfRelativePath(
2354        params["gyp_binary"], options.toplevel_dir
2355    )
2356    if not gyp_binary.startswith(os.sep):
2357        gyp_binary = os.path.join(".", gyp_binary)
2358
2359    root_makefile.write(
2360        "quiet_cmd_regen_makefile = ACTION Regenerating $@\n"
2361        "cmd_regen_makefile = cd $(srcdir); %(cmd)s\n"
2362        "%(makefile_name)s: %(deps)s\n"
2363        "\t$(call do_cmd,regen_makefile)\n\n"
2364        % {
2365            "makefile_name": makefile_name,
2366            "deps": " ".join(SourceifyAndQuoteSpaces(bf) for bf in build_files),
2367            "cmd": gyp.common.EncodePOSIXShellList(
2368                [gyp_binary, "-fmake"] + gyp.RegenerateFlags(options) + build_files_args
2369            ),
2370        }
2371    )
2372
2373
2374def PerformBuild(data, configurations, params):
2375    options = params["options"]
2376    for config in configurations:
2377        arguments = ["make"]
2378        if options.toplevel_dir and options.toplevel_dir != ".":
2379            arguments += "-C", options.toplevel_dir
2380        arguments.append("BUILDTYPE=" + config)
2381        print(f"Building [{config}]: {arguments}")
2382        subprocess.check_call(arguments)
2383
2384
2385def GenerateOutput(target_list, target_dicts, data, params):
2386    options = params["options"]
2387    flavor = gyp.common.GetFlavor(params)
2388    generator_flags = params.get("generator_flags", {})
2389    builddir_name = generator_flags.get("output_dir", "out")
2390    android_ndk_version = generator_flags.get("android_ndk_version", None)
2391    default_target = generator_flags.get("default_target", "all")
2392
2393    def CalculateMakefilePath(build_file, base_name):
2394        """Determine where to write a Makefile for a given gyp file."""
2395        # Paths in gyp files are relative to the .gyp file, but we want
2396        # paths relative to the source root for the master makefile.  Grab
2397        # the path of the .gyp file as the base to relativize against.
2398        # E.g. "foo/bar" when we're constructing targets for "foo/bar/baz.gyp".
2399        base_path = gyp.common.RelativePath(os.path.dirname(build_file), options.depth)
2400        # We write the file in the base_path directory.
2401        output_file = os.path.join(options.depth, base_path, base_name)
2402        if options.generator_output:
2403            output_file = os.path.join(
2404                options.depth, options.generator_output, base_path, base_name
2405            )
2406        base_path = gyp.common.RelativePath(
2407            os.path.dirname(build_file), options.toplevel_dir
2408        )
2409        return base_path, output_file
2410
2411    # TODO:  search for the first non-'Default' target.  This can go
2412    # away when we add verification that all targets have the
2413    # necessary configurations.
2414    default_configuration = None
2415    toolsets = {target_dicts[target]["toolset"] for target in target_list}
2416    for target in target_list:
2417        spec = target_dicts[target]
2418        if spec["default_configuration"] != "Default":
2419            default_configuration = spec["default_configuration"]
2420            break
2421    if not default_configuration:
2422        default_configuration = "Default"
2423
2424    srcdir = "."
2425    makefile_name = "Makefile" + options.suffix
2426    makefile_path = os.path.join(options.toplevel_dir, makefile_name)
2427    if options.generator_output:
2428        global srcdir_prefix
2429        makefile_path = os.path.join(
2430            options.toplevel_dir, options.generator_output, makefile_name
2431        )
2432        srcdir = gyp.common.RelativePath(srcdir, options.generator_output)
2433        srcdir_prefix = "$(srcdir)/"
2434
2435    flock_command = "flock"
2436    copy_archive_arguments = "-af"
2437    makedep_arguments = "-MMD"
2438    header_params = {
2439        "default_target": default_target,
2440        "builddir": builddir_name,
2441        "default_configuration": default_configuration,
2442        "flock": flock_command,
2443        "flock_index": 1,
2444        "link_commands": LINK_COMMANDS_LINUX,
2445        "extra_commands": "",
2446        "srcdir": srcdir,
2447        "copy_archive_args": copy_archive_arguments,
2448        "makedep_args": makedep_arguments,
2449        "CC.target": GetEnvironFallback(("CC_target", "CC"), "$(CC)"),
2450        "AR.target": GetEnvironFallback(("AR_target", "AR"), "$(AR)"),
2451        "CXX.target": GetEnvironFallback(("CXX_target", "CXX"), "$(CXX)"),
2452        "LINK.target": GetEnvironFallback(("LINK_target", "LINK"), "$(LINK)"),
2453        "CC.host": GetEnvironFallback(("CC_host", "CC"), "gcc"),
2454        "AR.host": GetEnvironFallback(("AR_host", "AR"), "ar"),
2455        "CXX.host": GetEnvironFallback(("CXX_host", "CXX"), "g++"),
2456        "LINK.host": GetEnvironFallback(("LINK_host", "LINK"), "$(CXX.host)"),
2457    }
2458    if flavor == "mac":
2459        flock_command = "./gyp-mac-tool flock"
2460        header_params.update(
2461            {
2462                "flock": flock_command,
2463                "flock_index": 2,
2464                "link_commands": LINK_COMMANDS_MAC,
2465                "extra_commands": SHARED_HEADER_MAC_COMMANDS,
2466            }
2467        )
2468    elif flavor == "android":
2469        header_params.update({"link_commands": LINK_COMMANDS_ANDROID})
2470    elif flavor == "zos":
2471        copy_archive_arguments = "-fPR"
2472        makedep_arguments = "-qmakedep=gcc"
2473        header_params.update(
2474            {
2475                "copy_archive_args": copy_archive_arguments,
2476                "makedep_args": makedep_arguments,
2477                "link_commands": LINK_COMMANDS_OS390,
2478                "CC.target": GetEnvironFallback(("CC_target", "CC"), "njsc"),
2479                "CXX.target": GetEnvironFallback(("CXX_target", "CXX"), "njsc++"),
2480                "CC.host": GetEnvironFallback(("CC_host", "CC"), "njsc"),
2481                "CXX.host": GetEnvironFallback(("CXX_host", "CXX"), "njsc++"),
2482            }
2483        )
2484    elif flavor == "solaris":
2485        copy_archive_arguments = "-pPRf@"
2486        header_params.update(
2487            {
2488                "copy_archive_args": copy_archive_arguments,
2489                "flock": "./gyp-flock-tool flock",
2490                "flock_index": 2,
2491            }
2492        )
2493    elif flavor == "freebsd":
2494        # Note: OpenBSD has sysutils/flock. lockf seems to be FreeBSD specific.
2495        header_params.update({"flock": "lockf"})
2496    elif flavor == "openbsd":
2497        copy_archive_arguments = "-pPRf"
2498        header_params.update({"copy_archive_args": copy_archive_arguments})
2499    elif flavor == "aix":
2500        copy_archive_arguments = "-pPRf"
2501        header_params.update(
2502            {
2503                "copy_archive_args": copy_archive_arguments,
2504                "link_commands": LINK_COMMANDS_AIX,
2505                "flock": "./gyp-flock-tool flock",
2506                "flock_index": 2,
2507            }
2508        )
2509    elif flavor == "os400":
2510        copy_archive_arguments = "-pPRf"
2511        header_params.update(
2512            {
2513                "copy_archive_args": copy_archive_arguments,
2514                "link_commands": LINK_COMMANDS_OS400,
2515                "flock": "./gyp-flock-tool flock",
2516                "flock_index": 2,
2517            }
2518        )
2519
2520    build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
2521    make_global_settings_array = data[build_file].get("make_global_settings", [])
2522    wrappers = {}
2523    for key, value in make_global_settings_array:
2524        if key.endswith("_wrapper"):
2525            wrappers[key[: -len("_wrapper")]] = "$(abspath %s)" % value
2526    make_global_settings = ""
2527    for key, value in make_global_settings_array:
2528        if re.match(".*_wrapper", key):
2529            continue
2530        if value[0] != "$":
2531            value = "$(abspath %s)" % value
2532        wrapper = wrappers.get(key)
2533        if wrapper:
2534            value = f"{wrapper} {value}"
2535            del wrappers[key]
2536        if key in ("CC", "CC.host", "CXX", "CXX.host"):
2537            make_global_settings += (
2538                "ifneq (,$(filter $(origin %s), undefined default))\n" % key
2539            )
2540            # Let gyp-time envvars win over global settings.
2541            env_key = key.replace(".", "_")  # CC.host -> CC_host
2542            if env_key in os.environ:
2543                value = os.environ[env_key]
2544            make_global_settings += f"  {key} = {value}\n"
2545            make_global_settings += "endif\n"
2546        else:
2547            make_global_settings += f"{key} ?= {value}\n"
2548    # TODO(ukai): define cmd when only wrapper is specified in
2549    # make_global_settings.
2550
2551    header_params["make_global_settings"] = make_global_settings
2552
2553    gyp.common.EnsureDirExists(makefile_path)
2554    root_makefile = open(makefile_path, "w")
2555    root_makefile.write(SHARED_HEADER % header_params)
2556    # Currently any versions have the same effect, but in future the behavior
2557    # could be different.
2558    if android_ndk_version:
2559        root_makefile.write(
2560            "# Define LOCAL_PATH for build of Android applications.\n"
2561            "LOCAL_PATH := $(call my-dir)\n"
2562            "\n"
2563        )
2564    for toolset in toolsets:
2565        root_makefile.write("TOOLSET := %s\n" % toolset)
2566        WriteRootHeaderSuffixRules(root_makefile)
2567
2568    # Put build-time support tools next to the root Makefile.
2569    dest_path = os.path.dirname(makefile_path)
2570    gyp.common.CopyTool(flavor, dest_path)
2571
2572    # Find the list of targets that derive from the gyp file(s) being built.
2573    needed_targets = set()
2574    for build_file in params["build_files"]:
2575        for target in gyp.common.AllTargets(target_list, target_dicts, build_file):
2576            needed_targets.add(target)
2577
2578    build_files = set()
2579    include_list = set()
2580    for qualified_target in target_list:
2581        build_file, target, toolset = gyp.common.ParseQualifiedTarget(qualified_target)
2582
2583        this_make_global_settings = data[build_file].get("make_global_settings", [])
2584        assert make_global_settings_array == this_make_global_settings, (
2585            "make_global_settings needs to be the same for all targets "
2586            f"{this_make_global_settings} vs. {make_global_settings}"
2587        )
2588
2589        build_files.add(gyp.common.RelativePath(build_file, options.toplevel_dir))
2590        included_files = data[build_file]["included_files"]
2591        for included_file in included_files:
2592            # The included_files entries are relative to the dir of the build file
2593            # that included them, so we have to undo that and then make them relative
2594            # to the root dir.
2595            relative_include_file = gyp.common.RelativePath(
2596                gyp.common.UnrelativePath(included_file, build_file),
2597                options.toplevel_dir,
2598            )
2599            abs_include_file = os.path.abspath(relative_include_file)
2600            # If the include file is from the ~/.gyp dir, we should use absolute path
2601            # so that relocating the src dir doesn't break the path.
2602            if params["home_dot_gyp"] and abs_include_file.startswith(
2603                params["home_dot_gyp"]
2604            ):
2605                build_files.add(abs_include_file)
2606            else:
2607                build_files.add(relative_include_file)
2608
2609        base_path, output_file = CalculateMakefilePath(
2610            build_file, target + "." + toolset + options.suffix + ".mk"
2611        )
2612
2613        spec = target_dicts[qualified_target]
2614        configs = spec["configurations"]
2615
2616        if flavor == "mac":
2617            gyp.xcode_emulation.MergeGlobalXcodeSettingsToSpec(data[build_file], spec)
2618
2619        writer = MakefileWriter(generator_flags, flavor)
2620        writer.Write(
2621            qualified_target,
2622            base_path,
2623            output_file,
2624            spec,
2625            configs,
2626            part_of_all=qualified_target in needed_targets,
2627        )
2628
2629        # Our root_makefile lives at the source root.  Compute the relative path
2630        # from there to the output_file for including.
2631        mkfile_rel_path = gyp.common.RelativePath(
2632            output_file, os.path.dirname(makefile_path)
2633        )
2634        include_list.add(mkfile_rel_path)
2635
2636    # Write out per-gyp (sub-project) Makefiles.
2637    depth_rel_path = gyp.common.RelativePath(options.depth, os.getcwd())
2638    for build_file in build_files:
2639        # The paths in build_files were relativized above, so undo that before
2640        # testing against the non-relativized items in target_list and before
2641        # calculating the Makefile path.
2642        build_file = os.path.join(depth_rel_path, build_file)
2643        gyp_targets = [
2644            target_dicts[qualified_target]["target_name"]
2645            for qualified_target in target_list
2646            if qualified_target.startswith(build_file)
2647            and qualified_target in needed_targets
2648        ]
2649        # Only generate Makefiles for gyp files with targets.
2650        if not gyp_targets:
2651            continue
2652        base_path, output_file = CalculateMakefilePath(
2653            build_file, os.path.splitext(os.path.basename(build_file))[0] + ".Makefile"
2654        )
2655        makefile_rel_path = gyp.common.RelativePath(
2656            os.path.dirname(makefile_path), os.path.dirname(output_file)
2657        )
2658        writer.WriteSubMake(output_file, makefile_rel_path, gyp_targets, builddir_name)
2659
2660    # Write out the sorted list of includes.
2661    root_makefile.write("\n")
2662    for include_file in sorted(include_list):
2663        # We wrap each .mk include in an if statement so users can tell make to
2664        # not load a file by setting NO_LOAD.  The below make code says, only
2665        # load the .mk file if the .mk filename doesn't start with a token in
2666        # NO_LOAD.
2667        root_makefile.write(
2668            "ifeq ($(strip $(foreach prefix,$(NO_LOAD),\\\n"
2669            "    $(findstring $(join ^,$(prefix)),\\\n"
2670            "                 $(join ^," + include_file + ")))),)\n"
2671        )
2672        root_makefile.write("  include " + include_file + "\n")
2673        root_makefile.write("endif\n")
2674    root_makefile.write("\n")
2675
2676    if not generator_flags.get("standalone") and generator_flags.get(
2677        "auto_regeneration", True
2678    ):
2679        WriteAutoRegenerationRule(params, root_makefile, makefile_name, build_files)
2680
2681    root_makefile.write(SHARED_FOOTER)
2682
2683    root_makefile.close()
2684