mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-14 02:08:27 +08:00
Xcode: Use "Link Binary With Libraries" build phase in some cases
OBJECT and STATIC libraries (framework or non-framework) do not use this build phase. Not all items to be linked use this build phase either. Co-Authored-By: Craig Scott <craig.scott@crascit.com>
This commit is contained in:

committed by
Craig Scott

parent
dc0898205c
commit
525464ed2a
@@ -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
|
||||
|
@@ -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
|
||||
|
52
Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst
Normal file
52
Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst
Normal file
@@ -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.
|
9
Help/release/dev/xcode-link-phase-all.rst
Normal file
9
Help/release/dev/xcode-link-phase-all.rst
Normal file
@@ -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.
|
7
Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst
Normal file
7
Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst
Normal file
@@ -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``.
|
@@ -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);
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
87
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
Normal file
87
Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
Normal file
@@ -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()
|
@@ -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()
|
@@ -0,0 +1 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.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()
|
@@ -0,0 +1 @@
|
||||
1
|
@@ -0,0 +1 @@
|
||||
CMake Error: Invalid value for XCODE_LINK_BUILD_PHASE_MODE: INVALID
|
@@ -0,0 +1,4 @@
|
||||
enable_language(CXX)
|
||||
|
||||
add_executable(app main.cpp)
|
||||
set_target_properties(app PROPERTIES XCODE_LINK_BUILD_PHASE_MODE INVALID)
|
@@ -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()
|
@@ -0,0 +1 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.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()
|
@@ -0,0 +1 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.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)
|
||||
|
Reference in New Issue
Block a user