Swift: Add abstraction for compilation mode

Add a `CMAKE_Swift_COMPILATION_MODE` variable and corresponding
`Swift_COMPILATION_MODE` target property to control the compilation
mode.  Select among `wholemodule`, `singlefile`, and `incremental`.

Add policy CMP0157 to remove the default `-wmo` flags in favor of the
abstract setting.

Issue: #25366
This commit is contained in:
Evan Wilde 2023-11-05 16:36:28 -08:00 committed by Brad King
parent c39384f540
commit c1d787e473
23 changed files with 353 additions and 22 deletions

View File

@ -374,6 +374,7 @@ syn keyword cmakeProperty contained
\ Swift_LANGUAGE_VERSION
\ Swift_MODULE_DIRECTORY
\ Swift_MODULE_NAME
\ Swift_COMPILATION_MODE
\ TARGET_ARCHIVES_MAY_BE_SHARED_LIBS
\ TARGET_MESSAGES
\ TARGET_SUPPORTS_SHARED_LIBS

View File

@ -57,6 +57,7 @@ Policies Introduced by CMake 3.29
.. toctree::
:maxdepth: 1
CMP0157: Swift compilation mode is selected by an abstraction. </policy/CMP0157>
CMP0156: De-duplicate libraries on link lines based on linker capabilities. </policy/CMP0156>
Policies Introduced by CMake 3.28

View File

@ -392,6 +392,7 @@ Properties on Targets
/prop_tgt/STATIC_LIBRARY_FLAGS_CONFIG
/prop_tgt/STATIC_LIBRARY_OPTIONS
/prop_tgt/SUFFIX
/prop_tgt/Swift_COMPILATION_MODE
/prop_tgt/Swift_DEPENDENCIES_FILE
/prop_tgt/Swift_LANGUAGE_VERSION
/prop_tgt/Swift_MODULE_DIRECTORY

View File

@ -115,6 +115,7 @@ Variables that Provide Information
/variable/CMAKE_SOURCE_DIR
/variable/CMAKE_STATIC_LIBRARY_PREFIX
/variable/CMAKE_STATIC_LIBRARY_SUFFIX
/variable/CMAKE_Swift_COMPILATION_MODE
/variable/CMAKE_Swift_MODULE_DIRECTORY
/variable/CMAKE_Swift_NUM_THREADS
/variable/CMAKE_TOOLCHAIN_FILE

48
Help/policy/CMP0157.rst Normal file
View File

@ -0,0 +1,48 @@
CMP0157
-------
.. versionadded:: 3.29
Swift compilation mode is selected by an abstraction.
The Swift compiler can compile modules in different modes. The desired build
mode depends whether the developer is iterating and wants to incrementally make
changes, or if they are building a release for distribution and want more
optimizations applied to the resulting binary.
CMake versions 3.26 through 3.28 build Swift binaries with whole-module
optimizations enabled when configured in a non-debug build type.
For CMake versions earlier than 3.26, the developer needs to specify
the necessary flag manually for the :ref:`Ninja Generators`, and cannot
not specify whole-module optimizations to the :generator:`Xcode` generator.
CMake versions 3.29 and above prefer to set the compilation mode using
the :prop_tgt:`Swift_COMPILATION_MODE` target property, which can be
initialized by the :variable:`CMAKE_Swift_COMPILATION_MODE` variable.
This policy provides compatibility for projects that have not been updated.
The policy setting takes effect as of the first :command:`project` or
:command:`enable_language` command that enables the ``Swift`` language.
.. note::
Once the policy has taken effect at the top of a project, that choice
must be used throughout the tree. In projects that have nested projects
in subdirectories, be sure to convert everything together.
The ``OLD`` behavior for this policy builds all Swift targets in
``wholemodule`` mode for non-debug configurations. :ref:`Ninja Generators`
prepend the ``-wmo`` flag to the default set of Swift flags.
The :generator:`Xcode` generator sets the ``SWIFT_COMPILATION_MODE``
attribute to ``wholemodule`` in the generated Xcode project file.
The ``NEW`` behavior for this policy is to apply the compilation mode specified
in the :prop_tgt:`Swift_COMPILATION_MODE` target property, initialized as each
target is created by the :variable:`CMAKE_Swift_COMPILATION_MODE` variable.
This policy was introduced in CMake version 3.29. Use the
:command:`cmake_policy` command to set it to ``OLD`` or ``NEW`` explicitly.
Unlike many policies, CMake version |release| does *not* warn when this policy
is not set and simply uses ``OLD`` behavior.
.. include:: DEPRECATED.txt

View File

@ -0,0 +1,19 @@
``incremental``
Compiles each Swift source in the module separately, resulting in better
parallelism in the build. The compiler emits additional information into
the build directory improving rebuild performance when small changes are made
to the source between rebuilds. This is the best option to use while
iterating on changes in a project.
``wholemodule``
Whole-module optimizations are slowest to compile, but results in the most
optimized library. The entire context is loaded into once instance of the
compiler, so there is no parallelism across source files in the module.
``singlefile``
Compiles each source in a Swift modules separately, resulting in better
parallelism. Unlike the ``incremental`` build mode, no additional information
is emitted by the compiler during the build, so rebuilding after making small
changes to the source file will not run faster. This option should be used
sparingly, preferring ``incremental`` builds, unless working around a compiler
bug.

View File

@ -0,0 +1,33 @@
Swift_COMPILATION_MODE
----------------------
.. versionadded:: 3.29
Specify how Swift compiles a target.
The allowed values are:
.. include:: Swift_COMPILATION_MODE-VALUES.txt
Use :manual:`generator expressions <cmake-generator-expressions(7)>` to support
per-configuration specification. For example, the code:
.. code-block:: cmake
add_library(foo foo.swift)
set_property(TARGET foo PROPERTY
Swift_COMPILATION_MODE "$<IF:$<CONFIG:Release>,wholemodule,incremental>")
sets the Swift compilation mode to wholemodule mode in the release configuration
and sets the property to incremental mode in other configurations.
The property is initialized from the value of the
:variable:`CMAKE_Swift_COMPILATION_MODE` variable, if it is set. If the property
is not set or is empty, then CMake uses the default value ``incremental`` to
specify the swift compilation mode.
.. note::
This property only has effect when policy :policy:`CMP0157` is set to ``NEW``
prior to the first :command:`project` or :command:`enable_language` command
that enables the Swift language.

View File

@ -0,0 +1,32 @@
CMAKE_Swift_COMPILATION_MODE
----------------------------
.. versionadded:: 3.29
Specify how Swift compiles a target. This variable is used to initialize the
:prop_tgt:`Swift_COMPILATION_MODE` property on targets as they are created.
The allowed values are:
.. include:: ../prop_tgt/Swift_COMPILATION_MODE-VALUES.txt
Use :manual:`generator expressions <cmake-generator-expressions(7)>` to support
per-configuration specification. For example, the code:
.. code-block:: cmake
set(CMAKE_Swift_COMPILATION_MODE
"$<IF:$<CONFIG:Release>,wholemodule,incremental>")
sets the default Swift compilation mode to wholemodule mode when building a
release configuration and to incremental mode in other configurations.
If this variable is not set then the :prop_tgt:`Swift_COMPILATION_MODE` target
property will not be set automatically. If that property is unset then CMake
uses the default value ``incremental`` to build the Swift source files.
.. note::
This property only has effect when policy :policy:`CMP0157` is set to ``NEW``
prior to the first :command:`project` or :command:`enable_language` command
that enables the Swift language.

View File

@ -68,30 +68,42 @@ set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDLL -libc MD)
set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebug -libc MTd)
set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebugDLL -libc MDd)
set(CMAKE_Swift_FLAGS_DEBUG_INIT "-Onone -g")
set(CMAKE_Swift_FLAGS_RELEASE_INIT "-O")
set(CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT "-O -g")
set(CMAKE_Swift_FLAGS_MINSIZEREL_INIT "-Osize")
if(CMAKE_GENERATOR STREQUAL "Xcode")
string(APPEND CMAKE_Swift_FLAGS_DEBUG_INIT " ${CMAKE_Swift_FLAGS_DEBUG_LINKER_FLAGS}")
string(APPEND CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT " ${CMAKE_Swift_FLAGS_RELWITHDEBINFO_LINKER_FLAGS}")
endif()
# Warns if unset and uses old policy.
# Old policy flag-smashes the wmo and incremental flags onto the compiler flags.
# New policy respects the Swift_COMPILATION_MODE target property to add
# incremental and wholemodule optimization flags as appropriate.
cmake_policy(GET CMP0157 __SWIFT_COMP_MODE_CMP0157)
if(__SWIFT_COMP_MODE_CMP0157 STREQUAL "NEW")
set(CMAKE_Swift_COMPILATION_MODE_DEFAULT "incremental")
else()
# Xcode has a separate Xcode project option (SWIFT_COMPILATION_MODE) used to set
# whether compiling with whole-module optimizations or incrementally. Setting
# these options here will have no effect when compiling with the built-in driver,
# and will explode violently, leaving build products in the source directory, when
# using the old swift driver.
set(CMAKE_Swift_FLAGS_DEBUG_INIT "-Onone -g ${CMAKE_Swift_FLAGS_DEBUG_LINKER_FLAGS}")
set(CMAKE_Swift_FLAGS_RELEASE_INIT "-O")
set(CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT "-O -g ${CMAKE_Swift_FLAGS_RELWITHDEBINFO_LINKER_FLAGS}")
set(CMAKE_Swift_FLAGS_MINSIZEREL_INIT "-Osize")
else()
set(CMAKE_Swift_FLAGS_DEBUG_INIT "-Onone -g -incremental")
set(CMAKE_Swift_FLAGS_RELEASE_INIT "-O")
set(CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT "-O -g")
set(CMAKE_Swift_FLAGS_MINSIZEREL_INIT "-Osize")
# Enable Whole Module Optimization by default unless the old
# C++ driver is being used, which behaves differently under WMO.
if(NOT CMAKE_Swift_COMPILER_USE_OLD_DRIVER)
string(APPEND CMAKE_Swift_FLAGS_RELEASE_INIT " -wmo")
string(APPEND CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT " -wmo")
string(APPEND CMAKE_Swift_FLAGS_MINSIZEREL_INIT " -wmo")
# using the old swift driver. Don't append `-incremental` or `-wmo` to the
# flags in the Xcode generator.
if(NOT CMAKE_GENERATOR STREQUAL "Xcode")
# Enable Whole Module Optimization by default unless the old
# C++ driver is being used, which behaves differently under WMO.
if(NOT CMAKE_Swift_COMPILER_USE_OLD_DRIVER)
string(APPEND CMAKE_Swift_FLAGS_RELEASE_INIT " -wmo")
string(APPEND CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT " -wmo")
string(APPEND CMAKE_Swift_FLAGS_MINSIZEREL_INIT " -wmo")
endif()
string(APPEND CMAKE_Swift_FLAGS_DEBUG_INIT " -incremental")
endif()
endif()
unset(__SWIFT_COMP_MODE_CMP0157)
if(CMAKE_EXECUTABLE_FORMAT STREQUAL "ELF")
if(NOT DEFINED CMAKE_Swift_LINK_WHAT_YOU_USE_FLAG)

View File

@ -904,6 +904,14 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode(
? "NEW"
: "OLD");
/* Set the appropriate policy information for Swift compilation mode */
fprintf(
fout, "cmake_policy(SET CMP0157 %s)\n",
this->Makefile->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT")
.IsEmpty()
? "OLD"
: "NEW");
// Workaround for -Wl,-headerpad_max_install_names issue until we can avoid
// adding that flag in the platform and compiler language files
fprintf(fout,

View File

@ -2479,6 +2479,25 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
buildSettings->AddAttribute("SWIFT_ACTIVE_COMPILATION_CONDITIONS",
swiftDefs.CreateList());
}
if (cm::optional<cmSwiftCompileMode> swiftCompileMode =
this->CurrentLocalGenerator->GetSwiftCompileMode(gtgt, configName)) {
switch (*swiftCompileMode) {
case cmSwiftCompileMode::Wholemodule:
buildSettings->AddAttribute("SWIFT_COMPILATION_MODE",
this->CreateString("wholemodule"));
break;
case cmSwiftCompileMode::Incremental:
case cmSwiftCompileMode::Singlefile:
break;
case cmSwiftCompileMode::Unknown:
this->CurrentLocalGenerator->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat("Unknown Swift_COMPILATION_MODE on target '",
gtgt->GetName(), "'"));
break;
}
}
}
std::string extraLinkOptionsVar;
@ -4610,6 +4629,10 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects(
buildSettings->AddAttribute("CODE_SIGNING_ALLOWED",
this->CreateString("NO"));
}
// This code supports the OLD behavior of CMP0157. We should be able to
// remove computing the debug configuration set once the old behavior is
// removed.
auto debugConfigs = this->GetCMakeInstance()->GetDebugConfigs();
std::set<std::string> debugConfigSet(debugConfigs.begin(),
debugConfigs.end());
@ -4619,9 +4642,16 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects(
cmXCodeObject* buildSettingsForCfg = this->CreateFlatClone(buildSettings);
if (debugConfigSet.count(cmSystemTools::UpperCase(config.first)) == 0) {
buildSettingsForCfg->AddAttribute("SWIFT_COMPILATION_MODE",
this->CreateString("wholemodule"));
// Supports the OLD behavior of CMP0157. CMP0157 OLD behavior globally set
// wholemodule compilation for all non-debug configurations, for all
// targets.
if (this->CurrentMakefile
->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT")
.IsEmpty()) {
if (debugConfigSet.count(cmSystemTools::UpperCase(config.first)) == 0) {
buildSettingsForCfg->AddAttribute("SWIFT_COMPILATION_MODE",
this->CreateString("wholemodule"));
}
}
// Put this last so it can override existing settings

View File

@ -1651,6 +1651,39 @@ std::vector<BT<std::string>> cmLocalGenerator::GetTargetCompileFlags(
if (lang == "Fortran") {
this->AppendFlags(compileFlags,
this->GetTargetFortranFlags(target, config));
} else if (lang == "Swift") {
// Only set the compile mode if CMP0157 is set
if (cm::optional<cmSwiftCompileMode> swiftCompileMode =
this->GetSwiftCompileMode(target, config)) {
std::string swiftCompileModeFlag;
switch (*swiftCompileMode) {
case cmSwiftCompileMode::Incremental: {
swiftCompileModeFlag = "-incremental";
if (cmValue flag =
mf->GetDefinition("CMAKE_Swift_COMPILE_OPTIONS_INCREMENTAL")) {
swiftCompileModeFlag = *flag;
}
break;
}
case cmSwiftCompileMode::Wholemodule: {
swiftCompileModeFlag = "-wmo";
if (cmValue flag =
mf->GetDefinition("CMAKE_Swift_COMPILE_OPTIONS_WMO")) {
swiftCompileModeFlag = *flag;
}
break;
}
case cmSwiftCompileMode::Singlefile:
break;
case cmSwiftCompileMode::Unknown: {
this->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat("Unknown Swift_COMPILATION_MODE on target '",
target->GetName(), "'"));
}
}
this->AppendFlags(compileFlags, swiftCompileModeFlag);
}
}
this->AddCMP0018Flags(compileFlags, target, lang, config);
@ -2955,6 +2988,34 @@ cm::optional<std::string> cmLocalGenerator::GetMSVCDebugFormatName(
return msvcDebugInformationFormat;
}
cm::optional<cmSwiftCompileMode> cmLocalGenerator::GetSwiftCompileMode(
cmGeneratorTarget const* target, std::string const& config)
{
cmMakefile const* mf = this->GetMakefile();
cmValue const swiftCompileModeDefault =
mf->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT");
if (!cmNonempty(swiftCompileModeDefault)) {
return {};
}
cmValue swiftCompileMode = target->GetProperty("Swift_COMPILATION_MODE");
if (!swiftCompileMode) {
swiftCompileMode = swiftCompileModeDefault;
}
std::string const expandedCompileMode =
cmGeneratorExpression::Evaluate(*swiftCompileMode, this, config, target);
if (expandedCompileMode == "wholemodule") {
return cmSwiftCompileMode::Wholemodule;
}
if (expandedCompileMode == "singlefile") {
return cmSwiftCompileMode::Singlefile;
}
if (expandedCompileMode == "incremental") {
return cmSwiftCompileMode::Incremental;
}
return cmSwiftCompileMode::Unknown;
}
namespace {
inline void RegisterUnitySources(cmGeneratorTarget* target, cmSourceFile* sf,

View File

@ -67,6 +67,15 @@ enum class cmBuildStep
Link
};
/** What compilation mode the swift files are in */
enum class cmSwiftCompileMode
{
Wholemodule,
Incremental,
Singlefile,
Unknown,
};
/** Target and source file which have a specific output. */
struct cmSourcesWithOutput
{
@ -549,6 +558,10 @@ public:
const std::string& prop,
const std::string& config);
// Return Swift_COMPILATION_MODE value if CMP0157 is NEW.
cm::optional<cmSwiftCompileMode> GetSwiftCompileMode(
cmGeneratorTarget const* target, std::string const& config);
protected:
// The default implementation converts to a Windows shortpath to
// help older toolchains handle spaces and such. A generator may

View File

@ -477,7 +477,10 @@ class cmMakefile;
SELECT( \
POLICY, CMP0156, \
"De-duplicate libraries on link lines based on linker capabilities.", 3, \
29, 0, cmPolicies::WARN)
29, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0157, \
"Swift compilation mode selected by an abstraction.", 3, 29, 0, \
cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
@ -518,7 +521,8 @@ class cmMakefile;
F(CMP0142) \
F(CMP0154) \
F(CMP0155) \
F(CMP0156)
F(CMP0156) \
F(CMP0157)
#define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F) \
F(CMP0116) \

View File

@ -438,6 +438,7 @@ TargetProperty const StaticTargetProperties[] = {
// ---- Swift
{ "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources },
{ "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources },
{ "Swift_COMPILATION_MODE"_s, IC::CanCompileSources },
// ---- moc
{ "AUTOMOC"_s, IC::CanCompileSources },
{ "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources },

View File

@ -0,0 +1,3 @@
CMake Warning \(dev\) in CMakeLists.txt:
Unknown Swift_COMPILATION_MODE on target 'greetings_who_knows'
This warning is for project developers. Use -Wno-dev to suppress it.

View File

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.28)
cmake_policy(SET CMP0157 NEW)
include(CMP0157-common.cmake)

View File

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.28)
cmake_policy(SET CMP0157 OLD)
include(CMP0157-common.cmake)

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.28)
include(CMP0157-common.cmake)

View File

@ -0,0 +1,19 @@
enable_language(Swift)
add_executable(greetings_default hello.swift)
add_executable(greetings_wmo hello.swift)
set_target_properties(greetings_wmo PROPERTIES
Swift_COMPILATION_MODE "wholemodule")
add_executable(greetings_incremental hello.swift)
set_target_properties(greetings_incremental PROPERTIES
Swift_COMPILATION_MODE "incremental")
add_executable(greetings_singlefile hello.swift)
set_target_properties(greetings_singlefile PROPERTIES
Swift_COMPILATION_MODE "singlefile")
add_executable(greetings_who_knows hello.swift)
set_target_properties(greetings_who_knows PROPERTIES
Swift_COMPILATION_MODE "not-a-real-mode")

View File

@ -3,6 +3,10 @@ include(RunCMake)
if(RunCMake_GENERATOR STREQUAL Xcode)
if(XCODE_VERSION VERSION_LESS 6.1)
run_cmake(XcodeTooOld)
elseif(CMake_TEST_Swift)
run_cmake(CMP0157-NEW)
run_cmake(CMP0157-OLD)
run_cmake(CMP0157-WARN)
endif()
elseif(RunCMake_GENERATOR STREQUAL Ninja)
if(CMake_TEST_Swift)
@ -45,11 +49,19 @@ elseif(RunCMake_GENERATOR STREQUAL Ninja)
run_cmake_command(IncrementalSwift-second ${CMAKE_COMMAND} --build ${IncrementalSwift_TEST_BINARY_DIR} -- -d explain)
endblock()
run_cmake(CMP0157-NEW)
run_cmake(CMP0157-OLD)
run_cmake(CMP0157-WARN)
endif()
elseif(RunCMake_GENERATOR STREQUAL "Ninja Multi-Config")
if(CMake_TEST_Swift)
set(RunCMake_TEST_OPTIONS "-DCMAKE_CONFIGURATION_TYPES=Debug\\;Release")
run_cmake(SwiftSimple)
run_cmake(CMP0157-NEW)
run_cmake(CMP0157-OLD)
run_cmake(CMP0157-WARN)
unset(RunCMake_TEST_OPTIONS)
endif()
else()

View File

@ -40,6 +40,7 @@
\* CMP0154
\* CMP0155
\* CMP0156
\* CMP0157
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.3)
if(POLICY CMP0126)
cmake_policy(SET CMP0126 NEW)
endif()
if(POLICY CMP0157)
cmake_policy(SET CMP0157 NEW)
endif()
# NOTE: Force the Release mode configuration as there are some issues with the
# debug information handling on macOS on certain Xcode builds.
@ -54,3 +57,20 @@ target_link_libraries(SwiftOnly PRIVATE SwiftIface)
if(CMAKE_Swift_COMPILER_VERSION VERSION_GREATER_EQUAL 5.2)
add_subdirectory("SwiftPlugin")
endif()
function(test_cmp0157_default mode)
cmake_policy(GET CMP0157 cmp0157_wmo)
if(cmp0157_wmo STREQUAL "NEW")
set(CMAKE_Swift_COMPILATION_MODE "${mode}")
add_executable(hi_${mode} main.swift)
get_target_property(${mode}_swift_comp_mode hi_${mode} "Swift_COMPILATION_MODE")
if(NOT ${mode}_swift_comp_mode STREQUAL ${mode})
message(SEND_ERROR "expected ${mode} -- found ${${mode}_swift_comp_mode}")
endif()
endif()
endfunction()
test_cmp0157_default("wholemodule")
test_cmp0157_default("incremental")
test_cmp0157_default("singlefile")