Browse Source

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
`<Swift_MODULE_NAME>.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
Evan Wilde 6 months ago
parent
commit
1711e86d6c

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1
 .. toctree::
 .. toctree::
    :maxdepth: 1
    :maxdepth: 1
 
 
+   CMP0195: Swift modules in build trees use the Swift module directory structure. </policy/CMP0195>
    CMP0194: MSVC is not an assembler for language ASM. </policy/CMP0194>
    CMP0194: MSVC is not an assembler for language ASM. </policy/CMP0194>
    CMP0193: GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for install prefix '/'. </policy/CMP0193>
    CMP0193: GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for install prefix '/'. </policy/CMP0193>
    CMP0192: GNUInstallDirs uses absolute SYSCONFDIR, LOCALSTATEDIR, and RUNSTATEDIR in special prefixes. </policy/CMP0192>
    CMP0192: GNUInstallDirs uses absolute SYSCONFDIR, LOCALSTATEDIR, and RUNSTATEDIR in special prefixes. </policy/CMP0192>

+ 27 - 0
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

+ 8 - 1
Source/cmGeneratorTarget.cxx

@@ -6001,8 +6001,15 @@ std::string cmGeneratorTarget::GetSwiftModuleName() const
 
 
 std::string cmGeneratorTarget::GetSwiftModuleFileName() const
 std::string cmGeneratorTarget::GetSwiftModuleFileName() const
 {
 {
-  return this->GetPropertyOrDefault(
+  std::string moduleFilename = this->GetPropertyOrDefault(
     "Swift_MODULE", this->GetSwiftModuleName() + ".swiftmodule");
     "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(
 std::string cmGeneratorTarget::GetSwiftModuleDirectory(

+ 7 - 2
Source/cmPolicies.h

@@ -580,7 +580,11 @@ class cmMakefile;
          "install prefix '/'.",                                               \
          "install prefix '/'.",                                               \
          4, 1, 0, WARN)                                                       \
          4, 1, 0, WARN)                                                       \
   SELECT(POLICY, CMP194, "MSVC is not an assembler for language ASM.", 4, 1,  \
   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_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
@@ -627,7 +631,8 @@ class cmMakefile;
   F(CMP0162)                                                                  \
   F(CMP0162)                                                                  \
   F(CMP0179)                                                                  \
   F(CMP0179)                                                                  \
   F(CMP0181)                                                                  \
   F(CMP0181)                                                                  \
-  F(CMP0182)
+  F(CMP0182)                                                                  \
+  F(CMP0195)
 
 
 #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F)                                  \
 #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F)                                  \
   F(CMP0116)                                                                  \
   F(CMP0116)                                                                  \

+ 1 - 0
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

+ 4 - 0
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)

+ 1 - 0
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

+ 4 - 0
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)

+ 6 - 0
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)

+ 15 - 10
Tests/RunCMake/Swift/CompileCommands-check.cmake

@@ -3,31 +3,32 @@ if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/compile_commands.json")
   return()
   return()
 endif()
 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 compile command for both files should contain all Swift source files in
 # the module
 # the module
 set(expected_compile_commands
 set(expected_compile_commands
 [==[^\[
 [==[^\[
 {
 {
-  "directory": "[^
-]*/Tests/RunCMake/Swift/CompileCommands-build",
+  "directory": "${BINARY_DIR}",
   "command": "[^
   "command": "[^
 ]*swiftc[^
 ]*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": "[^
   "file": "[^
 ]*/Tests/RunCMake/Swift/E.swift",
 ]*/Tests/RunCMake/Swift/E.swift",
   "output": "[^
   "output": "[^
 ]*/CMakeFiles/CompileCommandLib.dir/(Debug/)?E.swift.(o|obj)"
 ]*/CMakeFiles/CompileCommandLib.dir/(Debug/)?E.swift.(o|obj)"
 },
 },
 {
 {
-  "directory": "[^
-]*/Tests/RunCMake/Swift/CompileCommands-build",
+  "directory": "${BINARY_DIR}",
   "command": "[^
   "command": "[^
 ]*swiftc[^
 ]*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": "[^
   "file": "[^
 ]*/Tests/RunCMake/Swift/L.swift",
 ]*/Tests/RunCMake/Swift/L.swift",
   "output": "[^
   "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)
 file(READ "${RunCMake_TEST_BINARY_DIR}/compile_commands.json" compile_commands)
 if(NOT compile_commands MATCHES "${expected_compile_commands}")
 if(NOT compile_commands MATCHES "${expected_compile_commands}")
   string(REPLACE "\n" "\n  " expected_compile_commands_formatted "${expected_compile_commands}")
   string(REPLACE "\n" "\n  " expected_compile_commands_formatted "${expected_compile_commands}")

+ 4 - 0
Tests/RunCMake/Swift/CompileCommands.cmake

@@ -1,6 +1,10 @@
 if(POLICY CMP0157)
 if(POLICY CMP0157)
   cmake_policy(SET CMP0157 NEW)
   cmake_policy(SET CMP0157 NEW)
 endif()
 endif()
+if(POLICY CMP0195)
+  cmake_policy(SET CMP0195 NEW)
+endif()
+
 set(CMAKE_Swift_COMPILATION_MODE "singlefile")
 set(CMAKE_Swift_COMPILATION_MODE "singlefile")
 
 
 enable_language(Swift)
 enable_language(Swift)

+ 3 - 0
Tests/RunCMake/Swift/NoWorkToDo.cmake

@@ -1,4 +1,7 @@
 cmake_policy(SET CMP0157 NEW)
 cmake_policy(SET CMP0157 NEW)
+if(POLICY CMP0195)
+  cmake_policy(SET CMP0195 NEW)
+endif()
 enable_language(Swift)
 enable_language(Swift)
 
 
 file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/hello.swift "")
 file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/hello.swift "")

+ 15 - 0
Tests/RunCMake/Swift/RunCMakeTest.cmake

@@ -30,6 +30,21 @@ block()
   endif()
   endif()
 endblock()
 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()
 block()
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/SwiftSimple-build)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/SwiftSimple-build)
   run_cmake(SwiftSimple)
   run_cmake(SwiftSimple)

+ 1 - 0
Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt

@@ -46,6 +46,7 @@
    \* CMP0179
    \* CMP0179
    \* CMP0181
    \* CMP0181
    \* CMP0182
    \* CMP0182
+   \* CMP0195
 
 
 Call Stack \(most recent call first\):
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
   CMakeLists.txt:3 \(include\)

+ 13 - 2
Tests/SwiftOnly/CMakeLists.txt

@@ -5,6 +5,10 @@ endif()
 if(POLICY CMP0157)
 if(POLICY CMP0157)
   cmake_policy(SET CMP0157 NEW)
   cmake_policy(SET CMP0157 NEW)
 endif()
 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
 # NOTE: Force the Release mode configuration as there are some issues with the
 # debug information handling on macOS on certain Xcode builds.
 # 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.
 # FIXME(#25989): The Xcode generator doesn't respect CMAKE_Swift_MODULE_DIRECTORY.
 if(NOT CMAKE_GENERATOR STREQUAL "Xcode")
 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
   add_custom_command(TARGET M
     POST_BUILD
     POST_BUILD
     COMMAND "${CMAKE_COMMAND}" -E compare_files
     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"
     COMMENT "check that .swiftmodule files are generated in CMAKE_Swift_MODULE_DIRECTORY"
     VERBATIM)
     VERBATIM)
 endif()
 endif()