Xcode: Honor generator expressions in XCODE_EMBED_<type>

Evaluate the XCODE_EMBED_<type> target property through the generator
expression evaluator before building the copy-files phase.  Xcode shares
one copy-files build phase across all configurations, so the embedded set
cannot vary by configuration.  Reject any expression whose result depends
on the configuration, matching the existing per-source diagnostic.

Fixes #28082
This commit is contained in:
Tor Arne Vestbø
2026-09-11 16:45:25 +02:00
parent 790aebb209
commit 90c03329dd
9 changed files with 97 additions and 8 deletions
+8
View File
@@ -65,6 +65,14 @@ at least one of the following:
directory and no other :command:`project` calls between themselves and that
common :command:`project` call.
.. versionadded:: 4.5
Contents of ``XCODE_EMBED_<type>`` may use
:manual:`generator expressions <cmake-generator-expressions(7)>`.
Xcode shares one copy-files build phase across all configurations, so the
embedded set cannot vary by configuration. A generator expression whose
result depends on the configuration is rejected with an error.
See also :prop_tgt:`XCODE_EMBED_<type>_PATH`,
:prop_tgt:`XCODE_EMBED_<type>_REMOVE_HEADERS_ON_COPY` and
:prop_tgt:`XCODE_EMBED_<type>_CODE_SIGN_ON_COPY`.
+8
View File
@@ -0,0 +1,8 @@
xcode-embed-genex
-----------------
* The :prop_tgt:`XCODE_EMBED_<type>` target property now supports
:manual:`generator expressions <cmake-generator-expressions(7)>`.
Because Xcode shares one copy-files build phase across all configurations,
an expression whose result depends on the configuration is rejected with
an error.
+35 -8
View File
@@ -1037,6 +1037,14 @@ public:
{
}
XCodeGeneratorExpressionInterpreter(cmLocalGenerator* localGenerator,
cmGeneratorTarget* headTarget)
: cmGeneratorExpressionInterpreter(
localGenerator, "NO-PER-CONFIG-SUPPORT-IN-XCODE", headTarget)
, Target(headTarget)
{
}
XCodeGeneratorExpressionInterpreter(
XCodeGeneratorExpressionInterpreter const&) = delete;
XCodeGeneratorExpressionInterpreter& operator=(
@@ -1055,13 +1063,23 @@ public:
this->cmGeneratorExpressionInterpreter::Evaluate(expression, property);
if (this->CompiledGeneratorExpression->GetHadContextSensitiveCondition()) {
std::ostringstream e;
/* clang-format off */
e <<
"Xcode does not support per-config per-source " << property << ":\n"
" " << expression << "\n"
"specified for source:\n"
" " << this->SourceFile->ResolveFullPath() << '\n';
/* clang-format on */
if (this->SourceFile) {
/* clang-format off */
e <<
"Xcode does not support per-config per-source " << property << ":\n"
" " << expression << "\n"
"specified for source:\n"
" " << this->SourceFile->ResolveFullPath() << '\n';
/* clang-format on */
} else {
/* clang-format off */
e <<
"Xcode does not support per-config " << property << ":\n"
" " << expression << "\n"
"specified for target:\n"
" " << this->Target->GetName() << '\n';
/* clang-format on */
}
this->LocalGenerator->IssueMessage(MessageType::FATAL_ERROR, e.str());
}
@@ -1070,6 +1088,7 @@ public:
private:
cmSourceFile* SourceFile = nullptr;
cmGeneratorTarget* Target = nullptr;
};
cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile(
@@ -4396,6 +4415,14 @@ void cmGlobalXCodeGenerator::AddEmbeddedObjects(
return;
}
// Xcode shares one copy-files build phase across all configurations, so the
// embedded set cannot vary by config. Evaluate the property and reject any
// generator expression whose result depends on the configuration.
XCodeGeneratorExpressionInterpreter genexInterpreter(gt->GetLocalGenerator(),
gt);
std::string const& evaluatedFiles =
genexInterpreter.Evaluate(*files, embedPropertyName);
// Create an "Embedded Frameworks" build phase
auto* copyFilesBuildPhase =
this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase);
@@ -4418,7 +4445,7 @@ void cmGlobalXCodeGenerator::AddEmbeddedObjects(
this->CreateString("0"));
cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
// Collect all embedded frameworks and dylibs and add them to build phase
cmList relFiles{ *files };
cmList relFiles{ evaluatedFiles };
for (std::string const& relFile : relFiles) {
cmXCodeObject* buildFile{ nullptr };
std::string filePath = relFile;
@@ -0,0 +1,14 @@
# The generator expression must resolve to the "embedded" framework target so
# that an "Embed Frameworks" copy-files phase is created. Were the expression
# left unevaluated, target lookup would fail and no such phase would exist.
execute_process(
COMMAND grep -c "Embed Frameworks"
${RunCMake_TEST_BINARY_DIR}/${test}.xcodeproj/project.pbxproj
OUTPUT_VARIABLE actualCount
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT actualCount MATCHES "^[0-9]+$" OR actualCount EQUAL 0)
set(RunCMake_TEST_FAILED
"no Embed Frameworks phase in project; generator expression was not honored")
endif()
@@ -0,0 +1,9 @@
# A generator expression whose result does not depend on the configuration is
# honored. Here it resolves to the name of a framework target to embed.
add_library(embedded SHARED func.m)
set_target_properties(embedded PROPERTIES FRAMEWORK TRUE)
add_executable(app MACOSX_BUNDLE main.m)
set_target_properties(app PROPERTIES
XCODE_EMBED_FRAMEWORKS "$<1:embedded>"
)
@@ -0,0 +1,10 @@
^CMake Error in CMakeLists\.txt:
Xcode does not support per-config XCODE_EMBED_FRAMEWORKS:
\$<\$<CONFIG:Debug>:foo\.framework>
specified for target:
app
+
CMake Generate step failed\. Build files cannot be regenerated correctly\.$
@@ -0,0 +1,5 @@
add_executable(app MACOSX_BUNDLE main.m)
set_target_properties(app PROPERTIES
XCODE_EMBED_FRAMEWORKS "$<$<CONFIG:Debug>:foo.framework>"
)
@@ -21,6 +21,13 @@ function(ExternalDependencies)
endfunction()
ExternalDependencies()
# A generator expression that does not depend on the configuration is honored.
run_cmake(EmbedFrameworksGenex)
# A generator expression whose result depends on the configuration cannot be
# honored, because Xcode shares one copy-files build phase across all configs.
run_cmake(EmbedFrameworksPerConfig)
function(TestFlagsOn testName dependencyName)
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${testName}-${dependencyName}-build)