Skip to content

Commit

Permalink
[XABT] Create separate compress assemblies task.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Dec 19, 2024
1 parent f83583b commit 1ce7fb1
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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; } = [];

Expand All @@ -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<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>>? compressedAssembliesInfo = null;

if (compress) {
string key = CompressedAssemblyInfo.GetKey (ProjectFullPath);
Log.LogDebugMessage ($"Retrieving assembly compression info with key '{key}'");
compressedAssembliesInfo = BuildEngine4.UnregisterTaskObjectAssemblyLocal<IDictionary<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>>> (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<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>>? 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;
Expand All @@ -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;
Expand All @@ -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<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>>? 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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -213,15 +198,5 @@ void AddAssemblyConfigEntry (DSOWrapperGenerator.Config dsoWrapperConfig, Packag
files.AddItem (wrappedConfigFile, inArchivePath);
}

string CompressAssembly (ITaskItem assembly, bool compress, IDictionary<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>>? 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);
}
107 changes: 107 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
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<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>> GetCompressedAssemblyInfo ()
{
var key = CompressedAssemblyInfo.GetKey (ProjectFullPath);
Log.LogDebugMessage ($"Retrieving assembly compression info with key '{key}'");

var compressedAssembliesInfo = BuildEngine4.UnregisterTaskObjectAssemblyLocal<IDictionary<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>>> (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;
}
}
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> ("ArtifactFilename", null);

// Download artifact
var artifact_file = await MavenExtensions.DownloadPayload (repository, artifact, MavenCacheDirectory, maven_override_filename, Log, CancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ public static IEnumerable<XElement> ToXElements (this ICollection<ITaskItem> ite
}

[return: NotNullIfNotNull (nameof (defaultValue))]
public static string? GetMetadataOrDefault (this ITaskItem item, string name, string? defaultValue)
public static T? GetMetadataOrDefault<T> (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)
Expand Down
10 changes: 10 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
43 changes: 32 additions & 11 deletions src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,17 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<UsingTask TaskName="Xamarin.Android.Tasks.CollectPdbFiles" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CollectRuntimeConfigFilesForArchive" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CollectTypeMapFilesForArchive" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CompressAssemblies" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.ComputeHash" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateTypeManagerJava" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.ConvertResourcesCases" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.ConvertCustomView" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CopyIfChanged" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CopyResource" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateResgenManifest" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateTemporaryDirectory" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateAdditionalLibraryResourceCache" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateMsymManifest" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateResgenManifest" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateTemporaryDirectory" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateTypeManagerJava" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.FilterAssemblies" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.FindLayoutsToBind" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateLayoutBindings" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
Expand Down Expand Up @@ -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.
-->
<CompressAssemblies
ApkOutputPath="$(_ApkOutputPath)"
EnableCompression="$(AndroidEnableAssemblyCompression)"
IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"
ProjectFullPath="$(MSBuildProjectFullPath)"
ResolvedFrameworkAssemblies="@(_ShrunkFrameworkAssemblies)"
ResolvedUserAssemblies="@(_ShrunkUserAssemblies);@(_AndroidResolvedSatellitePaths)"
SupportedAbis="@(_BuildTargetAbis)">
<Output TaskParameter="ResolvedFrameworkAssembliesOutput" ItemName="_BuildApkResolvedFrameworkAssemblies" />
<Output TaskParameter="ResolvedUserAssembliesOutput" ItemName="_BuildApkResolvedUserAssemblies" />
</CompressAssemblies>

<CollectDalvikFilesForArchive
AndroidPackageFormat="$(AndroidPackageFormat)"
DalvikClasses="@(_DexFile)">
Expand All @@ -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)">
<Output TaskParameter="FilesToAddToArchive" ItemName="FilesToAddToArchive" />
Expand Down Expand Up @@ -2225,6 +2236,18 @@ because xbuild doesn't support framework reference assemblies.
</ItemGroup>

<!-- Put the assemblies and native libraries in the apk -->
<CompressAssemblies
ApkOutputPath="$(_ApkOutputPath)"
EnableCompression="$(AndroidEnableAssemblyCompression)"
IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"
ProjectFullPath="$(MSBuildProjectFullPath)"
ResolvedFrameworkAssemblies="@(_ShrunkFrameworkAssemblies)"
ResolvedUserAssemblies="@(_ShrunkUserAssemblies);@(_AndroidResolvedSatellitePaths)"
SupportedAbis="@(_BuildTargetAbis)">
<Output TaskParameter="ResolvedFrameworkAssembliesOutput" ItemName="_BuildApkResolvedFrameworkAssemblies" />
<Output TaskParameter="ResolvedUserAssembliesOutput" ItemName="_BuildApkResolvedUserAssemblies" />
</CompressAssemblies>

<CollectDalvikFilesForArchive
AndroidPackageFormat="$(AndroidPackageFormat)"
DalvikClasses="@(_DalvikClasses)">
Expand Down Expand Up @@ -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)">
<Output TaskParameter="FilesToAddToArchive" ItemName="FilesToAddToArchive" />
</CollectAssemblyFilesForArchive>
Expand Down

0 comments on commit 1ce7fb1

Please sign in to comment.