Swift/Ninja: Serialize compile edge after emit-module within target

Since commit b6367f723b (Swift/Ninja: Emit modules separately from
compilation, 2026-04-10) the compile and emit-module edges share the
same -output-file-map, which references a single module-level
.swiftdeps file.  When ninja schedules both edges concurrently, both
swift invocations write that file and corrupt it.

Add the .swiftmodule output as an order-only dependency of the object
compile edge so the two edges within a target run sequentially.
Downstream targets are unaffected: they still unblock as soon as the
emit-module edge finishes, preserving cross-target parallelism.

Move the object-build WriteBuild call to after the emit-module block
so the order-only dep is added after modBuild is copied from objBuild,
avoiding a self-reference on the emit-module edge's own output.

Issue: #27748
This commit is contained in:
Roman Lavrov
2026-04-29 10:42:37 -04:00
committed by Brad King
parent f15a0ea5d4
commit d741a4fb90
4 changed files with 55 additions and 5 deletions
+9 -5
View File
@@ -2276,11 +2276,6 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement(
objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
// Write object build
this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
objBuild,
this->ForceResponseFile() ? -1 : 0);
// Write a separate emit-module build edge that produces .swiftmodule
// without compile outputs. This allows downstream Swift targets to start
// compiling as soon as the module interface is ready, overlapping with
@@ -2311,7 +2306,16 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement(
this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
modBuild,
this->ForceResponseFile() ? -1 : 0);
// Both edges share the same -output-file-map; serialize the compile
// edge after emit-module so they do not race on the module .swiftdeps.
objBuild.OrderOnlyDeps.push_back(moduleFilepath);
}
// Write object build
this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
objBuild,
this->ForceResponseFile() ? -1 : 0);
}
void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
@@ -0,0 +1,28 @@
# The Swift object compile edge must order-only depend on the .swiftmodule
# produced by the separate emit-module edge. Both edges share the same
# -output-file-map; running them concurrently corrupts the module-level
# swift-dependencies file referenced by that map.
if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(path "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/impl-Debug.ninja")
else()
set(path "${RunCMake_TEST_BINARY_DIR}/build.ninja")
endif()
file(READ "${path}" build_ninja)
# Find the object compile edge for L and confirm L.swiftmodule appears
# after the order-only `||` separator. The edge ends at the next `build `
# at the start of a line (or end of file).
string(REGEX MATCH
"build [^\n]*L\\.swift\\.o[^\n]*:[^\n]*(\n [^\n]+)*"
obj_edge "${build_ninja}")
if(NOT obj_edge)
string(APPEND RunCMake_TEST_FAILED
"Could not find object compile edge for L.swift.\n")
return()
endif()
if(NOT obj_edge MATCHES "\\|\\|[^\n]*L\\.swiftmodule")
string(APPEND RunCMake_TEST_FAILED
"Object compile edge for L.swift is missing an order-only dependency on L.swiftmodule.\nEdge:\n${obj_edge}\n")
endif()
@@ -0,0 +1,14 @@
cmake_policy(SET CMP0157 NEW)
cmake_policy(SET CMP0215 NEW)
if(NOT CMAKE_GENERATOR MATCHES "Ninja")
message(SEND_ERROR "this test must use a Ninja generator, found ${CMAKE_GENERATOR}")
endif()
enable_language(Swift)
# The compile and emit-module edges share the same -output-file-map.
# Within the target, the object compile edge must order-only depend on the
# .swiftmodule so the two edges do not run concurrently and corrupt the
# shared module-level swift-dependencies entry.
add_library(L STATIC L.swift)
+4
View File
@@ -178,6 +178,10 @@ if(RunCMake_GENERATOR MATCHES "Ninja")
run_cmake_command(EmitModuleSeparatelyExistingModulePath-build ${CMAKE_COMMAND} --build . -- -vn)
endblock()
block()
run_cmake(EmitModuleSeparatelyOrdering)
endblock()
block()
if(CMAKE_SYSTEM_NAME MATCHES Windows)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ImportLibraryFlags-build)