diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets index 8a64f834b80..c01bf0248cd 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets @@ -29,4 +29,5 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets. + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyStores.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyStores.targets new file mode 100644 index 00000000000..1547a850e7a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyStores.targets @@ -0,0 +1,38 @@ + + + + + + + + + + + + <_CreateEmbeddedAssemblyStoreAssembly Include="@(_ShrunkUserAssemblies);@(_AndroidResolvedSatellitePaths);@(_ShrunkFrameworkAssemblies)"/> + <_CreateEmbeddedAssemblyStoreAssembly Condition=" '@(_CreateEmbeddedAssemblyStoreAssembly->Count())' == '0' " Include="@(_ResolvedUserAssemblies);@(_ResolvedFrameworkAssemblies);@(_AndroidResolvedSatellitePaths)" /> + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets index 17558b8efcd..b3285c44966 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets @@ -62,7 +62,6 @@ properties that determine build ordering. <_PrepareBuildApkDependsOnTargets> _SetLatestTargetFrameworkVersion; _GetLibraryImports; - _RemoveRegisterAttribute; _ResolveAssemblies; _ResolveSatellitePaths; _CreatePackageWorkspace; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectRuntimeConfigFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectRuntimeConfigFilesForArchive.cs deleted file mode 100644 index ca53119efcb..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CollectRuntimeConfigFilesForArchive.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.IO; -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Framework; - -namespace Xamarin.Android.Tasks; - -/// -/// Collects rc.bin to be added to the final archive. -/// -public class CollectRuntimeConfigFilesForArchive : AndroidTask -{ - const string ArchiveLibPath = "lib"; - - public override string TaskPrefix => "CRF"; - - [Required] - public string AndroidBinUtilsDirectory { get; set; } = ""; - - [Required] - public ITaskItem[] RuntimePackLibraryDirectories { get; set; } = Array.Empty (); - - [Required] - public string IntermediateOutputPath { get; set; } = ""; - - public string RuntimeConfigBinFilePath { get; set; } = ""; - - [Required] - public string [] SupportedAbis { get; set; } = []; - - [Output] - public ITaskItem [] FilesToAddToArchive { get; set; } = []; - - public override bool RunTask () - { - var files = new PackageFileListBuilder (); - var dsoWrapperConfig = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, RuntimePackLibraryDirectories, IntermediateOutputPath); - - // We will place rc.bin in the `lib` directory next to the blob, to make startup slightly faster, as we will find the config file right after we encounter - // our assembly store. Not only that, but also we'll be able to skip scanning the `base.apk` archive when split configs are enabled (which they are in 99% - // of cases these days, since AAB enforces that split). `base.apk` contains only ABI-agnostic file, while one of the split config files contains only - // ABI-specific data+code. - if (!string.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath)) { - foreach (var abi in SupportedAbis) { - // Prefix it with `a` because bundletool sorts entries alphabetically, and this will place it right next to `assemblies.*.blob.so`, which is what we - // like since we can finish scanning the zip central directory earlier at startup. - var inArchivePath = MakeArchiveLibPath (abi, "libarc.bin.so"); - var wrappedSourcePath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, MonoAndroidHelper.AbiToTargetArch (abi), RuntimeConfigBinFilePath, Path.GetFileName (inArchivePath)); - files.AddItem (wrappedSourcePath, inArchivePath); - } - } - - FilesToAddToArchive = files.ToArray (); - - return !Log.HasLoggedErrors; - } - - static string MakeArchiveLibPath (string abi, string fileName) => MonoAndroidHelper.MakeZipArchivePath (ArchiveLibPath, abi, fileName); -} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs index 9d6d324f678..8fed9a084b9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs @@ -2,12 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Threading; using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; namespace Xamarin.Android.Tasks @@ -37,109 +33,34 @@ sealed class Config public override System.Threading.Tasks.Task RunTaskAsync () { - return this.WhenAll (GetAssemblerConfigs (), RunAssembler); + var context = new NativeAssemblerCompilation.AssemblerRunContext ( + Log, + Path.GetFullPath (WorkingDirectory), + registerForCancellation: RegisterForCancellation, + cancel: Cancel + ); + + return this.WhenAll ( + GetAssemblerConfigs (), + (NativeAssemblerCompilation.AssemblerConfig config) => NativeAssemblerCompilation.RunAssembler (context, config) + ); } - void RunAssembler (Config config) + void RegisterForCancellation (Process proc) { - var stdout_completed = new ManualResetEvent (false); - var stderr_completed = new ManualResetEvent (false); - var psi = new ProcessStartInfo () { - FileName = config.AssemblerPath, - Arguments = config.AssemblerOptions, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - WorkingDirectory = WorkingDirectory, - }; - - string assemblerName = Path.GetFileName (config.AssemblerPath); - LogDebugMessage ($"[LLVM llc] {psi.FileName} {psi.Arguments}"); - - var stdoutLines = new List (); - var stderrLines = new List (); - - using (var proc = new Process ()) { - proc.OutputDataReceived += (s, e) => { - if (e.Data != null) { - OnOutputData (assemblerName, s, e); - stdoutLines.Add (e.Data); - } else - stdout_completed.Set (); - }; - - proc.ErrorDataReceived += (s, e) => { - if (e.Data != null) { - OnErrorData (assemblerName, s, e); - stderrLines.Add (e.Data); - } else - stderr_completed.Set (); - }; - - proc.StartInfo = psi; - proc.Start (); - proc.BeginOutputReadLine (); - proc.BeginErrorReadLine (); - CancellationToken.Register (() => { try { proc.Kill (); } catch (Exception) { } }); - proc.WaitForExit (); - - if (psi.RedirectStandardError) - stderr_completed.WaitOne (TimeSpan.FromSeconds (30)); - - if (psi.RedirectStandardOutput) - stdout_completed.WaitOne (TimeSpan.FromSeconds (30)); - - if (proc.ExitCode != 0) { - var sb = MonoAndroidHelper.MergeStdoutAndStderrMessages (stdoutLines, stderrLines); - LogCodedError ("XA3006", Properties.Resources.XA3006, Path.GetFileName (config.InputSource), sb.ToString ()); - Cancel (); + CancellationToken.Register (() => { + try { + proc.Kill (); + } catch (Exception) { } - } + }); } - IEnumerable GetAssemblerConfigs () + IEnumerable GetAssemblerConfigs () { - const string assemblerOptions = - "-O2 " + - "--debugger-tune=lldb " + // NDK uses lldb now - "--debugify-level=location+variables " + - "--fatal-warnings " + - "--filetype=obj " + - "--relocation-model=pic"; - string llcPath = Path.Combine (AndroidBinUtilsDirectory, "llc"); - foreach (ITaskItem item in Sources) { - // We don't need the directory since our WorkingDirectory is where all the sources are - string sourceFile = Path.GetFileName (item.ItemSpec); - string outputFile = QuoteFileName (sourceFile.Replace (".ll", ".o")); - string executableDir = Path.GetDirectoryName (llcPath); - string executableName = MonoAndroidHelper.GetExecutablePath (executableDir, Path.GetFileName (llcPath)); - - yield return new Config { - InputSource = item.ItemSpec, - AssemblerPath = Path.Combine (executableDir, executableName), - AssemblerOptions = $"{assemblerOptions} -o={outputFile} {QuoteFileName (sourceFile)}", - }; + yield return NativeAssemblerCompilation.GetAssemblerConfig (AndroidBinUtilsDirectory, item, stripFilePaths: true); } } - - void OnOutputData (string assemblerName, object sender, DataReceivedEventArgs e) - { - LogDebugMessage ($"[{assemblerName} stdout] {e.Data}"); - } - - void OnErrorData (string assemblerName, object sender, DataReceivedEventArgs e) - { - LogMessage ($"[{assemblerName} stderr] {e.Data}", MessageImportance.High); - } - - static string QuoteFileName (string fileName) - { - var builder = new CommandLineBuilder (); - builder.AppendFileNameIfNotNull (fileName); - return builder.ToString (); - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs index eb0a3724295..55ef0d7252d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs @@ -36,49 +36,16 @@ public class CreateAssemblyStore : AndroidTask public override bool RunTask () { // Get all the user and framework assemblies we may need to package - var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(ShouldSkipAssembly (asm))).ToArray (); + var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(AssemblyPackagingHelper.ShouldSkipAssembly (Log, asm))); if (!UseAssemblyStore) { - AssembliesToAddToArchive = assemblies; + AssembliesToAddToArchive = assemblies.ToArray (); return !Log.HasLoggedErrors; } - var store_builder = new AssemblyStoreBuilder (Log); - var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, SupportedAbis, true); - - foreach (var kvp in per_arch_assemblies) { - Log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'"); - - foreach (var assembly in kvp.Value.Values) { - var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec); - store_builder.AddAssembly (sourcePath, assembly, includeDebugSymbols: IncludeDebugSymbols); - - Log.LogDebugMessage ($"Added '{sourcePath}' to assembly store."); - } - } - - var assembly_store_paths = store_builder.Generate (AppSharedLibrariesDir); - - if (assembly_store_paths.Count == 0) { - throw new InvalidOperationException ("Assembly store generator did not generate any stores"); - } - - if (assembly_store_paths.Count != SupportedAbis.Length) { - throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI"); - } - + var assembly_store_paths = AssemblyPackagingHelper.CreateAssemblyStore (Log, assemblies, AppSharedLibrariesDir, SupportedAbis, IncludeDebugSymbols); AssembliesToAddToArchive = assembly_store_paths.Select (kvp => new TaskItem (kvp.Value, new Dictionary { { "Abi", MonoAndroidHelper.ArchToAbi (kvp.Key) } })).ToArray (); return !Log.HasLoggedErrors; } - - bool ShouldSkipAssembly (ITaskItem asm) - { - var should_skip = asm.GetMetadataOrDefault ("AndroidSkipAddToPackage", false); - - if (should_skip) - Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' "); - - return should_skip; - } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateEmbeddedAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateEmbeddedAssemblyStore.cs new file mode 100644 index 00000000000..c75474ef7aa --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateEmbeddedAssemblyStore.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +public class CreateEmbeddedAssemblyStore : AndroidTask +{ + public override string TaskPrefix => "CEAS"; + + [Required] + public string AndroidBinUtilsDirectory { get; set; } = ""; + + [Required] + public string AppSharedLibrariesDir { get; set; } = ""; + + [Required] + public string AssemblySourcesDir { get; set; } = ""; + + [Required] + public bool AssemblyStoreEmbeddedInRuntime { get; set; } + + [Required] + public bool IncludeDebugSymbols { get; set; } + + [Required] + public ITaskItem[] ResolvedUserAssemblies { get; set; } = []; + + [Required] + public ITaskItem[] ResolvedFrameworkAssemblies { get; set; } = []; + + [Required] + public string [] SupportedAbis { get; set; } = []; + + public override bool RunTask () + { + if (AssemblyStoreEmbeddedInRuntime) { + return EmbedAssemblyStore (); + } + + // Generate sources to satisfy libmonodroid's ABI requirements + foreach (string abi in SupportedAbis) { + ELFEmbeddingHelper.EmbedBinary ( + Log, + abi, + AndroidBinUtilsDirectory, + inputFile: null, + ELFEmbeddingHelper.KnownEmbedItems.AssemblyStore, + AssemblySourcesDir, + missingContentOK: true + ); + } + + return !Log.HasLoggedErrors; + } + + bool EmbedAssemblyStore () + { + var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(AssemblyPackagingHelper.ShouldSkipAssembly (Log, asm))); + var assemblyStorePaths = AssemblyPackagingHelper.CreateAssemblyStore ( + Log, assemblies, + Path.Combine (AppSharedLibrariesDir, "embedded"), + SupportedAbis, + IncludeDebugSymbols + ); + + foreach (var kvp in assemblyStorePaths) { + string abi = MonoAndroidHelper.ArchToAbi (kvp.Key); + string inputFile = kvp.Value; + + ELFEmbeddingHelper.EmbedBinary ( + Log, + abi, + AndroidBinUtilsDirectory, + inputFile, + ELFEmbeddingHelper.KnownEmbedItems.AssemblyStore, + AssemblySourcesDir, + missingContentOK: false + ); + } + + return !Log.HasLoggedErrors; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs index fd6b78fcebd..be5346385bf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs @@ -66,7 +66,7 @@ void GenerateCompressedAssemblySources () var assemblyKey = CompressedAssemblyInfo.GetDictionaryKey (assembly); if (assemblies.ContainsKey (assemblyKey)) { - Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec} (arch {MonoAndroidHelper.GetAssemblyAbi(assembly)})"); + Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec} (arch {MonoAndroidHelper.GetItemAbi(assembly)})"); continue; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs index c905016a9c3..acb45995b70 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs @@ -8,6 +8,7 @@ using System.Reflection.PortableExecutable; using System.Text; using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; using Java.Interop.Tools.TypeNameMappings; using Xamarin.Android.Tools; @@ -50,6 +51,9 @@ public class GenerateNativeApplicationConfigSources : AndroidTask [Required] public bool TargetsCLR { get; set; } + [Required] + public string AndroidBinUtilsDirectory { get; set; } = ""; + public bool EnableMarshalMethods { get; set; } public bool EnableManagedMarshalMethodsLookup { get; set; } public string? RuntimeConfigBinFilePath { get; set; } @@ -234,6 +238,10 @@ public override bool RunTask () } var uniqueNativeLibraries = new List (); + + // Number of DSOs that will be packaged, it may be different to the number of items in the above + // `uniqueNativeLibraries` list. + uint packagedNativeLibrariesCount = 0; var seenNativeLibraryNames = new HashSet (StringComparer.OrdinalIgnoreCase); if (NativeLibraries != null) { foreach (ITaskItem item in NativeLibraries) { @@ -243,12 +251,21 @@ public override bool RunTask () continue; } + if (!ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec)) { + packagedNativeLibrariesCount++; + } + seenNativeLibraryNames.Add (name); uniqueNativeLibraries.Add (item); } + + // libxamarin-app.so is not in NativeLibraries, but we must count it + if (!seenNativeLibraryNames.Contains ("libxamarin-app.so")) { + uniqueNativeLibraries.Add (new TaskItem ("libxamarin-app.so")); + packagedNativeLibrariesCount++; + } } - bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build); LLVMIR.LlvmIrComposer appConfigAsmGen; @@ -262,6 +279,7 @@ public override bool RunTask () NumberOfAssembliesInApk = assemblyCount, BundledAssemblyNameWidth = assemblyNameWidth, NativeLibraries = uniqueNativeLibraries, + PackagedNativeLibrariesCount = packagedNativeLibrariesCount, AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, JNIEnvInitializeToken = jnienv_initialize_method_token, JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, @@ -272,6 +290,17 @@ public override bool RunTask () IgnoreSplitConfigs = ShouldIgnoreSplitConfigs (), }; } else { + bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); + ELFEmbeddingHelper.EmbedBinary ( + Log, + SupportedAbis, + AndroidBinUtilsDirectory, + RuntimeConfigBinFilePath, + ELFEmbeddingHelper.KnownEmbedItems.RuntimeConfig, + EnvironmentOutputDirectory, + missingContentOK: !haveRuntimeConfigBlob + ); + appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, @@ -283,11 +312,11 @@ public override bool RunTask () PackageNamingPolicy = pnp, BoundExceptionType = boundExceptionType, JniAddNativeMethodRegistrationAttributePresent = NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent, - HaveRuntimeConfigBlob = haveRuntimeConfigBlob, NumberOfAssembliesInApk = assemblyCount, BundledAssemblyNameWidth = assemblyNameWidth, MonoComponents = (MonoComponent)monoComponents, NativeLibraries = uniqueNativeLibraries, + PackagedNativeLibrariesCount = packagedNativeLibrariesCount, HaveAssemblyStore = UseAssemblyStore, AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, JNIEnvInitializeToken = jnienv_initialize_method_token, diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs index cad5439df21..1f9148b4f9f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using System.Collections.Generic; using Microsoft.Build.Framework; @@ -10,13 +8,6 @@ namespace Xamarin.Android.Tasks { public class PrepareAbiItems : AndroidTask { - const string ArmV7a = "armeabi-v7a"; - const string TypeMapBase = "typemaps"; - const string EnvBase = "environment"; - const string CompressedAssembliesBase = "compressed_assemblies"; - const string JniRemappingBase = "jni_remap"; - const string MarshalMethodsBase = "marshal_methods"; - public override string TaskPrefix => "PAI"; [Required] @@ -28,9 +19,6 @@ public class PrepareAbiItems : AndroidTask [Required] public string Mode { get; set; } = ""; - [Required] - public bool Debug { get; set; } - [Output] public ITaskItem[]? AssemblySources { get; set; } @@ -40,33 +28,17 @@ public class PrepareAbiItems : AndroidTask public override bool RunTask () { var sources = new List (); - var includes = new List (); - string baseName; - - if (String.Compare ("typemap", Mode, StringComparison.OrdinalIgnoreCase) == 0) { - baseName = TypeMapBase; - } else if (String.Compare ("environment", Mode, StringComparison.OrdinalIgnoreCase) == 0) { - baseName = EnvBase; - } else if (String.Compare ("compressed", Mode, StringComparison.OrdinalIgnoreCase) == 0) { - baseName = CompressedAssembliesBase; - } else if (String.Compare ("jniremap", Mode, StringComparison.OrdinalIgnoreCase) == 0) { - baseName = JniRemappingBase; - } else if (String.Compare ("marshal_methods", Mode, StringComparison.OrdinalIgnoreCase) == 0) { - baseName = MarshalMethodsBase; - } else { - Log.LogError ($"Unknown mode: {Mode}"); - return false; - } TaskItem item; + NativeAssemblerItemsHelper.KnownMode mode = NativeAssemblerItemsHelper.ToKnownMode (Mode); foreach (string abi in BuildTargetAbis) { - item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{abi}.ll")); + item = new TaskItem (NativeAssemblerItemsHelper.GetSourcePath (Log, mode, NativeSourcesDir, abi)); item.SetMetadata ("abi", abi); + item.SetMetadata ("RuntimeIdentifier", MonoAndroidHelper.AbiToRid (abi)); sources.Add (item); } AssemblySources = sources.ToArray (); - AssemblyIncludes = includes.ToArray (); return !Log.HasLoggedErrors; } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index 21f48d77025..5931e9843e5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -129,7 +129,6 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto "System.Collections.dll", "System.Collections.Concurrent.dll", "System.Text.RegularExpressions.dll", - "libarc.bin.so", }; using (var b = CreateApkBuilder ()) { @@ -797,5 +796,46 @@ void CreateEmptyFile (string path) } } + [Test] + [TestCase (false)] + [TestCase (true)] + public void CheckEmbeddedAssemblyStorePackaging (bool useCLR) + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true + }; + + AndroidTargetArch[] supportedArches = new[] { + AndroidTargetArch.Arm64, + AndroidTargetArch.X86_64, + }; + + proj.SetRuntimeIdentifiers (supportedArches); + proj.SetProperty ("AndroidUseAssemblyStore", "true"); + proj.SetProperty ("_AndroidEmbedAssemblyStoreInRuntime", "true"); + proj.SetProperty ("UseMonoRuntime", useCLR ? "false" : "true"); + + using var b = CreateApkBuilder (); + Assert.IsTrue (b.Build (proj), "build should have succeeded."); + string apk = Path.Combine (Root, b.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk"); + + var knownAssemblyStoreFiles = new HashSet (StringComparer.Ordinal); + foreach (AndroidTargetArch arch in supportedArches) { + string archName = MonoAndroidHelper.ArchToAbi (arch); + knownAssemblyStoreFiles.Add ($"lib/{archName}/libassemblies.{archName}.blob.so"); + } + + var foundAssemblyStoreFiles = new HashSet (StringComparer.Ordinal); + using var zip = ZipHelper.OpenZip (apk); + + foreach (var entry in zip) { + if (knownAssemblyStoreFiles.Contains (entry.FullName)) { + foundAssemblyStoreFiles.Add (entry.FullName); + } + } + + Assert.IsFalse (foundAssemblyStoreFiles.Count != 0, $"APK should not contain any of the files: {FoundFiles ()}"); + string FoundFiles () => String.Join (", ", foundAssemblyStoreFiles); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 7a86601ce43..7aff38ae573 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -45,7 +45,6 @@ public sealed class ApplicationConfig public bool uses_assembly_preload; public bool broken_exception_transitions; public bool jni_add_native_method_registration_attribute_present; - public bool have_runtime_config_blob; public bool have_assemblies_blob; public bool marshal_methods_enabled; public bool ignore_split_configs; @@ -68,7 +67,7 @@ public sealed class ApplicationConfig public bool managed_marshal_methods_lookup_enabled; } - const uint ApplicationConfigFieldCount = 27; + const uint ApplicationConfigFieldCount = 26; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -237,107 +236,102 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile) ret.jni_add_native_method_registration_attribute_present = ConvertFieldToBool ("jni_add_native_method_registration_attribute_present", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 6: // have_runtime_config_blob: bool / .byte - AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); - ret.have_runtime_config_blob = ConvertFieldToBool ("have_runtime_config_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); - break; - - case 7: // have_assemblies_blob: bool / .byte + case 6: // have_assemblies_blob: bool / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 8: // marshal_methods_enabled: bool / .byte + case 7: // marshal_methods_enabled: bool / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.marshal_methods_enabled = ConvertFieldToBool ("marshal_methods_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 9: // ignore_split_configs: bool / .byte + case 8: // ignore_split_configs: bool / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.ignore_split_configs = ConvertFieldToBool ("ignore_split_configs", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 10: // bound_stream_io_exception_type: byte / .byte + case 9: // bound_stream_io_exception_type: byte / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 11: // package_naming_policy: uint32_t / .word | .long + case 10: // package_naming_policy: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 12: // environment_variable_count: uint32_t / .word | .long + case 11: // environment_variable_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 13: // system_property_count: uint32_t / .word | .long + case 12: // system_property_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 14: // number_of_assemblies_in_apk: uint32_t / .word | .long + case 13: // number_of_assemblies_in_apk: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 15: // bundled_assembly_name_width: uint32_t / .word | .long + case 14: // bundled_assembly_name_width: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 16: // number_of_assembly_store_files: uint32_t / .word | .long + case 15: // number_of_assembly_store_files: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 17: // number_of_dso_cache_entries: uint32_t / .word | .long + case 16: // number_of_dso_cache_entries: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 18: // number_of_aot_cache_entries: uint32_t / .word | .long + case 17: // number_of_aot_cache_entries: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_aot_cache_entries = ConvertFieldToUInt32 ("number_of_aot_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 19: // android_runtime_jnienv_class_token: uint32_t / .word | .long + case 18: // android_runtime_jnienv_class_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.android_runtime_jnienv_class_token = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 20: // jnienv_initialize_method_token: uint32_t / .word | .long + case 19: // jnienv_initialize_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jnienv_initialize_method_token = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 21: // jnienv_registerjninatives_method_token: uint32_t / .word | .long + case 20: // jnienv_registerjninatives_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jnienv_registerjninatives_method_token = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 22: // jni_remapping_replacement_type_count: uint32_t / .word | .long + case 21: // jni_remapping_replacement_type_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 23: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long + case 22: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 24: // mono_components_mask: uint32_t / .word | .long + case 23: // mono_components_mask: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 25: // android_package_name: string / [pointer type] + case 24: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; - case 26: // managed_marshal_methods_lookup_enabled: bool / .byte + case 25: // managed_marshal_methods_lookup_enabled: bool / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.managed_marshal_methods_lookup_enabled = ConvertFieldToBool ("managed_marshal_methods_lookup_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 0d09c5250f4..53233030a42 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,31 +5,31 @@ "Size": 3036 }, "classes.dex": { - "Size": 22484 + "Size": 22488 }, "lib/arm64-v8a/lib__Microsoft.Android.Resource.Designer.dll.so": { "Size": 18288 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 86688 + "Size": 87624 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 117712 + "Size": 120768 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 22384 + "Size": 23160 }, "lib/arm64-v8a/lib_System.Console.dll.so": { - "Size": 24392 + "Size": 24408 }, "lib/arm64-v8a/lib_System.Linq.dll.so": { - "Size": 25336 + "Size": 25344 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 628216 + "Size": 636760 }, "lib/arm64-v8a/lib_System.Runtime.dll.so": { - "Size": 20056 + "Size": 20096 }, "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { "Size": 21480 @@ -37,41 +37,38 @@ "lib/arm64-v8a/lib_UnnamedProject.dll.so": { "Size": 20024 }, - "lib/arm64-v8a/libarc.bin.so": { - "Size": 18872 - }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { - "Size": 36440 + "Size": 36616 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1524752 + "Size": 1525968 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 3101112 + "Size": 3118632 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { - "Size": 71976 + "Size": 71952 }, "lib/arm64-v8a/libSystem.IO.Compression.Native.so": { - "Size": 758896 + "Size": 759304 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 103520 + "Size": 104312 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { - "Size": 165000 + "Size": 165240 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 19536 + "Size": 22384 }, "META-INF/BNDLTOOL.RSA": { - "Size": 1221 + "Size": 1223 }, "META-INF/BNDLTOOL.SF": { - "Size": 3266 + "Size": 3167 }, "META-INF/MANIFEST.MF": { - "Size": 3139 + "Size": 3040 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 @@ -98,5 +95,5 @@ "Size": 1904 } }, - "PackageSize": 3078677 + "PackageSize": 3099084 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index cf5b327b70c..c397f8e99d2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -5,7 +5,7 @@ "Size": 6652 }, "classes.dex": { - "Size": 9179108 + "Size": 9173200 }, "kotlin/annotation/annotation.kotlin_builtins": { "Size": 928 @@ -35,13 +35,13 @@ "Size": 25424 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 96104 + "Size": 96320 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 562336 + "Size": 562416 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 23224 + "Size": 23200 }, "lib/arm64-v8a/lib_mscorlib.dll.so": { "Size": 21456 @@ -50,25 +50,25 @@ "Size": 23096 }, "lib/arm64-v8a/lib_System.Collections.Concurrent.dll.so": { - "Size": 29896 + "Size": 29904 }, "lib/arm64-v8a/lib_System.Collections.dll.so": { "Size": 36304 }, "lib/arm64-v8a/lib_System.Collections.NonGeneric.dll.so": { - "Size": 25776 + "Size": 25784 }, "lib/arm64-v8a/lib_System.Collections.Specialized.dll.so": { "Size": 23856 }, "lib/arm64-v8a/lib_System.ComponentModel.dll.so": { - "Size": 19608 + "Size": 19600 }, "lib/arm64-v8a/lib_System.ComponentModel.Primitives.dll.so": { - "Size": 21336 + "Size": 21328 }, "lib/arm64-v8a/lib_System.ComponentModel.TypeConverter.dll.so": { - "Size": 42440 + "Size": 42432 }, "lib/arm64-v8a/lib_System.Console.dll.so": { "Size": 24440 @@ -80,25 +80,25 @@ "Size": 24704 }, "lib/arm64-v8a/lib_System.dll.so": { - "Size": 19856 + "Size": 19864 }, "lib/arm64-v8a/lib_System.Drawing.dll.so": { - "Size": 19456 + "Size": 19448 }, "lib/arm64-v8a/lib_System.Drawing.Primitives.dll.so": { "Size": 30064 }, "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": { - "Size": 50312 + "Size": 50320 }, "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": { - "Size": 29496 + "Size": 29504 }, "lib/arm64-v8a/lib_System.IO.Compression.dll.so": { "Size": 33800 }, "lib/arm64-v8a/lib_System.IO.IsolatedStorage.dll.so": { - "Size": 28336 + "Size": 28328 }, "lib/arm64-v8a/lib_System.Linq.dll.so": { "Size": 40696 @@ -107,7 +107,7 @@ "Size": 185864 }, "lib/arm64-v8a/lib_System.Net.Http.dll.so": { - "Size": 85904 + "Size": 85856 }, "lib/arm64-v8a/lib_System.Net.Primitives.dll.so": { "Size": 42184 @@ -116,13 +116,13 @@ "Size": 21568 }, "lib/arm64-v8a/lib_System.ObjectModel.dll.so": { - "Size": 27088 + "Size": 27096 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 967200 + "Size": 968480 }, "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": { - "Size": 216496 + "Size": 216504 }, "lib/arm64-v8a/lib_System.Private.Uri.dll.so": { "Size": 62728 @@ -137,10 +137,10 @@ "Size": 20264 }, "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { - "Size": 21488 + "Size": 21480 }, "lib/arm64-v8a/lib_System.Runtime.Numerics.dll.so": { - "Size": 55784 + "Size": 55840 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.dll.so": { "Size": 19376 @@ -149,16 +149,16 @@ "Size": 20352 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.Primitives.dll.so": { - "Size": 21472 + "Size": 21464 }, "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": { - "Size": 81288 + "Size": 81280 }, "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": { - "Size": 187056 + "Size": 187120 }, "lib/arm64-v8a/lib_System.Xml.dll.so": { - "Size": 19272 + "Size": 19264 }, "lib/arm64-v8a/lib_System.Xml.Linq.dll.so": { "Size": 19288 @@ -235,17 +235,14 @@ "lib/arm64-v8a/lib_Xamarin.Google.Android.Material.dll.so": { "Size": 84912 }, - "lib/arm64-v8a/libarc.bin.so": { - "Size": 18936 - }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { - "Size": 36600 + "Size": 36616 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1516584 + "Size": 1525968 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 3110944 + "Size": 3118632 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { "Size": 71952 @@ -254,13 +251,13 @@ "Size": 759304 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 103520 + "Size": 104312 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { - "Size": 165000 + "Size": 165240 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 353048 + "Size": 357088 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -416,7 +413,7 @@ "Size": 1221 }, "META-INF/BNDLTOOL.SF": { - "Size": 98445 + "Size": 98346 }, "META-INF/com.android.tools/proguard/coroutines.pro": { "Size": 1345 @@ -443,7 +440,7 @@ "Size": 5 }, "META-INF/MANIFEST.MF": { - "Size": 98318 + "Size": 98219 }, "META-INF/maven/com.google.guava/listenablefuture/pom.properties": { "Size": 96 @@ -2483,5 +2480,5 @@ "Size": 812848 } }, - "PackageSize": 10955937 + "PackageSize": 10959960 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index 40ea29ee665..f9fcecb7e44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -30,7 +30,6 @@ sealed class ApplicationConfig public bool uses_assembly_preload; public bool broken_exception_transitions; public bool jni_add_native_method_registration_attribute_present; - public bool have_runtime_config_blob; public bool have_assemblies_blob; public bool marshal_methods_enabled; public bool ignore_split_configs; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 5844b9fb427..c45eb1fcaf6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -182,7 +182,6 @@ sealed class XamarinAndroidBundledAssembly public bool BrokenExceptionTransitions { get; set; } public global::Android.Runtime.BoundExceptionType BoundExceptionType { get; set; } public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } - public bool HaveRuntimeConfigBlob { get; set; } public bool HaveAssemblyStore { get; set; } public int NumberOfAssembliesInApk { get; set; } public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL @@ -194,6 +193,7 @@ sealed class XamarinAndroidBundledAssembly public MonoComponent MonoComponents { get; set; } public PackageNamingPolicy PackageNamingPolicy { get; set; } public List NativeLibraries { get; set; } + public uint PackagedNativeLibrariesCount { get; set; } public bool MarshalMethodsEnabled { get; set; } public bool ManagedMarshalMethodsLookupEnabled { get; set; } public bool IgnoreSplitConfigs { get; set; } @@ -237,7 +237,6 @@ protected override void Construct (LlvmIrModule module) uses_assembly_preload = UsesAssemblyPreload, broken_exception_transitions = BrokenExceptionTransitions, jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, - have_runtime_config_blob = HaveRuntimeConfigBlob, have_assemblies_blob = HaveAssemblyStore, marshal_methods_enabled = MarshalMethodsEnabled, managed_marshal_methods_lookup_enabled = ManagedMarshalMethodsLookupEnabled, @@ -247,7 +246,7 @@ protected override void Construct (LlvmIrModule module) environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2), system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2), number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk, - number_of_shared_libraries = (uint)NativeLibraries.Count, + number_of_shared_libraries = PackagedNativeLibrariesCount, bundled_assembly_name_width = (uint)BundledAssemblyNameWidth, number_of_dso_cache_entries = (uint)dsoCache.Count, number_of_aot_cache_entries = (uint)aotDsoCache.Count, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index ca8bc0d5f55..88bd177000c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -211,6 +211,7 @@ sealed class XamarinAndroidBundledAssembly public bool MarshalMethodsEnabled { get; set; } public bool ManagedMarshalMethodsLookupEnabled { get; set; } public bool IgnoreSplitConfigs { get; set; } + public uint PackagedNativeLibrariesCount { get; set; } public ApplicationConfigNativeAssemblyGeneratorCLR (IDictionary environmentVariables, IDictionary systemProperties, IDictionary? runtimeProperties, TaskLoggingHelper log) @@ -266,7 +267,7 @@ protected override void Construct (LlvmIrModule module) environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2), system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2), number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk, - number_of_shared_libraries = (uint)NativeLibraries.Count, + number_of_shared_libraries = PackagedNativeLibrariesCount, bundled_assembly_name_width = (uint)BundledAssemblyNameWidth, number_of_dso_cache_entries = (uint)dsoCache.Count, number_of_aot_cache_entries = (uint)aotDsoCache.Count, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs index 9ef32a4dcb8..71da2ab254a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs @@ -170,7 +170,7 @@ static string GetCompressedAssemblyOutputDirectory (ITaskItem assembly, string c { string assemblyOutputDir; string subDirectory = assembly.GetMetadata ("DestinationSubDirectory"); - string abi = MonoAndroidHelper.GetAssemblyAbi (assembly); + string abi = MonoAndroidHelper.GetItemAbi (assembly); if (!string.IsNullOrEmpty (subDirectory) && !(subDirectory.EndsWith ($"{abi}/", StringComparison.Ordinal) || subDirectory.EndsWith ($"{abi}\\", StringComparison.Ordinal))) { assemblyOutputDir = Path.Combine (compressedOutputDir, abi, subDirectory); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs index 1599a8e581e..572af36521b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs @@ -10,20 +10,52 @@ namespace Xamarin.Android.Tasks; static class AssemblyPackagingHelper { + public static bool ShouldSkipAssembly (TaskLoggingHelper log, ITaskItem asm) + { + var should_skip = asm.GetMetadataOrDefault ("AndroidSkipAddToPackage", false); + + if (should_skip) { + log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' "); + } + + return should_skip; + } + + public static Dictionary CreateAssemblyStore (TaskLoggingHelper log, IEnumerable assemblies, string outputDir, string[] supportedAbis, bool includeDebugSymbols) + { + var storeBuilder = new AssemblyStoreBuilder (log); + var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, supportedAbis, true); + + foreach (var kvp in per_arch_assemblies) { + log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'"); + + foreach (var assembly in kvp.Value.Values) { + var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec); + storeBuilder.AddAssembly (sourcePath, assembly, includeDebugSymbols: includeDebugSymbols); + + log.LogDebugMessage ($"Added '{sourcePath}' to assembly store."); + } + } + + Dictionary assemblyStorePaths = storeBuilder.Generate (outputDir); + if (assemblyStorePaths.Count == 0) { + throw new InvalidOperationException ("Assembly store generator did not generate any stores"); + } + + if (assemblyStorePaths.Count != supportedAbis.Length) { + throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI"); + } + + return assemblyStorePaths; + } + public static void AddAssembliesFromCollection (TaskLoggingHelper Log, ICollection SupportedAbis, ICollection assemblies, Action doAddAssembly) { Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies ( assemblies, SupportedAbis, validate: true, - shouldSkip: (ITaskItem asm) => { - if (bool.TryParse (asm.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) { - Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' "); - return true; - } - - return false; - } + shouldSkip: (ITaskItem asm) => ShouldSkipAssembly (Log, asm) ); foreach (var kvp in perArchAssemblies) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs index 024f2cf0ac2..35c355a5872 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs @@ -104,7 +104,9 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List (); var descriptors = new List (); ulong namesSize = 0; @@ -118,7 +120,8 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List { "--add-section", $"payload={MonoAndroidHelper.QuoteFileNameArgument (payloadFilePath)}", diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFEmbeddingHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFEmbeddingHelper.cs new file mode 100644 index 00000000000..7481164a829 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFEmbeddingHelper.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +class ELFEmbeddingHelper +{ + public sealed class EmbedItem + { + public readonly string SymbolName; + public readonly string BaseFileName; + public readonly NativeAssemblerItemsHelper.KnownMode NativeAssemblerMode; + + public EmbedItem (string symbolName, string baseFileName, NativeAssemblerItemsHelper.KnownMode nativeAssemblerMode) + { + SymbolName = symbolName; + BaseFileName = baseFileName; + NativeAssemblerMode = nativeAssemblerMode; + } + } + + public static class KnownEmbedItems + { + public static readonly EmbedItem RuntimeConfig = new ("embedded_runtime_config", "runtime_config", NativeAssemblerItemsHelper.KnownMode.EmbeddedRuntimeConfig); + public static readonly EmbedItem AssemblyStore = new ("embedded_assembly_store", "assembly_store", NativeAssemblerItemsHelper.KnownMode.EmbeddedAssemblyStore); + } + + static readonly Encoding asmFileEncoding = new UTF8Encoding (false); + + public static void EmbedBinary ( + TaskLoggingHelper log, + ICollection supportedAbis, + string androidBinUtilsDirectory, + string? inputFile, + EmbedItem embedItem, + string outputDirectory, + bool missingContentOK) + { + if (supportedAbis.Count < 1) { + log.LogDebugMessage ("ELFEmbeddingHelper: at least one target ABI must be specified. Probably a DTB build, skipping generation."); + return; + } + + foreach (string abi in supportedAbis) { + DoEmbed ( + log, + MonoAndroidHelper.AbiToTargetArch (abi), + inputFile, outputDirectory, + embedItem, + missingContentOK + ); + } + } + + public static void EmbedBinary ( + TaskLoggingHelper log, + string abi, + string androidBinUtilsDirectory, + string? inputFile, + EmbedItem embedItem, + string outputDirectory, + bool missingContentOK) + { + if (String.IsNullOrEmpty (abi)) { + log.LogDebugMessage ("ELFEmbeddingHelper: ABI must be specified. Probably a DTB build, skipping generation."); + return; + } + + DoEmbed ( + log, + MonoAndroidHelper.AbiToTargetArch (abi), + inputFile, + outputDirectory, + embedItem, + missingContentOK + ); + } + + static void DoEmbed ( + TaskLoggingHelper log, + AndroidTargetArch arch, + string? inputFile, + string outputDirectory, + EmbedItem item, + bool missingContentOK) + { + NativeAssemblerCompilation.LlvmMcTargetConfig cfg = NativeAssemblerCompilation.GetLlvmMcConfig (arch); + + bool haveInputFile = !String.IsNullOrEmpty (inputFile); + if (!haveInputFile) { + if (!missingContentOK) { + throw new InvalidOperationException ("Internal error: input file must be specified"); + } + } else { + inputFile = Path.GetFullPath (inputFile); + } + + long inputFileSize = 0; + string? sanitizedInputFilePath = null; + + if (haveInputFile) { + var fi = new FileInfo (inputFile); + if (fi.Exists) { + inputFileSize = fi.Length; + sanitizedInputFilePath = inputFile!.Replace ("\\", "\\\\"); + } else if (!missingContentOK) { + throw new InvalidOperationException ($"Internal error: input file '{inputFile}' does not exist"); + } + } + + string? asmSourceFile = NativeAssemblerItemsHelper.GetSourcePath (log, item.NativeAssemblerMode, outputDirectory, arch); + if (String.IsNullOrEmpty (asmSourceFile)) { + log.LogError ("Unable to embed a binary file in native assembly, no assembly source path given."); + return; + } + + Directory.CreateDirectory (Path.GetDirectoryName (asmSourceFile)); + using var fs = File.Open (asmSourceFile, FileMode.Create, FileAccess.Write, FileShare.Read); + using var sw = new StreamWriter (fs, asmFileEncoding); + + string symbolName = item.SymbolName; + sw.WriteLine ($".section .rodata,\"a\",{cfg.AssemblerDirectivePrefix}progbits"); + sw.WriteLine (".p2align 3, 0x00"); // Put the data at the 4k boundary + sw.WriteLine (); + sw.WriteLine ($".global {symbolName}"); + sw.WriteLine ($".type {symbolName},{cfg.AssemblerDirectivePrefix}object"); + sw.WriteLine ($"{symbolName}:"); + + if (!String.IsNullOrEmpty (sanitizedInputFilePath)) { + sw.WriteLine ($"\t.incbin \"{sanitizedInputFilePath}\""); + } + sw.WriteLine ($"\t.size {symbolName}, {inputFileSize}"); + sw.WriteLine (); + + symbolName += "_size"; + sw.WriteLine ($".global {symbolName}"); + sw.WriteLine ($"{symbolName}:"); + sw.WriteLine ($"\t{cfg.SizeType}\t{inputFileSize}"); + sw.WriteLine ($"\t.size {symbolName}, {cfg.WordSize}"); + + sw.Flush (); + sw.Close (); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.Basic.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.Basic.cs new file mode 100644 index 00000000000..df401b2f462 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.Basic.cs @@ -0,0 +1,61 @@ +using System; + +using ELFSharp.ELF; +using ELFSharp.ELF.Sections; + +namespace Xamarin.Android.Tasks; + +static partial class ELFHelper +{ + public static ISymbolTable? GetSymbolTable (IELF elf, string sectionName) + { + ISection? section = GetSection (elf, sectionName); + if (section == null) { + return null; + } + + var symtab = section as ISymbolTable; + if (symtab == null) { + return null; + } + + return symtab; + } + + public static ISection? GetSection (IELF elf, string sectionName) + { + if (!elf.TryGetSection (sectionName, out ISection section)) { + return null; + } + + return section; + } + + public static SymbolEntry? FindSymbol (ISymbolTable? symbolTable, string symbolName) where T: struct + { + if (symbolTable == null) { + return null; + } + + ISymbolEntry? symbol = null; + foreach (ISymbolEntry entry in symbolTable.Entries) { + if (String.Compare (entry.Name, symbolName, StringComparison.Ordinal) != 0) { + continue; + } + + symbol = entry; + break; + } + + if (symbol == null) { + return null; + } + + Type t = typeof(T); + if (t == typeof(ulong) || t == typeof(uint)) { + return (SymbolEntry)symbol; + } + + throw new InvalidOperationException ($"Only `ulong` and `uint` types are accepted"); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs index 5c4879dc323..f51b9fb30b9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs @@ -16,7 +16,7 @@ namespace Xamarin.Android.Tasks { - static class ELFHelper + static partial class ELFHelper { public static void AssertValidLibraryAlignment (TaskLoggingHelper log, int alignmentInPages, string path, ITaskItem? item) { @@ -226,29 +226,5 @@ bool IsNonEmptyCodeSymbol (SymbolEntry? symbolEntry) where T : struct return size != 0 && symbolEntry.PointedSection.Type == ELFSectionType.ProgBits; } } - - static ISymbolTable? GetSymbolTable (IELF elf, string sectionName) - { - ISection? section = GetSection (elf, sectionName); - if (section == null) { - return null; - } - - var symtab = section as ISymbolTable; - if (symtab == null) { - return null; - } - - return symtab; - } - - static ISection? GetSection (IELF elf, string sectionName) - { - if (!elf.TryGetSection (sectionName, out ISection section)) { - return null; - } - - return section; - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs index 416ccb3f78f..48369483e06 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs @@ -290,7 +290,7 @@ public IList Merge (TaskLoggingHelper log, TypeDefinitionCache cache, Li string.IsNullOrEmpty (VersionName) ? "1.0" : VersionName); } - app = CreateApplicationElement (manifest, applicationClass, subclasses, cache); + app = CreateApplicationElement (log, manifest, applicationClass, subclasses, cache); if (app.Attribute (androidNs + "label") == null && !string.IsNullOrEmpty (ApplicationLabel)) app.SetAttributeValue (androidNs + "label", ApplicationLabel); @@ -570,7 +570,7 @@ Func GetGenerator (T return null; } - XElement CreateApplicationElement (XElement manifest, string applicationClass, List subclasses, TypeDefinitionCache cache) + XElement CreateApplicationElement (TaskLoggingHelper log, XElement manifest, string applicationClass, List subclasses, TypeDefinitionCache cache) { var application = manifest.Descendants ("application").FirstOrDefault (); @@ -581,6 +581,11 @@ XElement CreateApplicationElement (XElement manifest, string applicationClass, L List usesConfigurationAttr = []; foreach (var assemblyPath in Assemblies) { var assembly = Resolver.GetAssembly (assemblyPath); + if (assembly == null) { + log.LogDebugMessage ($"Assembly '{assemblyPath}' not found."); + continue; + } + if (ApplicationAttribute.FromCustomAttributeProvider (assembly, cache) is ApplicationAttribute a) { assemblyAttr.Add (a); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 518477d9ec4..6e49b7973af 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -568,17 +568,17 @@ public static int ConvertSupportedOSPlatformVersionToApiLevel (string version) } #if MSBUILD - public static string GetAssemblyAbi (ITaskItem asmItem) + public static string GetItemAbi (ITaskItem asmItem) { string? abi = asmItem.GetMetadata ("Abi"); if (String.IsNullOrEmpty (abi)) { - throw new InvalidOperationException ($"Internal error: assembly '{asmItem}' lacks ABI metadata"); + throw new InvalidOperationException ($"Internal error: item '{asmItem}' lacks ABI metadata"); } return abi; } - public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) => AbiToTargetArch (GetAssemblyAbi (asmItem)); + public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) => AbiToTargetArch (GetItemAbi (asmItem)); public static AndroidTargetArch GetRequiredValidArchitecture (ITaskItem item) @@ -783,6 +783,15 @@ public static string QuoteFileNameArgument (string? fileName) return builder.ToString (); } + public static string GetLlvmObjcopyPath (string androidBinUtilsDirectory) => GetBinUtilsToolPath (androidBinUtilsDirectory, "llvm-objcopy"); + public static string GetLlvmMcPath (string androidBinUtilsDirectory) => GetBinUtilsToolPath (androidBinUtilsDirectory, "llvm-mc"); + public static string GetLlvmLlcPath (string androidBinUtilsDirectory) => GetBinUtilsToolPath (androidBinUtilsDirectory, "llc"); + + static string GetBinUtilsToolPath (string androidBinUtilsDirectory, string toolName) + { + return Path.Combine (androidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (androidBinUtilsDirectory, toolName)); + } + public static AndroidRuntime ParseAndroidRuntime (string androidRuntime) { if (string.Equals (androidRuntime, "CoreCLR", StringComparison.OrdinalIgnoreCase)) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerCompilation.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerCompilation.cs new file mode 100644 index 00000000000..6e4c08478ce --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerCompilation.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +class NativeAssemblerCompilation +{ + public sealed class AssemblerConfig + { + public readonly string ExecutablePath; + public readonly string Options; + public readonly string InputSource; + + public AssemblerConfig (string executablePath, string options, string inputSource) + { + ExecutablePath = executablePath; + Options = options; + InputSource = inputSource; + } + } + + public sealed class AssemblerRunContext + { + public readonly TaskLoggingHelper Log; + public readonly Action? RegisterForCancellation; + public readonly Action? Cancel; + public readonly string? WorkingDirectory; + + public AssemblerRunContext (TaskLoggingHelper log, string? workingDirectory = null, Action? registerForCancellation = null, Action? cancel = null) + { + Log = log; + RegisterForCancellation = registerForCancellation; + Cancel = cancel; + WorkingDirectory = workingDirectory; + } + } + + public sealed class LlvmMcTargetConfig + { + public readonly string TargetArch; + public readonly string TripleArch; + public readonly string TripleApiPrefix; + public readonly string AssemblerDirectivePrefix; + public readonly string SizeType; + public readonly uint WordSize; + + public LlvmMcTargetConfig (string targetArch, string tripleArch, string tripleApiPrefix, string assemblerDirectivePrefix, string sizeType, uint wordSize) + { + TargetArch = targetArch; + TripleArch = tripleArch; + TripleApiPrefix = tripleApiPrefix; + AssemblerDirectivePrefix = assemblerDirectivePrefix; + SizeType = sizeType; + WordSize = wordSize; + } + } + + static readonly Dictionary llvmMcConfigs = new () { + { AndroidTargetArch.Arm64, new ("aarch64", "aarch64", "android", "@", ".xword", 8) }, + { AndroidTargetArch.Arm, new ("arm", "armv7a", "androideabi", "%", ".long", 4) }, + { AndroidTargetArch.X86_64, new ("x86-64", "x86_64", "android", "@", ".quad", 8) }, + { AndroidTargetArch.X86, new ("x86", "i686", "android", "@", ".long", 4) }, + }; + + static readonly List llcArguments = new () { + "-O2", + "--debugger-tune=lldb", // NDK uses lldb now + "--debugify-level=location+variables", + "--fatal-warnings", + "--filetype=obj", + "--relocation-model=pic", + }; + + static readonly List llvmMcArguments = new () { + "--assemble", + "--filetype=obj", + "-g", + }; + + public static AssemblerConfig GetAssemblerConfig (string androidBinUtilsDir, ITaskItem source, bool stripFilePaths) + { + string sourceFile = stripFilePaths ? Path.GetFileName (source.ItemSpec) : source.ItemSpec; + string sourceExtension = Path.GetExtension (sourceFile); + string executable; + var arguments = new List (); + + if (String.Compare (".ll", sourceExtension, StringComparison.OrdinalIgnoreCase) == 0) { + executable = MonoAndroidHelper.GetLlvmLlcPath (androidBinUtilsDir); + arguments.AddRange (llcArguments); + } else if (String.Compare (".s", sourceExtension, StringComparison.OrdinalIgnoreCase) == 0) { + executable = MonoAndroidHelper.GetLlvmMcPath (androidBinUtilsDir); + arguments.AddRange (llvmMcArguments); + + LlvmMcTargetConfig cfg = GetLlvmMcConfig (MonoAndroidHelper.GetTargetArch (source)); + arguments.Add ($"--arch={cfg.TargetArch}"); + arguments.Add ($"--triple={cfg.TripleArch}-linux-{cfg.TripleApiPrefix}{XABuildConfig.AndroidMinimumDotNetApiLevel}"); + } else { + throw new InvalidOperationException ($"Internal exception: unknown native assembler source {source.ItemSpec}"); + } + + string outputFile = Path.ChangeExtension (sourceFile, ".o"); + arguments.Add ("-o"); + arguments.Add (MonoAndroidHelper.QuoteFileNameArgument (outputFile)); + arguments.Add (MonoAndroidHelper.QuoteFileNameArgument (sourceFile)); + + return new AssemblerConfig (executable, String.Join (" ", arguments), source.ItemSpec); + } + + public static LlvmMcTargetConfig GetLlvmMcConfig (AndroidTargetArch arch) + { + if (!llvmMcConfigs.TryGetValue (arch, out LlvmMcTargetConfig cfg)) { + throw new NotSupportedException ($"Internal error: unsupported target arch '{arch}'"); + } + + return cfg; + } + + public static void RunAssembler (AssemblerRunContext context, AssemblerConfig config) + { + var stdout_completed = new ManualResetEvent (false); + var stderr_completed = new ManualResetEvent (false); + var psi = new ProcessStartInfo () { + FileName = config.ExecutablePath, + Arguments = config.Options, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + WorkingDirectory = context.WorkingDirectory, + }; + + string assemblerName = Path.GetFileName (config.ExecutablePath); + context.Log.LogDebugMessage ($"[{assemblerName}] {psi.FileName} {psi.Arguments}"); + + var stdoutLines = new List (); + var stderrLines = new List (); + + using var proc = new Process (); + proc.OutputDataReceived += (s, e) => { + if (e.Data != null) { + OnOutputData (context, assemblerName, s, e); + stdoutLines.Add (e.Data); + } else { + stdout_completed.Set (); + } + }; + + proc.ErrorDataReceived += (s, e) => { + if (e.Data != null) { + OnErrorData (context, assemblerName, s, e); + stderrLines.Add (e.Data); + } else { + stderr_completed.Set (); + } + }; + + proc.StartInfo = psi; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + context.RegisterForCancellation?.Invoke (proc); + + proc.WaitForExit (); + + if (psi.RedirectStandardError) { + stderr_completed.WaitOne (TimeSpan.FromSeconds (30)); + } + + if (psi.RedirectStandardOutput) { + stdout_completed.WaitOne (TimeSpan.FromSeconds (30)); + } + + if (proc.ExitCode != 0) { + var sb = MonoAndroidHelper.MergeStdoutAndStderrMessages (stdoutLines, stderrLines); + context.Log.LogCodedError ("XA3006", Properties.Resources.XA3006, Path.GetFileName (config.InputSource), sb.ToString ()); + context.Cancel?.Invoke (); + } + } + + static void OnOutputData (AssemblerRunContext context, string assemblerName, object sender, DataReceivedEventArgs e) + { + context.Log.LogDebugMessage ($"[{assemblerName} stdout] {e.Data}"); + } + + static void OnErrorData (AssemblerRunContext context, string assemblerName, object sender, DataReceivedEventArgs e) + { + context.Log.LogMessage ($"[{assemblerName} stderr] {e.Data}", MessageImportance.High); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerItemsHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerItemsHelper.cs new file mode 100644 index 00000000000..6781f2461eb --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerItemsHelper.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +static class NativeAssemblerItemsHelper +{ + public enum KnownMode + { + CompressedAssemblies, + EmbeddedAssemblyStore, + EmbeddedRuntimeConfig, + Environment, + JNIRemap, + MarshalMethods, + TypeMap, + } + + sealed class ModeConfig + { + public readonly string FileNameBase; + public readonly string Extension; + + public ModeConfig (string fileNameBase, string extension) + { + FileNameBase = fileNameBase; + Extension = extension; + } + } + + const string LlvmIrExtension = "ll"; + const string NativeAssemblerExtension = "s"; + + static readonly Dictionary ModeConfigs = new () { + { KnownMode.CompressedAssemblies, new ("compressed_assemblies", LlvmIrExtension) }, + { KnownMode.EmbeddedAssemblyStore, new ("embed_assembly_store", NativeAssemblerExtension) }, + { KnownMode.EmbeddedRuntimeConfig, new ("embed_runtime_config", NativeAssemblerExtension) }, + { KnownMode.Environment, new ("environment", LlvmIrExtension) }, + { KnownMode.JNIRemap, new ("jni_remap", LlvmIrExtension) }, + { KnownMode.MarshalMethods, new ("marshal_methods", LlvmIrExtension) }, + { KnownMode.TypeMap, new ("typemaps", LlvmIrExtension) }, + }; + + public static string? GetSourcePath (TaskLoggingHelper log, KnownMode mode, string nativeSourcesDir, AndroidTargetArch arch) + { + return GetSourcePath (log, mode, nativeSourcesDir, MonoAndroidHelper.ArchToAbi (arch)); + } + + public static string? GetSourcePath (TaskLoggingHelper log, KnownMode mode, string nativeSourcesDir, string abi) + { + if (!ModeConfigs.TryGetValue (mode, out ModeConfig config)) { + log.LogError ($"Unknown mode: {mode}"); + return null; + } + + return Path.Combine (nativeSourcesDir, $"{config.FileNameBase}.{abi.ToLowerInvariant ()}.{config.Extension}"); + } + + public static KnownMode ToKnownMode (string mode) + { + if (!Enum.TryParse (mode, ignoreCase: true, out KnownMode result)) { + throw new InvalidOperationException ($"Internal exception: uknown native assembler generator mode '{mode}'"); + } + + return result; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 6421833e60e..d5289830858 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -47,7 +47,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - @@ -312,6 +311,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_AndroidFastDeployEnvironmentFiles Condition=" '$(_AndroidFastDeployEnvironmentFiles)' == '' And '$(EmbedAssembliesIntoApk)' == 'False' ">True <_AndroidFastDeployEnvironmentFiles Condition=" '$(_AndroidFastDeployEnvironmentFiles)' == '' ">False + <_AndroidCompressedAssembliesDir>$(IntermediateOutputPath)android\lz4 <_AndroidUseCLR Condition=" '$(_AndroidRuntime)' == 'CoreCLR' ">True <_AndroidUseCLR Condition=" '$(_AndroidRuntime)' != 'CoreCLR' ">False @@ -353,6 +353,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + <_EmbedAssemblyStoreInRuntime Condition=" '$(_AndroidUseAssemblyStore)' == 'True' And '$(_AndroidEmbedAssemblyStoreInRuntime)' == 'True' ">true + <_EmbedAssemblyStoreInRuntime Condition=" '$(_EmbedAssemblyStoreInRuntime)' != 'true' ">false + <_AndroidAotStripLibraries Condition=" '$(_AndroidAotStripLibraries)' == '' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True True False @@ -363,6 +366,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) + <_AndroidEmbedAssemblyStoreInRuntime Condition=" '$(AndroidUseAssemblyStore)' == 'True' And '$(EmbedAssembliesIntoApk)' == 'true' And '$(_AndroidEmbedAssemblyStoreInRuntime)' == '' ">True + <_AndroidEmbedAssemblyStoreInRuntime Condition="'$(_AndroidEmbedAssemblyStoreInRuntime)' == '' ">False + <_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' and '$(_AndroidUseMarshalMethods)' == 'True' and '$(_AndroidRuntime)' != 'MonoVM' ">True <_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' ">False @@ -1536,10 +1542,8 @@ because xbuild doesn't support framework reference assemblies. + Mode="TypeMap"> - @@ -1682,7 +1686,6 @@ because xbuild doesn't support framework reference assemblies. - @@ -1754,24 +1757,28 @@ because xbuild doesn't support framework reference assemblies. + Mode="Environment"> + Mode="CompressedAssemblies"> + Mode="MarshalMethods"> + + + @@ -1796,8 +1803,7 @@ because xbuild doesn't support framework reference assemblies. + Mode="JNIRemap"> @@ -1869,6 +1875,7 @@ because xbuild doesn't support framework reference assemblies. <_GeneratePackageManagerJavaInputs Include="@(_GenerateJavaStubsInputs)" /> + <_GeneratePackageManagerJavaInputs Include="$(_BinaryRuntimeConfigPath)" /> @@ -1877,7 +1884,7 @@ because xbuild doesn't support framework reference assemblies. DependsOnTargets="$(_GeneratePackageManagerJavaDependsOn)" Inputs="@(_GeneratePackageManagerJavaInputs)" Outputs="$(_AndroidStampDirectory)_GeneratePackageManagerJava.stamp"> - + + ProjectRuntimeConfigFilePath="$(ProjectRuntimeConfigFilePath)" + AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"> - + + @@ -2039,6 +2048,7 @@ because xbuild doesn't support framework reference assemblies. <_CompileToDalvikDependsOnTargets> _CompileJava; + _RemoveRegisterAttribute; _CreateApplicationSharedLibraries; _GetMonoPlatformJarPath; _GetLibraryImports; @@ -2131,7 +2141,7 @@ because xbuild doesn't support framework reference assemblies. - + <_NativeAssemblyTarget Include="@(_TypeMapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')"> %(_TypeMapAssemblySource.abi) @@ -2151,6 +2161,22 @@ because xbuild doesn't support framework reference assemblies. <_NativeAssemblyTarget Include="@(_AndroidRemapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')"> %(_AndroidRemapAssemblySource.abi) + <_NativeAssemblyTarget Include="@(_EmbeddedAssemblyStoreSourceFiles->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')"> + %(_EmbeddedAssemblyStoreSourceFiles.abi) + + <_NativeAssemblyTarget Include="@(_EmbeddedRuntimeConfigAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')"> + %(_EmbeddedRuntimeConfigAssemblySource.abi) + + + + + <_NativeAssemblySource Include="@(_AndroidRemapAssemblySource)" /> + <_NativeAssemblySource Include="@(_CompressedAssembliesAssemblySource)" /> + <_NativeAssemblySource Include="@(_EmbeddedAssemblyStoreSourceFiles)" /> + <_NativeAssemblySource Include="@(_EmbeddedRuntimeConfigAssemblySource)" /> + <_NativeAssemblySource Include="@(_EnvironmentAssemblySource)" /> + <_NativeAssemblySource Include="@(_MarshalMethodsAssemblySource)" /> + <_NativeAssemblySource Include="@(_TypeMapAssemblySource)" /> @@ -2169,11 +2195,11 @@ because xbuild doesn't support framework reference assemblies. - - - - - - - - - - (payload_start); - - auto get_full_store_path = [&apk_path, &store_path]() -> std::string { - std::string full_store_path; - - if (!apk_path.empty ()) { - full_store_path.append (apk_path); - // store path will be relative, to the apk - full_store_path.append ("!/"sv); - full_store_path.append (store_path); - } else { - full_store_path.append (store_path); - } - - return full_store_path; - }; + auto header = static_cast(data_start); if (header->magic != ASSEMBLY_STORE_MAGIC) { Helpers::abort_application ( LOG_ASSEMBLY, std::format ( "Assembly store '{}' is not a valid .NET for Android assembly store file"sv, - get_full_store_path () + name ) ); } @@ -274,7 +256,7 @@ void AssemblyStore::map (int fd, std::string_view const& apk_path, std::string_v LOG_ASSEMBLY, std::format ( "Assembly store '{}' uses format version {:x}, instead of the expected {:x}"sv, - get_full_store_path (), + name, header->version, ASSEMBLY_STORE_FORMAT_VERSION ) @@ -283,11 +265,35 @@ void AssemblyStore::map (int fd, std::string_view const& apk_path, std::string_v constexpr size_t header_size = sizeof(AssemblyStoreHeader); - assembly_store.data_start = static_cast(payload_start); + assembly_store.data_start = static_cast(data_start); assembly_store.assembly_count = header->entry_count; assembly_store.index_entry_count = header->index_entry_count; assembly_store.assemblies = reinterpret_cast(assembly_store.data_start + header_size + header->index_size); assembly_store_hashes = reinterpret_cast(assembly_store.data_start + header_size); +} + +void AssemblyStore::map (int fd, std::string_view const& apk_path, std::string_view const& store_path, uint32_t offset, uint32_t size) noexcept +{ + detail::mmap_info assembly_store_map = Util::mmap_file (fd, offset, size, store_path); + auto [payload_start, payload_size] = Util::get_wrapper_dso_payload_pointer_and_size (assembly_store_map, store_path); + log_debug (LOG_ASSEMBLY, "Adjusted assembly store pointer: {:p}; size: {}"sv, payload_start, payload_size); - log_debug (LOG_ASSEMBLY, "Mapped assembly store {}"sv, get_full_store_path ()); + std::string full_store_path; + if (!apk_path.empty ()) { + full_store_path.append (apk_path); + // store path will be relative, to the apk + full_store_path.append ("!/"sv); + full_store_path.append (store_path); + } else { + full_store_path.append (store_path); + } + + verify_assembly_store_and_set_info (payload_start, full_store_path); + log_debug (LOG_ASSEMBLY, "Mapped assembly store {}"sv, full_store_path); +} + +void AssemblyStore::map () noexcept +{ + verify_assembly_store_and_set_info (embedded_assembly_store, ""sv); + log_debug (LOG_ASSEMBLY, "Mapped embedded assembly store"); } diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index c48d1a7ca9c..de333da14bb 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -231,11 +231,23 @@ void Host::scan_filesystem_for_assemblies_and_libraries () noexcept void Host::gather_assemblies_and_libraries (jstring_array_wrapper& runtimeApks, bool have_split_apks) { + // Embedded assembly takes priority over the one found on the filesystem. + if (found_assembly_store) { + // We have an embedded store, map it + AssemblyStore::map (); + } + if (!AndroidSystem::is_embedded_dso_mode_enabled ()) { scan_filesystem_for_assemblies_and_libraries (); return; } + if (found_assembly_store) { + // In CoreCLR we only look in the APK for the assembly store. Since we have + // an embedded one, though, there's no need to waste time scanning the ZIP. + return; + } + int64_t apk_count = static_cast(runtimeApks.get_length ()); bool got_split_config_abi_apk = false; diff --git a/src/native/clr/include/host/assembly-store.hh b/src/native/clr/include/host/assembly-store.hh index 033e7d3ce1c..f49a85f94e3 100644 --- a/src/native/clr/include/host/assembly-store.hh +++ b/src/native/clr/include/host/assembly-store.hh @@ -14,6 +14,8 @@ namespace xamarin::android { public: static auto open_assembly (std::string_view const& name, int64_t &size) noexcept -> void*; + // Maps the embedded assembly store. + static void map () noexcept; static void map (int fd, std::string_view const& apk_path, std::string_view const& store_path, uint32_t offset, uint32_t size) noexcept; static void map (int fd, std::string_view const& file_path, uint32_t offset, uint32_t size) noexcept @@ -23,6 +25,7 @@ namespace xamarin::android { private: static void set_assembly_data_and_size (uint8_t* source_assembly_data, uint32_t source_assembly_data_size, uint8_t*& dest_assembly_data, uint32_t& dest_assembly_data_size) noexcept; + static void verify_assembly_store_and_set_info (void *data_start, std::string_view const& name) noexcept; // Returns a tuple of static auto get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData const& e, std::string_view const& name) noexcept -> std::tuple; diff --git a/src/native/clr/include/host/host.hh b/src/native/clr/include/host/host.hh index c1ae90788c2..fb1d2f89bc7 100644 --- a/src/native/clr/include/host/host.hh +++ b/src/native/clr/include/host/host.hh @@ -8,8 +8,9 @@ #include #include -#include "../shared/log_types.hh" +#include #include "managed-interface.hh" +#include namespace xamarin::android { class Host @@ -53,7 +54,7 @@ namespace xamarin::android { static inline void *clr_host = nullptr; static inline unsigned int domain_id = 0; static inline std::shared_ptr _timing{}; - static inline bool found_assembly_store = false; + static inline bool found_assembly_store = embedded_assembly_store_size > 0; static inline jnienv_register_jni_natives_fn jnienv_register_jni_natives = nullptr; static inline JavaVM *jvm = nullptr; diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 8caa09323d4..f896014d910 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -427,4 +427,10 @@ struct MarshalMethodName #endif // def RELEASE using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); -extern "C" [[gnu::visibility("default")]] void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept; + +extern "C" { + [[gnu::visibility("default")]] extern size_t embedded_assembly_store_size; + [[gnu::visibility("default")]] extern uint8_t embedded_assembly_store[]; + + [[gnu::visibility("default")]] void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept; +} diff --git a/src/native/clr/xamarin-app-stub/application_dso_stub.cc b/src/native/clr/xamarin-app-stub/application_dso_stub.cc index 7919083ef1f..8a32a41fba4 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -355,3 +355,6 @@ const char *init_runtime_property_names[] = { char *init_runtime_property_values[] { nullptr, }; + +size_t embedded_assembly_store_size = 0; +uint8_t embedded_assembly_store[0]; diff --git a/src/native/mono/monodroid/embedded-assemblies-zip.cc b/src/native/mono/monodroid/embedded-assemblies-zip.cc index 95c36da3f3c..30e01a25fbd 100644 --- a/src/native/mono/monodroid/embedded-assemblies-zip.cc +++ b/src/native/mono/monodroid/embedded-assemblies-zip.cc @@ -16,7 +16,7 @@ using namespace xamarin::android::internal; [[gnu::always_inline]] bool -EmbeddedAssemblies::zip_load_entry_common (size_t entry_index, std::vector const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept +EmbeddedAssemblies::zip_load_entry_common (size_t entry_index, std::span const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept { entry_name.clear (); @@ -62,14 +62,6 @@ EmbeddedAssemblies::zip_load_entry_common (size_t entry_index, std::vector const& buf, uint32_t num_entries, [[maybe_unused]] monodroid_should_register should_register, ZipEntryLoadState &state) noexcept +EmbeddedAssemblies::zip_load_individual_assembly_entries (std::span const& buf, uint32_t num_entries, [[maybe_unused]] monodroid_should_register should_register, ZipEntryLoadState &state) noexcept { // TODO: do away with all the string manipulation here. Replace it with generating xxhash for the entry name dynamic_local_string entry_name; @@ -183,6 +175,47 @@ EmbeddedAssemblies::zip_load_individual_assembly_entries (std::vector c } } + +[[gnu::always_inline]] void +EmbeddedAssemblies::verify_assembly_store_and_set_info (void *data_start, const char *name) noexcept +{ + auto header = static_cast(data_start); + + if (header->magic != ASSEMBLY_STORE_MAGIC) { + Helpers::abort_application ( + LOG_ASSEMBLY, + std::format ( + "Assembly store '{}' is not a valid .NET for Android assembly store file", + optional_string (name) + ) + ); + } + + if (header->version != ASSEMBLY_STORE_FORMAT_VERSION) { + Helpers::abort_application ( + LOG_ASSEMBLY, + std::format ( + "Assembly store '{}' uses format version 0x{:x}, instead of the expected 0x{:x}", + optional_string (name), + header->version, + ASSEMBLY_STORE_FORMAT_VERSION + ) + ); + } + + constexpr size_t header_size = sizeof(AssemblyStoreHeader); + + assembly_store.data_start = static_cast(data_start); + assembly_store.assembly_count = header->entry_count; + assembly_store.index_entry_count = header->index_entry_count; + assembly_store.assemblies = reinterpret_cast(assembly_store.data_start + header_size + header->index_size); + assembly_store_hashes = reinterpret_cast(assembly_store.data_start + header_size); + + number_of_found_assemblies += assembly_store.assembly_count; + number_of_mapped_assembly_stores++; + have_and_want_debug_symbols = register_debug_symbols; +} + inline void EmbeddedAssemblies::map_assembly_store (dynamic_local_string const& entry_name, ZipEntryLoadState &state) noexcept { @@ -219,55 +252,26 @@ EmbeddedAssemblies::map_assembly_store (dynamic_local_string } auto [payload_start, payload_size] = get_wrapper_dso_payload_pointer_and_size (assembly_store_map, entry_name.get ()); - log_debug (LOG_ASSEMBLY, "Adjusted assembly store pointer: {:p}; size: {}", payload_start, payload_size); - auto header = static_cast(payload_start); - - if (header->magic != ASSEMBLY_STORE_MAGIC) { - Helpers::abort_application ( - LOG_ASSEMBLY, - std::format ( - "Assembly store '{}' is not a valid .NET for Android assembly store file", - optional_string (entry_name.get ()) - ) - ); - } - - if (header->version != ASSEMBLY_STORE_FORMAT_VERSION) { - Helpers::abort_application ( - LOG_ASSEMBLY, - std::format ( - "Assembly store '{}' uses format version {:x}, instead of the expected {:x}", - optional_string (entry_name.get ()), - header->version, - ASSEMBLY_STORE_FORMAT_VERSION - ) - ); - } - - constexpr size_t header_size = sizeof(AssemblyStoreHeader); - - assembly_store.data_start = static_cast(payload_start); - assembly_store.assembly_count = header->entry_count; - assembly_store.index_entry_count = header->index_entry_count; - assembly_store.assemblies = reinterpret_cast(assembly_store.data_start + header_size + header->index_size); - assembly_store_hashes = reinterpret_cast(assembly_store.data_start + header_size); - - number_of_found_assemblies += assembly_store.assembly_count; - number_of_mapped_assembly_stores++; - have_and_want_debug_symbols = register_debug_symbols; + log_debug (LOG_ASSEMBLY, "Adjusted assembly store pointer: %p; size: %zu", payload_start, payload_size); + verify_assembly_store_and_set_info (payload_start, entry_name.get ()); } [[gnu::always_inline]] void -EmbeddedAssemblies::zip_load_assembly_store_entries (std::vector const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept +EmbeddedAssemblies::zip_load_assembly_store_entries (std::span const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept { if (all_required_zip_entries_found ()) { return; } dynamic_local_string entry_name; - bool assembly_store_found = false; + bool assembly_store_found = embedded_assembly_store_size != 0; + if (assembly_store_found) { + load_embedded_assembly_store (); + log_debug (LOG_ASSEMBLY, "Looking for DSOs in APK"); + } else { + log_debug (LOG_ASSEMBLY, "Looking for assembly store ('{}') and DSOs in APK", assembly_store_file_path); + } - log_debug (LOG_ASSEMBLY, "Looking for assembly stores in APK ('{}')", assembly_store_file_path.data ()); for (size_t i = 0uz; i < num_entries; i++) { if (all_required_zip_entries_found ()) { need_to_scan_more_apks = false; @@ -315,6 +319,7 @@ EmbeddedAssemblies::zip_load_assembly_store_entries (std::vector const& } } +[[gnu::flatten]] void EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unused]] monodroid_should_register should_register) noexcept { @@ -322,7 +327,7 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus uint32_t cd_size; uint16_t cd_entries; - if (!zip_read_cd_info (fd, cd_offset, cd_size, cd_entries)) { + if (!zip_read_cd_info (fd, cd_offset, cd_size, cd_entries)) [[unlikely]] { Helpers::abort_application ( LOG_ASSEMBLY, std::format ( @@ -331,13 +336,13 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus ) ); } -#ifdef DEBUG - log_info (LOG_ASSEMBLY, "Central directory offset: {}", cd_offset); - log_info (LOG_ASSEMBLY, "Central directory size: {}", cd_size); - log_info (LOG_ASSEMBLY, "Central directory entries: {}", cd_entries); -#endif + + log_debug (LOG_ASSEMBLY, "Central directory offset: {}", cd_offset); + log_debug (LOG_ASSEMBLY, "Central directory size: {}", cd_size); + log_debug (LOG_ASSEMBLY, "Central directory entries: {}", cd_entries); + off_t retval = ::lseek (fd, static_cast(cd_offset), SEEK_SET); - if (retval < 0) { + if (retval < 0) [[unlikely]] { Helpers::abort_application ( LOG_ASSEMBLY, std::format ( @@ -350,7 +355,6 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus ); } - std::vector buf (cd_size); const auto [prefix, prefix_len] = get_assemblies_prefix_and_length (); ZipEntryLoadState state { .file_fd = fd, @@ -367,8 +371,10 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus .max_assembly_file_name_size = 0u, }; + std::unique_ptr raw_data (new uint8_t[cd_size]); + std::span buf (raw_data.get (), cd_size); ssize_t nread = read (fd, buf.data (), buf.size ()); - if (static_cast(nread) != cd_size) { + if (static_cast(nread) != cd_size) [[unlikely]] { Helpers::abort_application ( LOG_ASSEMBLY, std::format ( @@ -647,7 +653,7 @@ EmbeddedAssemblies::zip_read_field (T const& buf, size_t index, size_t count, dy } bool -EmbeddedAssemblies::zip_read_entry_info (std::vector const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept +EmbeddedAssemblies::zip_read_entry_info (std::span const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept { constexpr size_t CD_COMPRESSION_METHOD_OFFSET = 10uz; constexpr size_t CD_UNCOMPRESSED_SIZE_OFFSET = 24uz; diff --git a/src/native/mono/monodroid/embedded-assemblies.cc b/src/native/mono/monodroid/embedded-assemblies.cc index c35324f408b..73bb98e4751 100644 --- a/src/native/mono/monodroid/embedded-assemblies.cc +++ b/src/native/mono/monodroid/embedded-assemblies.cc @@ -910,51 +910,6 @@ EmbeddedAssemblies::typemap_managed_to_java (MonoReflectionType *reflection_type return ret; } -EmbeddedAssemblies::md_mmap_info -EmbeddedAssemblies::md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename) -{ - md_mmap_info file_info; - md_mmap_info mmap_info; - - size_t pageSize = static_cast(Util::monodroid_getpagesize ()); - size_t offsetFromPage = offset % pageSize; - size_t offsetPage = offset - offsetFromPage; - size_t offsetSize = size + offsetFromPage; - - mmap_info.area = mmap (nullptr, offsetSize, PROT_READ, MAP_PRIVATE, fd, static_cast(offsetPage)); - - if (mmap_info.area == MAP_FAILED) { - Helpers::abort_application ( - LOG_ASSEMBLY, - std::format ( - "Could not mmap APK fd {}: {}; File={}", - fd, - strerror (errno), - optional_string (filename) - ) - ); - } - - mmap_info.size = offsetSize; - file_info.area = pointer_add (mmap_info.area, offsetFromPage); - file_info.size = size; - - log_info ( - LOG_ASSEMBLY, - " mmap_start: {:<8p}; mmap_end: {:<8p} mmap_len: {:<12} file_start: {:<8p} file_end: {:<8p} file_len: {:<12} apk descriptor: {} file: {}", - mmap_info.area, - pointer_add (mmap_info.area, mmap_info.size), - mmap_info.size, - file_info.area, - pointer_add (file_info.area, file_info.size), - file_info.size, - fd, - optional_string (filename) - ); - - return file_info; -} - void EmbeddedAssemblies::gather_bundled_assemblies_from_apk (const char* apk, monodroid_should_register should_register) noexcept { @@ -1450,13 +1405,21 @@ EmbeddedAssemblies::register_from_filesystem (const char *lib_dir_path,bool look size_t EmbeddedAssemblies::register_from_filesystem (monodroid_should_register should_register) noexcept { - log_debug (LOG_ASSEMBLY, "Registering assemblies from the filesystem"sv); - constexpr bool LookForMangledNames = true; - size_t assembly_count = register_from_filesystem ( - AndroidSystem::app_lib_directories[0], - LookForMangledNames, - should_register - ); + size_t assembly_count; + + if (embedded_assembly_store_size > 0) { + log_debug (LOG_ASSEMBLY, "Filesystem mode, but registering assemblies from the embedded assembly store"); + load_embedded_assembly_store (); + assembly_count = assembly_store.assembly_count; + } else { + log_debug (LOG_ASSEMBLY, "Registering assemblies from the filesystem"); + constexpr bool LookForMangledNames = true; + assembly_count = register_from_filesystem ( + AndroidSystem::app_lib_directories[0], + LookForMangledNames, + should_register + ); + } #if defined(DEBUG) constexpr bool DoNotLookForMangledNames = false; diff --git a/src/native/mono/monodroid/embedded-assemblies.hh b/src/native/mono/monodroid/embedded-assemblies.hh index f3447a69840..da3a86328f6 100644 --- a/src/native/mono/monodroid/embedded-assemblies.hh +++ b/src/native/mono/monodroid/embedded-assemblies.hh @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -193,11 +194,6 @@ namespace xamarin::android::internal { runtime_config_data_size = 0uz; } - static bool have_runtime_config_blob () noexcept - { - return application_config.have_runtime_config_blob && runtime_config_blob_mmap.area != nullptr; - } - static bool keep_scanning () noexcept { return need_to_scan_more_apks; @@ -258,7 +254,57 @@ namespace xamarin::android::internal { static const TypeMapEntry *typemap_managed_to_java (const char *managed_type_name) noexcept; #endif // DEBUG - static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename); + [[gnu::always_inline]] + static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename, md_mmap_info &original_info, md_mmap_info &adjusted_info) noexcept + { + size_t pageSize = static_cast(Util::monodroid_getpagesize ()); + size_t offsetFromPage = offset % pageSize; + size_t offsetPage = offset - offsetFromPage; + size_t offsetSize = size + offsetFromPage; + + original_info.area = mmap (nullptr, offsetSize, PROT_READ, MAP_PRIVATE, fd, static_cast(offsetPage)); + + if (original_info.area == MAP_FAILED) { + Helpers::abort_application ( + LOG_ASSEMBLY, + Util::monodroid_strdup_printf ( + "Could not mmap APK fd %d: %s; File=%s", + fd, + strerror (errno), + filename + ) + ); + } + + original_info.size = offsetSize; + adjusted_info.area = (void*)((const char*)original_info.area + offsetFromPage); + adjusted_info.size = size; + + log_info ( + LOG_ASSEMBLY, + " mmap_start: {:<8p}; mmap_end: {:<8p} mmap_len: {:<12} file_start: {:<8p} file_end: {:<8p} file_len: {:<12} apk descriptor: {} file: {}", + original_info.area, + pointer_add (original_info.area, original_info.size), + original_info.size, + adjusted_info.area, + pointer_add (adjusted_info.area, adjusted_info.size), + adjusted_info.size, + fd, + optional_string (filename) + ); + + return adjusted_info; + } + + [[gnu::flatten, gnu::always_inline]] + static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename) noexcept + { + md_mmap_info file_info; + md_mmap_info mmap_info; + + return md_mmap_apk_file (fd, offset, size, filename, mmap_info, file_info); + } + static MonoAssembly* open_from_bundles_full (MonoAssemblyName *aname, char **assemblies_path, void *user_data); static MonoAssembly* open_from_bundles (MonoAssemblyLoadContextGCHandle alc_gchandle, MonoAssemblyName *aname, char **assemblies_path, void *user_data, MonoError *error); @@ -268,9 +314,9 @@ namespace xamarin::android::internal { static void get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData const& e, uint8_t*& assembly_data, uint32_t& assembly_data_size) noexcept; static void zip_load_entries (int fd, const char *apk_name, monodroid_should_register should_register) noexcept; - static void zip_load_individual_assembly_entries (std::vector const& buf, uint32_t num_entries, monodroid_should_register should_register, ZipEntryLoadState &state) noexcept; - static void zip_load_assembly_store_entries (std::vector const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept; - static bool zip_load_entry_common (size_t entry_index, std::vector const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept; + static void zip_load_individual_assembly_entries (std::span const& buf, uint32_t num_entries, monodroid_should_register should_register, ZipEntryLoadState &state) noexcept; + static void zip_load_assembly_store_entries (std::span const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept; + static bool zip_load_entry_common (size_t entry_index, std::span const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept; static bool zip_read_cd_info (int fd, uint32_t& cd_offset, uint32_t& cd_size, uint16_t& cd_entries) noexcept; static bool zip_adjust_data_offset (int fd, ZipEntryLoadState &state) noexcept; @@ -292,7 +338,7 @@ namespace xamarin::android::internal { template static bool zip_read_field (T const& buf, size_t index, size_t count, dynamic_local_string& characters) noexcept; - static bool zip_read_entry_info (std::vector const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept; + static bool zip_read_entry_info (std::span const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept; [[gnu::always_inline]] static std::tuple get_wrapper_dso_payload_pointer_and_size (md_mmap_info const& map_info, const char *file_name) noexcept @@ -351,8 +397,7 @@ namespace xamarin::android::internal { static bool all_required_zip_entries_found () noexcept { return - number_of_mapped_assembly_stores == number_of_assembly_store_files && number_of_zip_dso_entries >= application_config.number_of_shared_libraries - && ((application_config.have_runtime_config_blob && runtime_config_blob_found) || !application_config.have_runtime_config_blob); + number_of_mapped_assembly_stores == number_of_assembly_store_files && number_of_zip_dso_entries >= application_config.number_of_shared_libraries; } [[gnu::always_inline]] static c_unique_ptr to_utf8 (const MonoString *s) noexcept @@ -378,6 +423,15 @@ namespace xamarin::android::internal { static void set_entry_data (XamarinAndroidBundledAssembly &entry, ZipEntryLoadState const& state, dynamic_local_string const& entry_name) noexcept; static void set_assembly_entry_data (XamarinAndroidBundledAssembly &entry, ZipEntryLoadState const& state, dynamic_local_string const& entry_name) noexcept; static void set_debug_entry_data (XamarinAndroidBundledAssembly &entry, ZipEntryLoadState const& state, dynamic_local_string const& entry_name) noexcept; + + static void verify_assembly_store_and_set_info (void *data_start, const char *name) noexcept; + + static void load_embedded_assembly_store () noexcept + { + log_debug (LOG_ASSEMBLY, "Loading embedded assembly store"); + verify_assembly_store_and_set_info (embedded_assembly_store, "embedded"); + } + static void map_assembly_store (dynamic_local_string const& entry_name, ZipEntryLoadState &state) noexcept; static const AssemblyStoreIndexEntry* find_assembly_store_entry (hash_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept; static void store_individual_assembly_data (dynamic_local_string const& entry_name, ZipEntryLoadState const& state, monodroid_should_register should_register) noexcept; @@ -458,11 +512,10 @@ namespace xamarin::android::internal { size_t type_map_count; #endif // DEBUG static inline const char *assemblies_prefix_override = nullptr; - static inline md_mmap_info runtime_config_blob_mmap{}; static inline void *runtime_config_data = nullptr; static inline size_t runtime_config_data_size = 0uz; - static inline bool runtime_config_blob_found = false; + static inline bool runtime_config_blob_found = embedded_runtime_config_size > 0u; static inline uint32_t number_of_mapped_assembly_stores = 0u; static inline uint32_t number_of_zip_dso_entries = 0u; static inline bool need_to_scan_more_apks = true; diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc index d8c722f48f2..954661663a2 100644 --- a/src/native/mono/monodroid/monodroid-glue.cc +++ b/src/native/mono/monodroid/monodroid-glue.cc @@ -728,13 +728,15 @@ MonodroidRuntime::create_domain (JNIEnv *env, jstring_array_wrapper &runtimeApks gather_bundled_assemblies (runtimeApks, &user_assemblies_count, have_split_apks); - if (EmbeddedAssemblies::have_runtime_config_blob ()) { + if (embedded_runtime_config_size > 0) { if (FastTiming::enabled ()) [[unlikely]] { internal_timing.start_event (TimingEventKind::RuntimeConfigBlob); } runtime_config_args.kind = 1; - EmbeddedAssemblies::get_runtime_config_blob (runtime_config_args.runtimeconfig.data.data, runtime_config_args.runtimeconfig.data.data_len); + runtime_config_args.runtimeconfig.data.data = reinterpret_cast(embedded_runtime_config); + runtime_config_args.runtimeconfig.data.data_len = static_cast(embedded_runtime_config_size); + monovm_runtimeconfig_initialize (&runtime_config_args, cleanup_runtime_config, nullptr); if (FastTiming::enabled ()) [[unlikely]] { @@ -1155,7 +1157,8 @@ MonodroidRuntime::set_profile_options () noexcept .append (OUTPUT_ARG) .append (output_path.get (), output_path.length ()); } - if (Util::create_directory (AndroidSystem::override_dirs[0], 0) < 0) { + + if (Util::create_directory (AndroidSystem::override_dirs[0], 0777, 000) < 0) { log_warn (LOG_DEFAULT, "Failed to create directory '{}'. {}", optional_string (AndroidSystem::override_dirs[0]), std::strerror (errno)); } diff --git a/src/native/mono/runtime-base/android-system.cc b/src/native/mono/runtime-base/android-system.cc index 0fcd825bead..62c358f3816 100644 --- a/src/native/mono/runtime-base/android-system.cc +++ b/src/native/mono/runtime-base/android-system.cc @@ -262,26 +262,14 @@ AndroidSystem::monodroid_get_system_property_from_overrides ([[maybe_unused]] co return 0; } -// TODO: review this. Do we really have to create the dir in release? void AndroidSystem::create_update_dir (char *override_dir) noexcept { -#if defined (RELEASE) - /* - * Don't create .__override__ on Release builds, because Google requires - * that pre-loaded apps not create world-writable directories. - * - * However, if any logging is enabled (which should _not_ happen with - * pre-loaded apps!), we need the .__override__ directory... - */ - if (log_categories == 0 && monodroid_get_system_property (SharedConstants::DEBUG_MONO_PROFILE_PROPERTY, nullptr) == 0) { - return; - } -#endif // def RELEASE - override_dirs [0] = override_dir; +#if defined(DEBUG) + log_debug (LOG_DEFAULT, "Creating public update directory: `%s`", override_dir); Util::create_public_directory (override_dir); - log_warn (LOG_DEFAULT, "Creating public update directory: `{}`", override_dir); +#endif } bool diff --git a/src/native/mono/runtime-base/util.cc b/src/native/mono/runtime-base/util.cc index fda41ecfd88..a5159f1f066 100644 --- a/src/native/mono/runtime-base/util.cc +++ b/src/native/mono/runtime-base/util.cc @@ -147,14 +147,14 @@ Util::path_combine (const char *path1, const char *path2) void Util::create_public_directory (const char *dir) { - int ret = create_directory (dir, 0777); + int ret = create_directory (dir, 0777, 0); if (ret < 0) { log_warn (LOG_DEFAULT, "Failed to create directory '{}'. {}", dir, std::strerror (errno)); } } int -Util::create_directory (const char *pathname, mode_t mode) +Util::create_directory (const char *pathname, mode_t mode, mode_t mask) { if (mode <= 0) mode = DEFAULT_DIRECTORY_MODE; @@ -163,7 +163,7 @@ Util::create_directory (const char *pathname, mode_t mode) errno = EINVAL; return -1; } - mode_t oldumask = umask (022); + mode_t oldumask = umask (mask); std::unique_ptr path {strdup_new (pathname)}; int rv, ret = 0; for (char *d = path.get (); d != nullptr && *d; ++d) { diff --git a/src/native/mono/runtime-base/util.hh b/src/native/mono/runtime-base/util.hh index a43009478ae..3bfd897658f 100644 --- a/src/native/mono/runtime-base/util.hh +++ b/src/native/mono/runtime-base/util.hh @@ -69,7 +69,7 @@ namespace xamarin::android static char *monodroid_strdup_vprintf (const char *format, va_list vargs); static char* path_combine (const char *path1, const char *path2); static void create_public_directory (const char *dir); - static int create_directory (const char *pathname, mode_t mode); + static int create_directory (const char *pathname, mode_t mode, mode_t mask = 022); static void set_world_accessable (const char *path); static auto set_world_accessible (int fd) noexcept -> bool; static void set_user_executable (const char *path); diff --git a/src/native/mono/xamarin-app-stub/application_dso_stub.cc b/src/native/mono/xamarin-app-stub/application_dso_stub.cc index 0316a55408b..d60e11c82c9 100644 --- a/src/native/mono/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/mono/xamarin-app-stub/application_dso_stub.cc @@ -48,7 +48,6 @@ const ApplicationConfig application_config = { .uses_assembly_preload = false, .broken_exception_transitions = false, .jni_add_native_method_registration_attribute_present = false, - .have_runtime_config_blob = false, .have_assembly_store = false, .marshal_methods_enabled = false, .ignore_split_configs = false, @@ -305,3 +304,9 @@ const JniRemappingTypeReplacementEntry jni_remapping_type_replacements[] = { .replacement = "another/replacement/java/type", }, }; + +size_t embedded_runtime_config_size = 0; +uint8_t embedded_runtime_config[0]; + +size_t embedded_assembly_store_size = 0; +uint8_t embedded_assembly_store[0]; diff --git a/src/native/mono/xamarin-app-stub/xamarin-app.hh b/src/native/mono/xamarin-app-stub/xamarin-app.hh index 5b058a99d2d..d8badede691 100644 --- a/src/native/mono/xamarin-app-stub/xamarin-app.hh +++ b/src/native/mono/xamarin-app-stub/xamarin-app.hh @@ -238,7 +238,6 @@ struct ApplicationConfig bool uses_assembly_preload; bool broken_exception_transitions; bool jni_add_native_method_registration_attribute_present; - bool have_runtime_config_blob; bool have_assembly_store; bool marshal_methods_enabled; bool ignore_split_configs; @@ -394,6 +393,12 @@ MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[]; #endif // def RELEASE +MONO_API MONO_API_EXPORT size_t embedded_runtime_config_size; +MONO_API MONO_API_EXPORT uint8_t embedded_runtime_config[]; + +MONO_API MONO_API_EXPORT size_t embedded_assembly_store_size; +MONO_API MONO_API_EXPORT uint8_t embedded_assembly_store[]; + using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); MONO_API MONO_API_EXPORT void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept; diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index d179740e994..92593ce63c1 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -1380,5 +1380,26 @@ public void AppStartsWithManagedMarshalMethodsLookupEnabled () Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30); Assert.IsTrue (didLaunch, "Activity should have started."); } + + [Test] + [TestCase (false)] + [TestCase (true)] + public void AppStartsWithEmbeddedAssemblyStore (bool useCLR) + { + var proj = new XamarinAndroidApplicationProject { IsRelease = true }; + proj.SetProperty ("_AndroidEmbedAssemblyStoreInRuntime", "true"); + proj.SetProperty ("UseMonoRuntime", useCLR ? "false" : "true"); + + using var builder = CreateApkBuilder (); + builder.Save (proj); + + var dotnet = new DotNetCLI (Path.Combine (Root, builder.ProjectDirectory, proj.ProjectFilePath)); + Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed"); + Assert.IsTrue (dotnet.Run (), "`dotnet run --no-build` should succeed"); + + bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity", + Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30); + Assert.IsTrue (didLaunch, "Activity should have started."); + } } } diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs index d6a5a638302..aef64a09d3d 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs @@ -140,7 +140,19 @@ public static (IList? explorers, string? errorMessage) Op ZipEntry entry = zip.ReadEntry (path); var stream = new MemoryStream (); entry.Extract (stream); - ret.Add (new AssemblyStoreExplorer (stream, $"{fi.FullName}!{path}")); + AssemblyStoreExplorer? explorer = null; + try { + // It may throw when opening an apk without any assembly stores, in which case the v2 store reader would + // always find `libxamarin-app.so` and try to read the embedded store and fail, throwing from the explorer + // constructor. + explorer = new AssemblyStoreExplorer (stream, $"{fi.FullName}!{path}"); + } catch (NotSupportedException) { + // Ignore + } + + if (explorer != null) { + ret.Add (explorer); + } } if (ret.Count == 0) { diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs b/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs index 932f151c80f..78372c26853 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs @@ -8,4 +8,5 @@ enum ELFPayloadError NotSharedLibrary, NotLittleEndian, NoPayloadSection, + NoEmbeddedStore, } diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs index a43960102e2..36dad0c4c45 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs @@ -40,6 +40,11 @@ static StoreReader_V2 () GetArchPath (AndroidTargetArch.Arm), GetArchPath (AndroidTargetArch.X86_64), GetArchPath (AndroidTargetArch.X86), + + GetArchPath (AndroidTargetArch.Arm64, embeddedBlob: true), + GetArchPath (AndroidTargetArch.Arm, embeddedBlob: true), + GetArchPath (AndroidTargetArch.X86_64, embeddedBlob: true), + GetArchPath (AndroidTargetArch.X86, embeddedBlob: true), }; ApkPaths = paths.AsReadOnly (); AabBasePaths = ApkPaths; @@ -50,10 +55,15 @@ static StoreReader_V2 () GetArchPath (AndroidTargetArch.Arm, AabBaseDir), GetArchPath (AndroidTargetArch.X86_64, AabBaseDir), GetArchPath (AndroidTargetArch.X86, AabBaseDir), + + GetArchPath (AndroidTargetArch.Arm64, AabBaseDir, embeddedBlob: true), + GetArchPath (AndroidTargetArch.Arm, AabBaseDir, embeddedBlob: true), + GetArchPath (AndroidTargetArch.X86_64, AabBaseDir, embeddedBlob: true), + GetArchPath (AndroidTargetArch.X86, AabBaseDir, embeddedBlob: true), }; AabPaths = paths.AsReadOnly (); - string GetArchPath (AndroidTargetArch arch, string? root = null) + string GetArchPath (AndroidTargetArch arch, string? root = null, bool embeddedBlob = false) { const string LibDirName = "lib"; @@ -65,7 +75,7 @@ string GetArchPath (AndroidTargetArch arch, string? root = null) root = LibDirName; } parts.Add (abi); - parts.Add (GetBlobName (abi)); + parts.Add (GetBlobName (abi, embeddedBlob)); return MonoAndroidHelper.MakeZipArchivePath (root, parts); } @@ -82,10 +92,23 @@ public StoreReader_V2 (Stream store, string path) }; } - static string GetBlobName (string abi) => $"libassemblies.{abi}.blob.so"; + static string GetBlobName (string abi, bool embeddedBlob) => embeddedBlob ? "libxamarin-app.so" : $"libassemblies.{abi}.blob.so"; protected override ulong GetStoreStartDataOffset () => elfOffset; + string PayloadErrorToString (ELFPayloadError error) + { + return error switch { + ELFPayloadError.NotELF => $"Store '{StorePath}' is not a valid ELF binary", + ELFPayloadError.LoadFailed => $"Store '{StorePath}' could not be loaded", + ELFPayloadError.NotSharedLibrary => $"Store '{StorePath}' is not a shared ELF library", + ELFPayloadError.NotLittleEndian => $"Store '{StorePath}' is not a little-endian ELF image", + ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section", + ELFPayloadError.NoEmbeddedStore => $"Store '{StorePath}' does not contain embedded data blob", + _ => $"Unknown ELF payload section error for store '{StorePath}': {error}" + }; + } + protected override bool IsSupported () { StoreStream.Seek (0, SeekOrigin.Begin); @@ -94,21 +117,24 @@ protected override bool IsSupported () uint magic = reader.ReadUInt32 (); if (magic == Utils.ELF_MAGIC) { ELFPayloadError error; - (elfOffset, _, error) = Utils.FindELFPayloadSectionOffsetAndSize (StoreStream); - + (elfOffset, _, error) = Utils.FindEmbeddedStoreOffsetAndSize (StoreStream); if (error != ELFPayloadError.None) { - string message = error switch { - ELFPayloadError.NotELF => $"Store '{StorePath}' is not a valid ELF binary", - ELFPayloadError.LoadFailed => $"Store '{StorePath}' could not be loaded", - ELFPayloadError.NotSharedLibrary => $"Store '{StorePath}' is not a shared ELF library", - ELFPayloadError.NotLittleEndian => $"Store '{StorePath}' is not a little-endian ELF image", - ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section", - _ => $"Unknown ELF payload section error for store '{StorePath}': {error}" - }; - Log.Debug (message); - } else if (elfOffset >= 0) { + MaybeLogError (error); + + (elfOffset, _, error) = Utils.FindELFPayloadSectionOffsetAndSize (StoreStream); + if (error != ELFPayloadError.None) { + MaybeLogError (error); + return false; + } + } + + // elfOffset cannot be 0, since we have ELF magic and headers (at least) at the beginning of + // the file. + if (elfOffset > 0) { StoreStream.Seek ((long)elfOffset, SeekOrigin.Begin); magic = reader.ReadUInt32 (); + } else { + return false; } } @@ -129,6 +155,15 @@ protected override bool IsSupported () header = new Header (magic, version, entry_count, index_entry_count, index_size); return true; + + void MaybeLogError (ELFPayloadError error) + { + if (error == ELFPayloadError.None) { + return; + } + + Log.Debug (PayloadErrorToString (error)); + } } protected override void Prepare () diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs b/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs index 284f501c9ac..2e2f03e5e52 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs @@ -4,6 +4,7 @@ using ELFSharp.ELF; using ELFSharp.ELF.Sections; +using Xamarin.Android.Tasks; using Xamarin.Tools.Zip; namespace Xamarin.Android.AssemblyStore; @@ -29,31 +30,28 @@ static class Utils public static readonly ArrayPool BytePool = ArrayPool.Shared; - public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSectionOffsetAndSize (Stream stream) + static (bool is64, IELF? elf, ELFPayloadError error) TryOpenELF (Stream stream) { + bool is64 = false; stream.Seek (0, SeekOrigin.Begin); Class elfClass = ELFReader.CheckELFType (stream); if (elfClass == Class.NotELF) { - return ReturnError (null, ELFPayloadError.NotELF); + return ReturnError (is64, null, ELFPayloadError.NotELF); } if (!ELFReader.TryLoad (stream, shouldOwnStream: false, out IELF? elf)) { - return ReturnError (elf, ELFPayloadError.LoadFailed); + return ReturnError (is64, elf, ELFPayloadError.LoadFailed); } if (elf.Type != FileType.SharedObject) { - return ReturnError (elf, ELFPayloadError.NotSharedLibrary); + return ReturnError (is64, elf, ELFPayloadError.NotSharedLibrary); } if (elf.Endianess != ELFSharp.Endianess.LittleEndian) { - return ReturnError (elf, ELFPayloadError.NotLittleEndian); - } - - if (!elf.TryGetSection ("payload", out ISection? payloadSection)) { - return ReturnError (elf, ELFPayloadError.NoPayloadSection); + return ReturnError (is64, elf, ELFPayloadError.NotLittleEndian); } - bool is64 = elf.Machine switch { + is64 = elf.Machine switch { Machine.ARM => false, Machine.Intel386 => false, @@ -63,6 +61,51 @@ public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSe _ => throw new NotSupportedException ($"Unsupported ELF architecture '{elf.Machine}'") }; + return (is64, elf, ELFPayloadError.None); + + (bool is64, IELF? elf, ELFPayloadError error) ReturnError (bool is64, IELF? elf, ELFPayloadError error) + { + elf?.Dispose (); + + return (is64, null, error); + } + } + + public static (ulong offset, ulong size, ELFPayloadError error) FindEmbeddedStoreOffsetAndSize (Stream stream) + { + (bool is64, IELF? elf, ELFPayloadError error) = TryOpenELF (stream); + if (elf == null || error != ELFPayloadError.None) { + return ReturnError (elf, error); + } + + const string SymbolName = "embedded_assembly_store"; + ISymbolTable? dynsym = ELFHelper.GetSymbolTable (elf, ".dynsym"); + if (is64) { + SymbolEntry? symbol = ELFHelper.FindSymbol (dynsym, SymbolName); + if (symbol != null) { + return (symbol.Value, symbol.Size, ELFPayloadError.None); + } + } else { + SymbolEntry? symbol = ELFHelper.FindSymbol (dynsym, SymbolName); + if (symbol != null) { + return ((ulong)symbol.Value, (ulong)symbol.Size, ELFPayloadError.None); + } + } + + return (0, 0, ELFPayloadError.NoEmbeddedStore); + } + + public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSectionOffsetAndSize (Stream stream) + { + (bool is64, IELF? elf, ELFPayloadError error) = TryOpenELF (stream); + if (elf == null || error != ELFPayloadError.None) { + return ReturnError (elf, error); + } + + if (!elf.TryGetSection ("payload", out ISection? payloadSection)) { + return ReturnError (elf, ELFPayloadError.NoPayloadSection); + } + ulong offset; ulong size; @@ -84,13 +127,13 @@ public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSe { return ((ulong)payload.Offset, (ulong)payload.Size); } + } - (ulong offset, ulong size, ELFPayloadError error) ReturnError (IELF? elf, ELFPayloadError error) - { - elf?.Dispose (); + static (ulong offset, ulong size, ELFPayloadError error) ReturnError (IELF? elf, ELFPayloadError error) + { + elf?.Dispose (); - return (0, 0, error); - } + return (0, 0, error); } public static (FileFormat format, FileInfo? info) DetectFileFormat (string path) diff --git a/tools/assembly-store-reader-mk2/assembly-store-reader.csproj b/tools/assembly-store-reader-mk2/assembly-store-reader.csproj index ec4c90fecb1..3bd2852d04e 100644 --- a/tools/assembly-store-reader-mk2/assembly-store-reader.csproj +++ b/tools/assembly-store-reader-mk2/assembly-store-reader.csproj @@ -24,6 +24,7 @@ +