Skip to content

Commit

Permalink
Create separate 'CreateAssemblyStore' and 'WrapAssembliesAsSharedLibr…
Browse files Browse the repository at this point in the history
…aries' tasks.
  • Loading branch information
jpobst committed Jan 15, 2025
1 parent dc8613c commit ea10b6f
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 89 deletions.
5 changes: 3 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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; }

Expand All @@ -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;
Expand Down
86 changes: 86 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// If using $(AndroidUseAssemblyStore), place all the assemblies in a single .blob file.
/// </summary>
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<string, string> { { "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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,98 +11,69 @@
namespace Xamarin.Android.Tasks;

/// <summary>
/// Collects managed assemblies to be added to the final archive.
/// 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.
/// </summary>
public class CollectAssemblyFilesForArchive : AndroidTask
public class WrapAssembliesAsSharedLibraries : AndroidTask
{
const string ArchiveAssembliesPath = "lib";
const string ArchiveLibPath = "lib";

public override string TaskPrefix => "CAF";
public override string TaskPrefix => "WAS";

[Required]
public string AndroidBinUtilsDirectory { get; set; } = "";

[Required]
public string ApkOutputPath { get; set; } = "";

[Required]
public string AppSharedLibrariesDir { get; set; } = "";

[Required]
public bool EnableCompression { get; set; }

public bool IncludeDebugSymbols { get; set; }

[Required]
public string IntermediateOutputPath { get; set; } = "";

[Required]
public ITaskItem [] ResolvedFrameworkAssemblies { get; set; } = [];
public bool UseAssemblyStore { get; set; }

[Required]
public ITaskItem [] ResolvedUserAssemblies { get; set; } = [];
public ITaskItem [] ResolvedAssemblies { get; set; } = [];

[Required]
public string [] SupportedAbis { get; set; } = [];

public bool UseAssemblyStore { get; set; }

[Output]
public ITaskItem [] FilesToAddToArchive { get; set; } = [];
public ITaskItem [] WrappedAssemblies { get; set; } = [];

public override bool RunTask ()
{
var wrapper_config = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, IntermediateOutputPath);
var files = new PackageFileListBuilder ();

var dsoWrapperConfig = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, IntermediateOutputPath);
if (UseAssemblyStore)
WrapAssemblyStores (wrapper_config, files);
else
AssemblyPackagingHelper.AddAssembliesFromCollection (Log, SupportedAbis, ResolvedAssemblies, (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) => WrapAssembly (log, arch, assembly, wrapper_config, files));

AddAssemblies (dsoWrapperConfig, files, assemblyStoreApkName: null);

FilesToAddToArchive = files.ToArray ();
WrappedAssemblies = files.ToArray ();

return !Log.HasLoggedErrors;
}

void AddAssemblies (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, string? assemblyStoreApkName)
void WrapAssemblyStores (DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files)
{
string compressedOutputDir = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ApkOutputPath), "..", "lz4"));
AssemblyStoreBuilder? storeBuilder = null;

if (UseAssemblyStore) {
storeBuilder = new AssemblyStoreBuilder (Log);
}

// Add user assemblies
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, storeBuilder));
foreach (var store in ResolvedAssemblies) {
var store_path = store.ItemSpec;
var abi = store.GetRequiredMetadata ("ResolvedAssemblies", "Abi", Log);

if (!UseAssemblyStore) {
return;
}

Dictionary<AndroidTargetArch, string> assemblyStorePaths = storeBuilder!.Generate (AppSharedLibrariesDir);

if (assemblyStorePaths.Count == 0) {
throw new InvalidOperationException ("Assembly store generator did not generate any stores");
}
// An error will already have been logged in GetRequiredMetadata
if (abi is null)
return;

if (assemblyStorePaths.Count != SupportedAbis.Length) {
throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI");
}
var arch = MonoAndroidHelper.AbiToTargetArch (abi);
var archive_path = MakeArchiveLibPath (abi, "lib" + Path.GetFileName (store_path));
var wrapped_source_path = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, store_path, Path.GetFileName (archive_path));

string inArchivePath;
foreach (var kvp in assemblyStorePaths) {
string abi = MonoAndroidHelper.ArchToAbi (kvp.Key);
inArchivePath = MakeArchiveLibPath (abi, "lib" + Path.GetFileName (kvp.Value));
string wrappedSourcePath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, kvp.Key, kvp.Value, Path.GetFileName (inArchivePath));
files.AddItem (wrappedSourcePath, inArchivePath);
files.AddItem (wrapped_source_path, archive_path);
}
}

void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly, DSOWrapperGenerator.Config dsoWrapperConfig, PackageFileListBuilder files, AssemblyStoreBuilder? storeBuilder)
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.
Expand All @@ -111,14 +82,9 @@ void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch
// or not we're supposed to compress .so files.
var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec);

if (UseAssemblyStore) {
storeBuilder!.AddAssembly (sourcePath, assembly, includeDebugSymbols: IncludeDebugSymbols);
return;
}

// Add assembly
(string assemblyPath, string assemblyDirectory) = GetInArchiveAssemblyPath (assembly);
string wrappedSourcePath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, sourcePath, Path.GetFileName (assemblyPath));
(var assemblyPath, var assemblyDirectory) = GetInArchiveAssemblyPath (assembly);
var wrappedSourcePath = DSOWrapperGenerator.WrapIt (log, dsoWrapperConfig, arch, sourcePath, Path.GetFileName (assemblyPath));
files.AddItem (wrappedSourcePath, assemblyPath);

// Try to add config if exists
Expand All @@ -130,16 +96,18 @@ void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch
return;
}

string symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb");
var 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));
var archiveSymbolsPath = assemblyDirectory + MonoAndroidHelper.MakeDiscreteAssembliesEntryName (Path.GetFileName (symbols));
var 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);

/// <summary>
/// Returns the in-archive path for an assembly
/// </summary>
Expand All @@ -148,12 +116,13 @@ void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch
var parts = new List<string> ();

// The PrepareSatelliteAssemblies task takes care of properly setting `DestinationSubDirectory`, so we can just use it here.
string? subDirectory = assembly.GetMetadata ("DestinationSubDirectory")?.Replace ('\\', '/');
var 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);
var 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:
Expand All @@ -166,7 +135,8 @@ void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch
// 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 ('/');
var subdirParts = subDirectory!.TrimEnd ('/').Split ('/');

if (subdirParts.Length == 1) {
// Not a satellite assembly
parts.Add (subDirectory);
Expand All @@ -178,22 +148,20 @@ void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch
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);
var 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));
var inArchivePath = MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyPath + Path.GetFileName (configFile));

if (!File.Exists (configFile)) {
return;
}

string wrappedConfigFile = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, configFile, Path.GetFileName (inArchivePath));
var wrappedConfigFile = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, arch, configFile, Path.GetFileName (inArchivePath));

files.AddItem (wrappedConfigFile, inArchivePath);
}

static string MakeArchiveLibPath (string abi, string fileName) => MonoAndroidHelper.MakeZipArchivePath (ArchiveLibPath, abi, fileName);
}
Loading

0 comments on commit ea10b6f

Please sign in to comment.