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/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets
index db38390c930..e580473f335 100644
--- a/build-tools/installers/create-installers.targets
+++ b/build-tools/installers/create-installers.targets
@@ -168,12 +168,21 @@
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)ManifestOverlays\Timing.xml" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libc.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libm.so" />
+
+
+
+
+
+ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm64\libAssEmbLyBloB.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libc.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libm.so" />
+ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-arm\libAssEmbLyBloB.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libc.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libm.so" />
+ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x64\libAssEmbLyBloB.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libc.so" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libm.so" />
+ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)libstubs\android-x86\libAssEmbLyBloB.so" />
<_MSBuildTargetsSrcFiles Include="$(MSBuildTargetsSrcDir)\Xamarin.Android.AvailableItems.targets" />
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_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="@(_AssemblyDSOSource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+ %(_AssemblyDSOSource.abi)
+
+
+
+
+
+
+ <_ApplicationSharedLibrary Include="$(_AndroidApplicationSharedLibraryPath)%(_BuildTargetAbis.Identity)\libxamarin-app.so">
+ %(_BuildTargetAbis.Identity)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_CreateApplicationSharedLibrariesDependsOn>
+ _PrepareAssemblyDSOSources;
+ _GenerateAssemblyBlobDSOs;
+ _CompileApplicationNativeAssemblySources;
+ _PrepareApplicationSharedLibraryItems
+
+
+
+
+
+
+
+
+
+
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/GenerateAssemblyBlobDSO.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAssemblyBlobDSO.cs
new file mode 100644
index 00000000000..89cb396893f
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAssemblyBlobDSO.cs
@@ -0,0 +1,184 @@
+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;
+
+using BlobAssemblyInfo = AssemblyBlobDSOGenerator.BlobAssemblyInfo;
+
+public class GenerateAssemblyBlobDSO : AndroidTask
+{
+ // We need our data to start at page boundary and since all stubs are (currently) smaller than 4k, we go with precisely that value
+ const long MaximumStubSize = 4096;
+
+ public override string TaskPrefix => "GABD";
+
+ [Required]
+ public ITaskItem[] Assemblies { get; set; }
+
+ [Required]
+ public ITaskItem[] AssemblyBlobDSOs { get; set; }
+
+ [Required]
+ public bool EnableCompression { get; set; }
+
+ [Required]
+ public string CompressedAssembliesOutputDirectory { get; set; }
+
+ [Required]
+ public string SourcesOutputDirectory { 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;
+ }
+
+ void Generate ()
+ {
+ var assemblies = new Dictionary> ();
+ var abis = new HashSet (StringComparer.Ordinal);
+
+ foreach (ITaskItem assembly in Assemblies) {
+ string abi = GetRequiredMetadata (assembly, DSOMetadata.Abi);
+ AndroidTargetArch arch = MonoAndroidHelper.AbiToTargetArch (abi);
+ if (!assemblies.TryGetValue (arch, out List archAssemblies)) {
+ archAssemblies = new List ();
+ assemblies.Add (arch, archAssemblies);
+ }
+ abis.Add (abi.ToLowerInvariant ());
+
+ var info = new BlobAssemblyInfo (assembly);
+ archAssemblies.Add (info);
+
+ string configFilePath = $"{assembly.ItemSpec}.config";
+ if (File.Exists (configFilePath)) {
+ info.Config = File.ReadAllText (configFilePath);
+ }
+
+ string inputAssembly;
+ if (!ShouldSkipCompression (assembly)) {
+ // TODO: compress
+ inputAssembly = "[TODO]";
+ info.IsCompressed = true;
+ info.Size = GetFileSize (assembly.ItemSpec);
+ info.SizeInBlob = GetFileSize (inputAssembly);
+ } else {
+ inputAssembly = assembly.ItemSpec;
+ info.Size = info.SizeInBlob = GetFileSize (inputAssembly);
+
+ }
+ assembly.SetMetadata (DSOMetadata.InputAssemblyPath, inputAssembly);
+ }
+
+ foreach (ITaskItem blobDSO in AssemblyBlobDSOs) {
+ AndroidTargetArch arch = MonoAndroidHelper.AbiToTargetArch (GetRequiredMetadata (blobDSO, DSOMetadata.Abi));
+ Generate (arch, blobDSO, assemblies[arch]);
+ }
+
+ var generator = new AssemblyBlobDSOGenerator (assemblies);
+ LLVMIR.LlvmIrModule module = generator.Construct ();
+
+ foreach (string abi in abis) {
+ string outputAsmFilePath = Path.Combine (SourcesOutputDirectory, MonoAndroidHelper.MakeNativeAssemblyFileName (PrepareAbiItems.AssemblyDSOBase, abi));
+
+ 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");
+ }
+ }
+ }
+
+ ulong GetFileSize (string path)
+ {
+ var fi = new FileInfo (path);
+ return (ulong)fi.Length;
+ }
+
+ void Generate (AndroidTargetArch arch, ITaskItem blobDSO, List assemblies)
+ {
+ string stubPath = GetRequiredMetadata (blobDSO, DSOMetadata.BlobStubPath);
+ var stubInfo = new FileInfo (stubPath);
+ long padding = MaximumStubSize - stubInfo.Length;
+
+ // If we have a fixed stub size, in native code we can simply use a constant and thus generate faster code. Currently, none of the stubs exceeds the size of 2.5kb
+ if (padding < 0) {
+ throw new InvalidOperationException ($"Internal error: stub '{stubPath}' is too big. Maximum supported size is {MaximumStubSize}b, stub is however {stubInfo.Length}b");
+ }
+
+ string outputFile = blobDSO.ItemSpec;
+ string? outputDir = Path.GetDirectoryName (outputFile);
+
+ if (!String.IsNullOrEmpty (outputDir)) {
+ Directory.CreateDirectory (outputDir);
+ }
+
+ // File.Copy (stubPath, outputFile);
+ // using var stubFS = File.Open (outputFile, FileMode.Open, FileAccess.Write, FileShare.Read);
+ // if (padding > 0) {
+ // long newLength = stubInfo.Length + padding;
+ // stubFS.SetLength (newLength);
+ // }
+ // stubFS.Seek (0, SeekOrigin.End);
+ using var stubFS = File.Open (outputFile, FileMode.Create, FileAccess.Write, FileShare.Read);
+ foreach (BlobAssemblyInfo info in assemblies) {
+ string inputFile = GetRequiredMetadata (info.Item, DSOMetadata.InputAssemblyPath);
+ info.OffsetInBlob = (ulong)stubFS.Position;
+
+ using var fs = File.Open (inputFile, FileMode.Open, FileAccess.Read, FileShare.Read);
+ fs.CopyTo (stubFS);
+ }
+
+ stubFS.Flush ();
+ }
+
+ static string GetRequiredMetadata (ITaskItem item, string name)
+ {
+ string ret = item.GetMetadata (name);
+ if (String.IsNullOrEmpty (ret)) {
+ throw new InvalidOperationException ($"Internal error: item {item} doesn't contain required metadata item '{name}' or its value is an empty string");
+ }
+
+ return ret;
+ }
+
+ bool ShouldSkipCompression (ITaskItem item)
+ {
+ if (assemblyCompressor == null) {
+ return true;
+ }
+
+ 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;
+ }
+}
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..1a40ab8cbc6 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
@@ -36,6 +36,7 @@ public class GeneratePackageManagerJava : AndroidTask
public ITaskItem[] SatelliteAssemblies { get; set; }
+ [Required]
public bool UseAssemblyStore { get; set; }
[Required]
@@ -145,26 +146,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 +227,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 +250,7 @@ void AddEnvironment ()
uniqueAssemblyNames.Add (assemblyName);
}
- if (!UseAssemblyStore) {
+ if (!UseAssemblyStore) { // TODO: modify for assemblies embedded in DSOs
assemblyCount++;
return;
}
@@ -316,7 +297,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) {
@@ -386,13 +367,8 @@ void AddEnvironment ()
HaveRuntimeConfigBlob = haveRuntimeConfigBlob,
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 +399,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/PrepareAssemblyBlobItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAssemblyBlobItems.cs
new file mode 100644
index 00000000000..563450f3909
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAssemblyBlobItems.cs
@@ -0,0 +1,50 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+public class PrepareAssemblyBlobItems : AndroidTask
+{
+ // If the name is changed here, it must also be changed in:
+ // * src/monodroid/jni/embedded-assemblies.hh
+ // * src/monodroid/CMakeLists.txt
+ // * build-tools/installers/create-installers.targets
+ const string BlobStubName = "AssembLyBloB.so";
+
+ public override string TaskPrefix => "PABI";
+
+ [Required]
+ public string AndroidBinUtilsDirectory { get; set; } = "";
+
+ [Required]
+ public string[] SupportedABIs { get; set; }
+
+ [Required]
+ public string SharedLibraryOutputPath { get; set; }
+
+ [Output]
+ public ITaskItem[] AssemblyBlobDSOs { get; set; }
+
+ public override bool RunTask ()
+ {
+ var blobs = new List ();
+ foreach (string abi in SupportedABIs) {
+ string stubPath = Path.Combine (SharedLibraryOutputPath, abi, BlobStubName);
+ var blobItem = new TaskItem (stubPath);
+ blobItem.SetMetadata (DSOMetadata.Abi, abi);
+ blobItem.SetMetadata (DSOMetadata.BlobStubPath, GetStubPath (MonoAndroidHelper.AbiToTargetArch (abi)));
+ blobs.Add (blobItem);
+ }
+
+ AssemblyBlobDSOs = blobs.ToArray ();
+ return !Log.HasLoggedErrors;
+ }
+
+ string GetStubPath (AndroidTargetArch arch) => Path.Combine (MonoAndroidHelper.GetLibstubsArchDirectoryPath (AndroidBinUtilsDirectory, arch), $"lib{BlobStubName}");
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs
index 80d16a1f72c..09c91282768 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessAssemblies.cs
@@ -105,9 +105,9 @@ void SetAssemblyAbiMetadata (string abi, string assetType, ITaskItem assembly, I
return;
}
- assembly.SetMetadata ("Abi", abi);
+ assembly.SetMetadata (DSOMetadata.Abi, abi);
if (symbol != null) {
- symbol.SetMetadata ("Abi", abi);
+ symbol.SetMetadata (DSOMetadata.Abi, abi);
}
}
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..a38d7e774b8 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
@@ -32,7 +32,6 @@ 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 marshal_methods_enabled;
public byte bound_stream_io_exception_type;
public uint package_naming_policy;
@@ -40,13 +39,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..8be51dae250 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,7 @@ sealed class XamarinAndroidBundledAssembly
public bool InstantRunEnabled { get; set; }
public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
public bool HaveRuntimeConfigBlob { get; set; }
- public bool HaveAssemblyStore { 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 +166,6 @@ 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,
marshal_methods_enabled = MarshalMethodsEnabled,
bound_stream_io_exception_type = (byte)BoundExceptionType,
package_naming_policy = (uint)PackageNamingPolicy,
@@ -219,7 +173,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 +188,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 +213,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 +304,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/AssemblyBlobDSOGenerator.Classes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyBlobDSOGenerator.Classes.cs
new file mode 100644
index 00000000000..4f701f92a23
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyBlobDSOGenerator.Classes.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Build.Framework;
+using Xamarin.Android.Tasks.LLVMIR;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+partial class AssemblyBlobDSOGenerator
+{
+ // Native structures and constants
+
+ // Must be identical to the like-named constants in src/monodroid/jni/xamarin-app.hh
+ const uint AssemblyEntry_IsCompressed = 1 << 0;
+ const uint AssemblyEntry_HasConfig = 1 << 1;
+
+ class AssemblyIndexEntryBase
+ {
+ [NativeAssembler (Ignore = true)]
+ public string Name;
+
+ [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)]
+ public T name_hash;
+ public uint input_data_offset;
+ public uint input_data_size;
+ public uint output_data_offset;
+ public uint output_data_size;
+ public uint info_index;
+ public uint flags;
+ }
+
+ sealed class AssemblyIndexEntry32 : AssemblyIndexEntryBase
+ {}
+
+ sealed class AssemblyIndexEntry64 : AssemblyIndexEntryBase
+ {}
+
+ struct AssembliesConfig
+ {
+ public uint assembly_blob_size;
+ public uint assembly_name_length;
+ public uint assembly_count;
+ public uint assembly_index_size;
+ }
+
+ // Generator support
+ public sealed class BlobAssemblyInfo
+ {
+ public bool IsCompressed { get; set; }
+ public ulong OffsetInBlob { get; set; }
+ public ulong SizeInBlob { get; set; }
+ public ulong Size { get; set; }
+ public ulong Offset { get; set; }
+ public string? Config { get; set; }
+ public ITaskItem Item { get; }
+ public string Name { get; }
+ public byte[] NameBytes { get; }
+
+ public BlobAssemblyInfo (ITaskItem item)
+ {
+ Item = item;
+ Name = Path.GetFileName (item.ItemSpec);
+ NameBytes = LlvmIrComposer.StringToBytes (Name);
+ }
+ }
+
+ sealed class ArchState
+ {
+ public ulong BlobSize = 0;
+ public ulong DataSize = 0;
+ public ulong AssemblyNameLength = 0;
+ public ulong AssemblyCount = 0;
+
+ public readonly bool Is64Bit;
+ public readonly List> Index32 = new List> ();
+ public readonly List> Index64 = new List> ();
+ public readonly List AssemblyNames = new List ();
+ public readonly List Assemblies;
+
+ public ArchState (List assemblies, AndroidTargetArch arch)
+ {
+ Assemblies = assemblies;
+ Is64Bit = arch switch {
+ AndroidTargetArch.Arm => false,
+ AndroidTargetArch.X86 => false,
+ AndroidTargetArch.Arm64 => true,
+ AndroidTargetArch.X86_64 => true,
+ _ => throw new NotSupportedException ($"Architecture '{arch}' is not supported")
+ };
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyBlobDSOGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyBlobDSOGenerator.cs
new file mode 100644
index 00000000000..b9f02df9e92
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyBlobDSOGenerator.cs
@@ -0,0 +1,344 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Xamarin.Android.Tasks.LLVMIR;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+partial class AssemblyBlobDSOGenerator : LlvmIrComposer
+{
+ // The XA* constants must have values identical to their native counterparts as defined in src/monodroid/jni/xamarin-app.hh
+ const string XAAssembliesConfigVarName = "xa_assemblies_config";
+ const string XAAssemblyConfigFilesVarName = "xa_assembly_config_files";
+ const string XAAssemblyDataVarName = "xa_assembly_data";
+ const string XAAssemblyIndexVarName = "xa_assembly_index";
+ const string XAAssemblyNamesVarName = "xa_assembly_names";
+ const string XALoadedAssembliesVarName = "xa_loaded_assemblies";
+
+ readonly Dictionary> assemblies;
+ readonly Dictionary archStates;
+
+ StructureInfo? assemblyIndexEntry32StructureInfo;
+ StructureInfo? assemblyIndexEntry64StructureInfo;
+ StructureInfo? assembliesConfigStructureInfo;
+
+ public AssemblyBlobDSOGenerator (Dictionary> assemblies)
+ {
+ archStates = new Dictionary ();
+
+ this.assemblies = assemblies;
+ }
+
+ void PrepareData (out ulong assemblyCount, out List assemblyConfigs)
+ {
+ assemblyConfigs = new List ();
+ uint assemblyNameLength = 0;
+ int expectedAssemblyCount = -1;
+ foreach (var kvp in assemblies) {
+ AndroidTargetArch arch = kvp.Key;
+ List asmList = kvp.Value;
+
+ if (expectedAssemblyCount == -1) {
+ expectedAssemblyCount = asmList.Count;
+ } else if (expectedAssemblyCount != asmList.Count) {
+ throw new InvalidOperationException ($"Internal error: target architecture {arch} has an invalid number of assemblies ({asmList.Count} instead of {expectedAssemblyCount}");
+ }
+
+ if (!archStates.TryGetValue (arch, out ArchState archState)) {
+ archState = new ArchState (asmList, arch);
+ archStates.Add (arch, archState);
+ }
+
+ ProcessAssemblies (archState, asmList, assemblyConfigs, ref assemblyNameLength);
+ }
+
+ if (expectedAssemblyCount <= 0) {
+ throw new InvalidOperationException ($"Internal error: no assemblies found");
+ }
+
+ assemblyNameLength++; // Must be nul-terminated
+ assemblyCount = (ulong)expectedAssemblyCount;
+
+ foreach (var kvp in archStates) {
+ ArchState state = kvp.Value;
+
+ state.AssemblyNameLength = assemblyNameLength;
+ state.AssemblyCount = assemblyCount;
+ }
+ }
+
+ void ProcessAssemblies (ArchState archState, List asmList, List assemblyConfigs, ref uint assemblyNameLength)
+ {
+ ulong outputDataOffset = 0;
+ uint infoIndex = 0;
+ var usedHashes = new HashSet ();
+
+ // We add configs only for the first ABI, since all assemblies, linked or not, share their .config files
+ bool addConfigs = assemblyConfigs.Count == 0;
+
+ foreach (BlobAssemblyInfo info in asmList) {
+ archState.DataSize = AddWithCheck (archState.DataSize, info.Size, UInt32.MaxValue, "Output data too big");
+ archState.BlobSize = AddWithCheck (archState.BlobSize, info.SizeInBlob, UInt32.MaxValue, "Input data too big");
+
+ if (info.NameBytes.Length > assemblyNameLength) {
+ assemblyNameLength = (uint)info.NameBytes.Length;
+ }
+
+ // Offset in the output data array (the `xa_assembly_data` variable), after decompression
+ info.Offset = outputDataOffset;
+ outputDataOffset = AddWithCheck (outputDataOffset, info.Size, UInt32.MaxValue, "Output data too big");
+ archState.AssemblyNames.Add (info.NameBytes);
+
+ if (addConfigs) {
+ assemblyConfigs.Add (info.Config);
+ }
+
+ string nameWithoutExtension = Path.GetFileNameWithoutExtension (info.Name);
+ byte[] nameWithoutExtensionBytes = StringToBytes (nameWithoutExtension);
+ ulong nameWithExtensionHash = EnsureUniqueHash (GetXxHash (info.NameBytes, archState.Is64Bit), info.Name);
+ ulong nameWithoutExtensionHash = EnsureUniqueHash (GetXxHash (nameWithoutExtensionBytes, archState.Is64Bit), nameWithoutExtension);
+ uint entryFlags = 0;
+
+ if (info.IsCompressed) {
+ entryFlags |= AssemblyEntry_IsCompressed;
+ }
+
+ if (!String.IsNullOrEmpty (info.Config)) {
+ entryFlags |= AssemblyEntry_HasConfig;
+ }
+
+ if (archState.Is64Bit) {
+ var entryWithExtension = new AssemblyIndexEntry64 {
+ Name = info.Name,
+ name_hash = nameWithExtensionHash,
+ input_data_offset = (uint)info.OffsetInBlob,
+ input_data_size = (uint)info.SizeInBlob,
+ output_data_offset = (uint)info.Offset,
+ output_data_size = (uint)info.Size,
+ info_index = infoIndex,
+ flags = entryFlags,
+ };
+ archState.Index64.Add (new StructureInstance (assemblyIndexEntry64StructureInfo, entryWithExtension));
+
+ var entryWithoutExtension = new AssemblyIndexEntry64 {
+ Name = nameWithoutExtension,
+ name_hash = nameWithoutExtensionHash,
+ input_data_offset = entryWithExtension.input_data_offset,
+ input_data_size = entryWithExtension.input_data_size,
+ output_data_offset = entryWithExtension.output_data_offset,
+ output_data_size = entryWithExtension.output_data_size,
+ info_index = entryWithExtension.info_index,
+ flags = entryWithExtension.flags,
+ };
+ archState.Index64.Add (new StructureInstance (assemblyIndexEntry64StructureInfo, entryWithoutExtension));
+ } else {
+ var entryWithExtension = new AssemblyIndexEntry32 {
+ Name = info.Name,
+ name_hash = (uint)nameWithExtensionHash,
+ input_data_offset = (uint)info.OffsetInBlob,
+ input_data_size = (uint)info.SizeInBlob,
+ output_data_offset = (uint)info.Offset,
+ output_data_size = (uint)info.Size,
+ info_index = infoIndex,
+ flags = entryFlags,
+ };
+ archState.Index32.Add (new StructureInstance (assemblyIndexEntry32StructureInfo, entryWithExtension));
+
+ var entryWithoutExtension = new AssemblyIndexEntry32 {
+ Name = nameWithoutExtension,
+ name_hash = (uint)nameWithoutExtensionHash,
+ input_data_offset = entryWithExtension.input_data_offset,
+ input_data_size = entryWithExtension.input_data_size,
+ output_data_offset = entryWithExtension.output_data_offset,
+ output_data_size = entryWithExtension.output_data_size,
+ info_index = entryWithExtension.info_index,
+ flags = entryWithExtension.flags,
+ };
+ archState.Index32.Add (new StructureInstance (assemblyIndexEntry32StructureInfo, entryWithoutExtension));
+ }
+ infoIndex++;
+ }
+
+ if (archState.Is64Bit) {
+ archState.Index64.Sort (
+ (StructureInstance a, StructureInstance b) => ((AssemblyIndexEntry64)a.Obj).name_hash.CompareTo (((AssemblyIndexEntry64)b.Obj).name_hash)
+ );
+ } else {
+ archState.Index32.Sort (
+ (StructureInstance a, StructureInstance b) => ((AssemblyIndexEntry32)a.Obj).name_hash.CompareTo (((AssemblyIndexEntry32)b.Obj).name_hash)
+ );
+ }
+
+ 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 {v - 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;
+ }
+ }
+
+ protected override void Construct (LlvmIrModule module)
+ {
+ MapStructures (module);
+ PrepareData (out ulong assemblyCount, out List assemblyConfigs);
+
+ var xa_assemblies_config = new LlvmIrGlobalVariable (typeof(StructureInstance), XAAssembliesConfigVarName) {
+ BeforeWriteCallback = AssembliesConfigBeforeWrite,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assemblies_config);
+
+ 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_config_files = new LlvmIrGlobalVariable (typeof(List), XAAssemblyConfigFilesVarName) {
+ Value = assemblyConfigs,
+ Options = LlvmIrVariableOptions.GlobalConstant,
+ };
+ module.Add (xa_assembly_config_files);
+
+ var xa_loaded_assemblies = new LlvmIrGlobalVariable (typeof(IntPtr[]), XALoadedAssembliesVarName) {
+ ArrayItemCount = assemblyCount,
+ Options = LlvmIrVariableOptions.GlobalWritable,
+ ZeroInitializeArray = true,
+ };
+ module.Add (xa_loaded_assemblies);
+
+ var xa_assembly_data = new LlvmIrGlobalVariable (typeof(byte[]), XAAssemblyDataVarName) {
+ BeforeWriteCallback = AssemblyDataBeforeWrite,
+ Options = LlvmIrVariableOptions.GlobalWritable,
+ ZeroInitializeArray = true,
+ };
+ module.Add (xa_assembly_data);
+ }
+
+ void AssemblyNamesBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ var names = new List ((int)archState.AssemblyCount);
+
+ foreach (byte[] nameBytes in archState.AssemblyNames) {
+ names.Add (GetProperlySizedBytesForNameArray ((uint)archState.AssemblyNameLength, 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;
+ }
+
+ 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 ()}'");
+ }
+
+ 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.Index64;
+ type = archState.Index64.GetType ();
+ } else if (target.TargetArch == AndroidTargetArch.Arm || target.TargetArch == AndroidTargetArch.X86) {
+ value = archState.Index32;
+ type = archState.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);
+ var gv = (LlvmIrGlobalVariable)variable;
+
+ if (archState.BlobSize > UInt32.MaxValue) {
+ throw new InvalidOperationException ("Assembly blob is too big");
+ }
+
+ var cfg = new AssembliesConfig {
+ assembly_blob_size = (uint)archState.BlobSize,
+ assembly_name_length = (uint)archState.AssemblyNameLength,
+ assembly_count = (uint)archState.AssemblyCount,
+ assembly_index_size = (uint)archState.AssemblyCount * 2,
+ };
+ variable.Value = new StructureInstance (assembliesConfigStructureInfo, cfg);
+ }
+
+ void AssemblyDataBeforeWrite (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
+ {
+ ArchState archState = GetArchState (target);
+ var gv = (LlvmIrGlobalVariable)variable;
+
+ gv.ArrayItemCount = archState.DataSize;
+ }
+
+ static string MakeComment (string name) => $" => {name}";
+ ArchState GetArchState (LlvmIrModuleTarget target) => GetArchState (target, archStates);
+
+ 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;
+ }
+
+ void MapStructures (LlvmIrModule module)
+ {
+ assemblyIndexEntry32StructureInfo = module.MapStructure ();
+ assemblyIndexEntry64StructureInfo = module.MapStructure ();
+ assembliesConfigStructureInfo = module.MapStructure ();
+ }
+}
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/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..b643e75580f
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/DSOMetadata.cs
@@ -0,0 +1,17 @@
+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 BlobStubPath = "BlobStubPath";
+ 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..5442c35fe77 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);
@@ -280,7 +310,7 @@ void WriteTypeAndValue (GeneratorWriteContext context, LlvmIrVariable variable,
return;
}
- throw new InvalidOperationException ($"Internal error: variable of type {variable.Type} must not have a null value");
+ throw new InvalidOperationException ($"Internal error: variable '{variable.Name}'' of type {variable.Type} must not have a null value");
}
if (valueType != variable.Type && !LlvmIrModule.NameValueArrayType.IsAssignableFrom (variable.Type)) {
@@ -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/LlvmIrModule.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs
index be1570dafe8..9a50f4ee5cf 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs
@@ -464,7 +464,7 @@ void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? string
Register (kvp.Value);
}
} else if (typeof(ICollection).IsAssignableFrom (variable.Type)) {
- foreach (string s in (ICollection)variable.Value) {
+ foreach (string s in (ICollection)variable.Value) {
Register (s);
}
} else {
@@ -473,8 +473,12 @@ void AddStringArrayGlobalVariable (LlvmIrGlobalVariable variable, string? string
AddStandardGlobalVariable (variable);
- void Register (string value)
+ void Register (string? value)
{
+ if (value == null) {
+ return;
+ }
+
RegisterString (value, stringGroupName, stringGroupComment, symbolSuffix);
}
}
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 subclasses, TypeDefinitionCache cache)
+ XElement CreateApplicationElement (XElement manifest, string applicationClass, ICollection subclasses, TypeDefinitionCache cache)
{
var application = manifest.Descendants ("application").FirstOrDefault ();
@@ -592,8 +591,7 @@ XElement CreateApplicationElement (XElement manifest, string applicationClass, L
List typeAttr = new List ();
List typeUsesLibraryAttr = new List ();
List typeUsesConfigurationAttr = new List ();
- foreach (JavaType jt in subclasses) {
- TypeDefinition t = jt.Type;
+ foreach (TypeDefinition t in subclasses) {
ApplicationAttribute aa = ApplicationAttribute.FromCustomAttributeProvider (t);
if (aa == null)
continue;
@@ -925,7 +923,7 @@ void AddSupportsGLTextures (XElement application, TypeDefinitionCache cache)
}
}
- void AddInstrumentations (XElement manifest, IList subclasses, int targetSdkVersion, TypeDefinitionCache cache)
+ void AddInstrumentations (XElement manifest, ICollection subclasses, int targetSdkVersion, TypeDefinitionCache cache)
{
var assemblyAttrs =
Assemblies.SelectMany (path => InstrumentationAttribute.FromCustomAttributeProvider (Resolver.GetAssembly (path)));
@@ -938,8 +936,7 @@ void AddInstrumentations (XElement manifest, IList subclasses, int tar
manifest.Add (ia.ToElement (PackageName, cache));
}
- foreach (JavaType jt in subclasses) {
- TypeDefinition type = jt.Type;
+ foreach (TypeDefinition type in subclasses) {
if (type.IsSubclassOf ("Android.App.Instrumentation", cache)) {
var xe = InstrumentationFromTypeDefinition (type, JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'), cache);
if (xe != null)
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
index 96063bf2126..cdb0f7a338a 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
@@ -26,7 +26,7 @@ sealed class AssemblyImports
IDictionary assemblyPaths;
TaskLoggingHelper log;
- public MarshalMethodsAssemblyRewriter (IDictionary> methods, ICollection uniqueAssemblies, IDictionary assemblyPaths, TaskLoggingHelper log)
+ public MarshalMethodsAssemblyRewriter (IDictionary> methods, ICollection uniqueAssemblies, TaskLoggingHelper log)
{
this.assemblyPaths = assemblyPaths;
this.methods = methods ?? throw new ArgumentNullException (nameof (methods));
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
index f87cf118971..da2f4a07b2a 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
@@ -922,6 +922,7 @@ void AddMarshalMethodNames (LlvmIrModule module, AssemblyCacheState acs)
var mm_method_names_variable = new LlvmIrGlobalVariable (mm_method_names, "mm_method_names", LlvmIrVariableOptions.GlobalConstant) {
BeforeWriteCallback = UpdateMarshalMethodNameIds,
BeforeWriteCallbackCallerState = acs,
+ NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal,
};
module.Add (mm_method_names_variable);
@@ -1021,6 +1022,7 @@ void AddAssemblyImageCache (LlvmIrModule module, out AssemblyCacheState acs)
BeforeWriteCallbackCallerState = acs,
GetArrayItemCommentCallback = GetAssemblyImageCacheItemComment,
GetArrayItemCommentCallbackCallerState = acs,
+ NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal,
};
module.Add (assembly_image_cache_hashes);
@@ -1066,7 +1068,7 @@ void UpdateAssemblyImageCacheHashes (LlvmIrVariable variable, LlvmIrModuleTarget
i = acs.Hashes32[v32].index;
}
- return $" {index}: {name} => 0x{value:x} => {i}";
+ return $" {index}: {name} => {i}";
}
void UpdateAssemblyImageCacheIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? callerState)
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
index d72b347d5e9..d4e6a454111 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
@@ -543,6 +543,8 @@ public static AndroidTargetArch AbiToTargetArch (string abi)
"arm64-v8a" => AndroidTargetArch.Arm64,
"x86_64" => AndroidTargetArch.X86_64,
"x86" => AndroidTargetArch.X86,
+ "" => throw new ArgumentException ("must not be empty string", nameof (abi)),
+ null => throw new ArgumentNullException (nameof (abi)),
_ => throw new NotSupportedException ($"Internal error: unsupported ABI '{abi}'")
};
}
@@ -643,5 +645,41 @@ public static string GetNativeLibsRootDirectoryPath (string androidBinUtilsDirec
string relPath = GetToolsRootDirectoryRelativePath (androidBinUtilsDirectory);
return Path.GetFullPath (Path.Combine (androidBinUtilsDirectory, relPath, "lib"));
}
+
+ public static string MakeNativeAssemblyFileName (string baseName, string abi) => $"{baseName}.{abi}.ll";
+
+ public static bool IsSatelliteAssembly (ITaskItem item)
+ {
+ return IsNotEmptyMetadataAndMaybeMatches (item, "AssetType", "resources") && IsNotEmptyMetadataAndMaybeMatches (item, "Culture");
+
+ bool IsNotEmptyMetadataAndMaybeMatches (ITaskItem item, string metadataName, string? expectedValue = null)
+ {
+ string? data = item.GetMetadata (metadataName);
+ if (String.IsNullOrEmpty (data)) {
+ return false;
+ }
+
+ if (String.IsNullOrEmpty (expectedValue)) {
+ return true;
+ }
+
+ return String.Compare (data, expectedValue, StringComparison.Ordinal) == 0;
+ }
+ }
+
+ public static HashSet MakeHashSet (ICollection items)
+ {
+ var ret = new HashSet (StringComparer.OrdinalIgnoreCase);
+
+ if (items.Count == 0) {
+ return ret;
+ }
+
+ foreach (ITaskItem item in items) {
+ ret.Add (item.ItemSpec);
+ }
+
+ return ret;
+ }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCompilationHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCompilationHelper.cs
new file mode 100644
index 00000000000..4825718c507
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCompilationHelper.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+class NativeCompilationHelper
+{
+ public abstract class Config
+ {
+ public readonly string ExecutablePath;
+ public readonly List ProcessOptions;
+ public readonly TaskLoggingHelper Log;
+ public readonly string OutputFile;
+ public readonly string? WorkingDirectory;
+ public readonly AndroidTargetArch TargetArch;
+
+ public object? State { get; set; }
+ public CancellationToken? CancellationToken { get; set; }
+ public Action? Cancel { get; set; }
+
+ protected Config (TaskLoggingHelper log, AndroidTargetArch targetArch, string executablePath, string outputFile, List processOptions, string? workingDirectory)
+ {
+ Log = log;
+ TargetArch = targetArch;
+ ExecutablePath = EnsureValidString (executablePath, nameof (executablePath));
+ OutputFile = EnsureValidString (outputFile, nameof (outputFile));
+ ProcessOptions = new List (processOptions);
+ WorkingDirectory = workingDirectory;
+ }
+
+ string EnsureValidString (string v, string name)
+ {
+ if (String.IsNullOrEmpty (v)) {
+ throw new ArgumentException ("must not be null or empty", name);
+ }
+
+ return v;
+ }
+ }
+
+ public sealed class LinkerConfig : Config
+ {
+ public readonly List ObjectFilePaths;
+ public readonly List ExtraLibraries;
+
+ public LinkerConfig (TaskLoggingHelper log, AndroidTargetArch targetArch, string linkerPath, string outputSharedLibrary, List? linkerOptions = null, string? workingDirectory = null)
+ : base (
+ log,
+ targetArch,
+ linkerPath,
+ outputSharedLibrary,
+ linkerOptions != null ? linkerOptions : NativeCompilationHelper.CommonLinkerArgs,
+ workingDirectory
+ )
+ {
+ AddArchOptions (ProcessOptions, targetArch);
+ ProcessOptions.Add ($"-soname {Path.GetFileName(OutputFile)}");
+ ProcessOptions.Add ($"-o {QuoteFileName(OutputFile)}");
+
+ ObjectFilePaths = new List ();
+ ExtraLibraries = new List ();
+ }
+
+ void AddArchOptions (List options, AndroidTargetArch arch)
+ {
+ string elfArch;
+ switch (arch) {
+ case AndroidTargetArch.Arm:
+ options.Add ("-X");
+ elfArch = "armelf_linux_eabi";
+ break;
+
+ case AndroidTargetArch.Arm64:
+ options.Add ("--fix-cortex-a53-843419");
+ elfArch = "aarch64linux";
+ break;
+
+ case AndroidTargetArch.X86:
+ elfArch = "elf_i386";
+ break;
+
+ case AndroidTargetArch.X86_64:
+ elfArch = "elf_x86_64";
+ break;
+
+ default:
+ throw new NotSupportedException ($"Unsupported Android target architecture '{arch}'");
+ }
+
+ options.Add ("-m");
+ options.Add (elfArch);
+ }
+ }
+
+ public sealed class AssemblerConfig : Config
+ {
+ public readonly string InputSource;
+
+ public AssemblerConfig (TaskLoggingHelper log, AndroidTargetArch targetArch, string assemblerPath, string inputSource, string workingDirectory, List? assemblerOptions = null, string? outputFile = null)
+ : base (
+ log,
+ targetArch,
+ assemblerPath,
+ String.IsNullOrEmpty (outputFile) ? Path.ChangeExtension (inputSource, ".o") : outputFile,
+ assemblerOptions != null ? assemblerOptions : NativeCompilationHelper.DefaultAssemblerOptions,
+ workingDirectory
+ )
+ {
+ InputSource = inputSource;
+
+ ProcessOptions.Add ($"-o={QuoteFileName(OutputFile)}");
+ ProcessOptions.Add (QuoteFileName (InputSource));
+ }
+ }
+
+ public static readonly List DefaultAssemblerOptions = new List {
+ "-O2",
+ "--debugger-tune=lldb",
+ "--debugify-level=location+variables",
+ "--fatal-warnings",
+ "--filetype=obj",
+ "--relocation-model=pic",
+ };
+
+ public static readonly List CommonLinkerArgs = new List {
+ "--shared",
+ "--allow-shlib-undefined",
+ "--export-dynamic",
+ "-z relro",
+ "-z noexecstack",
+ "--enable-new-dtags",
+ "--build-id",
+ "--warn-shared-textrel",
+ "--fatal-warnings",
+ };
+
+ public static string GetAssemblerPath (string androidBinUtilsDirectory)
+ {
+ string llcPath = Path.Combine (androidBinUtilsDirectory, "llc");
+ string executableDir = Path.GetDirectoryName (llcPath);
+ string executableName = MonoAndroidHelper.GetExecutablePath (executableDir, Path.GetFileName (llcPath));
+
+ return Path.Combine (executableDir, executableName);
+ }
+
+ public static string GetLinkerPath (string androidBinUtilsDirectory)
+ {
+ return Path.Combine (androidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (androidBinUtilsDirectory, "ld"));
+ }
+
+ static string QuoteFileName (string fileName)
+ {
+ var builder = new CommandLineBuilder ();
+ builder.AppendFileNameIfNotNull (fileName);
+ return builder.ToString ();
+ }
+
+ static void CreateOutputDirectory (string filePath)
+ {
+ string? dirPath = Path.GetDirectoryName (filePath);
+ if (!String.IsNullOrEmpty (dirPath)) {
+ Directory.CreateDirectory (dirPath);
+ }
+ }
+
+ public static bool RunLinker (LinkerConfig config)
+ {
+ if (config.ObjectFilePaths.Count > 0) {
+ foreach (string objFile in config.ObjectFilePaths) {
+ config.ProcessOptions.Add (QuoteFileName (objFile));
+ }
+ }
+
+ if (config.ExtraLibraries.Count > 0) {
+ foreach (string libName in config.ObjectFilePaths) {
+ config.ProcessOptions.Add ($"-l{libName}");
+ }
+ }
+
+ ProcessStartInfo psi = CreateProcessStartInfo (config);
+ return RunProcess (
+ config,
+ psi,
+ "LLVM ld",
+ (StringBuilder sb) => config.Log.LogCodedError ("XA3007", Properties.Resources.XA3007, Path.GetFileName (config.OutputFile), sb.ToString ())
+ );
+ }
+
+ public static bool RunAssembler (AssemblerConfig config)
+ {
+ ProcessStartInfo psi = CreateProcessStartInfo (config);
+ return RunProcess (
+ config,
+ psi,
+ "LLVM llc",
+ (StringBuilder sb) => config.Log.LogCodedError ("XA3006", Properties.Resources.XA3006, Path.GetFileName (config.InputSource), sb.ToString ())
+ );
+ }
+
+ static bool RunProcess (Config config, ProcessStartInfo psi, string processDisplayName, Action onError)
+ {
+ CreateOutputDirectory (config.OutputFile);
+
+ using var stdout_completed = new ManualResetEvent (false);
+ using var stderr_completed = new ManualResetEvent (false);
+
+ string assemblerName = Path.GetFileName (config.ExecutablePath);
+ config.Log.LogDebugMessage ($"[{processDisplayName}] {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 (config.Log, assemblerName, s, e);
+ stdoutLines.Add (e.Data);
+ } else
+ stdout_completed.Set ();
+ };
+
+ proc.ErrorDataReceived += (s, e) => {
+ if (e.Data != null) {
+ OnErrorData (config.Log, assemblerName, s, e);
+ stderrLines.Add (e.Data);
+ } else
+ stderr_completed.Set ();
+ };
+
+ proc.StartInfo = psi;
+ proc.Start ();
+ proc.BeginOutputReadLine ();
+ proc.BeginErrorReadLine ();
+ config.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);
+ onError (sb);
+ if (config.Cancel != null) {
+ config.Cancel ();
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ static ProcessStartInfo CreateProcessStartInfo (Config config)
+ {
+ var psi = new ProcessStartInfo {
+ FileName = config.ExecutablePath,
+ Arguments = String.Join (" ", config.ProcessOptions),
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ };
+
+ if (!String.IsNullOrEmpty (config.WorkingDirectory)) {
+ psi.WorkingDirectory = config.WorkingDirectory;
+ }
+
+ return psi;
+ }
+
+ static void OnOutputData (TaskLoggingHelper log, string assemblerName, object sender, DataReceivedEventArgs e)
+ {
+ log.LogDebugMessage ($"[{assemblerName} stdout] {e.Data}");
+ }
+
+ static void OnErrorData (TaskLoggingHelper log, string assemblerName, object sender, DataReceivedEventArgs e)
+ {
+ log.LogMessage ($"[{assemblerName} stderr] {e.Data}", MessageImportance.High);
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/RidAgnosticInputAssemblySet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/RidAgnosticInputAssemblySet.cs
new file mode 100644
index 00000000000..511fe24164e
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/RidAgnosticInputAssemblySet.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+
+using Microsoft.Build.Framework;
+
+namespace Xamarin.Android.Tasks;
+
+class RidAgnosticInputAssemblySet : InputAssemblySet
+{
+ Dictionary javaTypeAssemblies = new (AssemblyNameStringComparer);
+ Dictionary userAssemblies = new (AssemblyNameStringComparer);
+
+ public ICollection JavaTypeAssemblies => javaTypeAssemblies.Values;
+ public ICollection UserAssemblies => userAssemblies.Values;
+
+ public override void AddJavaTypeAssembly (ITaskItem assemblyItem)
+ {
+ Add (assemblyItem.ItemSpec, assemblyItem, javaTypeAssemblies);
+ }
+
+ public override void AddUserAssembly (ITaskItem assemblyItem)
+ {
+ Add (GetUserAssemblyKey (assemblyItem), assemblyItem, userAssemblies);
+ }
+
+ public override bool IsUserAssembly (string name) => userAssemblies.ContainsKey (name);
+
+ void Add (string key, ITaskItem assemblyItem, Dictionary