From 1803f6dacc3377c338f39491b7ab288d4b9b29ef Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 22 Jul 2026 16:24:17 +0200 Subject: [PATCH 1/5] [msbuild] Add AppManifestEntry item support Allow projects to add, override, and remove Info.plist entries using typed MSBuild items. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41397aea-ecce-4a13-83fa-453adc477ec7 --- docs/building-apps/build-items.md | 23 ++++++++ .../MSBStrings.resx | 20 +++++++ .../Tasks/CompileAppManifest.cs | 43 ++++++++++++++ msbuild/Xamarin.Shared/Xamarin.Shared.targets | 5 +- .../MyPartialAppManifestApp/shared.csproj | 2 + .../UnitTests/PartialAppManifestTest.cs | 14 +++++ .../TaskTests/CompileAppManifestTaskTests.cs | 56 +++++++++++++++++++ 7 files changed, 162 insertions(+), 1 deletion(-) diff --git a/docs/building-apps/build-items.md b/docs/building-apps/build-items.md index 84329b453a04..f68f3cb34972 100644 --- a/docs/building-apps/build-items.md +++ b/docs/building-apps/build-items.md @@ -62,6 +62,29 @@ See also: An item group that contains atlas textures. +## AppManifestEntry + +An item group that contains entries to add to the app manifest (Info.plist). + +These entries are processed last and override entries from the main app +manifest, partial app manifests, and generated values. + +The following types are supported: + +```xml + + + + + + + +``` + +Boolean values may be `true` or `false` (case-insensitive). String arrays use a +semicolon as the default separator, which can be changed with the +`ArraySeparator` metadata. + ## BGenReferencePath The list of assembly references to pass to the `bgen` tool (binding generator). diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index 35a965a622e3..e160d9999b39 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1697,6 +1697,26 @@ {1} - The assembly path. {2} - The computed extraction path. {3} - The intended output directory. + + + Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. + Shown when an AppManifestEntry item of type Remove has a value. +{0} - The invalid value. +{1} - The app manifest entry name. +{2} - The app manifest entry type. + + + Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. + Shown when an AppManifestEntry item of type Boolean has an invalid value. +{0} - The invalid value. +{1} - The app manifest entry name. +{2} - The app manifest entry type. + + + Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. + Shown when an AppManifestEntry item has an unknown type. +{0} - The unknown type. +{1} - The app manifest entry name. The PrepareAssemblies task failed without reporting a specific error. Please rebuild with increased verbosity for more details. diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs index b791af0eeff2..1e41706aeee5 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs @@ -34,6 +34,8 @@ public class CompileAppManifest : XamarinTask, IHasProjectDir, IHasResourcePrefi // This must be an ITaskItem to copy the file to Windows for remote builds. public ITaskItem? AppManifest { get; set; } + public ITaskItem [] AppManifestEntries { get; set; } = []; + [Required] public string BundleExecutable { get; set; } = ""; @@ -157,6 +159,8 @@ public override bool Execute () // Merge with any partial plists... MergePartialPlistTemplates (plist); + AddAppManifestEntries (plist); + Validation (plist); // write the resulting app manifest @@ -180,6 +184,45 @@ void AddXamarinVersionNumber (PDictionary plist) plist.Add (name, dict); } + void AddAppManifestEntries (PDictionary plist) + { + foreach (var item in AppManifestEntries) { + var key = item.ItemSpec; + var type = item.GetMetadata ("Type"); + var value = item.GetMetadata ("Value"); + + switch (type.ToLowerInvariant ()) { + case "remove": + if (!string.IsNullOrEmpty (value)) + Log.LogError (MSBStrings.E7184, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. */ value, key, type); + plist.Remove (key); + break; + case "boolean": + if (!bool.TryParse (value, out var booleanValue)) { + Log.LogError (MSBStrings.E7185, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. */ value, key, type); + continue; + } + plist [key] = new PBoolean (booleanValue); + break; + case "string": + plist [key] = new PString (value); + break; + case "stringarray": + var arraySeparator = item.GetMetadata ("ArraySeparator"); + if (string.IsNullOrEmpty (arraySeparator)) + arraySeparator = ";"; + var array = new PArray (); + foreach (var element in value.Split (new [] { arraySeparator }, StringSplitOptions.None)) + array.Add (new PString (element)); + plist [key] = array; + break; + default: + Log.LogError (MSBStrings.E7186, /* Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. */ type, key); + break; + } + } + } + void RegisterFonts (PDictionary plist) { if (FontFilesToRegister is null || FontFilesToRegister.Length == 0) diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 6ad1cd9b33f5..e4405e906faf 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -583,9 +583,10 @@ Copyright (C) 2018 Microsoft. All rights reserved. * An Info.plist in their project file (by using a `None` item with filename "Info.plist" or with a `Link` metadata with filename "Info.plist"). We figure this out in the DetectAppManifest target. * A partial plist in their project (using the `PartialAppManifest` item group). Developers can add targets to the public CollectAppManifestsDependsOn property to run targets that add to the `PartialAppManifest` item group before we process them. + * Individual entries in the `AppManifestEntry` item group. * 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 (a partial plist can also specify the "Overwrite=false" metadata to not overwrite any existing entries). + 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), which can be overridden by an AppManifestEntry item. 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. @@ -637,6 +638,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. <_CompileAppManifestInputLine Include="ApplicationTitle=$(ApplicationTitle)" /> <_CompileAppManifestInputLine Include="ApplicationVersion=$(ApplicationVersion)" /> <_CompileAppManifestInputLine Include="AppManifest=$(AppBundleManifest)" /> + <_CompileAppManifestInputLine Include="AppManifestEntry=%(AppManifestEntry.Identity)|%(AppManifestEntry.Type)|%(AppManifestEntry.Value)|%(AppManifestEntry.ArraySeparator)" /> <_CompileAppManifestInputLine Include="CompiledAppManifest=$(_TemporaryAppManifest)" /> <_CompileAppManifestInputLine Include="DefaultSdkVersion=$(_SdkVersion)" /> <_CompileAppManifestInputLine Include="FontFilesToRegister=$(_CompileAppManifestFontFilesToRegister)" /> @@ -687,6 +689,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. ApplicationTitle="$(ApplicationTitle)" ApplicationVersion="$(ApplicationVersion)" AppManifest="$(AppBundleManifest)" + AppManifestEntries="@(AppManifestEntry)" BundleExecutable="$(_NativeExecutableName)" CompiledAppManifest="$(_TemporaryAppManifest)" DefaultSdkVersion="$(_SdkVersion)" diff --git a/tests/dotnet/MyPartialAppManifestApp/shared.csproj b/tests/dotnet/MyPartialAppManifestApp/shared.csproj index d7bfc29c198d..d4c8dd68f262 100644 --- a/tests/dotnet/MyPartialAppManifestApp/shared.csproj +++ b/tests/dotnet/MyPartialAppManifestApp/shared.csproj @@ -6,12 +6,14 @@ MyPartialAppManifestApp com.xamarin.mypartialappmanifestapp 3.14 + LaunchScreen + diff --git a/tests/dotnet/UnitTests/PartialAppManifestTest.cs b/tests/dotnet/UnitTests/PartialAppManifestTest.cs index f0fb6536005c..d071f7643ba2 100644 --- a/tests/dotnet/UnitTests/PartialAppManifestTest.cs +++ b/tests/dotnet/UnitTests/PartialAppManifestTest.cs @@ -23,6 +23,7 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers) Assert.That (infoPlist.GetString ("CFBundleVersion").Value, Is.EqualTo ("3.14"), "CFBundleVersion"); Assert.That (infoPlist.GetString ("CFBundleShortVersionString").Value, Is.EqualTo ("3.14"), "CFBundleShortVersionString"); Assert.That (infoPlist.GetString ("Something").Value, Is.EqualTo ("SomeValue"), "Something"); + Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("LaunchScreen"), "UILaunchStoryboardName"); var partialAppManifestPath = Path.Combine (Path.GetDirectoryName (project_path)!, "..", "Partial.plist"); Configuration.Touch (partialAppManifestPath); @@ -33,6 +34,19 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers) rv = DotNet.AssertBuild (project_path, GetDefaultProperties (runtimeIdentifiers)); allTargets = BinLog.GetAllTargets (rv.BinLogPath); AssertTargetNotExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 2"); + + properties = GetDefaultProperties (runtimeIdentifiers); + properties ["LaunchStoryboardName"] = "AnotherLaunchScreen"; + rv = DotNet.AssertBuild (project_path, properties); + allTargets = BinLog.GetAllTargets (rv.BinLogPath); + AssertTargetExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 3"); + + infoPlist = PDictionary.OpenFile (infoPlistPath); + Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("AnotherLaunchScreen"), "UILaunchStoryboardName updated"); + + rv = DotNet.AssertBuild (project_path, properties); + allTargets = BinLog.GetAllTargets (rv.BinLogPath); + AssertTargetNotExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 4"); } [Test] diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs index 5987c452500a..cf648ec2a12c 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Utilities; @@ -49,6 +50,61 @@ public void MainMinimumOSVersions () Assert.That (plist.GetMinimumOSVersion (), Is.EqualTo ("14.0"), "MinimumOSVersion"); } + [Test] + public void AppManifestEntries () + { + var dir = Cache.CreateTemporaryDirectory (); + var task = CreateTask (dir); + + var mainPath = Path.Combine (dir, "Info.plist"); + var main = new PDictionary { + { "StringValue", new PString ("main") }, + { "RemoveValue", new PString ("remove me") }, + }; + main.Save (mainPath); + + var partialPath = Path.Combine (dir, "PartialAppManifest.plist"); + var partial = new PDictionary { + { "StringValue", new PString ("partial") }, + }; + partial.Save (partialPath); + + task.AppManifest = new TaskItem (mainPath); + task.PartialAppManifests = [new TaskItem (partialPath)]; + task.AppManifestEntries = [ + new TaskItem ("StringValue", new Dictionary { { "Type", "String" }, { "Value", "entry" } }), + new TaskItem ("BooleanValue", new Dictionary { { "Type", "Boolean" }, { "Value", "TrUe" } }), + new TaskItem ("StringArrayValue", new Dictionary { { "Type", "StringArray" }, { "Value", "a;b" } }), + new TaskItem ("CustomStringArrayValue", new Dictionary { { "Type", "StringArray" }, { "Value", "c|d" }, { "ArraySeparator", "|" } }), + new TaskItem ("RemoveValue", new Dictionary { { "Type", "Remove" } }), + ]; + + ExecuteTask (task); + + var plist = PDictionary.OpenFile (task.CompiledAppManifest!.ItemSpec); + Assert.That (plist.GetString ("StringValue").Value, Is.EqualTo ("entry"), "StringValue"); + Assert.That (plist.Get ("BooleanValue")?.Value, Is.True, "BooleanValue"); + Assert.That (plist.GetArray ("StringArrayValue").OfType ().Select (v => v.Value), Is.EqualTo (new [] { "a", "b" }), "StringArrayValue"); + Assert.That (plist.GetArray ("CustomStringArrayValue").OfType ().Select (v => v.Value), Is.EqualTo (new [] { "c", "d" }), "CustomStringArrayValue"); + Assert.That (plist.ContainsKey ("RemoveValue"), Is.False, "RemoveValue"); + } + + [Test] + [TestCase ("Remove", "unexpected", "Invalid value 'unexpected' for the app manifest entry 'TestEntry' of type 'Remove' specified in the AppManifestEntry item group. Expected no value at all.")] + [TestCase ("Boolean", "not-a-boolean", "Invalid value 'not-a-boolean' for the app manifest entry 'TestEntry' of type 'Boolean' specified in the AppManifestEntry item group. Expected 'true' or 'false'.")] + [TestCase ("Unknown", "value", "Unknown type 'Unknown' for the app manifest entry 'TestEntry' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'.")] + public void InvalidAppManifestEntry (string type, string value, string expectedError) + { + var task = CreateTask (); + task.AppManifestEntries = [ + new TaskItem ("TestEntry", new Dictionary { { "Type", type }, { "Value", value } }), + ]; + + ExecuteTask (task, expectedErrorCount: 1); + + Assert.That (Engine.Logger.ErrorEvents [0].Message, Is.EqualTo (expectedError)); + } + [Test] public void MultipleMinimumOSVersions () { From c975e893a62757502d0d84205a6c4cfba3544b71 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 22 Jul 2026 17:20:02 +0200 Subject: [PATCH 2/5] [msbuild] Avoid app manifest fingerprint collisions Hash length-prefixed AppManifestEntry metadata so arbitrary keys and values reliably invalidate incremental builds. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41397aea-ecce-4a13-83fa-453adc477ec7 --- .../Tasks/ComputeHashForItems.cs | 13 +++--- msbuild/Xamarin.Shared/Xamarin.Shared.targets | 11 ++++- .../MyPartialAppManifestApp/shared.csproj | 3 ++ .../UnitTests/PartialAppManifestTest.cs | 8 +++- .../TaskTests/ComputeHashForItemsTaskTests.cs | 40 +++++++++++++++++++ 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeHashForItemsTaskTests.cs diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs index b6673576adab..389569cf5cb1 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs @@ -1,7 +1,6 @@ #nullable enable using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -40,14 +39,18 @@ public override bool Execute () using var sha = CreateHashAlgorithm (); - var buffer = new List (); for (var i = 0; i < Input.Length; i++) { var input = Input [i]; - buffer.Clear (); + using var buffer = new MemoryStream (); + using var writer = new BinaryWriter (buffer, Encoding.UTF8, true); foreach (var im in InputMetadata) { - buffer.AddRange (Encoding.UTF8.GetBytes (input.GetMetadata (im.ItemSpec))); + var bytes = Encoding.UTF8.GetBytes (input.GetMetadata (im.ItemSpec)); + writer.Write (bytes.Length); + writer.Write (bytes); } - var hashBytes = sha.ComputeHash (buffer.ToArray ()); + writer.Flush (); + buffer.Position = 0; + var hashBytes = sha.ComputeHash (buffer); var hash = string.Join ("", hashBytes.Select (b => $"{b:x2}")); input.SetMetadata (OutputMetadata, hash); } diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index e4405e906faf..83e5a80c5735 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -623,7 +623,16 @@ Copyright (C) 2018 Microsoft. All rights reserved. <_FontFilesToRegister Include="@(BundleResource)" Condition="'%(BundleResource.RegisterFont)' == 'true'" /> + <_AppManifestEntryToHash Include="@(AppManifestEntry)" /> + <_AppManifestEntryHashMetadata Include="Identity;Type;Value;ArraySeparator" /> + + + <_CompileAppManifestInputFile>$(DeviceSpecificIntermediateOutputPath)_CompileAppManifest.inputs @@ -638,7 +647,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. <_CompileAppManifestInputLine Include="ApplicationTitle=$(ApplicationTitle)" /> <_CompileAppManifestInputLine Include="ApplicationVersion=$(ApplicationVersion)" /> <_CompileAppManifestInputLine Include="AppManifest=$(AppBundleManifest)" /> - <_CompileAppManifestInputLine Include="AppManifestEntry=%(AppManifestEntry.Identity)|%(AppManifestEntry.Type)|%(AppManifestEntry.Value)|%(AppManifestEntry.ArraySeparator)" /> + <_CompileAppManifestInputLine Include="AppManifestEntry=%(_AppManifestEntryWithHash._Hash)" /> <_CompileAppManifestInputLine Include="CompiledAppManifest=$(_TemporaryAppManifest)" /> <_CompileAppManifestInputLine Include="DefaultSdkVersion=$(_SdkVersion)" /> <_CompileAppManifestInputLine Include="FontFilesToRegister=$(_CompileAppManifestFontFilesToRegister)" /> diff --git a/tests/dotnet/MyPartialAppManifestApp/shared.csproj b/tests/dotnet/MyPartialAppManifestApp/shared.csproj index d4c8dd68f262..6b7d9b891175 100644 --- a/tests/dotnet/MyPartialAppManifestApp/shared.csproj +++ b/tests/dotnet/MyPartialAppManifestApp/shared.csproj @@ -7,12 +7,15 @@ com.xamarin.mypartialappmanifestapp 3.14 LaunchScreen + a|String + b + diff --git a/tests/dotnet/UnitTests/PartialAppManifestTest.cs b/tests/dotnet/UnitTests/PartialAppManifestTest.cs index d071f7643ba2..f828234a9038 100644 --- a/tests/dotnet/UnitTests/PartialAppManifestTest.cs +++ b/tests/dotnet/UnitTests/PartialAppManifestTest.cs @@ -24,6 +24,7 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers) Assert.That (infoPlist.GetString ("CFBundleShortVersionString").Value, Is.EqualTo ("3.14"), "CFBundleShortVersionString"); Assert.That (infoPlist.GetString ("Something").Value, Is.EqualTo ("SomeValue"), "Something"); Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("LaunchScreen"), "UILaunchStoryboardName"); + Assert.That (infoPlist.GetString ("a|String").Value, Is.EqualTo ("b"), "Delimiter value"); var partialAppManifestPath = Path.Combine (Path.GetDirectoryName (project_path)!, "..", "Partial.plist"); Configuration.Touch (partialAppManifestPath); @@ -36,13 +37,16 @@ public void Build (ApplePlatform platform, string runtimeIdentifiers) AssertTargetNotExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 2"); properties = GetDefaultProperties (runtimeIdentifiers); - properties ["LaunchStoryboardName"] = "AnotherLaunchScreen"; + properties ["ManifestKey"] = "a"; + properties ["ManifestValue"] = "String|b"; rv = DotNet.AssertBuild (project_path, properties); allTargets = BinLog.GetAllTargets (rv.BinLogPath); AssertTargetExecuted (allTargets, "_CompileAppManifest", "_CompileAppManifest rebuild 3"); infoPlist = PDictionary.OpenFile (infoPlistPath); - Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("AnotherLaunchScreen"), "UILaunchStoryboardName updated"); + Assert.That (infoPlist.GetString ("UILaunchStoryboardName").Value, Is.EqualTo ("LaunchScreen"), "UILaunchStoryboardName unchanged"); + Assert.That (infoPlist.ContainsKey ("a|String"), Is.False, "Old delimiter key"); + Assert.That (infoPlist.GetString ("a").Value, Is.EqualTo ("String|b"), "Updated delimiter value"); rv = DotNet.AssertBuild (project_path, properties); allTargets = BinLog.GetAllTargets (rv.BinLogPath); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeHashForItemsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeHashForItemsTaskTests.cs new file mode 100644 index 000000000000..cf321a86aeb2 --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeHashForItemsTaskTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Collections.Generic; + +using Microsoft.Build.Utilities; +using NUnit.Framework; + +namespace Xamarin.MacDev.Tasks { + [TestFixture] + public class ComputeHashForItemsTaskTests : TestBase { + [Test] + public void MetadataBoundariesAffectHash () + { + var first = new TaskItem ("aString", new Dictionary { + { "Type", "String" }, + { "Value", "b" }, + }); + var second = new TaskItem ("a", new Dictionary { + { "Type", "String" }, + { "Value", "Stringb" }, + }); + var task = CreateTask (); + task.Input = [first, second]; + task.InputMetadata = [ + new TaskItem ("Identity"), + new TaskItem ("Type"), + new TaskItem ("Value"), + new TaskItem ("ArraySeparator"), + ]; + task.OutputMetadata = "Hash"; + + ExecuteTask (task); + + Assert.That (first.GetMetadata ("Hash"), Is.Not.EqualTo (second.GetMetadata ("Hash"))); + } + } +} From c4905c6298ef266f38f33448ad63002d6f4c241a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 3 Aug 2026 13:49:13 +0200 Subject: [PATCH 3/5] [msbuild] Share plist item processing Reuse typed plist item parsing for app manifest entries and custom entitlements, and restore the reusable hash buffer. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41397aea-ecce-4a13-83fa-453adc477ec7 --- .../Xamarin.MacDev.Tasks/PListItemGroup.cs | 84 +++++++++++++++++++ .../Tasks/CompileAppManifest.cs | 44 ++-------- .../Tasks/CompileEntitlements.cs | 63 ++------------ .../Tasks/ComputeHashForItems.cs | 17 ++-- .../TaskTests/CompileAppManifestTaskTests.cs | 2 + .../TaskTests/CompileEntitlementsTaskTests.cs | 1 + 6 files changed, 114 insertions(+), 97 deletions(-) create mode 100644 msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs diff --git a/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs b/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs new file mode 100644 index 000000000000..ae5e17d3ea5c --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +using Xamarin.MacDev; + +namespace Xamarin.MacDev.Tasks { + delegate bool TryParseBoolean (string value, out bool result); + + static class PListItemGroup { + public static void Merge ( + TaskLoggingHelper log, + PDictionary dictionary, + IEnumerable? items, + Func transformString, + TryParseBoolean tryParseBoolean, + string invalidRemoveValueMessage, + string invalidBooleanValueMessage, + string unknownTypeMessage) + { + if (items is null) + return; + + foreach (var item in items) { + var key = item.ItemSpec; + var type = item.GetMetadata ("Type"); + var value = item.GetMetadata ("Value"); + + switch (type.ToLowerInvariant ()) { + case "remove": + if (!string.IsNullOrEmpty (value)) + log.LogError (invalidRemoveValueMessage, value, key, type); + dictionary.Remove (key); + break; + case "boolean": + if (!tryParseBoolean (value, out var booleanValue)) { + log.LogError (invalidBooleanValueMessage, value, key, type); + continue; + } + dictionary [key] = new PBoolean (booleanValue); + break; + case "string": + dictionary [key] = transformString (new PString (value), key); + break; + case "stringarray": + var arraySeparator = item.GetMetadata ("ArraySeparator"); + if (string.IsNullOrEmpty (arraySeparator)) + arraySeparator = ";"; + var array = new PArray (); + foreach (var element in value.Split (new [] { arraySeparator }, StringSplitOptions.None)) + array.Add (transformString (new PString (element), key)); + dictionary [key] = array; + break; + default: + log.LogError (unknownTypeMessage, type, key); + break; + } + } + } + + public static bool TryParseBooleanStrict (string value, out bool result) + { + if (string.Equals (value, "true", StringComparison.OrdinalIgnoreCase)) { + result = true; + return true; + } + + if (string.Equals (value, "false", StringComparison.OrdinalIgnoreCase)) { + result = false; + return true; + } + + result = false; + return false; + } + } +} diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs index 1e41706aeee5..0106ed12f20d 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs @@ -186,41 +186,15 @@ void AddXamarinVersionNumber (PDictionary plist) void AddAppManifestEntries (PDictionary plist) { - foreach (var item in AppManifestEntries) { - var key = item.ItemSpec; - var type = item.GetMetadata ("Type"); - var value = item.GetMetadata ("Value"); - - switch (type.ToLowerInvariant ()) { - case "remove": - if (!string.IsNullOrEmpty (value)) - Log.LogError (MSBStrings.E7184, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. */ value, key, type); - plist.Remove (key); - break; - case "boolean": - if (!bool.TryParse (value, out var booleanValue)) { - Log.LogError (MSBStrings.E7185, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. */ value, key, type); - continue; - } - plist [key] = new PBoolean (booleanValue); - break; - case "string": - plist [key] = new PString (value); - break; - case "stringarray": - var arraySeparator = item.GetMetadata ("ArraySeparator"); - if (string.IsNullOrEmpty (arraySeparator)) - arraySeparator = ";"; - var array = new PArray (); - foreach (var element in value.Split (new [] { arraySeparator }, StringSplitOptions.None)) - array.Add (new PString (element)); - plist [key] = array; - break; - default: - Log.LogError (MSBStrings.E7186, /* Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. */ type, key); - break; - } - } + PListItemGroup.Merge ( + Log, + plist, + AppManifestEntries, + static (value, _) => value, + bool.TryParse, + MSBStrings.E7184, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. */ + MSBStrings.E7185, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. */ + MSBStrings.E7186 /* Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. */); } void RegisterFonts (PDictionary plist) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs index 3e2e85074957..03bb05e901d5 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs @@ -277,60 +277,15 @@ PDictionary MergeEntitlementDictionary (PDictionary dict, MobileProvision? profi void AddCustomEntitlements (PDictionary dict, MobileProvision? profile) { - if (CustomEntitlements is null) - return; - - // Process any custom entitlements from the 'CustomEntitlements' item group. These are applied last, and will override anything else. - // Possible values: - // - // - // - // - // - // - // - - foreach (var item in CustomEntitlements) { - var entitlement = item.ItemSpec; - var type = item.GetMetadata ("Type"); - var value = item.GetMetadata ("Value"); - switch (type.ToLowerInvariant ()) { - case "remove": - if (!string.IsNullOrEmpty (value)) - Log.LogError (MSBStrings.E7102, /* Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all. */ value, entitlement, type); - dict.Remove (entitlement); - break; - case "boolean": - bool booleanValue; - if (string.Equals (value, "true", StringComparison.OrdinalIgnoreCase)) { - booleanValue = true; - } else if (string.Equals (value, "false", StringComparison.OrdinalIgnoreCase)) { - booleanValue = false; - } else { - Log.LogError (MSBStrings.E7103, /* "Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'." */ value, entitlement, type); - continue; - } - - dict [entitlement] = new PBoolean (booleanValue); - break; - case "string": - dict [entitlement] = MergeEntitlementString (new PString (value), profile, entitlement == ApplicationIdentifierKey, entitlement); - break; - case "stringarray": - var arraySeparator = item.GetMetadata ("ArraySeparator"); - if (string.IsNullOrEmpty (arraySeparator)) - arraySeparator = ";"; - var arrayContent = value.Split (new string [] { arraySeparator }, StringSplitOptions.None); - var parray = new PArray (); - foreach (var element in arrayContent) - parray.Add (MergeEntitlementString (new PString (element), profile, entitlement == ApplicationIdentifierKey, entitlement)); - dict [entitlement] = parray; - break; - default: - Log.LogError (MSBStrings.E7104, /* "Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'." */ type, entitlement); - break; - } - } + PListItemGroup.Merge ( + Log, + dict, + CustomEntitlements, + (value, entitlement) => MergeEntitlementString (value, profile, entitlement == ApplicationIdentifierKey, entitlement), + PListItemGroup.TryParseBooleanStrict, + MSBStrings.E7102, /* Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all. */ + MSBStrings.E7103, /* "Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'." */ + MSBStrings.E7104 /* "Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'." */); } static bool AreEqual (byte [] x, byte [] y) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs index 389569cf5cb1..9756ae6138e4 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeHashForItems.cs @@ -1,7 +1,7 @@ #nullable enable using System; -using System.IO; +using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -39,18 +39,19 @@ public override bool Execute () using var sha = CreateHashAlgorithm (); + var buffer = new List (); for (var i = 0; i < Input.Length; i++) { var input = Input [i]; - using var buffer = new MemoryStream (); - using var writer = new BinaryWriter (buffer, Encoding.UTF8, true); + buffer.Clear (); foreach (var im in InputMetadata) { var bytes = Encoding.UTF8.GetBytes (input.GetMetadata (im.ItemSpec)); - writer.Write (bytes.Length); - writer.Write (bytes); + buffer.Add ((byte) (bytes.Length >> 24)); + buffer.Add ((byte) (bytes.Length >> 16)); + buffer.Add ((byte) (bytes.Length >> 8)); + buffer.Add ((byte) bytes.Length); + buffer.AddRange (bytes); } - writer.Flush (); - buffer.Position = 0; - var hashBytes = sha.ComputeHash (buffer); + var hashBytes = sha.ComputeHash (buffer.ToArray ()); var hash = string.Join ("", hashBytes.Select (b => $"{b:x2}")); input.SetMetadata (OutputMetadata, hash); } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs index cf648ec2a12c..f8531e76fe71 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs @@ -74,6 +74,7 @@ public void AppManifestEntries () task.AppManifestEntries = [ new TaskItem ("StringValue", new Dictionary { { "Type", "String" }, { "Value", "entry" } }), new TaskItem ("BooleanValue", new Dictionary { { "Type", "Boolean" }, { "Value", "TrUe" } }), + new TaskItem ("WhitespaceBooleanValue", new Dictionary { { "Type", "Boolean" }, { "Value", " true " } }), new TaskItem ("StringArrayValue", new Dictionary { { "Type", "StringArray" }, { "Value", "a;b" } }), new TaskItem ("CustomStringArrayValue", new Dictionary { { "Type", "StringArray" }, { "Value", "c|d" }, { "ArraySeparator", "|" } }), new TaskItem ("RemoveValue", new Dictionary { { "Type", "Remove" } }), @@ -84,6 +85,7 @@ public void AppManifestEntries () var plist = PDictionary.OpenFile (task.CompiledAppManifest!.ItemSpec); Assert.That (plist.GetString ("StringValue").Value, Is.EqualTo ("entry"), "StringValue"); Assert.That (plist.Get ("BooleanValue")?.Value, Is.True, "BooleanValue"); + Assert.That (plist.Get ("WhitespaceBooleanValue")?.Value, Is.True, "WhitespaceBooleanValue"); Assert.That (plist.GetArray ("StringArrayValue").OfType ().Select (v => v.Value), Is.EqualTo (new [] { "a", "b" }), "StringArrayValue"); Assert.That (plist.GetArray ("CustomStringArrayValue").OfType ().Select (v => v.Value), Is.EqualTo (new [] { "c", "d" }), "CustomStringArrayValue"); Assert.That (plist.ContainsKey ("RemoveValue"), Is.False, "RemoveValue"); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs index 2165a3f632ff..ee7811ed1b3e 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs @@ -155,6 +155,7 @@ public void ValidateEntitlement () [TestCase ("Invalid", null, "Unknown type 'Invalid' for the entitlement 'com.xamarin.custom.entitlement' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'.")] [TestCase ("Boolean", null, "Invalid value '' for the entitlement 'com.xamarin.custom.entitlement' of type 'Boolean' specified in the CustomEntitlements item group. Expected 'true' or 'false'.")] [TestCase ("Boolean", "invalid", "Invalid value 'invalid' for the entitlement 'com.xamarin.custom.entitlement' of type 'Boolean' specified in the CustomEntitlements item group. Expected 'true' or 'false'.")] + [TestCase ("Boolean", " true ", "Invalid value ' true ' for the entitlement 'com.xamarin.custom.entitlement' of type 'Boolean' specified in the CustomEntitlements item group. Expected 'true' or 'false'.")] [TestCase ("Remove", "invalid", "Invalid value 'invalid' for the entitlement 'com.xamarin.custom.entitlement' of type 'Remove' specified in the CustomEntitlements item group. Expected no value at all.")] public void InvalidCustomEntitlements (string type, string? value, string errorMessage) { From 494219897a9d28cc7b6a30922d704bbbd75c6401 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 3 Aug 2026 14:24:46 +0200 Subject: [PATCH 4/5] [msbuild] Use strict plist boolean parsing Parse AppManifestEntry booleans with the same strict logic as CustomEntitlements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41397aea-ecce-4a13-83fa-453adc477ec7 --- msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs | 2 +- .../TaskTests/CompileAppManifestTaskTests.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs index 0106ed12f20d..28f131cd29a8 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs @@ -191,7 +191,7 @@ void AddAppManifestEntries (PDictionary plist) plist, AppManifestEntries, static (value, _) => value, - bool.TryParse, + PListItemGroup.TryParseBooleanStrict, MSBStrings.E7184, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. */ MSBStrings.E7185, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. */ MSBStrings.E7186 /* Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. */); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs index f8531e76fe71..0e65c489d8fa 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileAppManifestTaskTests.cs @@ -74,7 +74,6 @@ public void AppManifestEntries () task.AppManifestEntries = [ new TaskItem ("StringValue", new Dictionary { { "Type", "String" }, { "Value", "entry" } }), new TaskItem ("BooleanValue", new Dictionary { { "Type", "Boolean" }, { "Value", "TrUe" } }), - new TaskItem ("WhitespaceBooleanValue", new Dictionary { { "Type", "Boolean" }, { "Value", " true " } }), new TaskItem ("StringArrayValue", new Dictionary { { "Type", "StringArray" }, { "Value", "a;b" } }), new TaskItem ("CustomStringArrayValue", new Dictionary { { "Type", "StringArray" }, { "Value", "c|d" }, { "ArraySeparator", "|" } }), new TaskItem ("RemoveValue", new Dictionary { { "Type", "Remove" } }), @@ -85,7 +84,6 @@ public void AppManifestEntries () var plist = PDictionary.OpenFile (task.CompiledAppManifest!.ItemSpec); Assert.That (plist.GetString ("StringValue").Value, Is.EqualTo ("entry"), "StringValue"); Assert.That (plist.Get ("BooleanValue")?.Value, Is.True, "BooleanValue"); - Assert.That (plist.Get ("WhitespaceBooleanValue")?.Value, Is.True, "WhitespaceBooleanValue"); Assert.That (plist.GetArray ("StringArrayValue").OfType ().Select (v => v.Value), Is.EqualTo (new [] { "a", "b" }), "StringArrayValue"); Assert.That (plist.GetArray ("CustomStringArrayValue").OfType ().Select (v => v.Value), Is.EqualTo (new [] { "c", "d" }), "CustomStringArrayValue"); Assert.That (plist.ContainsKey ("RemoveValue"), Is.False, "RemoveValue"); @@ -94,6 +92,7 @@ public void AppManifestEntries () [Test] [TestCase ("Remove", "unexpected", "Invalid value 'unexpected' for the app manifest entry 'TestEntry' of type 'Remove' specified in the AppManifestEntry item group. Expected no value at all.")] [TestCase ("Boolean", "not-a-boolean", "Invalid value 'not-a-boolean' for the app manifest entry 'TestEntry' of type 'Boolean' specified in the AppManifestEntry item group. Expected 'true' or 'false'.")] + [TestCase ("Boolean", " true ", "Invalid value ' true ' for the app manifest entry 'TestEntry' of type 'Boolean' specified in the AppManifestEntry item group. Expected 'true' or 'false'.")] [TestCase ("Unknown", "value", "Unknown type 'Unknown' for the app manifest entry 'TestEntry' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'.")] public void InvalidAppManifestEntry (string type, string value, string expectedError) { From 1832790fe73a39f9bd5e1d0cdef084b0a4d05ed5 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 3 Aug 2026 14:33:51 +0200 Subject: [PATCH 5/5] [msbuild] Simplify plist boolean parsing Use the shared strict boolean parser directly instead of passing the same delegate from every caller. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41397aea-ecce-4a13-83fa-453adc477ec7 --- msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs | 5 +---- msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs | 1 - msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs b/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs index ae5e17d3ea5c..7338fd05532e 100644 --- a/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs +++ b/msbuild/Xamarin.MacDev.Tasks/PListItemGroup.cs @@ -12,15 +12,12 @@ using Xamarin.MacDev; namespace Xamarin.MacDev.Tasks { - delegate bool TryParseBoolean (string value, out bool result); - static class PListItemGroup { public static void Merge ( TaskLoggingHelper log, PDictionary dictionary, IEnumerable? items, Func transformString, - TryParseBoolean tryParseBoolean, string invalidRemoveValueMessage, string invalidBooleanValueMessage, string unknownTypeMessage) @@ -40,7 +37,7 @@ public static void Merge ( dictionary.Remove (key); break; case "boolean": - if (!tryParseBoolean (value, out var booleanValue)) { + if (!TryParseBooleanStrict (value, out var booleanValue)) { log.LogError (invalidBooleanValueMessage, value, key, type); continue; } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs index 28f131cd29a8..66f4033dd063 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileAppManifest.cs @@ -191,7 +191,6 @@ void AddAppManifestEntries (PDictionary plist) plist, AppManifestEntries, static (value, _) => value, - PListItemGroup.TryParseBooleanStrict, MSBStrings.E7184, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected no value at all. */ MSBStrings.E7185, /* Invalid value '{0}' for the app manifest entry '{1}' of type '{2}' specified in the AppManifestEntry item group. Expected 'true' or 'false'. */ MSBStrings.E7186 /* Unknown type '{0}' for the app manifest entry '{1}' specified in the AppManifestEntry item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. */); diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs index 03bb05e901d5..5a4dddd4918f 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs @@ -282,7 +282,6 @@ void AddCustomEntitlements (PDictionary dict, MobileProvision? profile) dict, CustomEntitlements, (value, entitlement) => MergeEntitlementString (value, profile, entitlement == ApplicationIdentifierKey, entitlement), - PListItemGroup.TryParseBooleanStrict, MSBStrings.E7102, /* Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all. */ MSBStrings.E7103, /* "Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'." */ MSBStrings.E7104 /* "Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'." */);