diff --git a/docs/build-apps/build-items.md b/docs/build-apps/build-items.md
index 608836c94d40..e8f6b01fb402 100644
--- a/docs/build-apps/build-items.md
+++ b/docs/build-apps/build-items.md
@@ -9,6 +9,42 @@ ms.date: 09/19/2024
Build items control how .NET for iOS, Mac Catalyst, macOS, and tvOS
application or library projects are built.
+## PartialAppManifest
+
+`PartialAppManifest` can be used to add additional partial app manifests that
+will be merged with the main app manifest (Info.plist).
+
+Any values in the partial app manifests will override values in the main app
+manifest unless the `Overwrite` metadata is set to `false`.
+
+If the same value is specified in multiple partial app manifests, it's
+undetermined which one will be the one used.
+
+```xml
+
+
+
+```
+
+If the developer needs to execute a target to compute what to add to the
+`PartialAppManifest` item group, it's possible to make sure this target is
+executed before the `PartialAppManifest` items are procesed by adding it to
+the `CollectAppManifestsDependsOn` property:
+
+```xml
+
+
+ AddPartialAppManifests;
+ $(CollectAppManifestsDependsOn);
+
+
+
+
+
+
+
+```
+
## XcodeProject
`` can be used to build and consume the outputs
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs
index 7901c1f9f72d..8f54d817cbd0 100644
--- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs
@@ -393,15 +393,17 @@ protected void SetValue (PDictionary dict, string key, string value)
dict [key] = value;
}
- public static void MergePartialPlistDictionary (PDictionary plist, PDictionary partial)
+ public static void MergePartialPlistDictionary (PDictionary plist, PDictionary partial, bool overwrite)
{
foreach (var property in partial) {
var key = property.Key!;
if (plist.ContainsKey (key)) {
+ if (!overwrite)
+ continue;
var value = plist [key];
if (value is PDictionary && property.Value is PDictionary) {
- MergePartialPlistDictionary ((PDictionary) value, (PDictionary) property.Value);
+ MergePartialPlistDictionary ((PDictionary) value, (PDictionary) property.Value, overwrite);
} else {
plist [key] = property.Value.Clone ();
}
@@ -418,6 +420,7 @@ public static void MergePartialPLists (Task task, PDictionary plist, IEnumerable
foreach (var template in partialLists) {
PDictionary partial;
+ var overwrite = !string.Equals (template.GetMetadata ("Overwrite"), "false", StringComparison.OrdinalIgnoreCase);
try {
partial = PDictionary.FromFile (template.ItemSpec)!;
@@ -426,7 +429,7 @@ public static void MergePartialPLists (Task task, PDictionary plist, IEnumerable
continue;
}
- MergePartialPlistDictionary (plist, partial);
+ MergePartialPlistDictionary (plist, partial, overwrite);
}
}
diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets
index 534665a2d6b8..caf764e77050 100644
--- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets
+++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets
@@ -509,7 +509,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
property to run targets that add to the `PartialAppManifest` item group before we process them.
* Some MSBuild properties can also add values.
- The precedence is: MSBuild properties can be overridden by the Info.plist, which can be overridden by a partial plist.
+ The precedence is: MSBuild properties can be overridden by the Info.plist, which can be overridden by a partial plist (a partial plist can also specify the "Overwrite=false" metadata to not overwrite any existing entries).
2. In the `CompileAppManifest` target we get all the inputs from above, and compute a temporary app manifest, which is written to a temporary output file.
diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs
index 93cc0b5e6d41..54b1e55a42d7 100644
--- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs
+++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs
@@ -77,6 +77,36 @@ public void MultipleMinimumOSVersions ()
Assert.AreEqual ("13.0", plist.GetMinimumOSVersion (), "MinimumOSVersion");
}
+ [Test]
+ [TestCase (false, "14.0")]
+ [TestCase (true, "13.0")]
+ public void MultipleMinimumOSVersions_Overwrite (bool overwrite, string expectedMinimumOSVersion)
+ {
+ var dir = Cache.CreateTemporaryDirectory ();
+ var task = CreateTask (dir);
+
+ var mainPath = Path.Combine (dir, "Info.plist");
+ var main = new PDictionary ();
+ main.SetMinimumOSVersion ("14.0");
+ main.Save (mainPath);
+
+ var partialPath = Path.Combine (dir, "PartialAppManifest.plist");
+ var partial = new PDictionary ();
+ partial.SetMinimumOSVersion ("13.0");
+ partial.Save (partialPath);
+
+ task.AppManifest = new TaskItem (mainPath);
+ var partialAppManifest = new TaskItem (partialPath);
+ partialAppManifest.SetMetadata ("Overwrite", overwrite ? "true" : "false");
+ task.PartialAppManifests = new [] { partialAppManifest };
+ task.SupportedOSPlatformVersion = "14.0";
+
+ ExecuteTask (task);
+
+ var plist = PDictionary.FromFile (task.CompiledAppManifest!.ItemSpec)!;
+ Assert.AreEqual (expectedMinimumOSVersion, plist.GetMinimumOSVersion (), "MinimumOSVersion");
+ }
+
[Test]
public void ErrorWithMismatchedInfoPlistMinimumOSVersion ()
{