Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/building-apps/build-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ application or library projects are built.

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

The following metadata can be set:

* Include: The path to the build directory for the Xcode app extension project.
* Name: The name of the extension.
* 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.
* 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).
* CodesignWarnIfNoEntitlements: A warning will be produced if no `CodesignEntitlements` value is set. This property can be set to `false` to silence this warning.

Example:

```xml
<ItemGroup>
<AdditionalAppExtensions Include="path/to/my.appex">
<Name>MyAppExtensionName</Name>
<BuildOutput Condition="'$(SdkIsSimulator)' == 'false'">DerivedData/MyAppExtensionName/Build/Products/Debug-iphoneos</BuildOutput>
<BuildOutput Condition="'$(SdkIsSimulator)' == 'true'">DerivedData/MyAppExtensionName/Build/Products/Debug-iphonesimulator</BuildOutput>
<CodesignEntitlements>path/to/Entitlements-appextension.plist</CodesignEntitlements>
<CodesignWarnIfNoEntitlements>false</CodesignWarnIfNoEntitlements>
</AdditionalAppExtensions>
</ItemGroup>
```

An example solution can be found here: [TestApplication](https://github.com/chamons/xamarin-ios-swift-extension/tree/master/App/net6/TestApplication).

## AlternateAppIcon

The `AlternateAppIcon` item group can be used to specify alternate app icons.
Expand Down
44 changes: 31 additions & 13 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/Codesign.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ string GetCodesignEntitlements (ITaskItem item)
return rv;
}

IList<string> GenerateCommandLineArguments (ITaskItem item)
bool TryGenerateCommandLineArguments (ITaskItem item, out IList<string> args)
{
var args = new List<string> ();
args = new List<string> ();
var isDeep = ParseBoolean (item, "CodesignDeep", IsAppExtension);
var useHardenedRuntime = ParseBoolean (item, "CodesignUseHardenedRuntime", UseHardenedRuntime);
var useSecureTimestamp = ParseBoolean (item, "CodesignUseSecureTimestamp", UseSecureTimestamp);
Expand All @@ -234,6 +234,15 @@ IList<string> GenerateCommandLineArguments (ITaskItem item)
var entitlements = GetCodesignEntitlements (item);
var extraArgs = GetNonEmptyStringOrFallback (item, "CodesignExtraArgs", ExtraArgs);

if (!string.IsNullOrEmpty (entitlements)) {
if (!File.Exists (entitlements)) {
Log.LogError (MSBStrings.E0112, entitlements);
return false;
}
} else if (ParseBoolean (item, "CodesignWarnIfNoEntitlements", false)) {
Log.LogWarning ($"No entitlements set for {item.ItemSpec}.");
}

args.Add ("-v");
args.Add ("--force");

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

return args;
return true;
}

void Sign (SignInfo info)
{
var item = info.Item;
var fileName = GetFullPathToTool ();
var arguments = info.GetCommandLineArguments (this);
if (!info.TryGetCommandLineArguments (this, out var arguments))
return;
var environment = new Dictionary<string, string?> () {
{ "CODESIGN_ALLOCATE", GetCodesignAllocate (item) },
};
Expand All @@ -320,14 +330,14 @@ void Sign (SignInfo info)
Log.LogMessage (MessageImportance.Low, "No stamp file '{0}' available for the item '{1}'", stampFile, item.ItemSpec);
} else if (IsUpToDate (item.ItemSpec, stampFile)) {
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' is already up-to-date for the item '{1}', updating it anyway", stampFile, item.ItemSpec);
File.WriteAllText (stampFile, info.GetStampFileContents (this));
File.WriteAllText (stampFile, info.GetStampFileContents (this, arguments));
} else if (File.Exists (stampFile)) {
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);
File.WriteAllText (stampFile, info.GetStampFileContents (this));
File.WriteAllText (stampFile, info.GetStampFileContents (this, arguments));
} else {
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' does not exit for the item '{1}', and it will be created", stampFile, item.ItemSpec);
Directory.CreateDirectory (Path.GetDirectoryName (stampFile)!);
File.WriteAllText (stampFile, info.GetStampFileContents (this));
File.WriteAllText (stampFile, info.GetStampFileContents (this, arguments));
}

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

public IList<string> GetCommandLineArguments (Codesign task)
public bool TryGetCommandLineArguments (Codesign task, out IList<string> arguments)
{
if (arguments is null)
arguments = task.GenerateCommandLineArguments (Item);
return arguments;
if (this.arguments is null) {
if (!task.TryGenerateCommandLineArguments (Item, out arguments))
return false;
this.arguments = arguments;
}

arguments = this.arguments;

return true;
}

public string GetStampFileContents (Codesign task)
public string GetStampFileContents (Codesign task, IList<string>? arguments = null)
{
return string.Join (" ", GetCommandLineArguments (task));
if (arguments is null)
TryGetCommandLineArguments (task, out arguments);
return string.Join (" ", arguments);
}
}

Expand Down
7 changes: 6 additions & 1 deletion msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2544,6 +2544,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</Target>

<Target Name="_ExtendAppExtensionReferences" DependsOnTargets="_ResolveAppExtensionReferences;_DetectSigningIdentity" Condition=" '@(AdditionalAppExtensions)' != ''">
<PropertyGroup>
<CodesignWarnIfNoEntitlements Condition="'$(CodesignWarnIfNoEntitlements)' == ''">true</CodesignWarnIfNoEntitlements>
</PropertyGroup>
<!-- The idea here is that after _ResolveAppExtensionReferences we inject the 3rd party extensions into the list being processed later for embedding and code signing.
- _ResolvedAppExtensionReferences is an item group of the path, so that's easy.
- _AppExtensionCodesignProperties less so. It is generated by reading codesign.items generated by the c# tasks during build, which we don't have.
Expand All @@ -2555,7 +2558,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<CodesignAllocate>$(_CodesignAllocate)</CodesignAllocate>
<CodesignDisableTimestamp>false</CodesignDisableTimestamp>
<CodesignDisableTimestamp Condition="'$(_SdkIsSimulator)' == 'true' Or '$(_BundlerDebug)' == 'true'">true</CodesignDisableTimestamp>
<CodesignEntitlements Condition="Exists('%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements')">%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements</CodesignEntitlements>
<CodesignEntitlements Condition="'%(AdditionalAppExtensions.CodesignEntitlements)' != ''">%(AdditionalAppExtensions.CodesignEntitlements)</CodesignEntitlements>
<CodesignEntitlements Condition="'%(AdditionalAppExtensions.CodesignEntitlements)' == '' And Exists('%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements')">%(AdditionalAppExtensions.Identity)/%(AdditionalAppExtensions.Name).entitlements</CodesignEntitlements>
<CodesignWarnIfNoEntitlements Condition="'%(AdditionalAppExtensions.CodesignWarnIfNoEntitlements)' == ''">$(CodesignWarnIfNoEntitlements)</CodesignWarnIfNoEntitlements>
<CodesignKeychain>$(CodesignKeychain)</CodesignKeychain>
<CodesignResourceRules>$(_PreparedResourceRules)</CodesignResourceRules>
<CodesignSigningKey>$(_CodeSigningKey)</CodesignSigningKey>
Expand Down
1 change: 1 addition & 0 deletions tests/dotnet/AdditionalAppExtensionConsumer/shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<AdditionalAppExtensions Include="$(AdditionalAppExtensionPath)">
<Name>NativeIntentsExtension</Name>
<BuildOutput>$(AdditionalAppExtensionBuildOutput)</BuildOutput>
<CodesignEntitlements Condition="'$(AdditionalAppExtensionEntitlements)' != ''">$(AdditionalAppExtensionEntitlements)</CodesignEntitlements>
</AdditionalAppExtensions>
</ItemGroup>
</Project>
32 changes: 26 additions & 6 deletions tests/dotnet/UnitTests/ExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
namespace Xamarin.Tests {
[TestFixture]
public class ExtensionsTest : TestBaseClass {
[TestCase (ApplePlatform.iOS, "ios-arm64")]
[TestCase (ApplePlatform.MacOSX, "osx-x64")]
[TestCase (ApplePlatform.TVOS, "tvos-arm64")]
public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeIdentifiers)
[TestCase (ApplePlatform.iOS, "ios-arm64", null)]
[TestCase (ApplePlatform.MacOSX, "osx-x64", null)]
[TestCase (ApplePlatform.TVOS, "tvos-arm64", null)]
[TestCase (ApplePlatform.iOS, "ios-arm64", "/does/not/exist")]
public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeIdentifiers, string entitlements)
{
var project = "AdditionalAppExtensionConsumer";
var extensionProject = "NativeIntentsExtension";
Expand All @@ -30,7 +31,7 @@ public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeId
{ "DEVELOPER_DIR", Configuration.XcodeLocation },
};
foreach (var action in new string [] { "clean", "build" })
ExecutionHelper.Execute ("/usr/bin/xcodebuild", xcodeBuildArgs.Concat (new [] { action }).ToArray (), environmentVariables: env, timeout: TimeSpan.FromMinutes (1), throwOnError: true);
ExecutionHelper.Execute ("/usr/bin/xcodebuild", xcodeBuildArgs.Concat (new [] { action }).ToArray (), environmentVariables: env, timeout: TimeSpan.FromMinutes (1), throwOnError: true, hide_output: true);

string buildPlatform;
switch (platform) {
Expand All @@ -49,7 +50,26 @@ public void AdditionalAppExtensionTest (ApplePlatform platform, string runtimeId
var properties = GetDefaultProperties (runtimeIdentifiers);
properties.Add ("AdditionalAppExtensionPath", xcodeProjectFolder);
properties.Add ("AdditionalAppExtensionBuildOutput", $"build/{configuration}{buildPlatform}");
var rv = DotNet.AssertBuild (project_path, properties);
if (!string.IsNullOrEmpty (entitlements)) {
properties.Add ("AdditionalAppExtensionEntitlements", entitlements);
var rv = DotNet.AssertBuildFailure (project_path, properties);
var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).ToArray ();
AssertErrorMessages (errors, "Entitlements.plist template '/does/not/exist' not found.");
return;
} else {
var rv = DotNet.AssertBuild (project_path, properties);
var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath)
.Where (v => v?.Message?.Contains ("Supported iPhone orientations have not been set") != true)
.ToArray ();
if (IsRuntimeIdentifierSigned (runtimeIdentifiers)) {
var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), $"{extensionProject}.appex");
AssertWarningMessages (warnings, [
$"No entitlements set for {extensionPath}."
]);
} else {
rv.AssertNoWarnings ();
}
}

var expectedDirectories = new List<string> ();
if (IsRuntimeIdentifierSigned (runtimeIdentifiers)) {
Expand Down