From af5692fd0b1704460f1904f79643209a9ed97c9b Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 25 Oct 2026 09:47:47 +0100 Subject: [PATCH 1/2] tools: do not lint GYP JS files They are not maintained in this repository. Signed-off-by: Antoine du Hamel --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index c19e8f3cf46e..9c4ad8f05651 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -57,6 +57,7 @@ export default [ 'lib/punycode.js', 'test/.tmp.*/**', 'test/addons/??_*', + 'tools/gyp/**', // We want to lint only a few specific fixtures folders 'test/fixtures/*', From d35c9b5d7c398fe6d542121512f83b01bbf5f0ba Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 25 Oct 2026 00:54:19 +0000 Subject: [PATCH 2/2] tools: update gyp-next to 0.22.4 --- tools/gyp/CHANGELOG.md | 10 ++++ ...itlint.config.mjs => commitlint.config.js} | 0 tools/gyp/pylib/gyp/common.py | 8 ++- tools/gyp/pylib/gyp/common_test.py | 12 +++++ tools/gyp/pylib/gyp/generator/make.py | 40 +++++++++++--- tools/gyp/pylib/gyp/generator/ninja.py | 24 +++++++-- tools/gyp/pylib/gyp/xcode_emulation.py | 5 +- tools/gyp/pyproject.toml | 2 +- tools/gyp/test/fixtures/pch_action.h | 1 + tools/gyp/test/fixtures/pch_asm.S | 2 + tools/gyp/test/fixtures/pch_dep.h | 1 + tools/gyp/test/fixtures/pch_prefix.gyp | 47 +++++++++++++++++ tools/gyp/test/integration_test.py | 52 +++++++++++++++++++ 13 files changed, 189 insertions(+), 15 deletions(-) rename tools/gyp/{commitlint.config.mjs => commitlint.config.js} (100%) create mode 100644 tools/gyp/test/fixtures/pch_action.h create mode 100644 tools/gyp/test/fixtures/pch_asm.S create mode 100644 tools/gyp/test/fixtures/pch_dep.h create mode 100644 tools/gyp/test/fixtures/pch_prefix.gyp diff --git a/tools/gyp/CHANGELOG.md b/tools/gyp/CHANGELOG.md index e590b6fdb8c8..c0f014ba294a 100644 --- a/tools/gyp/CHANGELOG.md +++ b/tools/gyp/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.22.4](https://github.com/nodejs/gyp-next/compare/v0.22.3...v0.22.4) (2026-10-20) + + +### Bug Fixes + +* avoid deprecated os.path.commonprefix() in RelativePath ([#365](https://github.com/nodejs/gyp-next/issues/365)) ([428343e](https://github.com/nodejs/gyp-next/commit/428343ee58c05e7b817d3603688fe7cdb7a10846)), closes [#364](https://github.com/nodejs/gyp-next/issues/364) +* build precompiled headers after generated inputs ([#360](https://github.com/nodejs/gyp-next/issues/360)) ([d8888e9](https://github.com/nodejs/gyp-next/commit/d8888e932040eae90604b77ec6030a25714f0077)) +* include the prefix header itself in assembly sources ([#360](https://github.com/nodejs/gyp-next/issues/360)) ([d8888e9](https://github.com/nodejs/gyp-next/commit/d8888e932040eae90604b77ec6030a25714f0077)) +* **make:** compile the C prefix header with CFLAGS ([#360](https://github.com/nodejs/gyp-next/issues/360)) ([d8888e9](https://github.com/nodejs/gyp-next/commit/d8888e932040eae90604b77ec6030a25714f0077)) + ## [0.22.3](https://github.com/nodejs/gyp-next/compare/v0.22.2...v0.22.3) (2026-09-29) diff --git a/tools/gyp/commitlint.config.mjs b/tools/gyp/commitlint.config.js similarity index 100% rename from tools/gyp/commitlint.config.mjs rename to tools/gyp/commitlint.config.js diff --git a/tools/gyp/pylib/gyp/common.py b/tools/gyp/pylib/gyp/common.py index 223ce47b0032..c034f69c0af7 100644 --- a/tools/gyp/pylib/gyp/common.py +++ b/tools/gyp/pylib/gyp/common.py @@ -157,8 +157,12 @@ def RelativePath(path, relative_to, follow_path_symlink=True): path_split = path.split(os.path.sep) relative_to_split = relative_to.split(os.path.sep) - # Determine how much of the prefix the two paths share. - prefix_len = len(os.path.commonprefix([path_split, relative_to_split])) + # Determine how much of the path prefix the two paths share. + prefix_len = 0 + for path_part, relative_to_part in zip(path_split, relative_to_split): + if path_part != relative_to_part: + break + prefix_len += 1 # Put enough ".." components to back up out of relative_to to the common # prefix, and then append the part of path_split after the common prefix. diff --git a/tools/gyp/pylib/gyp/common_test.py b/tools/gyp/pylib/gyp/common_test.py index b5988816c04a..d3e860534de6 100755 --- a/tools/gyp/pylib/gyp/common_test.py +++ b/tools/gyp/pylib/gyp/common_test.py @@ -182,5 +182,17 @@ def mock_run(env, defines_stdout, expected_cmd, throws=False): assert flavor6 == "wasi" +class TestRelativePath(unittest.TestCase): + def test_common_parent(self): + assert gyp.common.RelativePath("/a/b", "/a/c") == os.path.join("..", "b") + + def test_only_root_in_common(self): + """Paths whose only common ancestor is the filesystem root.""" + assert gyp.common.RelativePath("/a/b", "/c") == os.path.join("..", "a", "b") + + def test_same_path(self): + assert gyp.common.RelativePath("/a/b", "/a/b") == "" + + if __name__ == "__main__": unittest.main() diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py index 16b6f4e80b11..c47050a52285 100644 --- a/tools/gyp/pylib/gyp/generator/make.py +++ b/tools/gyp/pylib/gyp/generator/make.py @@ -603,7 +603,7 @@ def CalculateGeneratorInputInfo(params): # Commands for precompiled header files. quiet_cmd_pch_c = CXX($(TOOLSET)) $@ -cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< +cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET)) -c -o $@ $< quiet_cmd_pch_cc = CXX($(TOOLSET)) $@ cmd_pch_cc = $(CC.$(TOOLSET)) $(GYP_PCH_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< quiet_cmd_pch_m = CXX($(TOOLSET)) $@ @@ -1485,13 +1485,29 @@ def WriteSources( # See "Target-specific Variable Values" in the GNU Make manual.""" ) self.WriteLn("$(OBJS): TOOLSET := $(TOOLSET)") - self.WriteLn( - "$(OBJS): GYP_CFLAGS := " + gyp_cflags = ( "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " - "%s " % precompiled_header.GetInclude("c") + "$(CFLAGS_$(BUILDTYPE)) " + "%s " + "$(CFLAGS_$(BUILDTYPE)) " "$(CFLAGS_C_$(BUILDTYPE))" ) + self.WriteLn( + "$(OBJS): GYP_CFLAGS := " + + gyp_cflags % precompiled_header.GetInclude("c") + ) + asm_objs = [ + obj + for source, obj in zip(compilable, objs) + if source.endswith((".s", ".S")) + ] + if asm_objs and precompiled_header.compile_headers: + # Assembly is compiled with GYP_CFLAGS too, but without a gch + # for it, the -include of the C one fails. + self.WriteLn( + "%s: GYP_CFLAGS := " % " ".join(asm_objs) + + gyp_cflags % precompiled_header.GetInclude("asm") + ) self.WriteLn( "$(OBJS): GYP_CXXFLAGS := " "$(DEFS_$(BUILDTYPE)) " @@ -1521,7 +1537,9 @@ def WriteSources( "$(CFLAGS_OBJCC_$(BUILDTYPE))" ) - self.WritePchTargets(precompiled_header.GetPchBuildCommands()) + self.WritePchTargets( + precompiled_header.GetPchBuildCommands(), deps + extra_outputs + ) # If there are any object files in our input file list, link them into our # output. @@ -1529,8 +1547,13 @@ def WriteSources( self.WriteLn() - def WritePchTargets(self, pch_commands): - """Writes make rules to compile prefix headers.""" + def WritePchTargets(self, pch_commands, order_only_deps): + """Writes make rules to compile prefix headers. + + A prefix header may include generated headers, so like the object files, + the gch files are built after |order_only_deps|: the target's dependencies + and the outputs of its actions and rules. + """ if not pch_commands: return @@ -1547,6 +1570,9 @@ def WritePchTargets(self, pch_commands): "m": "GYP_PCH_OBJCFLAGS", "mm": "GYP_PCH_OBJCXXFLAGS", }[lang] + if order_only_deps: + self.WriteMakeRule([gch], order_only_deps, order_only=True) + self.WriteLn(f"{gch}: TOOLSET := $(TOOLSET)") self.WriteLn( f"{gch}: {var_name} := {lang_flag} " + "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py index 3ceaf470cec2..49ce563ea7a4 100644 --- a/tools/gyp/pylib/gyp/generator/ninja.py +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -1253,6 +1253,12 @@ def WriteSourcesForArch( cflags_cc, self.ExpandSpecial, ) + elif ext in ("s", "S") and precompiled_header.compile_headers: + # Assembly gets $cflags_pch_c too, but without a gch for it, the + # -include of the C one fails. + variables = [ + ("cflags_pch_c", precompiled_header.GetInclude("asm", arch)) + ] ninja_file.build( output, command, @@ -1274,13 +1280,17 @@ def WriteSourcesForArch( ], ) - self.WritePchTargets(ninja_file, pch_commands) + self.WritePchTargets(ninja_file, pch_commands, predepends) ninja_file.newline() return outputs - def WritePchTargets(self, ninja_file, pch_commands): - """Writes ninja rules to compile prefix headers.""" + def WritePchTargets(self, ninja_file, pch_commands, predepends): + """Writes ninja rules to compile prefix headers. + + A prefix header may include generated headers, so like the object files, + the gch files are built after |predepends|. + """ if not pch_commands: return @@ -1299,7 +1309,13 @@ def WritePchTargets(self, ninja_file, pch_commands): "mm": "objcxx", } cmd = map.get(lang) - ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)]) + ninja_file.build( + gch, + cmd, + input, + order_only=predepends, + variables=[(var_name, lang_flag)], + ) def WriteLink(self, spec, config_name, config, link_deps, compile_deps): """Write out a link step. Fills out target.binary.""" diff --git a/tools/gyp/pylib/gyp/xcode_emulation.py b/tools/gyp/pylib/gyp/xcode_emulation.py index d13eaa9af240..7ae3c4f9c42d 100644 --- a/tools/gyp/pylib/gyp/xcode_emulation.py +++ b/tools/gyp/pylib/gyp/xcode_emulation.py @@ -1429,7 +1429,10 @@ def _CompiledHeader(self, lang, arch): return h def GetInclude(self, lang, arch=None): - """Gets the cflags to include the prefix header for language |lang|.""" + """Gets the cflags to include the prefix header for language |lang|. + + There is no compiled header for assembly ('asm'), which includes the + prefix header itself.""" if self.compile_headers and lang in self.compiled_headers: return "-include %s" % self._CompiledHeader(lang, arch) elif self.header: diff --git a/tools/gyp/pyproject.toml b/tools/gyp/pyproject.toml index 9ce52a6785e4..71c24110cd4f 100644 --- a/tools/gyp/pyproject.toml +++ b/tools/gyp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "gyp-next" -version = "0.22.3" +version = "0.22.4" authors = [ { name="Node.js contributors", email="ryzokuken@disroot.org" }, ] diff --git a/tools/gyp/test/fixtures/pch_action.h b/tools/gyp/test/fixtures/pch_action.h new file mode 100644 index 000000000000..5e3d629b3e2b --- /dev/null +++ b/tools/gyp/test/fixtures/pch_action.h @@ -0,0 +1 @@ +#include "action_generated.h" diff --git a/tools/gyp/test/fixtures/pch_asm.S b/tools/gyp/test/fixtures/pch_asm.S new file mode 100644 index 000000000000..84d6417a91d6 --- /dev/null +++ b/tools/gyp/test/fixtures/pch_asm.S @@ -0,0 +1,2 @@ +// Assembly gets the prefix header too, but has no precompiled header. +.text diff --git a/tools/gyp/test/fixtures/pch_dep.h b/tools/gyp/test/fixtures/pch_dep.h new file mode 100644 index 000000000000..7df152ab9b12 --- /dev/null +++ b/tools/gyp/test/fixtures/pch_dep.h @@ -0,0 +1 @@ +#include "dep_generated.h" diff --git a/tools/gyp/test/fixtures/pch_prefix.gyp b/tools/gyp/test/fixtures/pch_prefix.gyp new file mode 100644 index 000000000000..c505559c7610 --- /dev/null +++ b/tools/gyp/test/fixtures/pch_prefix.gyp @@ -0,0 +1,47 @@ +{ + 'targets': [ + { + # The prefix header includes a header generated by the target's action. + 'target_name': 'pch_action', + 'type': 'static_library', + 'sources': [ 'pch.cc', 'pch_asm.S' ], + 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)' ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'pch_action.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + 'actions': [ + { + 'action_name': 'generate_action_header', + 'inputs': [], + 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/action_generated.h' ], + 'action': [ 'touch', '<@(_outputs)' ], + }, + ], + }, + { + # The prefix header includes a header generated by a dependency. + 'target_name': 'pch_dep', + 'type': 'static_library', + 'dependencies': [ 'generate_dep_header' ], + 'sources': [ 'pch.cc', 'pch_asm.S' ], + 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)' ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'pch_dep.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + }, + { + 'target_name': 'generate_dep_header', + 'type': 'none', + 'actions': [ + { + 'action_name': 'generate_dep_header', + 'inputs': [], + 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/dep_generated.h' ], + 'action': [ 'touch', '<@(_outputs)' ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/integration_test.py b/tools/gyp/test/integration_test.py index 8244708a9bd5..c93c8669482c 100644 --- a/tools/gyp/test/integration_test.py +++ b/tools/gyp/test/integration_test.py @@ -5,6 +5,7 @@ import os import re import shutil +import subprocess import sys import unittest @@ -13,6 +14,7 @@ fixture_dir = os.path.join(os.path.dirname(__file__), "fixtures") gyp_file = os.path.join(fixture_dir, "integration.gyp") pch_gyp_file = os.path.join(fixture_dir, "pch.gyp") +pch_prefix_gyp_file = os.path.join(fixture_dir, "pch_prefix.gyp") if sys.platform == "win32": sysname = sys.platform @@ -80,6 +82,56 @@ def test_cmake(self) -> None: assert_file(self, "out/Default/CMakeLists.txt", "cmake/CMakeLists.txt") +class TestGypMac(unittest.TestCase): + # The prefix header of each target includes a header generated by its + # action (pch_action) or by its dependency (pch_dep). Each output is built + # on its own from a clean build, so it fails unless it waits for that header. + pch_targets = ("pch_action", "pch_dep") + pch_langs = ("c", "cc", "m", "mm") + + def setUp(self) -> None: + if sysname != "darwin": + self.skipTest("macOS-only test") + shutil.rmtree(os.path.join(fixture_dir, "out"), ignore_errors=True) + + def test_make_precompiled_prefix_header(self) -> None: + rc = gyp.main( + [ + "-f", + "make", + "--depth", + fixture_dir, + "--generator-output", + "out", + pch_prefix_gyp_file, + ] + ) + assert rc == 0 + + makefile_dir = os.path.join(fixture_dir, "out") + # CXXFLAGS that a C compile rejects check the C gch doesn't get them. + env = {**os.environ, "CXXFLAGS": "-std=c++17"} + for target in self.pch_targets: + obj = f"out/Default/obj.target/{target}" + gchs = [f"{obj}/pch-{lang}/{target}.h.gch" for lang in self.pch_langs] + for output in [*gchs, f"{obj}/pch_asm.o"]: + shutil.rmtree(os.path.join(makefile_dir, "out"), ignore_errors=True) + subprocess.run( + ["make", "-C", makefile_dir, output], env=env, check=True + ) + + def test_ninja_precompiled_prefix_header(self) -> None: + rc = gyp.main(["-f", "ninja", "--depth", fixture_dir, pch_prefix_gyp_file]) + assert rc == 0 + + build_dir = os.path.join(fixture_dir, "out", "Default") + for target in self.pch_targets: + gchs = [f"obj/{target}.{target}.h-{lang}.gch" for lang in self.pch_langs] + for output in [*gchs, f"obj/{target}.pch_asm.o"]: + subprocess.run(["ninja", "-C", build_dir, "-t", "clean"], check=True) + subprocess.run(["ninja", "-C", build_dir, output], check=True) + + class TestGypWindows(unittest.TestCase): def setUp(self) -> None: if sys.platform != "win32":