diff --git a/Directory.Build.props b/Directory.Build.props
index 64033ca5019..57752f62665 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -49,7 +49,7 @@
6.12.0.148
6.0.0
6.0.0
- 2.13.1
+ 2.16.1
2.14.1
5.8.9.2
diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Private.CoreLib.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Private.CoreLib.xml
new file mode 100644
index 00000000000..c5aea679db6
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Private.CoreLib.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddRidMetadataAttributeStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddRidMetadataAttributeStep.cs
new file mode 100644
index 00000000000..6ee4fe3d2e7
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddRidMetadataAttributeStep.cs
@@ -0,0 +1,100 @@
+using System;
+
+using Mono.Cecil;
+using Mono.Linker;
+using Mono.Linker.Steps;
+
+#if ILLINK
+using Microsoft.Android.Sdk.ILLink;
+#endif
+
+namespace MonoDroid.Tuner;
+
+public class AddRidMetadataAttributeStep : BaseStep
+{
+ protected override void ProcessAssembly (AssemblyDefinition assembly)
+ {
+ if (!Annotations.HasAction (assembly)) {
+ return;
+ }
+
+ var action = Annotations.GetAction (assembly);
+ if (action == AssemblyAction.Skip || action == AssemblyAction.Delete) {
+ return;
+ }
+
+ string? rid = null;
+#if ILLINK
+ if (!Context.TryGetCustomData ("XARuntimeIdentifier", out rid)) {
+ throw new InvalidOperationException ("Missing XARuntimeIdentifier custom data");
+ }
+#endif
+ if (String.IsNullOrEmpty (rid)) {
+ throw new InvalidOperationException ("RID must have a non-empty value");
+ }
+
+ AssemblyDefinition corlib = GetCorlib ();
+ MethodDefinition assemblyMetadataAttributeCtor = FindAssemblyMetadataAttributeCtor (corlib);
+ TypeDefinition systemString = GetSystemString (corlib);
+
+ var attr = new CustomAttribute (assembly.MainModule.ImportReference (assemblyMetadataAttributeCtor));
+ attr.ConstructorArguments.Add (new CustomAttributeArgument (systemString, "XamarinAndroidAbi")); // key
+
+ // TODO: figure out how to get the RID...
+ attr.ConstructorArguments.Add (new CustomAttributeArgument (systemString, rid)); // value
+
+ assembly.CustomAttributes.Add (attr);
+
+ if (action == AssemblyAction.Copy) {
+ Annotations.SetAction (assembly, AssemblyAction.Save);
+ }
+ }
+
+ TypeDefinition GetSystemString (AssemblyDefinition asm) => FindType (asm, "System.String", required: true);
+
+ AssemblyDefinition GetCorlib ()
+ {
+ const string ImportAssembly = "System.Private.CoreLib";
+ AssemblyDefinition? asm = Context.Resolve (AssemblyNameReference.Parse (ImportAssembly));
+ if (asm == null) {
+ throw new InvalidOperationException ($"Unable to import assembly '{ImportAssembly}'");
+ }
+
+ return asm;
+ }
+
+ MethodDefinition FindAssemblyMetadataAttributeCtor (AssemblyDefinition asm)
+ {
+ const string AttributeType = "System.Reflection.AssemblyMetadataAttribute";
+
+ TypeDefinition assemblyMetadataAttribute = FindType (asm, AttributeType, required: true);
+ foreach (MethodDefinition md in assemblyMetadataAttribute!.Methods) {
+ if (!md.IsConstructor) {
+ continue;
+ }
+
+ return md;
+ }
+
+ throw new InvalidOperationException ($"Unable to find the {AttributeType} type constructor");
+ }
+
+ TypeDefinition? FindType (AssemblyDefinition asm, string typeName, bool required)
+ {
+ foreach (ModuleDefinition md in asm.Modules) {
+ foreach (TypeDefinition et in md.Types) {
+ if (String.Compare (typeName, et.FullName, StringComparison.Ordinal) != 0) {
+ continue;
+ }
+
+ return et;
+ }
+ }
+
+ if (required) {
+ throw new InvalidOperationException ($"Internal error: required type '{typeName}' in assembly {asm} not found");
+ }
+
+ return null;
+ }
+}
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 662aff0130b..aa9d99dd4c8 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
@@ -26,4 +26,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.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
index 346c89aa99b..4b47587b3b4 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
@@ -77,6 +77,35 @@ properties that determine build ordering.
_IncludeNativeSystemLibraries;
_CheckGoogleSdkRequirements;
+
+ <_GenerateJavaStubsDependsOnTargets>
+ _SetLatestTargetFrameworkVersion;
+ _PrepareAssemblies;
+ _PrepareNativeAssemblySources;
+ $(_AfterPrepareAssemblies);
+
+
+ <_GeneratePackageManagerJavaDependsOn>
+ _GenerateJavaStubs;
+ _RunAotForAllRIDs;
+ _ManifestMerger;
+ _ConvertCustomView;
+ $(_AfterConvertCustomView);
+ _AddStaticResources;
+ $(_AfterAddStaticResources);
+ _PrepareAssemblies;
+ _PrepareEnvironmentAssemblySources;
+ _GenerateEnvironmentFiles;
+ _GenerateAndroidRemapNativeCode;
+ _GenerateEmptyAndroidRemapNativeCode;
+ _IncludeNativeSystemLibraries;
+
+
+ <_GenerateAndroidRemapNativeCodeDependsOn>
+ _ConvertAndroidMamMappingFileToXml;
+ _CollectAndroidRemapMembers;
+ _PrepareAndroidRemapNativeAssemblySources
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
index 7400de192ff..0461633d57b 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
@@ -40,6 +40,7 @@ This file contains the .NET 5-specific targets to customize ILLink
https://github.com/dotnet/sdk/blob/a5393731b5b7b225692fff121f747fbbc9e8b140/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets#L147
-->
<_TrimmerCustomData Include="XATargetFrameworkDirectories" Value="$(_XATargetFrameworkDirectories)" />
+ <_TrimmerCustomData Include="XARuntimeIdentifier" Value="$(RuntimeIdentifier)" Condition=" '$(_AndroidAddRuntimeIdentifierToAssemblies)' == 'true' " />
<_TrimmerCustomData
Condition=" '$(_ProguardProjectConfiguration)' != '' "
Include="ProguardConfiguration"
@@ -94,6 +95,12 @@ This file contains the .NET 5-specific targets to customize ILLink
BeforeStep="MarkStep"
Type="MonoDroid.Tuner.FixLegacyResourceDesignerStep"
/>
+ <_TrimmerCustomSteps
+ Condition=" '$(_AndroidAddRuntimeIdentifierToAssemblies)' == 'true' "
+ Include="$(_AndroidLinkerCustomStepAssembly)"
+ AfterStep="CleanStep"
+ Type="MonoDroid.Tuner.AddRidMetadataAttributeStep"
+ />
<_PreserveLists Include="$(MSBuildThisFileDirectory)..\PreserveLists\*.xml" />
+
+
+
+
+
+
+
+
+
+
+ <_AndroidUseAssemblySharedLibraries Condition=" '$(EmbedAssembliesIntoApk)' != 'true' or '$(AndroidIncludeDebugSymbols)' == 'true' ">false
+ <_AndroidUseAssemblySharedLibraries Condition=" '$(_AndroidUseAssemblySharedLibraries)' == '' ">true
+
+ <_AndroidApplicationSharedLibraryPath>$(IntermediateOutputPath)app_shared_libraries\
+ <_CompressedAssembliesOutputDir>$(IntermediateOutputPath)android\lz4-2
+
+ <_AndroidKeepStandaloneAssemblySources Condition=" '$(_AndroidKeepStandaloneAssemblySources)' == '' ">false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_DSOFastPathAssembly Include="Java.Interop.dll" />
+ <_DSOFastPathAssembly Include="Mono.Android.Runtime.dll" />
+ <_DSOFastPathAssembly Include="Mono.Android.dll" />
+ <_DSOFastPathAssembly Include="System.Private.CoreLib.dll" />
+ <_DSOFastPathAssembly Include="System.Runtime.dll" />
+ <_DSOFastPathAssembly Include="$(AssemblyName).dll" />
+ <_DSOFastPathAssembly Include="$(_DesignerAssemblyName).dll">
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_NativeAssemblyTarget Include="@(_TypeMapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_TypeMapAssemblySource.abi)
+
+
+ <_NativeAssemblyTarget Include="@(_EnvironmentAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_EnvironmentAssemblySource.abi)
+
+
+ <_NativeAssemblyTarget Include="@(_MarshalMethodsAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_MarshalMethodsAssemblySource.abi)
+
+
+ <_NativeAssemblyTarget Include="@(_AndroidRemapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_AndroidRemapAssemblySource.abi)
+
+
+ <_NativeAssemblyTarget Include="@(_AssemblyDSOSourceApplication->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_AssemblyDSOSourceApplication.abi)
+
+
+
+
+
+
+ <_ApplicationSharedLibrary Include="$(_AndroidApplicationSharedLibraryPath)%(_BuildTargetAbis.Identity)\libxamarin-app.so">
+ %(_BuildTargetAbis.Identity)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_CreateApplicationSharedLibrariesDependsOn>
+ _PrepareAssemblyDSOSources;
+ _CreateStandaloneAssemblyDSOs;
+ _CompileApplicationNativeAssemblySources;
+ _PrepareApplicationSharedLibraryItems
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyNativeSourceGenerationTask.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyNativeSourceGenerationTask.cs
new file mode 100644
index 00000000000..942c257bee4
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyNativeSourceGenerationTask.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+public abstract class AssemblyNativeSourceGenerationTask : AndroidTask
+{
+ protected sealed class CompressionResult
+ {
+ public bool Compressed;
+ public uint CompressedSize;
+ public string OutputFile;
+ public FileInfo InputFileInfo;
+ }
+
+ internal sealed class GeneratedSource
+ {
+ public readonly string FilePath;
+ public readonly AndroidTargetArch TargetArch;
+ public readonly object? State;
+
+ public GeneratedSource (string filePath, AndroidTargetArch targetArch, object? state = null)
+ {
+ FilePath = filePath;
+ TargetArch = targetArch;
+ State = state;
+ }
+ }
+
+ [Required]
+ public string SourcesOutputDirectory { get; set; }
+
+ [Required]
+ public bool EnableCompression { get; set; }
+
+ [Required]
+ public string CompressedAssembliesOutputDirectory { get; set; }
+
+ AssemblyCompression? assemblyCompressor = null;
+
+ public override bool RunTask ()
+ {
+ if (EnableCompression) {
+ assemblyCompressor = new AssemblyCompression (Log, CompressedAssembliesOutputDirectory);
+ Log.LogDebugMessage ("Assembly compression ENABLED");
+ } else {
+ Log.LogDebugMessage ("Assembly compression DISABLED");
+ }
+
+ Generate ();
+ return !Log.HasLoggedErrors;
+ }
+
+ protected static bool ShouldSkipCompression (ITaskItem item)
+ {
+ string val = item.GetMetadata (DSOMetadata.AndroidSkipCompression);
+ if (String.IsNullOrEmpty (val)) {
+ return false;
+ }
+
+ if (!Boolean.TryParse (val, out bool skipCompression)) {
+ throw new InvalidOperationException ($"Internal error: unable to parse '{val}' as a boolean value, in item '{item.ItemSpec}', from the '{DSOMetadata.AndroidSkipCompression}' metadata");
+ }
+
+ return skipCompression;
+ }
+
+ protected CompressionResult Compress (ITaskItem assembly)
+ {
+ if (ShouldSkipCompression (assembly)) {
+ return new CompressionResult {
+ Compressed = false,
+ CompressedSize = 0,
+ OutputFile = assembly.ItemSpec,
+ InputFileInfo = new FileInfo (assembly.ItemSpec)
+ };
+ }
+
+ return Compress (assembly.ItemSpec, assembly.GetMetadata ("DestinationSubDirectory"));
+ }
+
+ protected CompressionResult Compress (string assemblyPath, string? destinationSubdirectory = null)
+ {
+ FileInfo fi = new (assemblyPath);
+
+ bool compressed;
+ string outputFile;
+ uint compressedSize = 0;
+
+ if (assemblyCompressor != null) {
+ (outputFile, compressed) = assemblyCompressor.CompressAssembly (assemblyPath, fi, destinationSubdirectory);
+
+ if (!compressed) {
+ compressedSize = 0;
+ } else {
+ var cfi = new FileInfo (outputFile);
+ compressedSize = (uint)cfi.Length;
+ }
+ } else {
+ outputFile = assemblyPath;
+ compressed = false;
+ compressedSize = 0;
+ }
+
+ Log.LogDebugMessage ($" will include from: {outputFile} (compressed? {compressed}; compressedSize == {compressedSize}");
+ return new CompressionResult {
+ Compressed = compressed,
+ CompressedSize = compressedSize,
+ OutputFile = outputFile,
+ InputFileInfo = fi,
+ };
+ }
+
+ internal List GenerateSources (ICollection supportedAbis, LLVMIR.LlvmIrComposer generator, LLVMIR.LlvmIrModule module, string baseFileName, object? sourceState = null)
+ {
+ if (String.IsNullOrEmpty (baseFileName)) {
+ throw new ArgumentException ("must not be null or empty", nameof (baseFileName));
+ }
+
+ var generatedSources = new List ();
+ foreach (string abi in supportedAbis) {
+ string targetAbi = abi.ToLowerInvariant ();
+ string outputAsmFilePath = Path.Combine (SourcesOutputDirectory, $"{baseFileName}.{targetAbi}.ll");
+
+ using var sw = MemoryStreamPool.Shared.CreateStreamWriter ();
+ AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
+ try {
+ generator.Generate (module, targetArch, sw, outputAsmFilePath);
+ } catch {
+ throw;
+ } finally {
+ sw.Flush ();
+ }
+
+ if (Files.CopyIfStreamChanged (sw.BaseStream, outputAsmFilePath)) {
+ Log.LogDebugMessage ($"File {outputAsmFilePath} was (re)generated");
+ }
+ generatedSources.Add (new GeneratedSource (outputAsmFilePath, targetArch, sourceState));
+ }
+
+ return generatedSources;
+ }
+
+ protected string GetAssemblyName (ITaskItem assembly)
+ {
+ if (!MonoAndroidHelper.IsSatelliteAssembly (assembly)) {
+ return Path.GetFileName (assembly.ItemSpec);
+ }
+
+ // It's a satellite assembly, %(DestinationSubDirectory) is the culture prefix
+ string? destinationSubDir = assembly.GetMetadata ("DestinationSubDirectory");
+ if (String.IsNullOrEmpty (destinationSubDir)) {
+ throw new InvalidOperationException ($"Satellite assembly '{assembly.ItemSpec}' has no culture metadata item");
+ }
+
+ string ret = $"{destinationSubDir}{Path.GetFileName (assembly.ItemSpec)}";
+ if (!assembly.ItemSpec.EndsWith (ret, StringComparison.OrdinalIgnoreCase)) {
+ throw new InvalidOperationException ($"Invalid metadata in satellite assembly '{assembly.ItemSpec}', culture metadata ('{destinationSubDir}') doesn't match file path");
+ }
+
+ return ret;
+ }
+
+ internal DSOAssemblyInfo MakeAssemblyInfo (ITaskItem assembly, string inputFile, long fileLength, uint compressedSize)
+ {
+ return new DSOAssemblyInfo (GetAssemblyName (assembly), inputFile, (uint)fileLength, compressedSize);
+ }
+
+ protected abstract void Generate ();
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildAndLinkStandaloneAssemblyDSOs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildAndLinkStandaloneAssemblyDSOs.cs
new file mode 100644
index 00000000000..e35f651bff4
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildAndLinkStandaloneAssemblyDSOs.cs
@@ -0,0 +1,337 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+using TPLTask = System.Threading.Tasks.Task;
+
+namespace Xamarin.Android.Tasks;
+
+public class BuildAndLinkStandaloneAssemblyDSOs : AssemblyNativeSourceGenerationTask
+{
+ sealed class TargetDSO
+ {
+ public readonly string Abi;
+ public readonly string OriginalAssemblyPath;
+ public readonly string SourceFileBaseName;
+ public readonly string DSOPath;
+ public readonly string? Culture;
+ public readonly bool SkipCompression;
+ public readonly ITaskItem TaskItem;
+ public readonly uint AssemblyLoadInfoIndex;
+
+ public TargetDSO (ITaskItem dso)
+ {
+ TaskItem = dso;
+ DSOPath = dso.ItemSpec;
+ Abi = EnsureValidMetadata (DSOMetadata.Abi);
+ OriginalAssemblyPath = EnsureValidMetadata (DSOMetadata.InputAssemblyPath);
+ SourceFileBaseName = EnsureValidMetadata (DSOMetadata.SourceFileBaseName);
+ Culture = dso.GetMetadata (DSOMetadata.SatelliteAssemblyCulture);
+
+ string metadata = EnsureValidMetadata (DSOMetadata.AssemblyLoadInfoIndex);
+ if (!UInt32.TryParse (metadata, out AssemblyLoadInfoIndex)) {
+ throw new InvalidOperationException ($"Internal error: unable to parse string '{metadata}' as an unsigned 32-bit integer");
+ }
+
+ SkipCompression = ShouldSkipCompression (dso);
+
+ string EnsureValidMetadata (string what)
+ {
+ string v = dso.GetMetadata (what);
+ if (String.IsNullOrEmpty (v)) {
+ throw new InvalidOperationException ($"Internal error: metadata '{what}' not found in item '{dso.ItemSpec}'");
+ }
+
+ return v;
+ }
+ }
+ }
+
+ sealed class LocalDSOAssemblyInfo : DSOAssemblyInfo
+ {
+ public readonly TargetDSO TargetDSO;
+ public readonly ITaskItem SharedLibraryItem;
+
+ public ulong? XAInputAssemblyDataOffset { get; set; }
+
+ public LocalDSOAssemblyInfo (TargetDSO targetDSO, ITaskItem sharedLibraryItem, string name, string inputFile, uint dataSize, uint compressedDataSize)
+ : base (name, inputFile, dataSize, compressedDataSize)
+ {
+ TargetDSO = targetDSO;
+ SharedLibraryItem = sharedLibraryItem;
+ }
+ }
+
+ const uint DefaultParallelBuilds = 8;
+
+ public override string TaskPrefix => "BALSAD";
+
+ [Required]
+ public ITaskItem[] TargetSharedLibraries { get; set; }
+
+ [Required]
+ public string AndroidBinUtilsDirectory { get; set; }
+
+ public bool KeepGeneratedSources { get; set; }
+
+ public string ParallelBuildsNumber { get; set; }
+
+ [Output]
+ public ITaskItem[] SharedLibraries { get; set; }
+
+ protected override void Generate ()
+ {
+ var assemblies = new Dictionary> (StringComparer.OrdinalIgnoreCase);
+ var sharedLibraries = new List ();
+ var supportedAbis = new HashSet ();
+
+ foreach (ITaskItem item in TargetSharedLibraries) {
+ var dso = new TargetDSO (item);
+ supportedAbis.Add (dso.Abi);
+
+ var dsoItem = new TaskItem (dso.DSOPath);
+ LocalDSOAssemblyInfo dsoInfo = AddAssembly (dso, dsoItem, assemblies);
+
+ dsoItem.SetMetadata (DSOMetadata.Abi, dso.Abi);
+ dsoItem.SetMetadata (DSOMetadata.AssemblyLoadInfoIndex, MonoAndroidHelper.CultureInvariantToString (dso.AssemblyLoadInfoIndex));
+ dsoItem.SetMetadata (DSOMetadata.Compressed, dsoInfo.CompressedDataSize == 0 ? "false" : "true");
+ dsoItem.SetMetadata (DSOMetadata.DataSize, MonoAndroidHelper.CultureInvariantToString (dsoInfo.CompressedDataSize == 0 ? dsoInfo.DataSize : dsoInfo.CompressedDataSize));
+ dsoItem.SetMetadata (DSOMetadata.InputAssemblyPath, dsoInfo.InputFile);
+ dsoItem.SetMetadata (DSOMetadata.OriginalAssemblyPath, dso.OriginalAssemblyPath);
+ dsoItem.SetMetadata (DSOMetadata.UncompressedDataSize, MonoAndroidHelper.CultureInvariantToString (dsoInfo.DataSize));
+
+ if (!String.IsNullOrEmpty (dso.Culture)) {
+ dsoItem.SetMetadata (DSOMetadata.SatelliteAssemblyCulture, dso.Culture);
+ }
+
+ sharedLibraries.Add (dsoItem);
+ }
+
+ uint maxParallelBuilds;
+ if (String.IsNullOrEmpty (ParallelBuildsNumber)) {
+ maxParallelBuilds = DefaultParallelBuilds;
+ } else if (!UInt32.TryParse (ParallelBuildsNumber, out maxParallelBuilds)) {
+ Log.LogWarning ($"Unable to parse parallel builds number from '{ParallelBuildsNumber}', an unsigned integer is expected. Will default to {DefaultParallelBuilds}");
+ maxParallelBuilds = DefaultParallelBuilds;
+ }
+ Log.LogDebugMessage ($"Will launch up to {maxParallelBuilds} builds at a time");
+
+ // Adjust maxParallelBuilds to be the next highest even multiple of supportedAbisCount, since each assembly will be built supportedAbisCount times and we
+ // want to build those sources together
+ uint supportedAbisCount = (uint)supportedAbis.Count;
+ if (maxParallelBuilds < supportedAbisCount) {
+ maxParallelBuilds = supportedAbisCount;
+ Log.LogDebugMessage ($"Maximum parallel builds number adjusted to match the number of supported ABIs, {supportedAbisCount}");
+ } else if (maxParallelBuilds % supportedAbisCount != 0) {
+ maxParallelBuilds += supportedAbisCount - (maxParallelBuilds % supportedAbisCount);
+ Log.LogDebugMessage ($"Maximum parallel builds number adjusted to the next even multiple of number of abis, {maxParallelBuilds}");
+ }
+
+ var sourcesBatch = new List ();
+ uint remainingSources = (uint)assemblies.Count * supportedAbisCount;
+ foreach (var kvp in assemblies) {
+ Dictionary infos = kvp.Value;
+
+ string baseName = String.Empty;
+ foreach (DSOAssemblyInfo info in infos.Values) {
+ var localInfo = (LocalDSOAssemblyInfo)info;
+
+ // All the architectures share the same base file name
+ baseName = localInfo.TargetDSO.SourceFileBaseName;
+ break;
+ }
+
+ var generator = new AssemblyDSOGenerator (infos);
+ List generatedSources = GenerateSources (supportedAbis, generator, generator.Construct (), baseName, sourceState: infos);
+ sourcesBatch.AddRange (generatedSources);
+ if ((uint)sourcesBatch.Count < maxParallelBuilds && remainingSources >= maxParallelBuilds) {
+ continue;
+ }
+
+ CompileAndLink (sourcesBatch);
+
+ remainingSources -= (uint)sourcesBatch.Count;
+ sourcesBatch.Clear ();
+ }
+
+ SharedLibraries = sharedLibraries.ToArray ();
+ }
+
+ void CompileAndLink (List generatedSources)
+ {
+ Log.LogDebugMessage ($"Compiling and linking {generatedSources.Count} shared libraries in parallel");
+ List configs = GetAssemblerConfigs (generatedSources);
+ var tasks = new List ();
+
+ foreach (NativeCompilationHelper.AssemblerConfig config in configs) {
+ var infos = config.State as Dictionary;
+ if (infos == null) {
+ throw new InvalidOperationException ($"Internal error: state for '{config.OutputFile}' is of invalid type.");
+ }
+
+ tasks.Add (TPLTask.Factory.StartNew (() => DoCompileAndLink (config, infos)));
+ }
+
+ // TODO: add timeout
+ // TODO: add cancellation support
+ try {
+ TPLTask.WaitAll (tasks.ToArray ());
+ } catch (AggregateException aex) {
+ foreach (Exception ex in aex.InnerExceptions) {
+ Log.LogErrorFromException (ex);
+ }
+
+ throw new InvalidOperationException ("Native compilation failed");
+ }
+ }
+
+ void DoCompileAndLink (NativeCompilationHelper.AssemblerConfig config, Dictionary infos)
+ {
+ bool success = NativeCompilationHelper.RunAssembler (config);
+ if (!success) {
+ return;
+ }
+
+ if (!infos.TryGetValue (config.TargetArch, out DSOAssemblyInfo genericDsoInfo)) {
+ throw new InvalidOperationException ($"Internal error: DSO info for arch '{config.TargetArch}' not found (input file: {config.InputSource})");
+ }
+
+ var dsoInfo = genericDsoInfo as LocalDSOAssemblyInfo;
+ if (dsoInfo == null) {
+ throw new InvalidOperationException ($"Internal error: DSO info must be an instance of {nameof(LocalDSOAssemblyInfo)}, but it was {genericDsoInfo.GetType ()} instead");
+ }
+
+ var linkerConfig = new NativeCompilationHelper.LinkerConfig (
+ log: Log,
+ targetArch: config.TargetArch,
+ linkerPath: NativeCompilationHelper.GetLinkerPath (AndroidBinUtilsDirectory),
+ outputSharedLibrary: dsoInfo.SharedLibraryItem.ItemSpec
+ );
+
+ string inputObjectFile;
+ if (!String.IsNullOrEmpty (config.WorkingDirectory)) {
+ inputObjectFile = Path.Combine (config.WorkingDirectory, config.OutputFile);
+ } else {
+ inputObjectFile = config.OutputFile;
+ }
+ linkerConfig.ObjectFilePaths.Add (inputObjectFile);
+
+ success = NativeCompilationHelper.RunLinker (linkerConfig);
+ if (!success || KeepGeneratedSources) {
+ return;
+ }
+
+ (dsoInfo.XAInputAssemblyDataOffset, ulong? symbolSize) = ELFHelper.GetExportedSymbolOffsetAndSize (Log, linkerConfig.OutputFile, AssemblyDSOGenerator.XAInputAssemblyDataVarName);
+ if (dsoInfo.XAInputAssemblyDataOffset == null) {
+ Log.LogError ($"Shared library '{linkerConfig.OutputFile}' does not export the required symbol '{AssemblyDSOGenerator.XAInputAssemblyDataVarName}'");
+ return;
+ }
+ Log.LogDebugMessage ($"Shared library '{linkerConfig.OutputFile}' has symbol '{AssemblyDSOGenerator.XAInputAssemblyDataVarName}' at offset {dsoInfo.XAInputAssemblyDataOffset}");
+ dsoInfo.SharedLibraryItem.SetMetadata (DSOMetadata.DataSymbolOffset, MonoAndroidHelper.CultureInvariantToString (dsoInfo.XAInputAssemblyDataOffset));
+
+ ulong expectedSize;
+ if (dsoInfo.CompressedDataSize == 0) {
+ expectedSize = dsoInfo.DataSize;
+ } else {
+ expectedSize = dsoInfo.CompressedDataSize;
+ }
+
+ if (expectedSize != symbolSize) {
+ Log.LogError ($"Shared library '{linkerConfig.OutputFile}' symbol '{AssemblyDSOGenerator.XAInputAssemblyDataVarName}' has invalid size {symbolSize} (expected {expectedSize})");
+ return;
+ }
+
+ string sourceFile;
+ if (!String.IsNullOrEmpty (config.WorkingDirectory)) {
+ sourceFile = Path.Combine (config.WorkingDirectory, config.InputSource);
+ } else {
+ sourceFile = config.InputSource;
+ }
+
+ try {
+ Log.LogDebugMessage ($"Will delete source: {sourceFile}");
+ if (File.Exists (sourceFile)) {
+ File.Delete (sourceFile);
+ } else {
+ Log.LogDebugMessage (" file doesn't exist");
+ }
+ } catch (Exception ex) {
+ Log.LogDebugMessage ($"Generated source file '{sourceFile}' not removed. Exception was thrown while removing it: {ex}");
+ }
+ }
+
+ List GetAssemblerConfigs (List generatedSources)
+ {
+ string assemblerPath = NativeCompilationHelper.GetAssemblerPath (AndroidBinUtilsDirectory);
+ string workingDirectory = Path.GetFullPath (SourcesOutputDirectory);
+
+ var configs = new List ();
+ foreach (GeneratedSource source in generatedSources) {
+ string sourceFile = Path.GetFileName (source.FilePath);
+
+ var config = new NativeCompilationHelper.AssemblerConfig (
+ log: Log,
+ targetArch: source.TargetArch,
+ assemblerPath: assemblerPath,
+ inputSource: sourceFile,
+ workingDirectory: workingDirectory
+ ) {
+ State = source.State,
+ };
+
+ configs.Add (config);
+ }
+
+ return configs;
+ }
+
+ LocalDSOAssemblyInfo AddAssembly (TargetDSO dso, ITaskItem dsoItem, Dictionary> assemblies)
+ {
+ string asmName = Path.GetFileNameWithoutExtension (dso.OriginalAssemblyPath);
+ if (!String.IsNullOrEmpty (dso.Culture)) {
+ asmName = $"{dso.Culture}/{asmName}";
+ }
+
+ if (!assemblies.TryGetValue (asmName, out Dictionary infos)) {
+ infos = new Dictionary ();
+ assemblies.Add (asmName, infos);
+ }
+
+ string destinationSubdirectory = dso.Abi;
+ if (!String.IsNullOrEmpty (dso.Culture)) {
+ destinationSubdirectory = Path.Combine (destinationSubdirectory, dso.Culture);
+ }
+
+ string inputFile;
+ uint compressedSize;
+ uint inputFileSize;
+
+ if (!dso.SkipCompression) {
+ CompressionResult cres = Compress (dso.OriginalAssemblyPath, destinationSubdirectory);
+ inputFile = cres.OutputFile; // It will be set to the **original** assembly path if compression wasn't done
+ compressedSize = cres.CompressedSize;
+ inputFileSize = (uint)cres.InputFileInfo.Length;
+ } else {
+ inputFile = dso.OriginalAssemblyPath;
+ compressedSize = 0;
+ var fi = new FileInfo (inputFile);
+ inputFileSize = (uint)fi.Length;
+ }
+
+ AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (dso.Abi);
+ var dsoInfo = new LocalDSOAssemblyInfo (dso, dsoItem, GetAssemblyName (dso.TaskItem), inputFile, inputFileSize, compressedSize);
+
+ try {
+ infos.Add (targetArch, dsoInfo);
+ } catch (Exception ex) {
+ throw new InvalidOperationException ($"Internal error: failed to add '{dso.OriginalAssemblyPath}' for target arch {targetArch}", ex);
+ }
+
+ return dsoInfo;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
index fbcaa1e0944..aa9cf1882f5 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
@@ -83,8 +83,6 @@ public class BuildApk : AndroidTask
public string TlsProvider { get; set; }
public string UncompressedFileExtensions { get; set; }
- // Make it required after https://github.com/xamarin/monodroid/pull/1094 is merged
- //[Required]
public bool EnableCompression { get; set; }
public bool IncludeWrapSh { get; set; }
@@ -95,6 +93,8 @@ public class BuildApk : AndroidTask
public bool UseAssemblyStore { get; set; }
+ public bool UseAssemblySharedLibraries { get; set; }
+
public string ZipFlushFilesLimit { get; set; }
public string ZipFlushSizeLimit { get; set; }
@@ -130,7 +130,7 @@ protected virtual void FixupArchive (ZipArchiveEx zip) { }
List excludePatterns = new List ();
- void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary compressedAssembliesInfo, string assemblyStoreApkName)
+ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, string assemblyStoreApkName)
{
ArchiveFileList files = new ArchiveFileList ();
bool refresh = true;
@@ -197,7 +197,7 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
}
if (EmbedAssemblies) {
- AddAssemblies (apk, debug, compress, compressedAssembliesInfo, assemblyStoreApkName);
+ AddAssemblies (apk, debug, assemblyStoreApkName);
apk.Flush ();
}
@@ -312,18 +312,8 @@ public override bool RunTask ()
}
bool debug = _Debug;
- bool compress = !debug && EnableCompression;
- IDictionary compressedAssembliesInfo = null;
-
- if (compress) {
- string key = CompressedAssemblyInfo.GetKey (ProjectFullPath);
- Log.LogDebugMessage ($"Retrieving assembly compression info with key '{key}'");
- compressedAssembliesInfo = BuildEngine4.UnregisterTaskObjectAssemblyLocal> (key, RegisteredTaskObjectLifetime.Build);
- if (compressedAssembliesInfo == null)
- throw new InvalidOperationException ($"Assembly compression info not found for key '{key}'. Compression will not be performed.");
- }
- ExecuteWithAbi (SupportedAbis, ApkInputPath, ApkOutputPath, debug, compress, compressedAssembliesInfo, assemblyStoreApkName: null);
+ ExecuteWithAbi (SupportedAbis, ApkInputPath, ApkOutputPath, debug, assemblyStoreApkName: null);
outputFiles.Add (ApkOutputPath);
if (CreatePackagePerAbi && SupportedAbis.Length > 1) {
foreach (var abi in SupportedAbis) {
@@ -332,7 +322,7 @@ public override bool RunTask ()
var apk = Path.GetFileNameWithoutExtension (ApkOutputPath);
ExecuteWithAbi (new [] { abi }, String.Format ("{0}-{1}", ApkInputPath, abi),
Path.Combine (path, String.Format ("{0}-{1}.apk", apk, abi)),
- debug, compress, compressedAssembliesInfo, assemblyStoreApkName: abi);
+ debug, assemblyStoreApkName: abi);
outputFiles.Add (Path.Combine (path, String.Format ("{0}-{1}.apk", apk, abi)));
}
}
@@ -362,34 +352,14 @@ static Regex FileGlobToRegEx (string fileGlob, RegexOptions options)
return new Regex (sb.ToString (), options);
}
- void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary compressedAssembliesInfo, string assemblyStoreApkName)
+ void AddAssemblies (ZipArchiveEx apk, bool debug, string assemblyStoreApkName)
{
- string sourcePath;
- AssemblyCompression.AssemblyData compressedAssembly = null;
- string compressedOutputDir = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ApkOutputPath), "..", "lz4"));
- AssemblyStoreGenerator storeGenerator;
-
- if (UseAssemblyStore) {
- storeGenerator = new AssemblyStoreGenerator (AssembliesPath, Log);
- } else {
- storeGenerator = null;
+ if (UseAssemblySharedLibraries) {
+ // All the assemblies are in shared libraries
+ return;
}
- AssemblyStoreAssemblyInfo storeAssembly = null;
-
- //
- // NOTE
- //
- // The very first store (ID 0) **must** contain an index of all the assemblies included in the application, even if they
- // are included in other APKs than the base one. The ID 0 store **must** be placed in the base assembly
- //
-
- // Currently, all the assembly stores end up in the "base" apk (the APK name is the key in the dictionary below) but the code is ready for the time when we
- // partition assemblies into "feature" APKs
- const string DefaultBaseApkName = "base";
- if (String.IsNullOrEmpty (assemblyStoreApkName)) {
- assemblyStoreApkName = DefaultBaseApkName;
- }
+ string sourcePath;
// Add user assemblies
AddAssembliesFromCollection (ResolvedUserAssemblies);
@@ -397,37 +367,6 @@ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary> assemblyStorePaths = storeGenerator.Generate (Path.GetDirectoryName (ApkOutputPath));
- if (assemblyStorePaths == null) {
- throw new InvalidOperationException ("Assembly store generator did not generate any stores");
- }
-
- if (!assemblyStorePaths.TryGetValue (assemblyStoreApkName, out List baseAssemblyStores) || baseAssemblyStores == null || baseAssemblyStores.Count == 0) {
- throw new InvalidOperationException ("Assembly store generator didn't generate the required base stores");
- }
-
- string assemblyStorePrefix = $"{assemblyStoreApkName}_";
- foreach (string assemblyStorePath in baseAssemblyStores) {
- string inArchiveName = Path.GetFileName (assemblyStorePath);
-
- if (inArchiveName.StartsWith (assemblyStorePrefix, StringComparison.Ordinal)) {
- inArchiveName = inArchiveName.Substring (assemblyStorePrefix.Length);
- }
-
- CompressionMethod compressionMethod;
- if (inArchiveName.EndsWith (".manifest", StringComparison.Ordinal)) {
- compressionMethod = CompressionMethod.Default;
- } else {
- compressionMethod = UncompressedMethod;
- }
-
- AddFileToArchiveIfNewer (apk, assemblyStorePath, AssembliesPath + inArchiveName, compressionMethod);
- }
-
void AddAssembliesFromCollection (ITaskItem[] assemblies)
{
foreach (ITaskItem assembly in assemblies) {
@@ -440,23 +379,15 @@ void AddAssembliesFromCollection (ITaskItem[] assemblies)
Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec);
}
- sourcePath = CompressAssembly (assembly);
+ sourcePath = assembly.ItemSpec;
// Add assembly
var assemblyPath = GetAssemblyPath (assembly, frameworkAssembly: false);
- if (UseAssemblyStore) {
- storeAssembly = new AssemblyStoreAssemblyInfo (sourcePath, assemblyPath, assembly.GetMetadata ("Abi"));
- } else {
- AddFileToArchiveIfNewer (apk, sourcePath, assemblyPath + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
- }
+ AddFileToArchiveIfNewer (apk, sourcePath, assemblyPath + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
// Try to add config if exists
var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config");
- if (UseAssemblyStore) {
- storeAssembly.SetConfigPath (config);
- } else {
- AddAssemblyConfigEntry (apk, assemblyPath, config);
- }
+ AddAssemblyConfigEntry (apk, assemblyPath, config);
// Try to add symbols if Debug
if (debug) {
@@ -468,72 +399,11 @@ void AddAssembliesFromCollection (ITaskItem[] assemblies)
}
if (!String.IsNullOrEmpty (symbolsPath)) {
- if (UseAssemblyStore) {
- storeAssembly.SetDebugInfoPath (symbolsPath);
- } else {
- AddFileToArchiveIfNewer (apk, symbolsPath, assemblyPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
- }
+ AddFileToArchiveIfNewer (apk, symbolsPath, assemblyPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
}
}
-
- if (UseAssemblyStore) {
- storeGenerator.Add (assemblyStoreApkName, storeAssembly);
- }
}
}
-
- void EnsureCompressedAssemblyData (string sourcePath, uint descriptorIndex)
- {
- if (compressedAssembly == null)
- compressedAssembly = new AssemblyCompression.AssemblyData (sourcePath, descriptorIndex);
- else
- compressedAssembly.SetData (sourcePath, descriptorIndex);
- }
-
- string CompressAssembly (ITaskItem assembly)
- {
- if (!compress) {
- return assembly.ItemSpec;
- }
-
- if (bool.TryParse (assembly.GetMetadata ("AndroidSkipCompression"), out bool value) && value) {
- Log.LogDebugMessage ($"Skipping compression of {assembly.ItemSpec} due to 'AndroidSkipCompression' == 'true' ");
- return assembly.ItemSpec;
- }
-
- var key = CompressedAssemblyInfo.GetDictionaryKey (assembly);
- if (compressedAssembliesInfo.TryGetValue (key, out CompressedAssemblyInfo info) && info != null) {
- EnsureCompressedAssemblyData (assembly.ItemSpec, info.DescriptorIndex);
- string assemblyOutputDir;
- string subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
- if (!String.IsNullOrEmpty (subDirectory))
- assemblyOutputDir = Path.Combine (compressedOutputDir, subDirectory);
- else
- assemblyOutputDir = compressedOutputDir;
- AssemblyCompression.CompressionResult result = AssemblyCompression.Compress (compressedAssembly, assemblyOutputDir);
- if (result != AssemblyCompression.CompressionResult.Success) {
- switch (result) {
- case AssemblyCompression.CompressionResult.EncodingFailed:
- Log.LogMessage ($"Failed to compress {assembly.ItemSpec}");
- break;
-
- case AssemblyCompression.CompressionResult.InputTooBig:
- Log.LogMessage ($"Input assembly {assembly.ItemSpec} exceeds maximum input size");
- break;
-
- default:
- Log.LogMessage ($"Unknown error compressing {assembly.ItemSpec}");
- break;
- }
- return assembly.ItemSpec;
- }
- return compressedAssembly.DestinationPath;
- } else {
- Log.LogDebugMessage ($"Assembly missing from {nameof (CompressedAssemblyInfo)}: {key}");
- }
-
- return assembly.ItemSpec;
- }
}
bool AddFileToArchiveIfNewer (ZipArchiveEx apk, string file, string inArchivePath, CompressionMethod compressionMethod = CompressionMethod.Default)
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs
index 2dca284c933..45d81a6b1c5 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs
@@ -1,14 +1,7 @@
-using System;
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 Xamarin.Build;
using Microsoft.Android.Build.Tasks;
namespace Xamarin.Android.Tasks
@@ -17,13 +10,6 @@ public class CompileNativeAssembly : AndroidAsyncTask
{
public override string TaskPrefix => "CNA";
- sealed class Config
- {
- public string AssemblerPath;
- public string AssemblerOptions;
- public string InputSource;
- }
-
[Required]
public ITaskItem[] Sources { get; set; }
@@ -38,109 +24,29 @@ sealed class Config
public override System.Threading.Tasks.Task RunTaskAsync ()
{
- return this.WhenAll (GetAssemblerConfigs (), RunAssembler);
+ return this.WhenAll (GetAssemblerConfigs (), (NativeCompilationHelper.AssemblerConfig config) => NativeCompilationHelper.RunAssembler (config));
}
- void RunAssembler (Config config)
+ IEnumerable GetAssemblerConfigs ()
{
- 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 ();
- }
- }
- }
-
- 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");
+ string assemblerPath = NativeCompilationHelper.GetAssemblerPath (AndroidBinUtilsDirectory);
+ string workingDirectory = Path.GetFullPath (WorkingDirectory);
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 new NativeCompilationHelper.AssemblerConfig (
+ log: Log,
+ targetArch: MonoAndroidHelper.AbiToTargetArch (item.GetMetadata ("abi")),
+ assemblerPath: assemblerPath,
+ inputSource: sourceFile,
+ workingDirectory: workingDirectory
+ ) {
+ CancellationToken = CancellationToken,
+ Cancel = () => Cancel (),
};
}
}
-
- 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/GenerateAppAssemblyDSONativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAppAssemblyDSONativeSourceFiles.cs
new file mode 100644
index 00000000000..cc2f999a02d
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAppAssemblyDSONativeSourceFiles.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+public class GenerateAppAssemblyDSONativeSourceFiles : AssemblyNativeSourceGenerationTask
+{
+ public override string TaskPrefix => "GADNSF";
+
+ [Required]
+ public string[] SupportedAbis { get; set; }
+
+ [Required]
+ public ITaskItem[] Assemblies { get; set; }
+
+ [Required]
+ public ITaskItem[] FastPathAssemblies { get; set; }
+
+ [Required]
+ public ITaskItem[] StandaloneAssemblyDSOs { get; set; }
+
+ protected override void Generate ()
+ {
+ Dictionary> dsoAssembliesInfo = new ();
+ var satelliteAssemblies = new List ();
+
+ // The figure here includes only the "fast path" assemblies, that is those which end up in libxamarin-app.so
+ ulong inputAssemblyDataSize = 0;
+
+ // This variable, however, keeps the total size of all the assemblies, both from libxamarin-app.so and from
+ // their respective .so shared libraries.
+ ulong uncompressedAssemblyDataSize = 0;
+ AndroidTargetArch arch;
+ var fastPathAssemblies = new Dictionary (StringComparer.OrdinalIgnoreCase);
+
+ if (FastPathAssemblies.Length > 0) {
+ foreach (ITaskItem item in FastPathAssemblies) {
+ if (fastPathAssemblies.ContainsKey (item.ItemSpec)) {
+ continue;
+ }
+ fastPathAssemblies.Add (item.ItemSpec, item);
+ }
+ };
+
+ Log.LogDebugMessage ("Processing input assemblies");
+ foreach (ITaskItem assembly in Assemblies) {
+ if (!fastPathAssemblies.TryGetValue (Path.GetFileName (assembly.ItemSpec), out ITaskItem fastPathItem)) {
+ continue;
+ }
+
+ string? skipCompression = fastPathItem.GetMetadata (DSOMetadata.AndroidSkipCompression);
+ if (!String.IsNullOrEmpty (skipCompression)) {
+ // It can potentially override the same metadata item in `assembly`, but that's fine, since the item's just a copy and we want the fast path items
+ // to dictate whether or not the assembly is compressed.
+ assembly.SetMetadata (DSOMetadata.AndroidSkipCompression, skipCompression);
+ }
+
+ CompressionResult cres = Compress (assembly);
+ string inputFile = cres.OutputFile;
+
+ Log.LogDebugMessage ($" Fast path input: {assembly.ItemSpec}; compressed? {cres.Compressed}");
+ inputAssemblyDataSize += cres.Compressed ? (ulong)cres.InputFileInfo.Length : cres.CompressedSize;
+ uncompressedAssemblyDataSize += (ulong)cres.InputFileInfo.Length;
+
+ if (!MonoAndroidHelper.IsSatelliteAssembly (assembly)) {
+ arch = MonoAndroidHelper.GetTargetArch (assembly);
+ StoreAssembly (arch, assembly, inputFile, cres.InputFileInfo.Length, cres.CompressedSize);
+ continue;
+ }
+
+ // Satellite assemblies don't have any ABI, so they need to be added to all supported architectures.
+ // We will do it after this loop, when all architectures are known.
+ satelliteAssemblies.Add (MakeAssemblyInfo (assembly, inputFile, cres.InputFileInfo.Length, cres.CompressedSize));
+ }
+
+ foreach (ITaskItem dsoItem in StandaloneAssemblyDSOs) {
+ arch = MonoAndroidHelper.GetTargetArch (dsoItem);
+ DSOAssemblyInfo info = MakeStandaloneAssemblyInfo (dsoItem);
+
+ Log.LogDebugMessage ($" Standalone input: {info.Name}");
+ uncompressedAssemblyDataSize += info.DataSize;
+ AddAssemblyToList (arch, info);
+ }
+
+ if (satelliteAssemblies.Count > 0) {
+ foreach (DSOAssemblyInfo info in satelliteAssemblies) {
+ foreach (AndroidTargetArch dsoArch in dsoAssembliesInfo.Keys.ToList ()) {
+ AddAssemblyToList (dsoArch, info);
+ }
+ }
+ }
+
+ Log.LogDebugMessage ($"Size of assembly data to stash: {inputAssemblyDataSize}");
+ Log.LogDebugMessage ($"Number of architectures to stash into DSOs: {dsoAssembliesInfo.Count}");
+ foreach (var kvp in dsoAssembliesInfo) {
+ Log.LogDebugMessage ($" {kvp.Key}: {kvp.Value.Count} assemblies");
+ }
+
+ var generator = new AssemblyDSOGenerator (fastPathAssemblies.Keys, dsoAssembliesInfo, inputAssemblyDataSize, uncompressedAssemblyDataSize);
+ GenerateSources (SupportedAbis, generator, generator.Construct (), PrepareAbiItems.AssemblyDSOBase);
+
+ void StoreAssembly (AndroidTargetArch arch, ITaskItem assembly, string inputFile, long fileLength, uint compressedSize, DSOAssemblyInfo? info = null)
+ {
+ AddAssemblyToList (arch, MakeAssemblyInfo (assembly, inputFile, fileLength, compressedSize));
+ }
+
+ void AddAssemblyToList (AndroidTargetArch arch, DSOAssemblyInfo info)
+ {
+ if (!dsoAssembliesInfo.TryGetValue (arch, out List? assemblyList)) {
+ assemblyList = new List ();
+ dsoAssembliesInfo.Add (arch, assemblyList);
+ }
+ assemblyList.Add (info);
+ Log.LogDebugMessage ($" added to arch {arch} with name: {assemblyList[assemblyList.Count - 1].Name}");
+ }
+ }
+
+ DSOAssemblyInfo MakeStandaloneAssemblyInfo (ITaskItem dsoItem)
+ {
+ string name = Path.GetFileName (GetRequiredMetadata (DSOMetadata.OriginalAssemblyPath));
+ string? cultureName = dsoItem.GetMetadata (DSOMetadata.SatelliteAssemblyCulture);
+
+ if (!String.IsNullOrEmpty (cultureName)) {
+ name = $"{cultureName}/{name}";
+ }
+
+ string inputFile = GetRequiredMetadata (DSOMetadata.InputAssemblyPath);
+ string compressed = GetRequiredMetadata (DSOMetadata.Compressed);
+ if (!Boolean.TryParse (compressed, out bool isCompressed)) {
+ throw new InvalidOperationException ($"Internal error: unable to parse '{compressed}' as a boolean value, from the '{DSOMetadata.Compressed}' metadata of item {dsoItem}");
+ }
+
+ uint dataSize = GetUintFromRequiredMetadata (DSOMetadata.DataSize);
+ uint compressedDataSize;
+ if (!isCompressed) {
+ compressedDataSize = 0;
+ } else {
+ compressedDataSize = dataSize;
+ dataSize = GetUintFromRequiredMetadata (DSOMetadata.UncompressedDataSize);
+
+ }
+
+ return new DSOAssemblyInfo (name, inputFile, dataSize, compressedDataSize, isStandalone: true, Path.GetFileName (dsoItem.ItemSpec)) {
+ AssemblyLoadInfoIndex = GetUintFromRequiredMetadata (DSOMetadata.AssemblyLoadInfoIndex),
+ AssemblyDataSymbolOffset = GetUlongFromRequiredMetadata (DSOMetadata.DataSymbolOffset),
+ };
+
+ string GetRequiredMetadata (string name)
+ {
+ string ret = dsoItem.GetMetadata (name);
+ if (String.IsNullOrEmpty (ret)) {
+ throw new InvalidOperationException ($"Internal error: item {dsoItem} doesn't contain required metadata item '{name}' or its value is an empty string");
+ }
+
+ return ret;
+ }
+
+ uint GetUintFromRequiredMetadata (string name)
+ {
+ string metadata = GetRequiredMetadata (name);
+ if (!UInt32.TryParse (metadata, out uint value)) {
+ throw new InvalidOperationException ($"Internal error: unable to parse '{metadata}' as a UInt32 value, from the '{name}' metadata of item {dsoItem}");
+ }
+
+ return value;
+ }
+
+ ulong GetUlongFromRequiredMetadata (string name)
+ {
+ string metadata = GetRequiredMetadata (name);
+ if (!UInt64.TryParse (metadata, out ulong value)) {
+ throw new InvalidOperationException ($"Internal error: unable to parse '{metadata}' as a UInt64 value, from the '{name}' metadata of item {dsoItem}");
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
deleted file mode 100644
index 48f849596e0..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Build.Framework;
-using Microsoft.Android.Build.Tasks;
-
-namespace Xamarin.Android.Tasks
-{
- public class GenerateCompressedAssembliesNativeSourceFiles : AndroidTask
- {
- public override string TaskPrefix => "GCANSF";
-
- [Required]
- public ITaskItem[] ResolvedAssemblies { get; set; }
-
- [Required]
- public string [] SupportedAbis { get; set; }
-
- [Required]
- public string EnvironmentOutputDirectory { get; set; }
-
- [Required]
- public bool Debug { get; set; }
-
- [Required]
- public bool EnableCompression { get; set; }
-
- [Required]
- public string ProjectFullPath { get; set; }
-
- public override bool RunTask ()
- {
- GenerateCompressedAssemblySources ();
- return !Log.HasLoggedErrors;
- }
-
- void GenerateCompressedAssemblySources ()
- {
- if (Debug || !EnableCompression) {
- Generate (null);
- return;
- }
-
- var assemblies = new SortedDictionary (StringComparer.Ordinal);
- foreach (ITaskItem assembly in ResolvedAssemblies) {
- if (bool.TryParse (assembly.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) {
- continue;
- }
-
- var assemblyKey = CompressedAssemblyInfo.GetDictionaryKey (assembly);
- if (assemblies.ContainsKey (assemblyKey)) {
- Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec}");
- continue;
- }
-
- var fi = new FileInfo (assembly.ItemSpec);
- if (!fi.Exists) {
- Log.LogError ($"Assembly {assembly.ItemSpec} does not exist");
- continue;
- }
-
- assemblies.Add (assemblyKey, new CompressedAssemblyInfo (checked((uint)fi.Length)));
- }
-
- uint index = 0;
- foreach (var kvp in assemblies) {
- kvp.Value.DescriptorIndex = index++;
- }
-
- string key = CompressedAssemblyInfo.GetKey (ProjectFullPath);
- Log.LogDebugMessage ($"Storing compression assemblies info with key '{key}'");
- BuildEngine4.RegisterTaskObjectAssemblyLocal (key, assemblies, RegisteredTaskObjectLifetime.Build);
- Generate (assemblies);
-
- void Generate (IDictionary dict)
- {
- var composer = new CompressedAssembliesNativeAssemblyGenerator (dict);
- LLVMIR.LlvmIrModule compressedAssemblies = composer.Construct ();
-
- foreach (string abi in SupportedAbis) {
- string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"compressed_assemblies.{abi.ToLowerInvariant ()}");
- string llvmIrFilePath = $"{baseAsmFilePath}.ll";
-
- using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
- try {
- composer.Generate (compressedAssemblies, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llvmIrFilePath);
- } catch {
- throw;
- } finally {
- sw.Flush ();
- }
-
- if (Files.CopyIfStreamChanged (sw.BaseStream, llvmIrFilePath)) {
- Log.LogDebugMessage ($"File {llvmIrFilePath} was regenerated");
- }
- }
- }
- }
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index 80f1f25b279..c25d4b3432f 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -2,14 +2,12 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
using Mono.Cecil;
@@ -27,6 +25,22 @@ namespace Xamarin.Android.Tasks
public class GenerateJavaStubs : AndroidTask
{
+ sealed class RunState
+ {
+ public XAAssemblyResolver Resolver { get; set; }
+ public ICollection JavaTypeAssemblies { get; set; }
+ public ICollection UserAssemblies { get; set; }
+ public InputAssemblySet AssemblySet { get; set; }
+ public bool UseMarshalMethods { get; set; }
+ public AndroidTargetArch TargetArch { get; set; } = AndroidTargetArch.None;
+
+ ///
+ /// If `true`, generate code/data that doesn't depend on a specific RID (e.g. ACW maps or JCWs)
+ /// To be used once per multi-RID runs.
+ ///
+ public bool GenerateRidAgnosticParts { get; set; }
+ }
+
public const string MarshalMethodsRegisterTaskKey = ".:!MarshalMethods!:.";
public override string TaskPrefix => "GJS";
@@ -101,12 +115,7 @@ public class GenerateJavaStubs : AndroidTask
public override bool RunTask ()
{
try {
- bool useMarshalMethods = !Debug && EnableMarshalMethods;
- // We're going to do 3 steps here instead of separate tasks so
- // we can share the list of JLO TypeDefinitions between them
- using (XAAssemblyResolver res = MakeResolver (useMarshalMethods)) {
- Run (res, useMarshalMethods);
- }
+ Run ();
} catch (XamarinAndroidException e) {
Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode);
if (MonoAndroidHelper.LogInternalExceptions)
@@ -141,23 +150,151 @@ XAAssemblyResolver MakeResolver (bool useMarshalMethods)
return res;
}
- void Run (XAAssemblyResolver res, bool useMarshalMethods)
+ void Run ()
{
- PackageNamingPolicy pnp;
- JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64;
+ if (Debug) {
+ if (LinkingEnabled) {
+ RunDebugWithLinking ();
+ } else {
+ RunDebugNoLinking ();
+ }
+ return;
+ }
- Dictionary>? abiSpecificAssembliesByPath = null;
- if (useMarshalMethods) {
- abiSpecificAssembliesByPath = new Dictionary> (StringComparer.Ordinal);
+ bool useMarshalMethods = !Debug && EnableMarshalMethods;
+ if (LinkingEnabled) {
+ RunReleaseWithLinking (useMarshalMethods);
+ } else {
+ RunReleaseNoLinking (useMarshalMethods);
}
+ }
+
+ // * We have one set of assemblies in general, some RID-specific ones (e.g. `System.Private.CoreLib`, potentially others).
+ // * Typemaps don't use MVIDs or metadata tokens, so we can process one each of the RID-specific ones together with the others
+ // * Marshal methods are never used
+ void RunDebugNoLinking ()
+ {
+ LogRunMode ("Debug, no linking");
+
+ XAAssemblyResolver resolver = MakeResolver (useMarshalMethods: false);
+ var assemblies = CollectInterestingAssemblies (new RidAgnosticInputAssemblySet (), (AndroidTargetArch arch) => resolver);
+ var state = new RunState {
+ UseMarshalMethods = false,
+ AssemblySet = assemblies,
+ JavaTypeAssemblies = assemblies.JavaTypeAssemblies,
+ UserAssemblies = assemblies.UserAssemblies,
+ GenerateRidAgnosticParts = true,
+ Resolver = resolver,
+ TargetArch = AndroidTargetArch.None,
+ };
+ DoRun (state, out ApplicationConfigTaskState appConfState);
+ RegisterApplicationConfigState (appConfState);
+ }
- // Put every assembly we'll need in the resolver
- bool hasExportReference = false;
- bool haveMonoAndroid = false;
- var allTypemapAssemblies = new Dictionary (StringComparer.OrdinalIgnoreCase);
- var userAssemblies = new Dictionary (StringComparer.OrdinalIgnoreCase);
+ // * We have as many sets of assemblies as there are RIDs, all assemblies are RID-specific
+ // * Typemaps don't use MVIDs or metadata tokens, so we can process a single set of RID-specific assemblies
+ // * Marshal methods are never used
+ void RunDebugWithLinking ()
+ {
+ LogRunMode ("Debug, with linking");
+
+ XAAssemblyResolver resolver = MakeResolver (useMarshalMethods: false);
+ var assemblies = CollectInterestingAssemblies (new RidSpecificInputAssemblySet (), (AndroidTargetArch arch) => resolver);
+
+ AndroidTargetArch firstArch = assemblies.JavaTypeAssemblies.Keys.First ();
+ var state = new RunState {
+ UseMarshalMethods = false,
+ AssemblySet = assemblies,
+ JavaTypeAssemblies = assemblies.JavaTypeAssemblies[firstArch].Values,
+ UserAssemblies = assemblies.UserAssemblies[firstArch].Values,
+ GenerateRidAgnosticParts = true,
+ Resolver = resolver,
+ TargetArch = AndroidTargetArch.None,
+ };
+ DoRun (state, out ApplicationConfigTaskState appConfState);
+ RegisterApplicationConfigState (appConfState);
+ }
+
+ // * We have one set of assemblies in general, some RID-specific ones (e.g. `System.Private.CoreLib`, potentially others).
+ // * Typemaps use MVIDs and metadata tokens, so we need to process all assemblies as per-RID ones (different MVIDs in the
+ // actually RID-specific assemblies may affect sorting of the RID-agnostic ones)
+ // * Marshal methods may be used
+ void RunReleaseNoLinking (bool useMarshalMethods)
+ {
+ LogRunMode ("Release, no linking");
- foreach (var assembly in ResolvedAssemblies) {
+ Dictionary resolvers = MakeResolvers (useMarshalMethods);
+
+ // All the RID-agnostic asseemblies will use resolvers of this architecture. This is because the RidAwareInputAssemblySet does not store
+ // such assemblies separately, but it copies them to **all** the target RIDs. This, in turn, is done because of typemaps and marshal methods
+ // which process data in a way that requires proper sorting of assemblies per MVID and it requires valid type and method token IDs.
+ AndroidTargetArch firstArch = resolvers.First ().Key;
+ resolvers.Add (AndroidTargetArch.None, resolvers[firstArch]);
+ var assemblies = CollectInterestingAssemblies (new RidAwareInputAssemblySet (resolvers.Keys), (AndroidTargetArch arch) => resolvers[arch]);
+ RunReleaseCommon (useMarshalMethods, assemblies, resolvers);
+ }
+
+ // * We have as many sets of assemblies as there are RIDs, all assemblies are RID-specific
+ // * Typemaps use MVIDs and metadata tokens, so we need per-RID set processing
+ // * Marshal methods may be used
+ void RunReleaseWithLinking (bool useMarshalMethods)
+ {
+ LogRunMode ("Release, with linking");
+
+ Dictionary resolvers = MakeResolvers (useMarshalMethods);
+ var assemblies = CollectInterestingAssemblies (new RidSpecificInputAssemblySet (), (AndroidTargetArch arch) => resolvers[arch]);
+ RunReleaseCommon (useMarshalMethods, assemblies, resolvers);
+ }
+
+ void RunReleaseCommon (bool useMarshalMethods, RidSensitiveInputAssemblySet assemblies, Dictionary resolvers)
+ {
+ bool first = true;
+
+ foreach (var kvp in resolvers) {
+ var state = new RunState {
+ UseMarshalMethods = useMarshalMethods,
+ AssemblySet = assemblies,
+ JavaTypeAssemblies = assemblies.JavaTypeAssemblies[kvp.Key].Values,
+ UserAssemblies = assemblies.UserAssemblies[kvp.Key].Values,
+ GenerateRidAgnosticParts = first,
+ Resolver = kvp.Value,
+ TargetArch = kvp.Key,
+ };
+
+ DoRun (state, out ApplicationConfigTaskState appConfState);
+ if (first) {
+ RegisterApplicationConfigState (appConfState);
+ first = false;
+ }
+ }
+ }
+
+ Dictionary MakeResolvers (bool useMarshalMethods)
+ {
+ var resolvers = new Dictionary ();
+ foreach (string abi in SupportedAbis) {
+ // Each ABI gets its own resolver in this mode...
+ XAAssemblyResolver resolver = MakeResolver (useMarshalMethods);
+ resolvers.Add (MonoAndroidHelper.AbiToTargetArch (abi), resolver);
+ }
+
+ return resolvers;
+ }
+
+ void RegisterApplicationConfigState (ApplicationConfigTaskState appConfState)
+ {
+ BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (ApplicationConfigTaskState.RegisterTaskObjectKey), appConfState, RegisteredTaskObjectLifetime.Build);
+ }
+
+ void LogRunMode (string mode)
+ {
+ Log.LogDebugMessage ($"GenerateJavaStubs mode: {mode}");
+ }
+
+ T CollectInterestingAssemblies (T assemblies, Func getResolver) where T: InputAssemblySet
+ {
+ AndroidTargetArch targetArch;
+ foreach (ITaskItem assembly in ResolvedAssemblies) {
bool value;
if (bool.TryParse (assembly.GetMetadata (AndroidSkipJavaStubGeneration), out value) && value) {
Log.LogDebugMessage ($"Skipping Java Stub Generation for {assembly.ItemSpec}");
@@ -166,173 +303,177 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods)
bool addAssembly = false;
string fileName = Path.GetFileName (assembly.ItemSpec);
- if (!hasExportReference && String.Compare ("Mono.Android.Export.dll", fileName, StringComparison.OrdinalIgnoreCase) == 0) {
- hasExportReference = true;
+ if (String.Compare ("Mono.Android.Export.dll", fileName, StringComparison.OrdinalIgnoreCase) == 0) {
addAssembly = true;
- } else if (!haveMonoAndroid && String.Compare ("Mono.Android.dll", fileName, StringComparison.OrdinalIgnoreCase) == 0) {
- haveMonoAndroid = true;
+ } else if (String.Compare ("Mono.Android.dll", fileName, StringComparison.OrdinalIgnoreCase) == 0) {
addAssembly = true;
} else if (MonoAndroidHelper.FrameworkAssembliesToTreatAsUserAssemblies.Contains (fileName)) {
if (!bool.TryParse (assembly.GetMetadata (AndroidSkipJavaStubGeneration), out value) || !value) {
string name = Path.GetFileNameWithoutExtension (fileName);
- if (!userAssemblies.ContainsKey (name))
- userAssemblies.Add (name, assembly.ItemSpec);
+ assemblies.AddUserAssembly (assembly);
addAssembly = true;
}
+ } else if (MonoAndroidHelper.IsSatelliteAssembly (assembly)) {
+ continue;
}
if (addAssembly) {
- MaybeAddAbiSpecifcAssembly (assembly, fileName);
- if (!allTypemapAssemblies.ContainsKey (assembly.ItemSpec)) {
- allTypemapAssemblies.Add (assembly.ItemSpec, assembly);
- }
+ assemblies.AddJavaTypeAssembly (assembly);
}
- res.Load (MonoAndroidHelper.GetTargetArch (assembly), assembly.ItemSpec);
+ targetArch = MonoAndroidHelper.GetTargetArch (assembly);
+
+ // We don't check whether we have a resolver for `targetArch` on purpose, if it throws then it means we have a bug which
+ // should be fixed since there shouldn't be any assemblies passed to this task that belong in ABIs other than those
+ // specified in `SupportedAbis` (and, perhaps, a RID-agnostic one)
+ try {
+ getResolver (targetArch).Load (targetArch, assembly.ItemSpec);
+ } catch (Exception ex) {
+ throw new InvalidOperationException ($"Internal error: failed to get resolver for assembly {assembly.ItemSpec}, target architecture '{targetArch}'", ex);
+ }
}
// However we only want to look for JLO types in user code for Java stub code generation
- foreach (var asm in ResolvedUserAssemblies) {
- if (bool.TryParse (asm.GetMetadata (AndroidSkipJavaStubGeneration), out bool value) && value) {
- Log.LogDebugMessage ($"Skipping Java Stub Generation for {asm.ItemSpec}");
+ foreach (ITaskItem assembly in ResolvedUserAssemblies) {
+ if (bool.TryParse (assembly.GetMetadata (AndroidSkipJavaStubGeneration), out bool value) && value) {
+ Log.LogDebugMessage ($"Skipping Java Stub Generation for {assembly.ItemSpec}");
continue;
}
- res.Load (MonoAndroidHelper.GetTargetArch (asm), asm.ItemSpec);
- MaybeAddAbiSpecifcAssembly (asm, Path.GetFileName (asm.ItemSpec));
- if (!allTypemapAssemblies.ContainsKey (asm.ItemSpec)) {
- allTypemapAssemblies.Add (asm.ItemSpec, asm);
- }
- string name = Path.GetFileNameWithoutExtension (asm.ItemSpec);
- if (!userAssemblies.ContainsKey (name))
- userAssemblies.Add (name, asm.ItemSpec);
+ targetArch = MonoAndroidHelper.GetTargetArch (assembly);
+ getResolver (targetArch).Load (targetArch, assembly.ItemSpec);
+
+ assemblies.AddJavaTypeAssembly (assembly);
+ assemblies.AddUserAssembly (assembly);
+ }
+
+ return assemblies;
+ }
+
+ void DoRun (RunState state, out ApplicationConfigTaskState? appConfState)
+ {
+ Log.LogDebugMessage ($"DoRun for arch {state.TargetArch}");
+ Log.LogDebugMessage ("Java type assemblies:");
+ foreach (ITaskItem assembly in state.JavaTypeAssemblies) {
+ Log.LogDebugMessage ($" {assembly.ItemSpec}");
+ }
+
+ Log.LogDebugMessage ("User assemblies:");
+ foreach (ITaskItem assembly in state.UserAssemblies) {
+ Log.LogDebugMessage ($" {assembly.ItemSpec}");
}
+ PackageNamingPolicy pnp;
+ JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64;
// Step 1 - Find all the JLO types
var cache = new TypeDefinitionCache ();
var scanner = new XAJavaTypeScanner (Log, cache) {
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
};
- List allJavaTypes = scanner.GetJavaTypes (allTypemapAssemblies.Values, res);
- var javaTypes = new List ();
+ ICollection allJavaTypes = scanner.GetJavaTypes (state.JavaTypeAssemblies, state.Resolver);
+ var javaTypes = new List ();
- foreach (JavaType jt in allJavaTypes) {
+ foreach (TypeDefinition javaType in allJavaTypes) {
// Whem marshal methods are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during
// application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
// build and stored in a jar file.
- if ((!useMarshalMethods && !userAssemblies.ContainsKey (jt.Type.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (jt.Type, cache)) {
+ if ((!state.UseMarshalMethods && !state.AssemblySet.IsUserAssembly (javaType.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (javaType, cache)) {
continue;
}
- javaTypes.Add (jt);
+ javaTypes.Add (javaType);
}
- MarshalMethodsClassifier classifier = null;
- if (useMarshalMethods) {
- classifier = new MarshalMethodsClassifier (cache, res, Log);
+ MarshalMethodsClassifier? classifier = null;
+ if (state.UseMarshalMethods) {
+ classifier = new MarshalMethodsClassifier (cache, state.Resolver, Log);
}
- // Step 2 - Generate Java stub code
- var success = CreateJavaSources (javaTypes, cache, classifier, useMarshalMethods);
- if (!success)
- return;
+ // TODO: JCWs don't need to be generated for every RID, but we do need the classifier for the marshal methods
+ // rewriter and generator. Add a mode to only classify marshal methods without generating the files.
+ // For now, always generate the JCWs if marshal methods are enabled
+ if (state.UseMarshalMethods || state.GenerateRidAgnosticParts) {
+ // Step 2 - Generate Java stub code
+ bool success = CreateJavaSources (javaTypes, cache, classifier, state.UseMarshalMethods);
+ if (!success) {
+ appConfState = null;
+ return; // TODO: throw? Return `false`?
+ }
+ }
- if (useMarshalMethods) {
+ if (state.UseMarshalMethods) {
// We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed
// in order to properly generate wrapper methods in the marshal methods assembly rewriter.
// We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for.
var environmentParser = new EnvironmentFilesParser ();
-
- Dictionary assemblyPaths = AddMethodsFromAbiSpecificAssemblies (classifier, res, abiSpecificAssembliesByPath);
-
- var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, assemblyPaths, Log);
- rewriter.Rewrite (res, environmentParser.AreBrokenExceptionTransitionsEnabled (Environments));
+ var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, Log);
+ rewriter.Rewrite (state.Resolver, environmentParser.AreBrokenExceptionTransitionsEnabled (Environments));
}
// Step 3 - Generate type maps
// Type mappings need to use all the assemblies, always.
- WriteTypeMappings (allJavaTypes, cache);
+ WriteTypeMappings (state.TargetArch, allJavaTypes, cache, out appConfState);
- // We need to save a map of .NET type -> ACW type for resource file fixups
- var managed = new Dictionary (javaTypes.Count, StringComparer.Ordinal);
- var java = new Dictionary (javaTypes.Count, StringComparer.Ordinal);
+ if (state.GenerateRidAgnosticParts) {
+ WriteAcwMaps (javaTypes, cache);
- var managedConflicts = new Dictionary> (0, StringComparer.Ordinal);
- var javaConflicts = new Dictionary> (0, StringComparer.Ordinal);
+ // Step 4 - Merge [Activity] and friends into AndroidManifest.xml
+ UpdateAndroidManifest (state, cache, allJavaTypes);
+ CreateAdditionalJavaSources (javaTypes, cache, classifier);
+ }
- using (var acw_map = MemoryStreamPool.Shared.CreateStreamWriter ()) {
- foreach (JavaType jt in javaTypes) {
- TypeDefinition type = jt.Type;
- string managedKey = type.FullName.Replace ('/', '.');
- string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
+ if (state.UseMarshalMethods) {
+ classifier.AddSpecialCaseMethods ();
- acw_map.Write (type.GetPartialAssemblyQualifiedName (cache));
- acw_map.Write (';');
- acw_map.Write (javaKey);
- acw_map.WriteLine ();
-
- TypeDefinition conflict;
- bool hasConflict = false;
- if (managed.TryGetValue (managedKey, out conflict)) {
- if (!conflict.Module.Name.Equals (type.Module.Name)) {
- if (!managedConflicts.TryGetValue (managedKey, out var list))
- managedConflicts.Add (managedKey, list = new List { conflict.GetPartialAssemblyName (cache) });
- list.Add (type.GetPartialAssemblyName (cache));
- }
- hasConflict = true;
- }
- if (java.TryGetValue (javaKey, out conflict)) {
- if (!conflict.Module.Name.Equals (type.Module.Name)) {
- if (!javaConflicts.TryGetValue (javaKey, out var list))
- javaConflicts.Add (javaKey, list = new List { conflict.GetAssemblyQualifiedName (cache) });
- list.Add (type.GetAssemblyQualifiedName (cache));
- success = false;
- }
- hasConflict = true;
- }
- if (!hasConflict) {
- managed.Add (managedKey, type);
- java.Add (javaKey, type);
-
- acw_map.Write (managedKey);
- acw_map.Write (';');
- acw_map.Write (javaKey);
- acw_map.WriteLine ();
-
- acw_map.Write (JavaNativeTypeManager.ToCompatJniName (type, cache).Replace ('/', '.'));
- acw_map.Write (';');
- acw_map.Write (javaKey);
- acw_map.WriteLine ();
- }
+ Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}");
+
+ if (classifier.RejectedMethodCount > 0) {
+ Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}");
}
- acw_map.Flush ();
- Files.CopyIfStreamChanged (acw_map.BaseStream, AcwMapFile);
+ if (classifier.WrappedMethodCount > 0) {
+ // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
+ Log.LogDebugMessage ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}");
+ }
}
+ }
- foreach (var kvp in managedConflicts) {
- Log.LogCodedWarning ("XA4214", Properties.Resources.XA4214, kvp.Key, string.Join (", ", kvp.Value));
- Log.LogCodedWarning ("XA4214", Properties.Resources.XA4214_Result, kvp.Key, kvp.Value [0]);
- }
+ void CreateAdditionalJavaSources (ICollection javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier? classifier)
+ {
+ StringWriter regCallsWriter = new StringWriter ();
+ regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
+ foreach (TypeDefinition type in javaTypes) {
+ if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) {
+ if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) {
+ continue;
+ }
- foreach (var kvp in javaConflicts) {
- Log.LogCodedError ("XA4215", Properties.Resources.XA4215, kvp.Key);
- foreach (var typeName in kvp.Value)
- Log.LogCodedError ("XA4215", Properties.Resources.XA4215_Details, kvp.Key, typeName);
+ string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
+ regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
+ type.GetAssemblyQualifiedName (cache), javaKey);
+ }
}
+ regCallsWriter.Close ();
+
+ var real_app_dir = Path.Combine (OutputDirectory, "src", "mono", "android", "app");
+ string applicationTemplateFile = "ApplicationRegistration.java";
+ SaveResource (applicationTemplateFile, applicationTemplateFile, real_app_dir,
+ template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()));
+ }
- // Step 3 - Merge [Activity] and friends into AndroidManifest.xml
+ void UpdateAndroidManifest (RunState state, TypeDefinitionCache cache, ICollection allJavaTypes)
+ {
var manifest = new ManifestDocument (ManifestTemplate) {
- PackageName = PackageName,
- VersionName = VersionName,
- ApplicationLabel = ApplicationLabel ?? PackageName,
- Placeholders = ManifestPlaceholders,
- Resolver = res,
- SdkDir = AndroidSdkDir,
- TargetSdkVersion = AndroidSdkPlatform,
- MinSdkVersion = MonoAndroidHelper.ConvertSupportedOSPlatformVersionToApiLevel (SupportedOSPlatformVersion).ToString (),
- Debug = Debug,
- MultiDex = MultiDex,
- NeedsInternet = NeedsInternet,
+ PackageName = PackageName,
+ VersionName = VersionName,
+ ApplicationLabel = ApplicationLabel ?? PackageName,
+ Placeholders = ManifestPlaceholders,
+ Resolver = state.Resolver,
+ SdkDir = AndroidSdkDir,
+ TargetSdkVersion = AndroidSdkPlatform,
+ MinSdkVersion = MonoAndroidHelper.ConvertSupportedOSPlatformVersionToApiLevel (SupportedOSPlatformVersion).ToString (),
+ Debug = Debug,
+ MultiDex = MultiDex,
+ NeedsInternet = NeedsInternet,
InstantRunEnabled = InstantRunEnabled
};
// Only set manifest.VersionCode if there is no existing value in AndroidManifest.xml.
@@ -341,7 +482,10 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods)
} else if (!string.IsNullOrEmpty (VersionCode)) {
manifest.VersionCode = VersionCode;
}
- manifest.Assemblies.AddRange (userAssemblies.Values);
+
+ foreach (ITaskItem assembly in state.UserAssemblies) {
+ manifest.Assemblies.Add (Path.GetFileName (assembly.ItemSpec));
+ }
if (!String.IsNullOrWhiteSpace (CheckedBuild)) {
// We don't validate CheckedBuild value here, this will be done in BuildApk. We just know that if it's
@@ -356,70 +500,12 @@ void Run (XAAssemblyResolver res, bool useMarshalMethods)
if (manifest.SaveIfChanged (Log, MergedAndroidManifestOutput)) {
Log.LogDebugMessage ($"Saving: {MergedAndroidManifestOutput}");
}
+ }
- // Create additional runtime provider java sources.
- string providerTemplateFile = "MonoRuntimeProvider.Bundled.java";
- string providerTemplate = GetResource (providerTemplateFile);
-
- foreach (var provider in additionalProviders) {
- var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider);
- var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java");
- Files.CopyIfStringChanged (contents, real_provider);
- }
-
- // Create additional application java sources.
- StringWriter regCallsWriter = new StringWriter ();
- regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
- foreach (JavaType jt in javaTypes) {
- TypeDefinition type = jt.Type;
- if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) {
- if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) {
- continue;
- }
-
- string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
- regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
- type.GetAssemblyQualifiedName (cache), javaKey);
- }
- }
- regCallsWriter.Close ();
-
- var real_app_dir = Path.Combine (OutputDirectory, "src", "mono", "android", "app");
- string applicationTemplateFile = "ApplicationRegistration.java";
- SaveResource (applicationTemplateFile, applicationTemplateFile, real_app_dir,
- template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()));
-
- if (useMarshalMethods) {
- classifier.AddSpecialCaseMethods ();
-
- Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}");
-
- if (classifier.RejectedMethodCount > 0) {
- Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}");
- }
-
- if (classifier.WrappedMethodCount > 0) {
- // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
- Log.LogDebugMessage ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}");
- }
- }
-
- void MaybeAddAbiSpecifcAssembly (ITaskItem assembly, string fileName)
- {
- if (abiSpecificAssembliesByPath == null) {
- return;
- }
-
- string? abi = assembly.GetMetadata ("Abi");
- if (!String.IsNullOrEmpty (abi)) {
- if (!abiSpecificAssembliesByPath.TryGetValue (fileName, out List? items)) {
- items = new List ();
- abiSpecificAssembliesByPath.Add (fileName, items);
- }
-
- items.Add (assembly);
- }
- }
+ void WriteAcwMaps (List javaTypes, TypeDefinitionCache cache)
+ {
+ var writer = new AcwMapWriter (Log, AcwMapFile);
+ writer.Write (javaTypes, cache);
}
AssemblyDefinition LoadAssembly (string path, XAAssemblyResolver? resolver = null)
@@ -452,7 +538,7 @@ AssemblyDefinition LoadAssembly (string path, XAAssemblyResolver? resolver = nul
}
}
- bool CreateJavaSources (IEnumerable newJavaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods)
+ bool CreateJavaSources (IEnumerable newJavaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods)
{
if (useMarshalMethods && classifier == null) {
throw new ArgumentNullException (nameof (classifier));
@@ -464,8 +550,7 @@ bool CreateJavaSources (IEnumerable newJavaTypes, TypeDefinitionCache
bool generateOnCreateOverrides = int.Parse (AndroidSdkPlatform) <= 10;
bool ok = true;
- foreach (JavaType jt in newJavaTypes) {
- TypeDefinition t = jt.Type; // JCW generator doesn't care about ABI-specific types or token ids
+ foreach (TypeDefinition t in newJavaTypes) {
if (t.IsInterface) {
// Interfaces are in typemap but they shouldn't have JCW generated for them
continue;
@@ -568,14 +653,14 @@ void SaveResource (string resource, string filename, string destDir, Func types, TypeDefinitionCache cache)
+ void WriteTypeMappings (AndroidTargetArch targetArch, ICollection types, TypeDefinitionCache cache, out ApplicationConfigTaskState appConfState)
{
- var tmg = new TypeMapGenerator ((string message) => Log.LogDebugMessage (message), SupportedAbis);
- if (!tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, types, cache, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState)) {
+ Log.LogDebugMessage ($"Generating typemaps for arch {targetArch}, {types.Count} types");
+ var tmg = new TypeMapGenerator (targetArch, Log, SupportedAbis);
+ if (!tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, types, cache, TypemapOutputDirectory, GenerateNativeAssembly, out appConfState)) {
throw new XamarinAndroidException (4308, Properties.Resources.XA4308);
}
GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray ();
- BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (ApplicationConfigTaskState.RegisterTaskObjectKey), appConfState, RegisteredTaskObjectLifetime.Build);
}
///
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs
index b89ce87d26d..b8eec268ddb 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs
@@ -86,7 +86,7 @@ void Generate (JniRemappingAssemblyGenerator jniRemappingComposer, int typeRepla
string llFilePath = $"{baseAsmFilePath}.ll";
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
- jniRemappingComposer.Generate (module, GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath);
+ jniRemappingComposer.Generate (module, MonoAndroidHelper.AbiToTargetArch (abi), sw, llFilePath);
sw.Flush ();
Files.CopyIfStreamChanged (sw.BaseStream, llFilePath);
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
index 670fcfe566e..4ada1c1d5a5 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
@@ -38,6 +38,9 @@ public class GeneratePackageManagerJava : AndroidTask
public bool UseAssemblyStore { get; set; }
+ [Required]
+ public bool UseAssemblySharedLibraries { get; set; }
+
[Required]
public string OutputDirectory { get; set; }
@@ -145,26 +148,6 @@ public override bool RunTask ()
return !Log.HasLoggedErrors;
}
- static internal AndroidTargetArch GetAndroidTargetArchForAbi (string abi)
- {
- switch (abi.Trim ()) {
- case "armeabi-v7a":
- return AndroidTargetArch.Arm;
-
- case "arm64-v8a":
- return AndroidTargetArch.Arm64;
-
- case "x86":
- return AndroidTargetArch.X86;
-
- case "x86_64":
- return AndroidTargetArch.X86_64;
-
- default:
- throw new InvalidOperationException ($"Unknown ABI {abi}");
- }
- }
-
static readonly string[] defaultLogLevel = {"MONO_LOG_LEVEL", "info"};
static readonly string[] defaultMonoDebug = {"MONO_DEBUG", "gen-compact-seq-points"};
static readonly string[] defaultHttpMessageHandler = {"XA_HTTP_CLIENT_HANDLER_TYPE", "System.Net.Http.HttpClientHandler, System.Net.Http"};
@@ -246,7 +229,7 @@ void AddEnvironment ()
Encoding assemblyNameEncoding = Encoding.UTF8;
Action updateNameWidth = (ITaskItem assembly) => {
- if (UseAssemblyStore) {
+ if (UseAssemblyStore) { // TODO: modify for assemblies embedded in DSOs
return;
}
@@ -269,7 +252,7 @@ void AddEnvironment ()
uniqueAssemblyNames.Add (assemblyName);
}
- if (!UseAssemblyStore) {
+ if (!UseAssemblyStore) { // TODO: modify for assemblies embedded in DSOs
assemblyCount++;
return;
}
@@ -316,7 +299,7 @@ void AddEnvironment ()
GetRequiredTokens (assembly.ItemSpec, out android_runtime_jnienv_class_token, out jnienv_initialize_method_token, out jnienv_registerjninatives_method_token);
}
- if (!UseAssemblyStore) {
+ if (!UseAssemblyStore) { // TODO: modify for assemblies embedded in DSOs
int abiNameLength = 0;
foreach (string abi in SupportedAbis) {
if (abi.Length <= abiNameLength) {
@@ -384,15 +367,11 @@ void AddEnvironment ()
InstantRunEnabled = InstantRunEnabled,
JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false,
HaveRuntimeConfigBlob = haveRuntimeConfigBlob,
+ HaveStandaloneAssemblyDSOs = UseAssemblySharedLibraries,
NumberOfAssembliesInApk = assemblyCount,
BundledAssemblyNameWidth = assemblyNameWidth,
- NumberOfAssemblyStoresInApks = 2, // Until feature APKs are a thing, we're going to have just two stores in each app - one for arch-agnostic
- // and up to 4 other for arch-specific assemblies. Only **one** arch-specific store is ever loaded on the app
- // runtime, thus the number 2 here. All architecture specific stores contain assemblies with the same names
- // and in the same order.
MonoComponents = (MonoComponent)monoComponents,
NativeLibraries = uniqueNativeLibraries,
- HaveAssemblyStore = UseAssemblyStore,
AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token,
JNIEnvInitializeToken = jnienv_initialize_method_token,
JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token,
@@ -423,7 +402,7 @@ void AddEnvironment ()
string marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}");
string environmentLlFilePath = $"{environmentBaseAsmFilePath}.ll";
string marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll";
- AndroidTargetArch targetArch = GetAndroidTargetArchForAbi (abi);
+ AndroidTargetArch targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
try {
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs
index 5f4b09ececa..4fc366e6325 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs
@@ -136,7 +136,7 @@ IEnumerable GetLinkerConfigs ()
string stripSymbolsArg = DebugBuild ? String.Empty : " -s";
- string ld = Path.Combine (AndroidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (AndroidBinUtilsDirectory, "ld"));
+ string ld = NativeCompilationHelper.GetLinkerPath (AndroidBinUtilsDirectory);
var targetLinkerArgs = new List ();
foreach (var kvp in abis) {
string abi = kvp.Key;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
index d826e94110c..31b10540bc4 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
@@ -13,9 +13,9 @@ 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 const string AssemblyDSOBase = "assembly_dso";
public override string TaskPrefix => "PAI";
@@ -50,12 +50,12 @@ public override bool RunTask ()
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 if (String.Compare ("assembly_dsos", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
+ baseName = AssemblyDSOBase;
} else {
Log.LogError ($"Unknown mode: {Mode}");
return false;
@@ -63,7 +63,7 @@ public override bool RunTask ()
TaskItem item;
foreach (string abi in BuildTargetAbis) {
- item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{abi}.ll"));
+ item = new TaskItem (Path.Combine (NativeSourcesDir, MonoAndroidHelper.MakeNativeAssemblyFileName (baseName, abi)));
item.SetMetadata ("abi", abi);
sources.Add (item);
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAssemblyStandaloneDSOAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAssemblyStandaloneDSOAbiItems.cs
new file mode 100644
index 00000000000..9200f96b38b
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAssemblyStandaloneDSOAbiItems.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Xamarin.Android.Tasks;
+
+public class PrepareAssemblyStandaloneDSOAbiItems : AndroidTask
+{
+ public override string TaskPrefix => "PASDAI";
+
+ [Required]
+ public ITaskItem[] Assemblies { get; set; }
+
+ [Required]
+ public ITaskItem[] FastPathAssemblies { get; set; }
+
+ [Required]
+ public string SharedLibraryOutputDir { get; set; }
+
+ [Output]
+ public ITaskItem[] SharedLibraries { get; set; }
+
+ public override bool RunTask ()
+ {
+ SharedLibraries = PrepareItems ().ToArray ();
+ return !Log.HasLoggedErrors;
+ }
+
+ List PrepareItems ()
+ {
+ var sharedLibraries = new List ();
+ var seenAbis = new HashSet (StringComparer.Ordinal);
+ var satelliteAssemblies = new List ();
+ ushort dsoIndexCounter = 0;
+ var assemblyIndexes = new Dictionary (StringComparer.OrdinalIgnoreCase);
+ HashSet fastPathItems = MonoAndroidHelper.MakeHashSet (FastPathAssemblies);
+
+ foreach (ITaskItem assembly in Assemblies) {
+ if (fastPathItems.Contains (Path.GetFileName (assembly.ItemSpec))) {
+ continue;
+ }
+
+ if (MonoAndroidHelper.IsSatelliteAssembly (assembly)) {
+ satelliteAssemblies.Add (assembly);
+ continue;
+ }
+
+ string? abi = assembly.GetMetadata ("Abi");
+ if (String.IsNullOrEmpty (abi)) {
+ throw new InvalidOperationException ($"Assembly item for '{assembly.ItemSpec}' is missing ABI metadata");
+ }
+ seenAbis.Add (abi);
+
+ sharedLibraries.Add (MakeTaskItem (assembly, abi, MakeBaseName (assembly)));
+ }
+
+ if (satelliteAssemblies.Count > 0) {
+ foreach (ITaskItem assembly in satelliteAssemblies) {
+ string culture = GetCultureName (assembly);
+ string baseName = $"{culture}-{MakeBaseName (assembly)}";
+
+ foreach (string abi in seenAbis) {
+ var newItem = MakeTaskItem (assembly, abi, baseName);
+ newItem.SetMetadata (DSOMetadata.SatelliteAssemblyCulture, culture);
+ sharedLibraries.Add (newItem);
+ }
+ }
+ }
+
+ return sharedLibraries;
+
+ ITaskItem MakeTaskItem (ITaskItem assembly, string abi, string baseName)
+ {
+ if (!assemblyIndexes.TryGetValue (baseName, out ushort index)) {
+ index = dsoIndexCounter++;
+ assemblyIndexes.Add (baseName, index);
+ }
+
+ // the 'XA' infix is to make it harder to produce library names that clash with 3rd party libraries
+ // If the infix changes, the `assembly_dso_prefix` constant in src/monodroid/jni/embedded-assemblies.hh must
+ // be changed as well. Index must be encoded as a hexadecimal number, without the 0x prefix and using capital
+ // letters. If this changes then code in src/monodroid/jni/embedded-assemblies-zip.cc must be adjusted accordingly.
+ string dsoName = $"libXA{baseName}.{index:X04}.so";
+
+ var item = new TaskItem (Path.Combine (SharedLibraryOutputDir, abi, dsoName));
+ item.SetMetadata (DSOMetadata.Abi, abi);
+ item.SetMetadata (DSOMetadata.InputAssemblyPath, assembly.ItemSpec);
+ item.SetMetadata (DSOMetadata.SourceFileBaseName, baseName);
+ item.SetMetadata (DSOMetadata.AssemblyLoadInfoIndex, MonoAndroidHelper.CultureInvariantToString (index));
+
+ string skipCompression = assembly.GetMetadata ("AndroidSkipCompression");
+ if (!String.IsNullOrEmpty (skipCompression)) {
+ item.SetMetadata (DSOMetadata.AndroidSkipCompression, skipCompression);
+ }
+
+ return item;
+ }
+
+ string MakeBaseName (ITaskItem assembly) => Path.GetFileNameWithoutExtension (assembly.ItemSpec);
+
+ string GetCultureName (ITaskItem assembly)
+ {
+ string? culture = assembly.GetMetadata ("Culture");
+ if (String.IsNullOrEmpty (culture)) {
+ throw new InvalidOperationException ($"Satellite assembly '{assembly.ItemSpec}' has no culture metadata item");
+ }
+
+ string path = Path.Combine (culture, Path.GetFileName (assembly.ItemSpec));
+ if (!assembly.ItemSpec.EndsWith (path, StringComparison.OrdinalIgnoreCase)) {
+ throw new InvalidOperationException ($"Invalid metadata in satellite assembly '{assembly.ItemSpec}', culture metadata ('{culture}') doesn't match file path");
+ }
+
+ return culture;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AcwMapWriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AcwMapWriter.cs
new file mode 100644
index 00000000000..315387ecbbf
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AcwMapWriter.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+
+using Java.Interop.Tools.Cecil;
+using Java.Interop.Tools.TypeNameMappings;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Mono.Cecil;
+
+namespace Xamarin.Android.Tasks;
+
+class AcwMapWriter
+{
+ readonly TaskLoggingHelper Log;
+ readonly string AcwMapFile;
+
+ public AcwMapWriter (TaskLoggingHelper log, string acwMapFile)
+ {
+ Log = log;
+ AcwMapFile = acwMapFile;
+ }
+
+ public void Write (ICollection javaTypes, TypeDefinitionCache cache)
+ {
+ // We need to save a map of .NET type -> ACW type for resource file fixups
+ var managed = new Dictionary (javaTypes.Count, StringComparer.Ordinal);
+ var java = new Dictionary (javaTypes.Count, StringComparer.Ordinal);
+
+ var managedConflicts = new Dictionary> (0, StringComparer.Ordinal);
+ var javaConflicts = new Dictionary> (0, StringComparer.Ordinal);
+
+ using (var acw_map = MemoryStreamPool.Shared.CreateStreamWriter ()) {
+ foreach (TypeDefinition type in javaTypes) {
+ string managedKey = type.FullName.Replace ('/', '.');
+ string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
+
+ acw_map.Write (type.GetPartialAssemblyQualifiedName (cache));
+ acw_map.Write (';');
+ acw_map.Write (javaKey);
+ acw_map.WriteLine ();
+
+ TypeDefinition conflict;
+ bool hasConflict = false;
+ if (managed.TryGetValue (managedKey, out conflict)) {
+ if (!conflict.Module.Name.Equals (type.Module.Name)) {
+ if (!managedConflicts.TryGetValue (managedKey, out var list))
+ managedConflicts.Add (managedKey, list = new List { conflict.GetPartialAssemblyName (cache) });
+ list.Add (type.GetPartialAssemblyName (cache));
+ }
+ hasConflict = true;
+ }
+ if (java.TryGetValue (javaKey, out conflict)) {
+ if (!conflict.Module.Name.Equals (type.Module.Name)) {
+ if (!javaConflicts.TryGetValue (javaKey, out var list))
+ javaConflicts.Add (javaKey, list = new List { conflict.GetAssemblyQualifiedName (cache) });
+ list.Add (type.GetAssemblyQualifiedName (cache));
+ }
+ hasConflict = true;
+ }
+ if (!hasConflict) {
+ managed.Add (managedKey, type);
+ java.Add (javaKey, type);
+
+ acw_map.Write (managedKey);
+ acw_map.Write (';');
+ acw_map.Write (javaKey);
+ acw_map.WriteLine ();
+
+ acw_map.Write (JavaNativeTypeManager.ToCompatJniName (type, cache).Replace ('/', '.'));
+ acw_map.Write (';');
+ acw_map.Write (javaKey);
+ acw_map.WriteLine ();
+ }
+ }
+
+ acw_map.Flush ();
+ Files.CopyIfStreamChanged (acw_map.BaseStream, AcwMapFile);
+ }
+
+ foreach (var kvp in managedConflicts) {
+ Log.LogCodedWarning ("XA4214", Properties.Resources.XA4214, kvp.Key, string.Join (", ", kvp.Value));
+ Log.LogCodedWarning ("XA4214", Properties.Resources.XA4214_Result, kvp.Key, kvp.Value [0]);
+ }
+
+ foreach (var kvp in javaConflicts) {
+ Log.LogCodedError ("XA4215", Properties.Resources.XA4215, kvp.Key);
+ foreach (var typeName in kvp.Value)
+ Log.LogCodedError ("XA4215", Properties.Resources.XA4215_Details, kvp.Key, typeName);
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
index 25550eb3473..cacea6de2a3 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
@@ -32,7 +32,7 @@ sealed class ApplicationConfig
public bool instant_run_enabled ;
public bool jni_add_native_method_registration_attribute_present;
public bool have_runtime_config_blob;
- public bool have_assemblies_blob;
+ public bool have_standalone_assembly_dsos;
public bool marshal_methods_enabled;
public byte bound_stream_io_exception_type;
public uint package_naming_policy;
@@ -40,13 +40,21 @@ sealed class ApplicationConfig
public uint system_property_count;
public uint number_of_assemblies_in_apk;
public uint bundled_assembly_name_width;
- public uint number_of_assembly_store_files;
public uint number_of_dso_cache_entries;
+
+ [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)]
public uint android_runtime_jnienv_class_token;
+
+ [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)]
public uint jnienv_initialize_method_token;
+
+ [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)]
public uint jnienv_registerjninatives_method_token;
+
public uint jni_remapping_replacement_type_count;
public uint jni_remapping_replacement_method_index_entry_count;
+
+ [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)]
public uint mono_components_mask;
public string android_package_name = String.Empty;
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs
index 2dcbac7b0e4..9c76a44a986 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs
@@ -28,7 +28,7 @@ public override string GetComment (object data, string fieldName)
{
var dso_entry = EnsureType (data);
if (String.Compare ("hash", fieldName, StringComparison.Ordinal) == 0) {
- return $" hash 0x{dso_entry.hash:x}, from name: {dso_entry.HashedName}";
+ return $" hash, from name: {dso_entry.HashedName}";
}
if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) {
@@ -57,49 +57,6 @@ sealed class DSOCacheEntry
public IntPtr handle = IntPtr.Zero;
}
- // Order of fields and their type must correspond *exactly* to that in
- // src/monodroid/jni/xamarin-app.hh AssemblyStoreAssemblyDescriptor structure
- sealed class AssemblyStoreAssemblyDescriptor
- {
- public uint data_offset;
- public uint data_size;
-
- public uint debug_data_offset;
- public uint debug_data_size;
-
- public uint config_data_offset;
- public uint config_data_size;
- }
-
- // Order of fields and their type must correspond *exactly* to that in
- // src/monodroid/jni/xamarin-app.hh AssemblyStoreSingleAssemblyRuntimeData structure
- sealed class AssemblyStoreSingleAssemblyRuntimeData
- {
- [NativePointer]
- public byte image_data;
-
- [NativePointer]
- public byte debug_info_data;
-
- [NativePointer]
- public byte config_data;
-
- [NativePointer]
- public AssemblyStoreAssemblyDescriptor descriptor;
- }
-
- // Order of fields and their type must correspond *exactly* to that in
- // src/monodroid/jni/xamarin-app.hh AssemblyStoreRuntimeData structure
- sealed class AssemblyStoreRuntimeData
- {
- [NativePointer]
- public byte data_start;
- public uint assembly_count;
-
- [NativePointer]
- public AssemblyStoreAssemblyDescriptor assemblies;
- }
-
sealed class XamarinAndroidBundledAssemblyContextDataProvider : NativeAssemblerStructContextDataProvider
{
public override ulong GetBufferSize (object data, string fieldName)
@@ -157,9 +114,8 @@ sealed class XamarinAndroidBundledAssembly
public bool InstantRunEnabled { get; set; }
public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
public bool HaveRuntimeConfigBlob { get; set; }
- public bool HaveAssemblyStore { get; set; }
+ public bool HaveStandaloneAssemblyDSOs { get; set; }
public int NumberOfAssembliesInApk { get; set; }
- public int NumberOfAssemblyStoresInApks { get; set; }
public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL
public int AndroidRuntimeJNIEnvToken { get; set; }
public int JNIEnvInitializeToken { get; set; }
@@ -211,7 +167,7 @@ protected override void Construct (LlvmIrModule module)
instant_run_enabled = InstantRunEnabled,
jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent,
have_runtime_config_blob = HaveRuntimeConfigBlob,
- have_assemblies_blob = HaveAssemblyStore,
+ have_standalone_assembly_dsos = HaveStandaloneAssemblyDSOs,
marshal_methods_enabled = MarshalMethodsEnabled,
bound_stream_io_exception_type = (byte)BoundExceptionType,
package_naming_policy = (uint)PackageNamingPolicy,
@@ -219,7 +175,6 @@ protected override void Construct (LlvmIrModule module)
system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2),
number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
- number_of_assembly_store_files = (uint)NumberOfAssemblyStoresInApks,
number_of_dso_cache_entries = (uint)dsoCache.Count,
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
@@ -235,24 +190,23 @@ protected override void Construct (LlvmIrModule module)
var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
Comment = " DSO cache entries",
BeforeWriteCallback = HashAndSortDSOCache,
+ NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal,
};
module.Add (dso_cache);
- if (!HaveAssemblyStore) {
- xamarinAndroidBundledAssemblies = new List> (NumberOfAssembliesInApk);
-
- var emptyBundledAssemblyData = new XamarinAndroidBundledAssembly {
- apk_fd = -1,
- data_offset = 0,
- data_size = 0,
- data = 0,
- name_length = (uint)BundledAssemblyNameWidth,
- name = null,
- };
+ // TODO: don't generate if we're embedding assemblies into DSOs
+ xamarinAndroidBundledAssemblies = new List> (NumberOfAssembliesInApk);
+ var emptyBundledAssemblyData = new XamarinAndroidBundledAssembly {
+ apk_fd = -1,
+ data_offset = 0,
+ data_size = 0,
+ data = 0,
+ name_length = (uint)BundledAssemblyNameWidth,
+ name = null,
+ };
- for (int i = 0; i < NumberOfAssembliesInApk; i++) {
- xamarinAndroidBundledAssemblies.Add (new StructureInstance (xamarinAndroidBundledAssemblyStructureInfo, emptyBundledAssemblyData));
- }
+ for (int i = 0; i < NumberOfAssembliesInApk; i++) {
+ xamarinAndroidBundledAssemblies.Add (new StructureInstance (xamarinAndroidBundledAssemblyStructureInfo, emptyBundledAssemblyData));
}
string bundledBuffersSize = xamarinAndroidBundledAssemblies == null ? "empty (unused when assembly stores are enabled)" : $"{BundledAssemblyNameWidth} bytes long";
@@ -261,25 +215,6 @@ protected override void Construct (LlvmIrModule module)
Comment = $" Bundled assembly name buffers, all {bundledBuffersSize}",
};
module.Add (bundled_assemblies);
-
- AddAssemblyStores (module);
- }
-
- void AddAssemblyStores (LlvmIrModule module)
- {
- ulong itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssembliesInApk : 0);
- var assembly_store_bundled_assemblies = new LlvmIrGlobalVariable (typeof(List>), "assembly_store_bundled_assemblies", LlvmIrVariableOptions.GlobalWritable) {
- ZeroInitializeArray = true,
- ArrayItemCount = itemCount,
- };
- module.Add (assembly_store_bundled_assemblies);
-
- itemCount = (ulong)(HaveAssemblyStore ? NumberOfAssemblyStoresInApks : 0);
- var assembly_stores = new LlvmIrGlobalVariable (typeof(List>), "assembly_stores", LlvmIrVariableOptions.GlobalWritable) {
- ZeroInitializeArray = true,
- ArrayItemCount = itemCount,
- };
- module.Add (assembly_stores);
}
void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
@@ -371,9 +306,6 @@ void AddNameMutations (string name)
void MapStructures (LlvmIrModule module)
{
applicationConfigStructureInfo = module.MapStructure ();
- module.MapStructure ();
- assemblyStoreSingleAssemblyRuntimeDataStructureinfo = module.MapStructure ();
- assemblyStoreRuntimeDataStructureInfo = module.MapStructure ();
xamarinAndroidBundledAssemblyStructureInfo = module.MapStructure ();
dsoCacheEntryStructureInfo = module.MapStructure ();
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ArchAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ArchAssemblyStore.cs
deleted file mode 100644
index a5b5811b7de..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ArchAssemblyStore.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
-
-namespace Xamarin.Android.Tasks
-{
- class ArchAssemblyStore : AssemblyStore
- {
- readonly Dictionary> assemblies;
- HashSet seenArchAssemblyNames;
-
- public ArchAssemblyStore (string apkName, string archiveAssembliesPrefix, TaskLoggingHelper log, uint id, AssemblyStoreGlobalIndex globalIndexCounter)
- : base (apkName, archiveAssembliesPrefix, log, id, globalIndexCounter)
- {
- assemblies = new Dictionary> (StringComparer.OrdinalIgnoreCase);
- }
-
- public override string WriteIndex (List globalIndex)
- {
- throw new InvalidOperationException ("Architecture-specific assembly blob cannot contain global assembly index");
- }
-
- public override void Add (AssemblyStoreAssemblyInfo blobAssembly)
- {
- if (String.IsNullOrEmpty (blobAssembly.Abi)) {
- throw new InvalidOperationException ($"Architecture-agnostic assembly cannot be added to an architecture-specific blob ({blobAssembly.FilesystemAssemblyPath})");
- }
-
- if (!assemblies.ContainsKey (blobAssembly.Abi)) {
- assemblies.Add (blobAssembly.Abi, new List ());
- }
-
- List blobAssemblies = assemblies[blobAssembly.Abi];
- blobAssemblies.Add (blobAssembly);
-
- if (seenArchAssemblyNames == null) {
- seenArchAssemblyNames = new HashSet (StringComparer.Ordinal);
- }
-
- string assemblyName = GetAssemblyName (blobAssembly);
- if (seenArchAssemblyNames.Contains (assemblyName)) {
- return;
- }
-
- seenArchAssemblyNames.Add (assemblyName);
- }
-
- public override void Generate (string outputDirectory, List globalIndex, List blobPaths)
- {
- if (assemblies.Count == 0) {
- return;
- }
-
- var assemblyNames = new Dictionary ();
- foreach (var kvp in assemblies) {
- string abi = kvp.Key;
- List archAssemblies = kvp.Value;
-
- // All the architecture blobs must have assemblies in exactly the same order
- archAssemblies.Sort ((AssemblyStoreAssemblyInfo a, AssemblyStoreAssemblyInfo b) => Path.GetFileName (a.FilesystemAssemblyPath).CompareTo (Path.GetFileName (b.FilesystemAssemblyPath)));
- if (assemblyNames.Count == 0) {
- for (int i = 0; i < archAssemblies.Count; i++) {
- AssemblyStoreAssemblyInfo info = archAssemblies[i];
- assemblyNames.Add (i, Path.GetFileName (info.FilesystemAssemblyPath));
- }
- continue;
- }
-
- if (archAssemblies.Count != assemblyNames.Count) {
- throw new InvalidOperationException ($"Assembly list for ABI '{abi}' has a different number of assemblies than other ABI lists (expected {assemblyNames.Count}, found {archAssemblies.Count}");
- }
-
- for (int i = 0; i < archAssemblies.Count; i++) {
- AssemblyStoreAssemblyInfo info = archAssemblies[i];
- string fileName = Path.GetFileName (info.FilesystemAssemblyPath);
-
- if (assemblyNames[i] != fileName) {
- throw new InvalidOperationException ($"Assembly list for ABI '{abi}' differs from other lists at index {i}. Expected '{assemblyNames[i]}', found '{fileName}'");
- }
- }
- }
-
- bool addToGlobalIndex = true;
- foreach (var kvp in assemblies) {
- string abi = kvp.Key;
- List archAssemblies = kvp.Value;
-
- if (archAssemblies.Count == 0) {
- continue;
- }
-
- // Android uses underscores in place of dashes in ABI names, let's follow the convention
- string androidAbi = abi.Replace ('-', '_');
- Generate (Path.Combine (outputDirectory, $"{ApkName}_{BlobPrefix}.{androidAbi}{BlobExtension}"), archAssemblies, globalIndex, blobPaths, addToGlobalIndex);
-
- // NOTE: not thread safe! The counter must grow monotonically but we also don't want to use different index values for the architecture-specific
- // assemblies with the same names, that would only waste space in the generated `libxamarin-app.so`. To use the same index values for the same
- // assemblies in different architectures we need to move the counter back here.
- GlobalIndexCounter.Subtract ((uint)archAssemblies.Count);
-
- if (addToGlobalIndex) {
- // We want the architecture-specific assemblies to be added to the global index only once
- addToGlobalIndex = false;
- }
- }
-
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs
index 1972dba0d85..feea6f7ccb3 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs
@@ -1,21 +1,25 @@
using System;
using System.Buffers;
+using System.Collections.Generic;
using System.IO;
using K4os.Compression.LZ4;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
namespace Xamarin.Android.Tasks
{
class AssemblyCompression
{
- public enum CompressionResult
+ enum CompressionResult
{
Success,
InputTooBig,
EncodingFailed,
}
- public sealed class AssemblyData
+ sealed class AssemblyData
{
public string SourcePath { get; internal set; }
public uint DescriptorIndex { get; internal set; }
@@ -47,15 +51,26 @@ public void SetData (string sourcePath, uint descriptorIndex)
// two - it should be more than enough for most needs.
//public const ulong InputAssemblySizeLimit = 60 * 1024 * 1024;
- static readonly ArrayPool bytePool = ArrayPool.Shared;
+ readonly ArrayPool bytePool = ArrayPool.Shared;
+ readonly TaskLoggingHelper log;
+ readonly string compressedOutputDir;
- public static CompressionResult Compress (AssemblyData data, string outputDirectory)
+ public AssemblyCompression (TaskLoggingHelper log, string compressedOutputDir)
{
- if (data == null)
+ this.log = log;
+ this.compressedOutputDir = compressedOutputDir;
+ }
+
+ // TODO: consider using https://github.com/emmanuel-marty/lz4ultra
+ CompressionResult Compress (AssemblyData data, string outputDirectory)
+ {
+ if (data == null) {
throw new ArgumentNullException (nameof (data));
+ }
- if (String.IsNullOrEmpty (outputDirectory))
+ if (String.IsNullOrEmpty (outputDirectory)) {
throw new ArgumentException ("must not be null or empty", nameof (outputDirectory));
+ }
Directory.CreateDirectory (outputDirectory);
@@ -79,8 +94,9 @@ public static CompressionResult Compress (AssemblyData data, string outputDirect
destBytes = bytePool.Rent (LZ4Codec.MaximumOutputSize (sourceBytes.Length));
int encodedLength = LZ4Codec.Encode (sourceBytes, 0, checked((int)fi.Length), destBytes, 0, destBytes.Length, LZ4Level.L09_HC);
- if (encodedLength < 0)
+ if (encodedLength < 0) {
return CompressionResult.EncodingFailed;
+ }
data.DestinationSize = (uint)encodedLength;
using (var fs = File.Open (data.DestinationPath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
@@ -94,13 +110,74 @@ public static CompressionResult Compress (AssemblyData data, string outputDirect
}
}
} finally {
- if (sourceBytes != null)
+ if (sourceBytes != null) {
bytePool.Return (sourceBytes);
- if (destBytes != null)
+ }
+ if (destBytes != null) {
bytePool.Return (destBytes);
+ }
}
return CompressionResult.Success;
}
+
+ public (string outputPath, bool compressed) CompressAssembly (ITaskItem assembly, FileInfo inputInfo)
+ {
+ if (Boolean.TryParse (assembly.GetMetadata ("AndroidSkipCompression"), out bool value) && value) {
+ log.LogDebugMessage ($"Skipping compression of {assembly.ItemSpec} due to 'AndroidSkipCompression' == 'true' ");
+ return (assembly.ItemSpec, false);
+ }
+
+ return CompressAssembly (assembly.ItemSpec, inputInfo, assembly.GetMetadata ("DestinationSubDirectory"));
+ }
+
+ public (string outputPath, bool compressed) CompressAssembly (string assemblyPath, FileInfo inputInfo, string? subDirectory)
+ {
+ if (!inputInfo.Exists) {
+ throw new InvalidOperationException ($"File '{assemblyPath}' does not exist");
+ }
+
+ string assemblyOutputDir;
+ if (!String.IsNullOrEmpty (subDirectory)) {
+ assemblyOutputDir = Path.Combine (compressedOutputDir, subDirectory);
+ } else {
+ assemblyOutputDir = compressedOutputDir;
+ }
+ string outputPath = Path.Combine (assemblyOutputDir, $"{Path.GetFileName (assemblyPath)}.lz4");
+ Directory.CreateDirectory (assemblyOutputDir);
+
+ byte[]? sourceBytes = null;
+ byte[]? destBytes = null;
+ try {
+ int inputLength = checked((int)inputInfo.Length);
+ sourceBytes = bytePool.Rent (inputLength);
+ using (var fs = File.Open (assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
+ fs.Read (sourceBytes, 0, inputLength);
+ }
+
+ destBytes = bytePool.Rent (LZ4Codec.MaximumOutputSize (sourceBytes.Length));
+ int encodedLength = LZ4Codec.Encode (sourceBytes, 0, inputLength, destBytes, 0, destBytes.Length, LZ4Level.L09_HC);
+ if (encodedLength < 0) {
+ log.LogMessage ($"Failed to compress {assemblyPath}");
+ return (assemblyPath, false);
+ }
+
+ using (var fs = File.Open (outputPath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
+ using (var bw = new BinaryWriter (fs)) {
+ bw.Write (destBytes, 0, encodedLength);
+ bw.Flush ();
+ }
+ }
+ } finally {
+ if (sourceBytes != null) {
+ bytePool.Return (sourceBytes);
+ }
+ if (destBytes != null) {
+ bytePool.Return (destBytes);
+ }
+ }
+
+ return (outputPath, true);
+ }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyDSOGenerator.Classes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyDSOGenerator.Classes.cs
new file mode 100644
index 00000000000..6d627ca623e
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyDSOGenerator.Classes.cs
@@ -0,0 +1,326 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using Xamarin.Android.Tasks.LLVMIR;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+partial class AssemblyDSOGenerator
+{
+ class StandaloneAssemblyEntry
+ {
+ [NativeAssembler (Ignore = true)]
+ public byte[]? AssemblyData;
+
+ [NativeAssembler (Ignore = true)]
+ public string InputFilePath;
+
+ // If `true`, we have an instance of a standalone assembly being processed when
+ // generating libxamarin-app.so sources. This is necessary because we still need
+ // all the assembly information (name, size etc) to generated indexes etc. However,
+ // in this case assembly data will not be needed as it's already in its own DSO
+ [NativeAssembler (Ignore = true)]
+ public bool IsStandalone;
+ }
+
+ // Must be identical to AssemblyEntry in src/monodroid/jni/xamarin-app.hh
+ sealed class AssemblyEntry : StandaloneAssemblyEntry
+ {
+ [NativeAssembler (Ignore = true)]
+ public string Name;
+
+ // offset into the `xa_input_assembly_data` array
+ public uint input_data_offset;
+
+ // number of bytes data of this assembly occupies
+ public uint input_data_size;
+
+ // offset into the `xa_uncompressed_assembly_data` array where the uncompressed
+ // assembly data (if any) lives.
+ public uint uncompressed_data_offset;
+
+ // Size of the uncompressed data. 0 if assembly wasn't compressed.
+ public uint uncompressed_data_size;
+ }
+
+ // Must be identical to AssemblyIndexEntry in src/monodroid/jni/xamarin-app.hh
+ class AssemblyIndexEntryBase
+ {
+ [NativeAssembler (Ignore = true)]
+ public string Name;
+
+ [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)]
+ public T name_hash;
+
+ // Index into the `xa_assemblies` descriptor array
+ public uint assemblies_index;
+
+ // Index into the `xa_load_info` array. We can't reuse the `assemblies_index` above because the order
+ // of entries in `xa_load_info` is determined in a different task than that of `xa_assemblies` and it
+ // also depends on the number of assemblies placed in the standalone DSOs.
+ public uint load_info_index;
+
+ // whether hashed name had extension
+ public bool has_extension;
+
+ // whether assembly data lives in a separate DSO
+ public bool is_standalone;
+ }
+
+ sealed class AssemblyIndexEntry32 : AssemblyIndexEntryBase
+ {}
+
+ sealed class AssemblyIndexEntry64 : AssemblyIndexEntryBase
+ {}
+
+ // Must be identical to AssemblyLoadInfo in src/monodroid/jni/xamarin-app.hh
+ sealed class AssemblyLoadInfo
+ {
+ // offset into the APK, or 0 if the assembly isn't in a standalone DSO or if the DSOs are extracted to disk at install time
+ public uint apk_offset;
+
+ // Size of the DSO in the APK
+ public uint apk_data_size;
+
+ // Address at which the assembly data was mmapped
+ public IntPtr mmap_addr;
+
+ // Address at which the assembly data is available. It may be the same as `mmap_addr` if the
+ // data wasn't compressed, different otherwise.
+ public IntPtr data_addr;
+ public uint data_size;
+ };
+
+ // Must be identical to AssembliesConfig in src/monodroid/jni/xamarin-app.hh
+ sealed class AssembliesConfig
+ {
+ public uint input_assembly_data_size;
+ public uint uncompressed_assembly_data_size;
+ public uint assembly_name_length;
+ public uint assembly_count;
+ public uint assembly_index_count;
+ public uint assembly_dso_count;
+ public uint shared_library_name_length;
+ };
+
+ // Members with underscores correspond to the native fields we output.
+ sealed class ArchState
+ {
+ // Currently we hash assembly name with and without the extension
+ const int AssemblyNameVariationsCount = 2;
+
+ public readonly List> xa_assemblies;
+ public readonly List>? xa_assembly_index32;
+ public readonly List>? xa_assembly_index64;
+ public readonly List xa_assembly_names;
+ public readonly List xa_assembly_dso_names;
+ public readonly AssembliesConfig xa_assemblies_config;
+ public readonly StandaloneAssemblyEntry? StandaloneAssembly;
+
+ public ArchState (int assemblyCount, AndroidTargetArch arch, StandaloneAssemblyEntry? standaloneAssembly = null)
+ {
+ if (assemblyCount < 0) {
+ throw new ArgumentException ("must not be a negative number", nameof (assemblyCount));
+ }
+
+ StandaloneAssembly = standaloneAssembly;
+ xa_assemblies = new List> (assemblyCount);
+ xa_assembly_names = new List (assemblyCount * AssemblyNameVariationsCount);
+ xa_assembly_dso_names = new List (assemblyCount);
+
+ switch (arch) {
+ case AndroidTargetArch.Arm64:
+ case AndroidTargetArch.X86_64:
+ xa_assembly_index64 = new List> (assemblyCount);
+ break;
+
+ case AndroidTargetArch.Arm:
+ case AndroidTargetArch.X86:
+ xa_assembly_index32 = new List> (assemblyCount);
+ break;
+
+ default:
+ throw new InvalidOperationException ($"Internal error: architecture {arch} not supported");
+ }
+
+ xa_assemblies_config = new AssembliesConfig {
+ input_assembly_data_size = 0,
+ uncompressed_assembly_data_size = 0,
+ assembly_name_length = 0,
+ assembly_count = (uint)assemblyCount,
+ assembly_index_count = (uint)assemblyCount * AssemblyNameVariationsCount,
+ };
+ }
+ }
+
+ abstract class StreamedArrayDataProvider : LlvmIrStreamedArrayDataProvider
+ {
+ readonly Dictionary assemblyArchStates;
+
+ protected StreamedArrayDataProvider (Type arrayElementType, Dictionary assemblyArchStates)
+ : base (arrayElementType)
+ {
+ this.assemblyArchStates = assemblyArchStates;
+ }
+
+ protected ArchState GetArchState (LlvmIrModuleTarget target) => AssemblyDSOGenerator.GetArchState (target, assemblyArchStates);
+
+ protected byte[] EnsureValidAssemblyData (StandaloneAssemblyEntry? entry)
+ {
+ if (entry == null) {
+ throw new ArgumentNullException (nameof (entry));
+ }
+
+ if (!entry.IsStandalone) {
+ if (entry.AssemblyData == null) {
+ throw new InvalidOperationException ("Internal error: assembly data must be present");
+ }
+
+ if (entry.AssemblyData.Length == 0) {
+ throw new InvalidOperationException ("Internal error: assembly data must not be empty");
+ }
+ }
+
+ return entry.AssemblyData;
+ }
+ }
+
+ sealed class StandaloneAssemblyInputDataArrayProvider : StreamedArrayDataProvider
+ {
+ public StandaloneAssemblyInputDataArrayProvider (Type arrayElementType, Dictionary assemblyArchStates)
+ : base (arrayElementType, assemblyArchStates)
+ {}
+
+ public override (LlvmIrStreamedArrayDataProviderState status, ICollection data) GetData (LlvmIrModuleTarget target)
+ {
+ ArchState archState = GetArchState (target);
+
+ return (
+ LlvmIrStreamedArrayDataProviderState.LastSection,
+ EnsureValidAssemblyData (archState.StandaloneAssembly)
+ );
+ }
+
+ public override ulong GetTotalDataSize (LlvmIrModuleTarget target)
+ {
+ ArchState archState = GetArchState (target);
+ return (ulong)(archState.StandaloneAssembly?.AssemblyData.Length ?? throw new InvalidOperationException ($"Internal error: standalone assembly not set"));
+ }
+ }
+
+ sealed class AssemblyInputDataArrayProvider : StreamedArrayDataProvider
+ {
+ sealed class DataState
+ {
+ public int Index = 0;
+ public string Comment = String.Empty;
+ public ulong TotalDataSize = 0;
+ }
+
+ Dictionary dataStates;
+
+ public AssemblyInputDataArrayProvider (Type arrayElementType, Dictionary assemblyArchStates)
+ : base (arrayElementType, assemblyArchStates)
+ {
+ dataStates = new Dictionary ();
+ foreach (var kvp in assemblyArchStates) {
+ dataStates.Add (kvp.Key, new DataState ());
+ }
+ }
+
+ public override (LlvmIrStreamedArrayDataProviderState status, ICollection? data) GetData (LlvmIrModuleTarget target)
+ {
+ ArchState archState = GetArchState (target);
+ DataState dataState = GetDataState (target);
+ int index = dataState.Index++;
+ if (index >= archState.xa_assemblies.Count) {
+ throw new InvalidOperationException ("Internal error: no more data left");
+ }
+
+ var entry = (AssemblyEntry)archState.xa_assemblies[index].Obj;
+ if (entry.IsStandalone) {
+ return (
+ IsLastEntry () ? LlvmIrStreamedArrayDataProviderState.LastSectionNoData : LlvmIrStreamedArrayDataProviderState.NextSectionNoData,
+ null
+ );
+ }
+
+ string name;
+ if (target.TargetArch == AndroidTargetArch.Arm64 || target.TargetArch == AndroidTargetArch.X86_64) {
+ name = ((AssemblyIndexEntry64)archState.xa_assembly_index64[index].Obj).Name;
+ } else if (target.TargetArch == AndroidTargetArch.Arm || target.TargetArch == AndroidTargetArch.X86) {
+ name = ((AssemblyIndexEntry32)archState.xa_assembly_index32[index].Obj).Name;
+ } else {
+ throw new InvalidOperationException ($"Internal error: architecture {target.TargetArch} not supported");
+ }
+
+ string compressed = entry.uncompressed_data_size == 0 ? "no" : "yes";
+
+ dataState.Comment = $" Assembly: {name} ({entry.InputFilePath}); Data size: {entry.AssemblyData.Length}; compressed: {compressed}";
+ // Each assembly is a new "section"
+ return (
+ IsLastEntry () ? LlvmIrStreamedArrayDataProviderState.LastSection : LlvmIrStreamedArrayDataProviderState.NextSection,
+ EnsureValidAssemblyData (entry)
+ );
+
+ bool IsLastEntry ()
+ {
+ if (index == archState.xa_assemblies.Count - 1) {
+ return true;
+ }
+
+ // Special case: if between the current index and the end of array are only standalone assemblies, we need to terminate now or we're going to have
+ // a dangling comma in the output which llc doesn't like. Since we're in a forward-only streaming mode, we must take care of that corner case here,
+ // alas.
+ for (int i = index + 1; i < archState.xa_assemblies.Count; i++) {
+ if (!((AssemblyEntry)archState.xa_assemblies[i].Obj).IsStandalone) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public override ulong GetTotalDataSize (LlvmIrModuleTarget target)
+ {
+ DataState dataState = GetDataState (target);
+ if (dataState.TotalDataSize > 0) {
+ return dataState.TotalDataSize;
+ }
+
+ ArchState archState = GetArchState (target);
+ ulong totalSize = 0;
+ foreach (StructureInstance si in archState.xa_assemblies) {
+ var entry = (AssemblyEntry)si.Obj;
+ if (entry.IsStandalone) {
+ continue;
+ }
+
+ byte[] data = EnsureValidAssemblyData (entry);
+ totalSize += (ulong)data.Length;
+ }
+
+ return dataState.TotalDataSize = totalSize;
+ }
+
+ public override string GetSectionStartComment (LlvmIrModuleTarget target)
+ {
+ DataState dataState = GetDataState (target);
+ string ret = dataState.Comment;
+ dataState.Comment = String.Empty;
+ return ret;
+ }
+
+ DataState GetDataState (LlvmIrModuleTarget target)
+ {
+ if (!dataStates.TryGetValue (target.TargetArch, out DataState dataState)) {
+ throw new InvalidOperationException ($"Internal error: data state for ABI {target.TargetArch} not available");
+ }
+
+ return dataState;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyDSOGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyDSOGenerator.cs
new file mode 100644
index 00000000000..402b8f54e0e
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyDSOGenerator.cs
@@ -0,0 +1,540 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Xamarin.Android.Tasks.LLVMIR;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+partial class AssemblyDSOGenerator : LlvmIrComposer
+{
+ const string XAAssembliesConfigVarName = "xa_assemblies_config";
+ const string XAAssembliesLoadInfo = "xa_assemblies_load_info";
+ const string XAAssembliesVarName = "xa_assemblies";
+ const string XAAssemblyDSONamesVarName = "xa_assembly_dso_names";
+ const string XAAssemblyIndexVarName = "xa_assembly_index";
+ const string XAAssemblyNamesVarName = "xa_assembly_names";
+ public const string XAInputAssemblyDataVarName = "xa_input_assembly_data";
+ const string XAUncompressedAssemblyDataVarName = "xa_uncompressed_assembly_data";
+
+ readonly Dictionary>? allAssemblies;
+ readonly Dictionary? standaloneAssemblies;
+ readonly Dictionary assemblyArchStates;
+ readonly HashSet? fastPathAssemblies;
+ readonly uint inputAssemblyDataSize;
+ readonly uint uncompressedAssemblyDataSize;
+ StructureInfo? assemblyEntryStructureInfo;
+ StructureInfo? assemblyIndexEntry32StructureInfo;
+ StructureInfo? assemblyIndexEntry64StructureInfo;
+ StructureInfo? assembliesConfigStructureInfo;
+ StructureInfo? assemblyLoadInfoStructureInfo;
+
+ public AssemblyDSOGenerator (Dictionary dsoAssemblies)
+ {
+ standaloneAssemblies = dsoAssemblies;
+ assemblyArchStates = MakeArchStates ();
+ }
+
+ public AssemblyDSOGenerator (ICollection fastPathAssemblyNames, Dictionary> dsoAssemblies, ulong inputAssemblyDataSize, ulong uncompressedAssemblyDataSize)
+ {
+ this.inputAssemblyDataSize = EnsureValidSize (inputAssemblyDataSize, nameof (inputAssemblyDataSize));
+ this.uncompressedAssemblyDataSize = EnsureValidSize (uncompressedAssemblyDataSize, nameof (uncompressedAssemblyDataSize));
+ allAssemblies = dsoAssemblies;
+ assemblyArchStates = MakeArchStates ();
+
+ if (fastPathAssemblyNames.Count == 0) {
+ return;
+ }
+
+ fastPathAssemblies = new HashSet (StringComparer.OrdinalIgnoreCase);
+ foreach (string asmName in fastPathAssemblyNames) {
+ fastPathAssemblies.Add (asmName);
+ }
+
+ uint EnsureValidSize (ulong v, string name)
+ {
+ if (v > UInt32.MaxValue) {
+ throw new ArgumentOutOfRangeException (name, "must not exceed UInt32.MaxValue");
+ }
+
+ return (uint)v;
+ }
+ }
+
+ Dictionary MakeArchStates () => new Dictionary ();
+
+ protected override void Construct (LlvmIrModule module)
+ {
+ if (standaloneAssemblies != null) {
+ ConstructStandalone (module);
+ } else {
+ ConstructFastPath (module);
+ }
+ }
+
+ void ConstructStandalone (LlvmIrModule module)
+ {
+ foreach (var kvp in standaloneAssemblies) {
+ AndroidTargetArch arch = kvp.Key;
+ DSOAssemblyInfo info = kvp.Value;
+
+ AddStandaloneAssemblyData (arch, info);
+ }
+
+ var xa_input_assembly_data = new LlvmIrGlobalVariable (typeof(byte[]), XAInputAssemblyDataVarName) {
+ Alignment = 4096,
+ ArrayDataProvider = new StandaloneAssemblyInputDataArrayProvider (typeof(byte), assemblyArchStates),
+ ArrayStride = 16,
+ NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ WriteOptions = LlvmIrVariableWriteOptions.ArrayFormatInRows,
+ };
+
+ module.Add (xa_input_assembly_data);
+ }
+
+ void ConstructFastPath (LlvmIrModule module)
+ {
+ MapStructures (module);
+
+ if (allAssemblies.Count == 0) {
+ ConstructEmptyModule ();
+ return;
+ }
+
+ int expectedAssemblyCount = -1;
+ foreach (var kvp in allAssemblies) {
+ AndroidTargetArch arch = kvp.Key;
+ List infos = kvp.Value;
+
+ if (expectedAssemblyCount < 0) {
+ expectedAssemblyCount = infos.Count;
+ }
+
+ if (infos.Count != expectedAssemblyCount) {
+ throw new InvalidOperationException ($"Collection of assemblies for architecture {arch} has a different number of entries ({infos.Count}) than expected ({expectedAssemblyCount})");
+ }
+
+ AddAssemblyData (arch, infos);
+ }
+
+ if (expectedAssemblyCount <= 0) {
+ ConstructEmptyModule ();
+ return;
+ }
+
+ var xa_assemblies_config = new LlvmIrGlobalVariable (typeof(StructureInstance), XAAssembliesConfigVarName) {
+ BeforeWriteCallback = AssembliesConfigBeforeWrite,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assemblies_config);
+
+ var xa_assemblies = new LlvmIrGlobalVariable (typeof(List>), XAAssembliesVarName) {
+ BeforeWriteCallback = AssembliesBeforeWrite,
+ GetArrayItemCommentCallback = AssembliesItemComment,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assemblies);
+
+ var xa_assembly_index = new LlvmIrGlobalVariable (typeof(List>), XAAssemblyIndexVarName) {
+ BeforeWriteCallback = AssemblyIndexBeforeWrite,
+ GetArrayItemCommentCallback = AssemblyIndexItemComment,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assembly_index);
+
+ var xa_assembly_names = new LlvmIrGlobalVariable (typeof(List), XAAssemblyNamesVarName) {
+ BeforeWriteCallback = AssemblyNamesBeforeWrite,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assembly_names);
+
+ var xa_assembly_dso_names = new LlvmIrGlobalVariable (typeof(List), XAAssemblyDSONamesVarName) {
+ BeforeWriteCallback = AssemblyDSONamesBeforeWrite,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assembly_dso_names);
+
+ var xa_assemblies_load_info = new LlvmIrGlobalVariable (typeof(StructureInstance[]), XAAssembliesLoadInfo) {
+ ArrayItemCount = (ulong)expectedAssemblyCount, // TODO: this should be equal to DSO count
+ Options = LlvmIrVariableOptions.GlobalWritable,
+ ZeroInitializeArray = true,
+ };
+ module.Add (xa_assemblies_load_info);
+
+ var xa_uncompressed_assembly_data = new LlvmIrGlobalVariable (typeof(byte[]), XAUncompressedAssemblyDataVarName) {
+ Alignment = 4096, // align to page boundary, may make access slightly faster
+ ArrayItemCount = uncompressedAssemblyDataSize,
+ Options = LlvmIrVariableOptions.GlobalWritable,
+ ZeroInitializeArray = true,
+ };
+ module.Add (xa_uncompressed_assembly_data);
+
+ var xa_input_assembly_data = new LlvmIrGlobalVariable (typeof(byte[]), XAInputAssemblyDataVarName) {
+ Alignment = 4096,
+ ArrayDataProvider = new AssemblyInputDataArrayProvider (typeof(byte), assemblyArchStates),
+ ArrayStride = 16,
+ NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ WriteOptions = LlvmIrVariableWriteOptions.ArrayFormatInRows,
+ };
+ module.Add (xa_input_assembly_data);
+ }
+
+ void AssembliesBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ variable.Value = archState.xa_assemblies;
+ }
+
+ string AssembliesItemComment (LlvmIrVariable variable, LlvmIrModuleTarget target, ulong index, object? itemValue, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ var entry = itemValue as StructureInstance;
+
+ if (entry != null) {
+ return MakeComment (((AssemblyEntry)entry.Obj).Name);
+ }
+
+ throw new InvalidOperationException ($"Internal error: assembly array member has unsupported type '{itemValue?.GetType ()}'");
+ }
+
+ string AssemblyIndexItemComment (LlvmIrVariable variable, LlvmIrModuleTarget target, ulong index, object? itemValue, object? state)
+ {
+ var value32 = itemValue as StructureInstance;
+ if (value32 != null) {
+ return MakeComment (((AssemblyIndexEntry32)value32.Obj).Name);
+ }
+
+ var value64 = itemValue as StructureInstance;
+ if (value64 != null) {
+ return MakeComment (((AssemblyIndexEntry64)value64.Obj).Name);
+ }
+
+ throw new InvalidOperationException ($"Internal error: assembly index array member has unsupported type '{itemValue?.GetType ()}'");
+ }
+
+ static string MakeComment (string name) => $" => {name}";
+
+ void AssemblyNamesBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ var names = new List ((int)archState.xa_assemblies_config.assembly_count);
+
+ foreach (byte[] nameBytes in archState.xa_assembly_names) {
+ names.Add (GetProperlySizedBytesForNameArray (archState.xa_assemblies_config.assembly_name_length, nameBytes));
+ }
+
+ variable.Value = names;
+ }
+
+ void AssemblyDSONamesBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ var names = new List ((int)archState.xa_assemblies_config.assembly_count);
+
+ foreach (byte[] nameBytes in archState.xa_assembly_dso_names) {
+ names.Add (GetProperlySizedBytesForNameArray (archState.xa_assemblies_config.shared_library_name_length, nameBytes));
+ }
+
+ variable.Value = names;
+ }
+
+ static byte[] GetProperlySizedBytesForNameArray (uint requiredSize, byte[] inputBytes)
+ {
+ if (inputBytes.Length > requiredSize - 1) {
+ throw new ArgumentOutOfRangeException (nameof (inputBytes), $"Must not exceed {requiredSize - 1} bytes");
+ }
+
+ var ret = new byte[requiredSize];
+ Array.Clear (ret, 0, ret.Length);
+ inputBytes.CopyTo (ret, 0);
+
+ return ret;
+ }
+ void AssemblyIndexBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ var gv = (LlvmIrGlobalVariable)variable;
+ object value;
+ Type type;
+
+ if (target.TargetArch == AndroidTargetArch.Arm64 || target.TargetArch == AndroidTargetArch.X86_64) {
+ value = archState.xa_assembly_index64;
+ type = archState.xa_assembly_index64.GetType ();
+ } else if (target.TargetArch == AndroidTargetArch.Arm || target.TargetArch == AndroidTargetArch.X86) {
+ value = archState.xa_assembly_index32;
+ type = archState.xa_assembly_index32.GetType ();
+ } else {
+ throw new InvalidOperationException ($"Internal error: architecture {target.TargetArch} not supported");
+ }
+
+ gv.OverrideValueAndType (type, value);
+ }
+
+ void AssembliesConfigBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ variable.Value = new StructureInstance (assembliesConfigStructureInfo, archState.xa_assemblies_config);
+ }
+
+ ArchState GetArchState (LlvmIrModuleTarget target) => GetArchState (target, assemblyArchStates);
+
+ static ArchState GetArchState (LlvmIrModuleTarget target, Dictionary archStates)
+ {
+ if (!archStates.TryGetValue (target.TargetArch, out ArchState archState)) {
+ throw new InvalidOperationException ($"Internal error: architecture state for ABI {target.TargetArch} not available");
+ }
+
+ return archState;
+ }
+
+ protected override void CleanupAfterGeneration (AndroidTargetArch arch)
+ {
+ if (!assemblyArchStates.TryGetValue (arch, out ArchState archState)) {
+ throw new InvalidOperationException ($"Internal error: data for ABI {arch} not available");
+ }
+
+ foreach (StructureInstance si in archState.xa_assemblies) {
+ var entry = (AssemblyEntry)si.Obj;
+ entry.AssemblyData = null; // Help the GC a bit
+ }
+ }
+
+ (ArchState archState, bool is64Bit) GetArchState (AndroidTargetArch arch, int assemblyCount, StandaloneAssemblyEntry? standaloneAssembly = null)
+ {
+ if (!assemblyArchStates.TryGetValue (arch, out ArchState archState)) {
+ archState = new ArchState (assemblyCount, arch, standaloneAssembly);
+ assemblyArchStates.Add (arch, archState);
+ }
+
+ bool is64Bit = arch switch {
+ AndroidTargetArch.Arm => false,
+ AndroidTargetArch.X86 => false,
+ AndroidTargetArch.Arm64 => true,
+ AndroidTargetArch.X86_64 => true,
+ _ => throw new NotSupportedException ($"Architecture '{arch}' is not supported")
+ };
+
+ return (archState, is64Bit);
+ }
+
+ uint GetInputSize (DSOAssemblyInfo info)
+ {
+ uint ret = info.CompressedDataSize == 0 ? (uint)info.DataSize : (uint)info.CompressedDataSize;
+ if (ret > Int32.MaxValue) {
+ throw new InvalidOperationException ($"Assembly {info.InputFile} size exceeds 2GB");
+ }
+
+ return ret;
+ }
+
+ void ReadAssemblyData (DSOAssemblyInfo info, StandaloneAssemblyEntry entry)
+ {
+ using (var asmFile = File.Open (info.InputFile, FileMode.Open, FileAccess.Read, FileShare.Read)) {
+ asmFile.Read (entry.AssemblyData, 0, entry.AssemblyData.Length);
+ }
+ }
+
+ void AddStandaloneAssemblyData (AndroidTargetArch arch, DSOAssemblyInfo info)
+ {
+ uint inputSize = GetInputSize (info);
+ var entry = new StandaloneAssemblyEntry {
+ AssemblyData = new byte[inputSize],
+ InputFilePath = info.InputFile,
+ IsStandalone = true,
+ };
+ (ArchState archState, bool _) = GetArchState (arch, 1, entry);
+
+ ReadAssemblyData (info, entry);
+ }
+
+ void AddAssemblyData (AndroidTargetArch arch, List infos)
+ {
+ if (infos.Count == 0) {
+ return;
+ }
+
+ (ArchState archState, bool is64Bit) = GetArchState (arch, infos.Count);
+ var usedHashes = new HashSet ();
+ ulong inputOffset = 0;
+ ulong uncompressedOffset = 0;
+ ulong assemblyNameLength = 0;
+ ulong sharedLibraryNameLength = 0;
+ ulong dso_count = 0;
+
+ foreach (DSOAssemblyInfo info in infos) {
+ bool isStandalone = info.IsStandalone;
+ uint inputSize = GetInputSize (info);
+
+ if (isStandalone) {
+ if (!info.AssemblyLoadInfoIndex.HasValue) {
+ throw new InvalidOperationException ($"Internal error: item for assembly '{info.Name}' is missing the required assembly load index value");
+ }
+
+ if (!info.AssemblyDataSymbolOffset.HasValue) {
+ throw new InvalidOperationException ($"Internal error: item for assembly '{info.Name}' is missing the required assembly data offset value");
+ }
+
+ dso_count++;
+ }
+
+ // We need to read each file into a separate array, as it is (theoretically) possible that all the assemblies data will exceed 2GB,
+ // which is the limit of we can allocate (or rent, below) in .NET, per single array.
+ //
+ // We also need to read all the assemblies for all the target ABIs, as it is possible that **all** of them will be different.
+ //
+ // All the data will then be concatenated on write time into a single native array.
+ var entry = new AssemblyEntry {
+ // We can't use the byte pool here, even though it would be more efficient, because the generator expects an ICollection,
+ // which it then iterates on, and the rented arrays can (and frequently will) be bigger than the requested size.
+ AssemblyData = isStandalone ? null : new byte[inputSize],
+ IsStandalone = isStandalone,
+ Name = info.Name,
+ InputFilePath = info.InputFile,
+ input_data_offset = isStandalone ? (uint)info.AssemblyDataSymbolOffset : (uint)inputOffset,
+ input_data_size = inputSize,
+ uncompressed_data_size = info.CompressedDataSize == 0 ? 0 : (uint)info.DataSize,
+ uncompressed_data_offset = (uint)uncompressedOffset,
+ };
+ inputOffset = AddWithCheck (inputOffset, inputSize, UInt32.MaxValue, "Input data too long");
+ if (!isStandalone) {
+ ReadAssemblyData (info, entry);
+ }
+
+ // This is way, way more than Google Play Store supports now, but we won't limit ourselves more than we have to
+ uncompressedOffset = AddWithCheck (uncompressedOffset, entry.uncompressed_data_size, UInt32.MaxValue, "Assembly data too long");
+ archState.xa_assemblies.Add (new StructureInstance (assemblyEntryStructureInfo, entry));
+
+ byte[] nameBytes = StringToBytes (info.Name);
+ archState.xa_assembly_names.Add (nameBytes);
+
+ byte[] sharedLibraryNameBytes = isStandalone ? StringToBytes (info.StandaloneDSOName) : Array.Empty ();
+ archState.xa_assembly_dso_names.Add (sharedLibraryNameBytes);
+
+ if (sharedLibraryNameBytes != null) {
+ if (sharedLibraryNameLength < (ulong)sharedLibraryNameBytes.Length) {
+ sharedLibraryNameLength = (ulong)sharedLibraryNameBytes.Length;
+ }
+ }
+
+ if ((ulong)nameBytes.Length > assemblyNameLength) {
+ assemblyNameLength = (ulong)nameBytes.Length;
+ }
+ ulong nameHash = EnsureUniqueHash (GetXxHash (nameBytes, is64Bit), info.Name);
+
+ string nameWithoutExtension;
+ string? dirName = Path.GetDirectoryName (info.Name);
+
+ if (String.IsNullOrEmpty (dirName)) {
+ nameWithoutExtension = Path.GetFileNameWithoutExtension (info.Name);
+ } else {
+ // Don't use Path.Combine because the `/` separator must remain as such, since it's not a "real"
+ // directory separator but a culture/name separator. Path.Combine would use `\` on Windows.
+ nameWithoutExtension = $"{dirName}/{Path.GetFileNameWithoutExtension (info.Name)}";
+ }
+
+ byte[] nameWithoutExtensionBytes = StringToBytes (nameWithoutExtension);
+ ulong nameWithoutExtensionHash = EnsureUniqueHash (GetXxHash (nameWithoutExtensionBytes, is64Bit), nameWithoutExtension);
+
+ uint assemblyIndex = (uint)archState.xa_assemblies.Count - 1;
+ uint loadInfoIndex = isStandalone ? info.AssemblyLoadInfoIndex.Value : UInt32.MaxValue;
+
+ // If the number of assembly name variations in the index changes, ArchState.AssemblyNameVariationsCount **MUST** be updated accordingly
+ if (is64Bit) {
+ var indexEntry = new AssemblyIndexEntry64 {
+ Name = info.Name,
+ name_hash = nameHash,
+ assemblies_index = assemblyIndex,
+ load_info_index = loadInfoIndex,
+ has_extension = true,
+ is_standalone = isStandalone,
+ };
+ archState.xa_assembly_index64.Add (new StructureInstance (assemblyIndexEntry64StructureInfo, indexEntry));
+
+ indexEntry = new AssemblyIndexEntry64 {
+ Name = nameWithoutExtension,
+ name_hash = nameWithoutExtensionHash,
+ assemblies_index = assemblyIndex,
+ load_info_index = loadInfoIndex,
+ has_extension = false,
+ is_standalone = isStandalone,
+ };
+ archState.xa_assembly_index64.Add (new StructureInstance (assemblyIndexEntry64StructureInfo, indexEntry));
+ } else {
+ var indexEntry = new AssemblyIndexEntry32 {
+ Name = info.Name,
+ name_hash = (uint)nameHash,
+ assemblies_index = assemblyIndex,
+ load_info_index = loadInfoIndex,
+ is_standalone = isStandalone,
+ };
+ archState.xa_assembly_index32.Add (new StructureInstance (assemblyIndexEntry32StructureInfo, indexEntry));
+
+ indexEntry = new AssemblyIndexEntry32 {
+ Name = nameWithoutExtension,
+ name_hash = (uint)nameWithoutExtensionHash,
+ assemblies_index = assemblyIndex,
+ load_info_index = loadInfoIndex,
+ has_extension = false,
+ is_standalone = isStandalone,
+ };
+ archState.xa_assembly_index32.Add (new StructureInstance (assemblyIndexEntry32StructureInfo, indexEntry));
+ }
+ }
+
+ if (is64Bit) {
+ archState.xa_assembly_index64.Sort (
+ (StructureInstance a, StructureInstance b) => ((AssemblyIndexEntry64)a.Obj).name_hash.CompareTo (((AssemblyIndexEntry64)b.Obj).name_hash)
+ );
+ } else {
+ archState.xa_assembly_index32.Sort (
+ (StructureInstance a, StructureInstance b) => ((AssemblyIndexEntry32)a.Obj).name_hash.CompareTo (((AssemblyIndexEntry32)b.Obj).name_hash)
+ );
+ }
+
+ archState.xa_assemblies_config.assembly_count = (uint)archState.xa_assemblies.Count;
+ archState.xa_assemblies_config.assembly_dso_count = (uint)dso_count;
+ archState.xa_assemblies_config.input_assembly_data_size = (uint)inputOffset;
+ archState.xa_assemblies_config.uncompressed_assembly_data_size = (uint)uncompressedOffset;
+
+ // Must include the terminating NUL
+ archState.xa_assemblies_config.assembly_name_length = (uint)AddWithCheck (assemblyNameLength, 1, UInt32.MaxValue, "Assembly name is too long");
+ archState.xa_assemblies_config.shared_library_name_length = (uint)AddWithCheck (sharedLibraryNameLength, 1, UInt32.MaxValue, "Shared library name is too long");
+
+ ulong AddWithCheck (ulong lhs, ulong rhs, ulong maxValue, string errorMessage)
+ {
+ ulong v = lhs + rhs;
+ if (v > maxValue) {
+ throw new InvalidOperationException ($"{errorMessage}, exceeding the maximum by {uncompressedOffset - maxValue}");
+ }
+
+ return v;
+ }
+
+ ulong EnsureUniqueHash (ulong hash, string name)
+ {
+ if (usedHashes.Contains (hash)) {
+ throw new InvalidOperationException ($"Hash 0x{hash:x} for name '{name}' is not unique");
+ }
+
+ usedHashes.Add (hash);
+ return hash;
+ }
+ }
+
+ void ConstructEmptyModule ()
+ {
+ throw new NotImplementedException ();
+ }
+
+ void MapStructures (LlvmIrModule module)
+ {
+ assemblyEntryStructureInfo = module.MapStructure ();
+ assemblyIndexEntry32StructureInfo = module.MapStructure ();
+ assemblyIndexEntry64StructureInfo = module.MapStructure ();
+ assembliesConfigStructureInfo = module.MapStructure ();
+ assemblyLoadInfoStructureInfo = module.MapStructure ();
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStore.cs
deleted file mode 100644
index 378a9ee8c46..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStore.cs
+++ /dev/null
@@ -1,377 +0,0 @@
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
-
-namespace Xamarin.Android.Tasks
-{
- abstract class AssemblyStore
- {
- // The two constants below must match their counterparts in src/monodroid/jni/xamarin-app.hh
- const uint BlobMagic = 0x41424158; // 'XABA', little-endian, must match the BUNDLED_ASSEMBLIES_BLOB_MAGIC native constant
- const uint BlobVersion = 1; // Must match the BUNDLED_ASSEMBLIES_BLOB_VERSION native constant
-
- // MUST be equal to the size of the BlobBundledAssembly struct in src/monodroid/jni/xamarin-app.hh
- const uint BlobBundledAssemblyNativeStructSize = 6 * sizeof (uint);
-
- // MUST be equal to the size of the BlobHashEntry struct in src/monodroid/jni/xamarin-app.hh
- const uint BlobHashEntryNativeStructSize = sizeof (ulong) + (3 * sizeof (uint));
-
- // MUST be equal to the size of the BundledAssemblyBlobHeader struct in src/monodroid/jni/xamarin-app.hh
- const uint BlobHeaderNativeStructSize = sizeof (uint) * 5;
-
- protected const string BlobPrefix = "assemblies";
- protected const string BlobExtension = ".blob";
-
- static readonly ArrayPool bytePool = ArrayPool.Shared;
-
- string archiveAssembliesPrefix;
- string indexBlobPath;
-
- protected string ApkName { get; }
- protected TaskLoggingHelper Log { get; }
- protected AssemblyStoreGlobalIndex GlobalIndexCounter { get; }
-
- public uint ID { get; }
- public bool IsIndexStore => ID == 0;
-
- protected AssemblyStore (string apkName, string archiveAssembliesPrefix, TaskLoggingHelper log, uint id, AssemblyStoreGlobalIndex globalIndexCounter)
- {
- if (String.IsNullOrEmpty (archiveAssembliesPrefix)) {
- throw new ArgumentException ("must not be null or empty", nameof (archiveAssembliesPrefix));
- }
-
- if (String.IsNullOrEmpty (apkName)) {
- throw new ArgumentException ("must not be null or empty", nameof (apkName));
- }
-
- GlobalIndexCounter = globalIndexCounter ?? throw new ArgumentNullException (nameof (globalIndexCounter));
- ID = id;
-
- this.archiveAssembliesPrefix = archiveAssembliesPrefix;
- ApkName = apkName;
- Log = log;
- }
-
- public abstract void Add (AssemblyStoreAssemblyInfo blobAssembly);
- public abstract void Generate (string outputDirectory, List globalIndex, List blobPaths);
-
- public virtual string WriteIndex (List globalIndex)
- {
- if (!IsIndexStore) {
- throw new InvalidOperationException ("Assembly index may be written only to blob with index 0");
- }
-
- if (String.IsNullOrEmpty (indexBlobPath)) {
- throw new InvalidOperationException ("Index blob path not set, was Generate called properly?");
- }
-
- if (globalIndex == null) {
- throw new ArgumentNullException (nameof (globalIndex));
- }
-
- string indexBlobHeaderPath = $"{indexBlobPath}.hdr";
- string indexBlobManifestPath = Path.ChangeExtension (indexBlobPath, "manifest");
-
- using (var hfs = File.Open (indexBlobHeaderPath, FileMode.Create, FileAccess.Write, FileShare.None)) {
- using (var writer = new BinaryWriter (hfs, Encoding.UTF8, leaveOpen: true)) {
- WriteIndex (writer, indexBlobManifestPath, globalIndex);
- writer.Flush ();
- }
-
- using (var ifs = File.Open (indexBlobPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
- ifs.CopyTo (hfs);
- hfs.Flush ();
- }
- }
-
- File.Delete (indexBlobPath);
- File.Move (indexBlobHeaderPath, indexBlobPath);
-
- return indexBlobManifestPath;
- }
-
- void WriteIndex (BinaryWriter blobWriter, string manifestPath, List globalIndex)
- {
- using (var manifest = File.Open (manifestPath, FileMode.Create, FileAccess.Write)) {
- using (var manifestWriter = new StreamWriter (manifest, new UTF8Encoding (false))) {
- WriteIndex (blobWriter, manifestWriter, globalIndex);
- manifestWriter.Flush ();
- }
- }
- }
-
- void WriteIndex (BinaryWriter blobWriter, StreamWriter manifestWriter, List globalIndex)
- {
- uint localEntryCount = 0;
- var localAssemblies = new List ();
-
- manifestWriter.WriteLine ("Hash 32 Hash 64 Blob ID Blob idx Name");
-
- var seenHashes32 = new HashSet ();
- var seenHashes64 = new HashSet ();
- bool haveDuplicates = false;
- foreach (AssemblyStoreIndexEntry assembly in globalIndex) {
- if (assembly.StoreID == ID) {
- localEntryCount++;
- localAssemblies.Add (assembly);
- }
-
- if (WarnAboutDuplicateHash ("32", assembly.Name, assembly.NameHash32, seenHashes32) ||
- WarnAboutDuplicateHash ("64", assembly.Name, assembly.NameHash64, seenHashes64)) {
- haveDuplicates = true;
- }
-
- manifestWriter.WriteLine ($"0x{assembly.NameHash32:x08} 0x{assembly.NameHash64:x016} {assembly.StoreID:d03} {assembly.LocalBlobIndex:d04} {assembly.Name}");
- }
-
- if (haveDuplicates) {
- throw new InvalidOperationException ("Duplicate assemblies encountered");
- }
-
- uint globalAssemblyCount = (uint)globalIndex.Count;
-
- blobWriter.Seek (0, SeekOrigin.Begin);
- WriteBlobHeader (blobWriter, localEntryCount, globalAssemblyCount);
-
- // Header and two tables of the same size, each for 32 and 64-bit hashes
- uint offsetFixup = BlobHeaderNativeStructSize + (BlobHashEntryNativeStructSize * globalAssemblyCount * 2);
-
- WriteAssemblyDescriptors (blobWriter, localAssemblies, CalculateOffsetFixup ((uint)localAssemblies.Count, offsetFixup));
-
- var sortedIndex = new List (globalIndex);
- sortedIndex.Sort ((AssemblyStoreIndexEntry a, AssemblyStoreIndexEntry b) => a.NameHash32.CompareTo (b.NameHash32));
- foreach (AssemblyStoreIndexEntry entry in sortedIndex) {
- WriteHash (entry, entry.NameHash32);
- }
-
- sortedIndex.Sort ((AssemblyStoreIndexEntry a, AssemblyStoreIndexEntry b) => a.NameHash64.CompareTo (b.NameHash64));
- foreach (AssemblyStoreIndexEntry entry in sortedIndex) {
- WriteHash (entry, entry.NameHash64);
- }
-
- void WriteHash (AssemblyStoreIndexEntry entry, ulong hash)
- {
- blobWriter.Write (hash);
- blobWriter.Write (entry.MappingIndex);
- blobWriter.Write (entry.LocalBlobIndex);
- blobWriter.Write (entry.StoreID);
- }
-
- bool WarnAboutDuplicateHash (string bitness, string assemblyName, ulong hash, HashSet seenHashes)
- {
- if (seenHashes.Contains (hash)) {
- Log.LogMessage (MessageImportance.High, $"Duplicate {bitness}-bit hash 0x{hash} encountered for assembly {assemblyName}");
- return true;
- }
-
- seenHashes.Add (hash);
- return false;
- }
- }
-
- protected string GetAssemblyName (AssemblyStoreAssemblyInfo assembly)
- {
- string assemblyName = Path.GetFileNameWithoutExtension (assembly.FilesystemAssemblyPath);
- if (assemblyName.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) {
- assemblyName = Path.GetFileNameWithoutExtension (assemblyName);
- }
-
- return assemblyName;
- }
-
- protected void Generate (string outputFilePath, List assemblies, List globalIndex, List blobPaths, bool addToGlobalIndex = true)
- {
- if (globalIndex == null) {
- throw new ArgumentNullException (nameof (globalIndex));
- }
-
- if (blobPaths == null) {
- throw new ArgumentNullException (nameof (blobPaths));
- }
-
- if (IsIndexStore) {
- indexBlobPath = outputFilePath;
- }
-
- blobPaths.Add (outputFilePath);
- Log.LogMessage (MessageImportance.Low, $"AssemblyBlobGenerator: generating blob: {outputFilePath}");
-
- using (var fs = File.Open (outputFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
- using (var writer = new BinaryWriter (fs, Encoding.UTF8)) {
- Generate (writer, assemblies, globalIndex, addToGlobalIndex);
- writer.Flush ();
- }
- }
- }
-
- void Generate (BinaryWriter writer, List assemblies, List globalIndex, bool addToGlobalIndex)
- {
- var localAssemblies = new List ();
-
- if (!IsIndexStore) {
- // Index blob's header and data before the assemblies is handled in WriteIndex in a slightly different
- // way.
- uint nbytes = BlobHeaderNativeStructSize + (BlobBundledAssemblyNativeStructSize * (uint)assemblies.Count);
- var zeros = bytePool.Rent ((int)nbytes);
- writer.Write (zeros, 0, (int)nbytes);
- bytePool.Return (zeros);
- }
-
- foreach (AssemblyStoreAssemblyInfo assembly in assemblies) {
- string assemblyName = GetAssemblyName (assembly);
- string archivePath = assembly.ArchiveAssemblyPath;
- if (archivePath.StartsWith (archiveAssembliesPrefix, StringComparison.OrdinalIgnoreCase)) {
- archivePath = archivePath.Substring (archiveAssembliesPrefix.Length);
- }
-
- if (!String.IsNullOrEmpty (assembly.Abi)) {
- string abiPath = $"{assembly.Abi}/";
- if (archivePath.StartsWith (abiPath, StringComparison.Ordinal)) {
- archivePath = archivePath.Substring (abiPath.Length);
- }
- }
-
- if (!String.IsNullOrEmpty (archivePath)) {
- if (archivePath.EndsWith ("/", StringComparison.Ordinal)) {
- assemblyName = $"{archivePath}{assemblyName}";
- } else {
- assemblyName = $"{archivePath}/{assemblyName}";
- }
- }
-
- AssemblyStoreIndexEntry entry = WriteAssembly (writer, assembly, assemblyName, (uint)localAssemblies.Count);
- if (addToGlobalIndex) {
- globalIndex.Add (entry);
- }
- localAssemblies.Add (entry);
- }
-
- writer.Flush ();
-
- if (IsIndexStore) {
- return;
- }
-
- writer.Seek (0, SeekOrigin.Begin);
- WriteBlobHeader (writer, (uint)localAssemblies.Count);
- WriteAssemblyDescriptors (writer, localAssemblies);
- }
-
- uint CalculateOffsetFixup (uint localAssemblyCount, uint extraOffset = 0)
- {
- return (BlobBundledAssemblyNativeStructSize * (uint)localAssemblyCount) + extraOffset;
- }
-
- void WriteBlobHeader (BinaryWriter writer, uint localEntryCount, uint globalEntryCount = 0)
- {
- // Header, must be identical to the BundledAssemblyBlobHeader structure in src/monodroid/jni/xamarin-app.hh
- writer.Write (BlobMagic); // magic
- writer.Write (BlobVersion); // version
- writer.Write (localEntryCount); // local_entry_count
- writer.Write (globalEntryCount); // global_entry_count
- writer.Write ((uint)ID); // blob_id
- }
-
- void WriteAssemblyDescriptors (BinaryWriter writer, List assemblies, uint offsetFixup = 0)
- {
- // Each assembly must be identical to the BlobBundledAssembly structure in src/monodroid/jni/xamarin-app.hh
-
- foreach (AssemblyStoreIndexEntry assembly in assemblies) {
- AdjustOffsets (assembly, offsetFixup);
-
- writer.Write (assembly.DataOffset);
- writer.Write (assembly.DataSize);
-
- writer.Write (assembly.DebugDataOffset);
- writer.Write (assembly.DebugDataSize);
-
- writer.Write (assembly.ConfigDataOffset);
- writer.Write (assembly.ConfigDataSize);
- }
- }
-
- void AdjustOffsets (AssemblyStoreIndexEntry assembly, uint offsetFixup)
- {
- if (offsetFixup == 0) {
- return;
- }
-
- assembly.DataOffset += offsetFixup;
-
- if (assembly.DebugDataOffset > 0) {
- assembly.DebugDataOffset += offsetFixup;
- }
-
- if (assembly.ConfigDataOffset > 0) {
- assembly.ConfigDataOffset += offsetFixup;
- }
- }
-
- AssemblyStoreIndexEntry WriteAssembly (BinaryWriter writer, AssemblyStoreAssemblyInfo assembly, string assemblyName, uint localBlobIndex)
- {
- uint offset;
- uint size;
-
- (offset, size) = WriteFile (assembly.FilesystemAssemblyPath, true);
-
- // NOTE: globalAssemblIndex++ is not thread safe but it **must** increase monotonically (see also ArchAssemblyStore.Generate for a special case)
- var ret = new AssemblyStoreIndexEntry (assemblyName, ID, GlobalIndexCounter.Increment (), localBlobIndex) {
- DataOffset = offset,
- DataSize = size,
- };
-
- (offset, size) = WriteFile (assembly.DebugInfoPath, required: false);
- if (offset != 0 && size != 0) {
- ret.DebugDataOffset = offset;
- ret.DebugDataSize = size;
- }
-
- // Config files must end with \0 (nul)
- (offset, size) = WriteFile (assembly.ConfigPath, required: false, appendNul: true);
- if (offset != 0 && size != 0) {
- ret.ConfigDataOffset = offset;
- ret.ConfigDataSize = size;
- }
-
- return ret;
-
- (uint offset, uint size) WriteFile (string filePath, bool required, bool appendNul = false)
- {
- if (!File.Exists (filePath)) {
- if (required) {
- throw new InvalidOperationException ($"Required file '{filePath}' not found");
- }
-
- return (0, 0);
- }
-
- var fi = new FileInfo (filePath);
- if (fi.Length == 0) {
- return (0, 0);
- }
-
- if (fi.Length > UInt32.MaxValue || writer.BaseStream.Position + fi.Length > UInt32.MaxValue) {
- throw new InvalidOperationException ($"Writing assembly '{filePath}' to assembly blob would exceed the maximum allowed data size.");
- }
-
- uint offset = (uint)writer.BaseStream.Position;
- using (var fs = File.Open (filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
- fs.CopyTo (writer.BaseStream);
- }
-
- uint length = (uint)fi.Length;
- if (appendNul) {
- length++;
- writer.Write ((byte)0);
- }
-
- return (offset, length);
- }
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs
deleted file mode 100644
index c5c166fb787..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.IO;
-
-namespace Xamarin.Android.Tasks
-{
- class AssemblyStoreAssemblyInfo
- {
- public string FilesystemAssemblyPath { get; }
- public string ArchiveAssemblyPath { get; }
- public string DebugInfoPath { get; private set; }
- public string ConfigPath { get; private set; }
- public string Abi { get; }
-
- public AssemblyStoreAssemblyInfo (string filesystemAssemblyPath, string archiveAssemblyPath, string abi)
- {
- if (String.IsNullOrEmpty (filesystemAssemblyPath)) {
- throw new ArgumentException ("must not be null or empty", nameof (filesystemAssemblyPath));
- }
-
- if (String.IsNullOrEmpty (archiveAssemblyPath)) {
- throw new ArgumentException ("must not be null or empty", nameof (archiveAssemblyPath));
- }
-
- FilesystemAssemblyPath = filesystemAssemblyPath;
- ArchiveAssemblyPath = archiveAssemblyPath;
- Abi = abi;
- }
-
- public void SetDebugInfoPath (string path)
- {
- DebugInfoPath = GetExistingPath (path);
- }
-
- public void SetConfigPath (string path)
- {
- ConfigPath = GetExistingPath (path);
- }
-
- string GetExistingPath (string path)
- {
- if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
- return String.Empty;
- }
-
- return path;
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs
deleted file mode 100644
index d60af903bc9..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
-
-namespace Xamarin.Android.Tasks
-{
- class AssemblyStoreGenerator
- {
- sealed class Store
- {
- public AssemblyStore Common;
- public AssemblyStore Arch;
- }
-
- readonly string archiveAssembliesPrefix;
- readonly TaskLoggingHelper log;
-
- // NOTE: when/if we have parallel BuildApk these should become concurrent collections
- readonly Dictionary stores = new Dictionary (StringComparer.Ordinal);
-
- AssemblyStore indexStore;
-
- // IDs must be counted per AssemblyStoreGenerator instance because it's possible that a single build will create more than one instance of the class and each time
- // the stores must be assigned IDs starting from 0, or there will be errors due to "missing" index store
- readonly Dictionary apkIds = new Dictionary (StringComparer.Ordinal);
-
- // Global assembly index must be restarted from 0 for the same reasons as apkIds above and at the same time it must be unique for each assembly added to **any**
- // assembly store, thus we need to keep the state here
- AssemblyStoreGlobalIndex globalIndexCounter = new AssemblyStoreGlobalIndex ();
-
- public AssemblyStoreGenerator (string archiveAssembliesPrefix, TaskLoggingHelper log)
- {
- if (String.IsNullOrEmpty (archiveAssembliesPrefix)) {
- throw new ArgumentException ("must not be null or empty", nameof (archiveAssembliesPrefix));
- }
-
- this.archiveAssembliesPrefix = archiveAssembliesPrefix;
- this.log = log;
- }
-
- public void Add (string apkName, AssemblyStoreAssemblyInfo storeAssembly)
- {
- if (String.IsNullOrEmpty (apkName)) {
- throw new ArgumentException ("must not be null or empty", nameof (apkName));
- }
-
- Store store;
- if (!stores.ContainsKey (apkName)) {
- store = new Store {
- Common = new CommonAssemblyStore (apkName, archiveAssembliesPrefix, log, GetNextStoreID (apkName), globalIndexCounter),
- Arch = new ArchAssemblyStore (apkName, archiveAssembliesPrefix, log, GetNextStoreID (apkName), globalIndexCounter)
- };
-
- stores.Add (apkName, store);
- SetIndexStore (store.Common);
- SetIndexStore (store.Arch);
- }
-
- store = stores[apkName];
- if (String.IsNullOrEmpty (storeAssembly.Abi)) {
- store.Common.Add (storeAssembly);
- } else {
- store.Arch.Add (storeAssembly);
- }
-
- void SetIndexStore (AssemblyStore b)
- {
- if (!b.IsIndexStore) {
- return;
- }
-
- if (indexStore != null) {
- throw new InvalidOperationException ("Index store already set!");
- }
-
- indexStore = b;
- }
- }
-
- uint GetNextStoreID (string apkName)
- {
- // NOTE: NOT thread safe, if we ever have parallel runs of BuildApk this operation must either be atomic or protected with a lock
- if (!apkIds.ContainsKey (apkName)) {
- apkIds.Add (apkName, 0);
- }
- return apkIds[apkName]++;
- }
-
- public Dictionary> Generate (string outputDirectory)
- {
- if (stores.Count == 0) {
- return null;
- }
-
- if (indexStore == null) {
- throw new InvalidOperationException ("Index store not found");
- }
-
- var globalIndex = new List ();
- var ret = new Dictionary> (StringComparer.Ordinal);
- string indexStoreApkName = null;
- foreach (var kvp in stores) {
- string apkName = kvp.Key;
- Store store = kvp.Value;
-
- if (!ret.ContainsKey (apkName)) {
- ret.Add (apkName, new List ());
- }
-
- if (store.Common == indexStore || store.Arch == indexStore) {
- indexStoreApkName = apkName;
- }
-
- GenerateStore (store.Common, apkName);
- GenerateStore (store.Arch, apkName);
- }
-
- string manifestPath = indexStore.WriteIndex (globalIndex);
- ret[indexStoreApkName].Add (manifestPath);
-
- return ret;
-
- void GenerateStore (AssemblyStore store, string apkName)
- {
- store.Generate (outputDirectory, globalIndex, ret[apkName]);
- }
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGlobalIndex.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGlobalIndex.cs
deleted file mode 100644
index 6ce93f11f9d..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGlobalIndex.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace Xamarin.Android.Tasks
-{
- // This class may seem weird, but it's designed with the specific needs of AssemblyStore instances in mind and also prepared for thread-safe use in the future, should the
- // need arise
- sealed class AssemblyStoreGlobalIndex
- {
- uint value = 0;
-
- public uint Value => value;
-
- ///
- /// Increments the counter and returns its previous value
- ///
- public uint Increment ()
- {
- uint ret = value++;
- return ret;
- }
-
- public void Subtract (uint count)
- {
- if (value < count) {
- return;
- }
-
- value -= count;
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreIndexEntry.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreIndexEntry.cs
deleted file mode 100644
index 51d2bd8ca77..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreIndexEntry.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.IO.Hashing;
-using System.Text;
-
-namespace Xamarin.Android.Tasks
-{
- class AssemblyStoreIndexEntry
- {
- public string Name { get; }
- public uint StoreID { get; }
- public uint MappingIndex { get; }
- public uint LocalBlobIndex { get; }
-
- // Hash values must have the same type as they are inside a union in the native code
- public ulong NameHash64 { get; }
- public ulong NameHash32 { get; }
-
- public uint DataOffset { get; set; }
- public uint DataSize { get; set; }
-
- public uint DebugDataOffset { get; set; }
- public uint DebugDataSize { get; set; }
-
- public uint ConfigDataOffset { get; set; }
- public uint ConfigDataSize { get; set; }
-
- public AssemblyStoreIndexEntry (string name, uint blobID, uint mappingIndex, uint localBlobIndex)
- {
- if (String.IsNullOrEmpty (name)) {
- throw new ArgumentException ("must not be null or empty", nameof (name));
- }
-
- Name = name;
- StoreID = blobID;
- MappingIndex = mappingIndex;
- LocalBlobIndex = localBlobIndex;
-
- byte[] nameBytes = Encoding.UTF8.GetBytes (name);
- NameHash32 = XxHash32.HashToUInt32 (nameBytes);
- NameHash64 = XxHash64.HashToUInt64 (nameBytes);
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CommonAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CommonAssemblyStore.cs
deleted file mode 100644
index 9709a200e5a..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/CommonAssemblyStore.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
-
-namespace Xamarin.Android.Tasks
-{
- class CommonAssemblyStore : AssemblyStore
- {
- readonly List assemblies;
-
- public CommonAssemblyStore (string apkName, string archiveAssembliesPrefix, TaskLoggingHelper log, uint id, AssemblyStoreGlobalIndex globalIndexCounter)
- : base (apkName, archiveAssembliesPrefix, log, id, globalIndexCounter)
- {
- assemblies = new List ();
- }
-
- public override void Add (AssemblyStoreAssemblyInfo blobAssembly)
- {
- if (!String.IsNullOrEmpty (blobAssembly.Abi)) {
- throw new InvalidOperationException ($"Architecture-specific assembly cannot be added to an architecture-agnostic blob ({blobAssembly.FilesystemAssemblyPath})");
- }
-
- assemblies.Add (blobAssembly);
- }
-
- public override void Generate (string outputDirectory, List globalIndex, List blobPaths)
- {
- if (assemblies.Count == 0) {
- return;
- }
-
- Generate (Path.Combine (outputDirectory, $"{ApkName}_{BlobPrefix}{BlobExtension}"), assemblies, globalIndex, blobPaths);
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs
deleted file mode 100644
index e0b3740a453..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssembliesNativeAssemblyGenerator.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-using Xamarin.Android.Tasks.LLVMIR;
-
-namespace Xamarin.Android.Tasks
-{
- partial class CompressedAssembliesNativeAssemblyGenerator : LlvmIrComposer
- {
- const string DescriptorsArraySymbolName = "compressed_assembly_descriptors";
- const string CompressedAssembliesSymbolName = "compressed_assemblies";
-
- sealed class CompressedAssemblyDescriptorContextDataProvider : NativeAssemblerStructContextDataProvider
- {
- public override string? GetPointedToSymbolName (object data, string fieldName)
- {
- if (String.Compare ("data", fieldName, StringComparison.Ordinal) != 0) {
- return null;
- }
-
- var descriptor = EnsureType (data);
- return descriptor.BufferSymbolName;
- }
- }
-
- // Order of fields and their type must correspond *exactly* to that in
- // src/monodroid/jni/xamarin-app.hh CompressedAssemblyDescriptor structure
- [NativeAssemblerStructContextDataProvider (typeof (CompressedAssemblyDescriptorContextDataProvider))]
- sealed class CompressedAssemblyDescriptor
- {
- [NativeAssembler (Ignore = true)]
- public string BufferSymbolName;
-
- public uint uncompressed_file_size;
- public bool loaded;
-
- [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")]
- public byte data;
- };
-
- sealed class CompressedAssembliesContextDataProvider : NativeAssemblerStructContextDataProvider
- {
- public override ulong GetBufferSize (object data, string fieldName)
- {
- if (String.Compare ("descriptors", fieldName, StringComparison.Ordinal) != 0) {
- return 0;
- }
-
- var cas = EnsureType (data);
- return cas.count;
- }
- }
-
- // Order of fields and their type must correspond *exactly* to that in
- // src/monodroid/jni/xamarin-app.hh CompressedAssemblies structure
- [NativeAssemblerStructContextDataProvider (typeof (CompressedAssembliesContextDataProvider))]
- sealed class CompressedAssemblies
- {
- public uint count;
-
- [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = DescriptorsArraySymbolName)]
- public CompressedAssemblyDescriptor descriptors;
- };
-
- IDictionary assemblies;
- StructureInfo compressedAssemblyDescriptorStructureInfo;
- StructureInfo compressedAssembliesStructureInfo;
-
- public CompressedAssembliesNativeAssemblyGenerator (IDictionary assemblies)
- {
- this.assemblies = assemblies;
- }
-
- void InitCompressedAssemblies (out List>? compressedAssemblyDescriptors,
- out StructureInstance? compressedAssemblies,
- out List? buffers)
- {
- if (assemblies == null || assemblies.Count == 0) {
- compressedAssemblyDescriptors = null;
- compressedAssemblies = null;
- buffers = null;
- return;
- }
-
- ulong counter = 0;
- compressedAssemblyDescriptors = new List> (assemblies.Count);
- buffers = new List (assemblies.Count);
- foreach (var kvp in assemblies) {
- string assemblyName = kvp.Key;
- CompressedAssemblyInfo info = kvp.Value;
-
- string bufferName = $"__compressedAssemblyData_{counter++}";
- var descriptor = new CompressedAssemblyDescriptor {
- BufferSymbolName = bufferName,
- uncompressed_file_size = info.FileSize,
- loaded = false,
- data = 0
- };
-
- var bufferVar = new LlvmIrGlobalVariable (typeof(List), bufferName, LlvmIrVariableOptions.LocalWritable) {
- ZeroInitializeArray = true,
- ArrayItemCount = descriptor.uncompressed_file_size,
- };
- buffers.Add (bufferVar);
-
- compressedAssemblyDescriptors.Add (new StructureInstance (compressedAssemblyDescriptorStructureInfo, descriptor));
- }
-
- compressedAssemblies = new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies { count = (uint)assemblies.Count });
- }
-
- protected override void Construct (LlvmIrModule module)
- {
- MapStructures (module);
-
- List>? compressedAssemblyDescriptors;
- StructureInstance? compressedAssemblies;
- List? buffers;
-
- InitCompressedAssemblies (out compressedAssemblyDescriptors, out compressedAssemblies, out buffers);
-
- if (compressedAssemblyDescriptors == null) {
- module.AddGlobalVariable (
- typeof(StructureInstance),
- CompressedAssembliesSymbolName,
- new StructureInstance (compressedAssembliesStructureInfo, new CompressedAssemblies ()) { IsZeroInitialized = true },
- LlvmIrVariableOptions.GlobalWritable
- );
- return;
- }
-
- module.AddGlobalVariable (CompressedAssembliesSymbolName, compressedAssemblies, LlvmIrVariableOptions.GlobalWritable);
- module.AddGlobalVariable (DescriptorsArraySymbolName, compressedAssemblyDescriptors, LlvmIrVariableOptions.LocalWritable);
-
- module.Add (new LlvmIrGroupDelimiterVariable ());
- module.Add (buffers);
- module.Add (new LlvmIrGroupDelimiterVariable ());
- }
-
- void MapStructures (LlvmIrModule module)
- {
- compressedAssemblyDescriptorStructureInfo = module.MapStructure ();
- compressedAssembliesStructureInfo = module.MapStructure ();
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssemblyInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssemblyInfo.cs
deleted file mode 100644
index b3f775a96b7..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Utilities/CompressedAssemblyInfo.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.IO;
-using Microsoft.Build.Framework;
-
-namespace Xamarin.Android.Tasks
-{
- class CompressedAssemblyInfo
- {
- const string CompressedAssembliesInfoKey = "__CompressedAssembliesInfo";
-
- public uint FileSize { get; }
- public uint DescriptorIndex { get; set; }
-
- public CompressedAssemblyInfo (uint fileSize)
- {
- FileSize = fileSize;
- DescriptorIndex = 0;
- }
-
- public static string GetKey (string projectFullPath)
- {
- if (String.IsNullOrEmpty (projectFullPath))
- throw new ArgumentException ("must be a non-empty string", nameof (projectFullPath));
-
- return $"{CompressedAssembliesInfoKey}:{projectFullPath}";
- }
-
- public static string GetDictionaryKey (ITaskItem assembly)
- {
- // Prefer %(DestinationSubPath) if set
- var path = assembly.GetMetadata ("DestinationSubPath");
- if (!string.IsNullOrEmpty (path)) {
- return path;
- }
- // MSBuild sometimes only sets %(DestinationSubDirectory)
- var subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
- return Path.Combine (subDirectory, Path.GetFileName (assembly.ItemSpec));
- }
- }
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/DSOAssemblyInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/DSOAssemblyInfo.cs
new file mode 100644
index 00000000000..8563bc81d6c
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/DSOAssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System;
+
+namespace Xamarin.Android.Tasks;
+
+class DSOAssemblyInfo
+{
+ ///
+ /// Size of the loadable assembly data (after decompression, if compression is enabled).
+ ///
+ public uint DataSize { get; }
+
+ ///
+ /// Size of the compressed assembly data or `0` if assembly is uncompressed.
+ ///
+ public uint CompressedDataSize { get; }
+
+ ///
+ /// The file data comes from, either the original assembly or its compressed copy
+ ///
+ public string InputFile { get; }
+
+ ///
+ /// Name of the assembly, including culture prefix if it's a satellite assembly. Must include the extension.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Indicates whether assembly data is stored in a standalone shared library.
+ ///
+ public bool IsStandalone { get; }
+
+ public string? StandaloneDSOName { get; }
+ public uint? AssemblyLoadInfoIndex { get; set; }
+ public ulong? AssemblyDataSymbolOffset { get; set; }
+
+ ///
+ /// is the original assembly name, including culture prefix (e.g. `en_US/`) if it is a
+ /// satellite assembly. should be the full path to the input file.
+ /// gives the original file size, while specifies
+ /// data size after compression, or `0` if file isn't compressed.
+ ///
+ public DSOAssemblyInfo (string name, string inputFile, uint dataSize, uint compressedDataSize, bool isStandalone = false, string? dsoName = null)
+ {
+ if (isStandalone && String.IsNullOrEmpty (dsoName)) {
+ throw new ArgumentException ("must not be null or empty for standalone assembly", nameof (dsoName));
+ }
+
+ Name = name;
+ InputFile = inputFile;
+ DataSize = dataSize;
+ CompressedDataSize = compressedDataSize;
+ IsStandalone = isStandalone;
+ StandaloneDSOName = isStandalone ? dsoName : null;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/DSOMetadata.cs b/src/Xamarin.Android.Build.Tasks/Utilities/DSOMetadata.cs
new file mode 100644
index 00000000000..129b7ebc60f
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/DSOMetadata.cs
@@ -0,0 +1,16 @@
+namespace Xamarin.Android.Tasks;
+
+public static class DSOMetadata
+{
+ public const string Abi = "Abi";
+ public const string AndroidSkipCompression = "AndroidSkipCompression";
+ public const string AssemblyLoadInfoIndex = "AssemblyLoadInfoIndex";
+ public const string Compressed = "Compressed";
+ public const string DataSize = "DataSize";
+ public const string DataSymbolOffset = "DataSymbolOffset";
+ public const string InputAssemblyPath = "InputAssemblyPath";
+ public const string OriginalAssemblyPath = "OriginalAssemblyPath";
+ public const string SatelliteAssemblyCulture = "SatelliteAssemblyCulture";
+ public const string SourceFileBaseName = "SourceFileBaseName";
+ public const string UncompressedDataSize = "UncompressedDataSize";
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
index a7a00b4af60..c40d917edd1 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
@@ -15,6 +15,40 @@ namespace Xamarin.Android.Tasks
{
static class ELFHelper
{
+ public static (ulong? offset, ulong? size) GetExportedSymbolOffsetAndSize (TaskLoggingHelper log, string dsoPath, string symbolName)
+ {
+ if (String.IsNullOrEmpty (dsoPath) || !File.Exists (dsoPath)) {
+ throw new ArgumentException ("must not be null or empty and the file must exist", nameof (dsoPath));
+ }
+
+ IELF elf = ELFReader.Load (dsoPath);
+ ISymbolTable? symtab = GetSymbolTable (elf, ".dynsym");
+ if (symtab == null) {
+ log.LogDebugMessage ($"Shared library '{dsoPath}' does not export any symbols");
+ return (null, null);
+ }
+
+ ISymbolEntry? symbol = null;
+ foreach (var entry in symtab.Entries) {
+ if (entry.Type == ELFSymbolType.Object && String.Compare (symbolName, entry.Name, StringComparison.Ordinal) == 0) {
+ symbol = entry;
+ break;
+ }
+ }
+
+ if (symbol == null) {
+ log.LogDebugMessage ($"Shared library '{dsoPath}' does not export symbol '{symbolName}'");
+ return (null, null);
+ }
+
+ if (elf.Class == Class.Bit64) {
+ var sym64 = (SymbolEntry)symbol;
+ return (sym64.Value, sym64.Size);
+ }
+ var sym32 = (SymbolEntry)symbol;
+ return ((ulong)sym32.Value, (ulong)sym32.Size);
+ }
+
public static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path)
{
if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/IInputAssemblySet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/IInputAssemblySet.cs
new file mode 100644
index 00000000000..9c4a7c16cab
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/IInputAssemblySet.cs
@@ -0,0 +1,17 @@
+using System;
+using System.IO;
+
+using Microsoft.Build.Framework;
+
+namespace Xamarin.Android.Tasks;
+
+abstract class InputAssemblySet
+{
+ public abstract void AddJavaTypeAssembly (ITaskItem assemblyItem);
+ public abstract void AddUserAssembly (ITaskItem assemblyItem);
+ public abstract bool IsUserAssembly (string name);
+
+ protected static readonly StringComparer AssemblyNameStringComparer = StringComparer.OrdinalIgnoreCase;
+
+ protected string GetUserAssemblyKey (ITaskItem assemblyItem) => Path.GetFileNameWithoutExtension (assemblyItem.ItemSpec);
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs
index 8db94269f32..2baa5c0fcc6 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs
@@ -32,11 +32,19 @@ public void Generate (LlvmIrModule module, AndroidTargetArch arch, StreamWriter
LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, fileName);
generator.Generate (output, module);
output.Flush ();
+
+ CleanupAfterGeneration (arch);
}
- public static ulong GetXxHash (string str, bool is64Bit)
+ protected virtual void CleanupAfterGeneration (AndroidTargetArch arch)
+ {}
+
+ public static byte[] StringToBytes (string str) => Encoding.UTF8.GetBytes (str);
+
+ public static ulong GetXxHash (string str, bool is64Bit) => GetXxHash (StringToBytes (str), is64Bit);
+
+ public static ulong GetXxHash (byte[] stringBytes, bool is64Bit)
{
- byte[] stringBytes = Encoding.UTF8.GetBytes (str);
if (is64Bit) {
return XxHash64.HashToUInt64 (stringBytes);
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs
index bb81b03db0e..c3102f3a5c3 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs
@@ -29,6 +29,7 @@ sealed class GeneratorWriteContext
public readonly LlvmIrMetadataManager MetadataManager;
public string CurrentIndent { get; private set; } = String.Empty;
public bool InVariableGroup { get; set; }
+ public LlvmIrVariableNumberFormat NumberFormat { get; set; } = LlvmIrVariableNumberFormat.Default;
public GeneratorWriteContext (TextWriter writer, LlvmIrModule module, LlvmIrModuleTarget target, LlvmIrMetadataManager metadataManager)
{
@@ -80,31 +81,56 @@ sealed class BasicType
public readonly string Name;
public readonly ulong Size;
public readonly bool IsNumeric;
+ public readonly bool IsUnsigned;
+ public readonly bool PreferHex;
+ public readonly string HexFormat;
- public BasicType (string name, ulong size, bool isNumeric = true)
+ public BasicType (string name, ulong size, bool isNumeric = true, bool isUnsigned = false, bool? preferHex = null)
{
Name = name;
Size = size;
IsNumeric = isNumeric;
+ IsUnsigned = isUnsigned;
+
+ // If hex preference isn't specified, we determine whether the type wants to be represented in
+ // the hexadecimal notation based on signedness. Unsigned types will be represented in hexadecimal,
+ // but signed types will remain decimal, as it's easier for humans to see the actual value of the
+ // variable, given this note from LLVM IR manual:
+ //
+ // Note that hexadecimal integers are sign extended from the number of active bits, i.e. the bit width minus the number of leading zeros. So ‘s0x0001’ of type ‘i16’ will be -1, not 1.
+ //
+ // See: https://llvm.org/docs/LangRef.html#simple-constants
+ //
+ if (preferHex.HasValue) {
+ PreferHex = preferHex.Value;
+ } else {
+ PreferHex = isUnsigned;
+ }
+ if (!PreferHex) {
+ HexFormat = String.Empty;
+ return;
+ }
+
+ HexFormat = $"x{size * 2}";
}
}
public const string IRPointerType = "ptr";
static readonly Dictionary basicTypeMap = new Dictionary {
- { typeof (bool), new ("i8", 1, isNumeric: false) },
- { typeof (byte), new ("i8", 1) },
- { typeof (char), new ("i16", 2) },
+ { typeof (bool), new ("i1", 1, isNumeric: false, isUnsigned: true, preferHex: false) },
+ { typeof (byte), new ("i8", 1, isUnsigned: true) },
+ { typeof (char), new ("i16", 2, isUnsigned: true, preferHex: false) },
{ typeof (sbyte), new ("i8", 1) },
{ typeof (short), new ("i16", 2) },
- { typeof (ushort), new ("i16", 2) },
+ { typeof (ushort), new ("i16", 2, isUnsigned: true) },
{ typeof (int), new ("i32", 4) },
- { typeof (uint), new ("i32", 4) },
+ { typeof (uint), new ("i32", 4, isUnsigned: true) },
{ typeof (long), new ("i64", 8) },
- { typeof (ulong), new ("i64", 8) },
+ { typeof (ulong), new ("i64", 8, isUnsigned: true) },
{ typeof (float), new ("float", 4) },
{ typeof (double), new ("double", 8) },
- { typeof (void), new ("void", 0, isNumeric: false) },
+ { typeof (void), new ("void", 0, isNumeric: false, preferHex: false) },
};
public string FilePath { get; }
@@ -191,6 +217,8 @@ void WriteGlobalVariables (GeneratorWriteContext context)
}
foreach (LlvmIrGlobalVariable gv in context.Module.GlobalVariables) {
+ context.NumberFormat = gv.NumberFormat;
+
if (gv is LlvmIrGroupDelimiterVariable groupDelimiter) {
if (!context.InVariableGroup && !String.IsNullOrEmpty (groupDelimiter.Comment)) {
context.Output.WriteLine ();
@@ -240,8 +268,10 @@ void WriteGlobalVariable (GeneratorWriteContext context, LlvmIrGlobalVariable va
context.Output.Write (", align ");
ulong alignment;
- if (typeInfo.IsAggregate) {
- ulong count = GetAggregateValueElementCount (variable);
+ if (variable.Alignment.HasValue) {
+ alignment = variable.Alignment.Value;
+ } else if (typeInfo.IsAggregate) {
+ ulong count = GetAggregateValueElementCount (context, variable);
alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, count * typeInfo.Size);
} else if (typeInfo.IsStructure) {
alignment = (ulong)target.GetAggregateAlignment ((int)typeInfo.MaxFieldAlignment, typeInfo.Size);
@@ -290,9 +320,9 @@ void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable,
WriteValue (context, valueType, variable);
}
- ulong GetAggregateValueElementCount (LlvmIrVariable variable) => GetAggregateValueElementCount (variable.Type, variable.Value, variable as LlvmIrGlobalVariable);
+ ulong GetAggregateValueElementCount (GeneratorWriteContext context, LlvmIrVariable variable) => GetAggregateValueElementCount (context, variable.Type, variable.Value, variable as LlvmIrGlobalVariable);
- ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVariable? globalVariable = null)
+ ulong GetAggregateValueElementCount (GeneratorWriteContext context, Type type, object? value, LlvmIrGlobalVariable? globalVariable = null)
{
if (!type.IsArray ()) {
throw new InvalidOperationException ($"Internal error: unknown type {type} when trying to determine aggregate type element count");
@@ -300,6 +330,9 @@ ulong GetAggregateValueElementCount (Type type, object? value, LlvmIrGlobalVaria
if (value == null) {
if (globalVariable != null) {
+ if (globalVariable.ArrayDataProvider != null) {
+ return globalVariable.ArrayDataProvider.GetTotalDataSize (context.Target);
+ }
return globalVariable.ArrayItemCount;
}
return 0;
@@ -386,9 +419,9 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv
if (type.IsArray ()) {
Type elementType = type.GetArrayElementType ();
- ulong elementCount = GetAggregateValueElementCount (type, value, globalVariable);
+ ulong elementCount = GetAggregateValueElementCount (context, type, value, globalVariable);
- WriteArrayType (context, elementType, elementCount, out typeInfo);
+ WriteArrayType (context, elementType, elementCount, globalVariable, out typeInfo);
return;
}
@@ -404,6 +437,11 @@ void WriteType (GeneratorWriteContext context, Type type, object? value, out Llv
}
void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, out LlvmTypeInfo typeInfo)
+ {
+ WriteArrayType (context, elementType, elementCount, variable: null, out typeInfo);
+ }
+
+ void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elementCount, LlvmIrGlobalVariable? variable, out LlvmTypeInfo typeInfo)
{
string irType;
ulong size;
@@ -420,6 +458,35 @@ void WriteArrayType (GeneratorWriteContext context, Type elementType, ulong elem
} else {
irType = GetIRType (elementType, out size, out isPointer);
maxFieldAlignment = size;
+
+ if (elementType.IsArray) {
+ if (variable == null) {
+ throw new InvalidOperationException ($"Internal error: array of arrays ({elementType}) requires variable to be defined");
+ }
+
+ // For the sake of simpler code, we currently assume that all the element arrays are of the same size, because that's the only scenario
+ // that we use at this time.
+ var value = variable.Value as ICollection;
+ if (value == null) {
+ throw new InvalidOperationException ($"Internal error: variable '{variable.Name}' of type '{variable.Type}' is required to have a value of type which implements the ICollection interface");
+ }
+
+ if (value.Count == 0) {
+ throw new InvalidOperationException ($"Internal error: variable '{variable.Name}' of type '{variable.Type}' is required to have a value which is a non-empty ICollection");
+ }
+
+ Array? firstItem = null;
+ foreach (object v in value) {
+ firstItem = (Array)v;
+ break;
+ }
+
+ if (firstItem == null) {
+ throw new InvalidOperationException ($"Internal error: variable '{variable.Name}' of type '{variable.Type}' is required to have a value which is a non-empty ICollection with non-null elements");
+ }
+
+ irType = $"[{MonoAndroidHelper.CultureInvariantToString (firstItem.Length)} x {irType}]";
+ }
}
typeInfo = new LlvmTypeInfo (
isPointer: isPointer,
@@ -449,12 +516,17 @@ ulong GetStructureMaxFieldAlignment (StructureInfo si)
void WriteValue (GeneratorWriteContext context, Type valueType, LlvmIrVariable variable)
{
+ if (variable is LlvmIrGlobalVariable globalVariable && globalVariable.ArrayDataProvider != null) {
+ WriteStreamedArrayValue (context, globalVariable, globalVariable.ArrayDataProvider);
+ return;
+ }
+
if (variable.Type.IsArray ()) {
bool zeroInitialize = false;
if (variable is LlvmIrGlobalVariable gv) {
zeroInitialize = gv.ZeroInitializeArray || variable.Value == null;
} else {
- zeroInitialize = GetAggregateValueElementCount (variable) == 0;
+ zeroInitialize = GetAggregateValueElementCount (context, variable) == 0;
}
if (zeroInitialize) {
@@ -478,6 +550,29 @@ void AssertArraySize (StructureInstance si, StructureMemberInfo smi, ulong lengt
throw new InvalidOperationException ($"Invalid array size in field '{smi.Info.Name}' of structure '{si.Info.Name}', expected {expectedLength}, found {length}");
}
+ void WriteInlineArray (GeneratorWriteContext context, byte[] bytes, bool encodeAsASCII)
+ {
+ if (encodeAsASCII) {
+ context.Output.Write ('c');
+ context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false));
+ return;
+ }
+
+ string irType = MapToIRType (typeof(byte));
+ bool first = true;
+ context.Output.Write ("[ ");
+ foreach (byte b in bytes) {
+ if (!first) {
+ context.Output.Write (", ");
+ } else {
+ first = false;
+ }
+
+ context.Output.Write ($"{irType} u0x{b:x02}");
+ }
+ context.Output.Write (" ]");
+ }
+
void WriteValue (GeneratorWriteContext context, StructureInstance structInstance, StructureMemberInfo smi, object? value)
{
if (smi.IsNativePointer) {
@@ -495,8 +590,7 @@ void WriteValue (GeneratorWriteContext context, StructureInstance structInstance
// Byte arrays are represented in the same way as strings, without the explicit NUL termination byte
AssertArraySize (structInstance, smi, length, smi.ArrayElements);
- context.Output.Write ('c');
- context.Output.Write (QuoteString (bytes, bytes.Length, out _, nullTerminated: false));
+ WriteInlineArray (context, bytes, encodeAsASCII: false);
return;
}
@@ -549,6 +643,27 @@ bool WriteNativePointerValue (GeneratorWriteContext context, StructureInstance s
return false;
}
+ string ToHex (BasicType basicTypeDesc, Type type, object? value)
+ {
+ const char prefixSigned = 's';
+ const char prefixUnsigned = 'u';
+
+ string hex;
+ if (type == typeof(byte)) {
+ hex = ((byte)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture);
+ } else if (type == typeof(ushort)) {
+ hex = ((ushort)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture);
+ } else if (type == typeof(uint)) {
+ hex = ((uint)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture);
+ } else if (type == typeof(ulong)) {
+ hex = ((ulong)value).ToString (basicTypeDesc.HexFormat, CultureInfo.InvariantCulture);
+ } else {
+ throw new NotImplementedException ($"Conversion to hexadecimal from type {type} is not implemented");
+ };
+
+ return $"{(basicTypeDesc.IsUnsigned ? prefixUnsigned : prefixSigned)}0x{hex}";
+ }
+
void WriteValue (GeneratorWriteContext context, Type type, object? value)
{
if (value is LlvmIrVariable variableRef) {
@@ -556,14 +671,26 @@ void WriteValue (GeneratorWriteContext context, Type type, object? value)
return;
}
- if (IsNumeric (type)) {
- context.Output.Write (MonoAndroidHelper.CultureInvariantToString (value));
- return;
- }
+ bool isBasic = basicTypeMap.TryGetValue (type, out BasicType basicTypeDesc);
+ if (isBasic) {
+ if (basicTypeDesc.IsNumeric) {
+ bool hex = context.NumberFormat switch {
+ LlvmIrVariableNumberFormat.Default => basicTypeDesc.PreferHex,
+ LlvmIrVariableNumberFormat.Decimal => false,
+ LlvmIrVariableNumberFormat.Hexadecimal => true,
+ _ => throw new InvalidOperationException ($"Internal error: number format {context.NumberFormat} is unsupported")
+ };
- if (type == typeof(bool)) {
- context.Output.Write ((bool)value ? '1' : '0');
- return;
+ context.Output.Write (
+ hex ? ToHex (basicTypeDesc, type, value) : MonoAndroidHelper.CultureInvariantToString (value)
+ );
+ return;
+ }
+
+ if (type == typeof(bool)) {
+ context.Output.Write ((bool)value ? "true" : "false");
+ return;
+ }
}
if (IsStructureInstance (type)) {
@@ -588,8 +715,13 @@ void WriteValue (GeneratorWriteContext context, Type type, object? value)
return;
}
- if (type.IsInlineArray ()) {
+ if (type.IsArray) {
+ if (type == typeof(byte[])) {
+ WriteInlineArray (context, (byte[])value, encodeAsASCII: true);
+ return;
+ }
+ throw new NotSupportedException ($"Internal error: array of type {type} is unsupported");
}
throw new NotSupportedException ($"Internal error: value type '{type}' is unsupported");
@@ -616,8 +748,20 @@ void WriteStructureValue (GeneratorWriteContext context, StructureInstance? inst
context.Output.Write (' ');
object? value = GetTypedMemberValue (context, info, smi, instance, smi.MemberType);
+ LlvmIrVariableNumberFormat numberFormat = smi.Info.GetNumberFormat ();
+ LlvmIrVariableNumberFormat? savedNumberFormat = null;
+
+ if (numberFormat != LlvmIrVariableNumberFormat.Default && numberFormat != context.NumberFormat) {
+ savedNumberFormat = context.NumberFormat;
+ context.NumberFormat = numberFormat;
+ }
+
WriteValue (context, instance, smi, value);
+ if (savedNumberFormat.HasValue) {
+ context.NumberFormat = savedNumberFormat.Value;
+ }
+
if (i < lastMember) {
context.Output.Write (", ");
}
@@ -628,9 +772,6 @@ void WriteStructureValue (GeneratorWriteContext context, StructureInstance? inst
sb.Append (MapManagedTypeToNative (smi));
sb.Append (' ');
sb.Append (smi.Info.Name);
- if (value != null && smi.MemberType.IsPrimitive && smi.MemberType != typeof(bool)) {
- sb.Append ($" (0x{value:x})");
- }
comment = sb.ToString ();
}
WriteCommentLine (context, comment);
@@ -641,75 +782,80 @@ void WriteStructureValue (GeneratorWriteContext context, StructureInstance? inst
context.Output.Write ('}');
}
- void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable)
+ void WriteArrayValueStart (GeneratorWriteContext context)
{
- ICollection entries;
- if (variable.Type.ImplementsInterface (typeof(IDictionary))) {
- var list = new List ();
- foreach (var kvp in (IDictionary)variable.Value) {
- list.Add (kvp.Key);
- list.Add (kvp.Value);
- }
- entries = list;
- } else {
- entries = (ICollection)variable.Value;
- }
-
- if (entries.Count == 0) {
- context.Output.Write ("zeroinitializer");
- return;
- }
-
context.Output.WriteLine ('[');
context.IncreaseIndent ();
+ }
- Type elementType = variable.Type.GetArrayElementType ();
- bool writeIndices = (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments;
- ulong counter = 0;
- string? prevItemComment = null;
- uint stride;
+ void WriteArrayValueEnd (GeneratorWriteContext context)
+ {
+ context.DecreaseIndent ();
+ context.Output.Write (']');
+ }
+ uint GetArrayStride (LlvmIrVariable variable)
+ {
if ((variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayFormatInRows) == LlvmIrVariableWriteOptions.ArrayFormatInRows) {
- stride = variable.ArrayStride > 0 ? variable.ArrayStride : 1;
- } else {
- stride = 1;
+ return variable.ArrayStride > 0 ? variable.ArrayStride : 1;
}
+ return 1;
+ }
+
+ void WriteArrayEntries (GeneratorWriteContext context, LlvmIrVariable variable, ICollection? entries, Type elementType, uint stride, bool writeIndices, bool terminateWithComma = false)
+ {
bool first = true;
+ bool ignoreComments = stride > 1;
+ string? prevItemComment = null;
+ ulong counter = 0;
- // TODO: implement output in rows
- foreach (object entry in entries) {
- if (!first) {
- context.Output.Write (',');
- WritePrevItemCommentOrNewline ();
- } else {
- first = false;
- }
+ if (entries != null) {
+ foreach (object entry in entries) {
+ if (!first) {
+ context.Output.Write (',');
+ if (stride == 1 || counter % stride == 0) {
+ WritePrevItemCommentOrNewline ();
+ context.Output.Write (context.CurrentIndent);
+ } else {
+ context.Output.Write (' ');
+ }
+ } else {
+ context.Output.Write (context.CurrentIndent);
+ first = false;
+ }
- prevItemComment = null;
- if (variable.GetArrayItemCommentCallback != null) {
- prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState);
- }
+ if (!ignoreComments) {
+ prevItemComment = null;
+ if (variable.GetArrayItemCommentCallback != null) {
+ prevItemComment = variable.GetArrayItemCommentCallback (variable, target, counter, entry, variable.GetArrayItemCommentCallbackCallerState);
+ }
- if (writeIndices && String.IsNullOrEmpty (prevItemComment)) {
- prevItemComment = $" {counter}";
- }
+ if (writeIndices && String.IsNullOrEmpty (prevItemComment)) {
+ prevItemComment = $" {counter}";
+ }
+ }
- counter++;
- context.Output.Write (context.CurrentIndent);
- WriteType (context, elementType, entry, out _);
+ counter++;
+ WriteType (context, elementType, entry, out _);
- context.Output.Write (' ');
- WriteValue (context, elementType, entry);
+ context.Output.Write (' ');
+ WriteValue (context, elementType, entry);
+ }
}
- WritePrevItemCommentOrNewline ();
- context.DecreaseIndent ();
- context.Output.Write (']');
+ if (terminateWithComma) {
+ if (!ignoreComments) {
+ context.Output.WriteLine (); // must put comma outside the comment
+ context.Output.Write (context.CurrentIndent);
+ }
+ context.Output.Write (',');
+ }
+ WritePrevItemCommentOrNewline ();
void WritePrevItemCommentOrNewline ()
{
- if (!String.IsNullOrEmpty (prevItemComment)) {
+ if (!ignoreComments && !String.IsNullOrEmpty (prevItemComment)) {
context.Output.Write (' ');
WriteCommentLine (context, prevItemComment);
} else {
@@ -718,6 +864,97 @@ void WritePrevItemCommentOrNewline ()
}
}
+ bool ArrayWantsToWriteIndices (LlvmIrVariable variable) => (variable.WriteOptions & LlvmIrVariableWriteOptions.ArrayWriteIndexComments) == LlvmIrVariableWriteOptions.ArrayWriteIndexComments;
+
+ void WriteStreamedArrayValue (GeneratorWriteContext context, LlvmIrGlobalVariable variable, LlvmIrStreamedArrayDataProvider dataProvider)
+ {
+ ulong dataSizeSoFar = 0;
+ ulong totalDataSize = dataProvider.GetTotalDataSize (context.Target);
+ bool first = true;
+
+ WriteArrayValueStart (context);
+ while (true) {
+ (LlvmIrStreamedArrayDataProviderState state, ICollection? data) = dataProvider.GetData (context.Target);
+ if (state == LlvmIrStreamedArrayDataProviderState.NextSectionNoData) {
+ continue;
+ }
+
+ bool mustHaveData = state != LlvmIrStreamedArrayDataProviderState.LastSectionNoData;
+ if (mustHaveData) {
+ if (data.Count == 0) {
+ throw new InvalidOperationException ("Data must be provided for streamed arrays");
+ }
+
+ dataSizeSoFar += (ulong)data.Count;
+ if (dataSizeSoFar > totalDataSize) {
+ throw new InvalidOperationException ($"Data provider {dataProvider} is trying to write more data than declared");
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ context.Output.WriteLine ();
+ }
+ string comment = dataProvider.GetSectionStartComment (context.Target);
+
+ if (comment.Length > 0) {
+ context.Output.Write (context.CurrentIndent);
+ WriteCommentLine (context, comment);
+ }
+ }
+
+ bool lastSection = state == LlvmIrStreamedArrayDataProviderState.LastSection || state == LlvmIrStreamedArrayDataProviderState.LastSectionNoData;
+ WriteArrayEntries (
+ context,
+ variable,
+ data,
+ dataProvider.ArrayElementType,
+ GetArrayStride (variable),
+ writeIndices: false,
+ terminateWithComma: !lastSection
+ );
+
+ if (lastSection) {
+ break;
+ }
+
+ }
+ WriteArrayValueEnd (context);
+ }
+
+ void WriteArrayValue (GeneratorWriteContext context, LlvmIrVariable variable)
+ {
+ ICollection entries;
+ if (variable.Type.ImplementsInterface (typeof(IDictionary))) {
+ var list = new List ();
+ foreach (var kvp in (IDictionary)variable.Value) {
+ list.Add (kvp.Key);
+ list.Add (kvp.Value);
+ }
+ entries = list;
+ } else {
+ entries = (ICollection)variable.Value;
+ }
+
+ if (entries.Count == 0) {
+ context.Output.Write ("zeroinitializer");
+ return;
+ }
+
+ WriteArrayValueStart (context);
+
+ WriteArrayEntries (
+ context,
+ variable,
+ entries,
+ variable.Type.GetArrayElementType (),
+ GetArrayStride (variable),
+ writeIndices: ArrayWantsToWriteIndices (variable)
+ );
+
+ WriteArrayValueEnd (context);
+ }
+
void WriteLinkage (GeneratorWriteContext context, LlvmIrLinkage linkage)
{
if (linkage == LlvmIrLinkage.Default) {
@@ -1232,8 +1469,6 @@ static string MapManagedTypeToNative (StructureMemberInfo smi)
return $"{nativeType}*";
}
- static bool IsNumeric (Type type) => basicTypeMap.TryGetValue (type, out BasicType typeDesc) && typeDesc.IsNumeric;
-
object? GetTypedMemberValue (GeneratorWriteContext context, StructureInfo info, StructureMemberInfo smi, StructureInstance instance, Type expectedType, object? defaultValue = null)
{
object? value = smi.GetValue (instance.Obj);
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs
index 7f741490ce0..49524f0bf6c 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Globalization;
namespace Xamarin.Android.Tasks.LLVMIR;
@@ -11,6 +12,13 @@ enum LlvmIrVariableWriteOptions
ArrayFormatInRows = 0x0002,
}
+enum LlvmIrVariableNumberFormat
+{
+ Default,
+ Hexadecimal,
+ Decimal,
+}
+
abstract class LlvmIrVariable : IEquatable
{
public abstract bool Global { get; }
@@ -29,6 +37,8 @@ abstract class LlvmIrVariable : IEquatable
public object? Value { get; set; }
public string? Comment { get; set; }
+ public LlvmIrVariableNumberFormat NumberFormat { get; set; } = LlvmIrVariableNumberFormat.Decimal;
+
///
/// Both global and local variables will want their names to matter in equality checks, but function
/// parameters must not take it into account, thus this property. If set to false,
@@ -146,6 +156,50 @@ public void AssignNumber (ulong n)
}
}
+enum LlvmIrStreamedArrayDataProviderState
+{
+ NextSection,
+ LastSection,
+ NextSectionNoData,
+ LastSectionNoData,
+}
+
+abstract class LlvmIrStreamedArrayDataProvider
+{
+ ///
+ /// Type of every member of the array returned by . Generator will check
+ /// every member type against this property, allowing also derived types.
+ ///
+ public Type ArrayElementType { get; }
+
+ protected LlvmIrStreamedArrayDataProvider (Type arrayElementType)
+ {
+ ArrayElementType = arrayElementType;
+ }
+
+ ///
+ /// Whenever returns the generator will call this method to obtain the new section
+ /// comment, if any, to be output before the actual data. Returning `String.Empty` prevents the comment
+ /// from being added.
+ ///
+ public virtual string GetSectionStartComment (LlvmIrModuleTarget target) => String.Empty;
+
+ ///
+ /// Provide the next chunk of data for the specified target (ABI). Implementations need to return at least one
+ /// non-empty collection of data. The returned collection **must** be exactly the size of contained data (e.g. it cannot be
+ /// a byte array rented from a byte pool, because these can be bigger than requested. When returning the last (or the only) section,
+ /// must have a value of .
+ /// Each section may be preceded by a comment, .
+ ///
+ public abstract (LlvmIrStreamedArrayDataProviderState status, ICollection data) GetData (LlvmIrModuleTarget target);
+
+ ///
+ /// Provide the total data size for the specified target (ABI). This needs to be used instead of
+ /// because a variable instance is created once and shared by all targets, while per-target data sets might have different sizes.
+ ///
+ public abstract ulong GetTotalDataSize (LlvmIrModuleTarget target);
+}
+
class LlvmIrGlobalVariable : LlvmIrVariable
{
///
@@ -162,9 +216,33 @@ class LlvmIrGlobalVariable : LlvmIrVariable
///
public virtual LlvmIrVariableOptions? Options { get; set; }
+ ///
+ /// If set to `true`, initialize the array with a shortcut zero-initializer statement. Useful when pre-allocating
+ /// space for runtime use that won't be filled in with any data at the build time.
+ ///
public bool ZeroInitializeArray { get; set; }
+
+ ///
+ /// Specify number of items in an array. Used in cases when we want to pre-allocate an array without giving it any
+ /// value, thus making it impossible for the generator to discover the number of items automatically. This is useful
+ /// when using . This property is used **only** if the variable
+ /// is `null`.
+ ///
public ulong ArrayItemCount { get; set; }
+ ///
+ /// If set, it will override any automatically calculated alignment for this variable
+ ///
+ public ulong? Alignment { get; set; }
+
+ ///
+ /// If set, the provider will be called to obtain all the data to be placed in an array variable. The total amount
+ /// of data that will be returned by the provider **must** be specified in the property,
+ /// in order for the generator to properly declare the variable. The generator will verify that the amount of data
+ /// is exactly that much and throw an exception otherwise.
+ ///
+ public LlvmIrStreamedArrayDataProvider? ArrayDataProvider { get; set; }
+
///
/// Constructs a local variable. is translated to one of the LLVM IR first class types (see
/// https://llvm.org/docs/LangRef.html#t-firstclass) only if it's an integral or floating point type. In all other cases it
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs
index 431d92b6229..713b0c5a1a6 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/MemberInfoUtilities.cs
@@ -71,5 +71,15 @@ public static bool InlineArrayNeedsPadding (this MemberInfo mi)
return attr.NeedsPadding;
}
+
+ public static LlvmIrVariableNumberFormat GetNumberFormat (this MemberInfo mi)
+ {
+ var attr = mi.GetCustomAttribute ();
+ if (attr == null) {
+ return LlvmIrVariableNumberFormat.Default;
+ }
+
+ return attr.NumberFormat;
+ }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs
index 39fc5e094bb..c239255e1ac 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeAssemblerAttribute.cs
@@ -33,6 +33,8 @@ class NativeAssemblerAttribute : Attribute
/// size to which the member must be padded is specified by
///
public bool NeedsPadding { get; set; }
+
+ public LLVMIR.LlvmIrVariableNumberFormat NumberFormat { get; set; } = LLVMIR.LlvmIrVariableNumberFormat.Default;
}
[AttributeUsage (AttributeTargets.Class, Inherited = true)]
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
index 8a1b8996794..bbf926358dd 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
@@ -255,7 +255,7 @@ void ReorderActivityAliases (TaskLoggingHelper log, XElement app)
}
}
- public IList Merge (TaskLoggingHelper log, TypeDefinitionCache cache, List subclasses, string applicationClass, bool embed, string bundledWearApplicationName, IEnumerable mergedManifestDocuments)
+ public IList Merge (TaskLoggingHelper log, TypeDefinitionCache cache, ICollection subclasses, string applicationClass, bool embed, string bundledWearApplicationName, IEnumerable mergedManifestDocuments)
{
var manifest = doc.Root;
@@ -330,8 +330,7 @@ public IList Merge (TaskLoggingHelper log, TypeDefinitionCache cache, Li
throw new InvalidOperationException (string.Format ("The targetSdkVersion ({0}) is not a valid API level", targetSdkVersion));
int targetSdkVersionValue = tryTargetSdkVersion.Value;
- foreach (JavaType jt in subclasses) {
- TypeDefinition t = jt.Type;
+ foreach (TypeDefinition t in subclasses) {
if (t.IsAbstract)
continue;
@@ -568,7 +567,7 @@ Func GetGenerator (T
return null;
}
- XElement CreateApplicationElement (XElement manifest, string applicationClass, List