diff --git a/pylib/gyp/generator/make.py b/pylib/gyp/generator/make.py index 16b6f4e8..9d4fdfdb 100644 --- a/pylib/gyp/generator/make.py +++ b/pylib/gyp/generator/make.py @@ -1477,6 +1477,27 @@ def WriteSources( self.WriteLn(f"{obj}: {gch}") self.WriteLn("# End precompiled header dependencies") + # A prefix header may include generated headers, so the .gch has to + # wait for the same generated inputs the objects wait for. The rules + # above give the objects these as order-only prerequisites; without + # the same rules here the .gch races code generation, and a clean + # build fails on a missing generated header. + gchs = sorted({gch for _, _, gch in pchdeps}) + if deps: + self.WriteMakeRule( + gchs, + deps, + comment="Make sure our dependencies are built before the pch.", + order_only=True, + ) + if extra_outputs: + self.WriteMakeRule( + gchs, + extra_outputs, + comment="Make sure our actions/rules run before the pch.", + order_only=True, + ) + if objs: extra_link_deps.append("$(OBJS)") self.WriteLn( diff --git a/test/fixtures/pch_prefix.gyp b/test/fixtures/pch_prefix.gyp new file mode 100644 index 00000000..09658481 --- /dev/null +++ b/test/fixtures/pch_prefix.gyp @@ -0,0 +1,23 @@ +{ + 'targets': [ + { + 'target_name': 'pch_prefix', + 'type': 'executable', + 'sources': [ 'pch.cc' ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'pch.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + # A prefix header is allowed to include generated headers, so the .gch has + # to wait for this target's actions the same way its objects do. + 'actions': [ + { + 'action_name': 'make_header', + 'inputs': [], + 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/generated.h' ], + 'action': [ 'touch', '<(SHARED_INTERMEDIATE_DIR)/generated.h' ], + }, + ], + }, + ] +} diff --git a/test/integration_test.py b/test/integration_test.py index 8244708a..41da0797 100644 --- a/test/integration_test.py +++ b/test/integration_test.py @@ -13,6 +13,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 +81,36 @@ def test_cmake(self) -> None: assert_file(self, "out/Default/CMakeLists.txt", "cmake/CMakeLists.txt") +class TestGypMac(unittest.TestCase): + 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_prefix_header_waits_for_generated_inputs(self) -> None: + rc = gyp.main( + [ + "-f", + "make", + "--depth", + fixture_dir, + "--generator-output", + "out", + pch_prefix_gyp_file, + ] + ) + assert rc == 0 + + with open(os.path.join(fixture_dir, "out/pch_prefix.target.mk")) as in_file: + mk = in_file.read() + + gch = "$(obj).target/$(TARGET)/pch-cc/pch.h.gch" + assert f"$(obj).target/$(TARGET)/pch.o: {gch}" in mk + # A prefix header may include generated headers, so the .gch must carry + # the same order-only prerequisites the objects carry. + self.assertRegex(mk, re.escape(f"{gch}: |") + r".*make_header") + + class TestGypWindows(unittest.TestCase): def setUp(self) -> None: if sys.platform != "win32":