From b6367f723bae0a9624e3bf431a77e32d1dee6f97 Mon Sep 17 00:00:00 2001 From: Roman Lavrov Date: Fri, 10 Apr 2026 16:16:02 -0400 Subject: [PATCH] Swift/Ninja: Emit modules separately from compilation Add policy CMP0215 to emit `.swiftmodule` from a dedicated `-emit-module` build edge for importable Swift targets in the Ninja generator. Downstream targets can begin compiling as soon as the module interface is ready, overlapping with upstream object compilation and linking. When CMP0215 is NEW and CMP0157 is NEW, separate module emission is enabled by default. The `Swift_SEPARATE_MODULE_EMISSION` target property (initialized by the `CMAKE_Swift_SEPARATE_MODULE_EMISSION` variable) can override this per target. The compile edge still passes `-emit-module` so that swiftc continues to produce `.swiftdoc`; only the `.swiftmodule` output moves to the new edge. Also fix swiftmodule dependency edges in cmNinjaNormalTargetGenerator to use IsLanguageUsed instead of GetLinkerLanguage, so C/C++ targets whose linker language propagated to Swift do not produce spurious implicit dependencies. Closes: #27748 --- Auxiliary/vim/syntax/cmake.vim | 2 + Help/manual/cmake-policies.7.rst | 1 + Help/manual/cmake-properties.7.rst | 1 + Help/manual/cmake-variables.7.rst | 1 + Help/policy/CMP0215.rst | 39 +++++ .../Swift_SEPARATE_MODULE_EMISSION.rst | 24 +++ .../swift-ninja-separate-swiftmodule-edge.rst | 8 + .../CMAKE_Swift_SEPARATE_MODULE_EMISSION.rst | 24 +++ Modules/CMakeSwiftInformation.cmake | 4 + Source/cmNinjaNormalTargetGenerator.cxx | 7 +- Source/cmNinjaTargetGenerator.cxx | 156 ++++++++++++++---- Source/cmNinjaTargetGenerator.h | 2 + Source/cmPolicies.h | 8 +- Source/cmTarget.cxx | 1 + .../EmitModuleSeparately-build-check.cmake | 9 + .../RunCMake/Swift/EmitModuleSeparately.cmake | 12 ++ ...leSeparatelyResponseFile-build-check.cmake | 11 ++ .../EmitModuleSeparatelyResponseFile.cmake | 14 ++ Tests/RunCMake/Swift/LClient.swift | 2 + Tests/RunCMake/Swift/RunCMakeTest.cmake | 16 ++ .../TargetPolicies/PolicyList-stderr.txt | 1 + 21 files changed, 308 insertions(+), 35 deletions(-) create mode 100644 Help/policy/CMP0215.rst create mode 100644 Help/prop_tgt/Swift_SEPARATE_MODULE_EMISSION.rst create mode 100644 Help/release/dev/swift-ninja-separate-swiftmodule-edge.rst create mode 100644 Help/variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION.rst create mode 100644 Tests/RunCMake/Swift/EmitModuleSeparately-build-check.cmake create mode 100644 Tests/RunCMake/Swift/EmitModuleSeparately.cmake create mode 100644 Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile-build-check.cmake create mode 100644 Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile.cmake create mode 100644 Tests/RunCMake/Swift/LClient.swift diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim index 280dd9bd27..fb3d64a90d 100644 --- a/Auxiliary/vim/syntax/cmake.vim +++ b/Auxiliary/vim/syntax/cmake.vim @@ -415,6 +415,7 @@ syn keyword cmakeProperty contained \ Swift_LANGUAGE_VERSION \ Swift_MODULE_DIRECTORY \ Swift_MODULE_NAME + \ Swift_SEPARATE_MODULE_EMISSION \ TARGET_ARCHIVES_MAY_BE_SHARED_LIBS \ TARGET_MESSAGES \ TARGET_SUPPORTS_SHARED_LIBS @@ -2037,6 +2038,7 @@ syn keyword cmakeVariable contained \ CMAKE_Swift_NUM_THREADS \ CMAKE_Swift_OUTPUT_EXTENSION \ CMAKE_Swift_PLATFORM_ID + \ CMAKE_Swift_SEPARATE_MODULE_EMISSION \ CMAKE_Swift_SIMULATE_ID \ CMAKE_Swift_SIMULATE_VERSION \ CMAKE_Swift_SIZEOF_DATA_PTR diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst index 3cf433528d..f21586c4d1 100644 --- a/Help/manual/cmake-policies.7.rst +++ b/Help/manual/cmake-policies.7.rst @@ -100,6 +100,7 @@ Policies Introduced by CMake 4.4 .. toctree:: :maxdepth: 1 + CMP0215: Ninja generators emit Swift modules separately from compilation. CMP0214: Honor CMAKE_EXE_LINKER_FLAGS for Swift executable targets. CMP0213: file(ARCHIVE_{CREATE,EXTRACT}) encode archive paths as UTF-8 by default. CMP0212: add_custom_command DEPENDS does not strip .exe suffixes. diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index c8ddc04bdd..4ec3b6e17f 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -427,6 +427,7 @@ Properties on Targets /prop_tgt/Swift_LANGUAGE_VERSION /prop_tgt/Swift_MODULE_DIRECTORY /prop_tgt/Swift_MODULE_NAME + /prop_tgt/Swift_SEPARATE_MODULE_EMISSION /prop_tgt/SYMBOLIC /prop_tgt/SYSTEM /prop_tgt/TEST_LAUNCHER diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 4be024d403..a2ddcefd67 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -142,6 +142,7 @@ Variables that Provide Information /variable/CMAKE_Swift_COMPILATION_MODE /variable/CMAKE_Swift_MODULE_DIRECTORY /variable/CMAKE_Swift_NUM_THREADS + /variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION /variable/CMAKE_TEST_LAUNCHER /variable/CMAKE_TOOLCHAIN_FILE /variable/CMAKE_TWEAK_VERSION diff --git a/Help/policy/CMP0215.rst b/Help/policy/CMP0215.rst new file mode 100644 index 0000000000..7573597e31 --- /dev/null +++ b/Help/policy/CMP0215.rst @@ -0,0 +1,39 @@ +CMP0215 +------- + +.. versionadded:: 4.4 + +:ref:`Ninja Generators` emit Swift modules separately from compilation. + +In CMake 4.3 and below, Swift targets built by :ref:`Ninja Generators` +emit ``.swiftmodule`` from the Swift compile edge together with object files. +Because Ninja treats a build edge as atomic, downstream Swift targets +must wait for compilation outputs to finish before they can use the module +interface. + +CMake 4.4 and above prefer to emit ``.swiftmodule`` from a dedicated +``-emit-module`` build edge so that downstream Swift targets can begin +compiling as soon as the module interface is available. This policy +provides compatibility with projects that have not been updated. + +The ``OLD`` behavior for this policy is to not emit modules separately. +The ``NEW`` behavior for this policy is to emit ``.swiftmodule`` from a +dedicated build edge for importable Swift targets by default. + +The :prop_tgt:`Swift_SEPARATE_MODULE_EMISSION` target property and +corresponding :variable:`CMAKE_Swift_SEPARATE_MODULE_EMISSION` variable +may be set to enable or disable the behavior on a per-target basis. + +.. note:: + + Separate module emission takes effect only when policy :policy:`CMP0157` + is set to ``NEW`` prior to the first :command:`project` or + :command:`enable_language` command that enables the Swift language. + The dedicated ``.swiftmodule`` edge is available only in the split + Swift build model. + +.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.4 +.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn +.. include:: include/STANDARD_ADVICE.rst + +.. include:: include/DEPRECATED.rst diff --git a/Help/prop_tgt/Swift_SEPARATE_MODULE_EMISSION.rst b/Help/prop_tgt/Swift_SEPARATE_MODULE_EMISSION.rst new file mode 100644 index 0000000000..81c61b67cb --- /dev/null +++ b/Help/prop_tgt/Swift_SEPARATE_MODULE_EMISSION.rst @@ -0,0 +1,24 @@ +Swift_SEPARATE_MODULE_EMISSION +------------------------------ + +.. versionadded:: 4.4 + +Emit Swift modules separately from compilation in the :ref:`Ninja Generators`. + +When this property is set to ``ON`` on an importable Swift target built by +:ref:`Ninja Generators`, CMake emits a separate ``-emit-module`` build edge +that produces the ``.swiftmodule`` independently of object compilation. +This allows downstream Swift targets to begin compiling as soon as the +module interface is available, rather than waiting for all upstream +compilation outputs. + +The property is initialized from the value of the +:variable:`CMAKE_Swift_SEPARATE_MODULE_EMISSION` variable, if it is set. +If this property is not set, separate module emission is enabled by default. +See policy :policy:`CMP0215`. + +.. note:: + + Separate module emission takes effect only when policy :policy:`CMP0157` + is set to ``NEW`` prior to the first :command:`project` or + :command:`enable_language` command that enables the Swift language. diff --git a/Help/release/dev/swift-ninja-separate-swiftmodule-edge.rst b/Help/release/dev/swift-ninja-separate-swiftmodule-edge.rst new file mode 100644 index 0000000000..4f2c07c29f --- /dev/null +++ b/Help/release/dev/swift-ninja-separate-swiftmodule-edge.rst @@ -0,0 +1,8 @@ +swift-ninja-separate-swiftmodule-edge +------------------------------------- + +* The :ref:`Ninja Generators` gained the + :prop_tgt:`Swift_SEPARATE_MODULE_EMISSION` target property, initialized by + the :variable:`CMAKE_Swift_SEPARATE_MODULE_EMISSION` variable, to control + whether importable Swift targets emit ``.swiftmodule`` from a dedicated + build edge. Policy :policy:`CMP0215` enables this by default. diff --git a/Help/variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION.rst b/Help/variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION.rst new file mode 100644 index 0000000000..d5ebcb0ca0 --- /dev/null +++ b/Help/variable/CMAKE_Swift_SEPARATE_MODULE_EMISSION.rst @@ -0,0 +1,24 @@ +CMAKE_Swift_SEPARATE_MODULE_EMISSION +------------------------------------- + +.. versionadded:: 4.4 + +Specify whether Swift targets emit ``.swiftmodule`` from a dedicated build +edge. This variable is used to initialize the +:prop_tgt:`Swift_SEPARATE_MODULE_EMISSION` property on targets as they are +created. + +When set to ``ON``, importable Swift targets built by :ref:`Ninja Generators` +produce the ``.swiftmodule`` from a separate ``-emit-module`` build edge, +allowing downstream Swift targets to compile in parallel with upstream +object compilation. + +If this variable is not set, separate module emission is enabled by default +in targets that do not set :prop_tgt:`Swift_SEPARATE_MODULE_EMISSION`. +See policy :policy:`CMP0215`. + +.. note:: + + Separate module emission takes effect only when policy :policy:`CMP0157` + is set to ``NEW`` prior to the first :command:`project` or + :command:`enable_language` command that enables the Swift language. diff --git a/Modules/CMakeSwiftInformation.cmake b/Modules/CMakeSwiftInformation.cmake index bafc6ce5fb..d6d96ece35 100644 --- a/Modules/CMakeSwiftInformation.cmake +++ b/Modules/CMakeSwiftInformation.cmake @@ -124,6 +124,10 @@ if(CMAKE_Swift_COMPILATION_MODE_DEFAULT) set(CMAKE_Swift_COMPILE_OBJECT " ${CMAKE_Swift_PARALLEL_FLAGS} -c ") endif() + if(NOT CMAKE_Swift_EMIT_MODULE) + set(CMAKE_Swift_EMIT_MODULE " ${CMAKE_Swift_PARALLEL_FLAGS} -emit-module ") + endif() + if(NOT CMAKE_Swift_CREATE_SHARED_LIBRARY) set(CMAKE_Swift_CREATE_SHARED_LIBRARY " ${CMAKE_Swift_PARALLEL_FLAGS} -emit-library ${CMAKE_Swift_IMPLIB_LINKER_FLAGS} -o ") endif() diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index e144e8d847..c9ce1df0a6 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -1639,10 +1639,11 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement( if (cmComputeLinkInformation* cli = this->GeneratorTarget->GetLinkInformation(config)) { for (auto const& dependency : cli->GetItems()) { - // Both the current target and the linked target must be swift targets - // in order for there to be a swiftmodule to depend on + // Only depend on swiftmodule from targets that actually compile + // Swift sources. A C/C++ target may have Swift as its linker + // language (due to language propagation) without producing one. if (dependency.Target && - dependency.Target->GetLinkerLanguage(config) == "Swift") { + dependency.Target->IsLanguageUsed("Swift", config)) { std::string swiftmodule = this->ConvertToNinjaPath( dependency.Target->GetSwiftModulePath(config)); linkBuild.ImplicitDeps.emplace_back(swiftmodule); diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index 480fedd9d9..bda7740cc6 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -130,6 +130,15 @@ std::string cmNinjaTargetGenerator::LanguageCompilerRule( withScanning == WithScanning::Yes ? "_scanned_" : "_unscanned_", config); } +std::string cmNinjaTargetGenerator::LanguageEmitModuleRule( + std::string const& lang, std::string const& config) const +{ + return cmStrCat( + lang, "_EMIT_MODULE__", + cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()), + '_', config); +} + std::string cmNinjaTargetGenerator::LanguagePreprocessAndScanRule( std::string const& lang, std::string const& config) const { @@ -1033,6 +1042,40 @@ void cmNinjaTargetGenerator::WriteCompileRule(std::string const& lang, rule.Comment = cmStrCat("Rule for compiling ", lang, " files."); rule.Description = cmStrCat("Building ", lang, " object $out"); this->GetGlobalGenerator()->AddRule(rule); + + // Write a separate emit-module rule for Swift (produces .swiftmodule + // without compile outputs, enabling downstream modules to compile in + // parallel with upstream compilation). + if (lang == "Swift" && withScanning == WithScanning::No) { + std::string const emitModCmdVar = "CMAKE_Swift_EMIT_MODULE"; + cmValue emitModCmdVal = mf->GetDefinition(emitModCmdVar); + if (emitModCmdVal) { + cmNinjaRule emitModRule(this->LanguageEmitModuleRule(lang, config)); + cmRulePlaceholderExpander::RuleVariables emVars = vars; + std::string emFlags = "$FLAGS"; + if (!responseFlag.empty()) { + // Reset placeholders after compile response-file setup. + emVars.Source = "$in"; + emVars.Object = "$out"; + emVars.Defines = "$DEFINES"; + emVars.Includes = "$INCLUDES"; + SetupResponseFile(emitModRule, emVars, emFlags, lang, responseFlag); + } + + emVars.Flags = emFlags.c_str(); + emitModRule.Restat = "1"; + + cmList emitModCmds = ExpandRuleCommands( + *emitModCmdVal, emVars, mf, lang, launcher, this->GetLocalGenerator(), + rulePlaceholderExpander.get()); + emitModRule.Command = this->GetLocalGenerator()->BuildCommandLine( + emitModCmds, config, config); + emitModRule.Comment = "Rule for emitting Swift .swiftmodule files."; + emitModRule.Description = + cmStrCat("Emitting Swift .swiftmodule ", "$out"); + this->GetGlobalGenerator()->AddRule(emitModRule); + } + } } void cmNinjaTargetGenerator::WriteObjectBuildStatements( @@ -2033,7 +2076,8 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( // - Definitions // - Include paths // - (single-output) output object filename - // - Swiftmodule + // - Swiftmodule (for importable targets), produced either by the compile + // edge or by a separate emit-module edge // // Per-File: // - compile-command @@ -2095,18 +2139,6 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( return !isMultiThread && compileMode == cmSwiftCompileMode::Wholemodule; }(); - // Without `-emit-library` or `-emit-executable`, targets with a single - // source file parse as a Swift script instead of like normal source. For - // non-executable targets, append this to ensure that they are parsed like a - // normal source. - if (target.GetType() != cmStateEnums::EXECUTABLE) { - this->LocalGenerator->AppendFlags(vars["FLAGS"], "-parse-as-library"); - } - - if (target.GetType() == cmStateEnums::STATIC_LIBRARY) { - this->LocalGenerator->AppendFlags(vars["FLAGS"], "-static"); - } - // Does this swift target emit a module file for importing into other // targets? auto isImportableTarget = [](cmGeneratorTarget const& tgt) -> bool { @@ -2117,7 +2149,34 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( } return true; }; + bool const targetIsImportable = isImportableTarget(target); + // Check if we can emit the module separately (produces .swiftmodule before + // compilation finishes, enabling downstream modules to compile in parallel). + bool const emitModuleSeparately = [&]() -> bool { + if (!targetIsImportable || + !this->GetMakefile()->GetDefinition("CMAKE_Swift_EMIT_MODULE")) { + return false; + } + cmValue prop = + this->GeneratorTarget->GetProperty("Swift_SEPARATE_MODULE_EMISSION"); + if (prop) { + return prop.IsOn(); + } + return this->GeneratorTarget->GetPolicyStatusCMP0215() == cmPolicies::NEW; + }(); + + // Build flags common to both compile and emit-module edges. + if (target.GetType() != cmStateEnums::EXECUTABLE) { + // Without `-emit-library` or `-emit-executable`, targets with a single + // source file parse as a Swift script instead of like normal source. For + // non-executable targets, append this to ensure that they are parsed like + // a normal source. + this->LocalGenerator->AppendFlags(vars["FLAGS"], "-parse-as-library"); + } + if (target.GetType() == cmStateEnums::STATIC_LIBRARY) { + this->LocalGenerator->AppendFlags(vars["FLAGS"], "-static"); + } this->LocalGenerator->AppendFlags(vars["FLAGS"], cmStrCat("-module-name ", moduleName)); @@ -2128,23 +2187,8 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( this->LocalGenerator->AppendFlags( vars["FLAGS"], cmStrCat(libraryLinkNameFlag, ' ', libraryLinkName)); } - this->LocalGenerator->AppendFlags(vars["FLAGS"], this->GetFlags(language, config)); - - // Swift modules only make sense to emit from things that can be imported. - // Executables that don't export symbols can't be imported, so don't try to - // emit a swiftmodule for them. It will break. - if (isImportableTarget(target)) { - std::string const emitModuleFlag = "-emit-module"; - std::string const modulePathFlag = "-emit-module-path"; - this->LocalGenerator->AppendFlags( - vars["FLAGS"], - { emitModuleFlag, modulePathFlag, - this->LocalGenerator->ConvertToOutputFormat( - moduleFilepath, cmOutputConverter::SHELL) }); - objBuild.Outputs.push_back(moduleFilepath); - } vars["DEFINES"] = this->GetDefines(language, config); vars["INCLUDES"] = this->GetIncludes(language, config); vars["CONFIG"] = config; @@ -2155,9 +2199,14 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( this->GetGlobalGenerator()->GetLanguageOutputExtension(language))); objBuild.RspFile = cmStrCat(targetObjectFilename, ".swift.rsp"); + // Importable targets keep -emit-module on compile so swiftc still emits + // .swiftdoc. When splitting module emission, only the .swiftmodule output + // moves to the separate emit-module edge. + if (targetIsImportable && !emitModuleSeparately) { + objBuild.Outputs.push_back(moduleFilepath); + } + if (isSingleOutput) { - this->LocalGenerator->AppendFlags(vars["FLAGS"], - cmStrCat("-o ", targetObjectFilename)); objBuild.Outputs.push_back(targetObjectFilename); this->Configs[config].Objects.push_back(targetObjectFilename); } @@ -2184,6 +2233,25 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]); } + // Save common flags for the emit-module edge before adding + // compile-specific flags (-emit-module, -emit-module-path, -o). + std::string const commonFlags = vars["FLAGS"]; + + std::string const moduleOutputPath = + this->LocalGenerator->ConvertToOutputFormat(moduleFilepath, + cmOutputConverter::SHELL); + if (targetIsImportable) { + std::string const emitModuleFlag = "-emit-module"; + std::string const modulePathFlag = "-emit-module-path"; + this->LocalGenerator->AppendFlags( + vars["FLAGS"], { emitModuleFlag, modulePathFlag, moduleOutputPath }); + } + + if (isSingleOutput) { + this->LocalGenerator->AppendFlags(vars["FLAGS"], + cmStrCat("-o ", targetObjectFilename)); + } + if (firstForConfig) { this->ExportSwiftObjectCompileCommand( sources, targetObjectFilename, vars["FLAGS"], vars["DEFINES"], @@ -2211,6 +2279,34 @@ void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement( 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 + // upstream compilation and linking. + if (emitModuleSeparately) { + cmNinjaBuild modBuild = objBuild; + modBuild.Rule = this->LanguageEmitModuleRule(language, config); + + // Start from common flags (shared with compile edge) and add + // emit-module-specific flags. The emit-module rule template already + // contains -emit-module, so we only need -emit-module-path here. + modBuild.Variables["FLAGS"] = commonFlags; + this->LocalGenerator->AppendFlags( + modBuild.Variables["FLAGS"], + cmStrCat("-emit-module-path ", moduleOutputPath)); + + modBuild.RspFile = cmStrCat(moduleFilepath, ".rsp"); + + // Output is just the .swiftmodule + this->EnsureParentDirectoryExists(moduleFilepath); + modBuild.Outputs.clear(); + modBuild.Outputs.push_back(moduleFilepath); + + this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig), + modBuild, + this->ForceResponseFile() ? -1 : 0); + } } void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang, diff --git a/Source/cmNinjaTargetGenerator.h b/Source/cmNinjaTargetGenerator.h index 39d3026f31..c706e75958 100644 --- a/Source/cmNinjaTargetGenerator.h +++ b/Source/cmNinjaTargetGenerator.h @@ -78,6 +78,8 @@ protected: std::string LanguageCompilerRule(std::string const& lang, std::string const& config, WithScanning withScanning) const; + std::string LanguageEmitModuleRule(std::string const& lang, + std::string const& config) const; std::string LanguagePreprocessAndScanRule(std::string const& lang, std::string const& config) const; std::string LanguageScanRule(std::string const& lang, diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index f31dc6b11b..4259c6250b 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -642,7 +642,10 @@ class cmMakefile; 4, 4, 0, WARN) \ SELECT(POLICY, CMP0214, \ "Honor CMAKE_EXE_LINKER_FLAGS for Swift executable targets.", 4, 4, \ - 0, WARN) + 0, WARN) \ + SELECT(POLICY, CMP0215, \ + "Ninja generators emit Swift modules separately from compilation.", \ + 4, 4, 0, WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ @@ -699,7 +702,8 @@ class cmMakefile; F(CMP0209) \ F(CMP0210) \ F(CMP0211) \ - F(CMP0214) + F(CMP0214) \ + F(CMP0215) #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F) \ F(CMP0116) \ diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 94f081ba33..8500f027a4 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -381,6 +381,7 @@ TargetProperty const StaticTargetProperties[] = { { "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources }, { "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources }, { "Swift_COMPILATION_MODE"_s, IC::CanCompileSources }, + { "Swift_SEPARATE_MODULE_EMISSION"_s, IC::CanCompileSources }, // ---- Rust { "Rust_EDITION"_s, IC::CanCompileSources }, { "Rust_MAIN_CRATE_ROOT"_s, IC::CanCompileSources }, diff --git a/Tests/RunCMake/Swift/EmitModuleSeparately-build-check.cmake b/Tests/RunCMake/Swift/EmitModuleSeparately-build-check.cmake new file mode 100644 index 0000000000..ec60bf801b --- /dev/null +++ b/Tests/RunCMake/Swift/EmitModuleSeparately-build-check.cmake @@ -0,0 +1,9 @@ +string( + REGEX MATCHALL + "swiftc(\\.exe)?\"? [^\n]* -emit-module-path [^\n]*L\\.swiftmodule" + swift_module_commands "${actual_stdout}") +list(LENGTH swift_module_commands swift_module_command_count) +if(swift_module_command_count LESS 2) + string(APPEND RunCMake_TEST_FAILED + "Expected separate compile and emit-module commands for L, found ${swift_module_command_count} command(s) with '-emit-module-path ... L.swiftmodule'.\n") +endif() diff --git a/Tests/RunCMake/Swift/EmitModuleSeparately.cmake b/Tests/RunCMake/Swift/EmitModuleSeparately.cmake new file mode 100644 index 0000000000..d5ac09c2de --- /dev/null +++ b/Tests/RunCMake/Swift/EmitModuleSeparately.cmake @@ -0,0 +1,12 @@ +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) + +add_library(L STATIC L.swift) +add_library(LClient STATIC LClient.swift) +target_link_libraries(LClient PRIVATE L) diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile-build-check.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile-build-check.cmake new file mode 100644 index 0000000000..c9d58ee957 --- /dev/null +++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile-build-check.cmake @@ -0,0 +1,11 @@ +if(NOT actual_stdout MATCHES + "swiftc(\\.exe)?\"? [^\n]* -c @CMakeFiles(/|\\\\)L\\.dir(/|\\\\)(Debug(/|\\\\))?L\\.o(bj)?\\.swift\\.rsp") + string(APPEND RunCMake_TEST_FAILED + "No Swift compile response-file command found for target L.\n") +endif() + +if(NOT actual_stdout MATCHES + "swiftc(\\.exe)?\"? [^\n]* -emit-module @.*L\\.swiftmodule\\.rsp") + string(APPEND RunCMake_TEST_FAILED + "No Swift emit-module response-file command found for target L.\n") +endif() diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile.cmake new file mode 100644 index 0000000000..fa405d4546 --- /dev/null +++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyResponseFile.cmake @@ -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() + +set(CMAKE_NINJA_FORCE_RESPONSE_FILE TRUE) + +enable_language(Swift) + +add_library(L STATIC L.swift) +add_library(LClient STATIC LClient.swift) +target_link_libraries(LClient PRIVATE L) diff --git a/Tests/RunCMake/Swift/LClient.swift b/Tests/RunCMake/Swift/LClient.swift new file mode 100644 index 0000000000..709f4e484d --- /dev/null +++ b/Tests/RunCMake/Swift/LClient.swift @@ -0,0 +1,2 @@ +import L +public func consume() -> Int { return ThirtyTwo } diff --git a/Tests/RunCMake/Swift/RunCMakeTest.cmake b/Tests/RunCMake/Swift/RunCMakeTest.cmake index d2ebf48cb4..21f4f359e9 100644 --- a/Tests/RunCMake/Swift/RunCMakeTest.cmake +++ b/Tests/RunCMake/Swift/RunCMakeTest.cmake @@ -146,6 +146,22 @@ if(RunCMake_GENERATOR MATCHES "Ninja") run_cmake_command(ForceResponseFile-check ${CMAKE_COMMAND} --build . -- -vn) endblock() + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/EmitModuleSeparately-build) + run_cmake(EmitModuleSeparately) + set(RunCMake_TEST_NO_CLEAN 1) + # -v: verbose to capture executed commands -n: dry-run to avoid actually compiling + run_cmake_command(EmitModuleSeparately-build ${CMAKE_COMMAND} --build . -- -vn) + endblock() + + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/EmitModuleSeparatelyResponseFile-build) + run_cmake(EmitModuleSeparatelyResponseFile) + set(RunCMake_TEST_NO_CLEAN 1) + # -v: verbose to capture executed commands -n: dry-run to avoid actually compiling + run_cmake_command(EmitModuleSeparatelyResponseFile-build ${CMAKE_COMMAND} --build . -- -vn) + endblock() + block() if(CMAKE_SYSTEM_NAME MATCHES Windows) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ImportLibraryFlags-build) diff --git a/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt b/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt index c61e3d2647..c239c984cc 100644 --- a/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt +++ b/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt @@ -56,6 +56,7 @@ \* CMP0210 \* CMP0211 \* CMP0214 + \* CMP0215 Call Stack \(most recent call first\): CMakeLists\.txt:3 \(include\)