in Source/cmGlobalXCodeGenerator.cxx [3589:4161]
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);
}
}
}
}