Xcode: Add embed resources support
diff --git a/Help/prop_tgt/XCODE_EMBED_type.rst b/Help/prop_tgt/XCODE_EMBED_type.rst
index da744c2..76b0e3d 100644
--- a/Help/prop_tgt/XCODE_EMBED_type.rst
+++ b/Help/prop_tgt/XCODE_EMBED_type.rst
@@ -37,6 +37,12 @@
   The specified items will be added to the ``Embed PlugIns`` build phase.
   They must be CMake target names.
 
+``RESOURCES``
+  .. versionadded:: 3.28
+
+  The specified items will be added to the ``Embed Resources`` build phase.
+  They must be CMake target names.
+
 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`.
diff --git a/Help/prop_tgt/XCODE_EMBED_type_PATH.rst b/Help/prop_tgt/XCODE_EMBED_type_PATH.rst
index 5a5c65f..ef04d14 100644
--- a/Help/prop_tgt/XCODE_EMBED_type_PATH.rst
+++ b/Help/prop_tgt/XCODE_EMBED_type_PATH.rst
@@ -22,3 +22,6 @@
 
 ``PLUGINS``
   .. versionadded:: 3.23
+
+``RESOURCES``
+  .. versionadded:: 3.28
diff --git a/Help/release/dev/xcode-embed-resources.rst b/Help/release/dev/xcode-embed-resources.rst
new file mode 100644
index 0000000..2678cfd
--- /dev/null
+++ b/Help/release/dev/xcode-embed-resources.rst
@@ -0,0 +1,6 @@
+xcode-embed-resources
+---------------------
+
+* The :prop_tgt:`XCODE_EMBED_RESOURCES <XCODE_EMBED_<type>>` target property
+  was added to tell the :generator:`Xcode` generator what targets to put in
+  the ``Embed Resources`` build phase.
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 0472631..7691bff 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -1267,10 +1267,16 @@
   fileRef->AddAttribute("sourceTree", this->CreateString(sourceTree));
 
   cmXCodeObject* group = this->GroupMap[key];
-  if (!group && IsLibraryType(fileType)) {
-    group = this->FrameworkGroup;
-    this->GroupMap[key] = group;
+  if (!group) {
+    if (IsLibraryType(fileType)) {
+      group = this->FrameworkGroup;
+    } else if (fileType == "folder") {
+      group = this->ResourcesGroup;
+    }
+    if (group)
+      this->GroupMap[key] = group;
   }
+
   if (!group) {
     cmSystemTools::Error("Could not find a PBX group for " + key);
     return nullptr;
@@ -4056,7 +4062,8 @@
         buildFile = it->second;
       }
     } else if (cmSystemTools::IsPathToFramework(relFile) ||
-               cmSystemTools::IsPathToMacOSSharedLibrary(relFile)) {
+               cmSystemTools::IsPathToMacOSSharedLibrary(relFile) ||
+               cmSystemTools::FileIsDirectory(filePath)) {
       // This is a regular string path - create file reference
       auto it = this->EmbeddedLibRefs.find(relFile);
       if (it == this->EmbeddedLibRefs.end()) {
@@ -4157,6 +4164,15 @@
                            "$(EXTENSIONS_FOLDER_PATH)");
 }
 
+void cmGlobalXCodeGenerator::AddEmbeddedResources(cmXCodeObject* target)
+{
+  static const auto dstSubfolderSpec = "7";
+
+  this->AddEmbeddedObjects(target, "Embed Resources",
+                           "XCODE_EMBED_RESOURCES_PATH", dstSubfolderSpec,
+                           NoActionOnCopyByDefault);
+}
+
 bool cmGlobalXCodeGenerator::CreateGroups(
   std::vector<cmLocalGenerator*>& generators)
 {
@@ -4358,6 +4374,15 @@
   this->FrameworkGroup->AddAttribute("children", frameworkGroupChildren);
   this->MainGroupChildren->AddObject(this->FrameworkGroup);
 
+  this->ResourcesGroup = this->CreateObject(cmXCodeObject::PBXGroup);
+  this->ResourcesGroup->AddAttribute("name", this->CreateString("Resources"));
+  this->ResourcesGroup->AddAttribute("sourceTree",
+                                     this->CreateString("<group>"));
+  cmXCodeObject* ResourcesGroupChildren =
+    this->CreateObject(cmXCodeObject::OBJECT_LIST);
+  this->ResourcesGroup->AddAttribute("children", ResourcesGroupChildren);
+  this->MainGroupChildren->AddObject(this->ResourcesGroup);
+
   this->RootObject = this->CreateObject(cmXCodeObject::PBXProject);
   this->RootObject->SetComment("Project object");
 
@@ -4549,6 +4574,7 @@
     this->AddEmbeddedPlugIns(t);
     this->AddEmbeddedAppExtensions(t);
     this->AddEmbeddedExtensionKitExtensions(t);
+    this->AddEmbeddedResources(t);
     // Inherit project-wide values for any target-specific search paths.
     this->InheritBuildSettingAttribute(t, "HEADER_SEARCH_PATHS");
     this->InheritBuildSettingAttribute(t, "SYSTEM_HEADER_SEARCH_PATHS");
diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h
index 1fdd189..9fff173 100644
--- a/Source/cmGlobalXCodeGenerator.h
+++ b/Source/cmGlobalXCodeGenerator.h
@@ -224,6 +224,7 @@
   void AddEmbeddedPlugIns(cmXCodeObject* target);
   void AddEmbeddedAppExtensions(cmXCodeObject* target);
   void AddEmbeddedExtensionKitExtensions(cmXCodeObject* target);
+  void AddEmbeddedResources(cmXCodeObject* target);
   void AddPositionIndependentLinkAttribute(cmGeneratorTarget* target,
                                            cmXCodeObject* buildSettings,
                                            const std::string& configName);
@@ -355,6 +356,7 @@
                                   std::string const& configName);
   cmXCodeObject* MainGroupChildren;
   cmXCodeObject* FrameworkGroup;
+  cmXCodeObject* ResourcesGroup;
   cmMakefile* CurrentMakefile;
   cmLocalGenerator* CurrentLocalGenerator;
   cmLocalGenerator* CurrentRootGenerator = nullptr;
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns-macOS-check.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns-macOS-check.cmake
index 576be11..66db44e 100644
--- a/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns-macOS-check.cmake
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns-macOS-check.cmake
@@ -1,4 +1,3 @@
 include(${CMAKE_CURRENT_LIST_DIR}/findAttribute.cmake)
 
-findAttribute(${test} "RemoveHeadersOnCopy" TRUE)
 findAttribute(${test} "CodeSignOnCopy" FALSE)
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns.cmake
index 1bd1bd0..f5c9364 100644
--- a/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns.cmake
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedPlugIns.cmake
@@ -1,4 +1,4 @@
-add_executable(plug_in MACOS_BUNDLE Empty.txt)
+add_executable(plug_in MACOSX_BUNDLE Empty.txt)
 set_target_properties(plug_in PROPERTIES
   LINKER_LANGUAGE CXX
   XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedResources-iOS-check.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-iOS-check.cmake
new file mode 100644
index 0000000..75aaa91
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-iOS-check.cmake
@@ -0,0 +1,3 @@
+include(${CMAKE_CURRENT_LIST_DIR}/findAttribute.cmake)
+
+findAttribute(${test} "Embed Resources" TRUE)
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedResources-iOS.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-iOS.cmake
new file mode 100644
index 0000000..54f9fc8
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-iOS.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/EmbedResources.cmake)
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedResources-macOS-check.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-macOS-check.cmake
new file mode 100644
index 0000000..75aaa91
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-macOS-check.cmake
@@ -0,0 +1,3 @@
+include(${CMAKE_CURRENT_LIST_DIR}/findAttribute.cmake)
+
+findAttribute(${test} "Embed Resources" TRUE)
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedResources-macOS.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-macOS.cmake
new file mode 100644
index 0000000..54f9fc8
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedResources-macOS.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/EmbedResources.cmake)
diff --git a/Tests/RunCMake/XcodeProject-Embed/EmbedResources.cmake b/Tests/RunCMake/XcodeProject-Embed/EmbedResources.cmake
new file mode 100644
index 0000000..0638037
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Embed/EmbedResources.cmake
@@ -0,0 +1,18 @@
+add_executable(app MACOSX_BUNDLE main.m)
+
+set(EMBED_RESOURCES_FOLDER ${CMAKE_BINARY_DIR}/runtime/shaders)
+
+# ensure embed resources folder exists
+if (NOT (IS_DIRECTORY ${EMBED_RESOURCES_FOLDER}))
+    file(MAKE_DIRECTORY ${EMBED_RESOURCES_FOLDER})
+endif()
+
+set_target_properties(app PROPERTIES
+    XCODE_EMBED_RESOURCES_PATH ${EMBED_RESOURCES_FOLDER}
+)
+
+set_target_properties(app PROPERTIES
+  XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
+  XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
+  MACOSX_BUNDLE_GUI_IDENTIFIER "com.example.app"
+)
diff --git a/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake b/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake
index a7bccee..3798ddc 100644
--- a/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake
+++ b/Tests/RunCMake/XcodeProject-Embed/RunCMakeTest.cmake
@@ -83,6 +83,25 @@
   )
 endfunction()
 
+function(TestEmbedCommon what platform)
+  set(testName Embed${what}-${platform})
+  if(NOT platform STREQUAL "macOS")
+    set(RunCMake_TEST_OPTIONS -DCMAKE_SYSTEM_NAME=${platform})
+  endif()
+  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()
+
 # Isolate device tests from host architecture selection.
 unset(ENV{CMAKE_OSX_ARCHITECTURES})
 
@@ -100,4 +119,7 @@
   # defaults, which is to remove headers on copy, but not code sign.
   TestAppExtension(macOS)
   TestAppExtension(iOS)
+  TestEmbedCommon(Resources macOS)
+  TestEmbedCommon(Resources iOS)
+  TestEmbedCommon(PlugIns macOS)
 endif()