Skip to content

Commit

Permalink
NativeAOT: Enable building app extensions with NativeAOT (#20872)
Browse files Browse the repository at this point in the history
### Description

This PR enables building app extensions with NativeAOT. 

App extensions are class libraries and to build them with NativeAOT we
must not specify `CustomNativeMain=true`. If we do, ILC would expect
that the input assembly has a managed Main as the module entry point.

Additionally, when building class libraries (with the absence of a
managed Main entry point), our static reference from:

https://github.com/xamarin/xamarin-macios/blob/2e5ef1eb1c14e185b29bbef091d227ed0c8cc875/runtime/nativeaot-bridge.m#L39

requires build-time symbol resolution. To avoid linking errors, we
generate an empty `__managed__Main`
in the native bootstrapping code of the app extension (e.g., in
`main.arm64.mm`).

### Testing

The unit tests have been introduced to test building app extensions with
both Mono and NativeAOT.
Executing an iOS app TodayExtension built with NativeAOT has been
verified manually on an actual device.

--- 
Fixes: #20653
  • Loading branch information
ivanpovazan authored Jul 19, 2024
1 parent e1290d2 commit 374e902
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 22 deletions.
9 changes: 7 additions & 2 deletions dotnet/targets/Xamarin.Shared.Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,12 @@
<!-- Disable our own assembly IL stripping logic, because ILC does that already -->
<EnableAssemblyILStripping>false</EnableAssemblyILStripping>

<!-- We're using our own native main function when using NativeAOT -->
<CustomNativeMain>true</CustomNativeMain>
<!--
We're using our own native main function when using NativeAOT.
This is true for both: managed executables and app extensions (which are libraries).
Since ILC expects to find a managed main function whenever NativeLib=static and CustomNativeMain=true,
we are only setting this flag when we are building executables. (Class libraries do not have a managed Main)
-->
<CustomNativeMain Condition="'$(OutputType)' == 'Exe'">true</CustomNativeMain>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion tests/dotnet/ExtensionConsumer/iOS/ExtensionConsumer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySimpleApp", "MySimpleApp.csproj", "{23664512-6B06-4135-9A94-C012BDA93CB1}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionConsumer", "ExtensionConsumer.csproj", "{23664512-6B06-4135-9A94-C012BDA93CB1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\iOS\ExtensionProject.csproj", "{8A72DB8F-4C30-4462-9F7A-6095E41D5D46}"
EndProject
Expand Down
2 changes: 1 addition & 1 deletion tests/dotnet/ExtensionConsumer/macOS/ExtensionConsumer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySimpleApp", "MySimpleApp.csproj", "{B7C29D40-0079-416C-8507-FE9EE82FBD4F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionConsumer", "ExtensionConsumer.csproj", "{B7C29D40-0079-416C-8507-FE9EE82FBD4F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\macOS\ExtensionProject.csproj", "{C32EB68F-1FF7-42DE-ABD8-C0151497595A}"
EndProject
Expand Down
2 changes: 1 addition & 1 deletion tests/dotnet/ExtensionConsumer/tvOS/ExtensionConsumer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySimpleApp", "MySimpleApp.csproj", "{D8448FDC-1002-432B-A3A7-CCFCB833F292}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionConsumer", "ExtensionConsumer.csproj", "{D8448FDC-1002-432B-A3A7-CCFCB833F292}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\tvOS\ExtensionProject.csproj", "{CD69BE1D-FF1B-4B6A-AB6E-5259E65B515E}"
EndProject
Expand Down
57 changes: 41 additions & 16 deletions tests/dotnet/UnitTests/ProjectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,46 +1063,71 @@ public void KillEverything ()
}


[TestCase (ApplePlatform.iOS)]
[TestCase (ApplePlatform.TVOS)]
[TestCase (ApplePlatform.MacOSX)]
[TestCase (ApplePlatform.iOS, "ios-arm64", false)]
[TestCase (ApplePlatform.iOS, "ios-arm64", true)]
[TestCase (ApplePlatform.iOS, "iossimulator-x64", false)]
[TestCase (ApplePlatform.iOS, "iossimulator-x64", true)]
[TestCase (ApplePlatform.TVOS, "tvossimulator-x64", false)]
[TestCase (ApplePlatform.TVOS, "tvossimulator-x64", true)]
[TestCase (ApplePlatform.MacOSX, "osx-x64", false)]
[TestCase (ApplePlatform.MacOSX, "osx-x64", true)]
[TestCase (ApplePlatform.MacOSX, "osx-x64;osx-arm64", false)]
// [TestCase (ApplePlatform.MacOSX, "osx-x64;osx-arm64", true)] See: https://github.com/xamarin/xamarin-macios/issues/20903
// [TestCase ("MacCatalyst", "")] - No extension support yet
public void BuildProjectsWithExtensions (ApplePlatform platform)
public void BuildProjectsWithExtensions (ApplePlatform platform, string runtimeIdentifier, bool isNativeAot)
{
Configuration.IgnoreIfIgnoredPlatform (platform);
var consumingProjectDir = GetProjectPath ("ExtensionConsumer", platform: platform);
var consumingProjectDir = GetProjectPath ("ExtensionConsumer", runtimeIdentifier, platform, out var appPath);
var extensionProjectDir = GetProjectPath ("ExtensionProject", platform: platform);

Clean (extensionProjectDir);
Clean (consumingProjectDir);

DotNet.AssertBuild (consumingProjectDir, verbosity);
var properties = GetDefaultProperties (runtimeIdentifier);

if (isNativeAot) {
properties ["PublishAot"] = "true";
properties ["_IsPublishing"] = "true";
}

DotNet.AssertBuild (consumingProjectDir, properties);

var extensionPath = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug", platform.ToFramework (), GetDefaultRuntimeIdentifier (platform), "MySimpleApp.app", GetPlugInsRelativePath (platform), "ExtensionProject.appex");
var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), "ExtensionProject.appex");
Assert.That (Directory.Exists (extensionPath), $"App extension directory does not exist: {extensionPath}");

var pathToSearch = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug");
string [] configFiles = Directory.GetFiles (pathToSearch, "*.runtimeconfig.*", SearchOption.AllDirectories);
Assert.AreNotEqual (0, configFiles.Length, "runtimeconfig.json file does not exist");
}

[TestCase (ApplePlatform.iOS)]
[TestCase (ApplePlatform.TVOS)]
[TestCase (ApplePlatform.MacOSX)]
[TestCase (ApplePlatform.iOS, "iossimulator-x64", false)]
[TestCase (ApplePlatform.iOS, "iossimulator-x64", true)]
[TestCase (ApplePlatform.TVOS, "tvossimulator-x64", false)]
[TestCase (ApplePlatform.TVOS, "tvossimulator-x64", true)]
[TestCase (ApplePlatform.MacOSX, "osx-x64", false)]
[TestCase (ApplePlatform.MacOSX, "osx-x64", true)]
[TestCase (ApplePlatform.MacOSX, "osx-x64;osx-arm64", false)]
// [TestCase (ApplePlatform.MacOSX, "osx-x64;osx-arm64", true)] See: https://github.com/xamarin/xamarin-macios/issues/20903
// [TestCase ("MacCatalyst", "")] - No extension support yet
public void BuildProjectsWithExtensionsAndFrameworks (ApplePlatform platform)
public void BuildProjectsWithExtensionsAndFrameworks (ApplePlatform platform, string runtimeIdentifier, bool isNativeAot)
{
Configuration.IgnoreIfIgnoredPlatform (platform);
var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform);
var consumingProjectDir = GetProjectPath ("ExtensionConsumerWithFrameworks", runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
var consumingProjectDir = GetProjectPath ("ExtensionConsumerWithFrameworks", runtimeIdentifiers: runtimeIdentifier, platform: platform, out var appPath);
var extensionProjectDir = GetProjectPath ("ExtensionProjectWithFrameworks", platform: platform);

Clean (extensionProjectDir);
Clean (consumingProjectDir);

DotNet.AssertBuild (consumingProjectDir, verbosity);
var properties = GetDefaultProperties (runtimeIdentifier);

if (isNativeAot) {
properties ["PublishAot"] = "true";
properties ["_IsPublishing"] = "true";
}

var extensionPath = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug", platform.ToFramework (), GetDefaultRuntimeIdentifier (platform), "ExtensionConsumerWithFrameworks.app", GetPlugInsRelativePath (platform), "ExtensionProjectWithFrameworks.appex");
DotNet.AssertBuild (consumingProjectDir, properties);

var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), "ExtensionProjectWithFrameworks.appex");
Assert.That (Directory.Exists (extensionPath), $"App extension directory does not exist: {extensionPath}");
var extensionFrameworksPath = Path.Combine (extensionPath, GetFrameworksRelativePath (platform));
Assert.IsFalse (Directory.Exists (extensionFrameworksPath), $"App extension framework directory exists when it shouldn't: {extensionFrameworksPath}");
Expand All @@ -1119,7 +1144,7 @@ public void BuildProjectsWithExtensionsAndFrameworks (ApplePlatform platform)
Assert.That (File.Exists (Path.Combine (appFrameworksPath, "UnknownE.framework", "UnknownE")), "UnknownE");

var appExecutable = GetNativeExecutable (platform, appPath);
ExecuteWithMagicWordAndAssert (platform, runtimeIdentifiers, appExecutable);
ExecuteWithMagicWordAndAssert (platform, runtimeIdentifier, appExecutable);
}


Expand Down
7 changes: 7 additions & 0 deletions tools/common/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,13 @@ void GenerateIOSMain (StringWriter sw, Abi abi)
sw.WriteLine ("\treturn rv;");
sw.WriteLine ("}");

// Add an empty __managed__Main function when building class lib app extensions with NativeAOT to workaround static reference to this symbol from nativeaot-bridge.m
if (app.IsExtension && app.XamarinRuntime == XamarinRuntime.NativeAOT) {
sw.WriteLine ();
sw.Write ("extern \"C\" int __managed__Main (int argc, const char** argv) { return 0; } ");
sw.WriteLine ();
}

string extension_main = null;
if (app.Platform == ApplePlatform.WatchOS && app.IsWatchExtension) {
// We're building a watch extension, and we have multiple scenarios, depending on the watchOS version we're executing on:
Expand Down
2 changes: 1 addition & 1 deletion tools/dotnet-linker/Steps/GenerateMainStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ protected override void TryEndProcess ()
if (app.IsTVExtension) {
extensionlib = "libtvextension-dotnet.a";
} else if (app.IsExtension) {
if (app.XamarinRuntime == Bundler.XamarinRuntime.CoreCLR) {
if (app.XamarinRuntime == Bundler.XamarinRuntime.CoreCLR || (app.XamarinRuntime == Bundler.XamarinRuntime.NativeAOT && app.Platform == Xamarin.Utils.ApplePlatform.MacOSX)) {
extensionlib = "libextension-dotnet-coreclr.a";
} else {
extensionlib = "libextension-dotnet.a";
Expand Down

3 comments on commit 374e902

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 [CI Build] Build failed 🔥

Build failed for the job 'Build macOS tests'

Pipeline on Agent
Hash: 374e9020756bdae22835b65d28a1f32e755b905d [CI build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ API diff for current PR / commit

Legacy Xamarin (No breaking changes)
  • iOS (no change detected)
  • tvOS (no change detected)
  • watchOS (no change detected)
  • macOS (no change detected)
NET (empty diffs)
  • iOS: (empty diff detected)
  • tvOS: (empty diff detected)
  • MacCatalyst: (empty diff detected)
  • macOS: (empty diff detected)

✅ API diff vs stable

Legacy Xamarin (No breaking changes)
.NET (No breaking changes)
Legacy Xamarin (stable) vs .NET

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 374e9020756bdae22835b65d28a1f32e755b905d [CI build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📚 [CI Build] Artifacts 📚

Packages generated

View packages

Pipeline on Agent
Hash: [CI build]

Please sign in to comment.