summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Help/manual/cmake-properties.7.rst1
-rw-r--r--Help/manual/cmake-variables.7.rst1
-rw-r--r--Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst52
-rw-r--r--Help/release/dev/xcode-link-phase-all.rst9
-rw-r--r--Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst7
-rw-r--r--Source/cmGlobalXCodeGenerator.cxx284
-rw-r--r--Source/cmTarget.cxx1
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake87
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY-check.cmake19
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY.cmake1
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake55
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-result.txt1
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-stderr.txt1
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID.cmake4
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION-check.cmake19
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION.cmake1
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE-check.cmake19
-rw-r--r--Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE.cmake1
-rw-r--r--Tests/RunCMake/XcodeProject/RunCMakeTest.cmake13
19 files changed, 508 insertions, 68 deletions
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index afdf78c..157ee18 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -395,6 +395,7 @@ Properties on Targets
/prop_tgt/XCODE_ATTRIBUTE_an-attribute
/prop_tgt/XCODE_EXPLICIT_FILE_TYPE
/prop_tgt/XCODE_GENERATE_SCHEME
+ /prop_tgt/XCODE_LINK_BUILD_PHASE_MODE
/prop_tgt/XCODE_PRODUCT_TYPE
/prop_tgt/XCODE_SCHEME_ADDRESS_SANITIZER
/prop_tgt/XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 53cdd0b..595ca1c 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -247,6 +247,7 @@ Variables that Change Behavior
/variable/CMAKE_WARN_DEPRECATED
/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION
/variable/CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY
+ /variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE
/variable/CMAKE_XCODE_SCHEME_ADDRESS_SANITIZER
/variable/CMAKE_XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN
/variable/CMAKE_XCODE_SCHEME_DEBUG_DOCUMENT_VERSIONING
diff --git a/Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst b/Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst
new file mode 100644
index 0000000..10cdedc
--- /dev/null
+++ b/Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst
@@ -0,0 +1,52 @@
+XCODE_LINK_BUILD_PHASE_MODE
+---------------------------
+
+When using the :generator:`Xcode` generator, libraries to be linked will be
+specified in the Xcode project file using either the "Link Binary With
+Libraries" build phase or directly as linker flags. The former allows Xcode
+to manage build paths, which may be necessary when creating Xcode archives
+because it may use different build paths to a regular build.
+
+This property controls usage of "Link Binary With Libraries" build phase for
+a target that is an app bundle, executable, shared library, shared framework
+or a module library.
+
+Possible values are:
+
+* ``NONE``
+ The libraries will be linked by specifying the linker flags directly.
+
+* ``BUILT_ONLY``
+ The "Link Binary With Libraries" build phase will be used to link to another
+ target under the following conditions:
+
+ - The target to be linked to is a regular non-imported, non-interface library
+ target.
+ - The output directory of the target being built has not been changed from
+ its default (see :prop_tgt:`RUNTIME_OUTPUT_DIRECTORY` and
+ :prop_tgt:`LIBRARY_OUTPUT_DIRECTORY`).
+
+* ``KNOWN_LOCATION``
+ The "Link Binary With Libraries" build phase will be used to link to another
+ target under the same conditions as with ``BUILT_ONLY`` and also:
+ - Imported library targets except those of type ``UNKNOWN``.
+ - Any non-target library specified directly with a path.
+
+For all other cases, the libraries will be linked by specifying the linker
+flags directly.
+
+.. warning::
+ Libraries linked using "Link Binary With Libraries" are linked after the
+ ones linked through regular linker flags. This order should be taken into
+ account when different static libraries contain symbols with the same name,
+ as the former ones will take precedence over the latter.
+
+.. warning::
+ If two or more directories contain libraries with identical file names and
+ some libraries are linked from those directories, the library search path
+ lookup will end up linking libraries from the first directory. This is a
+ known limitation of Xcode.
+
+This property is initialized by the value of the
+:variable:`CMAKE_XCODE_LINK_BUILD_PHASE_MODE` variable if it is set when a
+target is created.
diff --git a/Help/release/dev/xcode-link-phase-all.rst b/Help/release/dev/xcode-link-phase-all.rst
new file mode 100644
index 0000000..a38f70c
--- /dev/null
+++ b/Help/release/dev/xcode-link-phase-all.rst
@@ -0,0 +1,9 @@
+xcode-link-phase-all
+--------------------
+
+* The Xcode generator gained support for linking libraries and frameworks
+ via the *Link Binaries With Libraries* build phase instead of always by
+ embedding linker flags directly. This behavior is controlled by a new
+ :prop_tgt:`XCODE_LINK_BUILD_PHASE_MODE` target property, which is
+ initialized by a new :variable:`CMAKE_XCODE_LINK_BUILD_PHASE_MODE`
+ variable.
diff --git a/Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst b/Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst
new file mode 100644
index 0000000..ee4d37e
--- /dev/null
+++ b/Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst
@@ -0,0 +1,7 @@
+CMAKE_XCODE_LINK_BUILD_PHASE_MODE
+---------------------------------
+
+This variable is used to initialize the
+:prop_tgt:`XCODE_LINK_BUILD_PHASE_MODE` property on targets.
+It affects the methods that the :generator:`Xcode` generator uses to link
+different kinds of libraries. Its default value is ``NONE``.
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 1e44620..9f8e331 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -678,6 +678,7 @@ void cmGlobalXCodeGenerator::ClearXCodeObjects()
this->TargetGroup.clear();
this->FileRefs.clear();
this->ExternalLibRefs.clear();
+ this->FileRefToBuildFileMap.clear();
}
void cmGlobalXCodeGenerator::addObject(std::unique_ptr<cmXCodeObject> obj)
@@ -751,16 +752,23 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeBuildFileFromPath(
const std::string& 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.
- //
+ // fileRef object for any given full path. Same goes for the buildFile
+ // object.
cmXCodeObject* fileRef =
this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf);
-
- cmXCodeObject* buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
- buildFile->SetComment(fileRef->GetComment());
- buildFile->AddAttribute("fileRef", this->CreateObjectReference(fileRef));
-
- return buildFile;
+ 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
@@ -918,7 +926,9 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);
- buildFile->AddAttributeIfNotEmpty("settings", settings);
+ if (buildFile) {
+ buildFile->AddAttributeIfNotEmpty("settings", settings);
+ }
return buildFile;
}
@@ -935,16 +945,21 @@ void cmGlobalXCodeGenerator::AddXCodeProjBuildRule(
}
}
-bool IsLibraryExtension(const std::string& fileExt)
+namespace {
+
+bool IsLinkPhaseLibraryExtension(const std::string& 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" || fileExt == ".a" || fileExt == ".o" ||
- fileExt == ".dylib" || fileExt == ".tbd");
+ fileExt == ".dylib" || fileExt == ".tbd" || fileExt.empty());
}
bool IsLibraryType(const std::string& fileType)
{
return (fileType == "wrapper.framework" || fileType == "archive.ar" ||
fileType == "compiled.mach-o.objfile" ||
fileType == "compiled.mach-o.dylib" ||
+ fileType == "compiled.mach-o.executable" ||
fileType == "sourcecode.text-based-dylib-definition");
}
@@ -1020,6 +1035,9 @@ std::string GetSourcecodeValueFromFileExtension(
} else if (ext == "dylib") {
keepLastKnownFileType = true;
sourcecode = "compiled.mach-o.dylib";
+ } else if (ext == "framework") {
+ keepLastKnownFileType = true;
+ sourcecode = "wrapper.framework";
} else if (ext == "xcassets") {
keepLastKnownFileType = true;
sourcecode = "folder.assetcatalog";
@@ -1035,6 +1053,47 @@ std::string GetSourcecodeValueFromFileExtension(
return sourcecode;
}
+// If the file has no extension it's either a raw executable or might
+// be a direct reference to a binary within a framework (bad practice!).
+// This is where we change the path to point to the framework directory.
+// .tbd files also can be located in SDK frameworks (they are
+// placeholders for actual libraries shipped with the OS)
+std::string GetLibraryOrFrameworkPath(const std::string& path)
+{
+ auto ext = cmSystemTools::GetFilenameLastExtension(path);
+ if (ext.empty() || ext == ".tbd") {
+ auto name = cmSystemTools::GetFilenameWithoutExtension(path);
+ // Check for iOS framework structure:
+ // FwName.framework/FwName (and also on macOS where FwName lib is a
+ // symlink)
+ auto parentDir = cmSystemTools::GetParentDirectory(path);
+ auto parentName = cmSystemTools::GetFilenameWithoutExtension(parentDir);
+ ext = cmSystemTools::GetFilenameLastExtension(parentDir);
+ if (ext == ".framework" && name == parentName) {
+ return parentDir;
+ }
+ // Check for macOS framework structure:
+ // FwName.framework/Versions/*/FwName
+ std::vector<std::string> components;
+ cmSystemTools::SplitPath(path, components);
+ if (components.size() > 3 &&
+ components[components.size() - 3] == "Versions") {
+ ext = cmSystemTools::GetFilenameLastExtension(
+ components[components.size() - 4]);
+ parentName = cmSystemTools::GetFilenameWithoutExtension(
+ components[components.size() - 4]);
+ if (ext == ".framework" && name == parentName) {
+ components.erase(components.begin() + components.size() - 3,
+ components.end());
+ return cmSystemTools::JoinPath(components);
+ }
+ }
+ }
+ return path;
+}
+
+} // anonymous
+
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
const std::string& fullpath, cmGeneratorTarget* target,
const std::string& lang, cmSourceFile* sf)
@@ -1057,17 +1116,10 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
ext = ext.substr(1);
}
if (fileType.empty()) {
- // If file has no extension it's either a raw executable or might
- // be a direct reference to binary within a framework (bad practice!)
- // so this is where we change the path to the point to framework
- // directory.
- if (ext.empty()) {
- auto parentDir = cmSystemTools::GetParentDirectory(path);
- auto parentExt = cmSystemTools::GetFilenameLastExtension(parentDir);
- if (parentExt == ".framework") {
- path = parentDir;
- ext = parentExt.substr(1);
- }
+ path = 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
@@ -1077,8 +1129,17 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
fileType = GetDirectoryValueFromFileExtension(ext);
useLastKnownFileType = true;
} else {
- fileType = GetSourcecodeValueFromFileExtension(
- ext, lang, useLastKnownFileType, this->EnabledLangs);
+ 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);
+ }
}
}
@@ -1109,6 +1170,10 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
group = this->FrameworkGroup;
this->GroupMap[key] = group;
}
+ if (!group) {
+ cmSystemTools::Error("Could not find a PBX group for " + key);
+ return nullptr;
+ }
cmXCodeObject* children = group->GetAttribute("children");
if (!children->HasObject(fileRef)) {
children->AddObject(fileRef);
@@ -2866,9 +2931,9 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
// 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) can use this feature and only
- // targets that represent actual libraries (static or dynamic, local or
- // imported) not objects and not executables will be used. These are
+ // 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).
@@ -2891,55 +2956,92 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
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;
+ cmProp prop =
+ target->GetTarget()->GetProperty("XCODE_LINK_BUILD_PHASE_MODE");
+ if (prop) {
+ if (*prop == "BUILT_ONLY") {
+ useLinkPhase = true;
+ } else if (*prop == "KNOWN_LOCATION") {
+ useLinkPhase = true;
+ forceLinkPhase = true;
+ } else if (*prop != "NONE") {
+ cmSystemTools::Error("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()) {
- if (gt->IsBundleOnApple() &&
+ // 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 ||
- gt->GetType() == cmStateEnums::UNKNOWN_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 && libItem.IsPath))) {
- // Add unique configuration name to target-config map for later
- // checks
+ libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
+ libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY)) ||
+ (!libItem.Target && libItem.IsPath && forceLinkPhase))) {
std::string libName;
+ bool canUseLinkPhase = true;
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
const auto libExt = cmSystemTools::GetFilenameExtension(libName);
- if (!IsLibraryExtension(libExt)) {
- // Add this library item to a regular linker flag list
- addToLinkerArguments(configName, &libItem);
- continue;
+ if (!IsLinkPhaseLibraryExtension(libExt)) {
+ canUseLinkPhase = false;
}
}
- 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(ConfigItemPair(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);
+ 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(ConfigItemPair(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;
}
- } else {
- // Add this library item to a regular linker flag list
- addToLinkerArguments(configName, &libItem);
}
+ // Add this library item to a regular linker flag list
+ addToLinkerArguments(configName, &libItem);
}
}
@@ -2969,18 +3071,28 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
// 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;
for (auto const& libItem : linkPhaseTargetVector) {
// Add target output directory as a library search path
std::string linkDir;
if (libItem->Target) {
- linkDir = cmSystemTools::GetParentDirectory(
- libItem->Target->GetLocationForBuild());
+ linkDir = libItem->Target->GetLocationForBuild();
} else {
- linkDir = cmSystemTools::GetParentDirectory(libItem->Value.Value);
+ linkDir = libItem->Value.Value;
}
- if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
- linkSearchPaths.end()) {
- linkSearchPaths.push_back(linkDir);
+ linkDir = GetLibraryOrFrameworkPath(linkDir);
+ bool isFramework = cmSystemTools::IsPathToFramework(linkDir);
+ linkDir = cmSystemTools::GetParentDirectory(linkDir);
+ if (isFramework) {
+ if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(),
+ linkDir) == frameworkSearchPaths.end()) {
+ frameworkSearchPaths.push_back(linkDir);
+ }
+ } else {
+ if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
+ linkSearchPaths.end()) {
+ linkSearchPaths.push_back(linkDir);
+ }
}
// Add target dependency
if (libItem->Target && !libItem->Target->IsImported()) {
@@ -2998,6 +3110,13 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
if (it == this->ExternalLibRefs.end()) {
buildFile = CreateXCodeBuildFileFromPath(libItem->Value.Value, gt,
"", nullptr);
+ if (!buildFile) {
+ // Add this library item back to a regular linker flag list
+ for (const auto& conf : configItemMap) {
+ addToLinkerArguments(conf.first, libItem);
+ }
+ continue;
+ }
this->ExternalLibRefs.emplace(libItem->Value.Value, buildFile);
} else {
buildFile = it->second;
@@ -3032,18 +3151,21 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
// 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 (!buildFiles->HasObject(buildFile)) {
+ if (buildFile && !buildFiles->HasObject(buildFile)) {
buildFiles->AddObject(buildFile);
}
}
@@ -3104,25 +3226,51 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
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));
+ }
+ this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
+ fwSearchPaths.CreateList(),
+ configName);
+ }
+
// now add the left-over link libraries
{
- BuildObjectListOrString libSearchPaths(this, true);
+ BuildObjectListOrString libPaths(this, true);
for (auto const& libItem : configItemMap[configName]) {
auto const& libName = *libItem;
if (libName.IsPath) {
- libSearchPaths.Add(this->XCodeEscapePath(libName.Value.Value));
+ libPaths.Add(this->XCodeEscapePath(libName.Value.Value));
+ const auto libPath = GetLibraryOrFrameworkPath(libName.Value.Value);
+ if ((!libName.Target || libName.Target->IsImported()) &&
+ IsLinkPhaseLibraryExtension(libPath)) {
+ // Create file reference for embedding
+ auto it = this->ExternalLibRefs.find(libName.Value.Value);
+ if (it == this->ExternalLibRefs.end()) {
+ auto* buildFile = this->CreateXCodeBuildFileFromPath(
+ libName.Value.Value, gt, "", nullptr);
+ if (buildFile) {
+ this->ExternalLibRefs.emplace(libName.Value.Value, buildFile);
+ }
+ }
+ }
} else if (!libName.Target ||
libName.Target->GetType() !=
cmStateEnums::INTERFACE_LIBRARY) {
- libSearchPaths.Add(libName.Value.Value);
+ libPaths.Add(libName.Value.Value);
}
if (libName.Target && !libName.Target->IsImported()) {
target->AddDependTarget(configName, libName.Target->GetName());
}
}
- this->AppendBuildSettingAttribute(
- target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(),
- configName);
+ this->AppendBuildSettingAttribute(target,
+ this->GetTargetLinkFlagsVar(gt),
+ libPaths.CreateList(), configName);
}
}
}
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index bea9001..a694b14 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -398,6 +398,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
initProp("XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE");
initProp("XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS");
initProp("XCODE_SCHEME_ENVIRONMENT");
+ initPropValue("XCODE_LINK_BUILD_PHASE_MODE", "NONE");
}
#endif
}
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
new file mode 100644
index 0000000..22a181f
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
@@ -0,0 +1,87 @@
+enable_language(C)
+
+set(prototypes [[
+#include <stdio.h>
+#include <zlib.h>
+#include <resolv.h>
+int func1();
+int func2();
+int func3();
+int func4();
+int func5();
+]])
+set(impl [[
+{
+ printf("%p %p\n", compress, res_close);
+ return func1() + func2() + func3() + func4() + func5();
+}
+]])
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/mainOuter.c
+ "${prototypes}\nint main(int argc, char** argv) ${impl}"
+)
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/funcOuter.c
+ "${prototypes}\nint funcOuter() ${impl}"
+)
+
+foreach(i RANGE 1 5)
+ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/func${i}.c
+ "int func${i}() { return 32 + ${i}; }\n"
+ )
+endforeach()
+
+add_executable(app1 mainOuter.c)
+add_library(static1 STATIC funcOuter.c)
+add_library(shared1 SHARED funcOuter.c)
+add_library(module1 MODULE funcOuter.c)
+add_library(obj1 OBJECT funcOuter.c)
+add_library(staticFramework1 STATIC funcOuter.c)
+add_library(sharedFramework1 SHARED funcOuter.c)
+set_target_properties(staticFramework1 PROPERTIES FRAMEWORK TRUE)
+set_target_properties(sharedFramework1 PROPERTIES FRAMEWORK TRUE)
+
+add_library(static2 STATIC func1.c)
+add_library(shared2 SHARED func2.c)
+add_library(obj2 OBJECT func3.c)
+add_library(staticFramework2 STATIC func4.c)
+add_library(sharedFramework2 SHARED func5.c)
+set_target_properties(staticFramework2 PROPERTIES FRAMEWORK TRUE)
+set_target_properties(sharedFramework2 PROPERTIES FRAMEWORK TRUE)
+
+# Pick a couple of libraries that are always present in the Xcode SDK
+find_library(libz z REQUIRED)
+find_library(libresolv resolv REQUIRED)
+add_library(imported2 UNKNOWN IMPORTED)
+set_target_properties(imported2 PROPERTIES IMPORTED_LOCATION ${libz})
+
+# Save these for the check script to use
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/foundLibs.cmake "
+set(libz \"${libz}\")
+set(libresolv \"${libresolv}\")
+")
+
+set(mainTargets
+ app1
+ static1
+ shared1
+ module1
+ obj1
+ staticFramework1
+ sharedFramework1
+)
+set(linkToThings
+ static2
+ shared2
+ obj2
+ staticFramework2
+ sharedFramework2
+ imported2
+ ${libresolv}
+)
+
+foreach(mainTarget IN LISTS mainTargets)
+ foreach(linkTo IN LISTS linkToThings)
+ target_link_libraries(${mainTarget} PRIVATE ${linkTo})
+ endforeach()
+endforeach()
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY-check.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY-check.cmake
new file mode 100644
index 0000000..2b7edf5
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY-check.cmake
@@ -0,0 +1,19 @@
+include(${RunCMake_TEST_SOURCE_DIR}/LinkBinariesBuildPhase_Funcs.cmake)
+include(${RunCMake_TEST_BINARY_DIR}/foundLibs.cmake)
+
+# obj2 --> Embeds func3.o in the link flags, but obj2 is part of the path
+# ${libz} --> This is for imported2
+
+foreach(mainTarget IN ITEMS app1 shared1 module1 sharedFramework1)
+ checkFlags(OTHER_LDFLAGS ${mainTarget}
+ "obj2;${libz};${libresolv}"
+ "static2;shared2;staticFramework2;sharedFramework2"
+ )
+endforeach()
+
+foreach(mainTarget IN ITEMS static1 staticFramework1)
+ checkFlags(OTHER_LIBTOOLFLAGS ${mainTarget}
+ "obj2"
+ "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+ )
+endforeach()
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY.cmake
new file mode 100644
index 0000000..f9dd643
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake
new file mode 100644
index 0000000..601e52f
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake
@@ -0,0 +1,55 @@
+macro(returnOnError errorMsg)
+ if(NOT "${errorMsg}" STREQUAL "")
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}\n${errorMsg}" PARENT_SCOPE)
+ return()
+ endif()
+endmacro()
+
+function(getTargetFlags mainTarget projFlagsVar flagsVar errorVar)
+ # The flags variables in the project file might span over multiple lines
+ # so we can't easily read the flags directly from there. Instead, we use
+ # the xcodebuild -showBuildSettings option to report it on a single line.
+ execute_process(
+ COMMAND ${CMAKE_COMMAND}
+ --build ${RunCMake_TEST_BINARY_DIR}
+ --target ${mainTarget}
+ --config Debug
+ --
+ -showBuildSettings
+ COMMAND grep ${projFlagsVar}
+ OUTPUT_VARIABLE flagsContents
+ RESULT_VARIABLE result
+ )
+
+ if(result)
+ set(${errorVar} "Failed to get flags for ${mainTarget}: ${result}" PARENT_SCOPE)
+ else()
+ unset(${errorVar} PARENT_SCOPE)
+ endif()
+ set(${flagsVar} "${flagsContents}" PARENT_SCOPE)
+endfunction()
+
+function(checkFlags projFlagsVar mainTarget present absent)
+ getTargetFlags(${mainTarget} ${projFlagsVar} flags errorMsg)
+ returnOnError("${errorMsg}")
+
+ foreach(linkTo IN LISTS present)
+ string(REGEX MATCH "${linkTo}" result "${flags}")
+ if("${result}" STREQUAL "")
+ string(APPEND RunCMake_TEST_FAILED
+ "\n${mainTarget} ${projFlagsVar} is missing ${linkTo}"
+ )
+ endif()
+ endforeach()
+
+ foreach(linkTo IN LISTS absent)
+ string(REGEX MATCH "${linkTo}" result "${flags}")
+ if(NOT "${result}" STREQUAL "")
+ string(APPEND RunCMake_TEST_FAILED
+ "\n${mainTarget} ${projFlagsVar} unexpectedly contains ${linkTo}"
+ )
+ endif()
+ endforeach()
+
+ set(RunCMake_TEST_FAILED ${RunCMake_TEST_FAILED} PARENT_SCOPE)
+endfunction()
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-result.txt b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-stderr.txt b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-stderr.txt
new file mode 100644
index 0000000..0664304
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-stderr.txt
@@ -0,0 +1 @@
+CMake Error: Invalid value for XCODE_LINK_BUILD_PHASE_MODE: INVALID
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID.cmake
new file mode 100644
index 0000000..0a20d20
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID.cmake
@@ -0,0 +1,4 @@
+enable_language(CXX)
+
+add_executable(app main.cpp)
+set_target_properties(app PROPERTIES XCODE_LINK_BUILD_PHASE_MODE INVALID)
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION-check.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION-check.cmake
new file mode 100644
index 0000000..b0d2f7f
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION-check.cmake
@@ -0,0 +1,19 @@
+include(${RunCMake_TEST_SOURCE_DIR}/LinkBinariesBuildPhase_Funcs.cmake)
+include(${RunCMake_TEST_BINARY_DIR}/foundLibs.cmake)
+
+# obj2 --> Embeds func3.o in the link flags, but obj2 is part of the path
+# ${libz} --> This is for imported2
+
+foreach(mainTarget IN ITEMS app1 shared1 module1 sharedFramework1)
+ checkFlags(OTHER_LDFLAGS ${mainTarget}
+ "obj2"
+ "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+ )
+endforeach()
+
+foreach(mainTarget IN ITEMS static1 staticFramework1)
+ checkFlags(OTHER_LIBTOOLFLAGS ${mainTarget}
+ "obj2"
+ "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+ )
+endforeach()
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION.cmake
new file mode 100644
index 0000000..f9dd643
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE-check.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE-check.cmake
new file mode 100644
index 0000000..3074881
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE-check.cmake
@@ -0,0 +1,19 @@
+include(${RunCMake_TEST_SOURCE_DIR}/LinkBinariesBuildPhase_Funcs.cmake)
+include(${RunCMake_TEST_BINARY_DIR}/foundLibs.cmake)
+
+# obj2 --> Embeds func3.o in the link flags, but obj2 is part of the path
+# ${libz} --> This is for imported2
+
+foreach(mainTarget IN ITEMS app1 shared1 module1 sharedFramework1)
+ checkFlags(OTHER_LDFLAGS ${mainTarget}
+ "static2;shared2;staticFramework2;sharedFramework2;obj2;${libz};${libresolv}"
+ ""
+ )
+endforeach()
+
+foreach(mainTarget IN ITEMS static1 staticFramework1)
+ checkFlags(OTHER_LIBTOOLFLAGS ${mainTarget}
+ "obj2"
+ "static2;shared2;staticFramework2;sharedFramework2;${libz};${libresolv}"
+ )
+endforeach()
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE.cmake
new file mode 100644
index 0000000..f9dd643
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)
diff --git a/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake b/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
index ed300bb..70eaaeb 100644
--- a/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
+++ b/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
@@ -19,6 +19,19 @@ endfunction()
XcodeGenerateTopLevelProjectOnlyWithObjectLibrary()
+function(LinkBinariesBuildPhase mode)
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/LinkBinariesBuildPhase_${mode}-build)
+ set(RunCMake_TEST_OPTIONS "-DCMAKE_XCODE_LINK_BUILD_PHASE_MODE=${mode}")
+ run_cmake(LinkBinariesBuildPhase_${mode})
+ set(RunCMake_TEST_NO_CLEAN 1)
+ run_cmake_command(LinkBinariesBuildPhase_${mode}-build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+LinkBinariesBuildPhase(NONE)
+LinkBinariesBuildPhase(BUILT_ONLY)
+LinkBinariesBuildPhase(KNOWN_LOCATION)
+run_cmake(LinkBinariesBuildPhase_INVALID)
+
run_cmake(XcodeObjectNeedsEscape)
run_cmake(XcodeObjectNeedsQuote)
run_cmake(XcodeOptimizationFlags)