1
0
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:
Gusts Kaksis
2020-08-29 22:56:05 +10:00
committed by Craig Scott
parent dc0898205c
commit 525464ed2a
19 changed files with 508 additions and 68 deletions

View File

@@ -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

View File

@@ -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

View 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.

View 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.

View 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``.

View File

@@ -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);
}
}
}

View File

@@ -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
}

View 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()

View File

@@ -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()

View File

@@ -0,0 +1 @@
include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)

View File

@@ -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()

View File

@@ -0,0 +1 @@
CMake Error: Invalid value for XCODE_LINK_BUILD_PHASE_MODE: INVALID

View File

@@ -0,0 +1,4 @@
enable_language(CXX)
add_executable(app main.cpp)
set_target_properties(app PROPERTIES XCODE_LINK_BUILD_PHASE_MODE INVALID)

View File

@@ -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()

View File

@@ -0,0 +1 @@
include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)

View File

@@ -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()

View File

@@ -0,0 +1 @@
include(${CMAKE_CURRENT_LIST_DIR}/LinkBinariesBuildPhase.cmake)

View File

@@ -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)