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
+302 -27
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",