diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs
index 7914e4b7f3e..15f4d8351a6 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs
@@ -66,7 +66,6 @@ public override bool RunTask ()
void AddAssemblies (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, string? assemblyStoreApkName)
{
- string compressedOutputDir = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ApkOutputPath), "..", "lz4"));
AssemblyStoreBuilder? storeBuilder = null;
if (UseAssemblyStore) {
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs
index ccd6822fa00..e611e92186c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs
@@ -6,7 +6,6 @@
using System.Linq;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
using Xamarin.Android.Tools;
namespace Xamarin.Android.Tasks;
@@ -23,6 +22,8 @@ public class CompressAssemblies : AndroidTask
[Required]
public string ApkOutputPath { get; set; } = "";
+ public bool EmbedAssemblies { get; set; }
+
[Required]
public bool EnableCompression { get; set; }
@@ -48,7 +49,7 @@ public class CompressAssemblies : AndroidTask
public override bool RunTask ()
{
- if (IncludeDebugSymbols || !EnableCompression) {
+ if (IncludeDebugSymbols || !EnableCompression || !EmbedAssemblies) {
ResolvedFrameworkAssembliesOutput = ResolvedFrameworkAssemblies;
ResolvedUserAssembliesOutput = ResolvedUserAssemblies;
return true;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
new file mode 100644
index 00000000000..ed58c40481d
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
@@ -0,0 +1,86 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Xamarin.Android.Tasks;
+
+///
+/// If using $(AndroidUseAssemblyStore), place all the assemblies in a single file.
+///
+public class CreateAssemblyStore : AndroidTask
+{
+ public override string TaskPrefix => "CST";
+
+ [Required]
+ public string AppSharedLibrariesDir { get; set; } = "";
+
+ public bool IncludeDebugSymbols { get; set; }
+
+ [Required]
+ public ITaskItem [] ResolvedFrameworkAssemblies { get; set; } = [];
+
+ [Required]
+ public ITaskItem [] ResolvedUserAssemblies { get; set; } = [];
+
+ [Required]
+ public string [] SupportedAbis { get; set; } = [];
+
+ public bool UseAssemblyStore { get; set; }
+
+ [Output]
+ public ITaskItem [] AssembliesToAddToArchive { get; set; } = [];
+
+ public override bool RunTask ()
+ {
+ // Get all the user and framework assemblies we may need to package
+ var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(ShouldSkipAssembly (asm))).ToArray ();
+
+ if (!UseAssemblyStore) {
+ AssembliesToAddToArchive = assemblies;
+ return !Log.HasLoggedErrors;
+ }
+
+ var store_builder = new AssemblyStoreBuilder (Log);
+ var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, SupportedAbis, true);
+
+ foreach (var kvp in per_arch_assemblies) {
+ Log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'");
+
+ foreach (var assembly in kvp.Value.Values) {
+ var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec);
+ store_builder.AddAssembly (sourcePath, assembly, includeDebugSymbols: IncludeDebugSymbols);
+
+ Log.LogDebugMessage ($"Added '{sourcePath}' to assembly store.");
+ }
+ }
+
+ var assembly_store_paths = store_builder.Generate (AppSharedLibrariesDir);
+
+ if (assembly_store_paths.Count == 0) {
+ throw new InvalidOperationException ("Assembly store generator did not generate any stores");
+ }
+
+ if (assembly_store_paths.Count != SupportedAbis.Length) {
+ throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI");
+ }
+
+ AssembliesToAddToArchive = assembly_store_paths.Select (kvp => new TaskItem (kvp.Value, new Dictionary { { "Abi", MonoAndroidHelper.ArchToAbi (kvp.Key) } })).ToArray ();
+
+ return !Log.HasLoggedErrors;
+ }
+
+ bool ShouldSkipAssembly (ITaskItem asm)
+ {
+ var should_skip = asm.GetMetadataOrDefault ("AndroidSkipAddToPackage", false);
+
+ if (should_skip)
+ Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' ");
+
+ return should_skip;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/WrapAssembliesAsSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/WrapAssembliesAsSharedLibraries.cs
new file mode 100644
index 00000000000..0c38a1db32d
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/WrapAssembliesAsSharedLibraries.cs
@@ -0,0 +1,167 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+///
+/// In the "all assemblies are per-RID" world, assembly stores, assemblies, pdb and config are disguised as shared libraries (that is,
+/// their names end with the .so extension) so that Android allows us to put them in the `lib/{ARCH}` directory.
+///
+public class WrapAssembliesAsSharedLibraries : AndroidTask
+{
+ const string ArchiveAssembliesPath = "lib";
+ const string ArchiveLibPath = "lib";
+
+ public override string TaskPrefix => "WAS";
+
+ [Required]
+ public string AndroidBinUtilsDirectory { get; set; } = "";
+
+ public bool IncludeDebugSymbols { get; set; }
+
+ [Required]
+ public string IntermediateOutputPath { get; set; } = "";
+
+ public bool UseAssemblyStore { get; set; }
+
+ [Required]
+ public ITaskItem [] ResolvedAssemblies { get; set; } = [];
+
+ [Required]
+ public string [] SupportedAbis { get; set; } = [];
+
+ [Output]
+ public ITaskItem [] WrappedAssemblies { get; set; } = [];
+
+ public override bool RunTask ()
+ {
+ var dsoWrapperConfig = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, IntermediateOutputPath);
+ var files = new PackageFileListBuilder ();
+
+ if (UseAssemblyStore)
+ WrapAssemblyStores (dsoWrapperConfig, files);
+ else
+ AssemblyPackagingHelper.AddAssembliesFromCollection (Log, SupportedAbis, ResolvedAssemblies, (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) => WrapAssembly (log, arch, assembly, dsoWrapperConfig, files));
+
+ WrappedAssemblies = files.ToArray ();
+
+ return !Log.HasLoggedErrors;
+ }
+
+ bool WrapAssemblyStores (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files)
+ {
+ foreach (var store in ResolvedAssemblies) {
+ var store_path = store.ItemSpec;
+ var abi = store.GetRequiredMetadata ("ResolvedAssemblies", "Abi", Log);
+
+ if (abi is null)
+ return false;
+
+ var arch = MonoAndroidHelper.AbiToTargetArch (abi);
+ var inArchivePath = MakeArchiveLibPath (abi, "lib" + Path.GetFileName (store_path));
+ string wrappedSourcePath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, store_path, Path.GetFileName (inArchivePath));
+
+ files.AddItem (wrappedSourcePath, inArchivePath);
+ }
+
+ return true;
+ }
+
+ void WrapAssembly (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly, DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files)
+ {
+ // In the "all assemblies are per-RID" world, assemblies, pdb and config are disguised as shared libraries (that is,
+ // their names end with the .so extension) so that Android allows us to put them in the `lib/{ARCH}` directory.
+ // For this reason, they have to be treated just like other .so files, as far as compression rules are concerned.
+ // Thus, we no longer just store them in the apk but we call the `GetCompressionMethod` method to find out whether
+ // or not we're supposed to compress .so files.
+ var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec);
+
+ // Add assembly
+ (string assemblyPath, string assemblyDirectory) = GetInArchiveAssemblyPath (assembly);
+ string wrappedSourcePath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, sourcePath, Path.GetFileName (assemblyPath));
+ files.AddItem (wrappedSourcePath, assemblyPath);
+
+ // Try to add config if exists
+ var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config");
+ AddAssemblyConfigEntry (dsoWrapperConfig, files, arch, assemblyDirectory, config);
+
+ // Try to add symbols if Debug
+ if (!IncludeDebugSymbols) {
+ return;
+ }
+
+ string symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb");
+ if (!File.Exists (symbols)) {
+ return;
+ }
+
+ string archiveSymbolsPath = assemblyDirectory + MonoAndroidHelper.MakeDiscreteAssembliesEntryName (Path.GetFileName (symbols));
+ string wrappedSymbolsPath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, symbols, Path.GetFileName (archiveSymbolsPath));
+ files.AddItem (wrappedSymbolsPath, archiveSymbolsPath);
+ }
+
+ static string MakeArchiveLibPath (string abi, string fileName) => MonoAndroidHelper.MakeZipArchivePath (ArchiveLibPath, abi, fileName);
+
+ ///
+ /// Returns the in-archive path for an assembly
+ ///
+ (string assemblyFilePath, string assemblyDirectoryPath) GetInArchiveAssemblyPath (ITaskItem assembly)
+ {
+ var parts = new List ();
+
+ // The PrepareSatelliteAssemblies task takes care of properly setting `DestinationSubDirectory`, so we can just use it here.
+ string? subDirectory = assembly.GetMetadata ("DestinationSubDirectory")?.Replace ('\\', '/');
+ if (string.IsNullOrEmpty (subDirectory)) {
+ throw new InvalidOperationException ($"Internal error: assembly '{assembly}' lacks the required `DestinationSubDirectory` metadata");
+ }
+
+ string assemblyName = Path.GetFileName (assembly.ItemSpec);
+ // For discrete assembly entries we need to treat assemblies specially.
+ // All of the assemblies have their names mangled so that the possibility to clash with "real" shared
+ // library names is minimized. All of the assembly entries will start with a special character:
+ //
+ // `_` - for regular assemblies (e.g. `_Mono.Android.dll.so`)
+ // `-` - for satellite assemblies (e.g. `-es-Mono.Android.dll.so`)
+ //
+ // Second of all, we need to treat satellite assemblies with even more care.
+ // If we encounter one of them, we will return the culture as part of the path transformed
+ // so that it forms a `-culture-` assembly file name prefix, not a `culture/` subdirectory.
+ // This is necessary because Android doesn't allow subdirectories in `lib/{ABI}/`
+ //
+ string [] subdirParts = subDirectory!.TrimEnd ('/').Split ('/');
+ if (subdirParts.Length == 1) {
+ // Not a satellite assembly
+ parts.Add (subDirectory);
+ parts.Add (MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyName));
+ } else if (subdirParts.Length == 2) {
+ parts.Add (subdirParts [0]);
+ parts.Add (MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyName, subdirParts [1]));
+ } else {
+ throw new InvalidOperationException ($"Internal error: '{assembly}' `DestinationSubDirectory` metadata has too many components ({parts.Count} instead of 1 or 2)");
+ }
+
+ string assemblyFilePath = MonoAndroidHelper.MakeZipArchivePath (ArchiveAssembliesPath, parts);
+ return (assemblyFilePath, Path.GetDirectoryName (assemblyFilePath) + "/");
+ }
+
+ void AddAssemblyConfigEntry (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, AndroidTargetArch arch, string assemblyPath, string configFile)
+ {
+ string inArchivePath = MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyPath + Path.GetFileName (configFile));
+
+ if (!File.Exists (configFile)) {
+ return;
+ }
+
+ string wrappedConfigFile = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, configFile, Path.GetFileName (inArchivePath));
+
+ files.AddItem (wrappedConfigFile, inArchivePath);
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index fb68fbb8810..59cb0e5a344 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -55,6 +55,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+
@@ -97,6 +98,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+
@@ -2100,6 +2102,7 @@ because xbuild doesn't support framework reference assemblies.
-->
+
+
+
+
+
+
+
+
@@ -2126,7 +2149,7 @@ because xbuild doesn't support framework reference assemblies.
-
-
+ -->