mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-14 02:08:27 +08:00
Xcode: add support for embedding frameworks
This commit also prepares for embedding things other than frameworks. In the future, we may want to embed resources and other types supported by Xcode, so the target properties have been documented in a way that clearly signals the future intent.
This commit is contained in:

committed by
Craig Scott

parent
70f11b9d3c
commit
5651901c54
@@ -396,6 +396,10 @@ Properties on Targets
|
||||
/prop_tgt/WIN32_EXECUTABLE
|
||||
/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS
|
||||
/prop_tgt/XCODE_ATTRIBUTE_an-attribute
|
||||
/prop_tgt/XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY
|
||||
/prop_tgt/XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY
|
||||
/prop_tgt/XCODE_EMBED_type
|
||||
/prop_tgt/XCODE_EMBED_type_PATH
|
||||
/prop_tgt/XCODE_EXPLICIT_FILE_TYPE
|
||||
/prop_tgt/XCODE_GENERATE_SCHEME
|
||||
/prop_tgt/XCODE_LINK_BUILD_PHASE_MODE
|
||||
|
@@ -0,0 +1,8 @@
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY
|
||||
----------------------------------------
|
||||
|
||||
.. versionadded:: 3.20
|
||||
|
||||
Tell the :generator:`Xcode` generator to perform code signing for all the
|
||||
frameworks and libraries that are embedded using the
|
||||
:prop_tgt:`XCODE_EMBED_FRAMEWORKS <XCODE_EMBED_<type>>` property.
|
@@ -0,0 +1,8 @@
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY
|
||||
---------------------------------------------
|
||||
|
||||
.. versionadded:: 3.20
|
||||
|
||||
Tell the :generator:`Xcode` generator to remove headers from all the
|
||||
frameworks that are embedded using the
|
||||
:prop_tgt:`XCODE_EMBED_FRAMEWORKS <XCODE_EMBED_<type>>` property.
|
14
Help/prop_tgt/XCODE_EMBED_type.rst
Normal file
14
Help/prop_tgt/XCODE_EMBED_type.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
XCODE_EMBED_<type>
|
||||
------------------
|
||||
|
||||
.. versionadded:: 3.20
|
||||
|
||||
Tell the :generator:`Xcode` generator to embed the specified list of items into
|
||||
the target bundle. ``<type>`` specifies the embed build phase to use.
|
||||
|
||||
Currently, the only supported value for ``<type>`` is ``FRAMEWORKS``.
|
||||
The specified items will be added to the ``Embed Frameworks`` build phase.
|
||||
The items can be CMake target names or paths to frameworks or libraries.
|
||||
See also :prop_tgt:`XCODE_EMBED_<type>_PATH`,
|
||||
:prop_tgt:`XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY` and
|
||||
:prop_tgt:`XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY`.
|
9
Help/prop_tgt/XCODE_EMBED_type_PATH.rst
Normal file
9
Help/prop_tgt/XCODE_EMBED_type_PATH.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
XCODE_EMBED_<type>_PATH
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 3.20
|
||||
|
||||
Tell the :generator:`Xcode` generator the relative path to use when embedding
|
||||
the items specified by :prop_tgt:`XCODE_EMBED_<type>`. The path is relative
|
||||
to the base location of the ``Embed XXX`` build phase associated with
|
||||
``<type>``.
|
@@ -773,7 +773,9 @@ void cmGlobalXCodeGenerator::ClearXCodeObjects()
|
||||
this->TargetGroup.clear();
|
||||
this->FileRefs.clear();
|
||||
this->ExternalLibRefs.clear();
|
||||
this->EmbeddedLibRefs.clear();
|
||||
this->FileRefToBuildFileMap.clear();
|
||||
this->FileRefToEmbedBuildFileMap.clear();
|
||||
this->CommandsVisited.clear();
|
||||
}
|
||||
|
||||
@@ -1197,7 +1199,7 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath(
|
||||
}
|
||||
}
|
||||
// Make a copy so that we can override it later
|
||||
std::string path = fullpath;
|
||||
std::string path = cmSystemTools::CollapseFullPath(fullpath);
|
||||
// Compute the extension without leading '.'.
|
||||
std::string ext = cmSystemTools::GetFilenameLastExtension(path);
|
||||
if (!ext.empty()) {
|
||||
@@ -1793,6 +1795,10 @@ void cmGlobalXCodeGenerator::CreateCustomCommands(
|
||||
if (frameworkBuildPhase) {
|
||||
buildPhases->AddObject(frameworkBuildPhase);
|
||||
}
|
||||
|
||||
// When this build phase is present, it must be last. More build phases may
|
||||
// be added later for embedding things and they will insert themselves just
|
||||
// before this last build phase.
|
||||
if (postBuildPhase) {
|
||||
buildPhases->AddObject(postBuildPhase);
|
||||
}
|
||||
@@ -3632,6 +3638,130 @@ void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
|
||||
}
|
||||
}
|
||||
|
||||
void cmGlobalXCodeGenerator::AddEmbeddedFrameworks(cmXCodeObject* target)
|
||||
{
|
||||
cmGeneratorTarget* gt = target->GetTarget();
|
||||
if (!gt) {
|
||||
cmSystemTools::Error("Error no target on xobject\n");
|
||||
return;
|
||||
}
|
||||
if (!gt->IsInBuildSystem()) {
|
||||
return;
|
||||
}
|
||||
bool isFrameworkTarget = gt->IsFrameworkOnApple();
|
||||
bool isBundleTarget = gt->GetPropertyAsBool("MACOSX_BUNDLE");
|
||||
bool isCFBundleTarget = gt->IsCFBundleOnApple();
|
||||
if (!(isFrameworkTarget || isBundleTarget || isCFBundleTarget)) {
|
||||
return;
|
||||
}
|
||||
cmProp files = gt->GetProperty("XCODE_EMBED_FRAMEWORKS");
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an "Embedded Frameworks" build phase
|
||||
auto* copyFilesBuildPhase =
|
||||
this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase);
|
||||
std::string copyFilesBuildPhaseName = "Embed Frameworks";
|
||||
std::string destinationFrameworks = "10";
|
||||
copyFilesBuildPhase->SetComment(copyFilesBuildPhaseName);
|
||||
copyFilesBuildPhase->AddAttribute("buildActionMask",
|
||||
this->CreateString("2147483647"));
|
||||
copyFilesBuildPhase->AddAttribute("dstSubfolderSpec",
|
||||
this->CreateString(destinationFrameworks));
|
||||
copyFilesBuildPhase->AddAttribute(
|
||||
"name", this->CreateString(copyFilesBuildPhaseName));
|
||||
if (cmProp fwEmbedPath = gt->GetProperty("XCODE_EMBED_FRAMEWORKS_PATH")) {
|
||||
copyFilesBuildPhase->AddAttribute("dstPath",
|
||||
this->CreateString(*fwEmbedPath));
|
||||
} else {
|
||||
copyFilesBuildPhase->AddAttribute("dstPath", this->CreateString(""));
|
||||
}
|
||||
copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing",
|
||||
this->CreateString("0"));
|
||||
cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
|
||||
// Collect all embedded frameworks and add them to build phase
|
||||
std::vector<std::string> relFiles = cmExpandedList(*files);
|
||||
for (std::string const& relFile : relFiles) {
|
||||
cmXCodeObject* buildFile{ nullptr };
|
||||
std::string filePath = relFile;
|
||||
auto* genTarget = FindGeneratorTarget(relFile);
|
||||
if (genTarget) {
|
||||
// This is a target - get it's product path reference
|
||||
auto* xcTarget = FindXCodeTarget(genTarget);
|
||||
if (!xcTarget) {
|
||||
cmSystemTools::Error("Can not find a target for " +
|
||||
genTarget->GetName());
|
||||
continue;
|
||||
}
|
||||
// Add the target output file as a build reference for other targets
|
||||
// to link against
|
||||
auto* fileRefObject = xcTarget->GetAttribute("productReference");
|
||||
if (!fileRefObject) {
|
||||
cmSystemTools::Error("Target " + genTarget->GetName() +
|
||||
" is missing product reference");
|
||||
continue;
|
||||
}
|
||||
auto it = FileRefToEmbedBuildFileMap.find(fileRefObject);
|
||||
if (it == FileRefToEmbedBuildFileMap.end()) {
|
||||
buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
|
||||
buildFile->AddAttribute("fileRef", fileRefObject);
|
||||
FileRefToEmbedBuildFileMap[fileRefObject] = buildFile;
|
||||
} else {
|
||||
buildFile = it->second;
|
||||
}
|
||||
} else if (cmSystemTools::IsPathToFramework(relFile)) {
|
||||
// This is a regular string path - create file reference
|
||||
auto it = EmbeddedLibRefs.find(relFile);
|
||||
if (it == EmbeddedLibRefs.end()) {
|
||||
cmXCodeObject* fileRef =
|
||||
this->CreateXCodeFileReferenceFromPath(relFile, gt, "", nullptr);
|
||||
if (fileRef) {
|
||||
buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
|
||||
buildFile->SetComment(fileRef->GetComment());
|
||||
buildFile->AddAttribute("fileRef",
|
||||
this->CreateObjectReference(fileRef));
|
||||
}
|
||||
if (!buildFile) {
|
||||
cmSystemTools::Error("Can't create build file for " + relFile);
|
||||
continue;
|
||||
}
|
||||
this->EmbeddedLibRefs.emplace(filePath, buildFile);
|
||||
} else {
|
||||
buildFile = it->second;
|
||||
}
|
||||
}
|
||||
if (!buildFile) {
|
||||
cmSystemTools::Error("Can't find a build file for " + relFile);
|
||||
continue;
|
||||
}
|
||||
// Set build file configuration
|
||||
cmXCodeObject* settings =
|
||||
this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
|
||||
cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);
|
||||
const auto& rmHeadersProp =
|
||||
gt->GetSafeProperty("XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY");
|
||||
if (cmIsOn(rmHeadersProp)) {
|
||||
attrs->AddObject(this->CreateString("RemoveHeadersOnCopy"));
|
||||
}
|
||||
const auto& codeSignProp =
|
||||
gt->GetSafeProperty("XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY");
|
||||
if (cmIsOn(codeSignProp)) {
|
||||
attrs->AddObject(this->CreateString("CodeSignOnCopy"));
|
||||
}
|
||||
settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs);
|
||||
buildFile->AddAttributeIfNotEmpty("settings", settings);
|
||||
if (!buildFiles->HasObject(buildFile)) {
|
||||
buildFiles->AddObject(buildFile);
|
||||
}
|
||||
}
|
||||
copyFilesBuildPhase->AddAttribute("files", buildFiles);
|
||||
auto* buildPhases = target->GetAttribute("buildPhases");
|
||||
// Insert embed build phase right before the post-build command
|
||||
buildPhases->InsertObject(buildPhases->GetObjectCount() - 1,
|
||||
copyFilesBuildPhase);
|
||||
}
|
||||
|
||||
bool cmGlobalXCodeGenerator::CreateGroups(
|
||||
std::vector<cmLocalGenerator*>& generators)
|
||||
{
|
||||
@@ -4010,7 +4140,9 @@ bool cmGlobalXCodeGenerator::CreateXCodeObjects(
|
||||
// loop over all targets and add link and depend info
|
||||
for (auto t : targets) {
|
||||
this->AddDependAndLinkInformation(t);
|
||||
this->AddEmbeddedFrameworks(t);
|
||||
}
|
||||
|
||||
if (this->XcodeBuildSystem == BuildSystem::One) {
|
||||
this->CreateXCodeDependHackMakefile(targets);
|
||||
}
|
||||
|
@@ -187,6 +187,7 @@ private:
|
||||
const std::string& configName);
|
||||
cmXCodeObject* CreateUtilityTarget(cmGeneratorTarget* gtgt);
|
||||
void AddDependAndLinkInformation(cmXCodeObject* target);
|
||||
void AddEmbeddedFrameworks(cmXCodeObject* target);
|
||||
void AddPositionIndependentLinkAttribute(cmGeneratorTarget* target,
|
||||
cmXCodeObject* buildSettings,
|
||||
const std::string& configName);
|
||||
@@ -324,8 +325,10 @@ private:
|
||||
std::map<std::string, cmXCodeObject*> TargetGroup;
|
||||
std::map<std::string, cmXCodeObject*> FileRefs;
|
||||
std::map<std::string, cmXCodeObject*> ExternalLibRefs;
|
||||
std::map<std::string, cmXCodeObject*> EmbeddedLibRefs;
|
||||
std::map<cmGeneratorTarget const*, cmXCodeObject*> XCodeObjectMap;
|
||||
std::map<cmXCodeObject*, cmXCodeObject*> FileRefToBuildFileMap;
|
||||
std::map<cmXCodeObject*, cmXCodeObject*> FileRefToEmbedBuildFileMap;
|
||||
std::vector<std::string> Architectures;
|
||||
std::string ObjectDirArchDefault;
|
||||
std::string ObjectDirArch;
|
||||
|
@@ -81,6 +81,13 @@ public:
|
||||
void SetObject(cmXCodeObject* value) { this->Object = value; }
|
||||
cmXCodeObject* GetObject() { return this->Object; }
|
||||
void AddObject(cmXCodeObject* value) { this->List.push_back(value); }
|
||||
size_t GetObjectCount() { return this->List.size(); }
|
||||
void InsertObject(size_t position, cmXCodeObject* value)
|
||||
{
|
||||
if (position < GetObjectCount()) {
|
||||
this->List.insert(this->List.begin() + position, value);
|
||||
}
|
||||
}
|
||||
void PrependObject(cmXCodeObject* value)
|
||||
{
|
||||
this->List.insert(this->List.begin(), value);
|
||||
|
@@ -513,6 +513,7 @@ endif()
|
||||
|
||||
if(XCODE_VERSION)
|
||||
add_RunCMake_test(XcodeProject -DXCODE_VERSION=${XCODE_VERSION})
|
||||
add_RunCMake_test(XcodeProject-Embed)
|
||||
|
||||
# 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.
|
||||
|
3
Tests/RunCMake/XcodeProject-Embed/CMakeLists.txt
Normal file
3
Tests/RunCMake/XcodeProject-Embed/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(${RunCMake_TEST} LANGUAGES C)
|
||||
include(${RunCMake_TEST}.cmake)
|
@@ -0,0 +1,14 @@
|
||||
function(findAttribute project attr)
|
||||
execute_process(
|
||||
COMMAND grep ${attr} ${RunCMake_TEST_BINARY_DIR}/${project}.xcodeproj/project.pbxproj
|
||||
OUTPUT_VARIABLE output_var
|
||||
RESULT_VARIABLE result_var
|
||||
)
|
||||
|
||||
if(NOT result_var)
|
||||
set(RunCMake_TEST_FAILED "${attr} attribute is set" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
findAttribute(${test} "RemoveHeadersOnCopy")
|
||||
findAttribute(${test} "CodeSignOnCopy")
|
@@ -0,0 +1,7 @@
|
||||
add_executable(app MACOSX_BUNDLE main.m)
|
||||
|
||||
set_target_properties(app PROPERTIES
|
||||
XCODE_EMBED_FRAMEWORKS "${EXTERNAL_FWK}"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY OFF
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY OFF
|
||||
)
|
@@ -0,0 +1,3 @@
|
||||
if(NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/Debug/app.app/Contents/Frameworks/sharedFrameworkExt.framework)
|
||||
set(RunCMake_TEST_FAILED "Framework was not embedded at the expected location")
|
||||
endif()
|
@@ -0,0 +1,14 @@
|
||||
function(findAttribute project attr)
|
||||
execute_process(
|
||||
COMMAND grep ${attr} ${RunCMake_TEST_BINARY_DIR}/${project}.xcodeproj/project.pbxproj
|
||||
OUTPUT_VARIABLE output_var
|
||||
RESULT_VARIABLE result_var
|
||||
)
|
||||
|
||||
if(result_var)
|
||||
set(RunCMake_TEST_FAILED "${attr} attribute not set" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
findAttribute(${test} "RemoveHeadersOnCopy")
|
||||
findAttribute(${test} "CodeSignOnCopy")
|
@@ -0,0 +1,7 @@
|
||||
add_executable(app MACOSX_BUNDLE main.m)
|
||||
|
||||
set_target_properties(app PROPERTIES
|
||||
XCODE_EMBED_FRAMEWORKS "${EXTERNAL_FWK}"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY ON
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
|
||||
)
|
@@ -0,0 +1,3 @@
|
||||
if(NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/Debug/app.app/Contents/Frameworks/subdir/sharedFrameworkExt.framework)
|
||||
set(RunCMake_TEST_FAILED "Framework was not embedded at the expected location")
|
||||
endif()
|
@@ -0,0 +1,14 @@
|
||||
function(findAttribute project attr)
|
||||
execute_process(
|
||||
COMMAND grep ${attr} ${RunCMake_TEST_BINARY_DIR}/${project}.xcodeproj/project.pbxproj
|
||||
OUTPUT_VARIABLE output_var
|
||||
RESULT_VARIABLE result_var
|
||||
)
|
||||
|
||||
if(result_var)
|
||||
set(RunCMake_TEST_FAILED "${attr} attribute not set" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
findAttribute(${test} "RemoveHeadersOnCopy")
|
||||
findAttribute(${test} "CodeSignOnCopy")
|
@@ -0,0 +1,8 @@
|
||||
add_executable(app MACOSX_BUNDLE main.m)
|
||||
|
||||
set_target_properties(app PROPERTIES
|
||||
XCODE_EMBED_FRAMEWORKS "${EXTERNAL_FWK}"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY ON
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
|
||||
XCODE_EMBED_FRAMEWORKS_PATH "subdir"
|
||||
)
|
@@ -0,0 +1,2 @@
|
||||
add_library(sharedFrameworkExt SHARED func.m)
|
||||
set_target_properties(sharedFrameworkExt PROPERTIES FRAMEWORK TRUE)
|
43
Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake
Normal file
43
Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake
Normal file
@@ -0,0 +1,43 @@
|
||||
include(RunCMake)
|
||||
|
||||
# Build a framework that the other tests will use and treat as external.
|
||||
# Always build in the Debug configuration so that the path to the framework
|
||||
# is predictable.
|
||||
function(ExternalFramework)
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ExternalFramework-build)
|
||||
set(externalFramework ${RunCMake_TEST_BINARY_DIR}/Debug/sharedFrameworkExt.framework PARENT_SCOPE)
|
||||
|
||||
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
|
||||
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
|
||||
|
||||
run_cmake(ExternalFramework)
|
||||
run_cmake_command(ExternalFramework-build
|
||||
${CMAKE_COMMAND} --build ${RunCMake_TEST_BINARY_DIR}
|
||||
--config Debug
|
||||
--target sharedFrameworkExt
|
||||
)
|
||||
endfunction()
|
||||
ExternalFramework()
|
||||
|
||||
set(RunCMake_TEST_OPTIONS -DEXTERNAL_FWK=${externalFramework})
|
||||
|
||||
run_cmake(EmbedFrameworksFlagsOff)
|
||||
|
||||
function(TestFlagsOn testName)
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${testName}-build)
|
||||
|
||||
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
|
||||
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
|
||||
|
||||
run_cmake(${testName})
|
||||
run_cmake_command(${testName}-build
|
||||
${CMAKE_COMMAND} --build ${RunCMake_TEST_BINARY_DIR}
|
||||
--config Debug
|
||||
--target app
|
||||
)
|
||||
endfunction()
|
||||
|
||||
TestFlagsOn(EmbedFrameworksFlagsOnNoSubdir)
|
||||
TestFlagsOn(EmbedFrameworksFlagsOnWithSubdir)
|
6
Tests/RunCMake/XcodeProject-Embed/func.m
Normal file
6
Tests/RunCMake/XcodeProject-Embed/func.m
Normal file
@@ -0,0 +1,6 @@
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
int func()
|
||||
{
|
||||
return 1;
|
||||
}
|
6
Tests/RunCMake/XcodeProject-Embed/main.m
Normal file
6
Tests/RunCMake/XcodeProject-Embed/main.m
Normal file
@@ -0,0 +1,6 @@
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user