NativeAOT: Enable building app extensions with NativeAOT (#20872)

### 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:

2e5ef1eb1c/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: https://github.com/xamarin/xamarin-macios/issues/20653
This commit is contained in:
Ivan Povazan 2024-07-19 14:03:49 +02:00 коммит произвёл GitHub
Родитель e1290d2f15
Коммит 374e902075
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 59 добавлений и 22 удалений

Просмотреть файл

@ -229,7 +229,12 @@
<!-- Disable our own assembly IL stripping logic, because ILC does that already --> <!-- Disable our own assembly IL stripping logic, because ILC does that already -->
<EnableAssemblyILStripping>false</EnableAssemblyILStripping> <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> </PropertyGroup>
</Project> </Project>

Просмотреть файл

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105 VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\iOS\ExtensionProject.csproj", "{8A72DB8F-4C30-4462-9F7A-6095E41D5D46}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\iOS\ExtensionProject.csproj", "{8A72DB8F-4C30-4462-9F7A-6095E41D5D46}"
EndProject EndProject

Просмотреть файл

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105 VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\macOS\ExtensionProject.csproj", "{C32EB68F-1FF7-42DE-ABD8-C0151497595A}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\macOS\ExtensionProject.csproj", "{C32EB68F-1FF7-42DE-ABD8-C0151497595A}"
EndProject EndProject

Просмотреть файл

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105 VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\tvOS\ExtensionProject.csproj", "{CD69BE1D-FF1B-4B6A-AB6E-5259E65B515E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionProject", "..\..\ExtensionProject\tvOS\ExtensionProject.csproj", "{CD69BE1D-FF1B-4B6A-AB6E-5259E65B515E}"
EndProject EndProject

Просмотреть файл

@ -1063,22 +1063,36 @@ namespace Xamarin.Tests {
} }
[TestCase (ApplePlatform.iOS)] [TestCase (ApplePlatform.iOS, "ios-arm64", false)]
[TestCase (ApplePlatform.TVOS)] [TestCase (ApplePlatform.iOS, "ios-arm64", true)]
[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 // [TestCase ("MacCatalyst", "")] - No extension support yet
public void BuildProjectsWithExtensions (ApplePlatform platform) public void BuildProjectsWithExtensions (ApplePlatform platform, string runtimeIdentifier, bool isNativeAot)
{ {
Configuration.IgnoreIfIgnoredPlatform (platform); Configuration.IgnoreIfIgnoredPlatform (platform);
var consumingProjectDir = GetProjectPath ("ExtensionConsumer", platform: platform); var consumingProjectDir = GetProjectPath ("ExtensionConsumer", runtimeIdentifier, platform, out var appPath);
var extensionProjectDir = GetProjectPath ("ExtensionProject", platform: platform); var extensionProjectDir = GetProjectPath ("ExtensionProject", platform: platform);
Clean (extensionProjectDir); Clean (extensionProjectDir);
Clean (consumingProjectDir); Clean (consumingProjectDir);
DotNet.AssertBuild (consumingProjectDir, verbosity); var properties = GetDefaultProperties (runtimeIdentifier);
var extensionPath = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug", platform.ToFramework (), GetDefaultRuntimeIdentifier (platform), "MySimpleApp.app", GetPlugInsRelativePath (platform), "ExtensionProject.appex"); if (isNativeAot) {
properties ["PublishAot"] = "true";
properties ["_IsPublishing"] = "true";
}
DotNet.AssertBuild (consumingProjectDir, properties);
var extensionPath = Path.Combine (appPath, GetPlugInsRelativePath (platform), "ExtensionProject.appex");
Assert.That (Directory.Exists (extensionPath), $"App extension directory does not exist: {extensionPath}"); Assert.That (Directory.Exists (extensionPath), $"App extension directory does not exist: {extensionPath}");
var pathToSearch = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug"); var pathToSearch = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug");
@ -1086,23 +1100,34 @@ namespace Xamarin.Tests {
Assert.AreNotEqual (0, configFiles.Length, "runtimeconfig.json file does not exist"); Assert.AreNotEqual (0, configFiles.Length, "runtimeconfig.json file does not exist");
} }
[TestCase (ApplePlatform.iOS)] [TestCase (ApplePlatform.iOS, "iossimulator-x64", false)]
[TestCase (ApplePlatform.TVOS)] [TestCase (ApplePlatform.iOS, "iossimulator-x64", true)]
[TestCase (ApplePlatform.MacOSX)] [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 // [TestCase ("MacCatalyst", "")] - No extension support yet
public void BuildProjectsWithExtensionsAndFrameworks (ApplePlatform platform) public void BuildProjectsWithExtensionsAndFrameworks (ApplePlatform platform, string runtimeIdentifier, bool isNativeAot)
{ {
Configuration.IgnoreIfIgnoredPlatform (platform); Configuration.IgnoreIfIgnoredPlatform (platform);
var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform); var consumingProjectDir = GetProjectPath ("ExtensionConsumerWithFrameworks", runtimeIdentifiers: runtimeIdentifier, platform: platform, out var appPath);
var consumingProjectDir = GetProjectPath ("ExtensionConsumerWithFrameworks", runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
var extensionProjectDir = GetProjectPath ("ExtensionProjectWithFrameworks", platform: platform); var extensionProjectDir = GetProjectPath ("ExtensionProjectWithFrameworks", platform: platform);
Clean (extensionProjectDir); Clean (extensionProjectDir);
Clean (consumingProjectDir); Clean (consumingProjectDir);
DotNet.AssertBuild (consumingProjectDir, verbosity); var properties = GetDefaultProperties (runtimeIdentifier);
var extensionPath = Path.Combine (Path.GetDirectoryName (consumingProjectDir)!, "bin", "Debug", platform.ToFramework (), GetDefaultRuntimeIdentifier (platform), "ExtensionConsumerWithFrameworks.app", GetPlugInsRelativePath (platform), "ExtensionProjectWithFrameworks.appex"); if (isNativeAot) {
properties ["PublishAot"] = "true";
properties ["_IsPublishing"] = "true";
}
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}"); Assert.That (Directory.Exists (extensionPath), $"App extension directory does not exist: {extensionPath}");
var extensionFrameworksPath = Path.Combine (extensionPath, GetFrameworksRelativePath (platform)); var extensionFrameworksPath = Path.Combine (extensionPath, GetFrameworksRelativePath (platform));
Assert.IsFalse (Directory.Exists (extensionFrameworksPath), $"App extension framework directory exists when it shouldn't: {extensionFrameworksPath}"); Assert.IsFalse (Directory.Exists (extensionFrameworksPath), $"App extension framework directory exists when it shouldn't: {extensionFrameworksPath}");
@ -1119,7 +1144,7 @@ namespace Xamarin.Tests {
Assert.That (File.Exists (Path.Combine (appFrameworksPath, "UnknownE.framework", "UnknownE")), "UnknownE"); Assert.That (File.Exists (Path.Combine (appFrameworksPath, "UnknownE.framework", "UnknownE")), "UnknownE");
var appExecutable = GetNativeExecutable (platform, appPath); var appExecutable = GetNativeExecutable (platform, appPath);
ExecuteWithMagicWordAndAssert (platform, runtimeIdentifiers, appExecutable); ExecuteWithMagicWordAndAssert (platform, runtimeIdentifier, appExecutable);
} }

Просмотреть файл

@ -897,6 +897,13 @@ namespace Xamarin.Bundler {
sw.WriteLine ("\treturn rv;"); sw.WriteLine ("\treturn rv;");
sw.WriteLine ("}"); 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; string extension_main = null;
if (app.Platform == ApplePlatform.WatchOS && app.IsWatchExtension) { 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: // We're building a watch extension, and we have multiple scenarios, depending on the watchOS version we're executing on:

Просмотреть файл

@ -87,7 +87,7 @@ namespace Xamarin {
if (app.IsTVExtension) { if (app.IsTVExtension) {
extensionlib = "libtvextension-dotnet.a"; extensionlib = "libtvextension-dotnet.a";
} else if (app.IsExtension) { } 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"; extensionlib = "libextension-dotnet-coreclr.a";
} else { } else {
extensionlib = "libextension-dotnet.a"; extensionlib = "libextension-dotnet.a";