Files
CMake/Source/cmGlobalXCodeGenerator.cxx
Stepanov Igor 0b7d8e4ad6 xcode: Restore ctest --build-and-test without --build-project
Previously, it used to be possible to execute ctest --build-and-test
without specifying --build-project. When used with the Xcode generator,
this would work as long as there was only one .xcodeproj file in the
directory, where xcodebuild would then default to using that project.
The recent changes to support .xcworkspace files broke that logic, placing
a malformed pair of options "-project .xcodeproj" on the command line
instead of omitting the "-project" option altogether.

Fixes: #27090
2025-07-26 19:43:28 +10:00

5522 lines
199 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmGlobalXCodeGenerator.h"
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iomanip>
#include <sstream>
#include <unordered_set>
#include <utility>
#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
#include "cmsys/RegularExpression.hxx"
#include "cmComputeLinkInformation.h"
#include "cmCryptoHash.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandGenerator.h"
#include "cmCustomCommandLines.h"
#include "cmCustomCommandTypes.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorOptions.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGeneratorFactory.h"
#include "cmLinkItem.h"
#include "cmList.h"
#include "cmListFileCache.h"
#include "cmLocalGenerator.h"
#include "cmLocalXCodeGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmOutputConverter.h"
#include "cmPolicies.h"
#include "cmSourceFile.h"
#include "cmSourceFileLocation.h"
#include "cmSourceFileLocationKind.h"
#include "cmSourceGroup.h"
#include "cmState.h"
#include "cmStateSnapshot.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetDepend.h"
#include "cmXCode21Object.h"
#include "cmXCodeObject.h"
#include "cmXCodeScheme.h"
#include "cmXMLWriter.h"
#include "cmXcFramework.h"
#include "cmake.h"
#if !defined(CMAKE_BOOTSTRAP) && defined(__APPLE__)
# include <CoreFoundation/CFBundle.h>
# include <CoreFoundation/CFString.h>
# include <CoreFoundation/CFURL.h>
# include <CoreFoundation/CFUUID.h>
# if !TARGET_OS_IPHONE
# define HAVE_APPLICATION_SERVICES
# include <ApplicationServices/ApplicationServices.h>
# endif
#endif
#if !defined(CMAKE_BOOTSTRAP)
# include "cmXMLParser.h"
// parse the xml file storing the installed version of Xcode on
// the machine
class cmXcodeVersionParser : public cmXMLParser
{
public:
cmXcodeVersionParser()
: Version("1.5")
{
}
void StartElement(std::string const&, char const**) override
{
this->Data = "";
}
void EndElement(std::string const& name) override
{
if (name == "key"_s) {
this->Key = this->Data;
} else if (name == "string"_s) {
if (this->Key == "CFBundleShortVersionString"_s) {
this->Version = this->Data;
}
}
}
void CharacterDataHandler(char const* data, int length) override
{
this->Data.append(data, length);
}
std::string Version;
std::string Key;
std::string Data;
};
#endif
// Builds either an object list or a space-separated string from the
// given inputs.
class cmGlobalXCodeGenerator::BuildObjectListOrString
{
cmGlobalXCodeGenerator* Generator;
cmXCodeObject* Group = nullptr;
bool Empty = true;
std::string String;
public:
BuildObjectListOrString(cmGlobalXCodeGenerator* gen, bool buildObjectList)
: Generator(gen)
{
if (buildObjectList) {
this->Group = this->Generator->CreateObject(cmXCodeObject::OBJECT_LIST);
}
}
bool IsEmpty() const { return this->Empty; }
void Add(std::string const& newString)
{
this->Empty = false;
if (this->Group) {
this->Group->AddObject(this->Generator->CreateString(newString));
} else {
this->String += newString;
this->String += ' ';
}
}
std::string const& GetString() const { return this->String; }
cmXCodeObject* CreateList()
{
if (this->Group) {
return this->Group;
}
return this->Generator->CreateString(this->String);
}
};
class cmGlobalXCodeGenerator::Factory : public cmGlobalGeneratorFactory
{
public:
std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator(
std::string const& name, cmake* cm) const override;
cmDocumentationEntry GetDocumentation() const override
{
return cmGlobalXCodeGenerator::GetDocumentation();
}
std::vector<std::string> GetGeneratorNames() const override
{
std::vector<std::string> names;
names.push_back(cmGlobalXCodeGenerator::GetActualName());
return names;
}
bool SupportsToolset() const override { return true; }
bool SupportsPlatform() const override { return false; }
std::vector<std::string> GetKnownPlatforms() const override
{
return std::vector<std::string>();
}
std::string GetDefaultPlatformName() const override { return std::string(); }
};
cmGlobalXCodeGenerator::cmGlobalXCodeGenerator(
cmake* cm, std::string const& version_string, unsigned int version_number)
: cmGlobalGenerator(cm)
{
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;
this->FrameworkGroup = nullptr;
this->CurrentMakefile = nullptr;
this->CurrentLocalGenerator = nullptr;
this->XcodeBuildCommandInitialized = false;
this->ObjectDirArchDefault = "$(CURRENT_ARCH)";
this->ObjectDirArch = this->ObjectDirArchDefault;
cm->GetState()->SetIsGeneratorMultiConfig(true);
}
std::unique_ptr<cmGlobalGeneratorFactory> cmGlobalXCodeGenerator::NewFactory()
{
return std::unique_ptr<cmGlobalGeneratorFactory>(new Factory);
}
std::unique_ptr<cmGlobalGenerator>
cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(std::string const& name,
cmake* cm) const
{
if (name != GetActualName()) {
return std::unique_ptr<cmGlobalGenerator>();
}
#if !defined(CMAKE_BOOTSTRAP)
cmXcodeVersionParser parser;
std::string versionFile;
{
std::string out;
bool commandResult = cmSystemTools::RunSingleCommand(
"xcode-select --print-path", &out, nullptr, nullptr, nullptr,
cmSystemTools::OUTPUT_NONE);
if (commandResult) {
std::string::size_type pos = out.find(".app/");
if (pos != std::string::npos) {
versionFile =
cmStrCat(out.substr(0, pos + 5), "Contents/version.plist");
}
}
}
if (!versionFile.empty() && cmSystemTools::FileExists(versionFile)) {
parser.ParseFile(versionFile.c_str());
} else if (cmSystemTools::FileExists(
"/Applications/Xcode.app/Contents/version.plist")) {
parser.ParseFile("/Applications/Xcode.app/Contents/version.plist");
} else {
parser.ParseFile(
"/Developer/Applications/Xcode.app/Contents/version.plist");
}
std::string const& version_string = parser.Version;
// Compute an integer form of the version number.
unsigned int v[2] = { 0, 0 };
sscanf(version_string.c_str(), "%u.%u", &v[0], &v[1]);
unsigned int version_number = 10 * v[0] + v[1];
if (version_number < 50) {
cm->IssueMessage(MessageType::FATAL_ERROR,
cmStrCat("Xcode ", version_string, " not supported."));
return std::unique_ptr<cmGlobalGenerator>();
}
return std::unique_ptr<cmGlobalGenerator>(
cm::make_unique<cmGlobalXCodeGenerator>(cm, version_string,
version_number));
#else
std::cerr << "CMake should be built with cmake to use Xcode, "
"default to Xcode 1.5\n";
return std::unique_ptr<cmGlobalGenerator>(
cm::make_unique<cmGlobalXCodeGenerator>(cm));
#endif
}
namespace {
std::string ConvertToMakefilePath(std::string const& path)
{
std::string result;
result.reserve(path.size());
for (char c : path) {
switch (c) {
case '\\':
case ' ':
case '#':
result.push_back('\\');
CM_FALLTHROUGH;
default:
result.push_back(c);
break;
}
}
return result;
}
}
bool cmGlobalXCodeGenerator::FindMakeProgram(cmMakefile* mf)
{
// The Xcode generator knows how to lookup its build tool
// directly instead of needing a helper module to do it, so we
// do not actually need to put CMAKE_MAKE_PROGRAM into the cache.
if (mf->GetDefinition("CMAKE_MAKE_PROGRAM").IsOff()) {
mf->AddDefinition("CMAKE_MAKE_PROGRAM", this->GetXcodeBuildCommand());
}
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) {
this->XcodeBuildCommandInitialized = true;
this->XcodeBuildCommand = this->FindXcodeBuildCommand();
}
return this->XcodeBuildCommand;
}
std::string cmGlobalXCodeGenerator::FindXcodeBuildCommand()
{
std::string makeProgram = cmSystemTools::FindProgram("xcodebuild");
if (makeProgram.empty()) {
makeProgram = "xcodebuild";
}
return makeProgram;
}
bool cmGlobalXCodeGenerator::SetSystemName(std::string const& s,
cmMakefile* mf)
{
this->SystemName = s;
return this->cmGlobalGenerator::SetSystemName(s, mf);
}
namespace {
cm::string_view cmXcodeBuildSystemString(cmGlobalXCodeGenerator::BuildSystem b)
{
switch (b) {
case cmGlobalXCodeGenerator::BuildSystem::One:
return "1"_s;
case cmGlobalXCodeGenerator::BuildSystem::Twelve:
return "12"_s;
}
return {};
}
}
bool cmGlobalXCodeGenerator::SetGeneratorToolset(std::string const& ts,
bool build, cmMakefile* mf)
{
if (!this->ParseGeneratorToolset(ts, mf)) {
return false;
}
if (build) {
return true;
}
if (!this->GeneratorToolset.empty()) {
mf->AddDefinition("CMAKE_XCODE_PLATFORM_TOOLSET", this->GeneratorToolset);
}
mf->AddDefinition("CMAKE_XCODE_BUILD_SYSTEM",
cmXcodeBuildSystemString(this->XcodeBuildSystem));
return true;
}
bool cmGlobalXCodeGenerator::ParseGeneratorToolset(std::string const& ts,
cmMakefile* mf)
{
std::vector<std::string> const fields =
cmTokenize(ts, ',', cmTokenizerMode::New);
if (fields.empty()) {
return true;
}
auto fi = fields.cbegin();
// The first field may be the Xcode GCC_VERSION.
if (fi->find('=') == fi->npos) {
this->GeneratorToolset = *fi;
++fi;
}
std::unordered_set<std::string> handled;
// The rest of the fields must be key=value pairs.
for (; fi != fields.cend(); ++fi) {
std::string::size_type pos = fi->find('=');
if (pos == fi->npos) {
/* clang-format off */
std::string const& e = cmStrCat(
"Generator\n"
" ", this->GetName(), "\n"
"given toolset specification\n"
" ", ts, "\n"
"that contains a field after the first ',' with no '='."
);
/* clang-format on */
mf->IssueMessage(MessageType::FATAL_ERROR, e);
return false;
}
std::string const key = fi->substr(0, pos);
std::string const value = fi->substr(pos + 1);
if (!handled.insert(key).second) {
/* clang-format off */
std::string const& e = cmStrCat(
"Generator\n"
" ", this->GetName(), "\n"
"given toolset specification\n"
" ", ts, "\n"
"that contains duplicate field key '", key, "'."
);
/* clang-format on */
mf->IssueMessage(MessageType::FATAL_ERROR, e);
return false;
}
if (!this->ProcessGeneratorToolsetField(key, value, mf)) {
return false;
}
}
return true;
}
bool cmGlobalXCodeGenerator::ProcessGeneratorToolsetField(
std::string const& key, std::string const& value, cmMakefile* mf)
{
if (key == "buildsystem"_s) {
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(
"Generator\n"
" ", this->GetName(), "\n"
"toolset specification field\n"
" buildsystem=", value, "\n"
"value is unknown. 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) ||
(this->XcodeBuildSystem == BuildSystem::One &&
this->XcodeVersion >= 140)) {
/* 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);
return false;
}
return true;
}
/* clang-format off */
std::string const& e = cmStrCat(
"Generator\n"
" ", this->GetName(), "\n"
"given toolset specification that contains invalid field '", key, "'."
);
/* clang-format on */
mf->IssueMessage(MessageType::FATAL_ERROR, e);
return false;
}
void cmGlobalXCodeGenerator::EnableLanguage(
std::vector<std::string> const& lang, cmMakefile* mf, bool optional)
{
mf->AddDefinition("XCODE", "1");
mf->AddDefinition("XCODE_VERSION", this->VersionString);
mf->InitCMAKE_CONFIGURATION_TYPES("Debug;Release;MinSizeRel;RelWithDebInfo");
mf->AddDefinition("CMAKE_GENERATOR_NO_COMPILER_ENV", "1");
this->cmGlobalGenerator::EnableLanguage(lang, mf, optional);
this->ComputeArchitectures(mf);
}
bool cmGlobalXCodeGenerator::Open(std::string const& bindir,
std::string const& projectName, bool dryRun)
{
bool ret = false;
#ifdef HAVE_APPLICATION_SERVICES
// 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::FileIsDirectory(url);
}
CFStringRef cfStr = CFStringCreateWithCString(
kCFAllocatorDefault, url.c_str(), kCFStringEncodingUTF8);
if (cfStr) {
CFURLRef cfUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfStr,
kCFURLPOSIXPathStyle, true);
if (cfUrl) {
OSStatus err = LSOpenCFURLRef(cfUrl, nullptr);
ret = err == noErr;
CFRelease(cfUrl);
}
CFRelease(cfStr);
}
#else
(void)bindir;
(void)projectName;
(void)dryRun;
#endif
return ret;
}
std::vector<cmGlobalGenerator::GeneratedMakeCommand>
cmGlobalXCodeGenerator::GenerateBuildCommand(
std::string const& makeProgram, std::string const& projectName,
std::string const& /*projectDir*/,
std::vector<std::string> const& targetNames, std::string const& config,
int jobs, bool /*verbose*/, cmBuildOptions const& /*buildOptions*/,
std::vector<std::string> const& makeOptions)
{
std::string const xcodebuild =
this->SelectMakeProgram(makeProgram, this->GetXcodeBuildCommand());
// Note that projectName can be empty, such as from a command like this one:
// ctest --build-and-test . build --build-generator Xcode
// If projectName is empty, then isWorkspace set further below will always
// be false because workspacePath will never point to a valid workspace file.
// And if projectName is empty, we don't add any -workspace or -project
// option to the xcodebuild command line because we don't know what to put
// after either option. For that scenario, we rely on xcodebuild finding
// exactly one .xcodeproj file in the working directory.
std::string const workspacePath = cmStrCat(projectName, ".xcworkspace");
std::string const projectPath = cmStrCat(projectName, ".xcodeproj");
// If an external tool created a workspace then build it instead.
bool const isWorkspace = cmSystemTools::FileIsDirectory(workspacePath);
std::string const targetFlag = isWorkspace ? "-scheme" : "-target";
std::vector<std::string> requiredArgs;
if (isWorkspace) {
requiredArgs.insert(requiredArgs.end(), { "-workspace", workspacePath });
} else if (!projectName.empty()) {
requiredArgs.insert(requiredArgs.end(), { "-project", projectPath });
}
bool const isCleanBuild = cm::contains(targetNames, "clean");
bool const isTargetEmpty = targetNames.empty() ||
((targetNames.size() == 1) && targetNames.front().empty());
if (isCleanBuild) {
requiredArgs.push_back("clean");
} else {
requiredArgs.push_back("build");
}
requiredArgs.insert(requiredArgs.end(),
{ "-configuration", config.empty() ? "Debug" : config });
if (isWorkspace) {
requiredArgs.insert(
requiredArgs.end(),
{ "-destination",
cmStrCat("generic/platform=", this->GetAppleSpecificPlatformName()) });
}
if ((this->XcodeBuildSystem >= BuildSystem::Twelve) ||
(jobs != cmake::NO_BUILD_PARALLEL_LEVEL)) {
requiredArgs.push_back("-parallelizeTargets");
}
if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) &&
(jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) {
requiredArgs.insert(requiredArgs.end(), { "-jobs", std::to_string(jobs) });
}
if (this->XcodeVersion >= 70) {
requiredArgs.push_back("-hideShellScriptEnvironment");
}
requiredArgs.insert(requiredArgs.end(), makeOptions.begin(),
makeOptions.end());
if (isWorkspace && !isCleanBuild && targetNames.size() > 1) {
// For workspaces we need a separate command for each target,
// because xcodebuild can pass only one -scheme arg
std::vector<GeneratedMakeCommand> makeCommands;
for (auto const& target : targetNames) {
if (target.empty()) {
continue;
}
GeneratedMakeCommand makeCommand;
makeCommand.Add(xcodebuild);
makeCommand.Add(requiredArgs.cbegin(), requiredArgs.cend());
makeCommand.Add(targetFlag, target);
makeCommands.emplace_back(std::move(makeCommand));
}
return makeCommands;
}
if (isTargetEmpty || isCleanBuild) {
requiredArgs.insert(requiredArgs.end(), { targetFlag, "ALL_BUILD" });
} else {
for (auto const& target : targetNames) {
if (target.empty()) {
continue;
}
requiredArgs.insert(requiredArgs.end(), { targetFlag, target });
}
}
GeneratedMakeCommand makeCommand;
makeCommand.Add(xcodebuild);
makeCommand.Add(requiredArgs.cbegin(), requiredArgs.cend());
return { std::move(makeCommand) };
}
//! Create a local generator appropriate to this Global Generator
std::unique_ptr<cmLocalGenerator> cmGlobalXCodeGenerator::CreateLocalGenerator(
cmMakefile* mf)
{
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()
{
// make sure extra targets are added before calling
// the parent generate which will call trace depends
for (auto keyVal : this->ProjectMap) {
cmLocalGenerator* root = keyVal.second[0];
this->SetGenerationRoot(root);
// add ALL_BUILD, INSTALL, etc
this->AddExtraTargets(root, keyVal.second);
}
}
void cmGlobalXCodeGenerator::Generate()
{
this->cmGlobalGenerator::Generate();
if (cmSystemTools::GetErrorOccurredFlag()) {
return;
}
for (auto keyVal : this->ProjectMap) {
cmLocalGenerator* root = keyVal.second[0];
bool generateTopLevelProjectOnly =
root->GetMakefile()->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY");
if (generateTopLevelProjectOnly) {
cmStateSnapshot snp = root->GetStateSnapshot();
if (snp.GetBuildsystemDirectoryParent().IsValid()) {
continue;
}
}
// cache the enabled languages for source file type queries
this->GetEnabledLanguages(this->EnabledLangs);
this->SetGenerationRoot(root);
// now create the project
this->OutputXCodeProject(root, keyVal.second);
}
}
void cmGlobalXCodeGenerator::SetGenerationRoot(cmLocalGenerator* root)
{
this->CurrentProject = root->GetProjectName();
this->SetCurrentLocalGenerator(root);
this->CurrentRootGenerator = root;
this->CurrentXCodeHackMakefile =
cmStrCat(root->GetCurrentBinaryDirectory(), "/CMakeScripts");
cmSystemTools::MakeDirectory(this->CurrentXCodeHackMakefile);
this->CurrentXCodeHackMakefile += "/XCODE_DEPEND_HELPER.make";
}
std::string cmGlobalXCodeGenerator::PostBuildMakeTarget(
std::string const& tName, std::string const& configName)
{
std::string target = tName;
std::replace(target.begin(), target.end(), ' ', '_');
std::string out = cmStrCat("PostBuild.", target, '.', configName);
return out;
}
#define CMAKE_CHECK_BUILD_SYSTEM_TARGET "ZERO_CHECK"
void cmGlobalXCodeGenerator::AddExtraTargets(
cmLocalGenerator* root, std::vector<cmLocalGenerator*>& gens)
{
// Add ALL_BUILD
auto cc = cm::make_unique<cmCustomCommand>();
cc->SetCommandLines(
cmMakeSingleCommandLine({ "echo", "Build all projects" }));
cmTarget* allbuild =
root->AddUtilityCommand("ALL_BUILD", true, std::move(cc));
// Add xcconfig files to ALL_BUILD sources
for (auto& config : this->CurrentConfigurationTypes) {
auto xcconfig = cmGeneratorExpression::Evaluate(
this->CurrentMakefile->GetSafeDefinition("CMAKE_XCODE_XCCONFIG"),
this->CurrentLocalGenerator, config);
if (!xcconfig.empty()) {
allbuild->AddSource(xcconfig);
}
}
root->AddGeneratorTarget(cm::make_unique<cmGeneratorTarget>(allbuild, root));
// Add XCODE depend helper
std::string legacyDependHelperDir = root->GetCurrentBinaryDirectory();
cmCustomCommandLines legacyDependHelperCommandLines;
if (this->XcodeBuildSystem == BuildSystem::One) {
legacyDependHelperCommandLines = cmMakeSingleCommandLine(
{ "make", "-C", legacyDependHelperDir, "-f",
this->CurrentXCodeHackMakefile, "OBJDIR=$(OBJDIR)",
/* placeholder, see below */ "" });
}
// Add ZERO_CHECK
bool regenerate = !this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION");
bool generateTopLevelProjectOnly =
root->GetMakefile()->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY");
bool isTopLevel =
!root->GetStateSnapshot().GetBuildsystemDirectoryParent().IsValid();
bool isGenerateProject = isTopLevel || !generateTopLevelProjectOnly;
if (regenerate && isGenerateProject) {
this->CreateReRunCMakeFile(root, gens);
std::string file =
cmSystemTools::ConvertToOutputPath(this->CurrentReRunCMakeMakefile);
cmSystemTools::ReplaceString(file, "\\ ", " ");
cc = cm::make_unique<cmCustomCommand>();
cc->SetCommandLines(cmMakeSingleCommandLine({ "make", "-f", file }));
cmTarget* check = root->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET,
true, std::move(cc));
root->AddGeneratorTarget(cm::make_unique<cmGeneratorTarget>(check, root));
}
// now make the allbuild depend on all the non-utility targets
// in the project
for (auto& gen : gens) {
for (auto const& target : gen->GetGeneratorTargets()) {
if (target->GetType() == cmStateEnums::GLOBAL_TARGET) {
continue;
}
if (regenerate &&
(target->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) {
target->Target->AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET, false);
}
// make all exe, shared libs and modules
// run the depend check makefile as a post build rule
// this will make sure that when the next target is built
// things are up-to-date
if (this->XcodeBuildSystem == BuildSystem::One && isGenerateProject &&
target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
legacyDependHelperCommandLines.front().back() = // fill placeholder
this->PostBuildMakeTarget(target->GetName(), "$(CONFIGURATION)");
cc = cm::make_unique<cmCustomCommand>();
cc->SetCommandLines(legacyDependHelperCommandLines);
cc->SetComment("Depend check for xcode");
cc->SetWorkingDirectory(legacyDependHelperDir.c_str());
gen->AddCustomCommandToTarget(
target->GetName(), cmCustomCommandType::POST_BUILD, std::move(cc),
cmObjectLibraryCommands::Accept);
}
if (!this->IsExcluded(gens[0], target.get())) {
allbuild->AddUtility(target->GetName(), false);
}
}
}
}
void cmGlobalXCodeGenerator::CreateReRunCMakeFile(
cmLocalGenerator* root, std::vector<cmLocalGenerator*> const& gens)
{
std::vector<std::string> lfiles;
for (auto* gen : gens) {
cm::append(lfiles, gen->GetMakefile()->GetListFiles());
}
// sort the array
std::sort(lfiles.begin(), lfiles.end());
lfiles.erase(std::unique(lfiles.begin(), lfiles.end()), lfiles.end());
cmake* cm = this->GetCMakeInstance();
if (cm->DoWriteGlobVerifyTarget()) {
lfiles.emplace_back(cm->GetGlobVerifyStamp());
}
this->CurrentReRunCMakeMakefile =
cmStrCat(root->GetCurrentBinaryDirectory(), "/CMakeScripts");
cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile);
this->CurrentReRunCMakeMakefile += "/ReRunCMake.make";
cmGeneratedFileStream makefileStream(this->CurrentReRunCMakeMakefile);
makefileStream.SetCopyIfDifferent(true);
makefileStream << "# Generated by CMake, DO NOT EDIT\n\n"
"TARGETS:= \n"
"empty:= \n"
"space:= $(empty) $(empty)\n"
"spaceplus:= $(empty)\\ $(empty)\n\n";
for (auto const& lfile : lfiles) {
makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard "
<< ConvertToMakefilePath(lfile) << "))\n";
}
makefileStream << '\n';
std::string checkCache =
cmStrCat(root->GetBinaryDirectory(), "/CMakeFiles/cmake.check_cache");
if (cm->DoWriteGlobVerifyTarget()) {
makefileStream << ".NOTPARALLEL:\n\n"
".PHONY: all VERIFY_GLOBS\n\n"
"all: VERIFY_GLOBS "
<< ConvertToMakefilePath(checkCache)
<< "\n\n"
"VERIFY_GLOBS:\n"
"\t"
<< ConvertToMakefilePath(cmSystemTools::GetCMakeCommand())
<< " -P "
<< ConvertToMakefilePath(cm->GetGlobVerifyScript())
<< "\n\n";
}
makefileStream << ConvertToMakefilePath(checkCache) << ": $(TARGETS)\n";
makefileStream
<< '\t' << ConvertToMakefilePath(cmSystemTools::GetCMakeCommand()) << " -S"
<< ConvertToMakefilePath(root->GetSourceDirectory()) << " -B"
<< ConvertToMakefilePath(root->GetBinaryDirectory())
<< (cm->GetIgnoreCompileWarningAsError() ? " --compile-no-warning-as-error"
: "")
<< (cm->GetIgnoreLinkWarningAsError() ? " --link-no-warning-as-error" : "")
<< '\n';
}
static bool objectIdLessThan(std::unique_ptr<cmXCodeObject> const& l,
std::unique_ptr<cmXCodeObject> const& r)
{
return l->GetId() < r->GetId();
}
void cmGlobalXCodeGenerator::SortXCodeObjects()
{
std::sort(this->XCodeObjects.begin(), this->XCodeObjects.end(),
objectIdLessThan);
}
void cmGlobalXCodeGenerator::ClearXCodeObjects()
{
this->TargetDoneSet.clear();
this->XCodeObjects.clear();
this->XCodeObjectIDs.clear();
this->XCodeObjectMap.clear();
this->GroupMap.clear();
this->GroupNameMap.clear();
this->TargetGroup.clear();
this->FileRefs.clear();
this->ExternalLibRefs.clear();
this->FileRefToBuildFileMap.clear();
this->FileRefToEmbedBuildFileMap.clear();
this->CommandsVisited.clear();
}
void cmGlobalXCodeGenerator::addObject(std::unique_ptr<cmXCodeObject> obj)
{
if (obj->GetType() == cmXCodeObject::OBJECT) {
std::string const& id = obj->GetId();
// If this is a duplicate id, it's an error:
//
if (this->XCodeObjectIDs.count(id)) {
cmSystemTools::Error(
"Xcode generator: duplicate object ids not allowed");
}
this->XCodeObjectIDs.insert(id);
}
this->XCodeObjects.push_back(std::move(obj));
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(
cmXCodeObject::PBXType ptype, cm::string_view key)
{
auto obj = cm::make_unique<cmXCode21Object>(ptype, cmXCodeObject::OBJECT,
this->GetObjectId(ptype, key));
auto* ptr = obj.get();
this->addObject(std::move(obj));
return ptr;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(cmXCodeObject::Type type)
{
auto obj = cm::make_unique<cmXCodeObject>(
cmXCodeObject::None, type,
"Temporary cmake object, should not be referred to in Xcode file");
auto* ptr = obj.get();
this->addObject(std::move(obj));
return ptr;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateString(cm::string_view s)
{
cmXCodeObject* obj = this->CreateObject(cmXCodeObject::STRING);
obj->SetString(s);
return obj;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateObjectReference(
cmXCodeObject* ref)
{
cmXCodeObject* obj = this->CreateObject(cmXCodeObject::OBJECT_REF);
obj->SetObject(ref);
return obj;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateFlatClone(cmXCodeObject* orig)
{
cmXCodeObject* obj = this->CreateObject(orig->GetType());
obj->CopyAttributes(orig);
return obj;
}
static std::string GetGroupMapKeyFromPath(cmGeneratorTarget* target,
std::string const& fullpath)
{
return cmStrCat(target->GetName(), '-', fullpath);
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeBuildFileFromPath(
std::string const& fullpath, cmGeneratorTarget* target,
std::string const& lang, cmSourceFile* sf)
{
// Using a map and the full path guarantees that we will always get the same
// fileRef object for any given full path. Same goes for the buildFile
// object.
cmXCodeObject* fileRef =
this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf);
if (fileRef) {
auto it = this->FileRefToBuildFileMap.find(fileRef);
if (it == this->FileRefToBuildFileMap.end()) {
cmXCodeObject* buildFile =
this->CreateObject(cmXCodeObject::PBXBuildFile);
buildFile->SetComment(fileRef->GetComment());
buildFile->AddAttribute("fileRef", this->CreateObjectReference(fileRef));
this->FileRefToBuildFileMap[fileRef] = buildFile;
return buildFile;
}
return it->second;
}
return nullptr;
}
class XCodeGeneratorExpressionInterpreter
: public cmGeneratorExpressionInterpreter
{
public:
XCodeGeneratorExpressionInterpreter(cmSourceFile* sourceFile,
cmLocalGenerator* localGenerator,
cmGeneratorTarget* headTarget,
std::string const& lang)
: cmGeneratorExpressionInterpreter(
localGenerator, "NO-PER-CONFIG-SUPPORT-IN-XCODE", headTarget, lang)
, SourceFile(sourceFile)
{
}
XCodeGeneratorExpressionInterpreter(
XCodeGeneratorExpressionInterpreter const&) = delete;
XCodeGeneratorExpressionInterpreter& operator=(
XCodeGeneratorExpressionInterpreter const&) = delete;
std::string const& Evaluate(char const* expression,
std::string const& property)
{
return this->Evaluate(std::string(expression ? expression : ""), property);
}
std::string const& Evaluate(std::string const& expression,
std::string const& property)
{
std::string const& processed =
this->cmGeneratorExpressionInterpreter::Evaluate(expression, property);
if (this->CompiledGeneratorExpression->GetHadContextSensitiveCondition()) {
std::ostringstream e;
/* clang-format off */
e <<
"Xcode does not support per-config per-source " << property << ":\n"
" " << expression << "\n"
"specified for source:\n"
" " << this->SourceFile->ResolveFullPath() << '\n';
/* clang-format on */
this->LocalGenerator->IssueMessage(MessageType::FATAL_ERROR, e.str());
}
return processed;
}
private:
cmSourceFile* SourceFile = nullptr;
};
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt)
{
std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);
XCodeGeneratorExpressionInterpreter genexInterpreter(sf, lg, gtgt, lang);
// Add flags from target and source file properties.
std::string flags;
std::string const& srcfmt = sf->GetSafeProperty("Fortran_FORMAT");
switch (cmOutputConverter::GetFortranFormat(srcfmt)) {
case cmOutputConverter::FortranFormatFixed:
flags = cmStrCat("-fixed ", flags);
break;
case cmOutputConverter::FortranFormatFree:
flags = cmStrCat("-free ", flags);
break;
default:
break;
}
// Explicitly add the explicit language flag before any other flag
// so user flags can override it.
gtgt->AddExplicitLanguageFlags(flags, *sf);
std::string const COMPILE_FLAGS("COMPILE_FLAGS");
if (cmValue cflags = sf->GetProperty(COMPILE_FLAGS)) {
lg->AppendFlags(flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS));
}
std::string const COMPILE_OPTIONS("COMPILE_OPTIONS");
if (cmValue coptions = sf->GetProperty(COMPILE_OPTIONS)) {
lg->AppendCompileOptions(
flags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS));
}
// Add per-source definitions.
BuildObjectListOrString flagsBuild(this, false);
std::string const COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
if (cmValue compile_defs = sf->GetProperty(COMPILE_DEFINITIONS)) {
this->AppendDefines(
flagsBuild,
genexInterpreter.Evaluate(*compile_defs, COMPILE_DEFINITIONS).c_str(),
true);
}
if (sf->GetPropertyAsBool("SKIP_PRECOMPILE_HEADERS")) {
this->AppendDefines(flagsBuild, "CMAKE_SKIP_PRECOMPILE_HEADERS", true);
}
if (!flagsBuild.IsEmpty()) {
if (!flags.empty()) {
flags += ' ';
}
flags += flagsBuild.GetString();
}
// Add per-source include directories.
std::vector<std::string> includes;
std::string const INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
if (cmValue cincludes = sf->GetProperty(INCLUDE_DIRECTORIES)) {
lg->AppendIncludeDirectories(
includes, genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES),
*sf);
}
lg->AppendFlags(flags,
lg->GetIncludeFlags(includes, gtgt, lang, std::string()));
cmXCodeObject* buildFile =
this->CreateXCodeBuildFileFromPath(sf->ResolveFullPath(), gtgt, lang, sf);
cmXCodeObject* settings = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
settings->AddAttributeIfNotEmpty("COMPILER_FLAGS",
this->CreateString(flags));
cmGeneratorTarget::SourceFileFlags tsFlags =
gtgt->GetTargetSourceFileFlags(sf);
cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);
// Is this a "private" or "public" framework header file?
// Set the ATTRIBUTES attribute appropriately...
//
if (gtgt->IsFrameworkOnApple()) {
if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePrivateHeader) {
attrs->AddObject(this->CreateString("Private"));
} else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePublicHeader) {
attrs->AddObject(this->CreateString("Public"));
}
}
// Add user-specified file attributes.
cmValue extraFileAttributes = sf->GetProperty("XCODE_FILE_ATTRIBUTES");
if (extraFileAttributes) {
// Expand the list of attributes.
cmList attributes{ *extraFileAttributes };
// Store the attributes.
for (auto const& attribute : attributes) {
attrs->AddObject(this->CreateString(attribute));
}
}
settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);
if (buildFile) {
buildFile->AddAttributeIfNotEmpty("settings", settings);
}
return buildFile;
}
void cmGlobalXCodeGenerator::AddXCodeProjBuildRule(
cmGeneratorTarget* target, std::vector<cmSourceFile*>& sources) const
{
std::string listfile = this->GetCMakeInstance()->GetCMakeListFile(
target->GetLocalGenerator()->GetCurrentSourceDirectory());
cmSourceFile* srcCMakeLists = target->Makefile->GetOrCreateSource(
listfile, false, cmSourceFileLocationKind::Known);
if (!cm::contains(sources, srcCMakeLists)) {
sources.push_back(srcCMakeLists);
}
}
namespace {
bool IsLinkPhaseLibraryExtension(std::string const& fileExt)
{
// Empty file extension is a special case for paths to framework's
// internal binary which could be MyFw.framework/Versions/*/MyFw
return (fileExt == ".framework"_s || fileExt == ".xcframework"_s ||
fileExt == ".a"_s || fileExt == ".o"_s || fileExt == ".dylib"_s ||
fileExt == ".tbd"_s || fileExt.empty());
}
bool IsLibraryType(std::string const& fileType)
{
return (fileType == "wrapper.framework"_s ||
fileType == "wrapper.xcframework"_s || fileType == "archive.ar"_s ||
fileType == "compiled.mach-o.objfile"_s ||
fileType == "compiled.mach-o.dylib"_s ||
fileType == "compiled.mach-o.executable"_s ||
fileType == "sourcecode.text-based-dylib-definition"_s);
}
std::string GetDirectoryValueFromFileExtension(std::string const& dirExt)
{
std::string ext = cmSystemTools::LowerCase(dirExt);
if (ext == "framework"_s) {
return "wrapper.framework";
}
if (ext == "xcframework"_s) {
return "wrapper.xcframework";
}
if (ext == "xcassets"_s) {
return "folder.assetcatalog";
}
return "folder";
}
std::string GetSourcecodeValueFromFileExtension(
std::string const& _ext, std::string const& lang,
bool& keepLastKnownFileType, std::vector<std::string> const& enabled_langs)
{
std::string ext = cmSystemTools::LowerCase(_ext);
std::string sourcecode = "default";
if (ext == "o"_s) {
keepLastKnownFileType = true;
sourcecode = "compiled.mach-o.objfile";
} else if (ext == "xctest"_s) {
sourcecode = "wrapper.cfbundle";
} else if (ext == "xib"_s) {
keepLastKnownFileType = true;
sourcecode = "file.xib";
} else if (ext == "storyboard"_s) {
keepLastKnownFileType = true;
sourcecode = "file.storyboard";
// NOLINTNEXTLINE(bugprone-branch-clone)
} else if (ext == "mm"_s && !cm::contains(enabled_langs, "OBJCXX")) {
sourcecode = "sourcecode.cpp.objcpp";
// NOLINTNEXTLINE(bugprone-branch-clone)
} else if (ext == "m"_s && !cm::contains(enabled_langs, "OBJC")) {
sourcecode = "sourcecode.c.objc";
} else if (ext == "swift"_s) {
sourcecode = "sourcecode.swift";
} else if (ext == "plist"_s) {
sourcecode = "sourcecode.text.plist";
} else if (ext == "h"_s) {
sourcecode = "sourcecode.c.h";
} else if (ext == "hxx"_s || ext == "hpp"_s || ext == "txx"_s ||
ext == "pch"_s || ext == "hh"_s || ext == "inl"_s) {
sourcecode = "sourcecode.cpp.h";
} else if (ext == "png"_s || ext == "gif"_s || ext == "jpg"_s) {
keepLastKnownFileType = true;
sourcecode = "image";
} else if (ext == "txt"_s) {
sourcecode = "sourcecode.text";
} else if (lang == "CXX"_s) {
sourcecode = "sourcecode.cpp.cpp";
} else if (lang == "C"_s) {
sourcecode = "sourcecode.c.c";
} else if (lang == "OBJCXX"_s) {
sourcecode = "sourcecode.cpp.objcpp";
} else if (lang == "OBJC"_s) {
sourcecode = "sourcecode.c.objc";
} else if (lang == "Fortran"_s) {
sourcecode = "sourcecode.fortran.f90";
} else if (lang == "ASM"_s) {
sourcecode = "sourcecode.asm";
} else if (ext == "metal"_s) {
sourcecode = "sourcecode.metal";
} else if (ext == "mig"_s) {
sourcecode = "sourcecode.mig";
} else if (ext == "tbd"_s) {
sourcecode = "sourcecode.text-based-dylib-definition";
} else if (ext == "a"_s) {
keepLastKnownFileType = true;
sourcecode = "archive.ar";
} else if (ext == "dylib"_s) {
keepLastKnownFileType = true;
sourcecode = "compiled.mach-o.dylib";
} else if (ext == "framework"_s) {
keepLastKnownFileType = true;
sourcecode = "wrapper.framework";
} else if (ext == "xcassets"_s) {
keepLastKnownFileType = true;
sourcecode = "folder.assetcatalog";
} else if (ext == "xcconfig"_s) {
keepLastKnownFileType = true;
sourcecode = "text.xcconfig";
}
// else
// {
// // Already specialized above or we leave sourcecode == "sourcecode"
// // which is probably the most correct choice. Extensionless headers,
// // for example... Or file types unknown to Xcode that do not map to a
// // valid explicitFileType value.
// }
return sourcecode;
}
template <class T>
std::string GetTargetObjectDirArch(T const& target,
std::string const& defaultVal)
{
cmList archs{ target.GetSafeProperty("OSX_ARCHITECTURES") };
if (archs.size() > 1) {
return "$(CURRENT_ARCH)";
}
if (archs.size() == 1) {
return archs.front();
}
return defaultVal;
}
} // anonymous
// Extracts the framework directory, if path matches the framework syntax
// otherwise returns the path untouched
std::string cmGlobalXCodeGenerator::GetLibraryOrFrameworkPath(
std::string const& path) const
{
auto fwDescriptor = this->SplitFrameworkPath(path);
if (fwDescriptor) {
return fwDescriptor->GetFrameworkPath();
}
return path;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
std::string const& fullpath, cmGeneratorTarget* target,
std::string const& lang, cmSourceFile* sf)
{
bool useLastKnownFileType = false;
std::string fileType;
if (sf) {
if (cmValue e = sf->GetProperty("XCODE_EXPLICIT_FILE_TYPE")) {
fileType = *e;
} else if (cmValue l = sf->GetProperty("XCODE_LAST_KNOWN_FILE_TYPE")) {
useLastKnownFileType = true;
fileType = *l;
}
}
// Make a copy so that we can override it later
std::string path = cmSystemTools::CollapseFullPath(fullpath);
// Compute the extension without leading '.'.
std::string ext = cmSystemTools::GetFilenameLastExtension(path);
if (!ext.empty()) {
ext = ext.substr(1);
}
if (fileType.empty()) {
path = this->GetLibraryOrFrameworkPath(path);
ext = cmSystemTools::GetFilenameLastExtension(path);
if (!ext.empty()) {
ext = ext.substr(1);
}
// If fullpath references a directory, then we need to specify
// lastKnownFileType as folder in order for Xcode to be able to
// open the contents of the folder.
// (Xcode 4.6 does not like explicitFileType=folder).
if (cmSystemTools::FileIsDirectory(path)) {
fileType = GetDirectoryValueFromFileExtension(ext);
useLastKnownFileType = true;
} else {
if (ext.empty() && !sf) {
// Special case for executable or library without extension
// that is not a source file. We can't tell which without reading
// its Mach-O header, but the file might not exist yet, so we
// have to pick one here.
useLastKnownFileType = true;
fileType = "compiled.mach-o.executable";
} else {
fileType = GetSourcecodeValueFromFileExtension(
ext, lang, useLastKnownFileType, this->EnabledLangs);
}
}
}
std::string key = GetGroupMapKeyFromPath(target, path);
cmXCodeObject* fileRef = this->FileRefs[key];
if (!fileRef) {
fileRef = this->CreateObject(cmXCodeObject::PBXFileReference);
fileRef->SetComment(path);
this->FileRefs[key] = fileRef;
}
fileRef->AddAttribute("fileEncoding", this->CreateString("4"));
fileRef->AddAttribute(useLastKnownFileType ? "lastKnownFileType"
: "explicitFileType",
this->CreateString(fileType));
// Store the file path relative to the top of the source tree.
if (!IsLibraryType(fileType)) {
path = this->RelativeToSource(path);
}
std::string name = cmSystemTools::GetFilenameName(path);
char const* sourceTree =
cmSystemTools::FileIsFullPath(path) ? "<absolute>" : "SOURCE_ROOT";
fileRef->AddAttribute("name", this->CreateString(name));
fileRef->AddAttribute("path", this->CreateString(path));
fileRef->AddAttribute("sourceTree", this->CreateString(sourceTree));
cmXCodeObject* group = this->GroupMap[key];
if (!group) {
if (IsLibraryType(fileType)) {
group = this->FrameworkGroup;
} else if (fileType == "folder") {
group = this->ResourcesGroup;
}
if (group)
this->GroupMap[key] = group;
}
if (!group) {
cmSystemTools::Error(cmStrCat("Could not find a PBX group for ", key));
return nullptr;
}
cmXCodeObject* children = group->GetAttribute("children");
if (!children->HasObject(fileRef)) {
children->AddObject(fileRef);
}
return fileRef;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReference(
cmSourceFile* sf, cmGeneratorTarget* target)
{
std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf);
return this->CreateXCodeFileReferenceFromPath(sf->ResolveFullPath(), target,
lang, sf);
}
bool cmGlobalXCodeGenerator::SpecialTargetEmitted(std::string const& tname)
{
if (tname == "ALL_BUILD"_s || tname == "install"_s || tname == "package"_s ||
tname == "RUN_TESTS"_s || tname == CMAKE_CHECK_BUILD_SYSTEM_TARGET) {
if (this->TargetDoneSet.find(tname) != this->TargetDoneSet.end()) {
return true;
}
this->TargetDoneSet.insert(tname);
return false;
}
return false;
}
void cmGlobalXCodeGenerator::SetCurrentLocalGenerator(cmLocalGenerator* gen)
{
this->CurrentLocalGenerator = gen;
this->CurrentMakefile = gen->GetMakefile();
// Select the current set of configuration types.
this->CurrentConfigurationTypes =
this->CurrentMakefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
}
struct cmSourceFilePathCompare
{
bool operator()(cmSourceFile* l, cmSourceFile* r)
{
return l->ResolveFullPath() < r->ResolveFullPath();
}
};
struct cmCompareTargets
{
bool operator()(cmXCodeObject* l, cmXCodeObject* r) const
{
std::string const& a = l->GetTarget()->GetName();
std::string const& b = r->GetTarget()->GetName();
if (a == b) {
return false;
}
if (a == "ALL_BUILD"_s) {
return true;
}
if (b == "ALL_BUILD"_s) {
return false;
}
std::string a_low = cmSystemTools::LowerCase(l->GetTarget()->GetName());
std::string b_low = cmSystemTools::LowerCase(r->GetTarget()->GetName());
if (a_low != b_low) {
return a_low < b_low;
}
return a < b;
}
};
bool cmGlobalXCodeGenerator::CreateXCodeTargets(
cmLocalGenerator* gen, std::vector<cmXCodeObject*>& targets)
{
this->SetCurrentLocalGenerator(gen);
std::vector<cmGeneratorTarget*> gts =
this->GetLocalGeneratorTargetsInOrder(gen);
for (auto* gtgt : gts) {
if (!this->CreateXCodeTarget(gtgt, targets)) {
return false;
}
}
std::sort(targets.begin(), targets.end(), cmCompareTargets());
return true;
}
bool cmGlobalXCodeGenerator::CreateXCodeTarget(
cmGeneratorTarget* gtgt, std::vector<cmXCodeObject*>& targets)
{
std::string targetName = gtgt->GetName();
// make sure ALL_BUILD, INSTALL, etc are only done once
if (this->SpecialTargetEmitted(targetName)) {
return true;
}
if (!gtgt->IsInBuildSystem()) {
return true;
}
for (std::string const& configName : this->CurrentConfigurationTypes) {
gtgt->CheckCxxModuleStatus(configName);
}
auto& gtgt_visited = this->CommandsVisited[gtgt];
auto const& deps = this->GetTargetDirectDepends(gtgt);
for (auto const& d : deps) {
// Take the union of visited source files of custom commands so far.
// ComputeTargetOrder ensures our dependencies already visited their
// custom commands and updated CommandsVisited.
auto& dep_visited = this->CommandsVisited[d];
gtgt_visited.insert(dep_visited.begin(), dep_visited.end());
}
if (gtgt->GetType() == cmStateEnums::UTILITY ||
gtgt->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
gtgt->GetType() == cmStateEnums::GLOBAL_TARGET) {
cmXCodeObject* t = this->CreateUtilityTarget(gtgt);
if (!t) {
return false;
}
targets.push_back(t);
return true;
}
// organize the sources
std::vector<cmSourceFile*> commonSourceFiles;
if (!gtgt->GetConfigCommonSourceFilesForXcode(commonSourceFiles)) {
return false;
}
// Add CMakeLists.txt file for user convenience.
this->AddXCodeProjBuildRule(gtgt, commonSourceFiles);
// Add the Info.plist we are about to generate for an App Bundle.
if (gtgt->GetPropertyAsBool("MACOSX_BUNDLE")) {
std::string plist = this->ComputeInfoPListLocation(gtgt);
cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource(
plist, true, cmSourceFileLocationKind::Known);
commonSourceFiles.push_back(sf);
}
std::sort(commonSourceFiles.begin(), commonSourceFiles.end(),
cmSourceFilePathCompare());
gtgt->ComputeObjectMapping();
std::vector<cmXCodeObject*> externalObjFiles;
std::vector<cmXCodeObject*> headerFiles;
std::vector<cmXCodeObject*> resourceFiles;
std::vector<cmXCodeObject*> sourceFiles;
for (auto* sourceFile : commonSourceFiles) {
cmXCodeObject* xsf = this->CreateXCodeSourceFile(
this->CurrentLocalGenerator, sourceFile, gtgt);
cmXCodeObject* fr = xsf->GetAttribute("fileRef");
cmXCodeObject* filetype =
fr->GetObject()->GetAttribute("explicitFileType");
if (!filetype) {
filetype = fr->GetObject()->GetAttribute("lastKnownFileType");
}
cmGeneratorTarget::SourceFileFlags tsFlags =
gtgt->GetTargetSourceFileFlags(sourceFile);
if (filetype && filetype->GetString() == "compiled.mach-o.objfile"_s) {
if (sourceFile->GetObjectLibrary().empty()) {
externalObjFiles.push_back(xsf);
}
} else if (this->IsHeaderFile(sourceFile) ||
(tsFlags.Type ==
cmGeneratorTarget::SourceFileTypePrivateHeader) ||
(tsFlags.Type ==
cmGeneratorTarget::SourceFileTypePublicHeader)) {
headerFiles.push_back(xsf);
} else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypeResource) {
resourceFiles.push_back(xsf);
} else if (!sourceFile->GetPropertyAsBool("HEADER_FILE_ONLY") &&
!gtgt->IsSourceFilePartOfUnityBatch(
sourceFile->ResolveFullPath())) {
// Include this file in the build if it has a known language
// and has not been listed as an ignored extension for this
// generator.
if (!this->CurrentLocalGenerator->GetSourceFileLanguage(*sourceFile)
.empty() &&
!this->IgnoreFile(sourceFile->GetExtension().c_str())) {
sourceFiles.push_back(xsf);
}
}
}
// some build phases only apply to bundles and/or frameworks
bool isFrameworkTarget = gtgt->IsFrameworkOnApple();
bool isBundleTarget = gtgt->GetPropertyAsBool("MACOSX_BUNDLE");
bool isCFBundleTarget = gtgt->IsCFBundleOnApple();
cmXCodeObject* buildFiles = nullptr;
// create source build phase
cmXCodeObject* sourceBuildPhase = nullptr;
if (!sourceFiles.empty()) {
sourceBuildPhase = this->CreateObject(cmXCodeObject::PBXSourcesBuildPhase);
sourceBuildPhase->SetComment("Sources");
sourceBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (auto& sourceFile : sourceFiles) {
buildFiles->AddObject(sourceFile);
}
sourceBuildPhase->AddAttribute("files", buildFiles);
sourceBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
}
// create header build phase - only for framework targets
cmXCodeObject* headerBuildPhase = nullptr;
if (!headerFiles.empty() && isFrameworkTarget) {
headerBuildPhase = this->CreateObject(cmXCodeObject::PBXHeadersBuildPhase);
headerBuildPhase->SetComment("Headers");
headerBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (auto& headerFile : headerFiles) {
buildFiles->AddObject(headerFile);
}
headerBuildPhase->AddAttribute("files", buildFiles);
headerBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
}
// create resource build phase - only for framework or bundle targets
cmXCodeObject* resourceBuildPhase = nullptr;
if (!resourceFiles.empty() &&
(isFrameworkTarget || isBundleTarget || isCFBundleTarget)) {
resourceBuildPhase =
this->CreateObject(cmXCodeObject::PBXResourcesBuildPhase);
resourceBuildPhase->SetComment("Resources");
resourceBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (auto& resourceFile : resourceFiles) {
buildFiles->AddObject(resourceFile);
}
resourceBuildPhase->AddAttribute("files", buildFiles);
resourceBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
}
// create vector of "non-resource content file" build phases - only for
// framework or bundle targets
std::vector<cmXCodeObject*> contentBuildPhases;
if (isFrameworkTarget || isBundleTarget || isCFBundleTarget) {
using mapOfVectorOfSourceFiles =
std::map<std::string, std::vector<cmSourceFile*>>;
mapOfVectorOfSourceFiles bundleFiles;
for (auto* sourceFile : commonSourceFiles) {
cmGeneratorTarget::SourceFileFlags tsFlags =
gtgt->GetTargetSourceFileFlags(sourceFile);
if (tsFlags.Type == cmGeneratorTarget::SourceFileTypeMacContent) {
bundleFiles[tsFlags.MacFolder].push_back(sourceFile);
}
}
for (auto const& keySources : bundleFiles) {
cmXCodeObject* copyFilesBuildPhase =
this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase);
copyFilesBuildPhase->SetComment("Copy files");
copyFilesBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
copyFilesBuildPhase->AddAttribute("dstSubfolderSpec",
this->CreateString("6"));
std::ostringstream ostr;
if (gtgt->IsFrameworkOnApple()) {
// dstPath in frameworks is relative to Versions/<version>
ostr << keySources.first;
} else if (keySources.first != "MacOS"_s) {
if (gtgt->Target->GetMakefile()->PlatformIsAppleEmbedded()) {
ostr << keySources.first;
} else {
// dstPath in bundles is relative to Contents/MacOS
ostr << "../" << keySources.first;
}
}
copyFilesBuildPhase->AddAttribute("dstPath",
this->CreateString(ostr.str()));
copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
copyFilesBuildPhase->AddAttribute("files", buildFiles);
for (auto* sourceFile : keySources.second) {
cmXCodeObject* xsf = this->CreateXCodeSourceFile(
this->CurrentLocalGenerator, sourceFile, gtgt);
buildFiles->AddObject(xsf);
}
contentBuildPhases.push_back(copyFilesBuildPhase);
}
}
// create vector of "resource content file" build phases - only for
// framework or bundle targets
if (isFrameworkTarget || isBundleTarget || isCFBundleTarget) {
using mapOfVectorOfSourceFiles =
std::map<std::string, std::vector<cmSourceFile*>>;
mapOfVectorOfSourceFiles bundleFiles;
for (auto* sourceFile : commonSourceFiles) {
cmGeneratorTarget::SourceFileFlags tsFlags =
gtgt->GetTargetSourceFileFlags(sourceFile);
if (tsFlags.Type == cmGeneratorTarget::SourceFileTypeDeepResource) {
bundleFiles[tsFlags.MacFolder].push_back(sourceFile);
}
}
for (auto const& keySources : bundleFiles) {
cmXCodeObject* copyFilesBuildPhase =
this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase);
copyFilesBuildPhase->SetComment("Copy files");
copyFilesBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
copyFilesBuildPhase->AddAttribute("dstSubfolderSpec",
this->CreateString("7"));
copyFilesBuildPhase->AddAttribute("dstPath",
this->CreateString(keySources.first));
copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
copyFilesBuildPhase->AddAttribute("files", buildFiles);
for (auto* sourceFile : keySources.second) {
cmXCodeObject* xsf = this->CreateXCodeSourceFile(
this->CurrentLocalGenerator, sourceFile, gtgt);
buildFiles->AddObject(xsf);
}
contentBuildPhases.push_back(copyFilesBuildPhase);
}
}
// Always create Link Binary With Libraries build phase
cmXCodeObject* frameworkBuildPhase = nullptr;
frameworkBuildPhase =
this->CreateObject(cmXCodeObject::PBXFrameworksBuildPhase);
frameworkBuildPhase->SetComment("Frameworks");
frameworkBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
frameworkBuildPhase->AddAttribute("files", buildFiles);
// Add all collected .o files to this build phase
for (auto& externalObjFile : externalObjFiles) {
buildFiles->AddObject(externalObjFile);
}
frameworkBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
// create list of build phases and create the Xcode target
cmXCodeObject* buildPhases = this->CreateObject(cmXCodeObject::OBJECT_LIST);
this->CreateCustomCommands(buildPhases, sourceBuildPhase, headerBuildPhase,
resourceBuildPhase, contentBuildPhases,
frameworkBuildPhase, gtgt);
targets.push_back(this->CreateXCodeTarget(gtgt, buildPhases));
return true;
}
void cmGlobalXCodeGenerator::ForceLinkerLanguages()
{
for (auto const& localGenerator : this->LocalGenerators) {
// All targets depend on the build-system check target.
for (auto const& tgt : localGenerator->GetGeneratorTargets()) {
// This makes sure all targets link using the proper language.
this->ForceLinkerLanguage(tgt.get());
}
}
}
void cmGlobalXCodeGenerator::ForceLinkerLanguage(cmGeneratorTarget* gtgt)
{
// This matters only for targets that link.
if (gtgt->GetType() != cmStateEnums::EXECUTABLE &&
gtgt->GetType() != cmStateEnums::SHARED_LIBRARY &&
gtgt->GetType() != cmStateEnums::MODULE_LIBRARY) {
return;
}
std::string llang = gtgt->GetLinkerLanguage("NOCONFIG");
if (llang.empty()) {
return;
}
// If the language is compiled as a source trust Xcode to link with it.
for (auto const& Language :
gtgt->GetLinkImplementation("NOCONFIG", cmGeneratorTarget::UseTo::Link)
->Languages) {
if (Language == llang) {
return;
}
}
// Allow empty source file list for iOS Sticker packs
cm::string_view productType = this->GetTargetProductType(gtgt);
if (productType ==
"com.apple.product-type.app-extension.messages-sticker-pack"_s) {
return;
}
// Add an empty source file to the target that compiles with the
// linker language. This should convince Xcode to choose the proper
// language.
cmMakefile* mf = gtgt->Target->GetMakefile();
std::string fname = cmStrCat(
gtgt->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/CMakeFiles/",
gtgt->GetName(), "-CMakeForceLinker.", cmSystemTools::LowerCase(llang));
{
cmGeneratedFileStream fout(fname);
fout << '\n';
}
if (cmSourceFile* sf = mf->GetOrCreateSource(fname)) {
sf->SetProperty("LANGUAGE", llang);
sf->SetProperty("CXX_SCAN_FOR_MODULES", "0");
gtgt->AddSource(fname);
}
}
bool cmGlobalXCodeGenerator::IsHeaderFile(cmSourceFile* sf)
{
return cm::contains(this->CMakeInstance->GetHeaderExtensions(),
sf->GetExtension());
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateLegacyRunScriptBuildPhase(
char const* name, char const* name2, cmGeneratorTarget* target,
std::vector<cmCustomCommand> const& commands)
{
if (commands.empty() && strcmp(name, "CMake ReRun") != 0) {
return nullptr;
}
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"));
this->AddCommandsToBuildPhase(buildPhase, target, commands, name2);
return buildPhase;
}
void cmGlobalXCodeGenerator::CreateCustomCommands(
cmXCodeObject* buildPhases, cmXCodeObject* sourceBuildPhase,
cmXCodeObject* headerBuildPhase, cmXCodeObject* resourceBuildPhase,
std::vector<cmXCodeObject*> const& contentBuildPhases,
cmXCodeObject* frameworkBuildPhase, cmGeneratorTarget* gtgt)
{
std::vector<cmCustomCommand> const& prebuild = gtgt->GetPreBuildCommands();
std::vector<cmCustomCommand> const& prelink = gtgt->GetPreLinkCommands();
std::vector<cmCustomCommand> postbuild = gtgt->GetPostBuildCommands();
if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY &&
!gtgt->IsFrameworkOnApple()) {
std::string str_file = cmStrCat("$<TARGET_FILE:", gtgt->GetName(), '>');
std::string str_so_file =
cmStrCat("$<TARGET_SONAME_FILE:", gtgt->GetName(), '>');
std::string str_link_file =
cmStrCat("$<TARGET_LINKER_LIBRARY_FILE:", gtgt->GetName(), '>');
cmCustomCommandLines cmd = cmMakeSingleCommandLine(
{ cmSystemTools::GetCMakeCommand(), "-E", "cmake_symlink_library",
str_file, str_so_file, str_link_file });
cmCustomCommand command;
command.SetCommandLines(cmd);
command.SetComment("Creating symlinks");
command.SetWorkingDirectory("");
command.SetBacktrace(this->CurrentMakefile->GetBacktrace());
command.SetStdPipesUTF8(true);
postbuild.push_back(std::move(command));
}
if (gtgt->HasImportLibrary("") && !gtgt->IsFrameworkOnApple()) {
// create symbolic links for .tbd file
std::string file = cmStrCat("$<TARGET_IMPORT_FILE:", gtgt->GetName(), '>');
std::string soFile =
cmStrCat("$<TARGET_SONAME_IMPORT_FILE:", gtgt->GetName(), '>');
std::string linkFile =
cmStrCat("$<TARGET_LINKER_IMPORT_FILE:", gtgt->GetName(), '>');
cmCustomCommandLines symlink_command = cmMakeSingleCommandLine(
{ cmSystemTools::GetCMakeCommand(), "-E", "cmake_symlink_library", file,
soFile, linkFile });
cmCustomCommand command;
command.SetCommandLines(symlink_command);
command.SetComment("Creating import symlinks");
command.SetWorkingDirectory("");
command.SetBacktrace(this->CurrentMakefile->GetBacktrace());
command.SetStdPipesUTF8(true);
postbuild.push_back(std::move(command));
}
cmXCodeObject* legacyCustomCommandsBuildPhase = nullptr;
cmXCodeObject* preBuildPhase = nullptr;
cmXCodeObject* preLinkPhase = nullptr;
cmXCodeObject* postBuildPhase = nullptr;
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
// create prebuild phase
preBuildPhase =
this->CreateRunScriptBuildPhase("CMake PreBuild Rules", gtgt, prebuild);
// create prelink phase
preLinkPhase =
this->CreateRunScriptBuildPhase("CMake PreLink Rules", gtgt, prelink);
std::vector<std::string> depends;
if (gtgt->IsBundleOnApple()) {
// In Xcode 16+ the POST_BUILD phase needs explicit dependencies to
// ensure it runs after certain bundle files are generated.
depends = {
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/"
"Contents/Resources/DWARF/${PRODUCT_NAME}",
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/"
"Contents/Info.plist",
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
"$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)",
};
if (resourceBuildPhase) {
auto resourceFiles = resourceBuildPhase->GetAttribute("files");
for (auto xsf : resourceFiles->GetObjectList()) {
auto fileRef = xsf->GetAttribute("fileRef");
auto fileObj = fileRef->GetObject();
auto path = fileObj->GetAttribute("path");
auto fileName = cmSystemTools::GetFilenameName(path->GetString());
if (cmSystemTools::GetFilenameLastExtension(fileName) == ".plist") {
depends.push_back(
"$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/" +
fileName);
}
}
}
}
// create postbuild phase
postBuildPhase = this->CreateRunScriptBuildPhase("CMake PostBuild Rules",
gtgt, postbuild, depends);
} else {
std::vector<cmSourceFile*> classes;
if (!gtgt->GetConfigCommonSourceFilesForXcode(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());
}
}
// 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
// from an xcode template...
//
if (preBuildPhase) {
buildPhases->AddObject(preBuildPhase);
}
if (legacyCustomCommandsBuildPhase) {
buildPhases->AddObject(legacyCustomCommandsBuildPhase);
}
if (this->XcodeBuildSystem >= BuildSystem::Twelve) {
this->CreateRunScriptBuildPhases(buildPhases, gtgt);
}
if (headerBuildPhase) {
buildPhases->AddObject(headerBuildPhase);
}
if (resourceBuildPhase) {
buildPhases->AddObject(resourceBuildPhase);
}
for (auto* obj : contentBuildPhases) {
buildPhases->AddObject(obj);
}
if (sourceBuildPhase) {
buildPhases->AddObject(sourceBuildPhase);
}
if (preLinkPhase) {
buildPhases->AddObject(preLinkPhase);
}
if (frameworkBuildPhase) {
buildPhases->AddObject(frameworkBuildPhase);
}
// When this build phase is present, it must be last. More build phases may
// be added later for embedding things and they will insert themselves just
// before this last build phase.
if (postBuildPhase) {
buildPhases->AddObject(postBuildPhase);
}
}
void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases(
cmXCodeObject* buildPhases, cmGeneratorTarget const* gt)
{
std::vector<cmSourceFile*> sources;
if (!gt->GetConfigCommonSourceFilesForXcode(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;
cmXCodeObject* buildPhase =
this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase,
cmStrCat(gt->GetName(), ':', sf->GetFullPath()));
auto depfilesDirectory = cmStrCat(
gt->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/CMakeFiles/d/");
auto depfilesPrefix = cmStrCat(depfilesDirectory, buildPhase->GetId(), '.');
std::string shellScript = "set -e\n";
for (std::string const& configName : this->CurrentConfigurationTypes) {
cmCustomCommandGenerator ccg(
cc, configName, this->CurrentLocalGenerator, true, {},
[&depfilesPrefix](std::string const& config, std::string const&)
-> std::string { return cmStrCat(depfilesPrefix, config, ".d"); });
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");
}
if (!cc.GetDepfile().empty()) {
buildPhase->AddAttribute(
"dependencyFile",
this->CreateString(cmStrCat(depfilesDirectory, buildPhase->GetId(),
".$(CONFIGURATION).d")));
// to avoid spurious errors during first build, create empty dependency
// files
cmSystemTools::MakeDirectory(depfilesDirectory);
for (std::string const& configName : this->CurrentConfigurationTypes) {
auto file = cmStrCat(depfilesPrefix, configName, ".d");
if (!cmSystemTools::FileExists(file)) {
cmSystemTools::Touch(file, true);
}
}
}
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, cmGeneratorTarget const* gt,
std::vector<cmCustomCommand> const& commands,
std::vector<std::string> const& depends)
{
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,
cmStrCat(gt->GetName(), ':', name));
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* inputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (std::string const& s : depends) {
inputPaths->AddUniqueObject(this->CreateString(s));
}
buildPhase->AddAttribute("inputPaths", inputPaths);
}
{
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;
}
namespace {
void ReplaceScriptVars(std::string& cmd)
{
cmSystemTools::ReplaceString(cmd, "$(CONFIGURATION)", "$CONFIGURATION");
cmSystemTools::ReplaceString(cmd, "$(EFFECTIVE_PLATFORM_NAME)",
"$EFFECTIVE_PLATFORM_NAME");
}
}
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);
ReplaceScriptVars(wd);
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);
ReplaceScriptVars(cmd);
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(char const* flag,
std::string& flags)
{
std::string retFlag;
std::string::size_type lastOccurrencePos = flags.rfind(flag);
bool saved = false;
while (lastOccurrencePos != std::string::npos) {
// increment pos, we use lastOccurrencePos to reduce search space on next
// inc
std::string::size_type pos = lastOccurrencePos;
if (pos == 0 || flags[pos - 1] == ' ') {
while (pos < flags.size() && flags[pos] != ' ') {
if (!saved) {
retFlag += flags[pos];
}
flags[pos] = ' ';
pos++;
}
saved = true;
}
// decrement lastOccurrencePos while making sure we don't loop around
// and become a very large positive number since size_type is unsigned
lastOccurrencePos = lastOccurrencePos == 0 ? 0 : lastOccurrencePos - 1;
lastOccurrencePos = flags.rfind(flag, lastOccurrencePos);
}
return retFlag;
}
// This function removes each matching occurrence of the expression and
// returns the last one (i.e., the dominant flag in GCC)
std::string cmGlobalXCodeGenerator::ExtractFlagRegex(char const* exp,
int matchIndex,
std::string& flags)
{
std::string retFlag;
cmsys::RegularExpression regex(exp);
assert(regex.is_valid());
if (!regex.is_valid()) {
return retFlag;
}
std::string::size_type offset = 0;
while (regex.find(&flags[offset])) {
std::string::size_type const startPos = offset + regex.start(matchIndex);
std::string::size_type const endPos = offset + regex.end(matchIndex);
std::string::size_type const size = endPos - startPos;
offset = startPos + 1;
retFlag.assign(flags, startPos, size);
flags.replace(startPos, size, size, ' ');
}
return retFlag;
}
//----------------------------------------------------------------------------
// This function strips off Xcode attributes that do not target the current
// configuration
void cmGlobalXCodeGenerator::FilterConfigurationAttribute(
std::string const& configName, std::string& attribute)
{
// Handle [variant=<config>] condition explicitly here.
std::string::size_type beginVariant = attribute.find("[variant=");
if (beginVariant == std::string::npos) {
// There is no variant in this attribute.
return;
}
std::string::size_type endVariant = attribute.find(']', beginVariant + 9);
if (endVariant == std::string::npos) {
// There is no terminating bracket.
return;
}
// Compare the variant to the configuration.
std::string variant =
attribute.substr(beginVariant + 9, endVariant - beginVariant - 9);
if (variant == configName) {
// The variant matches the configuration so use this
// attribute but drop the [variant=<config>] condition.
attribute.erase(beginVariant, endVariant - beginVariant + 1);
} else {
// The variant does not match the configuration so
// do not use this attribute.
attribute.clear();
}
}
void cmGlobalXCodeGenerator::AddCommandsToBuildPhase(
cmXCodeObject* buildphase, cmGeneratorTarget* target,
std::vector<cmCustomCommand> const& commands, char const* name)
{
std::string dir = cmStrCat(
this->CurrentLocalGenerator->GetCurrentBinaryDirectory(), "/CMakeScripts");
cmSystemTools::MakeDirectory(dir);
std::string makefile =
cmStrCat(dir, '/', target->GetName(), '_', name, ".make");
for (auto const& currentConfig : this->CurrentConfigurationTypes) {
this->CreateCustomRulesMakefile(makefile.c_str(), target, commands,
currentConfig);
}
std::string cdir = this->CurrentLocalGenerator->GetCurrentBinaryDirectory();
cdir = cmSystemTools::ConvertToOutputPath(cdir);
std::string makecmd = cmStrCat(
"make -C ", cdir, " -f ", cmSystemTools::ConvertToOutputPath(makefile),
"$CONFIGURATION OBJDIR=$(basename \"$OBJECT_FILE_DIR_normal\") all");
buildphase->AddAttribute("shellScript", this->CreateString(makecmd));
buildphase->AddAttribute("showEnvVarsInLog", this->CreateString("0"));
}
void cmGlobalXCodeGenerator::CreateCustomRulesMakefile(
char const* makefileBasename, cmGeneratorTarget* target,
std::vector<cmCustomCommand> const& commands, std::string const& configName)
{
std::string makefileName = cmStrCat(makefileBasename, configName);
cmGeneratedFileStream makefileStream(makefileName);
if (!makefileStream) {
return;
}
makefileStream.SetCopyIfDifferent(true);
makefileStream << "# Generated by CMake, DO NOT EDIT\n"
"# Custom rules for "
<< target->GetName() << '\n';
// disable the implicit rules
makefileStream << ".SUFFIXES: "
"\n";
// have all depend on all outputs
makefileStream << "all: ";
std::map<cmCustomCommand const*, std::string> tname;
int count = 0;
for (auto const& command : commands) {
cmCustomCommandGenerator ccg(command, configName,
this->CurrentLocalGenerator);
if (ccg.GetNumberOfCommands() > 0) {
std::vector<std::string> const& outputs = ccg.GetOutputs();
if (!outputs.empty()) {
for (auto const& output : outputs) {
makefileStream << "\\\n\t" << ConvertToMakefilePath(output);
}
} else {
std::ostringstream str;
str << "_buildpart_" << count++;
tname[&ccg.GetCC()] = cmStrCat(target->GetName(), str.str());
makefileStream << "\\\n\t" << tname[&ccg.GetCC()];
}
}
}
makefileStream << "\n\n";
auto depfilesDirectory =
cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(),
"/CMakeFiles/d/");
for (auto const& command : commands) {
cmCustomCommandGenerator ccg(
command, configName, this->CurrentLocalGenerator, true, {},
[this, &depfilesDirectory](std::string const& config,
std::string const& file) -> std::string {
return cmStrCat(
depfilesDirectory,
this->GetObjectId(cmXCodeObject::PBXShellScriptBuildPhase, file),
'.', config, ".d");
});
auto depfile = ccg.GetInternalDepfile();
if (!depfile.empty()) {
makefileStream << "include "
<< cmSystemTools::ConvertToOutputPath(depfile) << "\n\n";
cmSystemTools::MakeDirectory(depfilesDirectory);
if (!cmSystemTools::FileExists(depfile)) {
cmSystemTools::Touch(depfile, true);
}
}
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));
}
}
if (ccg.GetNumberOfCommands() > 0) {
makefileStream << '\n';
std::vector<std::string> const& outputs = ccg.GetOutputs();
if (!outputs.empty()) {
// There is at least one output, start the rule for it
char const* sep = "";
for (auto const& output : outputs) {
makefileStream << sep << ConvertToMakefilePath(output);
sep = " ";
}
makefileStream << ": ";
} else {
// There are no outputs. Use the generated force rule name.
makefileStream << tname[&ccg.GetCC()] << ": ";
}
for (auto const& dep : realDepends) {
makefileStream << "\\\n" << ConvertToMakefilePath(dep);
}
makefileStream << '\n';
if (cm::optional<std::string> comment = ccg.GetComment()) {
std::string echo_cmd =
cmStrCat("echo ",
(this->CurrentLocalGenerator->EscapeForShell(
*comment, ccg.GetCC().GetEscapeAllowMakeVars())));
makefileStream << '\t' << echo_cmd << '\n';
}
// Add each command line to the set of commands.
for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) {
// Build the command line in a single string.
std::string cmd2 = ccg.GetCommand(c);
cmSystemTools::ReplaceString(cmd2, "/./", "/");
cmd2 = ConvertToMakefilePath(cmd2);
std::string cmd;
std::string wd = ccg.GetWorkingDirectory();
if (!wd.empty()) {
cmd += "cd ";
cmd += ConvertToMakefilePath(wd);
cmd += " && ";
}
cmd += cmd2;
ccg.AppendArguments(c, cmd);
makefileStream << '\t' << cmd << '\n';
}
// Symbolic inputs are not expected to exist, so add dummy rules.
for (auto const& dep : realDepends) {
if (cmSourceFile* dsf =
target->GetLocalGenerator()->GetMakefile()->GetSource(
dep, cmSourceFileLocationKind::Known)) {
if (dsf->GetPropertyAsBool("SYMBOLIC")) {
makefileStream << ConvertToMakefilePath(dep) << ":\n";
}
}
}
}
}
}
void cmGlobalXCodeGenerator::AddPositionIndependentLinkAttribute(
cmGeneratorTarget* target, cmXCodeObject* buildSettings,
std::string const& configName)
{
// For now, only EXECUTABLE is concerned
if (target->GetType() != cmStateEnums::EXECUTABLE) {
return;
}
char const* PICValue = target->GetLinkPIEProperty(configName);
if (PICValue == nullptr) {
// POSITION_INDEPENDENT_CODE is not set
return;
}
buildSettings->AddAttribute(
"LD_NO_PIE", this->CreateString(cmIsOn(PICValue) ? "NO" : "YES"));
}
void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt,
cmXCodeObject* buildSettings,
std::string const& configName)
{
if (!gtgt->IsInBuildSystem()) {
return;
}
std::string defFlags;
bool shared = ((gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) ||
(gtgt->GetType() == cmStateEnums::MODULE_LIBRARY));
bool binary = ((gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY) ||
(gtgt->GetType() == cmStateEnums::STATIC_LIBRARY) ||
(gtgt->GetType() == cmStateEnums::EXECUTABLE) || shared);
// Compute the compilation flags for each language.
std::set<std::string> languages;
gtgt->GetLanguages(languages, configName);
std::map<std::string, std::string> cflags;
for (auto const& lang : languages) {
std::string& flags = cflags[lang];
// Add language-specific flags.
this->CurrentLocalGenerator->AddLanguageFlags(
flags, gtgt, cmBuildStep::Compile, lang, configName);
if (gtgt->IsIPOEnabled(lang, configName)) {
this->CurrentLocalGenerator->AppendFeatureOptions(flags, lang, "IPO");
}
this->CurrentLocalGenerator->AddFeatureFlags(flags, gtgt, lang,
configName);
this->CurrentLocalGenerator->AddVisibilityPresetFlags(flags, gtgt, lang);
this->CurrentLocalGenerator->AddCompileOptions(flags, gtgt, lang,
configName);
}
std::string llang = gtgt->GetLinkerLanguage(configName);
if (binary && llang.empty()) {
cmSystemTools::Error(
cmStrCat("CMake can not determine linker language for target: ",
gtgt->GetName()));
return;
}
// Choose a language to use for target-wide preprocessor definitions.
static char const* ppLangs[] = { "CXX", "C", "OBJCXX", "OBJC" };
std::string langForPreprocessorDefinitions;
if (cm::contains(ppLangs, llang)) {
langForPreprocessorDefinitions = llang;
} else {
for (char const* l : ppLangs) {
if (languages.count(l)) {
langForPreprocessorDefinitions = l;
break;
}
}
}
if (gtgt->IsIPOEnabled(llang, configName)) {
char const* ltoValue =
this->CurrentMakefile->IsOn("_CMAKE_LTO_THIN") ? "YES_THIN" : "YES";
buildSettings->AddAttribute("LLVM_LTO", this->CreateString(ltoValue));
}
// Handle PIE linker configuration
this->AddPositionIndependentLinkAttribute(gtgt, buildSettings, configName);
// Add define flags
this->CurrentLocalGenerator->AppendFlags(
defFlags, this->CurrentMakefile->GetDefineFlags());
// Add preprocessor definitions for this target and configuration.
BuildObjectListOrString ppDefs(this, true);
this->AppendDefines(
ppDefs, "CMAKE_INTDIR=\"$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)\"");
if (std::string const* exportMacro = gtgt->GetExportMacro()) {
// Add the export symbol definition for shared library objects.
this->AppendDefines(ppDefs, exportMacro->c_str());
}
std::vector<std::string> targetDefines;
if (!langForPreprocessorDefinitions.empty()) {
gtgt->GetCompileDefinitions(targetDefines, configName,
langForPreprocessorDefinitions);
}
this->AppendDefines(ppDefs, targetDefines);
buildSettings->AddAttribute("GCC_PREPROCESSOR_DEFINITIONS",
ppDefs.CreateList());
if (languages.count("Swift")) {
// Swift uses a separate attribute for definitions.
std::vector<std::string> targetSwiftDefines;
gtgt->GetCompileDefinitions(targetSwiftDefines, configName, "Swift");
// Remove the '=value' parts, as Swift does not support them.
std::for_each(targetSwiftDefines.begin(), targetSwiftDefines.end(),
[](std::string& def) {
std::string::size_type pos = def.find('=');
if (pos != std::string::npos) {
def.erase(pos);
}
});
if (this->XcodeVersion < 80) {
std::string defineString;
std::set<std::string> defines(targetSwiftDefines.begin(),
targetSwiftDefines.end());
this->CurrentLocalGenerator->JoinDefines(defines, defineString, "Swift");
cflags["Swift"] += cmStrCat(' ', defineString);
} else {
BuildObjectListOrString swiftDefs(this, true);
this->AppendDefines(swiftDefs, targetSwiftDefines);
buildSettings->AddAttribute("SWIFT_ACTIVE_COMPILATION_CONDITIONS",
swiftDefs.CreateList());
}
if (cm::optional<cmSwiftCompileMode> swiftCompileMode =
this->CurrentLocalGenerator->GetSwiftCompileMode(gtgt, configName)) {
switch (*swiftCompileMode) {
case cmSwiftCompileMode::Wholemodule:
buildSettings->AddAttribute("SWIFT_COMPILATION_MODE",
this->CreateString("wholemodule"));
break;
case cmSwiftCompileMode::Incremental:
case cmSwiftCompileMode::Singlefile:
break;
case cmSwiftCompileMode::Unknown:
this->CurrentLocalGenerator->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat("Unknown Swift_COMPILATION_MODE on target '",
gtgt->GetName(), '\''));
break;
}
}
}
std::string extraLinkOptionsVar;
std::string extraLinkOptions;
if (gtgt->GetType() == cmStateEnums::EXECUTABLE) {
extraLinkOptionsVar = "CMAKE_EXE_LINKER_FLAGS";
} else if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) {
extraLinkOptionsVar = "CMAKE_SHARED_LINKER_FLAGS";
} else if (gtgt->GetType() == cmStateEnums::MODULE_LIBRARY) {
extraLinkOptionsVar = "CMAKE_MODULE_LINKER_FLAGS";
}
if (!extraLinkOptionsVar.empty()) {
this->CurrentLocalGenerator->AddConfigVariableFlags(
extraLinkOptions, extraLinkOptionsVar, gtgt, cmBuildStep::Link, llang,
configName);
}
if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY ||
gtgt->GetType() == cmStateEnums::STATIC_LIBRARY) {
this->CurrentLocalGenerator->GetStaticLibraryFlags(
extraLinkOptions, configName, llang, gtgt);
} else {
this->CurrentLocalGenerator->AppendLinkerTypeFlags(extraLinkOptions, gtgt,
configName, llang);
this->CurrentLocalGenerator->AppendWarningAsErrorLinkerFlags(
extraLinkOptions, gtgt, llang);
cmValue targetLinkFlags = gtgt->GetProperty("LINK_FLAGS");
if (targetLinkFlags) {
this->CurrentLocalGenerator->AppendFlags(extraLinkOptions,
*targetLinkFlags);
}
if (!configName.empty()) {
std::string linkFlagsVar =
cmStrCat("LINK_FLAGS_", cmSystemTools::UpperCase(configName));
if (cmValue linkFlags = gtgt->GetProperty(linkFlagsVar)) {
this->CurrentLocalGenerator->AppendFlags(extraLinkOptions, *linkFlags);
}
}
std::vector<std::string> opts;
gtgt->GetLinkOptions(opts, configName, llang);
// LINK_OPTIONS are escaped.
this->CurrentLocalGenerator->AppendCompileOptions(extraLinkOptions, opts);
}
// Set target-specific architectures.
std::vector<std::string> archs =
gtgt->GetAppleArchs(configName, cm::nullopt);
if (!archs.empty()) {
// Enable ARCHS attribute.
buildSettings->AddAttribute("ONLY_ACTIVE_ARCH", this->CreateString("NO"));
// Store ARCHS value.
if (archs.size() == 1) {
buildSettings->AddAttribute("ARCHS", this->CreateString(archs[0]));
} else {
cmXCodeObject* archObjects =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (auto& arch : archs) {
archObjects->AddObject(this->CreateString(arch));
}
buildSettings->AddAttribute("ARCHS", archObjects);
}
}
// Get the product name components.
cmGeneratorTarget::NameComponents const& components =
gtgt->GetFullNameComponents(configName);
cmValue version = gtgt->GetProperty("VERSION");
cmValue soversion = gtgt->GetProperty("SOVERSION");
if (!gtgt->HasSOName(configName) || gtgt->IsFrameworkOnApple()) {
version = nullptr;
soversion = nullptr;
}
if (version && !soversion) {
soversion = version;
}
if (!version && soversion) {
version = soversion;
}
std::string realName = components.base;
std::string soName = components.base;
if (version && soversion) {
realName += '.';
realName += *version;
soName += '.';
soName += *soversion;
}
if (gtgt->CanCompileSources()) {
std::string const tmpDir =
this->GetTargetTempDir(gtgt, this->GetCMakeCFGIntDir());
buildSettings->AddAttribute("TARGET_TEMP_DIR", this->CreateString(tmpDir));
std::string outDir;
if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY) {
// We cannot suppress the archive, so hide it with intermediate files.
outDir = tmpDir;
} else {
outDir = gtgt->GetDirectory(configName);
}
buildSettings->AddAttribute("CONFIGURATION_BUILD_DIR",
this->CreateString(outDir));
}
// Set attributes to specify the proper name for the target.
std::string pndir = this->CurrentLocalGenerator->GetCurrentBinaryDirectory();
if (gtgt->GetType() == cmStateEnums::STATIC_LIBRARY ||
gtgt->GetType() == cmStateEnums::SHARED_LIBRARY ||
gtgt->GetType() == cmStateEnums::MODULE_LIBRARY ||
gtgt->GetType() == cmStateEnums::EXECUTABLE) {
std::string prefix = components.prefix;
if (gtgt->IsFrameworkOnApple() || gtgt->IsCFBundleOnApple()) {
prefix = "";
}
buildSettings->AddAttribute("EXECUTABLE_PREFIX",
this->CreateString(prefix));
buildSettings->AddAttribute("EXECUTABLE_SUFFIX",
this->CreateString(components.suffix));
}
// Store the product name for all target types.
buildSettings->AddAttribute("PRODUCT_NAME", this->CreateString(realName));
// Handle settings for each target type.
switch (gtgt->GetType()) {
case cmStateEnums::STATIC_LIBRARY:
if (gtgt->GetPropertyAsBool("FRAMEWORK")) {
std::string fw_version = gtgt->GetFrameworkVersion();
buildSettings->AddAttribute("FRAMEWORK_VERSION",
this->CreateString(fw_version));
cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION");
if (ext) {
buildSettings->AddAttribute("WRAPPER_EXTENSION",
this->CreateString(*ext));
}
std::string plist = this->ComputeInfoPListLocation(gtgt);
// Xcode will create the final version of Info.plist at build time,
// so let it replace the framework name. This avoids creating
// a per-configuration Info.plist file.
this->CurrentLocalGenerator->GenerateFrameworkInfoPList(
gtgt, "$(EXECUTABLE_NAME)", plist);
buildSettings->AddAttribute("INFOPLIST_FILE",
this->CreateString(plist));
buildSettings->AddAttribute("MACH_O_TYPE",
this->CreateString("staticlib"));
} else {
buildSettings->AddAttribute("LIBRARY_STYLE",
this->CreateString("STATIC"));
}
break;
case cmStateEnums::OBJECT_LIBRARY: {
buildSettings->AddAttribute("LIBRARY_STYLE",
this->CreateString("STATIC"));
break;
}
case cmStateEnums::MODULE_LIBRARY: {
buildSettings->AddAttribute("LIBRARY_STYLE",
this->CreateString("BUNDLE"));
// Add the flags to create a module library (bundle).
std::string createFlags = this->LookupFlags(
"CMAKE_SHARED_MODULE_CREATE_", llang, "_FLAGS", gtgt);
if (this->GetTargetProductType(gtgt) !=
"com.apple.product-type.app-extension"_s) {
// Xcode passes -bundle automatically.
cmSystemTools::ReplaceString(createFlags, "-bundle", "");
}
createFlags = cmTrimWhitespace(createFlags);
if (!createFlags.empty()) {
extraLinkOptions += ' ';
extraLinkOptions += createFlags;
}
if (gtgt->IsCFBundleOnApple()) {
cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION");
if (ext) {
buildSettings->AddAttribute("WRAPPER_EXTENSION",
this->CreateString(*ext));
}
std::string plist = this->ComputeInfoPListLocation(gtgt);
// Xcode will create the final version of Info.plist at build time,
// so let it replace the cfbundle name. This avoids creating
// a per-configuration Info.plist file. The cfbundle plist
// is very similar to the application bundle plist
this->CurrentLocalGenerator->GenerateAppleInfoPList(
gtgt, "$(EXECUTABLE_NAME)", plist);
buildSettings->AddAttribute("INFOPLIST_FILE",
this->CreateString(plist));
} else {
buildSettings->AddAttribute("MACH_O_TYPE",
this->CreateString("mh_bundle"));
buildSettings->AddAttribute("GCC_DYNAMIC_NO_PIC",
this->CreateString("NO"));
}
break;
}
case cmStateEnums::SHARED_LIBRARY: {
if (gtgt->GetPropertyAsBool("FRAMEWORK")) {
std::string fw_version = gtgt->GetFrameworkVersion();
buildSettings->AddAttribute("FRAMEWORK_VERSION",
this->CreateString(fw_version));
cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION");
if (ext) {
buildSettings->AddAttribute("WRAPPER_EXTENSION",
this->CreateString(*ext));
}
std::string plist = this->ComputeInfoPListLocation(gtgt);
// Xcode will create the final version of Info.plist at build time,
// so let it replace the framework name. This avoids creating
// a per-configuration Info.plist file.
this->CurrentLocalGenerator->GenerateFrameworkInfoPList(
gtgt, "$(EXECUTABLE_NAME)", plist);
buildSettings->AddAttribute("INFOPLIST_FILE",
this->CreateString(plist));
} else {
// Add the flags to create a shared library.
std::string createFlags = this->LookupFlags(
"CMAKE_SHARED_LIBRARY_CREATE_", llang, "_FLAGS", gtgt);
// Xcode passes -dynamiclib automatically.
cmSystemTools::ReplaceString(createFlags, "-dynamiclib", "");
createFlags = cmTrimWhitespace(createFlags);
if (!createFlags.empty()) {
extraLinkOptions += ' ';
extraLinkOptions += createFlags;
}
}
buildSettings->AddAttribute("LIBRARY_STYLE",
this->CreateString("DYNAMIC"));
if (gtgt->HasImportLibrary(configName)) {
// Request .tbd file generation
buildSettings->AddAttribute("GENERATE_TEXT_BASED_STUBS",
this->CreateString("YES"));
}
break;
}
case cmStateEnums::EXECUTABLE: {
// Add the flags to create an executable.
std::string createFlags =
this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", gtgt);
if (!createFlags.empty()) {
extraLinkOptions += ' ';
extraLinkOptions += createFlags;
}
// Handle bundles and normal executables separately.
if (gtgt->GetPropertyAsBool("MACOSX_BUNDLE")) {
cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION");
if (ext) {
buildSettings->AddAttribute("WRAPPER_EXTENSION",
this->CreateString(*ext));
}
std::string plist = this->ComputeInfoPListLocation(gtgt);
// Xcode will create the final version of Info.plist at build time,
// so let it replace the executable name. This avoids creating
// a per-configuration Info.plist file.
this->CurrentLocalGenerator->GenerateAppleInfoPList(
gtgt, "$(EXECUTABLE_NAME)", plist);
buildSettings->AddAttribute("INFOPLIST_FILE",
this->CreateString(plist));
}
} break;
default:
break;
}
BuildObjectListOrString dirs(this, true);
BuildObjectListOrString fdirs(this, true);
BuildObjectListOrString sysdirs(this, true);
BuildObjectListOrString sysfdirs(this, true);
bool const emitSystemIncludes = this->XcodeVersion >= 83;
// Choose a language to use for target-wide include directories.
std::string const& langForIncludes = llang;
std::vector<std::string> includes;
if (!langForIncludes.empty()) {
this->CurrentLocalGenerator->GetIncludeDirectories(
includes, gtgt, langForIncludes, configName);
}
std::set<std::string> emitted;
emitted.insert("/System/Library/Frameworks");
for (auto& include : includes) {
if (this->NameResolvesToFramework(include)) {
std::string frameworkDir = cmStrCat(include, "/../");
frameworkDir = cmSystemTools::CollapseFullPath(frameworkDir);
if (emitted.insert(frameworkDir).second) {
std::string incpath = this->XCodeEscapePath(frameworkDir);
if (emitSystemIncludes &&
gtgt->IsSystemIncludeDirectory(frameworkDir, configName,
langForIncludes)) {
sysfdirs.Add(incpath);
} else {
fdirs.Add(incpath);
}
}
} else {
std::string incpath = this->XCodeEscapePath(include);
if (emitSystemIncludes &&
gtgt->IsSystemIncludeDirectory(include, configName,
langForIncludes)) {
sysdirs.Add(incpath);
} else {
dirs.Add(incpath);
}
}
}
// Add framework search paths needed for linking.
if (cmComputeLinkInformation* cli = gtgt->GetLinkInformation(configName)) {
for (auto const& fwDir : cli->GetFrameworkPaths()) {
if (emitted.insert(fwDir).second) {
std::string incpath = this->XCodeEscapePath(fwDir);
if (emitSystemIncludes &&
gtgt->IsSystemIncludeDirectory(fwDir, configName,
langForIncludes)) {
sysfdirs.Add(incpath);
} else {
fdirs.Add(incpath);
}
}
}
}
if (!fdirs.IsEmpty()) {
buildSettings->AddAttribute("FRAMEWORK_SEARCH_PATHS", fdirs.CreateList());
}
if (!dirs.IsEmpty()) {
buildSettings->AddAttribute("HEADER_SEARCH_PATHS", dirs.CreateList());
if (languages.count("Swift")) {
buildSettings->AddAttribute("SWIFT_INCLUDE_PATHS", dirs.CreateList());
}
}
if (!sysfdirs.IsEmpty()) {
buildSettings->AddAttribute("SYSTEM_FRAMEWORK_SEARCH_PATHS",
sysfdirs.CreateList());
}
if (!sysdirs.IsEmpty()) {
buildSettings->AddAttribute("SYSTEM_HEADER_SEARCH_PATHS",
sysdirs.CreateList());
}
if (this->XcodeVersion >= 60 && !emitSystemIncludes) {
// Add those per-language flags in addition to HEADER_SEARCH_PATHS to gain
// system include directory awareness. We need to also keep on setting
// HEADER_SEARCH_PATHS to work around a missing compile options flag for
// GNU assembly files (#16449)
for (auto const& language : languages) {
std::string includeFlags = this->CurrentLocalGenerator->GetIncludeFlags(
includes, gtgt, language, configName);
if (!includeFlags.empty()) {
cflags[language] += cmStrCat(' ', includeFlags);
}
}
}
bool same_gflags = true;
std::map<std::string, std::string> gflags;
std::string const* last_gflag = nullptr;
std::string optLevel = "0";
// Minimal map of flags to build settings.
for (auto const& language : languages) {
std::string& flags = cflags[language];
std::string& gflag = gflags[language];
std::string oflag =
this->ExtractFlagRegex("(^| )(-Ofast|-Os|-O[0-9]*)( |$)", 2, flags);
if (oflag.size() == 2) {
optLevel = "1";
} else if (oflag.size() > 2) {
optLevel = oflag.substr(2);
}
gflag = this->ExtractFlag("-g", flags);
// put back gdwarf-2 if used since there is no way
// to represent it in the gui, but we still want debug yes
if (gflag == "-gdwarf-2"_s) {
flags += ' ';
flags += gflag;
}
if (last_gflag && *last_gflag != gflag) {
same_gflags = false;
}
last_gflag = &gflag;
}
char const* debugStr = "YES";
if (!same_gflags) {
// We can't set the Xcode flag differently depending on the language,
// so put them back in this case.
for (auto const& language : languages) {
cflags[language] += ' ';
cflags[language] += gflags[language];
}
debugStr = "NO";
} else if (last_gflag && (last_gflag->empty() || *last_gflag == "-g0"_s)) {
debugStr = "NO";
}
// extract C++ stdlib
for (auto const& language : languages) {
if (language != "CXX"_s && language != "OBJCXX"_s) {
continue;
}
std::string& flags = cflags[language];
auto stdlib =
this->ExtractFlagRegex("(^| )(-stdlib=[^ ]+)( |$)", 2, flags);
if (stdlib.size() > 8) {
auto const cxxLibrary = stdlib.substr(8);
if (language == "CXX"_s ||
!buildSettings->GetAttribute("CLANG_CXX_LIBRARY")) {
buildSettings->AddAttribute("CLANG_CXX_LIBRARY",
this->CreateString(cxxLibrary));
}
}
}
buildSettings->AddAttribute("COMBINE_HIDPI_IMAGES",
this->CreateString("YES"));
buildSettings->AddAttribute("GCC_GENERATE_DEBUGGING_SYMBOLS",
this->CreateString(debugStr));
buildSettings->AddAttribute("GCC_OPTIMIZATION_LEVEL",
this->CreateString(optLevel));
buildSettings->AddAttribute("GCC_SYMBOLS_PRIVATE_EXTERN",
this->CreateString("NO"));
buildSettings->AddAttribute("GCC_INLINES_ARE_PRIVATE_EXTERN",
this->CreateString("NO"));
for (auto const& language : languages) {
std::string flags = cmStrCat(cflags[language], ' ', defFlags);
if (language == "CXX"_s || language == "OBJCXX"_s) {
if (language == "CXX"_s ||
!buildSettings->GetAttribute("OTHER_CPLUSPLUSFLAGS")) {
buildSettings->AddAttribute("OTHER_CPLUSPLUSFLAGS",
this->CreateString(flags));
}
} else if (language == "Fortran"_s) {
buildSettings->AddAttribute("IFORT_OTHER_FLAGS",
this->CreateString(flags));
} else if (language == "C"_s || language == "OBJC"_s) {
if (language == "C"_s || !buildSettings->GetAttribute("OTHER_CFLAGS")) {
buildSettings->AddAttribute("OTHER_CFLAGS", this->CreateString(flags));
}
} else if (language == "Swift"_s) {
buildSettings->AddAttribute("OTHER_SWIFT_FLAGS",
this->CreateString(flags));
}
}
// Add Fortran source format attribute if property is set.
char const* format = nullptr;
std::string const& tgtfmt = gtgt->GetSafeProperty("Fortran_FORMAT");
switch (cmOutputConverter::GetFortranFormat(tgtfmt)) {
case cmOutputConverter::FortranFormatFixed:
format = "fixed";
break;
case cmOutputConverter::FortranFormatFree:
format = "free";
break;
default:
break;
}
if (format) {
buildSettings->AddAttribute("IFORT_LANG_SRCFMT",
this->CreateString(format));
}
// Create the INSTALL_PATH attribute.
std::string install_name_dir;
if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) {
// Get the install_name directory for the build tree.
install_name_dir = gtgt->GetInstallNameDirForBuildTree(configName);
// Xcode doesn't create the correct install_name in some cases.
// That is, if the INSTALL_PATH is empty, or if we have versioning
// of dylib libraries, we want to specify the install_name.
// This is done by adding a link flag to create an install_name
// with just the library soname.
std::string install_name;
if (!install_name_dir.empty()) {
// Convert to a path for the native build tool.
cmSystemTools::ConvertToUnixSlashes(install_name_dir);
install_name += install_name_dir;
install_name += '/';
}
install_name += gtgt->GetSOName(configName);
if ((realName != soName) || install_name_dir.empty()) {
install_name_dir = "";
extraLinkOptions += " -install_name ";
extraLinkOptions += XCodeEscapePath(install_name);
}
}
buildSettings->AddAttribute("INSTALL_PATH",
this->CreateString(install_name_dir));
// Create the LD_RUNPATH_SEARCH_PATHS
cmComputeLinkInformation* pcli = gtgt->GetLinkInformation(configName);
if (pcli) {
std::string search_paths;
std::vector<std::string> runtimeDirs;
pcli->GetRPath(runtimeDirs, false);
// runpath dirs needs to be unique to prevent corruption
std::set<std::string> unique_dirs;
for (auto runpath : runtimeDirs) {
runpath = this->ExpandCFGIntDir(runpath, configName);
if (unique_dirs.find(runpath) == unique_dirs.end()) {
unique_dirs.insert(runpath);
if (!search_paths.empty()) {
search_paths += ' ';
}
search_paths += this->XCodeEscapePath(runpath);
}
}
if (!search_paths.empty()) {
buildSettings->AddAttribute("LD_RUNPATH_SEARCH_PATHS",
this->CreateString(search_paths));
}
}
buildSettings->AddAttribute(this->GetTargetLinkFlagsVar(gtgt),
this->CreateString(extraLinkOptions));
buildSettings->AddAttribute("OTHER_REZFLAGS", this->CreateString(""));
buildSettings->AddAttribute("SECTORDER_FLAGS", this->CreateString(""));
buildSettings->AddAttribute("ALWAYS_SEARCH_USER_PATHS",
this->CreateString("NO"));
buildSettings->AddAttribute("USE_HEADERMAP", this->CreateString("NO"));
cmXCodeObject* group = this->CreateObject(cmXCodeObject::OBJECT_LIST);
group->AddObject(this->CreateString("$(inherited)"));
buildSettings->AddAttribute("WARNING_CFLAGS", group);
// Runtime version information.
if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) {
int major;
int minor;
int patch;
// MACHO_CURRENT_VERSION or VERSION -> current_version
gtgt->GetTargetVersionFallback("MACHO_CURRENT_VERSION", "VERSION", major,
minor, patch);
std::ostringstream v;
// Xcode always wants at least 1.0.0 or nothing
if (!(major == 0 && minor == 0 && patch == 0)) {
v << major << '.' << minor << '.' << patch;
}
buildSettings->AddAttribute("DYLIB_CURRENT_VERSION",
this->CreateString(v.str()));
// MACHO_COMPATIBILITY_VERSION or SOVERSION -> compatibility_version
gtgt->GetTargetVersionFallback("MACHO_COMPATIBILITY_VERSION", "SOVERSION",
major, minor, patch);
std::ostringstream vso;
// Xcode always wants at least 1.0.0 or nothing
if (!(major == 0 && minor == 0 && patch == 0)) {
vso << major << '.' << minor << '.' << patch;
}
buildSettings->AddAttribute("DYLIB_COMPATIBILITY_VERSION",
this->CreateString(vso.str()));
}
// Precompile Headers
std::string pchHeader =
gtgt->GetPchHeader(configName, langForPreprocessorDefinitions);
if (!pchHeader.empty()) {
buildSettings->AddAttribute("GCC_PREFIX_HEADER",
this->CreateString(pchHeader));
buildSettings->AddAttribute("GCC_PRECOMPILE_PREFIX_HEADER",
this->CreateString("YES"));
}
// put this last so it can override existing settings
// Convert "XCODE_ATTRIBUTE_*" properties directly.
{
for (auto const& prop : gtgt->GetPropertyKeys()) {
if (cmHasLiteralPrefix(prop, "XCODE_ATTRIBUTE_")) {
std::string attribute = prop.substr(16);
this->FilterConfigurationAttribute(configName, attribute);
if (!attribute.empty()) {
std::string const& pr = gtgt->GetSafeProperty(prop);
std::string processed = cmGeneratorExpression::Evaluate(
pr, this->CurrentLocalGenerator, configName);
buildSettings->AddAttribute(attribute,
this->CreateString(processed));
}
}
}
}
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateUtilityTarget(
cmGeneratorTarget* gtgt)
{
cmXCodeObject* shellBuildPhase = this->CreateObject(
cmXCodeObject::PBXShellScriptBuildPhase, gtgt->GetName());
shellBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
shellBuildPhase->AddAttribute("files", buildFiles);
cmXCodeObject* inputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST);
shellBuildPhase->AddAttribute("inputPaths", inputPaths);
cmXCodeObject* outputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST);
shellBuildPhase->AddAttribute("outputPaths", outputPaths);
shellBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
shellBuildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh"));
shellBuildPhase->AddAttribute(
"shellScript", this->CreateString("# shell script goes here\nexit 0"));
shellBuildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0"));
std::string targetBinaryPath = cmStrCat(
gtgt->Makefile->GetCurrentBinaryDirectory(), '/', gtgt->GetName());
cmXCodeObject* target = this->CreateObject(
cmXCodeObject::PBXAggregateTarget,
cmStrCat("PBXAggregateTarget:", gtgt->GetName(), ':', targetBinaryPath));
target->SetComment(gtgt->GetName());
cmXCodeObject* buildPhases = this->CreateObject(cmXCodeObject::OBJECT_LIST);
std::vector<cmXCodeObject*> emptyContentVector;
this->CreateCustomCommands(buildPhases, nullptr, nullptr, nullptr,
emptyContentVector, nullptr, gtgt);
target->AddAttribute("buildPhases", buildPhases);
this->AddConfigurations(target, gtgt);
cmXCodeObject* dependencies = this->CreateObject(cmXCodeObject::OBJECT_LIST);
target->AddAttribute("dependencies", dependencies);
target->AddAttribute("name", this->CreateString(gtgt->GetName()));
target->AddAttribute("productName", this->CreateString(gtgt->GetName()));
target->SetTarget(gtgt);
this->XCodeObjectMap[gtgt] = target;
// Add source files without build rules for editing convenience.
if (gtgt->GetType() != cmStateEnums::GLOBAL_TARGET &&
gtgt->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) {
std::vector<cmSourceFile*> sources;
if (!gtgt->GetConfigCommonSourceFilesForXcode(sources)) {
return nullptr;
}
// Add CMakeLists.txt file for user convenience.
this->AddXCodeProjBuildRule(gtgt, sources);
for (auto* sourceFile : sources) {
if (!sourceFile->GetIsGenerated()) {
this->CreateXCodeFileReference(sourceFile, gtgt);
}
}
}
target->SetId(this->GetOrCreateId(gtgt->GetName(), target->GetId()));
return target;
}
std::string cmGlobalXCodeGenerator::AddConfigurations(cmXCodeObject* target,
cmGeneratorTarget* gtgt)
{
cmList const configList{ this->CurrentMakefile->GetRequiredDefinition(
"CMAKE_CONFIGURATION_TYPES") };
cmXCodeObject* configlist =
this->CreateObject(cmXCodeObject::XCConfigurationList);
cmXCodeObject* buildConfigurations =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
configlist->AddAttribute("buildConfigurations", buildConfigurations);
std::string comment = cmStrCat("Build configuration list for ",
cmXCodeObject::PBXTypeNames[target->GetIsA()],
" \"", gtgt->GetName(), '"');
configlist->SetComment(comment);
target->AddAttribute("buildConfigurationList",
this->CreateObjectReference(configlist));
for (auto const& i : configList) {
cmXCodeObject* config =
this->CreateObject(cmXCodeObject::XCBuildConfiguration);
buildConfigurations->AddObject(config);
cmXCodeObject* buildSettings =
this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
this->CreateBuildSettings(gtgt, buildSettings, i);
config->AddAttribute("name", this->CreateString(i));
config->SetComment(i);
config->AddAttribute("buildSettings", buildSettings);
this->CreateTargetXCConfigSettings(gtgt, config, i);
}
if (!configList.empty()) {
configlist->AddAttribute("defaultConfigurationName",
this->CreateString(configList[0]));
configlist->AddAttribute("defaultConfigurationIsVisible",
this->CreateString("0"));
return configList[0];
}
return "";
}
void cmGlobalXCodeGenerator::CreateGlobalXCConfigSettings(
cmLocalGenerator* root, cmXCodeObject* config, std::string const& configName)
{
auto xcconfig = cmGeneratorExpression::Evaluate(
this->CurrentMakefile->GetSafeDefinition("CMAKE_XCODE_XCCONFIG"),
this->CurrentLocalGenerator, configName);
if (xcconfig.empty()) {
return;
}
auto* sf = this->CurrentMakefile->GetSource(xcconfig);
if (!sf) {
cmSystemTools::Error(
cmStrCat("sources for ALL_BUILD do not contain xcconfig file: '",
xcconfig, "' (configuration: ", configName, ')'));
return;
}
cmXCodeObject* fileRef = this->CreateXCodeFileReferenceFromPath(
sf->ResolveFullPath(), root->FindGeneratorTargetToUse("ALL_BUILD"), "",
sf);
if (!fileRef) {
// error is already reported by CreateXCodeFileReferenceFromPath
return;
}
config->AddAttribute("baseConfigurationReference",
this->CreateObjectReference(fileRef));
}
void cmGlobalXCodeGenerator::CreateTargetXCConfigSettings(
cmGeneratorTarget* target, cmXCodeObject* config,
std::string const& configName)
{
auto xcconfig =
cmGeneratorExpression::Evaluate(target->GetSafeProperty("XCODE_XCCONFIG"),
this->CurrentLocalGenerator, configName);
if (xcconfig.empty()) {
return;
}
auto* sf = target->Makefile->GetSource(xcconfig);
if (!sf) {
cmSystemTools::Error(cmStrCat("target sources for target ",
target->Target->GetName(),
" do not contain xcconfig file: '", xcconfig,
"' (configuration: ", configName, ')'));
return;
}
cmXCodeObject* fileRef = this->CreateXCodeFileReferenceFromPath(
sf->ResolveFullPath(), target, "", sf);
if (!fileRef) {
// error is already reported by CreateXCodeFileReferenceFromPath
return;
}
config->AddAttribute("baseConfigurationReference",
this->CreateObjectReference(fileRef));
}
char const* cmGlobalXCodeGenerator::GetTargetLinkFlagsVar(
cmGeneratorTarget const* target) const
{
if (this->XcodeVersion >= 60 &&
(target->GetType() == cmStateEnums::STATIC_LIBRARY ||
target->GetType() == cmStateEnums::OBJECT_LIBRARY)) {
return "OTHER_LIBTOOLFLAGS";
}
return "OTHER_LDFLAGS";
}
char const* cmGlobalXCodeGenerator::GetTargetFileType(
cmGeneratorTarget* target)
{
if (cmValue e = target->GetProperty("XCODE_EXPLICIT_FILE_TYPE")) {
return e->c_str();
}
switch (target->GetType()) {
case cmStateEnums::OBJECT_LIBRARY:
return "archive.ar";
case cmStateEnums::STATIC_LIBRARY:
return (target->GetPropertyAsBool("FRAMEWORK") ? "wrapper.framework"
: "archive.ar");
case cmStateEnums::MODULE_LIBRARY:
if (target->IsXCTestOnApple()) {
return "wrapper.cfbundle";
}
if (target->IsCFBundleOnApple()) {
return "wrapper.plug-in";
}
return "compiled.mach-o.executable";
case cmStateEnums::SHARED_LIBRARY:
return (target->GetPropertyAsBool("FRAMEWORK")
? "wrapper.framework"
: "compiled.mach-o.dylib");
case cmStateEnums::EXECUTABLE:
return "compiled.mach-o.executable";
default:
break;
}
return nullptr;
}
cm::string_view cmGlobalXCodeGenerator::GetTargetProductType(
cmGeneratorTarget* target)
{
if (cmValue e = target->GetProperty("XCODE_PRODUCT_TYPE")) {
return cm::string_view(*e);
}
switch (target->GetType()) {
case cmStateEnums::OBJECT_LIBRARY:
return "com.apple.product-type.library.static"_s;
case cmStateEnums::STATIC_LIBRARY:
return target->GetPropertyAsBool("FRAMEWORK")
? "com.apple.product-type.framework"_s
: "com.apple.product-type.library.static"_s;
case cmStateEnums::MODULE_LIBRARY:
if (target->IsXCTestOnApple()) {
return "com.apple.product-type.bundle.unit-test"_s;
} else if (target->IsCFBundleOnApple()) {
return "com.apple.product-type.bundle"_s;
} else {
return "com.apple.product-type.tool"_s;
}
case cmStateEnums::SHARED_LIBRARY:
return target->GetPropertyAsBool("FRAMEWORK")
? "com.apple.product-type.framework"_s
: "com.apple.product-type.library.dynamic"_s;
case cmStateEnums::EXECUTABLE:
return target->GetPropertyAsBool("MACOSX_BUNDLE")
? "com.apple.product-type.application"_s
: "com.apple.product-type.tool"_s;
default:
break;
}
return ""_s;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeTarget(
cmGeneratorTarget* gtgt, cmXCodeObject* buildPhases)
{
if (!gtgt->IsInBuildSystem()) {
return nullptr;
}
std::string targetBinaryPath = this->RelativeToRootBinary(cmStrCat(
gtgt->Makefile->GetCurrentBinaryDirectory(), '/', gtgt->GetName()));
cmXCodeObject* target = this->CreateObject(
cmXCodeObject::PBXNativeTarget,
cmStrCat("PBXNativeTarget:", gtgt->GetName(), ':', targetBinaryPath));
target->AddAttribute("buildPhases", buildPhases);
cmXCodeObject* buildRules = this->CreateObject(cmXCodeObject::OBJECT_LIST);
target->AddAttribute("buildRules", buildRules);
std::string defConfig;
defConfig = this->AddConfigurations(target, gtgt);
cmXCodeObject* dependencies = this->CreateObject(cmXCodeObject::OBJECT_LIST);
target->AddAttribute("dependencies", dependencies);
target->AddAttribute("name", this->CreateString(gtgt->GetName()));
target->AddAttribute("productName", this->CreateString(gtgt->GetName()));
cmXCodeObject* fileRef = this->CreateObject(cmXCodeObject::PBXFileReference);
if (char const* fileType = this->GetTargetFileType(gtgt)) {
fileRef->AddAttribute("explicitFileType", this->CreateString(fileType));
}
std::string fullName;
if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY) {
fullName = cmStrCat("lib", gtgt->GetName(), ".a");
} else {
fullName = gtgt->GetFullName(defConfig);
}
fileRef->AddAttribute("path", this->CreateString(fullName));
fileRef->AddAttribute("sourceTree",
this->CreateString("BUILT_PRODUCTS_DIR"));
fileRef->SetComment(gtgt->GetName());
target->AddAttribute("productReference",
this->CreateObjectReference(fileRef));
cm::string_view productType = this->GetTargetProductType(gtgt);
if (!productType.empty()) {
target->AddAttribute("productType", this->CreateString(productType));
}
target->SetTarget(gtgt);
this->XCodeObjectMap[gtgt] = target;
target->SetId(this->GetOrCreateId(gtgt->GetName(), target->GetId()));
return target;
}
cmXCodeObject* cmGlobalXCodeGenerator::FindXCodeTarget(
cmGeneratorTarget const* t)
{
if (!t) {
return nullptr;
}
auto const i = this->XCodeObjectMap.find(t);
if (i == this->XCodeObjectMap.end()) {
return nullptr;
}
return i->second;
}
std::string cmGlobalXCodeGenerator::GetObjectId(cmXCodeObject::PBXType ptype,
cm::string_view key)
{
std::string objectId;
if (!key.empty()) {
cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
hash.Initialize();
hash.Append(&ptype, sizeof(ptype));
hash.Append(key);
objectId = cmSystemTools::UpperCase(hash.FinalizeHex().substr(0, 24));
} else {
char cUuid[40] = { 0 };
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef s = CFUUIDCreateString(kCFAllocatorDefault, uuid);
CFStringGetCString(s, cUuid, sizeof(cUuid), kCFStringEncodingUTF8);
objectId = cUuid;
CFRelease(s);
CFRelease(uuid);
cmSystemTools::ReplaceString(objectId, "-", "");
if (objectId.size() > 24) {
objectId = objectId.substr(0, 24);
}
}
return objectId;
}
std::string cmGlobalXCodeGenerator::GetOrCreateId(std::string const& name,
std::string const& id)
{
std::string guidStoreName = cmStrCat(name, "_GUID_CMAKE");
cmValue storedGUID = this->CMakeInstance->GetCacheDefinition(guidStoreName);
if (storedGUID) {
return *storedGUID;
}
this->CMakeInstance->AddCacheEntry(
guidStoreName, id, "Stored Xcode object GUID", cmStateEnums::INTERNAL);
return id;
}
void cmGlobalXCodeGenerator::AddDependTarget(cmXCodeObject* target,
cmXCodeObject* dependTarget)
{
// This is called once for every edge in the target dependency graph.
cmXCodeObject* container =
this->CreateObject(cmXCodeObject::PBXContainerItemProxy);
container->SetComment("PBXContainerItemProxy");
container->AddAttribute("containerPortal",
this->CreateObjectReference(this->RootObject));
container->AddAttribute("proxyType", this->CreateString("1"));
container->AddAttribute("remoteGlobalIDString",
this->CreateObjectReference(dependTarget));
container->AddAttribute(
"remoteInfo", this->CreateString(dependTarget->GetTarget()->GetName()));
cmXCodeObject* targetdep =
this->CreateObject(cmXCodeObject::PBXTargetDependency);
targetdep->SetComment("PBXTargetDependency");
targetdep->AddAttribute("target", this->CreateObjectReference(dependTarget));
targetdep->AddAttribute("targetProxy",
this->CreateObjectReference(container));
cmXCodeObject* depends = target->GetAttribute("dependencies");
if (!depends) {
cmSystemTools::Error(
"target does not have dependencies attribute error..");
} else {
depends->AddUniqueObject(targetdep);
}
}
void cmGlobalXCodeGenerator::AppendOrAddBuildSetting(cmXCodeObject* settings,
char const* attribute,
cmXCodeObject* value)
{
if (settings) {
cmXCodeObject* attr = settings->GetAttribute(attribute);
if (!attr) {
settings->AddAttribute(attribute, value);
} else {
this->AppendBuildSettingAttribute(settings, attribute, attr, value);
}
}
}
void cmGlobalXCodeGenerator::AppendBuildSettingAttribute(
cmXCodeObject* settings, char const* attribute, cmXCodeObject* attr,
cmXCodeObject* value)
{
if (value->GetType() != cmXCodeObject::OBJECT_LIST &&
value->GetType() != cmXCodeObject::STRING) {
cmSystemTools::Error(
cmStrCat("Unsupported value type for appending: ", attribute));
return;
}
if (attr->GetType() == cmXCodeObject::OBJECT_LIST) {
if (value->GetType() == cmXCodeObject::OBJECT_LIST) {
for (auto* obj : value->GetObjectList()) {
attr->AddObject(obj);
}
} else {
attr->AddObject(value);
}
} else if (attr->GetType() == cmXCodeObject::STRING) {
if (value->GetType() == cmXCodeObject::OBJECT_LIST) {
// Add old value as a list item to new object list
// and replace the attribute with the new list
value->PrependObject(attr);
settings->AddAttribute(attribute, value);
} else {
std::string newValue =
cmStrCat(attr->GetString(), ' ', value->GetString());
attr->SetString(newValue);
}
} else {
cmSystemTools::Error(
cmStrCat("Unsupported attribute type for appending: ", attribute));
}
}
void cmGlobalXCodeGenerator::AppendBuildSettingAttribute(
cmXCodeObject* target, char const* attribute, cmXCodeObject* value,
std::string const& configName)
{
// There are multiple configurations. Add the setting to the
// buildSettings of the configuration name given.
cmXCodeObject* configurationList =
target->GetAttribute("buildConfigurationList")->GetObject();
cmXCodeObject* buildConfigs =
configurationList->GetAttribute("buildConfigurations");
for (auto* obj : buildConfigs->GetObjectList()) {
if (configName.empty() ||
obj->GetAttribute("name")->GetString() == configName) {
cmXCodeObject* settings = obj->GetAttribute("buildSettings");
this->AppendOrAddBuildSetting(settings, attribute, value);
}
}
}
void cmGlobalXCodeGenerator::InheritBuildSettingAttribute(
cmXCodeObject* target, char const* attribute)
{
cmXCodeObject* configurationList =
target->GetAttribute("buildConfigurationList")->GetObject();
cmXCodeObject* buildConfigs =
configurationList->GetAttribute("buildConfigurations");
for (auto* obj : buildConfigs->GetObjectList()) {
cmXCodeObject* settings = obj->GetAttribute("buildSettings");
if (cmXCodeObject* attr = settings->GetAttribute(attribute)) {
BuildObjectListOrString inherited(this, true);
inherited.Add("$(inherited)");
this->AppendBuildSettingAttribute(settings, attribute, attr,
inherited.CreateList());
}
}
}
void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
{
cmGeneratorTarget* gt = target->GetTarget();
if (!gt) {
cmSystemTools::Error("Error no target on xobject\n");
return;
}
if (!gt->IsInBuildSystem()) {
return;
}
// Add dependencies on other CMake targets.
for (auto const& dep : this->GetTargetDirectDepends(gt)) {
if (cmXCodeObject* dptarget = this->FindXCodeTarget(dep)) {
this->AddDependTarget(target, dptarget);
}
}
// Separate libraries into ones that can be linked using "Link Binary With
// Libraries" build phase and the ones that can't. Only targets that build
// Apple bundles (.app, .framework, .bundle), executables and dylibs can use
// this feature and only targets that represent actual libraries (object,
// static, dynamic or bundle, excluding executables) will be used. These are
// limitations imposed by CMake use-cases - otherwise a lot of things break.
// The rest will be linked using linker flags (OTHER_LDFLAGS setting in Xcode
// project).
std::map<std::string, std::vector<cmComputeLinkInformation::Item const*>>
configItemMap;
auto addToLinkerArguments =
[&configItemMap](std::string const& configName,
cmComputeLinkInformation::Item const* libItemPtr) {
auto& linkVector = configItemMap[configName];
if (std::find_if(linkVector.begin(), linkVector.end(),
[libItemPtr](cmComputeLinkInformation::Item const* p) {
return p == libItemPtr;
}) == linkVector.end()) {
linkVector.push_back(libItemPtr);
}
};
std::vector<cmComputeLinkInformation::Item const*> linkPhaseTargetVector;
std::map<std::string, std::vector<std::string>> targetConfigMap;
using ConfigItemPair =
std::pair<std::string, cmComputeLinkInformation::Item const*>;
std::map<std::string, std::vector<ConfigItemPair>> targetItemMap;
std::map<std::string, std::vector<std::string>> targetProductNameMap;
bool useLinkPhase = false;
bool forceLinkPhase = false;
cmValue prop =
target->GetTarget()->GetProperty("XCODE_LINK_BUILD_PHASE_MODE");
if (prop) {
if (*prop == "BUILT_ONLY"_s) {
useLinkPhase = true;
} else if (*prop == "KNOWN_LOCATION"_s) {
useLinkPhase = true;
forceLinkPhase = true;
} else if (*prop != "NONE"_s) {
cmSystemTools::Error(
cmStrCat("Invalid value for XCODE_LINK_BUILD_PHASE_MODE: ", *prop));
return;
}
}
for (auto const& configName : this->CurrentConfigurationTypes) {
cmComputeLinkInformation* cli = gt->GetLinkInformation(configName);
if (!cli) {
continue;
}
for (auto const& libItem : cli->GetItems()) {
// Explicitly ignore OBJECT libraries as Xcode emulates them as static
// libraries without an artifact. Avoid exposing this to the rest of
// CMake's compilation model.
if (libItem.Target &&
libItem.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
continue;
}
// We want to put only static libraries, dynamic libraries, frameworks
// and bundles that are built from targets that are not imported in "Link
// Binary With Libraries" build phase. Except if the target property
// XCODE_LINK_BUILD_PHASE_MODE is KNOWN_LOCATION then all imported and
// non-target libraries will be added as well.
if (useLinkPhase &&
(gt->GetType() == cmStateEnums::EXECUTABLE ||
gt->GetType() == cmStateEnums::SHARED_LIBRARY ||
gt->GetType() == cmStateEnums::MODULE_LIBRARY) &&
((libItem.Target &&
(!libItem.Target->IsImported() || forceLinkPhase) &&
(libItem.Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
libItem.Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY)) ||
(!libItem.Target &&
libItem.IsPath == cmComputeLinkInformation::ItemIsPath::Yes &&
forceLinkPhase))) {
std::string libName;
bool canUseLinkPhase = !libItem.HasFeature() ||
libItem.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s ||
libItem.GetFeatureName() == "FRAMEWORK"_s ||
libItem.GetFeatureName() == "__CMAKE_LINK_XCFRAMEWORK"_s ||
libItem.GetFeatureName() == "XCFRAMEWORK"_s ||
libItem.GetFeatureName() == "WEAK_FRAMEWORK"_s ||
libItem.GetFeatureName() == "WEAK_LIBRARY"_s;
if (canUseLinkPhase) {
if (libItem.Target) {
if (libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY) {
canUseLinkPhase = canUseLinkPhase && forceLinkPhase;
} else {
// If a library target uses custom build output directory Xcode
// won't pick it up so we have to resort back to linker flags,
// but that's OK as long as the custom output dir is absolute
// path.
for (auto const& libConfigName :
this->CurrentConfigurationTypes) {
canUseLinkPhase = canUseLinkPhase &&
libItem.Target->UsesDefaultOutputDir(
libConfigName, cmStateEnums::RuntimeBinaryArtifact);
}
}
libName = libItem.Target->GetName();
} else {
libName = cmSystemTools::GetFilenameName(libItem.Value.Value);
// We don't want all the possible files here, just standard
// libraries
auto const libExt = cmSystemTools::GetFilenameExtension(libName);
if (!IsLinkPhaseLibraryExtension(libExt)) {
canUseLinkPhase = false;
}
}
}
if (canUseLinkPhase) {
// Add unique configuration name to target-config map for later
// checks
auto& configVector = targetConfigMap[libName];
if (std::find(configVector.begin(), configVector.end(),
configName) == configVector.end()) {
configVector.push_back(configName);
}
// Add a pair of config and item to target-item map
auto& itemVector = targetItemMap[libName];
itemVector.emplace_back(configName, &libItem);
// Add product file-name to a lib-product map
auto productName =
cmSystemTools::GetFilenameName(libItem.Value.Value);
auto& productVector = targetProductNameMap[libName];
if (std::find(productVector.begin(), productVector.end(),
productName) == productVector.end()) {
productVector.push_back(productName);
}
continue;
}
}
// Add this library item to a regular linker flag list
addToLinkerArguments(configName, &libItem);
}
}
// Go through target library map and separate libraries that are linked
// in all configurations and produce only single product, from the rest.
// Only these will be linked through "Link Binary With Libraries" build
// phase.
for (auto const& targetLibConfigs : targetConfigMap) {
// Add this library to "Link Binary With Libraries" build phase if it's
// linked in all configurations and it has only one product name
auto& itemVector = targetItemMap[targetLibConfigs.first];
auto& productVector = targetProductNameMap[targetLibConfigs.first];
if (targetLibConfigs.second == this->CurrentConfigurationTypes &&
productVector.size() == 1) {
// Add this library to "Link Binary With Libraries" list
linkPhaseTargetVector.push_back(itemVector[0].second);
} else {
for (auto const& libItem : targetItemMap[targetLibConfigs.first]) {
// Add this library item to a regular linker flag list
addToLinkerArguments(libItem.first, libItem.second);
}
}
}
// Add libraries to "Link Binary With Libraries" build phase and collect
// their search paths. Xcode does not support per-configuration linking
// in this build phase so we don't have to do this for each configuration
// separately.
std::vector<std::string> linkSearchPaths;
std::vector<std::string> frameworkSearchPaths;
std::set<std::pair<cmXCodeObject*, std::string>> linkBuildFileSet;
for (auto const& libItem : linkPhaseTargetVector) {
// Add target output directory as a library search path
std::string linkDir;
if (libItem->Target) {
linkDir = libItem->Target->GetLocationForBuild();
} else {
linkDir = libItem->Value.Value;
}
if (cmHasSuffix(libItem->GetFeatureName(), "FRAMEWORK"_s)) {
auto fwDescriptor = this->SplitFrameworkPath(
linkDir, cmGlobalGenerator::FrameworkFormat::Extended);
if (fwDescriptor && !fwDescriptor->Directory.empty()) {
linkDir = fwDescriptor->Directory;
if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(),
linkDir) == frameworkSearchPaths.end()) {
frameworkSearchPaths.push_back(linkDir);
}
}
} else {
linkDir = cmSystemTools::GetParentDirectory(linkDir);
if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
linkSearchPaths.end()) {
linkSearchPaths.push_back(linkDir);
}
}
if (libItem->Target && !libItem->Target->IsImported()) {
for (auto const& configName : this->CurrentConfigurationTypes) {
target->AddDependTarget(configName, libItem->Target->GetName());
}
}
// Get the library target
auto* libTarget = FindXCodeTarget(libItem->Target);
cmXCodeObject* buildFile;
if (!libTarget) {
if (libItem->IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
// Get or create a direct file ref in the root project
auto cleanPath = libItem->Value.Value;
if (cmSystemTools::FileIsFullPath(cleanPath)) {
// Some arguments are reported as paths, but they are actually not,
// so we can't collapse them, and neither can we collapse relative
// paths
cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
}
auto it = this->ExternalLibRefs.find(cleanPath);
if (it == this->ExternalLibRefs.end()) {
buildFile = CreateXCodeBuildFileFromPath(cleanPath, gt, "", nullptr);
if (!buildFile) {
// Add this library item back to a regular linker flag list
for (auto const& conf : configItemMap) {
addToLinkerArguments(conf.first, libItem);
}
continue;
}
this->ExternalLibRefs.emplace(cleanPath, buildFile);
} else {
buildFile = it->second;
}
} else {
// Add this library item back to a regular linker flag list
for (auto const& conf : configItemMap) {
addToLinkerArguments(conf.first, libItem);
}
continue;
}
} else {
// Add the target output file as a build reference for other targets
// to link against
auto* fileRefObject = libTarget->GetAttribute("productReference");
if (!fileRefObject) {
// Add this library item back to a regular linker flag list
for (auto const& conf : configItemMap) {
addToLinkerArguments(conf.first, libItem);
}
continue;
}
auto it = FileRefToBuildFileMap.find(fileRefObject);
if (it == FileRefToBuildFileMap.end()) {
buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
buildFile->AddAttribute("fileRef", fileRefObject);
FileRefToBuildFileMap[fileRefObject] = buildFile;
} else {
buildFile = it->second;
}
}
// Add this reference to current target
auto* buildPhases = target->GetAttribute("buildPhases");
if (!buildPhases) {
cmSystemTools::Error("Missing buildPhase of target");
continue;
}
auto* frameworkBuildPhase =
buildPhases->GetObject(cmXCodeObject::PBXFrameworksBuildPhase);
if (!frameworkBuildPhase) {
cmSystemTools::Error("Missing PBXFrameworksBuildPhase of buildPhase");
continue;
}
auto* buildFiles = frameworkBuildPhase->GetAttribute("files");
if (!buildFiles) {
cmSystemTools::Error("Missing files of PBXFrameworksBuildPhase");
continue;
}
if (buildFile) {
if (cmHasPrefix(libItem->GetFeatureName(), "WEAK_"_s)) {
auto key = std::make_pair(buildFile->GetAttribute("fileRef"),
libItem->GetFeatureName());
if (linkBuildFileSet.find(key) != linkBuildFileSet.end()) {
continue;
}
linkBuildFileSet.insert(key);
cmXCodeObject* buildObject =
this->CreateObject(cmXCodeObject::PBXBuildFile);
buildObject->AddAttribute("fileRef", key.first);
// Add settings, ATTRIBUTES, Weak flag
cmXCodeObject* settings =
this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);
attrs->AddObject(this->CreateString("Weak"));
settings->AddAttribute("ATTRIBUTES", attrs);
buildObject->AddAttribute("settings", settings);
buildFile = buildObject;
}
if (!buildFiles->HasObject(buildFile)) {
buildFiles->AddObject(buildFile);
}
}
}
// Loop over configuration types and set per-configuration info.
for (auto const& configName : this->CurrentConfigurationTypes) {
{
// Add object library contents as link flags.
BuildObjectListOrString libSearchPaths(this, true);
std::vector<cmSourceFile const*> objs;
gt->GetExternalObjects(objs, configName);
for (auto const* sourceFile : objs) {
if (sourceFile->GetObjectLibrary().empty()) {
continue;
}
libSearchPaths.Add(this->XCodeEscapePath(sourceFile->GetFullPath()));
}
this->AppendBuildSettingAttribute(
target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(),
configName);
}
// Compute the link library and directory information.
cmComputeLinkInformation* cli = gt->GetLinkInformation(configName);
if (!cli) {
continue;
}
// add .xcframework include paths
{
// Keep track of framework search paths we've already added or that are
// part of the set of implicit search paths. We don't want to repeat
// them and we also need to avoid hard-coding any SDK-specific paths.
// This is essential for getting device-and-simulator builds to work,
// otherwise we end up hard-coding a path to the wrong SDK for
// SDK-provided frameworks that are added by their full path.
std::set<std::string> emitted(cli->GetFrameworkPathsEmitted());
BuildObjectListOrString includePaths(this, true);
BuildObjectListOrString fwSearchPaths(this, true);
for (auto const& libItem : configItemMap[configName]) {
auto const& libName = *libItem;
if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
auto cleanPath = libName.Value.Value;
if (cmSystemTools::FileIsFullPath(cleanPath)) {
cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
}
bool isXcFramework =
cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s);
if (isXcFramework) {
auto plist = cmParseXcFrameworkPlist(
cleanPath, *this->Makefiles.front(), libName.Value.Backtrace);
if (!plist) {
return;
}
if (auto const* library = plist->SelectSuitableLibrary(
*this->Makefiles.front(), libName.Value.Backtrace)) {
auto libraryPath =
cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
library->LibraryPath);
if (auto const fwDescriptor = this->SplitFrameworkPath(
libraryPath,
cmGlobalGenerator::FrameworkFormat::Relaxed)) {
if (!fwDescriptor->Directory.empty() &&
emitted.insert(fwDescriptor->Directory).second) {
// This is a search path we had not added before and it
// isn't an implicit search path, so we need it
fwSearchPaths.Add(
this->XCodeEscapePath(fwDescriptor->Directory));
}
} else {
if (!library->HeadersPath.empty()) {
includePaths.Add(this->XCodeEscapePath(
cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
library->HeadersPath)));
}
}
} else {
return;
}
}
}
}
if (!includePaths.IsEmpty()) {
this->AppendBuildSettingAttribute(target, "HEADER_SEARCH_PATHS",
includePaths.CreateList(),
configName);
}
if (!fwSearchPaths.IsEmpty()) {
this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
fwSearchPaths.CreateList(),
configName);
}
}
// Skip link information for object libraries.
if (gt->GetType() == cmStateEnums::OBJECT_LIBRARY ||
gt->GetType() == cmStateEnums::STATIC_LIBRARY) {
continue;
}
// Add dependencies directly on library files.
for (auto const& libDep : cli->GetDepends()) {
target->AddDependLibrary(configName, libDep);
}
// add the library search paths
{
BuildObjectListOrString libSearchPaths(this, true);
for (auto const& libDir : cli->GetDirectories()) {
if (!libDir.empty() && libDir != "/usr/lib"_s) {
cmPolicies::PolicyStatus cmp0142 =
target->GetTarget()->GetPolicyStatusCMP0142();
if (cmp0142 == cmPolicies::OLD || cmp0142 == cmPolicies::WARN) {
libSearchPaths.Add(this->XCodeEscapePath(cmStrCat(
libDir, "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)")));
}
libSearchPaths.Add(this->XCodeEscapePath(libDir));
}
}
// Add previously collected paths where to look for libraries
// that were added to "Link Binary With Libraries"
for (auto& libDir : linkSearchPaths) {
libSearchPaths.Add(this->XCodeEscapePath(libDir));
}
// Add toolchain specified language link directories
std::string const& linkDirsString =
this->Makefiles.front()->GetSafeDefinition(cmStrCat(
"CMAKE_", cli->GetLinkLanguage(), "_STANDARD_LINK_DIRECTORIES"));
for (auto const& libDir : cmList(linkDirsString)) {
libSearchPaths.Add(this->XCodeEscapePath(libDir));
}
if (!libSearchPaths.IsEmpty()) {
this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS",
libSearchPaths.CreateList(),
configName);
}
}
// add framework search paths
{
BuildObjectListOrString fwSearchPaths(this, true);
// Add previously collected paths where to look for frameworks
// that were added to "Link Binary With Libraries"
for (auto& fwDir : frameworkSearchPaths) {
fwSearchPaths.Add(this->XCodeEscapePath(fwDir));
}
if (!fwSearchPaths.IsEmpty()) {
this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
fwSearchPaths.CreateList(),
configName);
}
}
// now add the left-over link libraries
{
// Keep track of framework search paths we've already added or that are
// part of the set of implicit search paths. We don't want to repeat
// them and we also need to avoid hard-coding any SDK-specific paths.
// This is essential for getting device-and-simulator builds to work,
// otherwise we end up hard-coding a path to the wrong SDK for
// SDK-provided frameworks that are added by their full path.
std::set<std::string> emitted(cli->GetFrameworkPathsEmitted());
BuildObjectListOrString libPaths(this, true);
BuildObjectListOrString fwSearchPaths(this, true);
for (auto const& libItem : configItemMap[configName]) {
auto const& libName = *libItem;
if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
auto cleanPath = libName.Value.Value;
if (cmSystemTools::FileIsFullPath(cleanPath)) {
cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
}
bool isXcFramework =
cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s);
bool isFramework = !isXcFramework &&
cmHasSuffix(libName.GetFeatureName(), "FRAMEWORK"_s);
if (isFramework) {
auto const fwDescriptor = this->SplitFrameworkPath(
cleanPath, cmGlobalGenerator::FrameworkFormat::Extended);
if (isFramework && !fwDescriptor->Directory.empty() &&
emitted.insert(fwDescriptor->Directory).second) {
// This is a search path we had not added before and it isn't
// an implicit search path, so we need it
fwSearchPaths.Add(
this->XCodeEscapePath(fwDescriptor->Directory));
}
if (libName.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s) {
// use the full path
libPaths.Add(
libName.GetFormattedItem(this->XCodeEscapePath(cleanPath))
.Value);
} else {
libPaths.Add(libName
.GetFormattedItem(this->XCodeEscapePath(
fwDescriptor->GetLinkName()))
.Value);
}
} else if (isXcFramework) {
auto plist = cmParseXcFrameworkPlist(
cleanPath, *this->Makefiles.front(), libName.Value.Backtrace);
if (!plist) {
return;
}
if (auto const* library = plist->SelectSuitableLibrary(
*this->Makefiles.front(), libName.Value.Backtrace)) {
auto libraryPath =
cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
library->LibraryPath);
if (auto const fwDescriptor = this->SplitFrameworkPath(
libraryPath,
cmGlobalGenerator::FrameworkFormat::Relaxed)) {
libPaths.Add(cmStrCat(
"-framework ",
this->XCodeEscapePath(fwDescriptor->GetLinkName())));
} else {
libPaths.Add(
libName.GetFormattedItem(this->XCodeEscapePath(libraryPath))
.Value);
}
} else {
return;
}
} else {
libPaths.Add(
libName.GetFormattedItem(this->XCodeEscapePath(cleanPath))
.Value);
}
if ((!libName.Target || libName.Target->IsImported()) &&
(isFramework || isXcFramework ||
IsLinkPhaseLibraryExtension(cleanPath))) {
// Create file reference for embedding
auto it = this->ExternalLibRefs.find(cleanPath);
if (it == this->ExternalLibRefs.end()) {
auto* buildFile =
this->CreateXCodeBuildFileFromPath(cleanPath, gt, "", nullptr);
if (buildFile) {
this->ExternalLibRefs.emplace(cleanPath, buildFile);
}
}
}
} else if (!libName.Target ||
libName.Target->GetType() !=
cmStateEnums::INTERFACE_LIBRARY) {
libPaths.Add(libName.Value.Value);
}
if (libName.Target && !libName.Target->IsImported()) {
target->AddDependTarget(configName, libName.Target->GetName());
}
}
if (!libPaths.IsEmpty()) {
this->AppendBuildSettingAttribute(target,
this->GetTargetLinkFlagsVar(gt),
libPaths.CreateList(), configName);
}
if (!fwSearchPaths.IsEmpty()) {
this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
fwSearchPaths.CreateList(),
configName);
}
}
}
}
void cmGlobalXCodeGenerator::AddEmbeddedObjects(
cmXCodeObject* target, std::string const& copyFilesBuildPhaseName,
std::string const& embedPropertyName, std::string const& dstSubfolderSpec,
int actionsOnByDefault, std::string const& defaultDstPath)
{
cmGeneratorTarget* gt = target->GetTarget();
if (!gt) {
cmSystemTools::Error("Error no target on xobject\n");
return;
}
if (!gt->IsInBuildSystem()) {
return;
}
bool isFrameworkTarget = gt->IsFrameworkOnApple();
bool isBundleTarget = gt->GetPropertyAsBool("MACOSX_BUNDLE");
bool isCFBundleTarget = gt->IsCFBundleOnApple();
if (!(isFrameworkTarget || isBundleTarget || isCFBundleTarget)) {
return;
}
cmValue files = gt->GetProperty(embedPropertyName);
if (!files) {
return;
}
// Create an "Embedded Frameworks" build phase
auto* copyFilesBuildPhase =
this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase);
copyFilesBuildPhase->SetComment(copyFilesBuildPhaseName);
copyFilesBuildPhase->AddAttribute("buildActionMask",
this->CreateString("2147483647"));
copyFilesBuildPhase->AddAttribute("dstSubfolderSpec",
this->CreateString(dstSubfolderSpec));
copyFilesBuildPhase->AddAttribute(
"name", this->CreateString(copyFilesBuildPhaseName));
if (cmValue fwEmbedPath =
gt->GetProperty(cmStrCat(embedPropertyName, "_PATH"))) {
copyFilesBuildPhase->AddAttribute("dstPath",
this->CreateString(*fwEmbedPath));
} else {
copyFilesBuildPhase->AddAttribute("dstPath",
this->CreateString(defaultDstPath));
}
copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
this->CreateString("0"));
cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
// Collect all embedded frameworks and dylibs and add them to build phase
cmList relFiles{ *files };
for (std::string const& relFile : relFiles) {
cmXCodeObject* buildFile{ nullptr };
std::string filePath = relFile;
auto* genTarget = this->FindGeneratorTarget(relFile);
if (genTarget) {
// This is a target - get it's product path reference
auto* xcTarget = this->FindXCodeTarget(genTarget);
if (!xcTarget) {
cmSystemTools::Error(
cmStrCat("Can not find a target for ", genTarget->GetName()));
continue;
}
// Add the target output file as a build reference for other targets
// to link against
auto* fileRefObject = xcTarget->GetAttribute("productReference");
if (!fileRefObject) {
cmSystemTools::Error(cmStrCat("Target ", genTarget->GetName(),
" is missing product reference"));
continue;
}
auto it = this->FileRefToEmbedBuildFileMap.find(fileRefObject);
if (it == this->FileRefToEmbedBuildFileMap.end()) {
buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
buildFile->AddAttribute("fileRef", fileRefObject);
this->FileRefToEmbedBuildFileMap[fileRefObject] = buildFile;
} else {
buildFile = it->second;
}
} else if (cmSystemTools::IsPathToFramework(relFile) ||
cmSystemTools::IsPathToMacOSSharedLibrary(relFile) ||
cmSystemTools::FileIsDirectory(filePath)) {
// This is a regular string path - create file reference
cmXCodeObject* fileRef =
this->CreateXCodeFileReferenceFromPath(relFile, gt, "", nullptr);
if (fileRef) {
buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
buildFile->SetComment(fileRef->GetComment());
buildFile->AddAttribute("fileRef",
this->CreateObjectReference(fileRef));
}
if (!buildFile) {
cmSystemTools::Error(
cmStrCat("Can't create build file for ", relFile));
continue;
}
}
if (!buildFile) {
cmSystemTools::Error(cmStrCat("Can't find a build file for ", relFile));
continue;
}
// Set build file configuration
cmXCodeObject* settings =
this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);
bool removeHeaders = actionsOnByDefault & RemoveHeadersOnCopyByDefault;
if (cmValue prop = gt->GetProperty(
cmStrCat(embedPropertyName, "_REMOVE_HEADERS_ON_COPY"))) {
removeHeaders = prop.IsOn();
}
if (removeHeaders) {
attrs->AddObject(this->CreateString("RemoveHeadersOnCopy"));
}
bool codeSign = actionsOnByDefault & CodeSignOnCopyByDefault;
if (cmValue prop =
gt->GetProperty(cmStrCat(embedPropertyName, "_CODE_SIGN_ON_COPY"))) {
codeSign = prop.IsOn();
}
if (codeSign) {
attrs->AddObject(this->CreateString("CodeSignOnCopy"));
}
settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);
buildFile->AddAttributeIfNotEmpty("settings", settings);
if (!buildFiles->HasObject(buildFile)) {
buildFiles->AddObject(buildFile);
}
}
copyFilesBuildPhase->AddAttribute("files", buildFiles);
auto* buildPhases = target->GetAttribute("buildPhases");
// Embed-something build phases must be inserted before the post-build
// command because that command is expected to be last
buildPhases->InsertObject(buildPhases->GetObjectCount() - 1,
copyFilesBuildPhase);
}
void cmGlobalXCodeGenerator::AddEmbeddedFrameworks(cmXCodeObject* target)
{
static auto const* const dstSubfolderSpec = "10";
// Despite the name, by default Xcode uses "Embed Frameworks" build phase
// for both frameworks and dynamic libraries
this->AddEmbeddedObjects(target, "Embed Frameworks",
"XCODE_EMBED_FRAMEWORKS", dstSubfolderSpec,
NoActionOnCopyByDefault);
}
void cmGlobalXCodeGenerator::AddEmbeddedPlugIns(cmXCodeObject* target)
{
static auto const* const dstSubfolderSpec = "13";
this->AddEmbeddedObjects(target, "Embed PlugIns", "XCODE_EMBED_PLUGINS",
dstSubfolderSpec, NoActionOnCopyByDefault);
}
void cmGlobalXCodeGenerator::AddEmbeddedAppExtensions(cmXCodeObject* target)
{
static auto const* const dstSubfolderSpec = "13";
this->AddEmbeddedObjects(target, "Embed App Extensions",
"XCODE_EMBED_APP_EXTENSIONS", dstSubfolderSpec,
RemoveHeadersOnCopyByDefault);
}
void cmGlobalXCodeGenerator::AddEmbeddedExtensionKitExtensions(
cmXCodeObject* target)
{
static auto const* const dstSubfolderSpec = "16";
this->AddEmbeddedObjects(target, "Embed App Extensions",
"XCODE_EMBED_EXTENSIONKIT_EXTENSIONS",
dstSubfolderSpec, RemoveHeadersOnCopyByDefault,
"$(EXTENSIONS_FOLDER_PATH)");
}
void cmGlobalXCodeGenerator::AddEmbeddedResources(cmXCodeObject* target)
{
static auto const dstSubfolderSpec = "7";
this->AddEmbeddedObjects(target, "Embed Resources", "XCODE_EMBED_RESOURCES",
dstSubfolderSpec, NoActionOnCopyByDefault);
}
void cmGlobalXCodeGenerator::AddEmbeddedXPCServices(cmXCodeObject* target)
{
static auto const dstSubfolderSpec = "16";
this->AddEmbeddedObjects(
target, "Embed XPC Services", "XCODE_EMBED_XPC_SERVICES", dstSubfolderSpec,
NoActionOnCopyByDefault, "$(CONTENTS_FOLDER_PATH)/XPCServices");
}
bool cmGlobalXCodeGenerator::CreateGroups(
std::vector<cmLocalGenerator*>& generators)
{
for (auto& generator : generators) {
cmMakefile* mf = generator->GetMakefile();
std::vector<cmSourceGroup> sourceGroups = mf->GetSourceGroups();
for (auto const& gtgt : generator->GetGeneratorTargets()) {
// Same skipping logic here as in CreateXCodeTargets so that we do not
// end up with (empty anyhow) ZERO_CHECK, install, or test source
// groups:
//
if (!gtgt->IsInBuildSystem() ||
gtgt->GetType() == cmStateEnums::GLOBAL_TARGET ||
gtgt->GetName() == CMAKE_CHECK_BUILD_SYSTEM_TARGET) {
continue;
}
auto addSourceToGroup = [this, mf, &gtgt,
&sourceGroups](std::string const& source) {
cmSourceGroup* sourceGroup = mf->FindSourceGroup(source, sourceGroups);
cmXCodeObject* pbxgroup =
this->CreateOrGetPBXGroup(gtgt.get(), sourceGroup);
std::string key = GetGroupMapKeyFromPath(gtgt.get(), source);
this->GroupMap[key] = pbxgroup;
};
// Put cmSourceFile instances in proper groups:
for (auto const& si : gtgt->GetAllConfigSources()) {
cmSourceFile const* sf = si.Source;
if (!sf->GetObjectLibrary().empty()) {
// Object library files go on the link line instead.
continue;
}
addSourceToGroup(sf->GetFullPath());
}
// Add CMakeLists.txt file for user convenience.
{
std::string listfile = this->GetCMakeInstance()->GetCMakeListFile(
gtgt->GetLocalGenerator()->GetCurrentSourceDirectory());
cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource(
listfile, false, cmSourceFileLocationKind::Known);
addSourceToGroup(sf->ResolveFullPath());
}
// Add the Info.plist we are about to generate for an App Bundle.
if (gtgt->GetPropertyAsBool("MACOSX_BUNDLE")) {
std::string plist = this->ComputeInfoPListLocation(gtgt.get());
cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource(
plist, true, cmSourceFileLocationKind::Known);
addSourceToGroup(sf->ResolveFullPath());
}
}
}
return true;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreatePBXGroup(cmXCodeObject* parent,
std::string const& name)
{
cmXCodeObject* parentChildren = nullptr;
if (parent) {
parentChildren = parent->GetAttribute("children");
}
cmXCodeObject* group = this->CreateObject(cmXCodeObject::PBXGroup);
cmXCodeObject* groupChildren =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
group->AddAttribute("name", this->CreateString(name));
group->AddAttribute("children", groupChildren);
group->AddAttribute("sourceTree", this->CreateString("<group>"));
if (parentChildren) {
parentChildren->AddObject(group);
}
return group;
}
cmXCodeObject* cmGlobalXCodeGenerator::CreateOrGetPBXGroup(
cmGeneratorTarget* gtgt, cmSourceGroup* sg)
{
std::string s;
std::string target;
std::string const targetFolder = gtgt->GetEffectiveFolderName();
if (!targetFolder.empty()) {
target = cmStrCat(targetFolder, '/');
}
target += gtgt->GetName();
s = cmStrCat(target, '/', sg->GetFullName());
auto it = this->GroupNameMap.find(s);
if (it != this->GroupNameMap.end()) {
return it->second;
}
it = this->TargetGroup.find(target);
cmXCodeObject* tgroup = nullptr;
if (it != this->TargetGroup.end()) {
tgroup = it->second;
} else {
std::vector<std::string> const tgt_folders = cmTokenize(target, '/');
std::string curr_tgt_folder;
for (std::vector<std::string>::size_type i = 0; i < tgt_folders.size();
i++) {
if (i != 0) {
curr_tgt_folder += '/';
}
curr_tgt_folder += tgt_folders[i];
it = this->TargetGroup.find(curr_tgt_folder);
if (it != this->TargetGroup.end()) {
tgroup = it->second;
continue;
}
tgroup = this->CreatePBXGroup(tgroup, tgt_folders[i]);
this->TargetGroup[curr_tgt_folder] = tgroup;
if (i == 0) {
this->MainGroupChildren->AddObject(tgroup);
}
}
}
this->TargetGroup[target] = tgroup;
// If it's the default source group (empty name) then put the source file
// directly in the tgroup...
//
if (sg->GetFullName().empty()) {
this->GroupNameMap[s] = tgroup;
return tgroup;
}
// It's a recursive folder structure, let's find the real parent group
if (sg->GetFullName() != sg->GetName()) {
std::string curr_folder = cmStrCat(target, '/');
for (auto const& folder : cmTokenize(sg->GetFullName(), '\\')) {
curr_folder += folder;
auto const i_folder = this->GroupNameMap.find(curr_folder);
// Create new folder
if (i_folder == this->GroupNameMap.end()) {
cmXCodeObject* group = this->CreatePBXGroup(tgroup, folder);
this->GroupNameMap[curr_folder] = group;
tgroup = group;
} else {
tgroup = i_folder->second;
}
curr_folder += '\\';
}
return tgroup;
}
cmXCodeObject* group = this->CreatePBXGroup(tgroup, sg->GetName());
this->GroupNameMap[s] = group;
return group;
}
bool cmGlobalXCodeGenerator::CreateXCodeObjects(
cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators)
{
this->ClearXCodeObjects();
this->RootObject = nullptr;
this->MainGroupChildren = nullptr;
this->FrameworkGroup = nullptr;
cmXCodeObject* group = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
group->AddAttribute("COPY_PHASE_STRIP", this->CreateString("NO"));
cmXCodeObject* listObjs = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (std::string const& CurrentConfigurationType :
this->CurrentConfigurationTypes) {
cmXCodeObject* buildStyle =
this->CreateObject(cmXCodeObject::PBXBuildStyle);
std::string const& name = CurrentConfigurationType;
buildStyle->AddAttribute("name", this->CreateString(name));
buildStyle->SetComment(name);
cmXCodeObject* sgroup = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
sgroup->AddAttribute("COPY_PHASE_STRIP", this->CreateString("NO"));
buildStyle->AddAttribute("buildSettings", sgroup);
listObjs->AddObject(buildStyle);
}
cmXCodeObject* mainGroup = this->CreateObject(cmXCodeObject::PBXGroup);
this->MainGroupChildren = this->CreateObject(cmXCodeObject::OBJECT_LIST);
mainGroup->AddAttribute("children", this->MainGroupChildren);
mainGroup->AddAttribute("sourceTree", this->CreateString("<group>"));
// now create the cmake groups
if (!this->CreateGroups(generators)) {
return false;
}
cmXCodeObject* productGroup = this->CreateObject(cmXCodeObject::PBXGroup);
productGroup->AddAttribute("name", this->CreateString("Products"));
productGroup->AddAttribute("sourceTree", this->CreateString("<group>"));
cmXCodeObject* productGroupChildren =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
productGroup->AddAttribute("children", productGroupChildren);
this->MainGroupChildren->AddObject(productGroup);
this->FrameworkGroup = this->CreateObject(cmXCodeObject::PBXGroup);
this->FrameworkGroup->AddAttribute("name", this->CreateString("Frameworks"));
this->FrameworkGroup->AddAttribute("sourceTree",
this->CreateString("<group>"));
cmXCodeObject* frameworkGroupChildren =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
this->FrameworkGroup->AddAttribute("children", frameworkGroupChildren);
this->MainGroupChildren->AddObject(this->FrameworkGroup);
this->ResourcesGroup = this->CreateObject(cmXCodeObject::PBXGroup);
this->ResourcesGroup->AddAttribute("name", this->CreateString("Resources"));
this->ResourcesGroup->AddAttribute("sourceTree",
this->CreateString("<group>"));
cmXCodeObject* ResourcesGroupChildren =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
this->ResourcesGroup->AddAttribute("children", ResourcesGroupChildren);
this->MainGroupChildren->AddObject(this->ResourcesGroup);
this->RootObject = this->CreateObject(cmXCodeObject::PBXProject);
this->RootObject->SetComment("Project object");
std::string project_id = cmStrCat("PROJECT_", root->GetProjectName());
this->RootObject->SetId(
this->GetOrCreateId(project_id, this->RootObject->GetId()));
group = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
this->RootObject->AddAttribute("mainGroup",
this->CreateObjectReference(mainGroup));
this->RootObject->AddAttribute("buildSettings", group);
this->RootObject->AddAttribute("buildStyles", listObjs);
this->RootObject->AddAttribute("hasScannedForEncodings",
this->CreateString("0"));
group = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
group->AddAttribute("BuildIndependentTargetsInParallel",
this->CreateString("YES"));
std::ostringstream v;
v << std::setfill('0') << std::setw(4) << XcodeVersion * 10;
group->AddAttribute("LastUpgradeCheck", this->CreateString(v.str()));
this->RootObject->AddAttribute("attributes", group);
this->RootObject->AddAttribute("compatibilityVersion",
this->CreateString("Xcode 3.2"));
// Point Xcode at the top of the source tree.
{
std::string pdir =
this->RelativeToBinary(root->GetCurrentSourceDirectory());
this->RootObject->AddAttribute("projectDirPath", this->CreateString(pdir));
this->RootObject->AddAttribute("projectRoot", this->CreateString(""));
}
cmXCodeObject* configlist =
this->CreateObject(cmXCodeObject::XCConfigurationList);
cmXCodeObject* buildConfigurations =
this->CreateObject(cmXCodeObject::OBJECT_LIST);
using Configs = std::vector<std::pair<std::string, cmXCodeObject*>>;
Configs configs;
std::string defaultConfigName;
for (auto const& name : this->CurrentConfigurationTypes) {
if (defaultConfigName.empty()) {
defaultConfigName = name;
}
cmXCodeObject* config =
this->CreateObject(cmXCodeObject::XCBuildConfiguration);
config->AddAttribute("name", this->CreateString(name));
configs.emplace_back(name, config);
}
if (defaultConfigName.empty()) {
defaultConfigName = "Debug";
}
for (auto& config : configs) {
buildConfigurations->AddObject(config.second);
}
configlist->AddAttribute("buildConfigurations", buildConfigurations);
std::string comment = cmStrCat("Build configuration list for PBXProject \"",
this->CurrentProject, '"');
configlist->SetComment(comment);
configlist->AddAttribute("defaultConfigurationIsVisible",
this->CreateString("0"));
configlist->AddAttribute("defaultConfigurationName",
this->CreateString(defaultConfigName));
cmXCodeObject* buildSettings =
this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
cmValue sysroot = this->CurrentMakefile->GetDefinition("CMAKE_OSX_SYSROOT");
cmValue deploymentTarget =
this->CurrentMakefile->GetDefinition("CMAKE_OSX_DEPLOYMENT_TARGET");
if (sysroot) {
buildSettings->AddAttribute("SDKROOT", this->CreateString(*sysroot));
}
// recompute this as it may have been changed since enable language
this->ComputeArchitectures(this->CurrentMakefile);
std::string const archs = cmJoin(this->Architectures, " ");
if (archs.empty()) {
// Tell Xcode to use NATIVE_ARCH instead of ARCHS.
buildSettings->AddAttribute("ONLY_ACTIVE_ARCH", this->CreateString("YES"));
// When targeting macOS, use only the host architecture.
if (this->SystemName == "Darwin"_s &&
(!cmNonempty(sysroot) ||
cmSystemTools::LowerCase(*sysroot).find("macos") !=
std::string::npos)) {
buildSettings->AddAttribute("ARCHS",
this->CreateString("$(NATIVE_ARCH_ACTUAL)"));
}
} else {
// Tell Xcode to use ARCHS (ONLY_ACTIVE_ARCH defaults to NO).
buildSettings->AddAttribute("ARCHS", this->CreateString(archs));
}
if (cmNonempty(deploymentTarget)) {
buildSettings->AddAttribute(GetDeploymentPlatform(root->GetMakefile()),
this->CreateString(*deploymentTarget));
}
if (!this->GeneratorToolset.empty()) {
buildSettings->AddAttribute("GCC_VERSION",
this->CreateString(this->GeneratorToolset));
}
if (this->GetLanguageEnabled("Swift")) {
std::string swiftVersion;
if (cmValue vers = this->CurrentMakefile->GetDefinition(
"CMAKE_Swift_LANGUAGE_VERSION")) {
swiftVersion = *vers;
} else if (this->XcodeVersion >= 102) {
swiftVersion = "4.0";
} else if (this->XcodeVersion >= 83) {
swiftVersion = "3.0";
} else {
swiftVersion = "2.3";
}
buildSettings->AddAttribute("SWIFT_VERSION",
this->CreateString(swiftVersion));
}
std::string const symroot = this->GetSymrootDir();
buildSettings->AddAttribute("SYMROOT", this->CreateString(symroot));
// Inside a try_compile project, do not require signing on any platform.
if (this->CMakeInstance->GetIsInTryCompile()) {
buildSettings->AddAttribute("CODE_SIGNING_ALLOWED",
this->CreateString("NO"));
}
// This code supports the OLD behavior of CMP0157. We should be able to
// remove computing the debug configuration set once the old behavior is
// removed.
auto debugConfigs = this->GetCMakeInstance()->GetDebugConfigs();
std::set<std::string> debugConfigSet(debugConfigs.begin(),
debugConfigs.end());
for (auto& config : configs) {
CreateGlobalXCConfigSettings(root, config.second, config.first);
cmXCodeObject* buildSettingsForCfg = this->CreateFlatClone(buildSettings);
// Supports the OLD behavior of CMP0157. CMP0157 OLD behavior globally set
// wholemodule compilation for all non-debug configurations, for all
// targets.
if (this->CurrentMakefile
->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT")
.IsEmpty()) {
if (debugConfigSet.count(cmSystemTools::UpperCase(config.first)) == 0) {
buildSettingsForCfg->AddAttribute("SWIFT_COMPILATION_MODE",
this->CreateString("wholemodule"));
}
}
// Put this last so it can override existing settings
// Convert "CMAKE_XCODE_ATTRIBUTE_*" variables directly.
for (auto const& var : this->CurrentMakefile->GetDefinitions()) {
if (cmHasLiteralPrefix(var, "CMAKE_XCODE_ATTRIBUTE_")) {
std::string attribute = var.substr(22);
this->FilterConfigurationAttribute(config.first, attribute);
if (!attribute.empty()) {
std::string processed = cmGeneratorExpression::Evaluate(
this->CurrentMakefile->GetSafeDefinition(var),
this->CurrentLocalGenerator, config.first);
buildSettingsForCfg->AddAttribute(attribute,
this->CreateString(processed));
}
}
}
// store per-config buildSettings into configuration object
config.second->AddAttribute("buildSettings", buildSettingsForCfg);
}
this->RootObject->AddAttribute("buildConfigurationList",
this->CreateObjectReference(configlist));
std::vector<cmXCodeObject*> targets;
for (auto& generator : generators) {
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) {
this->AddDependAndLinkInformation(t);
this->AddEmbeddedFrameworks(t);
this->AddEmbeddedPlugIns(t);
this->AddEmbeddedAppExtensions(t);
this->AddEmbeddedExtensionKitExtensions(t);
this->AddEmbeddedResources(t);
this->AddEmbeddedXPCServices(t);
// Inherit project-wide values for any target-specific search paths.
this->InheritBuildSettingAttribute(t, "HEADER_SEARCH_PATHS");
this->InheritBuildSettingAttribute(t, "SYSTEM_HEADER_SEARCH_PATHS");
this->InheritBuildSettingAttribute(t, "FRAMEWORK_SEARCH_PATHS");
this->InheritBuildSettingAttribute(t, "SYSTEM_FRAMEWORK_SEARCH_PATHS");
this->InheritBuildSettingAttribute(t, "LIBRARY_SEARCH_PATHS");
this->InheritBuildSettingAttribute(t, "LD_RUNPATH_SEARCH_PATHS");
this->InheritBuildSettingAttribute(t, "GCC_PREPROCESSOR_DEFINITIONS");
this->InheritBuildSettingAttribute(t, "OTHER_CFLAGS");
this->InheritBuildSettingAttribute(t, "OTHER_LDFLAGS");
this->InheritBuildSettingAttribute(t, "OTHER_SWIFT_FLAGS");
this->InheritBuildSettingAttribute(t,
"SWIFT_ACTIVE_COMPILATION_CONDITIONS");
}
if (this->XcodeBuildSystem == BuildSystem::One) {
this->CreateXCodeDependHackMakefile(targets);
}
// now add all targets to the root object
cmXCodeObject* allTargets = this->CreateObject(cmXCodeObject::OBJECT_LIST);
for (auto* t : targets) {
allTargets->AddObject(t);
cmXCodeObject* productRef = t->GetAttribute("productReference");
if (productRef) {
productGroupChildren->AddObject(productRef->GetObject());
}
}
this->RootObject->AddAttribute("targets", allTargets);
return true;
}
std::string cmGlobalXCodeGenerator::GetSymrootDir() const
{
return cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(), "/build");
}
std::string cmGlobalXCodeGenerator::GetTargetTempDir(
cmGeneratorTarget const* gt, std::string const& configName) const
{
// Use a path inside the SYMROOT.
return cmStrCat(this->GetSymrootDir(), '/', gt->GetName(), ".build/",
configName);
}
void cmGlobalXCodeGenerator::ComputeArchitectures(cmMakefile* mf)
{
this->Architectures.clear();
cmList::append(this->Architectures,
mf->GetDefinition("CMAKE_OSX_ARCHITECTURES"));
if (this->Architectures.empty()) {
cmList::append(this->Architectures,
mf->GetDefinition("_CMAKE_APPLE_ARCHS_DEFAULT"));
}
if (this->Architectures.empty()) {
// With no ARCHS we use ONLY_ACTIVE_ARCH and possibly a
// platform-specific default ARCHS placeholder value.
// Look up the arch that Xcode chooses in this case.
if (cmValue arch = mf->GetDefinition("CMAKE_XCODE_ARCHS")) {
this->ObjectDirArchDefault = *arch;
// We expect only one arch but choose the first just in case.
std::string::size_type pos = this->ObjectDirArchDefault.find(';');
if (pos != std::string::npos) {
this->ObjectDirArchDefault = this->ObjectDirArchDefault.substr(0, pos);
}
}
}
this->ComputeObjectDirArch(mf);
}
void cmGlobalXCodeGenerator::ComputeObjectDirArch(cmMakefile* mf)
{
if (this->Architectures.size() > 1 || this->UseEffectivePlatformName(mf)) {
this->ObjectDirArch = "$(CURRENT_ARCH)";
} else if (!this->Architectures.empty()) {
this->ObjectDirArch = this->Architectures[0];
} else {
this->ObjectDirArch = this->ObjectDirArchDefault;
}
}
void cmGlobalXCodeGenerator::CreateXCodeDependHackMakefile(
std::vector<cmXCodeObject*>& targets)
{
cmGeneratedFileStream makefileStream(this->CurrentXCodeHackMakefile);
if (!makefileStream) {
cmSystemTools::Error(
cmStrCat("Could not create ", this->CurrentXCodeHackMakefile));
return;
}
makefileStream.SetCopyIfDifferent(true);
// one more pass for external depend information not handled
// correctly by xcode
/* clang-format off */
makefileStream << "# DO NOT EDIT\n"
"# This makefile makes sure all linkable targets are\n"
"# up-to-date with anything they link to\n"
"default:\n"
"\techo \"Do not invoke directly\"\n"
"\n";
/* clang-format on */
std::set<std::string> dummyRules;
// Write rules to help Xcode relink things at the right time.
/* clang-format off */
makefileStream <<
"# Rules to remove targets that are older than anything to which they\n"
"# link. This forces Xcode to relink the targets from scratch. It\n"
"# does not seem to check these dependencies itself.\n";
/* clang-format on */
for (auto const& configName : this->CurrentConfigurationTypes) {
for (auto* target : targets) {
cmGeneratorTarget* gt = target->GetTarget();
if (gt->GetType() == cmStateEnums::EXECUTABLE ||
gt->GetType() == cmStateEnums::OBJECT_LIBRARY ||
gt->GetType() == cmStateEnums::STATIC_LIBRARY ||
gt->GetType() == cmStateEnums::SHARED_LIBRARY ||
gt->GetType() == cmStateEnums::MODULE_LIBRARY) {
// Declare an entry point for the target post-build phase.
makefileStream << this->PostBuildMakeTarget(gt->GetName(), configName)
<< ":\n";
}
if (gt->GetType() == cmStateEnums::EXECUTABLE ||
gt->GetType() == cmStateEnums::STATIC_LIBRARY ||
gt->GetType() == cmStateEnums::SHARED_LIBRARY ||
gt->GetType() == cmStateEnums::MODULE_LIBRARY) {
std::string tfull = gt->GetFullPath(configName);
std::string trel = ConvertToMakefilePath(tfull);
// Add this target to the post-build phases of its dependencies.
auto const y = target->GetDependTargets().find(configName);
if (y != target->GetDependTargets().end()) {
for (auto const& deptgt : y->second) {
makefileStream << this->PostBuildMakeTarget(deptgt, configName)
<< ": " << trel << '\n';
}
}
std::vector<cmGeneratorTarget*> objlibs;
gt->GetObjectLibrariesInSources(objlibs);
for (auto* objLib : objlibs) {
makefileStream << this->PostBuildMakeTarget(objLib->GetName(),
configName)
<< ": " << trel << '\n';
}
// Create a rule for this target.
makefileStream << trel << ':';
// List dependencies if any exist.
auto const x = target->GetDependLibraries().find(configName);
if (x != target->GetDependLibraries().end()) {
for (auto const& deplib : x->second) {
std::string file = ConvertToMakefilePath(deplib);
makefileStream << "\\\n\t" << file;
dummyRules.insert(file);
}
}
for (auto* objLib : objlibs) {
std::string const objLibName = objLib->GetName();
std::string d = cmStrCat(this->GetTargetTempDir(gt, configName),
"/lib", objLibName, ".a");
std::string dependency = ConvertToMakefilePath(d);
makefileStream << "\\\n\t" << dependency;
dummyRules.insert(dependency);
}
// Write the action to remove the target if it is out of date.
makefileStream << "\n"
"\t/bin/rm -f "
<< ConvertToMakefilePath(tfull) << '\n';
// if building for more than one architecture
// then remove those executables as well
if (this->Architectures.size() > 1) {
std::string universal =
cmStrCat(this->GetTargetTempDir(gt, configName), "/$(OBJDIR)/");
for (auto const& architecture : this->Architectures) {
std::string universalFile = cmStrCat(universal, architecture, '/',
gt->GetFullName(configName));
makefileStream << "\t/bin/rm -f "
<< ConvertToMakefilePath(universalFile) << '\n';
}
}
makefileStream << "\n\n";
}
}
}
makefileStream << "\n\n"
"# For each target create a dummy rule"
"so the target does not have to exist\n";
for (auto const& dummyRule : dummyRules) {
makefileStream << dummyRule << ":\n";
}
}
void cmGlobalXCodeGenerator::OutputXCodeProject(
cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators)
{
if (generators.empty()) {
return;
}
if (!this->CreateXCodeObjects(root, generators)) {
return;
}
std::string xcodeDir = cmStrCat(root->GetCurrentBinaryDirectory(), '/',
root->GetProjectName(), ".xcodeproj");
cmSystemTools::MakeDirectory(xcodeDir);
std::string xcodeProjFile = cmStrCat(xcodeDir, "/project.pbxproj");
cmGeneratedFileStream fout(xcodeProjFile);
fout.SetCopyIfDifferent(true);
if (!fout) {
return;
}
this->WriteXCodePBXProj(fout, root, generators);
bool hasGeneratedSchemes = this->OutputXCodeSharedSchemes(xcodeDir, root);
this->OutputXCodeWorkspaceSettings(xcodeDir, hasGeneratedSchemes);
this->ClearXCodeObjects();
// Since this call may have created new cache entries, save the cache:
//
root->GetMakefile()->GetCMakeInstance()->SaveCache(
root->GetBinaryDirectory());
}
bool cmGlobalXCodeGenerator::OutputXCodeSharedSchemes(
std::string const& xcProjDir, cmLocalGenerator* root)
{
// collect all tests for the targets
std::map<std::string, cmXCodeScheme::TestObjects> testables;
for (auto const& obj : this->XCodeObjects) {
if (obj->GetType() != cmXCodeObject::OBJECT ||
obj->GetIsA() != cmXCodeObject::PBXNativeTarget) {
continue;
}
if (!obj->GetTarget()->IsXCTestOnApple()) {
continue;
}
cmValue testee = obj->GetTarget()->GetProperty("XCTEST_TESTEE");
if (!testee) {
continue;
}
testables[*testee].push_back(obj.get());
}
// generate scheme
bool ret = false;
// Since the lowest available Xcode version for testing was 6.4,
// I'm setting this as a limit then
if (this->XcodeVersion >= 64) {
for (auto const& obj : this->XCodeObjects) {
if (obj->GetType() == cmXCodeObject::OBJECT &&
(obj->GetIsA() == cmXCodeObject::PBXNativeTarget ||
obj->GetIsA() == cmXCodeObject::PBXAggregateTarget) &&
(root->GetMakefile()->GetCMakeInstance()->GetIsInTryCompile() ||
obj->GetTarget()->GetPropertyAsBool("XCODE_GENERATE_SCHEME"))) {
std::string const& targetName = obj->GetTarget()->GetName();
cmXCodeScheme schm(root, obj.get(), testables[targetName],
this->CurrentConfigurationTypes,
this->XcodeVersion);
schm.WriteXCodeSharedScheme(xcProjDir,
this->RelativeToSource(xcProjDir));
ret = true;
}
}
}
return ret;
}
void cmGlobalXCodeGenerator::OutputXCodeWorkspaceSettings(
std::string const& xcProjDir, bool hasGeneratedSchemes)
{
std::string xcodeSharedDataDir =
cmStrCat(xcProjDir, "/project.xcworkspace/xcshareddata");
cmSystemTools::MakeDirectory(xcodeSharedDataDir);
std::string workspaceSettingsFile =
cmStrCat(xcodeSharedDataDir, "/WorkspaceSettings.xcsettings");
cmGeneratedFileStream fout(workspaceSettingsFile);
fout.SetCopyIfDifferent(true);
if (!fout) {
return;
}
cmXMLWriter xout(fout);
xout.StartDocument();
xout.Doctype("plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\""
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"");
xout.StartElement("plist");
xout.Attribute("version", "1.0");
xout.StartElement("dict");
if (this->XcodeVersion >= 100) {
xout.Element("key", "BuildSystemType");
switch (this->XcodeBuildSystem) {
case BuildSystem::One:
xout.Element("string", "Original");
if (this->XcodeVersion >= 130) {
xout.Element("key", "DisableBuildSystemDeprecationDiagnostic");
} else {
xout.Element("key", "DisableBuildSystemDeprecationWarning");
}
xout.Element("true");
break;
case BuildSystem::Twelve:
xout.Element("string", "Latest");
break;
}
}
if (hasGeneratedSchemes) {
xout.Element("key",
"IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded");
xout.Element("false");
}
xout.EndElement(); // dict
xout.EndElement(); // plist
xout.EndDocument();
}
void cmGlobalXCodeGenerator::WriteXCodePBXProj(std::ostream& fout,
cmLocalGenerator*,
std::vector<cmLocalGenerator*>&)
{
SortXCodeObjects();
fout << "// !$*UTF8*$!\n"
"{\n";
cmXCodeObject::Indent(1, fout);
fout << "archiveVersion = 1;\n";
cmXCodeObject::Indent(1, fout);
fout << "classes = {\n";
cmXCodeObject::Indent(1, fout);
fout << "};\n";
cmXCodeObject::Indent(1, fout);
fout << "objectVersion = 46;\n";
cmXCode21Object::PrintList(this->XCodeObjects, fout);
cmXCodeObject::Indent(1, fout);
fout << "rootObject = " << this->RootObject->GetId()
<< " /* Project object */;\n"
"}\n";
}
char const* cmGlobalXCodeGenerator::GetCMakeCFGIntDir() const
{
return "$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
}
std::string cmGlobalXCodeGenerator::ExpandCFGIntDir(
std::string const& str, std::string const& config) const
{
std::string replace1 = "$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
std::string replace2 = "$(CONFIGURATION)";
std::string tmp = str;
for (std::string::size_type i = tmp.find(replace1); i != std::string::npos;
i = tmp.find(replace1, i)) {
tmp.replace(i, replace1.size(), config);
i += config.size();
}
for (std::string::size_type i = tmp.find(replace2); i != std::string::npos;
i = tmp.find(replace2, i)) {
tmp.replace(i, replace2.size(), config);
i += config.size();
}
return tmp;
}
cmDocumentationEntry cmGlobalXCodeGenerator::GetDocumentation()
{
return { cmGlobalXCodeGenerator::GetActualName(),
"Generate Xcode project files." };
}
std::string cmGlobalXCodeGenerator::RelativeToSource(std::string const& p)
{
std::string const& rootSrc =
this->CurrentRootGenerator->GetCurrentSourceDirectory();
if (cmSystemTools::IsSubDirectory(p, rootSrc)) {
return cmSystemTools::ForceToRelativePath(rootSrc, p);
}
return p;
}
std::string cmGlobalXCodeGenerator::RelativeToRootBinary(std::string const& p)
{
std::string binaryDirectory =
this->CurrentRootGenerator->GetCurrentBinaryDirectory();
if (cmSystemTools::IsSubDirectory(p, binaryDirectory)) {
binaryDirectory = cmSystemTools::ForceToRelativePath(binaryDirectory, p);
}
return binaryDirectory;
}
std::string cmGlobalXCodeGenerator::RelativeToBinary(std::string const& p)
{
return this->CurrentRootGenerator->MaybeRelativeToCurBinDir(p);
}
std::string cmGlobalXCodeGenerator::XCodeEscapePath(std::string const& p)
{
if (p.find_first_of(" []") != std::string::npos) {
std::string t = cmStrCat('"', p, '"');
return t;
}
return p;
}
void cmGlobalXCodeGenerator::AppendDirectoryForConfig(
std::string const& prefix, std::string const& config,
std::string const& suffix, std::string& dir)
{
if (!config.empty()) {
dir += prefix;
dir += config;
dir += suffix;
}
}
std::string cmGlobalXCodeGenerator::LookupFlags(
std::string const& varNamePrefix, std::string const& varNameLang,
std::string const& varNameSuffix, cmGeneratorTarget const* gt)
{
std::string flags;
if (!varNameLang.empty()) {
std::string varName = cmStrCat(varNamePrefix, varNameLang, varNameSuffix);
if (cmValue varValue = this->CurrentMakefile->GetDefinition(varName)) {
if (!varValue->empty()) {
this->CurrentLocalGenerator->AppendFlags(
flags, *varValue, varName, gt, cmBuildStep::Link, varNameLang);
}
}
}
return flags;
}
void cmGlobalXCodeGenerator::AppendDefines(BuildObjectListOrString& defs,
char const* defines_list,
bool dflag)
{
// Skip this if there are no definitions.
if (!defines_list) {
return;
}
// Expand the list of definitions.
cmList defines{ defines_list };
// Store the definitions in the string.
this->AppendDefines(defs, defines, dflag);
}
void cmGlobalXCodeGenerator::AppendDefines(
BuildObjectListOrString& defs, std::vector<std::string> const& defines,
bool dflag)
{
// GCC_PREPROCESSOR_DEFINITIONS is a space-separated list of definitions.
std::string def;
for (auto const& define : defines) {
// Start with -D if requested.
if (dflag && !cmHasLiteralPrefix(define, "-D")) {
def = cmStrCat("-D", define);
} else if (!dflag && cmHasLiteralPrefix(define, "-D")) {
def = define.substr(2);
} else {
def = define;
}
// Append the flag with needed escapes.
std::string tmp;
this->AppendFlag(tmp, def);
defs.Add(tmp);
}
}
void cmGlobalXCodeGenerator::AppendFlag(std::string& flags,
std::string const& flag) const
{
// Short-circuit for an empty flag.
if (flag.empty()) {
return;
}
// Separate from previous flags.
if (!flags.empty()) {
flags += ' ';
}
// Check if the flag needs quoting.
bool quoteFlag =
flag.find_first_of("`~!@#$%^&*()+={}[]|:;\"'<>,.? ") != std::string::npos;
// We escape a flag as follows:
// - Place each flag in single quotes ''
// - Escape a single quote as \'
// - Escape a backslash as \\ since it itself is an escape
// Note that in the code below we need one more level of escapes for
// C string syntax in this source file.
//
// The final level of escaping is done when the string is stored
// into the project file by cmXCodeObject::PrintString.
if (quoteFlag) {
// Open single quote.
flags += '\'';
}
// Flag value with escaped quotes and backslashes.
for (auto c : flag) {
if (c == '\'') {
flags += "'\\''";
} else if (c == '\\') {
flags += "\\\\";
} else {
flags += c;
}
}
if (quoteFlag) {
// Close single quote.
flags += '\'';
}
}
std::string cmGlobalXCodeGenerator::ComputeInfoPListLocation(
cmGeneratorTarget* target)
{
std::string plist = cmStrCat(target->GetSupportDirectory(), "/Info.plist");
return plist;
}
// Return true if the generated build tree may contain multiple builds.
// i.e. "Can I build Debug and Release in the same tree?"
bool cmGlobalXCodeGenerator::IsMultiConfig() const
{
// Newer Xcode versions are multi config:
return true;
}
bool cmGlobalXCodeGenerator::HasKnownObjectFileLocation(
cmTarget const& target, std::string* reason) const
{
auto objectDirArch = GetTargetObjectDirArch(target, this->ObjectDirArch);
if (objectDirArch.find('$') != std::string::npos) {
if (reason != nullptr) {
*reason = " under Xcode with multiple architectures";
}
return false;
}
return true;
}
bool cmGlobalXCodeGenerator::UseEffectivePlatformName(cmMakefile* mf) const
{
cmValue epnValue = this->GetCMakeInstance()->GetState()->GetGlobalProperty(
"XCODE_EMIT_EFFECTIVE_PLATFORM_NAME");
if (!epnValue) {
return mf->PlatformIsAppleEmbedded();
}
return epnValue.IsOn();
}
bool cmGlobalXCodeGenerator::ShouldStripResourcePath(cmMakefile*) const
{
// Xcode determines Resource location itself
return true;
}
void cmGlobalXCodeGenerator::ComputeTargetObjectDirectory(
cmGeneratorTarget* gt) const
{
auto objectDirArch = GetTargetObjectDirArch(*gt, this->ObjectDirArch);
gt->ObjectDirectory =
cmStrCat(this->GetTargetTempDir(gt, this->GetCMakeCFGIntDir()),
"/$(OBJECT_FILE_DIR_normal:base)/", objectDirArch, '/');
}
std::string cmGlobalXCodeGenerator::GetDeploymentPlatform(cmMakefile const* mf)
{
switch (mf->GetAppleSDKType()) {
case cmMakefile::AppleSDK::AppleTVOS:
case cmMakefile::AppleSDK::AppleTVSimulator:
return "TVOS_DEPLOYMENT_TARGET";
case cmMakefile::AppleSDK::IPhoneOS:
case cmMakefile::AppleSDK::IPhoneSimulator:
return "IPHONEOS_DEPLOYMENT_TARGET";
case cmMakefile::AppleSDK::WatchOS:
case cmMakefile::AppleSDK::WatchSimulator:
return "WATCHOS_DEPLOYMENT_TARGET";
case cmMakefile::AppleSDK::XROS:
case cmMakefile::AppleSDK::XRSimulator:
return "XROS_DEPLOYMENT_TARGET";
case cmMakefile::AppleSDK::MacOS:
default:
return "MACOSX_DEPLOYMENT_TARGET";
}
}
cmValue cmGlobalXCodeGenerator::GetDebuggerWorkingDirectory(
cmGeneratorTarget* gt) const
{
if (cmValue ret = gt->GetProperty("XCODE_SCHEME_WORKING_DIRECTORY")) {
return ret;
}
return cmGlobalGenerator::GetDebuggerWorkingDirectory(gt);
}