-
Notifications
You must be signed in to change notification settings - Fork 534
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create separate 'CreateAssemblyStore' and 'WrapAssembliesAsSharedLibr…
…aries' tasks.
- Loading branch information
Showing
5 changed files
with
282 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
src/Xamarin.Android.Build.Tasks/Tasks/WrapAssembliesAsSharedLibraries.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters