diff --git a/Help/prop_tgt/XCODE_EMBED_type.rst b/Help/prop_tgt/XCODE_EMBED_type.rst index 0354f978c4..963baa8afc 100644 --- a/Help/prop_tgt/XCODE_EMBED_type.rst +++ b/Help/prop_tgt/XCODE_EMBED_type.rst @@ -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_`` may use + :manual:`generator expressions `. + 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__PATH`, :prop_tgt:`XCODE_EMBED__REMOVE_HEADERS_ON_COPY` and :prop_tgt:`XCODE_EMBED__CODE_SIGN_ON_COPY`. diff --git a/Help/release/dev/xcode-embed-genex.rst b/Help/release/dev/xcode-embed-genex.rst new file mode 100644 index 0000000000..629c658937 --- /dev/null +++ b/Help/release/dev/xcode-embed-genex.rst @@ -0,0 +1,8 @@ +xcode-embed-genex +----------------- + +* The :prop_tgt:`XCODE_EMBED_` target property now supports + :manual:`generator expressions `. + Because Xcode shares one copy-files build phase across all configurations, + an expression whose result depends on the configuration is rejected with + an error. diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 8241fbc67e..78d9cb1045 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -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; diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksGenex-check.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksGenex-check.cmake new file mode 100644 index 0000000000..78ab3dd91b --- /dev/null +++ b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksGenex-check.cmake @@ -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() diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksGenex.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksGenex.cmake new file mode 100644 index 0000000000..7f41f6d2ed --- /dev/null +++ b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksGenex.cmake @@ -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>" +) diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig-result.txt b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig-stderr.txt b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig-stderr.txt new file mode 100644 index 0000000000..a7eabf0222 --- /dev/null +++ b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig-stderr.txt @@ -0,0 +1,10 @@ +^CMake Error in CMakeLists\.txt: + Xcode does not support per-config XCODE_EMBED_FRAMEWORKS: + + \$<\$:foo\.framework> + + specified for target: + + app ++ +CMake Generate step failed\. Build files cannot be regenerated correctly\.$ diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig.cmake new file mode 100644 index 0000000000..689b68a351 --- /dev/null +++ b/Tests/RunCMake/XcodeProject-Embed/EmbedFrameworksPerConfig.cmake @@ -0,0 +1,5 @@ +add_executable(app MACOSX_BUNDLE main.m) + +set_target_properties(app PROPERTIES + XCODE_EMBED_FRAMEWORKS "$<$:foo.framework>" +) diff --git a/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake b/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake index 70b74b87ad..1a5f0c2947 100644 --- a/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake +++ b/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake @@ -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)