diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst index 4004e91bce..779fdc2b89 100644 --- a/Help/manual/cmake.1.rst +++ b/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 ================= diff --git a/Help/release/dev/xcode-build-workspace.rst b/Help/release/dev/xcode-build-workspace.rst new file mode 100644 index 0000000000..37a009ebde --- /dev/null +++ b/Help/release/dev/xcode-build-workspace.rst @@ -0,0 +1,7 @@ +xcode-build-workspace +--------------------- + +* The :ref:`cmake --build ` 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. diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index b40457877b..de1dd6f723 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -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 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 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"); diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index 6c860cbb44..38c4839194 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/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; diff --git a/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake b/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake index 0515ec331e..16a235ded7 100644 --- a/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake +++ b/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'. diff --git a/Tests/RunCMake/XcodeProject/XcodeWorkspace-build-stdout.txt b/Tests/RunCMake/XcodeProject/XcodeWorkspace-build-stdout.txt new file mode 100644 index 0000000000..5d3768e683 --- /dev/null +++ b/Tests/RunCMake/XcodeProject/XcodeWorkspace-build-stdout.txt @@ -0,0 +1 @@ +xcodebuild -workspace XcodeWorkspace\.xcworkspace build -scheme ALL_BUILD -destination generic/platform=MacOS diff --git a/Tests/RunCMake/XcodeProject/XcodeWorkspace.cmake b/Tests/RunCMake/XcodeProject/XcodeWorkspace.cmake new file mode 100644 index 0000000000..bf7626a03b --- /dev/null +++ b/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" [[ + + + + +]]) diff --git a/Tests/RunCMake/XcodeProject/main.c b/Tests/RunCMake/XcodeProject/main.c index e69de29bb2..8488f4e58f 100644 --- a/Tests/RunCMake/XcodeProject/main.c +++ b/Tests/RunCMake/XcodeProject/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +}