macOS: Add support for linking against .xcframework folders

Issue: #21752
stage/master/nightly/2023/07/27^2
Kyle Edwards 2023-05-17 13:50:32 -04:00
parent 93ed53790c
commit 7050ac56a1
94 changed files with 828 additions and 6 deletions

View File

@ -66,6 +66,12 @@ Each ``<item>`` may be:
:ref:`usage requirement <Target Usage Requirements>`. This has the same
effect as passing the framework directory as an include directory.
.. versionadded:: 3.28
The library file may point to a ``.xcframework`` folder on macOS. If it
does, the target will get the selected library's ``Headers`` directory as
a usage requirement.
.. versionadded:: 3.8
On :ref:`Visual Studio Generators` for VS 2010 and above, library files
ending in ``.targets`` will be treated as MSBuild targets files and

View File

@ -20,6 +20,12 @@ non-imported targets.
For frameworks on macOS, this may be the location of the framework folder
itself.
.. versionadded:: 3.28
This may be the location of a ``.xcframework`` folder on macOS. If it is,
any target that links against it will get the selected library's ``Headers``
directory as a usage requirement.
The ``IMPORTED_LOCATION`` target property may be overridden for a
given configuration ``<CONFIG>`` by the configuration-specific
:prop_tgt:`IMPORTED_LOCATION_<CONFIG>` target property. Furthermore,

View File

@ -0,0 +1,7 @@
xcframework-target-link-libraries
---------------------------------
* Targets may now link against an ``.xcframework`` folder in
:command:`target_link_libraries`.
* The :prop_tgt:`IMPORTED_LOCATION` property of a target may now be an
``.xcframework`` folder.

View File

@ -371,6 +371,8 @@ add_library(
cmNewLineStyle.cxx
cmOrderDirectories.cxx
cmOrderDirectories.h
cmPlistParser.cxx
cmPlistParser.h
cmPolicies.h
cmPolicies.cxx
cmProcessOutput.cxx
@ -451,6 +453,8 @@ add_library(
cmWorkerPool.h
cmWorkingDirectory.cxx
cmWorkingDirectory.h
cmXcFramework.cxx
cmXcFramework.h
cmXMLParser.cxx
cmXMLParser.h
cmXMLSafe.cxx

View File

@ -29,6 +29,7 @@
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
#include "cmXcFramework.h"
#include "cmake.h"
// #define CM_COMPUTE_LINK_INFO_DEBUG
@ -373,6 +374,10 @@ cmComputeLinkInformation::cmComputeLinkInformation(
this->LibraryFeatureDescriptors.emplace(
"__CMAKE_LINK_FRAMEWORK",
LibraryFeatureDescriptor{ "__CMAKE_LINK_FRAMEWORK", "<LIBRARY>" });
// To link xcframework using a full path
this->LibraryFeatureDescriptors.emplace(
"__CMAKE_LINK_XCFRAMEWORK",
LibraryFeatureDescriptor{ "__CMAKE_LINK_XCFRAMEWORK", "<LIBRARY>" });
// Check the platform policy for missing soname case.
this->NoSONameUsesPath =
@ -519,6 +524,12 @@ cmComputeLinkInformation::GetFrameworkPathsEmitted() const
return this->FrameworkPathsEmitted;
}
std::vector<std::string> const&
cmComputeLinkInformation::GetXcFrameworkHeaderPaths() const
{
return this->XcFrameworkHeaderPaths;
}
const std::set<const cmGeneratorTarget*>&
cmComputeLinkInformation::GetSharedLibrariesLinked() const
{
@ -1159,6 +1170,13 @@ void cmComputeLinkInformation::AddItem(LinkEntry const& entry)
} else {
this->ObjectLibrariesLinked.push_back(entry.Target);
}
} else if (this->GlobalGenerator->IsXcode() &&
!tgt->GetImportedXcFrameworkPath(config).empty()) {
this->Items.emplace_back(
tgt->GetImportedXcFrameworkPath(config), ItemIsPath::Yes, tgt,
this->FindLibraryFeature(entry.Feature == DEFAULT
? "__CMAKE_LINK_XCFRAMEWORK"
: entry.Feature));
} else {
// Decide whether to use an import library.
cmStateEnums::ArtifactType artifact = tgt->HasImportLibrary(config)
@ -1198,6 +1216,25 @@ void cmComputeLinkInformation::AddItem(LinkEntry const& entry)
this->AddRuntimeDLL(tgt);
}
}
auto xcFrameworkPath = tgt->GetImportedXcFrameworkPath(config);
if (!xcFrameworkPath.empty()) {
auto plist = cmParseXcFrameworkPlist(xcFrameworkPath, *this->Makefile,
item.Backtrace);
if (!plist) {
return;
}
if (auto const* library =
plist->SelectSuitableLibrary(*this->Makefile, item.Backtrace)) {
if (!library->HeadersPath.empty()) {
this->AddXcFrameworkHeaderPath(cmStrCat(xcFrameworkPath, '/',
library->LibraryIdentifier,
'/', library->HeadersPath));
}
} else {
return;
}
}
} else {
// This is not a CMake target. Use the name given.
if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s) ||
@ -1206,6 +1243,12 @@ void cmComputeLinkInformation::AddItem(LinkEntry const& entry)
this->Target->IsApple())) {
// This is a framework.
this->AddFrameworkItem(entry);
} else if (cmHasSuffix(entry.Feature, "XCFRAMEWORK"_s) ||
(entry.Feature == DEFAULT &&
cmSystemTools::IsPathToXcFramework(item.Value) &&
this->Target->IsApple())) {
// This is a framework.
this->AddXcFrameworkItem(entry);
} else if (cmSystemTools::FileIsFullPath(item.Value)) {
if (cmSystemTools::FileIsDirectory(item.Value)) {
// This is a directory.
@ -1945,6 +1988,46 @@ void cmComputeLinkInformation::AddFrameworkItem(LinkEntry const& entry)
}
}
void cmComputeLinkInformation::AddXcFrameworkItem(LinkEntry const& entry)
{
auto plist = cmParseXcFrameworkPlist(entry.Item.Value, *this->Makefile,
entry.Item.Backtrace);
if (!plist) {
return;
}
if (auto const* lib =
plist->SelectSuitableLibrary(*this->Makefile, entry.Item.Backtrace)) {
if (this->GlobalGenerator->IsXcode()) {
this->Items.emplace_back(
entry.Item.Value, ItemIsPath::Yes, nullptr,
this->FindLibraryFeature(entry.Feature == DEFAULT
? "__CMAKE_LINK_XCFRAMEWORK"
: entry.Feature));
} else {
auto libraryPath = cmStrCat(
entry.Item.Value, '/', lib->LibraryIdentifier, '/', lib->LibraryPath);
LinkEntry libraryEntry(
BT<std::string>(libraryPath, entry.Item.Backtrace), entry.Target);
if (cmSystemTools::IsPathToFramework(libraryPath) &&
this->Target->IsApple()) {
// This is a framework.
this->AddFrameworkItem(libraryEntry);
} else {
this->Depends.push_back(libraryPath);
this->AddFullItem(libraryEntry);
this->AddLibraryRuntimeInfo(libraryPath);
if (!lib->HeadersPath.empty()) {
this->AddXcFrameworkHeaderPath(cmStrCat(entry.Item.Value, '/',
lib->LibraryIdentifier, '/',
lib->HeadersPath));
}
}
}
}
}
void cmComputeLinkInformation::DropDirectoryItem(BT<std::string> const& item)
{
// A full path to a directory was found as a link item. Warn the
@ -1982,6 +2065,11 @@ void cmComputeLinkInformation::AddFrameworkPath(std::string const& p)
}
}
void cmComputeLinkInformation::AddXcFrameworkHeaderPath(std::string const& p)
{
this->XcFrameworkHeaderPaths.push_back(p);
}
bool cmComputeLinkInformation::CheckSharedLibNoSOName(LinkEntry const& entry)
{
// This platform will use the path to a library as its soname if the

View File

@ -88,6 +88,7 @@ public:
std::vector<std::string> const& GetDepends() const;
std::vector<std::string> const& GetFrameworkPaths() const;
std::set<std::string> const& GetFrameworkPathsEmitted() const;
std::vector<std::string> const& GetXcFrameworkHeaderPaths() const;
std::string GetLinkLanguage() const { return this->LinkLanguage; }
std::vector<std::string> const& GetRuntimeSearchPath() const;
std::string const& GetRuntimeFlag() const { return this->RuntimeFlag; }
@ -132,6 +133,7 @@ private:
std::vector<std::string> Directories;
std::vector<std::string> Depends;
std::vector<std::string> FrameworkPaths;
std::vector<std::string> XcFrameworkHeaderPaths;
std::vector<std::string> RuntimeSearchPath;
std::set<cmGeneratorTarget const*> SharedLibrariesLinked;
std::vector<cmGeneratorTarget const*> ObjectLibrariesLinked;
@ -204,6 +206,7 @@ private:
bool CheckImplicitDirItem(LinkEntry const& entry);
void AddUserItem(LinkEntry const& entry, bool pathNotKnown);
void AddFrameworkItem(LinkEntry const& entry);
void AddXcFrameworkItem(LinkEntry const& entry);
void DropDirectoryItem(BT<std::string> const& item);
bool CheckSharedLibNoSOName(LinkEntry const& entry);
void AddSharedLibNoSOName(LinkEntry const& entry);
@ -214,6 +217,8 @@ private:
void AddFrameworkPath(std::string const& p);
std::set<std::string> FrameworkPathsEmitted;
void AddXcFrameworkHeaderPath(std::string const& p);
// Linker search path computation.
std::unique_ptr<cmOrderDirectories> OrderLinkerSearchPath;
bool FinishLinkerSearchDirectories();

View File

@ -8874,6 +8874,47 @@ std::string cmGeneratorTarget::GenerateHeaderSetVerificationFile(
return filename;
}
std::string cmGeneratorTarget::GetImportedXcFrameworkPath(
const std::string& config) const
{
if (!(this->IsApple() && this->IsImported() &&
(this->GetType() == cmStateEnums::SHARED_LIBRARY ||
this->GetType() == cmStateEnums::STATIC_LIBRARY ||
this->GetType() == cmStateEnums::UNKNOWN_LIBRARY))) {
return {};
}
std::string desiredConfig = config;
if (config.empty()) {
desiredConfig = "NOCONFIG";
}
std::string result;
cmValue loc = nullptr;
cmValue imp = nullptr;
std::string suffix;
if (this->Target->GetMappedConfig(desiredConfig, loc, imp, suffix)) {
if (loc) {
result = *loc;
} else {
std::string impProp = cmStrCat("IMPORTED_LOCATION", suffix);
if (cmValue configLocation = this->GetProperty(impProp)) {
result = *configLocation;
} else if (cmValue location = this->GetProperty("IMPORTED_LOCATION")) {
result = *location;
}
}
if (cmSystemTools::IsPathToXcFramework(result)) {
return result;
}
}
return {};
}
bool cmGeneratorTarget::HaveFortranSources(std::string const& config) const
{
auto sources = cmGeneratorTarget::GetSourceFiles(config);

View File

@ -926,6 +926,8 @@ public:
cmSourceFile& source, const std::string& dir,
cm::optional<std::set<std::string>>& languages) const;
std::string GetImportedXcFrameworkPath(const std::string& config) const;
private:
void AddSourceCommon(const std::string& src, bool before = false);

View File

@ -53,6 +53,7 @@
#include "cmXCodeObject.h"
#include "cmXCodeScheme.h"
#include "cmXMLWriter.h"
#include "cmXcFramework.h"
#include "cmake.h"
#if !defined(CMAKE_BOOTSTRAP) && defined(__APPLE__)
@ -1061,12 +1062,14 @@ 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.empty());
return (fileExt == ".framework" || fileExt == ".xcframework" ||
fileExt == ".a" || fileExt == ".o" || fileExt == ".dylib" ||
fileExt == ".tbd" || fileExt.empty());
}
bool IsLibraryType(const std::string& fileType)
{
return (fileType == "wrapper.framework" || fileType == "archive.ar" ||
return (fileType == "wrapper.framework" ||
fileType == "wrapper.xcframework" || fileType == "archive.ar" ||
fileType == "compiled.mach-o.objfile" ||
fileType == "compiled.mach-o.dylib" ||
fileType == "compiled.mach-o.executable" ||
@ -1079,6 +1082,9 @@ std::string GetDirectoryValueFromFileExtension(const std::string& dirExt)
if (ext == "framework") {
return "wrapper.framework";
}
if (ext == "xcframework") {
return "wrapper.xcframework";
}
if (ext == "xcassets") {
return "folder.assetcatalog";
}
@ -3607,6 +3613,8 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
bool canUseLinkPhase = !libItem.HasFeature() ||
libItem.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s ||
libItem.GetFeatureName() == "FRAMEWORK"_s ||
libItem.GetFeatureName() == "__CMAKE_LINK_XCFRAMEWORK"_s ||
libItem.GetFeatureName() == "XCFRAMEWORK"_s ||
libItem.GetFeatureName() == "WEAK_FRAMEWORK"_s ||
libItem.GetFeatureName() == "WEAK_LIBRARY"_s;
if (canUseLinkPhase) {
@ -3917,12 +3925,14 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
if (cmSystemTools::FileIsFullPath(cleanPath)) {
cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
}
bool isFramework =
bool isXcFramework =
cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s);
bool isFramework = !isXcFramework &&
cmHasSuffix(libName.GetFeatureName(), "FRAMEWORK"_s);
if (isFramework) {
const auto fwDescriptor = this->SplitFrameworkPath(
cleanPath, cmGlobalGenerator::FrameworkFormat::Extended);
if (!fwDescriptor->Directory.empty() &&
if (isFramework && !fwDescriptor->Directory.empty() &&
emitted.insert(fwDescriptor->Directory).second) {
// This is a search path we had not added before and it isn't
// an implicit search path, so we need it
@ -3940,13 +3950,54 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
fwDescriptor->GetLinkName()))
.Value);
}
} else if (isXcFramework) {
auto plist = cmParseXcFrameworkPlist(
cleanPath, *this->Makefiles.front(), libName.Value.Backtrace);
if (!plist) {
return;
}
if (auto const* library = plist->SelectSuitableLibrary(
*this->Makefiles.front(), libName.Value.Backtrace)) {
auto libraryPath =
cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
library->LibraryPath);
if (auto const fwDescriptor = this->SplitFrameworkPath(
libraryPath,
cmGlobalGenerator::FrameworkFormat::Relaxed)) {
if (!fwDescriptor->Directory.empty() &&
emitted.insert(fwDescriptor->Directory).second) {
// This is a search path we had not added before and it
// isn't an implicit search path, so we need it
fwSearchPaths.Add(
this->XCodeEscapePath(fwDescriptor->Directory));
}
libPaths.Add(cmStrCat(
"-framework ",
this->XCodeEscapePath(fwDescriptor->GetLinkName())));
} else {
libPaths.Add(
libName.GetFormattedItem(this->XCodeEscapePath(libraryPath))
.Value);
if (!library->HeadersPath.empty()) {
this->AppendBuildSettingAttribute(
target, "HEADER_SEARCH_PATHS",
this->CreateString(this->XCodeEscapePath(
cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
library->HeadersPath))),
configName);
}
}
} else {
return;
}
} else {
libPaths.Add(
libName.GetFormattedItem(this->XCodeEscapePath(cleanPath))
.Value);
}
if ((!libName.Target || libName.Target->IsImported()) &&
(isFramework || IsLinkPhaseLibraryExtension(cleanPath))) {
(isFramework || isXcFramework ||
IsLinkPhaseLibraryExtension(cleanPath))) {
// Create file reference for embedding
auto it = this->ExternalLibRefs.find(cleanPath);
if (it == this->ExternalLibRefs.end()) {

View File

@ -1659,6 +1659,8 @@ std::vector<BT<std::string>> cmLocalGenerator::GetTargetCompileFlags(
this->AppendFlags(compileFlags, mf->GetDefineFlags());
this->AppendFlags(compileFlags,
this->GetFrameworkFlags(lang, config, target));
this->AppendFlags(compileFlags,
this->GetXcFrameworkFlags(lang, config, target));
if (!compileFlags.empty()) {
flags.emplace_back(std::move(compileFlags));
@ -1724,6 +1726,43 @@ std::string cmLocalGenerator::GetFrameworkFlags(std::string const& lang,
return flags;
}
std::string cmLocalGenerator::GetXcFrameworkFlags(std::string const& lang,
std::string const& config,
cmGeneratorTarget* target)
{
cmLocalGenerator* lg = target->GetLocalGenerator();
cmMakefile* mf = lg->GetMakefile();
if (!target->IsApple()) {
return std::string();
}
cmValue includeSearchFlag =
mf->GetDefinition(cmStrCat("CMAKE_INCLUDE_FLAG_", lang));
cmValue sysIncludeSearchFlag =
mf->GetDefinition(cmStrCat("CMAKE_INCLUDE_SYSTEM_FLAG_", lang));
if (!includeSearchFlag && !sysIncludeSearchFlag) {
return std::string{};
}
std::string flags;
if (cmComputeLinkInformation* cli = target->GetLinkInformation(config)) {
std::vector<std::string> const& paths = cli->GetXcFrameworkHeaderPaths();
for (std::string const& path : paths) {
if (sysIncludeSearchFlag &&
target->IsSystemIncludeDirectory(path, config, lang)) {
flags += *sysIncludeSearchFlag;
} else {
flags += *includeSearchFlag;
}
flags += lg->ConvertToOutputFormat(path, cmOutputConverter::SHELL);
flags += " ";
}
}
return flags;
}
void cmLocalGenerator::GetTargetDefines(cmGeneratorTarget const* target,
std::string const& config,
std::string const& lang,

View File

@ -520,6 +520,9 @@ public:
std::string GetFrameworkFlags(std::string const& l,
std::string const& config,
cmGeneratorTarget* target);
std::string GetXcFrameworkFlags(std::string const& l,
std::string const& config,
cmGeneratorTarget* target);
virtual std::string GetTargetFortranFlags(cmGeneratorTarget const* target,
std::string const& config);

33
Source/cmPlistParser.cxx Normal file
View File

@ -0,0 +1,33 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmPlistParser.h"
#include <cm3p/json/reader.h>
#include <cm3p/json/value.h>
#include "cmUVProcessChain.h"
#include "cmUVStream.h"
cm::optional<Json::Value> cmParsePlist(const std::string& filename)
{
cmUVProcessChainBuilder builder;
builder.AddCommand(
{ "/usr/bin/plutil", "-convert", "json", "-o", "-", filename });
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT);
auto chain = builder.Start();
chain.Wait();
auto const& status = chain.GetStatus(0);
if (status.ExitStatus != 0) {
return cm::nullopt;
}
Json::Reader reader;
Json::Value value;
cmUVPipeIStream outputStream(chain.GetLoop(), chain.OutputStream());
if (!reader.parse(outputStream, value)) {
return cm::nullopt;
}
return cm::optional<Json::Value>(value);
}

13
Source/cmPlistParser.h Normal file
View File

@ -0,0 +1,13 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <cm/optional>
namespace Json {
class Value;
}
cm::optional<Json::Value> cmParsePlist(const std::string& filename);

View File

@ -1900,6 +1900,12 @@ bool cmSystemTools::IsPathToFramework(const std::string& path)
cmHasLiteralSuffix(path, ".framework"));
}
bool cmSystemTools::IsPathToXcFramework(const std::string& path)
{
return (cmSystemTools::FileIsFullPath(path) &&
cmHasLiteralSuffix(path, ".xcframework"));
}
bool cmSystemTools::IsPathToMacOSSharedLibrary(const std::string& path)
{
return (cmSystemTools::FileIsFullPath(path) &&

View File

@ -111,6 +111,9 @@ public:
//! Return true if the path is a framework
static bool IsPathToFramework(const std::string& path);
//! Return true if the path is a xcframework
static bool IsPathToXcFramework(const std::string& path);
//! Return true if the path is a macOS non-framework shared library (aka
//! .dylib)
static bool IsPathToMacOSSharedLibrary(const std::string& path);

View File

@ -40,6 +40,7 @@
#include "cmSystemTools.h"
#include "cmTargetPropertyComputer.h"
#include "cmValue.h"
#include "cmXcFramework.h"
#include "cmake.h"
template <>
@ -2800,6 +2801,25 @@ std::string cmTarget::ImportedGetFullPath(
}
}
}
if (this->IsApple() &&
(this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
this->impl->TargetType == cmStateEnums::STATIC_LIBRARY ||
this->impl->TargetType == cmStateEnums::UNKNOWN_LIBRARY) &&
cmSystemTools::IsPathToXcFramework(result)) {
auto plist = cmParseXcFrameworkPlist(result, *this->impl->Makefile,
this->impl->Backtrace);
if (!plist) {
return "";
}
auto const* library = plist->SelectSuitableLibrary(
*this->impl->Makefile, this->impl->Backtrace);
if (library) {
result = cmStrCat(result, '/', library->LibraryIdentifier, '/',
library->LibraryPath);
} else {
return "";
}
}
break;
case cmStateEnums::ImportLibraryArtifact:

174
Source/cmXcFramework.cxx Normal file
View File

@ -0,0 +1,174 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmXcFramework.h"
#include <functional>
#include <string>
#include <cmext/string_view>
#include <cm3p/json/value.h>
#include "cmJSONHelpers.h"
#include "cmJSONState.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPlistParser.h"
#include "cmStringAlgorithms.h"
#include "cmake.h"
namespace {
struct PlistMetadata
{
std::string CFBundlePackageType;
std::string XCFrameworkFormatVersion;
};
auto const PlistMetadataHelper =
cmJSONHelperBuilder::Object<PlistMetadata>{}
.Bind("CFBundlePackageType"_s, &PlistMetadata::CFBundlePackageType,
cmJSONHelperBuilder::String())
.Bind("XCFrameworkFormatVersion"_s,
&PlistMetadata::XCFrameworkFormatVersion,
cmJSONHelperBuilder::String());
bool PlistSupportedPlatformHelper(
cmXcFrameworkPlistSupportedPlatform& platform, const Json::Value* value,
cmJSONState* /*state*/)
{
if (!value) {
return false;
}
if (!value->isString()) {
return false;
}
if (value->asString() == "macos") {
platform = cmXcFrameworkPlistSupportedPlatform::macOS;
return true;
}
if (value->asString() == "ios") {
platform = cmXcFrameworkPlistSupportedPlatform::iOS;
return true;
}
if (value->asString() == "tvos") {
platform = cmXcFrameworkPlistSupportedPlatform::tvOS;
return true;
}
if (value->asString() == "watchos") {
platform = cmXcFrameworkPlistSupportedPlatform::watchOS;
return true;
}
if (value->asString() == "xros") {
platform = cmXcFrameworkPlistSupportedPlatform::visionOS;
return true;
}
return false;
}
auto const PlistLibraryHelper =
cmJSONHelperBuilder::Object<cmXcFrameworkPlistLibrary>{}
.Bind("LibraryIdentifier"_s, &cmXcFrameworkPlistLibrary::LibraryIdentifier,
cmJSONHelperBuilder::String())
.Bind("LibraryPath"_s, &cmXcFrameworkPlistLibrary::LibraryPath,
cmJSONHelperBuilder::String())
.Bind("HeadersPath"_s, &cmXcFrameworkPlistLibrary::HeadersPath,
cmJSONHelperBuilder::String(), false)
.Bind("SupportedArchitectures"_s,
&cmXcFrameworkPlistLibrary::SupportedArchitectures,
cmJSONHelperBuilder::Vector<std::string>(
JsonErrors::EXPECTED_TYPE("array"), cmJSONHelperBuilder::String()))
.Bind("SupportedPlatform"_s, &cmXcFrameworkPlistLibrary::SupportedPlatform,
PlistSupportedPlatformHelper);
auto const PlistHelper =
cmJSONHelperBuilder::Object<cmXcFrameworkPlist>{}.Bind(
"AvailableLibraries"_s, &cmXcFrameworkPlist::AvailableLibraries,
cmJSONHelperBuilder::Vector<cmXcFrameworkPlistLibrary>(
JsonErrors::EXPECTED_TYPE("array"), PlistLibraryHelper));
}
cm::optional<cmXcFrameworkPlist> cmParseXcFrameworkPlist(
const std::string& xcframeworkPath, const cmMakefile& mf,
const cmListFileBacktrace& bt)
{
std::string plistPath = cmStrCat(xcframeworkPath, "/Info.plist");
auto value = cmParsePlist(plistPath);
if (!value) {
mf.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Unable to parse plist file:\n ", plistPath), bt);
return cm::nullopt;
}
cmJSONState state;
PlistMetadata metadata;
if (!PlistMetadataHelper(metadata, &*value, &state)) {
mf.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Invalid xcframework .plist file:\n ", plistPath), bt);
return cm::nullopt;
}
if (metadata.CFBundlePackageType != "XFWK" ||
metadata.XCFrameworkFormatVersion != "1.0") {
mf.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Expected:\n ", plistPath,
"\nto have CFBundlePackageType \"XFWK\" and "
"XCFrameworkFormatVersion \"1.0\""),
bt);
return cm::nullopt;
}
cmXcFrameworkPlist plist;
if (!PlistHelper(plist, &*value, &state)) {
mf.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Invalid xcframework .plist file:\n ", plistPath), bt);
return cm::nullopt;
}
plist.Path = plistPath;
return cm::optional<cmXcFrameworkPlist>(plist);
}
const cmXcFrameworkPlistLibrary* cmXcFrameworkPlist::SelectSuitableLibrary(
const cmMakefile& mf, const cmListFileBacktrace& bt) const
{
auto systemName = mf.GetSafeDefinition("CMAKE_SYSTEM_NAME");
for (auto const& lib : this->AvailableLibraries) {
std::string supportedSystemName;
switch (lib.SupportedPlatform) {
case cmXcFrameworkPlistSupportedPlatform::macOS:
supportedSystemName = "Darwin";
break;
case cmXcFrameworkPlistSupportedPlatform::iOS:
supportedSystemName = "iOS";
break;
case cmXcFrameworkPlistSupportedPlatform::tvOS:
supportedSystemName = "tvOS";
break;
case cmXcFrameworkPlistSupportedPlatform::watchOS:
supportedSystemName = "watchOS";
break;
case cmXcFrameworkPlistSupportedPlatform::visionOS:
supportedSystemName = "visionOS";
break;
}
if (systemName == supportedSystemName) {
return &lib;
}
}
mf.GetCMakeInstance()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Unable to find suitable library in:\n ", this->Path,
"\nfor system name \"", systemName, '"'),
bt);
return nullptr;
}

44
Source/cmXcFramework.h Normal file
View File

@ -0,0 +1,44 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <vector>
#include <cm/optional>
#include "cmListFileCache.h"
class cmMakefile;
enum class cmXcFrameworkPlistSupportedPlatform
{
macOS,
iOS,
tvOS,
watchOS,
visionOS,
};
struct cmXcFrameworkPlistLibrary
{
std::string LibraryIdentifier;
std::string LibraryPath;
std::string HeadersPath;
std::vector<std::string> SupportedArchitectures;
cmXcFrameworkPlistSupportedPlatform SupportedPlatform;
};
struct cmXcFrameworkPlist
{
std::string Path;
std::vector<cmXcFrameworkPlistLibrary> AvailableLibraries;
const cmXcFrameworkPlistLibrary* SelectSuitableLibrary(
const cmMakefile& mf,
const cmListFileBacktrace& bt = cmListFileBacktrace{}) const;
};
cm::optional<cmXcFrameworkPlist> cmParseXcFrameworkPlist(
const std::string& xcframeworkPath, const cmMakefile& mf,
const cmListFileBacktrace& bt = cmListFileBacktrace{});

View File

@ -703,6 +703,23 @@ endif()
if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang"
AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.0)
add_RunCMake_test(Framework)
if(NOT DEFINED CMake_TEST_XcFramework)
set(CMake_TEST_XcFramework ON)
endif()
if(CMake_TEST_XcFramework AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 11.0)
set(XcFramework_ARGS -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
add_RunCMake_test(XcFramework)
# This test can take a very long time due to lots of combinations.
# Use a long default timeout and provide an option to customize it.
if(NOT DEFINED CMake_TEST_XcFramework_TIMEOUT)
set(CMake_TEST_XcFramework_TIMEOUT 3000)
endif()
set_tests_properties(RunCMake.XcFramework PROPERTIES
TIMEOUT "${CMake_TEST_XcFramework_TIMEOUT}"
RUN_SERIAL TRUE
)
endif()
endif()
add_RunCMake_test(File_Archive)

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.26)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@ -0,0 +1,83 @@
include(RunCMake)
function(create_library type platform system_name archs)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/create-${type}-${platform}-build)
run_cmake_with_options(create-${type}-${platform} -DCMAKE_SYSTEM_NAME=${system_name} -DCMAKE_OSX_ARCHITECTURES=${archs} -DCMAKE_INSTALL_PREFIX=${RunCMake_TEST_BINARY_DIR}/install)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(create-${type}-${platform}-build ${CMAKE_COMMAND} --build . --config Release)
run_cmake_command(create-${type}-${platform}-install ${CMAKE_COMMAND} --install . --config Release)
endfunction()
function(create_libraries type)
create_library(${type} macos Darwin "${macos_archs_2}")
create_library(${type} ios iOS "arm64")
create_library(${type} tvos tvOS "arm64")
create_library(${type} watchos watchOS "armv7k\\\\;arm64_32")
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15)
create_library(${type} visionos visionOS "arm64")
endif()
endfunction()
function(create_xcframework name type platforms)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/create-xcframework-${name}-build)
set(args)
foreach(platform IN LISTS platforms)
if(type STREQUAL "framework")
list(APPEND args -framework ${RunCMake_BINARY_DIR}/create-${type}-${platform}-build/install/lib/mylib.framework)
else()
list(APPEND args -library ${RunCMake_BINARY_DIR}/create-${type}-${platform}-build/install/lib/libmylib.a -headers ${RunCMake_SOURCE_DIR}/mylib/include)
endif()
endforeach()
run_cmake_command(create-xcframework-${name} xcodebuild -create-xcframework ${args} -output ${RunCMake_TEST_BINARY_DIR}/mylib.xcframework)
endfunction()
function(create_executable name xcfname system_name archs)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/create-executable-${name}-build)
run_cmake_with_options(create-executable-${name} -DCMAKE_SYSTEM_NAME=${system_name} -DCMAKE_OSX_ARCHITECTURES=${archs} -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-${xcfname}-build/mylib.xcframework)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(create-executable-${name}-build ${CMAKE_COMMAND} --build . --config Release)
endfunction()
function(create_executables name type)
create_executable(${name}-macos ${type} Darwin "${macos_archs_2}")
create_executable(${name}-ios ${type} iOS "arm64")
create_executable(${name}-tvos ${type} tvOS "arm64")
create_executable(${name}-watchos ${type} watchOS "armv7k\\\\;arm64_32")
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15)
create_executable(${name}-visionos ${type} visionOS "arm64")
endif()
endfunction()
set(xcframework_platforms macos ios tvos watchos)
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15)
list(APPEND xcframework_platforms visionos)
endif()
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12)
set(macos_archs_1 "x86_64\\;arm64")
set(macos_archs_2 "x86_64\\\\;arm64")
else()
set(macos_archs_1 "x86_64")
set(macos_archs_2 "x86_64")
endif()
create_libraries(library)
create_libraries(framework)
create_xcframework(library library "${xcframework_platforms}")
create_xcframework(framework framework "${xcframework_platforms}")
create_xcframework(incomplete framework "tvos;watchos")
create_executables(library library)
create_executables(framework framework)
run_cmake_with_options(create-executable-incomplete -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
create_executables(target-library library)
create_executables(target-framework framework)
run_cmake_with_options(create-executable-target-incomplete -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
if(RunCMake_GENERATOR STREQUAL "Xcode" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12)
create_executables(library-link-phase library)
create_executables(framework-link-phase framework)
run_cmake_with_options(create-executable-incomplete-link-phase -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
create_executables(target-library-link-phase library)
create_executables(target-framework-link-phase framework)
run_cmake_with_options(create-executable-target-incomplete-link-phase -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
endif()

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1,11 @@
CMake Error at create-executable\.cmake:[0-9]+ \(target_link_libraries\):
Unable to find suitable library in:
[^
]*/Tests/RunCMake/XcFramework/create-xcframework-incomplete-build/mylib\.xcframework/Info\.plist
for system name "Darwin"
Call Stack \(most recent call first\):
create-executable-link-phase\.cmake:[0-9]+ \(include\)
create-executable-incomplete-link-phase\.cmake:[0-9]+ \(include\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1,10 @@
CMake Error at create-executable\.cmake:[0-9]+ \(target_link_libraries\):
Unable to find suitable library in:
[^
]*/Tests/RunCMake/XcFramework/create-xcframework-incomplete-build/mylib\.xcframework/Info\.plist
for system name "Darwin"
Call Stack \(most recent call first\):
create-executable-incomplete\.cmake:[0-9]+ \(include\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1 @@
include(create-executable.cmake)

View File

@ -0,0 +1,2 @@
include(create-executable.cmake)
set_property(TARGET myexe PROPERTY XCODE_LINK_BUILD_PHASE_MODE "KNOWN_LOCATION")

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1,11 @@
CMake Error at create-executable-target\.cmake:[0-9]+ \(target_link_libraries\):
Unable to find suitable library in:
[^
]*/Tests/RunCMake/XcFramework/create-xcframework-incomplete-build/mylib\.xcframework/Info\.plist
for system name "Darwin"
Call Stack \(most recent call first\):
create-executable-target-link-phase\.cmake:[0-9]+ \(include\)
create-executable-target-incomplete-link-phase\.cmake:[0-9]+ \(include\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1,10 @@
CMake Error at create-executable-target\.cmake:[0-9]+ \(target_link_libraries\):
Unable to find suitable library in:
[^
]*/Tests/RunCMake/XcFramework/create-xcframework-incomplete-build/mylib\.xcframework/Info\.plist
for system name "Darwin"
Call Stack \(most recent call first\):
create-executable-target-incomplete\.cmake:[0-9]+ \(include\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target-link-phase.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1 @@
include(create-executable-target.cmake)

View File

@ -0,0 +1,2 @@
include(create-executable-target.cmake)
set_property(TARGET myexe PROPERTY XCODE_LINK_BUILD_PHASE_MODE "KNOWN_LOCATION")

View File

@ -0,0 +1,21 @@
enable_language(C)
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "tvOS" OR CMAKE_SYSTEM_NAME STREQUAL "watchOS" OR CMAKE_SYSTEM_NAME STREQUAL "visionOS")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
endif()
add_library(mylib IMPORTED STATIC)
set_property(TARGET mylib PROPERTY IMPORTED_LOCATION ${MYLIB_LIBRARY})
add_executable(myexe myexe/myexe.c)
target_link_libraries(myexe PRIVATE mylib)

View File

@ -0,0 +1,18 @@
enable_language(C)
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "tvOS" OR CMAKE_SYSTEM_NAME STREQUAL "watchOS" OR CMAKE_SYSTEM_NAME STREQUAL "visionOS")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
endif()
add_executable(myexe myexe/myexe.c)
target_link_libraries(myexe PRIVATE ${MYLIB_LIBRARY})

View File

@ -0,0 +1 @@
include(create-framework.cmake)

View File

@ -0,0 +1 @@
include(create-framework.cmake)

View File

@ -0,0 +1 @@
include(create-framework.cmake)

View File

@ -0,0 +1 @@
include(create-framework.cmake)

View File

@ -0,0 +1 @@
include(create-framework.cmake)

View File

@ -0,0 +1,3 @@
set(CMAKE_FRAMEWORK ON)
include(create-library-common.cmake)
install(FILES mylib/include/mylib/mylib.h DESTINATION lib/mylib.framework/Headers)

View File

@ -0,0 +1,12 @@
enable_language(C)
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "tvOS" OR CMAKE_SYSTEM_NAME STREQUAL "watchOS" OR CMAKE_SYSTEM_NAME STREQUAL "visionOS")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
endif()
add_library(mylib STATIC mylib/mylib.c)
install(TARGETS mylib DESTINATION lib)

View File

@ -0,0 +1 @@
include(create-library.cmake)

View File

@ -0,0 +1 @@
include(create-library.cmake)

View File

@ -0,0 +1 @@
include(create-library.cmake)

View File

@ -0,0 +1 @@
include(create-library.cmake)

View File

@ -0,0 +1 @@
include(create-library.cmake)

View File

@ -0,0 +1 @@
include(create-library-common.cmake)

View File

@ -0,0 +1,7 @@
#include <mylib/mylib.h>
int main(void)
{
mylib();
return 0;
}

View File

@ -0,0 +1,3 @@
#pragma once
extern void mylib(void);

View File

@ -0,0 +1,3 @@
void mylib(void)
{
}

View File

@ -451,6 +451,7 @@ CMAKE_CXX_SOURCES="\
cmGccDepfileReader \
cmReturnCommand \
cmPlaceholderExpander \
cmPlistParser \
cmRulePlaceholderExpander \
cmRuntimeDependencyArchive \
cmScriptGenerator \
@ -500,6 +501,7 @@ CMAKE_CXX_SOURCES="\
cmWhileCommand \
cmWindowsRegistry \
cmWorkingDirectory \
cmXcFramework \
cmake \
cmakemain \
cmcmd \