From b36175abdbbc0e71f6a2580cf74483c0c4b4e9dc Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Thu, 3 Apr 2025 16:56:28 -0700 Subject: [PATCH 1/2] Swift: Detect module triple from compiler On Swift versions later than 5.2, the compiler is able to specify target info, including the module triple among other properties about the underlying platform. Grab the triple and save it in the compiler info. --- Modules/CMakeDetermineSwiftCompiler.cmake | 13 +++++++++++++ Modules/CMakeSwiftCompiler.cmake.in | 2 ++ 2 files changed, 15 insertions(+) diff --git a/Modules/CMakeDetermineSwiftCompiler.cmake b/Modules/CMakeDetermineSwiftCompiler.cmake index a7dc1500e4..6d46e78f07 100644 --- a/Modules/CMakeDetermineSwiftCompiler.cmake +++ b/Modules/CMakeDetermineSwiftCompiler.cmake @@ -126,6 +126,19 @@ elseif(NOT DEFINED CMAKE_Swift_COMPILER_USE_OLD_DRIVER) unset(_CMAKE_Swift_COMPILER_CHECK_OUTPUT) endif() +if(CMAKE_Swift_COMPILER_VERSION VERSION_GREATER_EQUAL 5.2) + set(target_info_command "${CMAKE_Swift_COMPILER}" -print-target-info) + if(CMAKE_Swift_COMPILER_TARGET) + list(APPEND target_info_command -target ${CMAKE_Swift_COMPILER_TARGET}) + endif() + execute_process( + COMMAND ${target_info_command} + OUTPUT_VARIABLE swift_target_info) + message(CONFIGURE_LOG "Swift target info:\n" "${swift_target_info}") + string(JSON module_triple GET "${swift_target_info}" "target" "moduleTriple") + set(CMAKE_Swift_MODULE_TRIPLE ${module_triple}) +endif() + if (NOT _CMAKE_TOOLCHAIN_LOCATION) get_filename_component(_CMAKE_TOOLCHAIN_LOCATION "${CMAKE_Swift_COMPILER}" PATH) endif () diff --git a/Modules/CMakeSwiftCompiler.cmake.in b/Modules/CMakeSwiftCompiler.cmake.in index 92108804db..5e2483a02d 100644 --- a/Modules/CMakeSwiftCompiler.cmake.in +++ b/Modules/CMakeSwiftCompiler.cmake.in @@ -16,3 +16,5 @@ set(CMAKE_Swift_SOURCE_FILE_EXTENSIONS swift) set(CMAKE_Swift_COMPILER_USE_OLD_DRIVER "@CMAKE_Swift_COMPILER_USE_OLD_DRIVER@") set(CMAKE_Swift_IMPLICIT_INCLUDE_DIRECTORIES "@CMAKE_Swift_IMPLICIT_INCLUDE_DIRECTORIES@") + +set(CMAKE_Swift_MODULE_TRIPLE "@CMAKE_Swift_MODULE_TRIPLE@") From 1711e86d6c432cf2eb84be0b5125b0327c02231f Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Fri, 4 Apr 2025 11:03:03 -0700 Subject: [PATCH 2/2] Swift: Generate nested swift modules in build dir The nested Swift module structure is recommended for Swift projects. This structure has an outer directory called `.swiftmodule` that contains generated files for the interface. The binary swift modules, textual swift interface, and other supplemental outputs that make up the interface to a Swift library all go in this outer directory with the Swift module triple as the filename followed by the appropriate file extension based on what that file contains. Issue: #19284 --- Help/manual/cmake-policies.7.rst | 1 + Help/policy/CMP0195.rst | 27 +++++++++++++++++++ Source/cmGeneratorTarget.cxx | 9 ++++++- Source/cmPolicies.h | 9 +++++-- .../Swift/CMP0195-NEW-build-stdout.txt | 1 + Tests/RunCMake/Swift/CMP0195-NEW.cmake | 4 +++ .../Swift/CMP0195-OLD-build-stdout.txt | 1 + Tests/RunCMake/Swift/CMP0195-OLD.cmake | 4 +++ Tests/RunCMake/Swift/CMP0195-common.cmake | 6 +++++ .../Swift/CompileCommands-check.cmake | 25 ++++++++++------- Tests/RunCMake/Swift/CompileCommands.cmake | 4 +++ Tests/RunCMake/Swift/NoWorkToDo.cmake | 3 +++ Tests/RunCMake/Swift/RunCMakeTest.cmake | 15 +++++++++++ .../TargetPolicies/PolicyList-stderr.txt | 1 + Tests/SwiftOnly/CMakeLists.txt | 15 +++++++++-- 15 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 Help/policy/CMP0195.rst create mode 100644 Tests/RunCMake/Swift/CMP0195-NEW-build-stdout.txt create mode 100644 Tests/RunCMake/Swift/CMP0195-NEW.cmake create mode 100644 Tests/RunCMake/Swift/CMP0195-OLD-build-stdout.txt create mode 100644 Tests/RunCMake/Swift/CMP0195-OLD.cmake create mode 100644 Tests/RunCMake/Swift/CMP0195-common.cmake diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst index b9cf81ac13..c960828aca 100644 --- a/Help/manual/cmake-policies.7.rst +++ b/Help/manual/cmake-policies.7.rst @@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1 .. toctree:: :maxdepth: 1 + CMP0195: Swift modules in build trees use the Swift module directory structure. CMP0194: MSVC is not an assembler for language ASM. CMP0193: GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for install prefix '/'. CMP0192: GNUInstallDirs uses absolute SYSCONFDIR, LOCALSTATEDIR, and RUNSTATEDIR in special prefixes. diff --git a/Help/policy/CMP0195.rst b/Help/policy/CMP0195.rst new file mode 100644 index 0000000000..09f81aaa61 --- /dev/null +++ b/Help/policy/CMP0195.rst @@ -0,0 +1,27 @@ +CMP0195 +------- + +.. versionadded:: 4.1 + +Swift modules in build trees use the Swift module directory structure. + +The Swift compiler emits several supplementary files that make up the +interface to a Swift library. It accepts finding these files separately +or in a single swiftmodule directory. The single file keeps things better +organized and makes it easier to install the resulting products. + +CMake versions 4.1 and above prefer to generate the modules in the +directory structure when working with a new enough Swift compiler. +This policy provides compatibility for projects that have not been +updated to expect the new behavior. + +The ``OLD`` behavior for this policy is to emit the interface files directly +into the current binary directory. +The ``NEW`` behavior for this policy is to gather the binary swiftmodule and +other supplemental compiler outputs in a single Swift module directory. + +.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.1 +.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn +.. include:: include/STANDARD_ADVICE.rst + +.. include:: include/DEPRECATED.rst diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 2a4f347d1d..1b7841f421 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -6001,8 +6001,15 @@ std::string cmGeneratorTarget::GetSwiftModuleName() const std::string cmGeneratorTarget::GetSwiftModuleFileName() const { - return this->GetPropertyOrDefault( + std::string moduleFilename = this->GetPropertyOrDefault( "Swift_MODULE", this->GetSwiftModuleName() + ".swiftmodule"); + if (this->GetPolicyStatusCMP0195() == cmPolicies::NEW) { + if (cmValue moduleTriple = + this->Makefile->GetDefinition("CMAKE_Swift_MODULE_TRIPLE")) { + moduleFilename += "/" + *moduleTriple + ".swiftmodule"; + } + } + return moduleFilename; } std::string cmGeneratorTarget::GetSwiftModuleDirectory( diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 9b17f42416..c987e6fadd 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -580,7 +580,11 @@ class cmMakefile; "install prefix '/'.", \ 4, 1, 0, WARN) \ SELECT(POLICY, CMP194, "MSVC is not an assembler for language ASM.", 4, 1, \ - 0, WARN) + 0, WARN) \ + SELECT( \ + POLICY, CMP0195, \ + "Swift modules in build trees use the Swift module directory structure.", \ + 4, 1, 0, WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ @@ -627,7 +631,8 @@ class cmMakefile; F(CMP0162) \ F(CMP0179) \ F(CMP0181) \ - F(CMP0182) + F(CMP0182) \ + F(CMP0195) #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F) \ F(CMP0116) \ diff --git a/Tests/RunCMake/Swift/CMP0195-NEW-build-stdout.txt b/Tests/RunCMake/Swift/CMP0195-NEW-build-stdout.txt new file mode 100644 index 0000000000..94839b9056 --- /dev/null +++ b/Tests/RunCMake/Swift/CMP0195-NEW-build-stdout.txt @@ -0,0 +1 @@ +swiftc(.exe)? .* -emit-module -emit-module-path El(/|\\)((Debug|Release)(/|\\))?El\.swiftmodule(/|\\)[-_a-z0-9]+\.swiftmodule -module-name El diff --git a/Tests/RunCMake/Swift/CMP0195-NEW.cmake b/Tests/RunCMake/Swift/CMP0195-NEW.cmake new file mode 100644 index 0000000000..cf4ba38704 --- /dev/null +++ b/Tests/RunCMake/Swift/CMP0195-NEW.cmake @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 4.0) + +cmake_policy(SET CMP0195 NEW) +include(CMP0195-common.cmake) diff --git a/Tests/RunCMake/Swift/CMP0195-OLD-build-stdout.txt b/Tests/RunCMake/Swift/CMP0195-OLD-build-stdout.txt new file mode 100644 index 0000000000..1c0df06dd2 --- /dev/null +++ b/Tests/RunCMake/Swift/CMP0195-OLD-build-stdout.txt @@ -0,0 +1 @@ +swiftc(.exe)? .* -emit-module -emit-module-path El(/|\\)((Debug|Release)(/|\\))?El.swiftmodule -module-name El diff --git a/Tests/RunCMake/Swift/CMP0195-OLD.cmake b/Tests/RunCMake/Swift/CMP0195-OLD.cmake new file mode 100644 index 0000000000..b877c2fa8c --- /dev/null +++ b/Tests/RunCMake/Swift/CMP0195-OLD.cmake @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 4.0) + +cmake_policy(SET CMP0195 OLD) +include(CMP0195-common.cmake) diff --git a/Tests/RunCMake/Swift/CMP0195-common.cmake b/Tests/RunCMake/Swift/CMP0195-common.cmake new file mode 100644 index 0000000000..074e78c510 --- /dev/null +++ b/Tests/RunCMake/Swift/CMP0195-common.cmake @@ -0,0 +1,6 @@ +enable_language(Swift) + +add_library(L OBJECT L.swift) +set_target_properties(L PROPERTIES + Swift_MODULE_NAME El + Swift_MODULE_DIRECTORY El) diff --git a/Tests/RunCMake/Swift/CompileCommands-check.cmake b/Tests/RunCMake/Swift/CompileCommands-check.cmake index 0ce347a15f..5ab3b4b165 100644 --- a/Tests/RunCMake/Swift/CompileCommands-check.cmake +++ b/Tests/RunCMake/Swift/CompileCommands-check.cmake @@ -3,31 +3,32 @@ if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/compile_commands.json") return() endif() +set(ESCAPED_BINARY_DIR [==[[^ +]*/Tests/RunCMake/Swift/CompileCommands-build]==]) +set(E_SOURCE_PATH [==[(\\")?[^ +]*(/Tests/RunCMake/Swift/E.swift|\\\\Tests\\\\RunCMake\\\\Swift\\\\E.swift)(\\")?]==]) +set(L_SOURCE_PATH [==[(\\")?[^ +]*(/Tests/RunCMake/Swift/L.swift|\\\\Tests\\\\RunCMake\\\\Swift\\\\L.swift)(\\")?]==]) + # The compile command for both files should contain all Swift source files in # the module set(expected_compile_commands [==[^\[ { - "directory": "[^ -]*/Tests/RunCMake/Swift/CompileCommands-build", + "directory": "${BINARY_DIR}", "command": "[^ ]*swiftc[^ -]* (\\")?[^ -]*(/Tests/RunCMake/Swift/E.swift|\\\\Tests\\\\RunCMake\\\\Swift\\\\E.swift)(\\")? (\\")?[^ -]*(/Tests/RunCMake/Swift/L.swift|\\\\Tests\\\\RunCMake\\\\Swift\\\\L.swift)(\\")?", +]* ${E_SOURCE_PATH} ${L_SOURCE_PATH}", "file": "[^ ]*/Tests/RunCMake/Swift/E.swift", "output": "[^ ]*/CMakeFiles/CompileCommandLib.dir/(Debug/)?E.swift.(o|obj)" }, { - "directory": "[^ -]*/Tests/RunCMake/Swift/CompileCommands-build", + "directory": "${BINARY_DIR}", "command": "[^ ]*swiftc[^ -]* (\\")?[^ -]*(/Tests/RunCMake/Swift/E.swift|\\\\Tests\\\\RunCMake\\\\Swift\\\\E.swift)(\\")? (\\")?[^ -]*(/Tests/RunCMake/Swift/L.swift|\\\\Tests\\\\RunCMake\\\\Swift\\\\L.swift)(\\")?", +]* ${E_SOURCE_PATH} ${L_SOURCE_PATH}", "file": "[^ ]*/Tests/RunCMake/Swift/L.swift", "output": "[^ @@ -35,6 +36,10 @@ set(expected_compile_commands }]==] ) +string(REPLACE [=[${BINARY_DIR}]=] "${ESCAPED_BINARY_DIR}" expected_compile_commands "${expected_compile_commands}") +string(REPLACE [=[${E_SOURCE_PATH}]=] "${E_SOURCE_PATH}" expected_compile_commands "${expected_compile_commands}") +string(REPLACE [=[${L_SOURCE_PATH}]=] "${L_SOURCE_PATH}" expected_compile_commands "${expected_compile_commands}") + file(READ "${RunCMake_TEST_BINARY_DIR}/compile_commands.json" compile_commands) if(NOT compile_commands MATCHES "${expected_compile_commands}") string(REPLACE "\n" "\n " expected_compile_commands_formatted "${expected_compile_commands}") diff --git a/Tests/RunCMake/Swift/CompileCommands.cmake b/Tests/RunCMake/Swift/CompileCommands.cmake index f859693e17..f0a88dad2b 100644 --- a/Tests/RunCMake/Swift/CompileCommands.cmake +++ b/Tests/RunCMake/Swift/CompileCommands.cmake @@ -1,6 +1,10 @@ if(POLICY CMP0157) cmake_policy(SET CMP0157 NEW) endif() +if(POLICY CMP0195) + cmake_policy(SET CMP0195 NEW) +endif() + set(CMAKE_Swift_COMPILATION_MODE "singlefile") enable_language(Swift) diff --git a/Tests/RunCMake/Swift/NoWorkToDo.cmake b/Tests/RunCMake/Swift/NoWorkToDo.cmake index b58f8ffba8..750a40aece 100644 --- a/Tests/RunCMake/Swift/NoWorkToDo.cmake +++ b/Tests/RunCMake/Swift/NoWorkToDo.cmake @@ -1,4 +1,7 @@ cmake_policy(SET CMP0157 NEW) +if(POLICY CMP0195) + cmake_policy(SET CMP0195 NEW) +endif() enable_language(Swift) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/hello.swift "") diff --git a/Tests/RunCMake/Swift/RunCMakeTest.cmake b/Tests/RunCMake/Swift/RunCMakeTest.cmake index 716075e99f..63ad2323b7 100644 --- a/Tests/RunCMake/Swift/RunCMakeTest.cmake +++ b/Tests/RunCMake/Swift/RunCMakeTest.cmake @@ -30,6 +30,21 @@ block() endif() endblock() +if(RunCMake_GENERATOR MATCHES "Ninja.*") + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0195-NEW-build) + run_cmake(CMP0195-NEW) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(CMP0195-NEW-build ${CMAKE_COMMAND} --build . -- -n -v) + endblock() + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0195-OLD-build) + run_cmake(CMP0195-OLD) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(CMP0195-OLD-build ${CMAKE_COMMAND} --build . -- -n -v) + endblock() +endif() + block() set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/SwiftSimple-build) run_cmake(SwiftSimple) diff --git a/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt b/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt index 0e57ffd8cd..80f0047e4a 100644 --- a/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt +++ b/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt @@ -46,6 +46,7 @@ \* CMP0179 \* CMP0181 \* CMP0182 + \* CMP0195 Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\) diff --git a/Tests/SwiftOnly/CMakeLists.txt b/Tests/SwiftOnly/CMakeLists.txt index 494a9bf51f..01243848dd 100644 --- a/Tests/SwiftOnly/CMakeLists.txt +++ b/Tests/SwiftOnly/CMakeLists.txt @@ -5,6 +5,10 @@ endif() if(POLICY CMP0157) cmake_policy(SET CMP0157 NEW) endif() +if(POLICY CMP0195) + cmake_policy(SET CMP0195 NEW) + set(Swift_NESTED_MODULE TRUE) +endif() # NOTE: Force the Release mode configuration as there are some issues with the # debug information handling on macOS on certain Xcode builds. @@ -52,11 +56,18 @@ target_link_libraries(N PUBLIC # FIXME(#25989): The Xcode generator doesn't respect CMAKE_Swift_MODULE_DIRECTORY. if(NOT CMAKE_GENERATOR STREQUAL "Xcode") + if(Swift_NESTED_MODULE) + # Swift module is a directory, grab the binary swiftmodule file + set(ModulePath "${CMAKE_Swift_MODULE_DIRECTORY}/M.swiftmodule/${CMAKE_Swift_MODULE_TRIPLE}.swiftmodule") + else() + # Swift modules are files + set(ModulePath "${CMAKE_Swift_MODULE_DIRECTORY}/M.swiftmodule") + endif() add_custom_command(TARGET M POST_BUILD COMMAND "${CMAKE_COMMAND}" -E compare_files - "${CMAKE_Swift_MODULE_DIRECTORY}/M.swiftmodule" - "${CMAKE_Swift_MODULE_DIRECTORY}/M.swiftmodule" + "${ModulePath}" + "${ModulePath}" COMMENT "check that .swiftmodule files are generated in CMAKE_Swift_MODULE_DIRECTORY" VERBATIM) endif()