Xcode: Switch to the "new build system" for Xcode 12 and above

Provide an option to switch back to the original build system via
`-T buildsystem=1`.

Fixes: #18088
This commit is contained in:
Brad King 2020-09-14 15:02:47 -04:00
parent 2db623f554
commit 8d5f4c4db9
26 changed files with 423 additions and 31 deletions

View File

@ -30,3 +30,6 @@ Supported pairs are:
``buildsystem=<variant>``
Specify the buildsystem variant to use.
See the :variable:`CMAKE_XCODE_BUILD_SYSTEM` variable for allowed values.
For example, to select the original build system under Xcode 12,
run :manual:`cmake(1)` with the option ``-T buildsystem=1``.

View File

@ -0,0 +1,7 @@
xcode-12-new-build-system
-------------------------
* The :generator:`Xcode` generator now uses the Xcode "new build system"
when generating for Xcode 12.0 or higher.
See the :variable:`CMAKE_XCODE_BUILD_SYSTEM` variable.
One may use ``-T buildsystem=1`` to switch to the legacy build system.

View File

@ -10,7 +10,12 @@ mature enough for use by CMake. The possible values are:
``1``
The original Xcode build system.
This is the default.
This is the default when using Xcode 11.x or below.
``12``
The Xcode "new build system" introduced by Xcode 10.
It became mature enough for use by CMake in Xcode 12.
This is the default when using Xcode 12.x or above.
The ``CMAKE_XCODE_BUILD_SYSTEM`` variable is informational and should not
be modified by project code. See the :ref:`Xcode Build System Selection`

View File

@ -171,6 +171,11 @@ cmGlobalXCodeGenerator::cmGlobalXCodeGenerator(
{
this->VersionString = version_string;
this->XcodeVersion = version_number;
if (this->XcodeVersion >= 120) {
this->XcodeBuildSystem = BuildSystem::Twelve;
} else {
this->XcodeBuildSystem = BuildSystem::One;
}
this->RootObject = nullptr;
this->MainGroupChildren = nullptr;
@ -287,6 +292,8 @@ cm::string_view cmXcodeBuildSystemString(cmGlobalXCodeGenerator::BuildSystem b)
switch (b) {
case cmGlobalXCodeGenerator::BuildSystem::One:
return "1"_s;
case cmGlobalXCodeGenerator::BuildSystem::Twelve:
return "12"_s;
}
return {};
}
@ -371,6 +378,8 @@ bool cmGlobalXCodeGenerator::ProcessGeneratorToolsetField(
if (key == "buildsystem") {
if (value == "1"_s) {
this->XcodeBuildSystem = BuildSystem::One;
} else if (value == "12"_s) {
this->XcodeBuildSystem = BuildSystem::Twelve;
} else {
/* clang-format off */
std::string const& e = cmStrCat(
@ -378,7 +387,21 @@ bool cmGlobalXCodeGenerator::ProcessGeneratorToolsetField(
" ", this->GetName(), "\n"
"toolset specification field\n"
" buildsystem=", value, "\n"
"value is unkonwn. It must be '1'."
"value is unkonwn. It must be '1' or '12'."
);
/* clang-format on */
mf->IssueMessage(MessageType::FATAL_ERROR, e);
return false;
}
if (this->XcodeBuildSystem == BuildSystem::Twelve &&
this->XcodeVersion < 120) {
/* clang-format off */
std::string const& e = cmStrCat(
"Generator\n"
" ", this->GetName(), "\n"
"toolset specification field\n"
" buildsystem=", value, "\n"
"is not allowed with Xcode ", this->VersionString, '.'
);
/* clang-format on */
mf->IssueMessage(MessageType::FATAL_ERROR, e);
@ -479,6 +502,9 @@ cmGlobalXCodeGenerator::GenerateBuildCommand(
}
}
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
makeCommand.Add("-parallelizeTargets");
}
makeCommand.Add("-configuration", (config.empty() ? "Debug" : config));
if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
@ -499,8 +525,15 @@ cmGlobalXCodeGenerator::GenerateBuildCommand(
std::unique_ptr<cmLocalGenerator> cmGlobalXCodeGenerator::CreateLocalGenerator(
cmMakefile* mf)
{
return std::unique_ptr<cmLocalGenerator>(
std::unique_ptr<cmLocalGenerator> lg(
cm::make_unique<cmLocalXCodeGenerator>(this, mf));
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
// For this build system variant we generate custom commands as
// shell scripts directly rather than inside Makefiles.
// FIXME: Rename or refactor this option for clarity.
lg->SetLinkScriptShell(true);
}
return lg;
}
void cmGlobalXCodeGenerator::AddExtraIDETargets()
@ -1692,30 +1725,46 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
cmXCodeObject* preLinkPhase = nullptr;
cmXCodeObject* postBuildPhase = nullptr;
std::vector<cmSourceFile*> classes;
if (!gtgt->GetConfigCommonSourceFiles(classes)) {
return;
}
// add all the sources
std::vector<cmCustomCommand> commands;
auto& visited = this->CommandsVisited[gtgt];
for (auto sourceFile : classes) {
if (sourceFile->GetCustomCommand() && visited.insert(sourceFile).second) {
commands.push_back(*sourceFile->GetCustomCommand());
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
// create prebuild phase
preBuildPhase =
this->CreateRunScriptBuildPhase("CMake PreBuild Rules", prebuild);
// create prelink phase
preLinkPhase =
this->CreateRunScriptBuildPhase("CMake PreLink Rules", prelink);
// create postbuild phase
postBuildPhase =
this->CreateRunScriptBuildPhase("CMake PostBuild Rules", postbuild);
} else {
std::vector<cmSourceFile*> classes;
if (!gtgt->GetConfigCommonSourceFiles(classes)) {
return;
}
// add all the sources
std::vector<cmCustomCommand> commands;
auto& visited = this->CommandsVisited[gtgt];
for (auto sourceFile : classes) {
if (sourceFile->GetCustomCommand() &&
visited.insert(sourceFile).second) {
commands.push_back(*sourceFile->GetCustomCommand());
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
this->CustomCommandRoots[sourceFile].insert(gtgt);
}
}
}
// create custom commands phase
legacyCustomCommandsBuildPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake Rules", "cmakeRulesBuildPhase", gtgt, commands);
// create prebuild phase
preBuildPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake PreBuild Rules", "preBuildCommands", gtgt, prebuild);
// create prelink phase
preLinkPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake PreLink Rules", "preLinkCommands", gtgt, prelink);
// create postbuild phase
postBuildPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake PostBuild Rules", "postBuildPhase", gtgt, postbuild);
}
// create custom commands phase
legacyCustomCommandsBuildPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake Rules", "cmakeRulesBuildPhase", gtgt, commands);
// create prebuild phase
preBuildPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake PreBuild Rules", "preBuildCommands", gtgt, prebuild);
// create prelink phase
preLinkPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake PreLink Rules", "preLinkCommands", gtgt, prelink);
// create postbuild phase
postBuildPhase = this->CreateLegacyRunScriptBuildPhase(
"CMake PostBuild Rules", "postBuildPhase", gtgt, postbuild);
// The order here is the order they will be built in.
// The order "headers, resources, sources" mimics a native project generated
@ -1727,6 +1776,9 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
if (legacyCustomCommandsBuildPhase) {
buildPhases->AddObject(legacyCustomCommandsBuildPhase);
}
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
this->CreateRunScriptBuildPhases(buildPhases, gtgt);
}
if (headerBuildPhase) {
buildPhases->AddObject(headerBuildPhase);
}
@ -1750,6 +1802,199 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
}
}
void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases(
cmXCodeObject* buildPhases, cmGeneratorTarget const* gt)
{
std::vector<cmSourceFile*> sources;
if (!gt->GetConfigCommonSourceFiles(sources)) {
return;
}
auto& visited = this->CommandsVisited[gt];
for (auto sf : sources) {
this->CreateRunScriptBuildPhases(buildPhases, sf, gt, visited);
}
}
void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases(
cmXCodeObject* buildPhases, cmSourceFile const* sf,
cmGeneratorTarget const* gt, std::set<cmSourceFile const*>& visited)
{
cmCustomCommand const* cc = sf->GetCustomCommand();
if (cc && visited.insert(sf).second) {
this->CustomCommandRoots[sf].insert(gt);
if (std::vector<cmSourceFile*> const* depends = gt->GetSourceDepends(sf)) {
for (cmSourceFile const* di : *depends) {
this->CreateRunScriptBuildPhases(buildPhases, di, gt, visited);
}
}
cmXCodeObject* buildPhase = this->CreateRunScriptBuildPhase(sf, gt, *cc);
buildPhases->AddObject(buildPhase);
}
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase(
cmSourceFile const* sf, cmGeneratorTarget const* gt,
cmCustomCommand const& cc)
{
std::set<std::string> allConfigInputs;
std::set<std::string> allConfigOutputs;
std::string shellScript = "set -e\n";
for (std::string const& configName : this->CurrentConfigurationTypes) {
cmCustomCommandGenerator ccg(cc, configName, this->CurrentLocalGenerator);
std::vector<std::string> realDepends;
realDepends.reserve(ccg.GetDepends().size());
for (auto const& d : ccg.GetDepends()) {
std::string dep;
if (this->CurrentLocalGenerator->GetRealDependency(d, configName, dep)) {
realDepends.emplace_back(std::move(dep));
}
}
allConfigInputs.insert(realDepends.begin(), realDepends.end());
allConfigOutputs.insert(ccg.GetByproducts().begin(),
ccg.GetByproducts().end());
allConfigOutputs.insert(ccg.GetOutputs().begin(), ccg.GetOutputs().end());
shellScript =
cmStrCat(shellScript, R"(if test "$CONFIGURATION" = ")", configName,
"\"; then :\n", this->ConstructScript(ccg), "fi\n");
}
cmXCodeObject* buildPhase =
this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase);
buildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
buildPhase->AddAttribute("files", buildFiles);
{
std::string name;
if (!allConfigOutputs.empty()) {
name = cmStrCat("Generate ",
this->RelativeToBinary(*allConfigOutputs.begin()));
} else {
name = sf->GetLocation().GetName();
}
buildPhase->AddAttribute("name", this->CreateString(name));
}
buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh"));
buildPhase->AddAttribute("shellScript", this->CreateString(shellScript));
buildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0"));
bool symbolic = false;
{
cmXCodeObject* inputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (std::string const& i : allConfigInputs) {
inputPaths->AddUniqueObject(this->CreateString(i));
if (!symbolic) {
if (cmSourceFile* isf =
gt->GetLocalGenerator()->GetMakefile()->GetSource(
i, cmSourceFileLocationKind::Known)) {
symbolic = isf->GetPropertyAsBool("SYMBOLIC");
}
}
}
buildPhase->AddAttribute("inputPaths", inputPaths);
}
{
cmXCodeObject* outputPaths =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (std::string const& o : allConfigOutputs) {
outputPaths->AddUniqueObject(this->CreateString(o));
if (!symbolic) {
if (cmSourceFile* osf =
gt->GetLocalGenerator()->GetMakefile()->GetSource(
o, cmSourceFileLocationKind::Known)) {
symbolic = osf->GetPropertyAsBool("SYMBOLIC");
}
}
}
buildPhase->AddAttribute("outputPaths", outputPaths);
}
if (symbolic) {
buildPhase->AddAttribute("alwaysOutOfDate", this->CreateString("1"));
}
return buildPhase;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase(
std::string const& name, std::vector<cmCustomCommand> const& commands)
{
if (commands.empty()) {
return nullptr;
}
std::set<std::string> allConfigOutputs;
std::string shellScript = "set -e\n";
for (std::string const& configName : this->CurrentConfigurationTypes) {
shellScript = cmStrCat(shellScript, R"(if test "$CONFIGURATION" = ")",
configName, "\"; then :\n");
for (cmCustomCommand const& cc : commands) {
cmCustomCommandGenerator ccg(cc, configName,
this->CurrentLocalGenerator);
shellScript = cmStrCat(shellScript, this->ConstructScript(ccg));
allConfigOutputs.insert(ccg.GetByproducts().begin(),
ccg.GetByproducts().end());
}
shellScript = cmStrCat(shellScript, "fi\n");
}
cmXCodeObject* buildPhase =
this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase);
buildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
buildPhase->AddAttribute("files", buildFiles);
buildPhase->AddAttribute("name", this->CreateString(name));
buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh"));
buildPhase->AddAttribute("shellScript", this->CreateString(shellScript));
buildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0"));
{
cmXCodeObject* outputPaths =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (std::string const& o : allConfigOutputs) {
outputPaths->AddUniqueObject(this->CreateString(o));
}
buildPhase->AddAttribute("outputPaths", outputPaths);
}
buildPhase->AddAttribute("alwaysOutOfDate", this->CreateString("1"));
return buildPhase;
}
std::string cmGlobalXCodeGenerator::ConstructScript(
cmCustomCommandGenerator const& ccg)
{
std::string script;
cmLocalGenerator* lg = this->CurrentLocalGenerator;
std::string wd = ccg.GetWorkingDirectory();
if (wd.empty()) {
wd = lg->GetCurrentBinaryDirectory();
}
wd = lg->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
script = cmStrCat(script, " cd ", wd, "\n");
for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) {
std::string cmd = ccg.GetCommand(c);
if (cmd.empty()) {
continue;
}
cmSystemTools::ReplaceString(cmd, "/./", "/");
cmd = lg->ConvertToOutputFormat(cmd, cmOutputConverter::SHELL);
ccg.AppendArguments(c, cmd);
cmSystemTools::ReplaceString(cmd, "$(CONFIGURATION)", "$CONFIGURATION");
cmSystemTools::ReplaceString(cmd, "$(EFFECTIVE_PLATFORM_NAME)",
"$EFFECTIVE_PLATFORM_NAME");
script = cmStrCat(script, " ", cmd, '\n');
}
return script;
}
// This function removes each occurrence of the flag and returns the last one
// (i.e., the dominant flag in GCC)
std::string cmGlobalXCodeGenerator::ExtractFlag(const char* flag,
@ -3713,6 +3958,29 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects(
if (!this->CreateXCodeTargets(generator, targets)) {
return false;
}
for (auto const& ccRoot : this->CustomCommandRoots) {
if (ccRoot.second.size() > 1) {
std::string e = "The custom command ";
std::vector<std::string> const& outputs =
ccRoot.first->GetCustomCommand()->GetOutputs();
if (!outputs.empty()) {
e = cmStrCat(e, "generating\n ", outputs[0]);
} else {
e = cmStrCat(e, "driven by\n ", ccRoot.first->GetFullPath());
}
e = cmStrCat(e, "\nis attached to multiple targets:");
for (cmGeneratorTarget const* gt : ccRoot.second) {
e = cmStrCat(e, "\n ", gt->GetName());
}
e = cmStrCat(
e,
"\nbut none of these is a common dependency of the other(s). "
"This is not allowed by the Xcode \"new build system\".");
generator->IssueMessage(MessageType::FATAL_ERROR, e);
return false;
}
}
this->CustomCommandRoots.clear();
}
// loop over all targets and add link and depend info
for (auto t : targets) {
@ -4007,9 +4275,16 @@ void cmGlobalXCodeGenerator::OutputXCodeWorkspaceSettings(
xout.StartElement("dict");
if (this->XcodeVersion >= 100) {
xout.Element("key", "BuildSystemType");
xout.Element("string", "Original");
xout.Element("key", "DisableBuildSystemDeprecationWarning");
xout.Element("true");
switch (this->XcodeBuildSystem) {
case BuildSystem::One:
xout.Element("string", "Original");
xout.Element("key", "DisableBuildSystemDeprecationWarning");
xout.Element("true");
break;
case BuildSystem::Twelve:
xout.Element("string", "Latest");
break;
}
}
if (hasGeneratedSchemes) {
xout.Element("key",

View File

@ -15,6 +15,7 @@
#include "cmXCodeObject.h"
class cmCustomCommand;
class cmCustomCommandGenerator;
class cmGeneratorTarget;
class cmGlobalGeneratorFactory;
class cmLocalGenerator;
@ -116,6 +117,7 @@ public:
enum class BuildSystem
{
One = 1,
Twelve = 12,
};
protected:
@ -233,6 +235,18 @@ private:
cmXCodeObject* CreateLegacyRunScriptBuildPhase(
const char* name, const char* name2, cmGeneratorTarget* target,
const std::vector<cmCustomCommand>&);
void CreateRunScriptBuildPhases(cmXCodeObject* buildPhases,
cmGeneratorTarget const* gt);
void CreateRunScriptBuildPhases(cmXCodeObject* buildPhases,
cmSourceFile const* sf,
cmGeneratorTarget const* gt,
std::set<cmSourceFile const*>& visited);
cmXCodeObject* CreateRunScriptBuildPhase(cmSourceFile const* sf,
cmGeneratorTarget const* gt,
cmCustomCommand const& cc);
cmXCodeObject* CreateRunScriptBuildPhase(
std::string const& name, std::vector<cmCustomCommand> const& commands);
std::string ConstructScript(cmCustomCommandGenerator const& ccg);
void CreateReRunCMakeFile(cmLocalGenerator* root,
std::vector<cmLocalGenerator*> const& gens);
@ -315,4 +329,6 @@ private:
std::vector<std::string> EnabledLangs;
std::map<cmGeneratorTarget const*, std::set<cmSourceFile const*>>
CommandsVisited;
std::map<cmSourceFile const*, std::set<cmGeneratorTarget const*>>
CustomCommandRoots;
};

View File

@ -7,4 +7,4 @@ CMake Error at CMakeLists.txt:[0-9]+ \(project\):
buildsystem=bad
value is unkonwn. It must be '1'\.$
value is unkonwn. It must be '1' or '12'\.$

View File

@ -0,0 +1,10 @@
CMake Error at CMakeLists.txt:[0-9]+ \(project\):
Generator
Xcode
toolset specification field
buildsystem=12
is not allowed with Xcode [0-9.]+\.$

View File

@ -0,0 +1 @@
message(FATAL_ERROR "This should not be reached!")

View File

@ -56,8 +56,19 @@ elseif("${RunCMake_GENERATOR}" STREQUAL "Xcode")
run_cmake(BadToolsetHostArchXcode)
set(RunCMake_GENERATOR_TOOLSET "buildsystem=bad")
run_cmake(BadToolsetXcodeBuildSystem)
set(RunCMake_GENERATOR_TOOLSET "Test Toolset")
run_cmake(TestToolsetXcodeBuildSystemDefault1)
if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
set(RunCMake_GENERATOR_TOOLSET "Test Toolset")
run_cmake(TestToolsetXcodeBuildSystemDefault12)
set(RunCMake_GENERATOR_TOOLSET "Test Toolset,buildsystem=1")
run_cmake(TestToolsetXcodeBuildSystem1)
set(RunCMake_GENERATOR_TOOLSET "Test Toolset,buildsystem=12")
run_cmake(TestToolsetXcodeBuildSystem12)
else()
set(RunCMake_GENERATOR_TOOLSET "Test Toolset")
run_cmake(TestToolsetXcodeBuildSystemDefault1)
set(RunCMake_GENERATOR_TOOLSET "buildsystem=12")
run_cmake(BadToolsetXcodeBuildSystem12)
endif()
else()
set(RunCMake_GENERATOR_TOOLSET "Bad Toolset")
run_cmake(BadToolset)

View File

@ -0,0 +1,4 @@
^CMake Error at TestToolsetXcodeBuildSystem1.cmake:[0-9]+ \(message\):
CMAKE_GENERATOR_TOOLSET is "Test Toolset,buildsystem=1" as expected.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1 @@
CMAKE_XCODE_BUILD_SYSTEM='1'

View File

@ -0,0 +1,8 @@
message(STATUS "CMAKE_XCODE_BUILD_SYSTEM='${CMAKE_XCODE_BUILD_SYSTEM}'")
if(CMAKE_GENERATOR_TOOLSET STREQUAL "Test Toolset,buildsystem=1")
message(FATAL_ERROR "CMAKE_GENERATOR_TOOLSET is \"Test Toolset,buildsystem=1\" as expected.")
else()
message(FATAL_ERROR
"CMAKE_GENERATOR_TOOLSET is \"${CMAKE_GENERATOR_TOOLSET}\" "
"but should be \"Test Toolset,buildsystem=1\"!")
endif()

View File

@ -0,0 +1,4 @@
^CMake Error at TestToolsetXcodeBuildSystem12.cmake:[0-9]+ \(message\):
CMAKE_GENERATOR_TOOLSET is "Test Toolset,buildsystem=12" as expected.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1 @@
CMAKE_XCODE_BUILD_SYSTEM='12'

View File

@ -0,0 +1,8 @@
message(STATUS "CMAKE_XCODE_BUILD_SYSTEM='${CMAKE_XCODE_BUILD_SYSTEM}'")
if(CMAKE_GENERATOR_TOOLSET STREQUAL "Test Toolset,buildsystem=12")
message(FATAL_ERROR "CMAKE_GENERATOR_TOOLSET is \"Test Toolset,buildsystem=12\" as expected.")
else()
message(FATAL_ERROR
"CMAKE_GENERATOR_TOOLSET is \"${CMAKE_GENERATOR_TOOLSET}\" "
"but should be \"Test Toolset,buildsystem=12\"!")
endif()

View File

@ -0,0 +1,4 @@
^CMake Error at TestToolsetXcodeBuildSystemDefault12.cmake:[0-9]+ \(message\):
CMAKE_GENERATOR_TOOLSET is "Test Toolset" as expected.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1 @@
CMAKE_XCODE_BUILD_SYSTEM='12'

View File

@ -0,0 +1,8 @@
message(STATUS "CMAKE_XCODE_BUILD_SYSTEM='${CMAKE_XCODE_BUILD_SYSTEM}'")
if(CMAKE_GENERATOR_TOOLSET STREQUAL "Test Toolset")
message(FATAL_ERROR "CMAKE_GENERATOR_TOOLSET is \"Test Toolset\" as expected.")
else()
message(FATAL_ERROR
"CMAKE_GENERATOR_TOOLSET is \"${CMAKE_GENERATOR_TOOLSET}\" "
"but should be \"Test Toolset\"!")
endif()

View File

@ -10,6 +10,10 @@ run_cmake(XcodeAttributeGenex)
run_cmake(XcodeAttributeGenexError)
run_cmake(XcodeGenerateTopLevelProjectOnly)
if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
run_cmake(XcodeDuplicateCustomCommand)
endif()
function(XcodeGenerateTopLevelProjectOnlyWithObjectLibrary)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeGenerateTopLevelProjectOnlyWithObjectLibrary-build)
run_cmake(XcodeGenerateTopLevelProjectOnlyWithObjectLibrary)

View File

@ -0,0 +1,13 @@
^CMake Error in CMakeLists.txt:
The custom command generating
[^
]*/Tests/RunCMake/XcodeProject/XcodeDuplicateCustomCommand-build/out.txt
is attached to multiple targets:
drive[0-9]
drive[0-9]
but none of these is a common dependency of the other\(s\). This is not
allowed by the Xcode "new build system".

View File

@ -0,0 +1,3 @@
add_custom_command(OUTPUT out.txt COMMAND false)
add_custom_target(drive1 DEPENDS out.txt)
add_custom_target(drive2 DEPENDS out.txt)