diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs index 20f3e7e15cb..f158ccf2b95 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CollectAssemblyFilesForArchive.cs @@ -31,17 +31,11 @@ public class CollectAssemblyFilesForArchive : AndroidTask public bool EmbedAssemblies { get; set; } - [Required] - public bool EnableCompression { get; set; } - public bool IncludeDebugSymbols { get; set; } [Required] public string IntermediateOutputPath { get; set; } = ""; - [Required] - public string ProjectFullPath { get; set; } = ""; - [Required] public ITaskItem [] ResolvedFrameworkAssemblies { get; set; } = []; @@ -64,26 +58,16 @@ public override bool RunTask () var files = new PackageFileListBuilder (); - DSOWrapperGenerator.Config dsoWrapperConfig = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, IntermediateOutputPath); - bool compress = !IncludeDebugSymbols && 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."); - } + var dsoWrapperConfig = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, IntermediateOutputPath); - AddAssemblies (dsoWrapperConfig, files, IncludeDebugSymbols, compress, compressedAssembliesInfo, assemblyStoreApkName: null); + AddAssemblies (dsoWrapperConfig, files, assemblyStoreApkName: null); FilesToAddToArchive = files.ToArray (); return !Log.HasLoggedErrors; } - void AddAssemblies (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, bool debug, bool compress, IDictionary>? compressedAssembliesInfo, string? assemblyStoreApkName) + void AddAssemblies (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, string? assemblyStoreApkName) { string compressedOutputDir = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ApkOutputPath), "..", "lz4")); AssemblyStoreBuilder? storeBuilder = null; @@ -93,10 +77,10 @@ void AddAssemblies (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileList } // Add user assemblies - AssemblyPackagingHelper.AddAssembliesFromCollection (Log, SupportedAbis, ResolvedUserAssemblies, (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) => DoAddAssembliesFromArchCollection (log, arch, assembly, dsoWrapperConfig, files, debug, compress, compressedAssembliesInfo, compressedOutputDir, storeBuilder)); + AssemblyPackagingHelper.AddAssembliesFromCollection (Log, SupportedAbis, ResolvedUserAssemblies, (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) => DoAddAssembliesFromArchCollection (log, arch, assembly, dsoWrapperConfig, files, storeBuilder)); // Add framework assemblies - AssemblyPackagingHelper.AddAssembliesFromCollection (Log, SupportedAbis, ResolvedFrameworkAssemblies, (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) => DoAddAssembliesFromArchCollection (log, arch, assembly, dsoWrapperConfig, files, debug, compress, compressedAssembliesInfo, compressedOutputDir, storeBuilder)); + AssemblyPackagingHelper.AddAssembliesFromCollection (Log, SupportedAbis, ResolvedFrameworkAssemblies, (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) => DoAddAssembliesFromArchCollection (log, arch, assembly, dsoWrapperConfig, files, storeBuilder)); if (!UseAssemblyStore) { return; @@ -121,16 +105,17 @@ void AddAssemblies (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileList } } - void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly, DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, bool debug, bool compress, IDictionary>? compressedAssembliesInfo, string compressedOutputDir, AssemblyStoreBuilder? storeBuilder) + void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly, DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, AssemblyStoreBuilder? storeBuilder) { // 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 = CompressAssembly (assembly, compress, compressedAssembliesInfo, compressedOutputDir); + var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec); + if (UseAssemblyStore) { - storeBuilder!.AddAssembly (sourcePath, assembly, includeDebugSymbols: debug); + storeBuilder!.AddAssembly (sourcePath, assembly, includeDebugSymbols: IncludeDebugSymbols); return; } @@ -144,7 +129,7 @@ void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch AddAssemblyConfigEntry (dsoWrapperConfig, files, arch, assemblyDirectory, config); // Try to add symbols if Debug - if (!debug) { + if (!IncludeDebugSymbols) { return; } @@ -213,15 +198,5 @@ void AddAssemblyConfigEntry (DSOWrapperGenerator.Config dsoWrapperConfig, Packag files.AddItem (wrappedConfigFile, inArchivePath); } - string CompressAssembly (ITaskItem assembly, bool compress, IDictionary>? compressedAssembliesInfo, string compressedOutputDir) - { - if (!compress) { - return assembly.ItemSpec; - } - - // NRT: compressedAssembliesInfo is guaranteed to be non-null if compress is true - return AssemblyCompression.Compress (Log, assembly, compressedAssembliesInfo!, compressedOutputDir); - } - static string MakeArchiveLibPath (string abi, string fileName) => MonoAndroidHelper.MakeZipArchivePath (ArchiveLibPath, abi, fileName); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs new file mode 100644 index 00000000000..ccd6822fa00 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs @@ -0,0 +1,107 @@ +#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; + +/// +/// Compresses assemblies using LZ4 compression before placing them in the APK. +/// Note this is independent of whether they are stored compressed with ZIP in the APK. +/// Our runtime bits will LZ4 decompress them at assembly load time. +/// +public class CompressAssemblies : AndroidTask +{ + public override string TaskPrefix => "CAS"; + + [Required] + public string ApkOutputPath { get; set; } = ""; + + [Required] + public bool EnableCompression { get; set; } + + public bool IncludeDebugSymbols { get; set; } + + [Required] + public string ProjectFullPath { get; set; } = ""; + + [Required] + public ITaskItem [] ResolvedFrameworkAssemblies { get; set; } = []; + + [Required] + public ITaskItem [] ResolvedUserAssemblies { get; set; } = []; + + [Required] + public string [] SupportedAbis { get; set; } = []; + + [Output] + public ITaskItem [] ResolvedFrameworkAssembliesOutput { get; set; } = []; + + [Output] + public ITaskItem [] ResolvedUserAssembliesOutput { get; set; } = []; + + public override bool RunTask () + { + if (IncludeDebugSymbols || !EnableCompression) { + ResolvedFrameworkAssembliesOutput = ResolvedFrameworkAssemblies; + ResolvedUserAssembliesOutput = ResolvedUserAssemblies; + return true; + } + + var compressed_assemblies_info = GetCompressedAssemblyInfo (); + + // Get all the user and framework assemblies we may need to compresss + var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(ShouldSkipAssembly (asm))).ToArray (); + var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, SupportedAbis, true); + var compressed_output_dir = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ApkOutputPath), "..", "lz4")); + + foreach (var kvp in per_arch_assemblies) { + Log.LogDebugMessage ($"Compressing assemblies for architecture '{kvp.Key}'"); + + foreach (var asm in kvp.Value.Values) { + MonoAndroidHelper.LogIfReferenceAssembly (asm, Log); + + var compressed_assembly = AssemblyCompression.Compress (Log, asm, compressed_assemblies_info, compressed_output_dir); + + if (compressed_assembly.HasValue ()) { + Log.LogDebugMessage ($"Compressed '{asm.ItemSpec}' to '{compressed_assembly}'."); + asm.SetMetadata ("CompressedAssembly", compressed_assembly); + } + } + } + + ResolvedFrameworkAssembliesOutput = ResolvedFrameworkAssemblies; + ResolvedUserAssembliesOutput = ResolvedUserAssemblies; + + return !Log.HasLoggedErrors; + } + + IDictionary> GetCompressedAssemblyInfo () + { + var key = CompressedAssemblyInfo.GetKey (ProjectFullPath); + Log.LogDebugMessage ($"Retrieving assembly compression info with key '{key}'"); + + var compressedAssembliesInfo = BuildEngine4.UnregisterTaskObjectAssemblyLocal>> (key, RegisteredTaskObjectLifetime.Build); + + if (compressedAssembliesInfo is null) + throw new InvalidOperationException ($"Assembly compression info not found for key '{key}'. Compression will not be performed."); + + return compressedAssembliesInfo; + } + + 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/MavenDownload.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs index c746d26c5a1..924f5d14a32 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs @@ -88,7 +88,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync () return null; // Allow user to override the Maven filename of the artifact - var maven_override_filename = item.GetMetadataOrDefault ("ArtifactFilename", null); + var maven_override_filename = item.GetMetadataOrDefault ("ArtifactFilename", null); // Download artifact var artifact_file = await MavenExtensions.DownloadPayload (repository, artifact, MavenCacheDirectory, maven_override_filename, Log, CancellationToken); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs index a04c8203548..40521623cfa 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ITaskItemExtensions.cs @@ -25,14 +25,14 @@ public static IEnumerable ToXElements (this ICollection ite } [return: NotNullIfNotNull (nameof (defaultValue))] - public static string? GetMetadataOrDefault (this ITaskItem item, string name, string? defaultValue) + public static T? GetMetadataOrDefault (this ITaskItem item, string name, T? defaultValue) { var value = item.GetMetadata (name); if (string.IsNullOrWhiteSpace (value)) return defaultValue; - return value; + return (T?)Convert.ChangeType (value, typeof (T)); } public static string? GetRequiredMetadata (this ITaskItem item, string itemName, string name, TaskLoggingHelper log) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index b4daa6d8bc0..a261e923baf 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -380,6 +380,16 @@ public static bool IsReferenceAssembly (string assembly, TaskLoggingHelper log) } } + public static bool LogIfReferenceAssembly (ITaskItem assembly, TaskLoggingHelper log) + { + if (IsReferenceAssembly (assembly.ItemSpec, log)) { + log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec); + return true; + } + + return false; + } + public static bool IsForceRetainedAssembly (string assembly) { switch (assembly) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 87406c4e337..109cfc20ea7 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -48,16 +48,17 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + - - - + + + @@ -2097,6 +2098,18 @@ because xbuild doesn't support framework reference assemblies. also need to have the args added to Xamarin.Android.Common.Debugging.targets in monodroid. --> + + + + + @@ -2118,12 +2131,10 @@ because xbuild doesn't support framework reference assemblies. ApkOutputPath="$(_ApkOutputPath)" AppSharedLibrariesDir="$(_AndroidApplicationSharedLibraryPath)" EmbedAssemblies="$(EmbedAssembliesIntoApk)" - EnableCompression="$(AndroidEnableAssemblyCompression)" IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)" IntermediateOutputPath="$(IntermediateOutputPath)" - ProjectFullPath="$(MSBuildProjectFullPath)" - ResolvedFrameworkAssemblies="@(_ShrunkFrameworkAssemblies)" - ResolvedUserAssemblies="@(_ShrunkUserAssemblies);@(_AndroidResolvedSatellitePaths)" + ResolvedFrameworkAssemblies="@(_BuildApkResolvedFrameworkAssemblies)" + ResolvedUserAssemblies="@(_BuildApkResolvedUserAssemblies)" SupportedAbis="@(_BuildTargetAbis)" UseAssemblyStore="$(AndroidUseAssemblyStore)"> @@ -2225,6 +2236,18 @@ because xbuild doesn't support framework reference assemblies. + + + + + @@ -2253,12 +2276,10 @@ because xbuild doesn't support framework reference assemblies. ApkOutputPath="$(_ApkOutputPath)" AppSharedLibrariesDir="$(_AndroidApplicationSharedLibraryPath)" EmbedAssemblies="$(EmbedAssembliesIntoApk)" - EnableCompression="$(AndroidEnableAssemblyCompression)" IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)" IntermediateOutputPath="$(IntermediateOutputPath)" - ProjectFullPath="$(MSBuildProjectFullPath)" - ResolvedFrameworkAssemblies="@(_ShrunkFrameworkAssemblies)" - ResolvedUserAssemblies="@(_ResolvedUserAssemblies);@(_AndroidResolvedSatellitePaths)" + ResolvedFrameworkAssemblies="@(_BuildApkResolvedFrameworkAssemblies)" + ResolvedUserAssemblies="@(_BuildApkResolvedUserAssemblies)" SupportedAbis="@(_BuildTargetAbis)">