Browse Source

Merge topic 'swift-split-compilation-model'

9bed4f4d81 Swift/Ninja: Split compilation model
64b3367845 cmGlobalGenerator: Allow passing language to GetLangaugeOutputExtension

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !8907
Brad King 2 years ago
parent
commit
ba41ca758a

+ 51 - 21
Modules/CMakeSwiftInformation.cmake

@@ -116,36 +116,66 @@ endif()
 
 cmake_initialize_per_config_variable(CMAKE_Swift_FLAGS "Swift Compiler Flags")
 
-# NOTE(compnerd) we do not have an object compile rule since we build the objects as part of the link step
-if(NOT CMAKE_Swift_COMPILE_OBJECT)
-  set(CMAKE_Swift_COMPILE_OBJECT ":")
-endif()
-
 if(NOT CMAKE_Swift_NUM_THREADS MATCHES "^[0-9]+$")
   cmake_host_system_information(RESULT CMAKE_Swift_NUM_THREADS QUERY NUMBER_OF_LOGICAL_CORES)
 endif()
 
-if(NOT CMAKE_Swift_CREATE_SHARED_LIBRARY)
-  set(CMAKE_Swift_CREATE_SHARED_LIBRARY "<CMAKE_Swift_COMPILER> -j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS} -emit-library -o <TARGET> -module-name <SWIFT_MODULE_NAME> -module-link-name <SWIFT_LIBRARY_NAME> -emit-module -emit-module-path <SWIFT_MODULE> -emit-dependencies <DEFINES> <FLAGS> <INCLUDES> <SWIFT_SOURCES> <LINK_FLAGS> <SONAME_FLAG> <TARGET_INSTALLNAME_DIR><TARGET_SONAME> ${CMAKE_Swift_IMPLIB_LINKER_FLAGS} <LINK_LIBRARIES>")
-endif()
+# Swift split-compilation requires CMP0157 NEW policy
+if(CMAKE_Swift_COMPILATION_MODE_DEFAULT)
+  set(CMAKE_Swift_PARALLEL_FLAGS "-j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS}")
+  if(NOT CMAKE_Swift_COMPILE_OBJECT)
+    # Omit the object output. The output is controlled by the output-file-map
+    # for normal builds. For wholemodule builds, CMake appends the appropriate
+    # flags.
+    set(CMAKE_Swift_COMPILE_OBJECT "<CMAKE_Swift_COMPILER> ${CMAKE_Swift_PARALLEL_FLAGS} -c <DEFINES> <FLAGS> <INCLUDES> <SOURCE>")
+  endif()
 
-if(NOT CMAKE_Swift_CREATE_SHARED_MODULE)
-  set(CMAKE_Swift_CREATE_SHARED_MODULE ${CMAKE_Swift_CREATE_SHARED_LIBRARY})
-endif()
+  if(NOT CMAKE_Swift_CREATE_SHARED_LIBRARY)
+    set(CMAKE_Swift_CREATE_SHARED_LIBRARY "<CMAKE_Swift_COMPILER> ${CMAKE_Swift_PARALLEL_FLAGS} -emit-library <CMAKE_SHARED_LIBRARY_Swift_FLAGS> <LANGUAGE_COMPILE_FLAGS> <LINK_FLAGS> <SONAME_FLAG> <TARGET_SONAME> -o <TARGET> <OBJECTS> <LINK_LIBRARIES>")
+  endif()
 
-if(NOT CMAKE_Swift_LINK_EXECUTABLE)
-  set(CMAKE_Swift_LINK_EXECUTABLE "<CMAKE_Swift_COMPILER> -j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS} -emit-executable -o <TARGET> -emit-dependencies <DEFINES> <FLAGS> <INCLUDES> <SWIFT_SOURCES> <LINK_FLAGS> <LINK_LIBRARIES>")
-endif()
+  if(NOT CMAKE_Swift_CREATE_SHARED_MODULE)
+    set(CMAKE_Swift_CREATE_SHARED_MODULE ${CMAKE_Swift_CREATE_SHARED_LIBRARY})
+  endif()
 
-if(NOT CMAKE_Swift_LINK_EXECUTABLE_WITH_EXPORTS)
-  set(CMAKE_Swift_LINK_EXECUTABLE_WITH_EXPORTS "${CMAKE_Swift_LINK_EXECUTABLE} -emit-module -emit-module-path <SWIFT_MODULE> ${CMAKE_Swift_IMPLIB_LINKER_FLAGS}")
-endif()
+  if(NOT CMAKE_Swift_LINK_EXECUTABLE)
+    set(CMAKE_Swift_LINK_EXECUTABLE "<CMAKE_Swift_COMPILER> ${CMAKE_Swift_PARALLEL_FLAGS} -emit-executable -o <TARGET> <FLAGS> <OBJECTS> <LINK_FLAGS> <LINK_LIBRARIES>")
+  endif()
 
-if(NOT CMAKE_Swift_CREATE_STATIC_LIBRARY)
-  set(CMAKE_Swift_CREATE_STATIC_LIBRARY "<CMAKE_Swift_COMPILER> -j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS} -emit-library -static -o <TARGET> -module-name <SWIFT_MODULE_NAME> -module-link-name <SWIFT_LIBRARY_NAME> -emit-module -emit-module-path <SWIFT_MODULE> -emit-dependencies <DEFINES> <FLAGS> <INCLUDES> <SWIFT_SOURCES> <LINK_FLAGS> <LINK_LIBRARIES>")
+  if(NOT CMAKE_Swift_CREATE_STATIC_LIBRARY)
+    set(CMAKE_Swift_CREATE_STATIC_LIBRARY "<CMAKE_Swift_COMPILER> -emit-library -static -o <TARGET> <OBJECTS> <LINK_FLAGS>")
+    set(CMAKE_Swift_ARCHIVE_CREATE "<CMAKE_AR> crs <TARGET> <OBJECTS>")
+    set(CMAKE_Swift_ARCHIVE_FINISH "")
+  endif()
+  unset(CMAKE_Swift_PARALLEL_FLAGS)
+else()
+  # NOTE(compnerd) we do not have an object compile rule since we build the objects as part of the link step
+  if(NOT CMAKE_Swift_COMPILE_OBJECT)
+    set(CMAKE_Swift_COMPILE_OBJECT ":")
+  endif()
+
+  if(NOT CMAKE_Swift_CREATE_SHARED_LIBRARY)
+    set(CMAKE_Swift_CREATE_SHARED_LIBRARY "<CMAKE_Swift_COMPILER> -j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS} -emit-library -o <TARGET> -module-name <SWIFT_MODULE_NAME> -module-link-name <SWIFT_LIBRARY_NAME> -emit-module -emit-module-path <SWIFT_MODULE> -emit-dependencies <DEFINES> <FLAGS> <INCLUDES> <SWIFT_SOURCES> <LINK_FLAGS> <SONAME_FLAG> <TARGET_INSTALLNAME_DIR><TARGET_SONAME> ${CMAKE_Swift_IMPLIB_LINKER_FLAGS} <LINK_LIBRARIES>")
+  endif()
 
-  set(CMAKE_Swift_ARCHIVE_CREATE "<CMAKE_AR> crs <TARGET> <OBJECTS>")
-  set(CMAKE_Swift_ARCHIVE_FINISH "")
+  if(NOT CMAKE_Swift_CREATE_SHARED_MODULE)
+    set(CMAKE_Swift_CREATE_SHARED_MODULE ${CMAKE_Swift_CREATE_SHARED_LIBRARY})
+  endif()
+
+  if(NOT CMAKE_Swift_LINK_EXECUTABLE)
+    set(CMAKE_Swift_LINK_EXECUTABLE "<CMAKE_Swift_COMPILER> -j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS} -emit-executable -o <TARGET> -emit-dependencies <DEFINES> <FLAGS> <INCLUDES> <SWIFT_SOURCES> <LINK_FLAGS> <LINK_LIBRARIES>")
+  endif()
+
+  if(NOT CMAKE_Swift_LINK_EXECUTABLE_WITH_EXPORTS)
+    set(CMAKE_Swift_LINK_EXECUTABLE_WITH_EXPORTS "${CMAKE_Swift_LINK_EXECUTABLE} -emit-module -emit-module-path <SWIFT_MODULE> ${CMAKE_Swift_IMPLIB_LINKER_FLAGS}")
+  endif()
+
+  if(NOT CMAKE_Swift_CREATE_STATIC_LIBRARY)
+    set(CMAKE_Swift_CREATE_STATIC_LIBRARY "<CMAKE_Swift_COMPILER> -j ${CMAKE_Swift_NUM_THREADS} -num-threads ${CMAKE_Swift_NUM_THREADS} -emit-library -static -o <TARGET> -module-name <SWIFT_MODULE_NAME> -module-link-name <SWIFT_LIBRARY_NAME> -emit-module -emit-module-path <SWIFT_MODULE> -emit-dependencies <DEFINES> <FLAGS> <INCLUDES> <SWIFT_SOURCES> <LINK_FLAGS> <LINK_LIBRARIES>")
+
+    set(CMAKE_Swift_ARCHIVE_CREATE "<CMAKE_AR> crs <TARGET> <OBJECTS>")
+    set(CMAKE_Swift_ARCHIVE_FINISH "")
+  endif()
 endif()
 
 set(CMAKE_Swift_INFORMATION_LOADED 1)

+ 19 - 13
Source/cmGlobalGenerator.cxx

@@ -1158,24 +1158,30 @@ std::string cmGlobalGenerator::GetLanguageOutputExtension(
 {
   const std::string& lang = source.GetLanguage();
   if (!lang.empty()) {
-    auto const it = this->LanguageToOutputExtension.find(lang);
-    if (it != this->LanguageToOutputExtension.end()) {
-      return it->second;
-    }
-  } else {
-    // if no language is found then check to see if it is already an
-    // output extension for some language.  In that case it should be ignored
-    // and in this map, so it will not be compiled but will just be used.
-    std::string const& ext = source.GetExtension();
-    if (!ext.empty()) {
-      if (this->OutputExtensions.count(ext)) {
-        return ext;
-      }
+    return this->GetLanguageOutputExtension(lang);
+  }
+  // if no language is found then check to see if it is already an
+  // output extension for some language.  In that case it should be ignored
+  // and in this map, so it will not be compiled but will just be used.
+  std::string const& ext = source.GetExtension();
+  if (!ext.empty()) {
+    if (this->OutputExtensions.count(ext)) {
+      return ext;
     }
   }
   return "";
 }
 
+std::string cmGlobalGenerator::GetLanguageOutputExtension(
+  std::string const& lang) const
+{
+  auto const it = this->LanguageToOutputExtension.find(lang);
+  if (it != this->LanguageToOutputExtension.end()) {
+    return it->second;
+  }
+  return "";
+}
+
 std::string cmGlobalGenerator::GetLanguageFromExtension(const char* ext) const
 {
   // if there is an extension and it starts with . then move past the

+ 2 - 0
Source/cmGlobalGenerator.h

@@ -349,6 +349,8 @@ public:
   int GetLinkerPreference(const std::string& lang) const;
   //! What is the object file extension for a given source file?
   std::string GetLanguageOutputExtension(cmSourceFile const&) const;
+  //! What is the object file extension for a given language?
+  std::string GetLanguageOutputExtension(std::string const& lang) const;
 
   //! What is the configurations directory variable called?
   virtual const char* GetCMakeCFGIntDir() const { return "."; }

+ 7 - 1
Source/cmLocalGenerator.cxx

@@ -1366,7 +1366,7 @@ std::vector<BT<std::string>> cmLocalGenerator::GetStaticLibraryFlags(
 {
   const std::string configUpper = cmSystemTools::UpperCase(config);
   std::vector<BT<std::string>> flags;
-  if (linkLanguage != "Swift") {
+  if (linkLanguage != "Swift" && !this->IsSplitSwiftBuild()) {
     std::string staticLibFlags;
     this->AppendFlags(
       staticLibFlags,
@@ -3020,6 +3020,12 @@ cm::optional<cmSwiftCompileMode> cmLocalGenerator::GetSwiftCompileMode(
   return cmSwiftCompileMode::Unknown;
 }
 
+bool cmLocalGenerator::IsSplitSwiftBuild() const
+{
+  return cmNonempty(this->GetMakefile()->GetDefinition(
+    "CMAKE_Swift_COMPILATION_MODE_DEFAULT"));
+}
+
 namespace {
 
 inline void RegisterUnitySources(cmGeneratorTarget* target, cmSourceFile* sf,

+ 4 - 0
Source/cmLocalGenerator.h

@@ -562,6 +562,10 @@ public:
   cm::optional<cmSwiftCompileMode> GetSwiftCompileMode(
     cmGeneratorTarget const* target, std::string const& config);
 
+  // Can we build Swift with a separate object build and link step
+  // (If CMP0157 is NEW, we can do a split build)
+  bool IsSplitSwiftBuild() const;
+
 protected:
   // The default implementation converts to a Windows shortpath to
   // help older toolchains handle spaces and such.  A generator may

+ 8 - 7
Source/cmNinjaNormalTargetGenerator.cxx

@@ -1209,7 +1209,10 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     globalGen->GetByproductsForCleanTarget(config).push_back(targetOutputReal);
   }
 
-  if (this->TargetLinkLanguage(config) == "Swift") {
+  // If we can't split the Swift build model (CMP0157 is OLD or unset), fall
+  // back on the old one-step "build/link" logic.
+  if (!this->GetLocalGenerator()->IsSplitSwiftBuild() &&
+      this->TargetLinkLanguage(config) == "Swift") {
     vars["SWIFT_LIBRARY_NAME"] = [this, config]() -> std::string {
       cmGeneratorTarget::Names targetNames =
         this->GetGeneratorTarget()->GetLibraryNames(config);
@@ -1222,12 +1225,12 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
       cmOutputConverter::SHELL);
 
     vars["SWIFT_SOURCES"] = [this, config]() -> std::string {
-      std::vector<cmSourceFile const*> sources;
+      std::vector<cmSourceFile const*> sourceFiles;
       std::stringstream oss;
 
-      this->GetGeneratorTarget()->GetObjectSources(sources, config);
+      this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
       cmLocalGenerator const* LocalGen = this->GetLocalGenerator();
-      for (const auto& source : sources) {
+      for (const auto& source : sourceFiles) {
         const std::string sourcePath = source->GetLanguage() == "Swift"
           ? this->GetCompiledSourceNinjaPath(source)
           : this->GetObjectFilePath(source, config);
@@ -1245,10 +1248,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement(
     vars["FLAGS"] = this->GetFlags("Swift", config);
     vars["INCLUDES"] = this->GetIncludes("Swift", config);
     this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]);
-  }
 
-  // Compute specific libraries to link with.
-  if (this->TargetLinkLanguage(config) == "Swift") {
+    // Compute specific libraries to link with.
     std::vector<cmSourceFile const*> sources;
     gt->GetObjectSources(sources, config);
     for (const auto& source : sources) {

+ 231 - 7
Source/cmNinjaTargetGenerator.cxx

@@ -14,6 +14,7 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/algorithm>
 #include <cmext/string_view>
@@ -31,6 +32,7 @@
 #include "cmGlobalCommonGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
 #include "cmList.h"
+#include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalNinjaGenerator.h"
 #include "cmMakefile.h"
@@ -47,6 +49,7 @@
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
+#include "cmTargetDepend.h"
 #include "cmValue.h"
 #include "cmake.h"
 
@@ -145,7 +148,7 @@ std::string cmNinjaTargetGenerator::LanguageScanRule(
 bool cmNinjaTargetGenerator::NeedExplicitPreprocessing(
   std::string const& lang) const
 {
-  return lang == "Fortran";
+  return lang == "Fortran" || lang == "Swift";
 }
 
 bool cmNinjaTargetGenerator::CompileWithDefines(std::string const& lang) const
@@ -1136,9 +1139,18 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
     std::vector<cmSourceFile const*> objectSources;
     this->GeneratorTarget->GetObjectSources(objectSources, config);
 
+    std::vector<cmSourceFile const*> swiftSources;
+
     for (cmSourceFile const* sf : objectSources) {
-      this->WriteObjectBuildStatement(sf, config, fileConfig, firstForConfig);
+      if (sf->GetLanguage() == "Swift") {
+        swiftSources.push_back(sf);
+      } else {
+        this->WriteObjectBuildStatement(sf, config, fileConfig,
+                                        firstForConfig);
+      }
     }
+    WriteSwiftObjectBuildStatement(swiftSources, config, fileConfig,
+                                   firstForConfig);
   }
 
   {
@@ -1234,9 +1246,11 @@ void cmNinjaTargetGenerator::GenerateSwiftOutputFileMap(
     if (cmValue name = target->GetProperty("Swift_DEPENDENCIES_FILE")) {
       return *name;
     }
-    return this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(),
-                                             '/', config, '/',
-                                             target->GetName(), ".swiftdeps"));
+    return this->GetLocalGenerator()->ConvertToOutputFormat(
+      this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(), '/',
+                                        config, '/', target->GetName(),
+                                        ".swiftdeps")),
+      cmOutputConverter::SHELL);
   }();
 
   std::string mapFilePath =
@@ -1254,8 +1268,10 @@ void cmNinjaTargetGenerator::GenerateSwiftOutputFileMap(
 
   // Add flag
   this->LocalGenerator->AppendFlags(flags, "-output-file-map");
-  this->LocalGenerator->AppendFlagEscape(flags,
-                                         ConvertToNinjaPath(mapFilePath));
+  this->LocalGenerator->AppendFlagEscape(
+    flags,
+    this->GetLocalGenerator()->ConvertToOutputFormat(
+      ConvertToNinjaPath(mapFilePath), cmOutputConverter::SHELL));
 }
 
 namespace {
@@ -1861,6 +1877,214 @@ void cmNinjaTargetGenerator::WriteCxxModuleBmiBuildStatement(
                                          bmiBuild, commandLineLengthLimit);
 }
 
+void cmNinjaTargetGenerator::WriteSwiftObjectBuildStatement(
+  std::vector<cmSourceFile const*> const& sources, std::string const& config,
+  std::string const& fileConfig, bool firstForConfig)
+{
+  // Swift sources are compiled as a module, not individually like with C/C++.
+  // Flags, header search paths, and definitions are passed to the entire
+  // module build, but we still need to emit compile-commands for each source
+  // file in order to support CMAKE_EXPORT_COMPILE_COMMANDS.
+  // In whole-module mode, with a single thread, the Swift compiler will
+  // only emit a single object file, but if more than one thread is specified,
+  // or building in other modes, the compiler will emit multiple object files.
+  // When building a single-output, we do not provide an output-file-map (OFM),
+  // and instead pass `-o` to tell the compiler where to write the object.
+  // When building multiple outputs, we provide an OFM to tell the compiler
+  // where to put each object.
+  //
+  //
+  // Per-Target (module):
+  //  - Flags
+  //  - Definitions
+  //  - Include paths
+  //  - (single-output) output object filename
+  //  - Swiftmodule
+  //
+  //  Per-File:
+  //  - compile-command
+  //  - (multi-output) OFM data
+  //  - (multi-output) output object filename
+  //
+  //  Note: Due to the differences in the build models, we are only able to
+  //  build the object build-graph if we know what mode the target is built in.
+  //  For that, we need the "NEW" behavior for CMP0157. Otherwise, we have to
+  //  fall back on the old "linker" build. Otherwise, this should be
+  //  indistinguishable from the old behavior.
+  //
+  //  FIXME(#25490): Add response file support to Swift object build step
+  //  FIXME(#25491): Include all files in module in compile_commands.json
+
+  if (sources.empty()) {
+    return;
+  }
+
+  cmSwiftCompileMode compileMode;
+  if (cm::optional<cmSwiftCompileMode> optionalCompileMode =
+        this->LocalGenerator->GetSwiftCompileMode(this->GeneratorTarget,
+                                                  config)) {
+    compileMode = *optionalCompileMode;
+  } else {
+    // CMP0157 is not NEW, bailing early!
+    return;
+  }
+
+  auto getTargetPropertyOrDefault =
+    [](cmGeneratorTarget const& target, std::string const& property,
+       std::string defaultValue) -> std::string {
+    if (cmValue value = target.GetProperty(property)) {
+      return *value;
+    }
+    return defaultValue;
+  };
+
+  std::string const language = "Swift";
+  std::string const objectDir = this->ConvertToNinjaPath(
+    cmStrCat(this->GeneratorTarget->GetSupportDirectory(),
+             this->GetGlobalGenerator()->ConfigDirectory(config)));
+
+  cmGeneratorTarget const& target = *this->GeneratorTarget;
+  cmNinjaBuild objBuild(
+    this->LanguageCompilerRule(language, config, WithScanning::No));
+  cmNinjaVars& vars = objBuild.Variables;
+
+  std::string const moduleName =
+    getTargetPropertyOrDefault(target, "Swift_MODULE_NAME", target.GetName());
+  std::string const moduleDirectory = getTargetPropertyOrDefault(
+    target, "Swift_MODULE_DIRECTORY",
+    target.LocalGenerator->GetCurrentBinaryDirectory());
+  std::string const moduleFilename = getTargetPropertyOrDefault(
+    target, "Swift_MODULE", cmStrCat(moduleName, ".swiftmodule"));
+  std::string const moduleFilepath =
+    this->ConvertToNinjaPath(cmStrCat(moduleDirectory, '/', moduleFilename));
+
+  bool const isSingleOutput = [this, compileMode]() -> bool {
+    bool isMultiThread = false;
+    if (cmValue numThreadStr =
+          this->GetMakefile()->GetDefinition("CMAKE_Swift_NUM_THREADS")) {
+      unsigned long numThreads;
+      cmStrToULong(*numThreadStr, &numThreads);
+      // numThreads == 1 is multi-threaded according to swiftc
+      isMultiThread = numThreads > 0;
+    }
+    return !isMultiThread && compileMode == cmSwiftCompileMode::Wholemodule;
+  }();
+
+  // 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 (target.GetType() != cmStateEnums::EXECUTABLE ||
+      target.IsExecutableWithExports()) {
+    std::string const emitModuleFlag = "-emit-module";
+    std::string const modulePathFlag = "-emit-module-path";
+    this->LocalGenerator->AppendFlags(
+      vars["FLAGS"], { emitModuleFlag, modulePathFlag, moduleFilepath });
+    objBuild.Outputs.push_back(moduleFilepath);
+
+    std::string const moduleNameFlag = "-module-name";
+    this->LocalGenerator->AppendFlags(
+      vars["FLAGS"], cmStrCat(moduleNameFlag, ' ', moduleName));
+  }
+
+  if (target.GetType() != cmStateEnums::EXECUTABLE) {
+    std::string const libraryLinkNameFlag = "-module-link-name";
+    std::string const libraryLinkName =
+      this->GetGeneratorTarget()->GetLibraryNames(config).Base;
+    this->LocalGenerator->AppendFlags(
+      vars["FLAGS"], cmStrCat(libraryLinkNameFlag, ' ', libraryLinkName));
+  }
+
+  // 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");
+  }
+
+  this->LocalGenerator->AppendFlags(vars["FLAGS"],
+                                    this->GetFlags(language, config));
+  vars["DEFINES"] = this->GetDefines(language, config);
+  vars["INCLUDES"] = this->GetIncludes(language, config);
+
+  // target-level object filename
+  std::string const targetObjectFilename = this->ConvertToNinjaPath(cmStrCat(
+    objectDir, '/', moduleName,
+    this->GetGlobalGenerator()->GetLanguageOutputExtension(language)));
+
+  if (isSingleOutput) {
+    this->LocalGenerator->AppendFlags(vars["FLAGS"],
+                                      cmStrCat("-o ", targetObjectFilename));
+    objBuild.Outputs.push_back(targetObjectFilename);
+    this->Configs[config].Objects.push_back(targetObjectFilename);
+  }
+
+  for (cmSourceFile const* sf : sources) {
+    // Add dependency to object build on each source file
+    std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(sf);
+    objBuild.ExplicitDeps.push_back(sourceFilePath);
+
+    if (isSingleOutput) {
+      if (firstForConfig) {
+        this->ExportObjectCompileCommand(
+          language, sourceFilePath, objectDir, targetObjectFilename,
+          cmSystemTools::GetFilenamePath(targetObjectFilename), vars["FLAGS"],
+          vars["DEFINES"], vars["INCLUDES"],
+          /*compile pdb*/ "", /*target pdb*/ "", config);
+      }
+    } else {
+      // Object outputs
+      std::string const objectFilepath =
+        this->ConvertToNinjaPath(this->GetObjectFilePath(sf, config));
+      this->EnsureParentDirectoryExists(objectFilepath);
+      objBuild.Outputs.push_back(objectFilepath);
+      this->Configs[config].Objects.push_back(objectFilepath);
+
+      // Add OFM data
+      this->EmitSwiftDependencyInfo(sf, config);
+
+      // Emit compile commands
+      if (firstForConfig) {
+        this->ExportObjectCompileCommand(
+          language, sourceFilePath, objectDir, objectFilepath,
+          cmSystemTools::GetFilenamePath(objectFilepath), vars["FLAGS"],
+          vars["DEFINES"], vars["INCLUDES"],
+          /*compile pdb*/ "",
+          /*target pdb*/ "", config);
+      }
+    }
+  }
+
+  if (!isSingleOutput) {
+    this->GenerateSwiftOutputFileMap(config, vars["FLAGS"]);
+  }
+
+  for (cmTargetDepend const& dep :
+       this->GetGlobalGenerator()->GetTargetDirectDepends(&target)) {
+    if (!dep->IsLanguageUsed("Swift", config)) {
+      continue;
+    }
+    // Add dependencies on the emitted swiftmodule file from swift targets we
+    // depend on
+    std::string const depModuleName =
+      getTargetPropertyOrDefault(*dep, "Swift_MODULE_NAME", dep->GetName());
+    std::string const depModuleDir = getTargetPropertyOrDefault(
+      *dep, "Swift_MODULE_DIRECTORY",
+      dep->LocalGenerator->GetCurrentBinaryDirectory());
+    std::string const depModuleFilename = getTargetPropertyOrDefault(
+      *dep, "Swift_MODULE", cmStrCat(depModuleName, ".swiftmodule"));
+    std::string const depModuleFilepath =
+      this->ConvertToNinjaPath(cmStrCat(depModuleDir, '/', depModuleFilename));
+    objBuild.ImplicitDeps.push_back(depModuleFilepath);
+  }
+
+  objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
+
+  // Write object build
+  this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
+                                         objBuild);
+}
+
 void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
                                                    const std::string& config)
 {

+ 3 - 0
Source/cmNinjaTargetGenerator.h

@@ -171,6 +171,9 @@ protected:
                                        const std::string& config,
                                        const std::string& fileConfig,
                                        bool firstForConfig);
+  void WriteSwiftObjectBuildStatement(
+    std::vector<cmSourceFile const*> const& sources, const std::string& config,
+    const std::string& fileConfig, bool firstForConfig);
   void WriteObjectBuildStatement(cmSourceFile const* source,
                                  const std::string& config,
                                  const std::string& fileConfig,

+ 3 - 3
Tests/RunCMake/Swift/IncrementalSwift-second-stdout.txt

@@ -1,3 +1,3 @@
-.*Linking Swift static library libA.a
-.*Linking Swift static library libB.a
-FAILED: libB.a CMakeFiles/B.dir/b.swift.o B.swiftmodule
+.*Building Swift object A.swiftmodule CMakeFiles/A.dir/a.swift.o
+.*Building Swift object B.swiftmodule CMakeFiles/B.dir/b.swift.o
+FAILED: B.swiftmodule CMakeFiles/B.dir/b.swift.o

+ 16 - 8
Tests/SwiftOnly/CMakeLists.txt

@@ -43,6 +43,13 @@ add_library(N N.swift)
 target_link_libraries(N PUBLIC
   M)
 
+if(NOT XCODE_VERSION OR XCODE_VERSION VERSION_GREATER_EQUAL 9.0)
+  # TODO: Add a wholemodule object-library test once that is working
+  add_library(O OBJECT O.swift L.swift)
+  target_link_libraries(N PUBLIC O)
+  set_target_properties(O PROPERTIES Swift_COMPILATION_MODE "incremental")
+endif()
+
 # Dummy to make sure generation works with such targets.
 add_library(SwiftIface INTERFACE)
 target_link_libraries(SwiftOnly PRIVATE SwiftIface)
@@ -59,14 +66,15 @@ if(CMAKE_Swift_COMPILER_VERSION VERSION_GREATER_EQUAL 5.2)
 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}")
+  if(POLICY CMP0157)
+    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()
   endif()
 endfunction()

+ 0 - 0
Tests/SwiftOnly/O.swift