diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs index d58532bf01..e3f50499f1 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs @@ -2029,6 +2029,33 @@ namespace Xamarin.Localization.MSBuild { } } + /// + /// Looks up a localized string similar to Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all.. + /// + public static string E7102 { + get { + return ResourceManager.GetString("E7102", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'.. + /// + public static string E7103 { + get { + return ResourceManager.GetString("E7103", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'.. + /// + public static string E7104 { + get { + return ResourceManager.GetString("E7104", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid framework: {0}. /// diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index dc1745d14b..2b1f5c18ec 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1425,4 +1425,29 @@ Unknown property '{0}' with value '{1}'. + + + Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all. + + Don't translate: CustomEntitlements (name of option in project file) + + + + + Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'. + + Don't translate: + * CustomEntitlements (name of option in project file) + * 'true', 'false' + + + + + Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'. + + Don't translate: + * CustomEntitlements (name of option in project file) + * 'Remove', 'Boolean', 'String', 'StringArray' + + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlementsTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlementsTaskBase.cs index d2fd1648c6..798cd13527 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlementsTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlementsTaskBase.cs @@ -59,6 +59,8 @@ namespace Xamarin.MacDev.Tasks [Required] public ITaskItem? CompiledEntitlements { get; set; } + public ITaskItem[] CustomEntitlements { get; set; } = Array.Empty (); + public bool Debug { get; set; } public string Entitlements { get; set; } = string.Empty; @@ -250,6 +252,64 @@ namespace Xamarin.MacDev.Tasks return result; } + void AddCustomEntitlements (PDictionary dict) + { + 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] = new PString (value ?? string.Empty); + 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 (new PString (element)); + 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; + } + } + } + static bool AreEqual (byte[] x, byte[] y) { if (x.Length != y.Length) @@ -356,6 +416,8 @@ namespace Xamarin.MacDev.Tasks break; } + AddCustomEntitlements (entitlements); + return entitlements; } diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index abc2d96e83..b223b541d6 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -645,12 +645,18 @@ Copyright (C) 2018 Microsoft. All rights reserved. Condition="'$(_RequireCodeSigning)' == 'true' Or '$(CodesignEntitlements)' != ''" DependsOnTargets="$(_CompileEntitlementsDependsOn)" Outputs="$(DeviceSpecificIntermediateOutputPath)Entitlements.xcent"> + + + + + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + diff --git a/tests/dotnet/Entitlements/MacCatalyst/Entitlements.plist b/tests/dotnet/Entitlements/MacCatalyst/Entitlements.plist new file mode 100644 index 0000000000..6631ffa6f2 --- /dev/null +++ b/tests/dotnet/Entitlements/MacCatalyst/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/Entitlements/MacCatalyst/Makefile b/tests/dotnet/Entitlements/MacCatalyst/Makefile new file mode 100644 index 0000000000..110d078f45 --- /dev/null +++ b/tests/dotnet/Entitlements/MacCatalyst/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/Entitlements/Makefile b/tests/dotnet/Entitlements/Makefile new file mode 100644 index 0000000000..6affa45ff1 --- /dev/null +++ b/tests/dotnet/Entitlements/Makefile @@ -0,0 +1,2 @@ +TOP=../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/Entitlements/iOS/Entitlements.csproj b/tests/dotnet/Entitlements/iOS/Entitlements.csproj new file mode 100644 index 0000000000..86d408734a --- /dev/null +++ b/tests/dotnet/Entitlements/iOS/Entitlements.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/Entitlements/iOS/Entitlements.plist b/tests/dotnet/Entitlements/iOS/Entitlements.plist new file mode 100644 index 0000000000..6631ffa6f2 --- /dev/null +++ b/tests/dotnet/Entitlements/iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/Entitlements/iOS/Makefile b/tests/dotnet/Entitlements/iOS/Makefile new file mode 100644 index 0000000000..110d078f45 --- /dev/null +++ b/tests/dotnet/Entitlements/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/Entitlements/macOS/Entitlements.csproj b/tests/dotnet/Entitlements/macOS/Entitlements.csproj new file mode 100644 index 0000000000..a77287b9ba --- /dev/null +++ b/tests/dotnet/Entitlements/macOS/Entitlements.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + diff --git a/tests/dotnet/Entitlements/macOS/Entitlements.plist b/tests/dotnet/Entitlements/macOS/Entitlements.plist new file mode 100644 index 0000000000..6631ffa6f2 --- /dev/null +++ b/tests/dotnet/Entitlements/macOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/Entitlements/macOS/Makefile b/tests/dotnet/Entitlements/macOS/Makefile new file mode 100644 index 0000000000..110d078f45 --- /dev/null +++ b/tests/dotnet/Entitlements/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/Entitlements/shared.csproj b/tests/dotnet/Entitlements/shared.csproj new file mode 100644 index 0000000000..648f2f35c4 --- /dev/null +++ b/tests/dotnet/Entitlements/shared.csproj @@ -0,0 +1,13 @@ + + + + Exe + Entitlements + + + + + + + + diff --git a/tests/dotnet/Entitlements/shared.mk b/tests/dotnet/Entitlements/shared.mk new file mode 100644 index 0000000000..ec2a0d435d --- /dev/null +++ b/tests/dotnet/Entitlements/shared.mk @@ -0,0 +1,3 @@ +TOP=../../../.. +TESTNAME=AutoDetectEntitlements +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/Entitlements/tvOS/Entitlements.csproj b/tests/dotnet/Entitlements/tvOS/Entitlements.csproj new file mode 100644 index 0000000000..bd487ddcd8 --- /dev/null +++ b/tests/dotnet/Entitlements/tvOS/Entitlements.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + diff --git a/tests/dotnet/Entitlements/tvOS/Entitlements.plist b/tests/dotnet/Entitlements/tvOS/Entitlements.plist new file mode 100644 index 0000000000..6631ffa6f2 --- /dev/null +++ b/tests/dotnet/Entitlements/tvOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/dotnet/Entitlements/tvOS/Makefile b/tests/dotnet/Entitlements/tvOS/Makefile new file mode 100644 index 0000000000..110d078f45 --- /dev/null +++ b/tests/dotnet/Entitlements/tvOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index cafccb063c..eb2c759979 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -996,5 +996,30 @@ namespace Xamarin.Tests { properties ["AppBundleDir"] = customAppBundleDir; var result = DotNet.AssertBuild (project_path, properties); } + + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-x64", "Release")] + [TestCase (ApplePlatform.MacOSX, "osx-arm64", "Debug")] + public void AutoAllowJitEntitlements (ApplePlatform platform, string runtimeIdentifiers, string configuration) + { + var project = "Entitlements"; + Configuration.IgnoreIfIgnoredPlatform (platform); + + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration); + Clean (project_path); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Configuration"] = configuration; + DotNet.AssertBuild (project_path, properties); + + var executable = GetNativeExecutable (platform, appPath); + var foundEntitlements = TryGetEntitlements (executable, out var entitlements); + if (configuration == "Release") { + Assert.IsTrue (foundEntitlements, "Found in Release"); + Assert.IsTrue (entitlements!.Get("com.apple.security.cs.allow-jit")?.Value, "Jit Allowed"); + } else { + var jitNotAllowed = !foundEntitlements || !entitlements!.ContainsKey ("com.apple.security.cs.allow-jit"); + Assert.True (jitNotAllowed, "Jit Not Allowed"); + } + } } } diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index 81d23e5b0f..64476599af 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -1,9 +1,11 @@ #nullable enable +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Mono.Cecil; +using Xamarin.MacDev; using Xamarin.Tests; namespace Xamarin.Tests { @@ -347,5 +349,25 @@ namespace Xamarin.Tests { } return false; } + + protected bool TryGetEntitlements (string nativeExecutable, [NotNullWhen (true)] out PDictionary? entitlements) + { + var entitlementsPath = Path.Combine (Cache.CreateTemporaryDirectory (), "EntitlementsInBinary.plist"); + var args = new string [] { + "--display", + "--entitlements", + entitlementsPath, + "--xml", + nativeExecutable + }; + var rv = ExecutionHelper.Execute ("codesign", args, out var codesignOutput, TimeSpan.FromSeconds (15)); + Assert.AreEqual (0, rv, $"'codesign {string.Join (" ", args)}' failed:\n{codesignOutput}"); + if (File.Exists (entitlementsPath)) { + entitlements = PDictionary.FromFile(entitlementsPath); + return true; + } + entitlements = null; + return false; + } } } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs index dcb0df7474..88767eae0d 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/CompileEntitlementsTaskTests.cs @@ -1,16 +1,20 @@ +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Utilities; +using Newtonsoft.Json.Linq; using NUnit.Framework; using Xamarin.iOS.Tasks; using Xamarin.MacDev; +#nullable enable + namespace Xamarin.MacDev.Tasks { class CustomCompileEntitlements : CompileEntitlements { - protected override MobileProvision GetMobileProvision (MobileProvisionPlatform platform, string uuid) + protected override MobileProvision? GetMobileProvision (MobileProvisionPlatform platform, string uuid) { if (File.Exists (ProvisioningProfile)) return MobileProvision.LoadFromFile (ProvisioningProfile); @@ -22,19 +26,9 @@ namespace Xamarin.MacDev.Tasks [TestFixture] public class CompileEntitlementsTaskTests : TestBase { - CustomCompileEntitlements task { - get; set; - } - - string compiledEntitlements { - get; set; - } - - public override void Setup () + CustomCompileEntitlements CreateEntitlementsTask (out string compiledEntitlements) { - base.Setup (); - - task = CreateTask (); + var task = CreateTask (); task.AppBundleDir = AppBundlePath; task.AppIdentifier = "32UV7A8CDE.com.xamarin.MySingleView"; @@ -48,11 +42,14 @@ namespace Xamarin.MacDev.Tasks task.TargetFrameworkMoniker = "Xamarin.iOS,v1.0"; compiledEntitlements = task.CompiledEntitlements.ItemSpec; + + return task; } [Test (Description = "Xambug #46298")] public void ValidateEntitlement () { + var task = CreateEntitlementsTask (out var compiledEntitlements); ExecuteTask (task); var compiled = PDictionary.FromFile (compiledEntitlements); Assert.IsTrue (compiled.Get (EntitlementKeys.GetTaskAllow).Value, "#1"); @@ -63,5 +60,146 @@ namespace Xamarin.MacDev.Tasks Assert.AreEqual ("Z8CSQKJE7R.com.xamarin.MySingleView", compiled.GetUbiquityKeyValueStore (), "#6"); Assert.AreEqual ("32UV7A8CDE.com.xamarin.MySingleView", compiled.GetKeychainAccessGroups ().ToStringArray ().First (), "#7"); } + + [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 ("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) + { + var dict = new Dictionary { + { "Type", type } + }; + if (value is not null) + dict ["Value"] = value; + + var customEntitlements = new TaskItem[] { + new TaskItem ("com.xamarin.custom.entitlement", dict) + }; + var task = CreateEntitlementsTask (out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask (task, expectedErrorCount: 1); + Assert.AreEqual (errorMessage, Engine.Logger.ErrorEvents [0].Message, "Error message"); + } + + [Test] + [TestCase ("a-string-value")] + [TestCase ("")] + [TestCase (null)] + public void CustomEntitlemements_String (string value) + { + var dict = new Dictionary { + { "Type", "String" }, + { "Value", value }, + }; + var customEntitlements = new TaskItem[] { + new TaskItem ("com.xamarin.custom.entitlement", dict) + }; + var task = CreateEntitlementsTask (out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask (task); + var compiled = PDictionary.FromFile (compiledEntitlements); + Assert.AreEqual (value ?? string.Empty, compiled.GetString ("com.xamarin.custom.entitlement")?.Value, "#1"); + } + + [Test] + public void CustomEntitlemements_StringArray () + { + var dict = new Dictionary { + { "Type", "StringArray" }, + { "Value", "A;B;C" }, + }; + var customEntitlements = new TaskItem [] { + new TaskItem ("com.xamarin.custom.entitlement", dict) + }; + var task = CreateEntitlementsTask (out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask (task); + var compiled = PDictionary.FromFile (compiledEntitlements); + var array = compiled.GetArray ("com.xamarin.custom.entitlement"); + Assert.NotNull (array, "array"); + Assert.AreEqual (new string [] { "A", "B", "C" }, array.ToStringArray (), "array contents"); + } + + [Test] + [TestCase (",")] + [TestCase ("😁")] + public void CustomEntitlemements_StringArray_CustomSeparator (string separator) + { + var dict = new Dictionary { + { "Type", "StringArray" }, + { "Value", $"A;B;C{separator}D{separator}E" }, + { "ArraySeparator", separator }, + }; + var customEntitlements = new TaskItem[] { + new TaskItem ("com.xamarin.custom.entitlement", dict) + }; + var task = CreateEntitlementsTask(out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask(task); + var compiled = PDictionary.FromFile(compiledEntitlements); + var array = compiled.GetArray ("com.xamarin.custom.entitlement"); + Assert.NotNull (array, "array"); + Assert.AreEqual (new string [] { "A;B;C", "D", "E" }, array.ToStringArray (), "array contents"); + } + + [Test] + public void AllowJit_Default () + { + var task = CreateEntitlementsTask (out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + ExecuteTask (task); + var compiled = PDictionary.FromFile (compiledEntitlements); + Assert.IsFalse (compiled.ContainsKey (EntitlementKeys.AllowExecutionOfJitCode), "#1"); + } + + [Test] + public void AllowJit_True () + { + var customEntitlements = new TaskItem[] { + new TaskItem ("com.apple.security.cs.allow-jit", new Dictionary { { "Type", "Boolean" }, { "Value", "true" } }), + }; + var task = CreateEntitlementsTask (out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask (task); + var compiled = PDictionary.FromFile(compiledEntitlements); + Assert.IsTrue (compiled.ContainsKey (EntitlementKeys.AllowExecutionOfJitCode), "#1"); + Assert.IsTrue (compiled.Get(EntitlementKeys.AllowExecutionOfJitCode).Value, "#2"); + } + + [Test] + public void AllowJit_False () + { + var customEntitlements = new TaskItem[] { + new TaskItem ("com.apple.security.cs.allow-jit", new Dictionary { { "Type", "Boolean" }, { "Value", "false" } }), + }; + var task = CreateEntitlementsTask(out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask(task); + var compiled = PDictionary.FromFile (compiledEntitlements); + Assert.IsTrue (compiled.ContainsKey (EntitlementKeys.AllowExecutionOfJitCode), "#1"); + Assert.IsFalse (compiled.Get (EntitlementKeys.AllowExecutionOfJitCode).Value, "#2"); + } + + [Test] + public void AllowJit_None () + { + var customEntitlements = new TaskItem[] { + new TaskItem ("com.apple.security.cs.allow-jit", new Dictionary { { "Type", "Remove" } }), + }; + var task = CreateEntitlementsTask(out var compiledEntitlements); + task.TargetFrameworkMoniker = ".NETCoreApp,Version=v6.0,Profile=maccatalyst"; + task.CustomEntitlements = customEntitlements; + ExecuteTask(task); + var compiled = PDictionary.FromFile(compiledEntitlements); + Assert.IsFalse (compiled.ContainsKey (EntitlementKeys.AllowExecutionOfJitCode), "#1"); + } + } }