Skip to content

Commit d588d0e

Browse files
authored
[dotnet] Improve entitlements lookup for AdditionalAppExtensions. Fixes #19242. (#22882)
* Add support for a 'CodesignEntitlements' metadata to the 'AdditionalAppExtensions' item group, to make it easier to specify the entitlements for such an app extension. * Warn if no entitlements are set for app extensions in 'AdditionalAppExtensions' items. Fixes #19242.
1 parent ca3aaa6 commit d588d0e

File tree

5 files changed

+88
-20
lines changed

5 files changed

+88
-20
lines changed

docs/building-apps/build-items.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,30 @@ application or library projects are built.
1313

1414
An item group that contains any additional app extensions to copy into the app bundle.
1515

16+
The following metadata can be set:
17+
18+
* Include: The path to the build directory for the Xcode app extension project.
19+
* Name: The name of the extension.
20+
* BuildOutput: This value is appended to the `Include` value to produce the location of the appex bundle. Typically Xcode will place simulator and device builds in different locations, so this can be used to have a single `AdditionalAppExtensions` entry pointing to two different appex bundles, depending on whether building for the simulator or device.
21+
* CodesignEntitlements: Specifies the entitlements to use when signing the app extension. The default value is '%(Name).entitlements' in the 'Include' build directory (if this file exists).
22+
* CodesignWarnIfNoEntitlements: A warning will be produced if no `CodesignEntitlements` value is set. This property can be set to `false` to silence this warning.
23+
24+
Example:
25+
26+
```xml
27+
<ItemGroup>
28+
<AdditionalAppExtensions Include="path/to/my.appex">
29+
<Name>MyAppExtensionName</Name>
30+
<BuildOutput Condition="'$(SdkIsSimulator)' == 'false'">DerivedData/MyAppExtensionName/Build/Products/Debug-iphoneos</BuildOutput>
31+
<BuildOutput Condition="'$(SdkIsSimulator)' == 'true'">DerivedData/MyAppExtensionName/Build/Products/Debug-iphonesimulator</BuildOutput>
32+
<CodesignEntitlements>path/to/Entitlements-appextension.plist</CodesignEntitlements>
33+
<CodesignWarnIfNoEntitlements>false</CodesignWarnIfNoEntitlements>
34+
</AdditionalAppExtensions>
35+
</ItemGroup>
36+
```
37+
38+
An example solution can be found here: [TestApplication](https://github.com/chamons/xamarin-ios-swift-extension/tree/master/App/net6/TestApplication).
39+
1640
## AlternateAppIcon
1741

1842
The `AlternateAppIcon` item group can be used to specify alternate app icons.

msbuild/Xamarin.MacDev.Tasks/Tasks/Codesign.cs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ string GetCodesignEntitlements (ITaskItem item)
221221
return rv;
222222
}
223223

224-
IList<string> GenerateCommandLineArguments (ITaskItem item)
224+
bool TryGenerateCommandLineArguments (ITaskItem item, out IList<string> args)
225225
{
226-
var args = new List<string> ();
226+
args = new List<string> ();
227227
var isDeep = ParseBoolean (item, "CodesignDeep", IsAppExtension);
228228
var useHardenedRuntime = ParseBoolean (item, "CodesignUseHardenedRuntime", UseHardenedRuntime);
229229
var useSecureTimestamp = ParseBoolean (item, "CodesignUseSecureTimestamp", UseSecureTimestamp);
@@ -234,6 +234,15 @@ IList<string> GenerateCommandLineArguments (ITaskItem item)
234234
var entitlements = GetCodesignEntitlements (item);
235235
var extraArgs = GetNonEmptyStringOrFallback (item, "CodesignExtraArgs", ExtraArgs);
236236

237+
if (!string.IsNullOrEmpty (entitlements)) {
238+
if (!File.Exists (entitlements)) {
239+
Log.LogError (MSBStrings.E0112, entitlements);
240+
return false;
241+
}
242+
} else if (ParseBoolean (item, "CodesignWarnIfNoEntitlements", false)) {
243+
Log.LogWarning ($"No entitlements set for {item.ItemSpec}.");
244+
}
245+
237246
args.Add ("-v");
238247
args.Add ("--force");
239248

@@ -290,14 +299,15 @@ IList<string> GenerateCommandLineArguments (ITaskItem item)
290299
path = PathUtils.ResolveSymbolicLinks (path);
291300
args.Add (Path.GetFullPath (path));
292301

293-
return args;
302+
return true;
294303
}
295304

296305
void Sign (SignInfo info)
297306
{
298307
var item = info.Item;
299308
var fileName = GetFullPathToTool ();
300-
var arguments = info.GetCommandLineArguments (this);
309+
if (!info.TryGetCommandLineArguments (this, out var arguments))
310+
return;
301311
var environment = new Dictionary<string, string?> () {
302312
{ "CODESIGN_ALLOCATE", GetCodesignAllocate (item) },
303313
};
@@ -320,14 +330,14 @@ void Sign (SignInfo info)
320330
Log.LogMessage (MessageImportance.Low, "No stamp file '{0}' available for the item '{1}'", stampFile, item.ItemSpec);
321331
} else if (IsUpToDate (item.ItemSpec, stampFile)) {
322332
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' is already up-to-date for the item '{1}', updating it anyway", stampFile, item.ItemSpec);
323-
File.WriteAllText (stampFile, info.GetStampFileContents (this));
333+
File.WriteAllText (stampFile, info.GetStampFileContents (this, arguments));
324334
} else if (File.Exists (stampFile)) {
325335
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' is not up-to-date for the item '{1}', and it will be updated", stampFile, item.ItemSpec);
326-
File.WriteAllText (stampFile, info.GetStampFileContents (this));
336+
File.WriteAllText (stampFile, info.GetStampFileContents (this, arguments));
327337
} else {
328338
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' does not exit for the item '{1}', and it will be created", stampFile, item.ItemSpec);
329339
Directory.CreateDirectory (Path.GetDirectoryName (stampFile)!);
330-
File.WriteAllText (stampFile, info.GetStampFileContents (this));
340+
File.WriteAllText (stampFile, info.GetStampFileContents (this, arguments));
331341
}
332342

333343
var additionalFilesToTouch = item.GetMetadata ("CodesignAdditionalFilesToTouch").Split (new char [] { ';' }, StringSplitOptions.RemoveEmptyEntries);
@@ -586,16 +596,24 @@ public SignInfo (ITaskItem item)
586596
Item = item;
587597
}
588598

589-
public IList<string> GetCommandLineArguments (Codesign task)
599+
public bool TryGetCommandLineArguments (Codesign task, out IList<string> arguments)
590600
{
591-
if (arguments is null)
592-
arguments = task.GenerateCommandLineArguments (Item);
593-
return arguments;
601+
if (this.arguments is null) {
602+
if (!task.TryGenerateCommandLineArguments (Item, out arguments))
603+
return false;
604+
this.arguments = arguments;
605+
}
606+
607+
arguments = this.arguments;
608+
609+
return true;
594610
}
595611

596-
public string GetStampFileContents (Codesign task)
612+
public string GetStampFileContents (Codesign task, IList<string>? arguments = null)
597613
{
598-
return string.Join (" ", GetCommandLineArguments (task));
614+
if (arguments is null)
615+
TryGetCommandLineArguments (task, out arguments);
616+
return string.Join (" ", arguments);
599617
}
600618
}
601619

msbuild/Xamarin.Shared/Xamarin.Shared.targets

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2544,6 +2544,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
25442544
</Target>
25452545

25462546
<Target Name="_ExtendAppExtensionReferences" DependsOnTargets="_ResolveAppExtensionReferences;_DetectSigningIdentity" Condition=" '@(AdditionalAppExtensions)' != ''">
2547+
<PropertyGroup>
2548+
<CodesignWarnIfNoEntitlements Condition="'$(CodesignWarnIfNoEntitlements)' == ''">true</CodesignWarnIfNoEntitlements>
2549+
</PropertyGroup>
25472550
<!-- The idea here is that after _ResolveAppExtensionReferences we inject the 3rd party extensions into the list being processed later for embedding and code signing.
25482551
- _ResolvedAppExtensionReferences is an item group of the path, so that's easy.
25492552
- _AppExtensionCodesignProperties less so. It is generated by reading codesign.items generated by the c# tasks during build, which we don't have.
@@ -2555,7 +2558,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
25552558
<CodesignAllocate>$(_CodesignAllocate)</CodesignAllocate>
25562559
<CodesignDisableTimestamp>false</CodesignDisableTimestamp>
25572560
<CodesignDisableTimestamp Condition="'$(_SdkIsSimulator)' == 'true' Or '$(_BundlerDebug)' == 'true'">true</CodesignDisableTimestamp>
2558-
<CodesignEntitlements Condition="Exists('%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements')">%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements</CodesignEntitlements>
2561+
<CodesignEntitlements Condition="'%(AdditionalAppExtensions.CodesignEntitlements)' != ''">%(AdditionalAppExtensions.CodesignEntitlements)</CodesignEntitlements>
2562+
<CodesignEntitlements Condition="'%(AdditionalAppExtensions.CodesignEntitlements)' == '' And Exists('%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements')">%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements</CodesignEntitlements>
2563+
<CodesignWarnIfNoEntitlements Condition="'%(AdditionalAppExtensions.CodesignWarnIfNoEntitlements)' == ''">$(CodesignWarnIfNoEntitlements)</CodesignWarnIfNoEntitlements>
25592564
<CodesignKeychain>$(CodesignKeychain)</CodesignKeychain>
25602565
<CodesignResourceRules>$(_PreparedResourceRules)</CodesignResourceRules>
25612566
<CodesignSigningKey>$(_CodeSigningKey)</CodesignSigningKey>

tests/dotnet/AdditionalAppExtensionConsumer/shared.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<AdditionalAppExtensions Include="$(AdditionalAppExtensionPath)">
2020
<Name>NativeIntentsExtension</Name>
2121
<BuildOutput>$(AdditionalAppExtensionBuildOutput)</BuildOutput>
22+
<CodesignEntitlements Condition="'$(AdditionalAppExtensionEntitlements)' != ''">$(AdditionalAppExtensionEntitlements)</CodesignEntitlements>
2223
</AdditionalAppExtensions>
2324
</ItemGroup>
2425
</Project>

tests/dotnet/UnitTests/ExtensionsTest.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
namespace Xamarin.Tests {
88
[TestFixture]
99
public class ExtensionsTest : TestBaseClass {
10-
[TestCase (ApplePlatform.iOS, "ios-arm64")]
11-
[TestCase (ApplePlatform.MacOSX, "osx-x64")]
12-
[TestCase (ApplePlatform.TVOS, "tvos-arm64")]
13-
public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeIdentifiers)
10+
[TestCase (ApplePlatform.iOS, "ios-arm64", null)]
11+
[TestCase (ApplePlatform.MacOSX, "osx-x64", null)]
12+
[TestCase (ApplePlatform.TVOS, "tvos-arm64", null)]
13+
[TestCase (ApplePlatform.iOS, "ios-arm64", "/does/not/exist")]
14+
public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeIdentifiers, string entitlements)
1415
{
1516
var project = "AdditionalAppExtensionConsumer";
1617
var extensionProject = "NativeIntentsExtension";
@@ -30,7 +31,7 @@ public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeId
3031
{ "DEVELOPER_DIR", Configuration.XcodeLocation },
3132
};
3233
foreach (var action in new string [] { "clean", "build" })
33-
ExecutionHelper.Execute ("/usr/bin/xcodebuild", xcodeBuildArgs.Concat (new [] { action }).ToArray (), environmentVariables: env, timeout: TimeSpan.FromMinutes (1), throwOnError: true);
34+
ExecutionHelper.Execute ("/usr/bin/xcodebuild", xcodeBuildArgs.Concat (new [] { action }).ToArray (), environmentVariables: env, timeout: TimeSpan.FromMinutes (1), throwOnError: true, hide_output: true);
3435

3536
string buildPlatform;
3637
switch (platform) {
@@ -49,7 +50,26 @@ public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeId
4950
var properties = GetDefaultProperties (runtimeIdentifiers);
5051
properties.Add ("AdditionalAppExtensionPath", xcodeProjectFolder);
5152
properties.Add ("AdditionalAppExtensionBuildOutput", $"build/{configuration}{buildPlatform}");
52-
var rv = DotNet.AssertBuild (project_path, properties);
53+
if (!string.IsNullOrEmpty (entitlements)) {
54+
properties.Add ("AdditionalAppExtensionEntitlements", entitlements);
55+
var rv = DotNet.AssertBuildFailure (project_path, properties);
56+
var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray ();
57+
AssertErrorMessages (errors, "Entitlements.plist template '/does/not/exist' not found.");
58+
return;
59+
} else {
60+
var rv = DotNet.AssertBuild (project_path, properties);
61+
var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath)
62+
.Where (v => v?.Message?.Contains ("Supported iPhone orientations have not been set") != true)
63+
.ToArray ();
64+
if (IsRuntimeIdentifierSigned (runtimeIdentifiers)) {
65+
var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), $"{extensionProject}.appex");
66+
AssertWarningMessages (warnings, [
67+
$"No entitlements set for {extensionPath}."
68+
]);
69+
} else {
70+
rv.AssertNoWarnings ();
71+
}
72+
}
5373

5474
var expectedDirectories = new List<string> ();
5575
if (IsRuntimeIdentifierSigned (runtimeIdentifiers)) {

0 commit comments

Comments
 (0)