From 7b3d8411a2c051c98858dbc91cb51513ecc8242d Mon Sep 17 00:00:00 2001 From: Gusts Kaksis Date: Thu, 23 Jul 2020 13:13:57 +0300 Subject: Xcode: Refactor build setting append code and attribute getter naming Support both STRING and OBJECT_LIST types in build setting attributes and make it possible to mix them --- Source/cmGlobalXCodeGenerator.cxx | 115 ++++++++++++++++++++++---------------- Source/cmGlobalXCodeGenerator.h | 4 +- Source/cmXCode21Object.cxx | 2 +- Source/cmXCodeObject.h | 6 +- 4 files changed, 76 insertions(+), 51 deletions(-) diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 793f6f7..27fbab2 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -1106,7 +1106,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath( group = this->FrameworkGroup; this->GroupMap[key] = group; } - cmXCodeObject* children = group->GetObject("children"); + cmXCodeObject* children = group->GetAttribute("children"); if (!children->HasObject(fileRef)) { children->AddObject(fileRef); } @@ -1243,10 +1243,11 @@ bool cmGlobalXCodeGenerator::CreateXCodeTarget( for (auto sourceFile : commonSourceFiles) { cmXCodeObject* xsf = this->CreateXCodeSourceFile( this->CurrentLocalGenerator, sourceFile, gtgt); - cmXCodeObject* fr = xsf->GetObject("fileRef"); - cmXCodeObject* filetype = fr->GetObject()->GetObject("explicitFileType"); + cmXCodeObject* fr = xsf->GetAttribute("fileRef"); + cmXCodeObject* filetype = + fr->GetObject()->GetAttribute("explicitFileType"); if (!filetype) { - filetype = fr->GetObject()->GetObject("lastKnownFileType"); + filetype = fr->GetObject()->GetAttribute("lastKnownFileType"); } cmGeneratorTarget::SourceFileFlags tsFlags = @@ -2321,7 +2322,7 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt, if (stdlib.size() > 8) { const auto cxxLibrary = stdlib.substr(8); if (language == "CXX" || - !buildSettings->GetObject("CLANG_CXX_LIBRARY")) { + !buildSettings->GetAttribute("CLANG_CXX_LIBRARY")) { buildSettings->AddAttribute("CLANG_CXX_LIBRARY", this->CreateString(cxxLibrary)); } @@ -2343,7 +2344,7 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt, std::string flags = cflags[language] + " " + defFlags; if (language == "CXX" || language == "OBJCXX") { if (language == "CXX" || - !buildSettings->GetObject("OTHER_CPLUSPLUSFLAGS")) { + !buildSettings->GetAttribute("OTHER_CPLUSPLUSFLAGS")) { buildSettings->AddAttribute("OTHER_CPLUSPLUSFLAGS", this->CreateString(flags)); } @@ -2351,7 +2352,7 @@ void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt, buildSettings->AddAttribute("IFORT_OTHER_FLAGS", this->CreateString(flags)); } else if (language == "C" || language == "OBJC") { - if (language == "C" || !buildSettings->GetObject("OTHER_CFLAGS")) { + if (language == "C" || !buildSettings->GetAttribute("OTHER_CFLAGS")) { buildSettings->AddAttribute("OTHER_CFLAGS", this->CreateString(flags)); } } else if (language == "Swift") { @@ -2771,7 +2772,7 @@ void cmGlobalXCodeGenerator::AddDependTarget(cmXCodeObject* target, targetdep->AddAttribute("targetProxy", this->CreateObjectReference(container)); - cmXCodeObject* depends = target->GetObject("dependencies"); + cmXCodeObject* depends = target->GetAttribute("dependencies"); if (!depends) { cmSystemTools::Error( "target does not have dependencies attribute error.."); @@ -2783,33 +2784,60 @@ void cmGlobalXCodeGenerator::AddDependTarget(cmXCodeObject* target, void cmGlobalXCodeGenerator::AppendOrAddBuildSetting(cmXCodeObject* settings, const char* attribute, - const char* value) + cmXCodeObject* value) { if (settings) { - cmXCodeObject* attr = settings->GetObject(attribute); + cmXCodeObject* attr = settings->GetAttribute(attribute); if (!attr) { - settings->AddAttribute(attribute, this->CreateString(value)); + settings->AddAttribute(attribute, value); } else { - std::string oldValue = cmStrCat(attr->GetString(), ' ', value); - attr->SetString(oldValue); + if (value->GetType() != cmXCodeObject::OBJECT_LIST && + value->GetType() != cmXCodeObject::STRING) { + cmSystemTools::Error("Unsupported value type for appending: " + + std::string(attribute)); + return; + } + if (attr->GetType() == cmXCodeObject::OBJECT_LIST) { + if (value->GetType() == cmXCodeObject::OBJECT_LIST) { + for (auto* obj : value->GetObjectList()) { + attr->AddObject(obj); + } + } else { + attr->AddObject(value); + } + } else if (attr->GetType() == cmXCodeObject::STRING) { + if (value->GetType() == cmXCodeObject::OBJECT_LIST) { + // Add old value as a list item to new object list + // and replace the attribute with the new list + value->PrependObject(attr); + settings->AddAttribute(attribute, value); + } else { + std::string newValue = + cmStrCat(attr->GetString(), ' ', value->GetString()); + attr->SetString(newValue); + } + } else { + cmSystemTools::Error("Unsupported attribute type for appending: " + + std::string(attribute)); + } } } } void cmGlobalXCodeGenerator::AppendBuildSettingAttribute( - cmXCodeObject* target, const char* attribute, const char* value, + cmXCodeObject* target, const char* attribute, cmXCodeObject* value, const std::string& configName) { // There are multiple configurations. Add the setting to the // buildSettings of the configuration name given. cmXCodeObject* configurationList = - target->GetObject("buildConfigurationList")->GetObject(); + target->GetAttribute("buildConfigurationList")->GetObject(); cmXCodeObject* buildConfigs = - configurationList->GetObject("buildConfigurations"); + configurationList->GetAttribute("buildConfigurations"); for (auto obj : buildConfigs->GetObjectList()) { if (configName.empty() || - obj->GetObject("name")->GetString() == configName) { - cmXCodeObject* settings = obj->GetObject("buildSettings"); + obj->GetAttribute("name")->GetString() == configName) { + cmXCodeObject* settings = obj->GetAttribute("buildSettings"); this->AppendOrAddBuildSetting(settings, attribute, value); } } @@ -2981,7 +3009,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) } else { // Add the target output file as a build reference for other targets // to link against - auto* fileRefObject = libTarget->GetObject("productReference"); + auto* fileRefObject = libTarget->GetAttribute("productReference"); if (!fileRefObject) { // Add this library item back to a regular linker flag list for (const auto& conf : configItemMap) { @@ -2999,7 +3027,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) } } // Add this reference to current target - auto* buildPhases = target->GetObject("buildPhases"); + auto* buildPhases = target->GetAttribute("buildPhases"); if (!buildPhases) { continue; } @@ -3008,7 +3036,7 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) if (!frameworkBuildPhase) { continue; } - auto* buildFiles = frameworkBuildPhase->GetObject("files"); + auto* buildFiles = frameworkBuildPhase->GetAttribute("files"); if (!buildFiles) { continue; } @@ -3021,20 +3049,18 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) for (auto const& configName : this->CurrentConfigurationTypes) { { // Add object library contents as link flags. - std::string linkObjs; - const char* sep = ""; + BuildObjectListOrString libSearchPaths(this, true); std::vector objs; gt->GetExternalObjects(objs, configName); for (auto sourceFile : objs) { if (sourceFile->GetObjectLibrary().empty()) { continue; } - linkObjs += sep; - sep = " "; - linkObjs += this->XCodeEscapePath(sourceFile->GetFullPath()); + libSearchPaths.Add(this->XCodeEscapePath(sourceFile->GetFullPath())); } this->AppendBuildSettingAttribute( - target, this->GetTargetLinkFlagsVar(gt), linkObjs.c_str(), configName); + target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(), + configName); } // Skip link information for object libraries. @@ -3056,49 +3082,44 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) // add the library search paths { + BuildObjectListOrString libSearchPaths(this, true); std::string linkDirs; for (auto const& libDir : cli->GetDirectories()) { if (!libDir.empty() && libDir != "/usr/lib") { - // Now add the same one but append - // $(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) to it: - linkDirs += " "; - linkDirs += this->XCodeEscapePath( - libDir + "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"); - linkDirs += " "; - linkDirs += this->XCodeEscapePath(libDir); + libSearchPaths.Add(this->XCodeEscapePath( + libDir + "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)")); + libSearchPaths.Add(this->XCodeEscapePath(libDir)); } } // Add previously collected paths where to look for libraries // that were added to "Link Binary With Libraries" - for (auto& linkDir : linkSearchPaths) { - linkDirs += " "; - linkDirs += this->XCodeEscapePath(linkDir); + for (auto& libDir : linkSearchPaths) { + libSearchPaths.Add(this->XCodeEscapePath(libDir)); } this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS", - linkDirs.c_str(), configName); + libSearchPaths.CreateList(), + configName); } // now add the left-over link libraries { - std::string linkLibs; - const char* sep = ""; + BuildObjectListOrString libSearchPaths(this, true); for (auto const& libItem : configItemMap[configName]) { auto const& libName = *libItem; - linkLibs += sep; - sep = " "; if (libName.IsPath) { - linkLibs += this->XCodeEscapePath(libName.Value.Value); + libSearchPaths.Add(this->XCodeEscapePath(libName.Value.Value)); } else if (!libName.Target || libName.Target->GetType() != cmStateEnums::INTERFACE_LIBRARY) { - linkLibs += libName.Value.Value; + libSearchPaths.Add(libName.Value.Value); } if (libName.Target && !libName.Target->IsImported()) { target->AddDependTarget(configName, libName.Target->GetName()); } } this->AppendBuildSettingAttribute( - target, this->GetTargetLinkFlagsVar(gt), linkLibs.c_str(), configName); + target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(), + configName); } } } @@ -3166,7 +3187,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreatePBXGroup(cmXCodeObject* parent, { cmXCodeObject* parentChildren = nullptr; if (parent) { - parentChildren = parent->GetObject("children"); + parentChildren = parent->GetAttribute("children"); } cmXCodeObject* group = this->CreateObject(cmXCodeObject::PBXGroup); cmXCodeObject* groupChildren = @@ -3465,7 +3486,7 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects( cmXCodeObject* allTargets = this->CreateObject(cmXCodeObject::OBJECT_LIST); for (auto t : targets) { allTargets->AddObject(t); - cmXCodeObject* productRef = t->GetObject("productReference"); + cmXCodeObject* productRef = t->GetAttribute("productReference"); if (productRef) { productGroupChildren->AddObject(productRef->GetObject()); } diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index 7018de7..f9b6300 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -168,9 +168,9 @@ private: std::string AddConfigurations(cmXCodeObject* target, cmGeneratorTarget* gtgt); void AppendOrAddBuildSetting(cmXCodeObject* settings, const char* attr, - const char* value); + cmXCodeObject* value); void AppendBuildSettingAttribute(cmXCodeObject* target, const char* attr, - const char* value, + cmXCodeObject* value, const std::string& configName); cmXCodeObject* CreateUtilityTarget(cmGeneratorTarget* gtgt); void AddDependAndLinkInformation(cmXCodeObject* target); diff --git a/Source/cmXCode21Object.cxx b/Source/cmXCode21Object.cxx index 6b133a9..1cf9a95 100644 --- a/Source/cmXCode21Object.cxx +++ b/Source/cmXCode21Object.cxx @@ -16,7 +16,7 @@ cmXCode21Object::cmXCode21Object(PBXType ptype, Type type) void cmXCode21Object::PrintComment(std::ostream& out) { if (this->Comment.empty()) { - cmXCodeObject* n = this->GetObject("name"); + cmXCodeObject* n = this->GetAttribute("name"); if (n) { this->Comment = n->GetString(); cmSystemTools::ReplaceString(this->Comment, "\"", ""); diff --git a/Source/cmXCodeObject.h b/Source/cmXCodeObject.h index 24ecaa2..282cca5 100644 --- a/Source/cmXCodeObject.h +++ b/Source/cmXCodeObject.h @@ -82,6 +82,10 @@ public: void SetObject(cmXCodeObject* value) { this->Object = value; } cmXCodeObject* GetObject() { return this->Object; } void AddObject(cmXCodeObject* value) { this->List.push_back(value); } + void PrependObject(cmXCodeObject* value) + { + this->List.insert(this->List.begin(), value); + } bool HasObject(cmXCodeObject* o) const { return cm::contains(this->List, o); @@ -107,7 +111,7 @@ public: void SetTarget(cmGeneratorTarget* t) { this->Target = t; } const std::string& GetComment() const { return this->Comment; } bool HasComment() const { return (!this->Comment.empty()); } - cmXCodeObject* GetObject(const char* name) const + cmXCodeObject* GetAttribute(const char* name) const { auto const i = this->ObjectAttributes.find(name); if (i != this->ObjectAttributes.end()) { -- cgit v0.12 From dc0898205c7fcebcb5224ba3eb6dd95c05193bdd Mon Sep 17 00:00:00 2001 From: Gusts Kaksis Date: Wed, 19 Aug 2020 18:43:19 +0300 Subject: Xcode: Add special case for file type extension map for .xcassets --- Source/cmGlobalXCodeGenerator.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 27fbab2..1e44620 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -1020,6 +1020,9 @@ std::string GetSourcecodeValueFromFileExtension( } else if (ext == "dylib") { keepLastKnownFileType = true; sourcecode = "compiled.mach-o.dylib"; + } else if (ext == "xcassets") { + keepLastKnownFileType = true; + sourcecode = "folder.assetcatalog"; } // else // { -- cgit v0.12 From 525464ed2a8857be3fac224b4afde22c8c7dadeb Mon Sep 17 00:00:00 2001 From: Gusts Kaksis Date: Sat, 29 Aug 2020 22:56:05 +1000 Subject: 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 --- Help/manual/cmake-properties.7.rst | 1 + Help/manual/cmake-variables.7.rst | 1 + Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst | 52 ++++ Help/release/dev/xcode-link-phase-all.rst | 9 + .../variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst | 7 + Source/cmGlobalXCodeGenerator.cxx | 284 ++++++++++++++++----- Source/cmTarget.cxx | 1 + .../XcodeProject/LinkBinariesBuildPhase.cmake | 87 +++++++ .../LinkBinariesBuildPhase_BUILT_ONLY-check.cmake | 19 ++ .../LinkBinariesBuildPhase_BUILT_ONLY.cmake | 1 + .../LinkBinariesBuildPhase_Funcs.cmake | 55 ++++ .../LinkBinariesBuildPhase_INVALID-result.txt | 1 + .../LinkBinariesBuildPhase_INVALID-stderr.txt | 1 + .../LinkBinariesBuildPhase_INVALID.cmake | 4 + ...nkBinariesBuildPhase_KNOWN_LOCATION-check.cmake | 19 ++ .../LinkBinariesBuildPhase_KNOWN_LOCATION.cmake | 1 + .../LinkBinariesBuildPhase_NONE-check.cmake | 19 ++ .../XcodeProject/LinkBinariesBuildPhase_NONE.cmake | 1 + Tests/RunCMake/XcodeProject/RunCMakeTest.cmake | 13 + 19 files changed, 508 insertions(+), 68 deletions(-) create mode 100644 Help/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE.rst create mode 100644 Help/release/dev/xcode-link-phase-all.rst create mode 100644 Help/variable/CMAKE_XCODE_LINK_BUILD_PHASE_MODE.rst create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY-check.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_BUILT_ONLY.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-result.txt create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID-stderr.txt create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_INVALID.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION-check.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_KNOWN_LOCATION.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE-check.cmake create mode 100644 Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_NONE.cmake 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 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 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::map> targetItemMap; std::map> 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 linkSearchPaths; + std::vector 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..a694b141 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 +#include +#include +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) -- cgit v0.12