Przeglądaj źródła

cmake --build: Add support for driving Xcode workspaces

External tools may create a `.xcworkspace` directory next to the
`.xcodeproj` directory that CMake generates.  If a workspace exists,
drive the build through it instead.

Closes: #26958
Co-authored-by: Brad King <[email protected]>
Stepanov Igor 9 miesięcy temu
rodzic
commit
844d79916a

+ 13 - 0
Help/manual/cmake.1.rst

@@ -736,6 +736,19 @@ following options:
 
 Run :option:`cmake --build` with no options for quick help.
 
+Generator-Specific Build Tool Behavior
+--------------------------------------
+
+``cmake --build`` has special behavior with some generators:
+
+:generator:`Xcode`
+
+  .. versionadded:: 4.1
+
+    If a third-party tool has written a ``.xcworkspace`` next to
+    the CMake-generated ``.xcodeproj``, ``cmake --build`` drives
+    the build through the workspace instead.
+
 Install a Project
 =================
 

+ 7 - 0
Help/release/dev/xcode-build-workspace.rst

@@ -0,0 +1,7 @@
+xcode-build-workspace
+---------------------
+
+* The :ref:`cmake --build <Build Tool Mode>` command-line tool, when used
+  with the :generator:`Xcode` generator, now detects when a third-party
+  tool has wrapped the generated ``.xcodeproj`` in a ``.xcworkspace``,
+  and drives the build through the workspace instead.

+ 62 - 8
Source/cmGlobalXCodeGenerator.cxx

@@ -14,6 +14,7 @@
 
 #include <cm/memory>
 #include <cm/optional>
+#include <cm/string_view>
 #include <cmext/algorithm>
 #include <cmext/string_view>
 
@@ -294,6 +295,40 @@ bool cmGlobalXCodeGenerator::FindMakeProgram(cmMakefile* mf)
   return true;
 }
 
+std::string cmGlobalXCodeGenerator::GetAppleSpecificPlatformName()
+{
+  std::string sdkRoot =
+    this->GetCMakeInstance()->GetState()->GetCacheEntryValue(
+      "CMAKE_OSX_SYSROOT");
+  sdkRoot = cmSystemTools::LowerCase(sdkRoot);
+
+  struct SdkDatabaseEntry
+  {
+    cm::string_view Name;
+    cm::string_view AppleName;
+  };
+
+  std::array<SdkDatabaseEntry, 6> const sdkDatabase{ {
+    { "appletvos"_s, "tvOS"_s },
+    { "appletvsimulator"_s, "tvOS Simulator"_s },
+    { "iphoneos"_s, "iOS"_s },
+    { "iphonesimulator"_s, "iOS Simulator"_s },
+    { "watchos"_s, "watchOS"_s },
+    { "watchsimulator"_s, "watchOS Simulator"_s },
+  } };
+
+  cm::string_view platformName = "MacOS"_s;
+  for (SdkDatabaseEntry const& entry : sdkDatabase) {
+    if (cmHasPrefix(sdkRoot, entry.Name) ||
+        sdkRoot.find(cmStrCat('/', entry.Name)) != std::string::npos) {
+      platformName = entry.AppleName;
+      break;
+    }
+  }
+
+  return std::string(platformName);
+}
+
 std::string const& cmGlobalXCodeGenerator::GetXcodeBuildCommand()
 {
   if (!this->XcodeBuildCommandInitialized) {
@@ -473,10 +508,15 @@ bool cmGlobalXCodeGenerator::Open(std::string const& bindir,
   bool ret = false;
 
 #ifdef HAVE_APPLICATION_SERVICES
-  std::string url = cmStrCat(bindir, '/', projectName, ".xcodeproj");
+  // If an external tool created a workspace then open it instead.
+  std::string url = cmStrCat(bindir, '/', projectName, ".xcworkspace");
+  bool const isWorkspace = cmSystemTools::FileIsDirectory(url);
+  if (!isWorkspace) {
+    url = cmStrCat(bindir, '/', projectName, ".xcodeproj");
+  }
 
   if (dryRun) {
-    return cmSystemTools::FileExists(url, false);
+    return cmSystemTools::FileIsDirectory(url);
   }
 
   CFStringRef cfStr = CFStringCreateWithCString(
@@ -508,33 +548,47 @@ cmGlobalXCodeGenerator::GenerateBuildCommand(
   int jobs, bool /*verbose*/, cmBuildOptions const& /*buildOptions*/,
   std::vector<std::string> const& makeOptions)
 {
+  // If an external tool created a workspace then build it instead.
+  std::string projectPath = cmStrCat(projectName, ".xcworkspace");
+  bool const isWorkspace = cmSystemTools::FileIsDirectory(projectPath);
+  if (!isWorkspace) {
+    projectPath = cmStrCat(projectName, ".xcodeproj");
+  }
+
+  std::string const targetFlag = isWorkspace ? "-scheme" : "-target";
+  std::string const projectFlag = isWorkspace ? "-workspace" : "-project";
+
   GeneratedMakeCommand makeCommand;
   // now build the test
   makeCommand.Add(
     this->SelectMakeProgram(makeProgram, this->GetXcodeBuildCommand()));
 
   if (!projectName.empty()) {
-    makeCommand.Add("-project");
-    std::string projectArg = cmStrCat(projectName, ".xcodeproj");
-    makeCommand.Add(projectArg);
+    makeCommand.Add(projectFlag, projectPath);
   }
   if (cm::contains(targetNames, "clean")) {
     makeCommand.Add("clean");
-    makeCommand.Add("-target", "ALL_BUILD");
+    makeCommand.Add(targetFlag, "ALL_BUILD");
   } else {
     makeCommand.Add("build");
     if (targetNames.empty() ||
         ((targetNames.size() == 1) && targetNames.front().empty())) {
-      makeCommand.Add("-target", "ALL_BUILD");
+      makeCommand.Add(targetFlag, "ALL_BUILD");
     } else {
       for (auto const& tname : targetNames) {
         if (!tname.empty()) {
-          makeCommand.Add("-target", tname);
+          makeCommand.Add(targetFlag, tname);
         }
       }
     }
   }
 
+  if (isWorkspace) {
+    makeCommand.Add(
+      "-destination",
+      cmStrCat("generic/platform=", this->GetAppleSpecificPlatformName()));
+  }
+
   if ((this->XcodeBuildSystem >= BuildSystem::Twelve) ||
       (jobs != cmake::NO_BUILD_PARALLEL_LEVEL)) {
     makeCommand.Add("-parallelizeTargets");

+ 1 - 0
Source/cmGlobalXCodeGenerator.h

@@ -335,6 +335,7 @@ protected:
   BuildSystem XcodeBuildSystem = BuildSystem::One;
 
 private:
+  std::string GetAppleSpecificPlatformName();
   std::string const& GetXcodeBuildCommand();
   std::string FindXcodeBuildCommand();
   std::string XcodeBuildCommand;

+ 9 - 0
Tests/RunCMake/XcodeProject/RunCMakeTest.cmake

@@ -178,4 +178,13 @@ endfunction()
 
 BundleLinkBundle()
 
+if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
+  block()
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeWorkspace-build)
+    run_cmake(XcodeWorkspace)
+    set(RunCMake_TEST_NO_CLEAN 1)
+    run_cmake_command(XcodeWorkspace-build ${CMAKE_COMMAND} --build . --config Debug)
+  endblock()
+endif()
+
 # Please add device-specific tests to '../XcodeProject-Device/RunCMakeTest.cmake'.

+ 1 - 0
Tests/RunCMake/XcodeProject/XcodeWorkspace-build-stdout.txt

@@ -0,0 +1 @@
+xcodebuild -workspace XcodeWorkspace\.xcworkspace build -scheme ALL_BUILD -destination generic/platform=MacOS

+ 8 - 0
Tests/RunCMake/XcodeProject/XcodeWorkspace.cmake

@@ -0,0 +1,8 @@
+enable_language(C)
+add_executable(main main.c)
+file(WRITE "${CMAKE_BINARY_DIR}/XcodeWorkspace.xcworkspace/contents.xcworkspacedata" [[
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace version = "1.0">
+   <FileRef location = "container:XcodeWorkspace.xcodeproj"/>
+</Workspace>
+]])

+ 4 - 0
Tests/RunCMake/XcodeProject/main.c

@@ -0,0 +1,4 @@
+int main(void)
+{
+  return 0;
+}