diff options
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) |