diff options
author | Gusts Kaksis <gusts.kaksis@sonarworks.com> | 2020-08-29 12:56:05 (GMT) |
---|---|---|
committer | Craig Scott <craig.scott@crascit.com> | 2020-08-31 21:38:48 (GMT) |
commit | 525464ed2a8857be3fac224b4afde22c8c7dadeb (patch) | |
tree | dc2f31f41a340ca3f06f1770de04950dd18268b8 /Source | |
parent | dc0898205c7fcebcb5224ba3eb6dd95c05193bdd (diff) | |
download | CMake-525464ed2a8857be3fac224b4afde22c8c7dadeb.zip CMake-525464ed2a8857be3fac224b4afde22c8c7dadeb.tar.gz CMake-525464ed2a8857be3fac224b4afde22c8c7dadeb.tar.bz2 |
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>
Diffstat (limited to 'Source')
-rw-r--r-- | Source/cmGlobalXCodeGenerator.cxx | 284 | ||||
-rw-r--r-- | Source/cmTarget.cxx | 1 |
2 files changed, 217 insertions, 68 deletions
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 } |