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 14, 2025
1 parent dc8613c commit 33cee50
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
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 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
@@ -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;

/// <summary>
/// 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 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);

/// <summary>
/// Returns the in-archive path for an assembly
/// </summary>
(string assemblyFilePath, string assemblyDirectoryPath) GetInArchiveAssemblyPath (ITaskItem assembly)
{
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 ('\\', '/');
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);
}
}
28 changes: 26 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<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.CreateAdditionalLibraryResourceCache" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CreateAssemblyStore" 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" />
Expand Down Expand Up @@ -97,6 +98,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<UsingTask TaskName="Xamarin.Android.Tasks.CompileNativeAssembly" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.LinkApplicationSharedLibraries" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.PrepareAbiItems" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.WrapAssembliesAsSharedLibraries" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.WriteLockFile" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="GenerateCompressedAssembliesNativeSourceFiles" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.UnzipToFolder" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
Expand Down Expand Up @@ -2100,6 +2102,7 @@ because xbuild doesn't support framework reference assemblies.
-->
<CompressAssemblies
ApkOutputPath="$(_ApkOutputPath)"
EmbedAssemblies="$(EmbedAssembliesIntoApk)"
EnableCompression="$(AndroidEnableAssemblyCompression)"
IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"
ProjectFullPath="$(MSBuildProjectFullPath)"
Expand All @@ -2110,6 +2113,26 @@ because xbuild doesn't support framework reference assemblies.
<Output TaskParameter="ResolvedUserAssembliesOutput" ItemName="_BuildApkResolvedUserAssemblies" />
</CompressAssemblies>

<CreateAssemblyStore
AppSharedLibrariesDir="$(_AndroidApplicationSharedLibraryPath)"
IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"
ResolvedFrameworkAssemblies="@(_BuildApkResolvedFrameworkAssemblies)"
ResolvedUserAssemblies="@(_BuildApkResolvedUserAssemblies)"
SupportedAbis="@(_BuildTargetAbis)"
UseAssemblyStore="$(AndroidUseAssemblyStore)">
<Output TaskParameter="AssembliesToAddToArchive" ItemName="_BuildApkAssembliesToAddToArchive" />
</CreateAssemblyStore>

<WrapAssembliesAsSharedLibraries
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"
IntermediateOutputPath="$(IntermediateOutputPath)"
ResolvedAssemblies="@(_BuildApkAssembliesToAddToArchive)"
SupportedAbis="@(_BuildTargetAbis)"
UseAssemblyStore="$(AndroidUseAssemblyStore)">
<Output TaskParameter="WrappedAssemblies" ItemName="FilesToAddToArchive" />
</WrapAssembliesAsSharedLibraries>

<CollectDalvikFilesForArchive
AndroidPackageFormat="$(AndroidPackageFormat)"
DalvikClasses="@(_DexFile)">
Expand All @@ -2126,7 +2149,7 @@ because xbuild doesn't support framework reference assemblies.
<Output TaskParameter="FilesToAddToArchive" ItemName="FilesToAddToArchive" />
</CollectJarContentFilesForArchive>

<CollectAssemblyFilesForArchive
<!-- <CollectAssemblyFilesForArchive
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
ApkOutputPath="$(_ApkOutputPath)"
AppSharedLibrariesDir="$(_AndroidApplicationSharedLibraryPath)"
Expand All @@ -2138,7 +2161,7 @@ because xbuild doesn't support framework reference assemblies.
SupportedAbis="@(_BuildTargetAbis)"
UseAssemblyStore="$(AndroidUseAssemblyStore)">
<Output TaskParameter="FilesToAddToArchive" ItemName="FilesToAddToArchive" />
</CollectAssemblyFilesForArchive>
</CollectAssemblyFilesForArchive>-->

<CollectRuntimeConfigFilesForArchive
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
Expand Down Expand Up @@ -2238,6 +2261,7 @@ because xbuild doesn't support framework reference assemblies.
<!-- Put the assemblies and native libraries in the apk -->
<CompressAssemblies
ApkOutputPath="$(_ApkOutputPath)"
EmbedAssemblies="$(EmbedAssembliesIntoApk)"
EnableCompression="$(AndroidEnableAssemblyCompression)"
IncludeDebugSymbols="$(AndroidIncludeDebugSymbols)"
ProjectFullPath="$(MSBuildProjectFullPath)"
Expand Down

0 comments on commit 33cee50

Please sign in to comment.