Skip to content

Commit

Permalink
[msbuild] Automatically add the 'com.apple.security.cs.allow-jit' ent…
Browse files Browse the repository at this point in the history
…itlement for desktop release builds. Fixes #15745. (#15927)

* Add support for specifying custom entitlements with an MSBuild item group.
* Use this new support to automatically add the 'com.apple.security.cs.allow-jit'
  entitlement to .NET desktop apps when building for release, since all apps that
  go through notarization will need it in order to be able to use the JIT.

It's possible to override the default behavior by adding something like this to the project file:

    <ItemGroup>
        <CustomEntitlements Include="com.apple.security.cs.allow-jit" Type="Remove" />
    </ItemGroup>

Fixes #15745.
  • Loading branch information
rolfbjarne authored Sep 13, 2022
1 parent 7df80a4 commit 4019996
Show file tree
Hide file tree
Showing 23 changed files with 411 additions and 13 deletions.
27 changes: 27 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1425,4 +1425,29 @@
<data name="E7101" xml:space="preserve">
<value>Unknown property '{0}' with value '{1}'.</value>
</data>

<data name="E7102" xml:space="preserve">
<value>Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected no value at all.</value>
<comment>
Don't translate: CustomEntitlements (name of option in project file)
</comment>
</data>

<data name="E7103" xml:space="preserve">
<value>Invalid value '{0}' for the entitlement '{1}' of type '{2}' specified in the CustomEntitlements item group. Expected 'true' or 'false'.</value>
<comment>
Don't translate:
* CustomEntitlements (name of option in project file)
* 'true', 'false'
</comment>
</data>

<data name="E7104" xml:space="preserve">
<value>Unknown type '{0}' for the entitlement '{1}' specified in the CustomEntitlements item group. Expected 'Remove', 'Boolean', 'String', or 'StringArray'.</value>
<comment>
Don't translate:
* CustomEntitlements (name of option in project file)
* 'Remove', 'Boolean', 'String', 'StringArray'
</comment>
</data>
</root>
62 changes: 62 additions & 0 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlementsTaskBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public abstract class CompileEntitlementsTaskBase : XamarinTask
[Required]
public ITaskItem? CompiledEntitlements { get; set; }

public ITaskItem[] CustomEntitlements { get; set; } = Array.Empty<ITaskItem> ();

public bool Debug { get; set; }

public string Entitlements { get; set; } = string.Empty;
Expand Down Expand Up @@ -250,6 +252,64 @@ PDictionary MergeEntitlementDictionary (PDictionary dict, MobileProvision? profi
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:
// <ItemGroup>
// <CustomEntitlements Include="name.of.entitlement" Type="Boolean" Value="true" /> <!-- value can be 'false' too (case doesn't matter) -->
// <CustomEntitlements Include="name.of.entitlement" Type="String" Value="stringvalue" />
// <CustomEntitlements Include="name.of.entitlement" Type="StringArray" Value="a;b" /> <!-- array of strings, separated by semicolon -->
// <CustomEntitlements Include="name.of.entitlement" Type="StringArray" Value="a😁b" ArraySeparator="😁" /> <!-- array of strings, separated by 😁 -->
// <CustomEntitlements Include="name.of.entitlement" Type="Remove" /> <!-- This will remove the corresponding entitlement -->
// </ItemGroup>

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)
Expand Down Expand Up @@ -356,6 +416,8 @@ protected virtual PDictionary GetCompiledEntitlements (MobileProvision? profile,
break;
}

AddCustomEntitlements (entitlements);

return entitlements;
}

Expand Down
6 changes: 6 additions & 0 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -645,12 +645,18 @@ Copyright (C) 2018 Microsoft. All rights reserved.
Condition="'$(_RequireCodeSigning)' == 'true' Or '$(CodesignEntitlements)' != ''"
DependsOnTargets="$(_CompileEntitlementsDependsOn)"
Outputs="$(DeviceSpecificIntermediateOutputPath)Entitlements.xcent">
<!-- Automatically add the 'allow-jit' entitlement for desktop release builds in .NET -->
<ItemGroup Condition="'$(Configuration)' == 'Release' And ('$(_PlatformName)' == 'macOS' Or '$(_PlatformName)' == 'MacCatalyst') And '$(UsingAppleNETSdk)' == 'true'">
<!-- We need to compare the result of AnyHaveMetadataValue to 'true', because if it's empty, the return value is not a boolean, it's an empty string -->
<CustomEntitlements Condition="'@(CustomEntitlements->AnyHaveMetadataValue('Identity','com.apple.security.cs.allow-jit'))' != 'true'" Include="com.apple.security.cs.allow-jit" Type="Boolean" Value="true" />
</ItemGroup>
<CompileEntitlements
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AppBundleDir="$(AppBundleDir)"
AppIdentifier="$(_AppIdentifier)"
BundleIdentifier="$(_BundleIdentifier)"
CustomEntitlements="@(CustomEntitlements)"
Entitlements="$(CodesignEntitlements)"
CompiledEntitlements="$(DeviceSpecificIntermediateOutputPath)Entitlements.xcent"
IsAppExtension="$(IsAppExtension)"
Expand Down
19 changes: 19 additions & 0 deletions tests/dotnet/Entitlements/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Runtime.InteropServices;

using Foundation;

namespace MySimpleApp
{
public class Program
{
static int Main (string[] args)
{
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly

Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));

return args.Length;
}
}
}
7 changes: 7 additions & 0 deletions tests/dotnet/Entitlements/MacCatalyst/Entitlements.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
6 changes: 6 additions & 0 deletions tests/dotnet/Entitlements/MacCatalyst/Entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
1 change: 1 addition & 0 deletions tests/dotnet/Entitlements/MacCatalyst/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
2 changes: 2 additions & 0 deletions tests/dotnet/Entitlements/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TOP=../../..
include $(TOP)/tests/common/shared-dotnet-test.mk
7 changes: 7 additions & 0 deletions tests/dotnet/Entitlements/iOS/Entitlements.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
6 changes: 6 additions & 0 deletions tests/dotnet/Entitlements/iOS/Entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
1 change: 1 addition & 0 deletions tests/dotnet/Entitlements/iOS/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
7 changes: 7 additions & 0 deletions tests/dotnet/Entitlements/macOS/Entitlements.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-macos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
6 changes: 6 additions & 0 deletions tests/dotnet/Entitlements/macOS/Entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
1 change: 1 addition & 0 deletions tests/dotnet/Entitlements/macOS/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
13 changes: 13 additions & 0 deletions tests/dotnet/Entitlements/shared.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<OutputType>Exe</OutputType>
<ApplicationId>Entitlements</ApplicationId>
</PropertyGroup>

<Import Project="../../common/shared-dotnet.csproj" />

<ItemGroup>
<Compile Include="../*.cs" />
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions tests/dotnet/Entitlements/shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TOP=../../../..
TESTNAME=AutoDetectEntitlements
include $(TOP)/tests/common/shared-dotnet.mk
7 changes: 7 additions & 0 deletions tests/dotnet/Entitlements/tvOS/Entitlements.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-tvos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
6 changes: 6 additions & 0 deletions tests/dotnet/Entitlements/tvOS/Entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
1 change: 1 addition & 0 deletions tests/dotnet/Entitlements/tvOS/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
25 changes: 25 additions & 0 deletions tests/dotnet/UnitTests/ProjectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -996,5 +996,30 @@ public void CustomAppBundleDir (ApplePlatform platform, string runtimeIdentifier
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<PBoolean>("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");
}
}
}
}
22 changes: 22 additions & 0 deletions tests/dotnet/UnitTests/TestBaseClass.cs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -347,5 +349,25 @@ protected bool IsRuntimeIdentifierSigned (string runtimeIdentifiers)
}
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;
}
}
}
Loading

5 comments on commit 4019996

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.