diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs index 46c60436728..192fc5767a8 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs @@ -425,27 +425,23 @@ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary assemblyStorePaths = storeGenerator.Generate (AppSharedLibrariesDir); + Dictionary assemblyStorePaths = storeBuilder.Generate (AppSharedLibrariesDir); if (assemblyStorePaths.Count == 0) { throw new InvalidOperationException ("Assembly store generator did not generate any stores"); @@ -463,90 +459,46 @@ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies ( - assemblies, - SupportedAbis, - validate: true, - shouldSkip: (ITaskItem asm) => { - if (bool.TryParse (asm.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) { - Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' "); - return true; - } - - return false; - } - ); - - foreach (var kvp in perArchAssemblies) { - Log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'"); - DoAddAssembliesFromArchCollection (kvp.Key, kvp.Value); - } - } - - void DoAddAssembliesFromArchCollection (AndroidTargetArch arch, Dictionary assemblies) + void DoAddAssembliesFromArchCollection (TaskLoggingHelper log, AndroidTargetArch arch, ITaskItem assembly) { // 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. - foreach (ITaskItem assembly in assemblies.Values) { - if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec, Log)) { - Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec); - } - - sourcePath = CompressAssembly (assembly); - - // Add assembly - (string assemblyPath, string assemblyDirectory) = GetInArchiveAssemblyPath (assembly); - if (UseAssemblyStore) { - storeAssemblyInfo = new AssemblyStoreAssemblyInfo (sourcePath, assembly); - } else { - string wrappedSourcePath = DSOWrapperGenerator.WrapIt (arch, sourcePath, Path.GetFileName (assemblyPath), this); - AddFileToArchiveIfNewer (apk, wrappedSourcePath, assemblyPath, compressionMethod: GetCompressionMethod (assemblyPath)); - } - - // Try to add config if exists - var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config"); - if (UseAssemblyStore) { - if (File.Exists (config)) { - storeAssemblyInfo.ConfigFile = new FileInfo (config); - } - } else { - AddAssemblyConfigEntry (apk, arch, assemblyDirectory, config); - } + sourcePath = CompressAssembly (assembly); + if (UseAssemblyStore) { + storeBuilder.AddAssembly (sourcePath, assembly, includeDebugSymbols: debug); + return; + } - // Try to add symbols if Debug - if (debug) { - var symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb"); - string? symbolsPath = null; + // Add assembly + (string assemblyPath, string assemblyDirectory) = GetInArchiveAssemblyPath (assembly); + string wrappedSourcePath = DSOWrapperGenerator.WrapIt (arch, sourcePath, Path.GetFileName (assemblyPath), this); + AddFileToArchiveIfNewer (apk, wrappedSourcePath, assemblyPath, compressionMethod: GetCompressionMethod (assemblyPath)); - if (File.Exists (symbols)) { - symbolsPath = symbols; - } + // Try to add config if exists + var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config"); + AddAssemblyConfigEntry (apk, arch, assemblyDirectory, config); - if (!String.IsNullOrEmpty (symbolsPath)) { - if (UseAssemblyStore) { - storeAssemblyInfo.SymbolsFile = new FileInfo (symbolsPath); - } else { - string archiveSymbolsPath = assemblyDirectory + MonoAndroidHelper.MakeDiscreteAssembliesEntryName (Path.GetFileName (symbols)); - string wrappedSymbolsPath = DSOWrapperGenerator.WrapIt (arch, symbolsPath, Path.GetFileName (archiveSymbolsPath), this); - AddFileToArchiveIfNewer ( - apk, - wrappedSymbolsPath, - archiveSymbolsPath, - compressionMethod: GetCompressionMethod (archiveSymbolsPath) - ); - } - } - } + // Try to add symbols if Debug + if (!debug) { + return; + } - if (UseAssemblyStore) { - storeGenerator.Add (storeAssemblyInfo); - } + string symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb"); + if (!File.Exists (symbols)) { + return; } + + string archiveSymbolsPath = assemblyDirectory + MonoAndroidHelper.MakeDiscreteAssembliesEntryName (Path.GetFileName (symbols)); + string wrappedSymbolsPath = DSOWrapperGenerator.WrapIt (arch, symbols, Path.GetFileName (archiveSymbolsPath), this); + AddFileToArchiveIfNewer ( + apk, + wrappedSymbolsPath, + archiveSymbolsPath, + compressionMethod: GetCompressionMethod (archiveSymbolsPath) + ); } void EnsureCompressedAssemblyData (string sourcePath, uint descriptorIndex) @@ -655,33 +607,28 @@ void AddAssemblyConfigEntry (ZipArchiveEx apk, AndroidTargetArch arch, string as } string assemblyName = Path.GetFileName (assembly.ItemSpec); - if (UseAssemblyStore) { + // 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 (assemblyName); + parts.Add (MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyName)); + } else if (subdirParts.Length == 2) { + parts.Add (subdirParts[0]); + parts.Add (MonoAndroidHelper.MakeDiscreteAssembliesEntryName (assemblyName, subdirParts[1])); } else { - // 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)"); - } + 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); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs new file mode 100644 index 00000000000..1599a8e581e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +static class AssemblyPackagingHelper +{ + public static void AddAssembliesFromCollection (TaskLoggingHelper Log, ICollection SupportedAbis, ICollection assemblies, Action doAddAssembly) + { + Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies ( + assemblies, + SupportedAbis, + validate: true, + shouldSkip: (ITaskItem asm) => { + if (bool.TryParse (asm.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) { + Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' "); + return true; + } + + return false; + } + ); + + foreach (var kvp in perArchAssemblies) { + Log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'"); + DoAddAssembliesFromArchCollection (Log, kvp.Key, kvp.Value, doAddAssembly); + } + } + + static void DoAddAssembliesFromArchCollection (TaskLoggingHelper Log, AndroidTargetArch arch, Dictionary assemblies, Action doAddAssembly) + { + foreach (ITaskItem assembly in assemblies.Values) { + if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec, Log)) { + Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, Properties.Resources.XA0107, assembly.ItemSpec); + } + + doAddAssembly (Log, arch, assembly); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs new file mode 100644 index 00000000000..80a65fb25ce --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks; + +class AssemblyStoreBuilder +{ + readonly TaskLoggingHelper log; + readonly AssemblyStoreGenerator storeGenerator; + + public AssemblyStoreBuilder (TaskLoggingHelper log) + { + this.log = log; + storeGenerator = new (log); + } + + public void AddAssembly (string assemblySourcePath, ITaskItem assemblyItem, bool includeDebugSymbols) + { + var storeAssemblyInfo = new AssemblyStoreAssemblyInfo (assemblySourcePath, assemblyItem); + + // Try to add config if exists. We use assemblyItem, because `sourcePath` might refer to a compressed + // assembly file in a different location. + var config = Path.ChangeExtension (assemblyItem.ItemSpec, "dll.config"); + if (File.Exists (config)) { + storeAssemblyInfo.ConfigFile = new FileInfo (config); + } + + if (includeDebugSymbols) { + string debugSymbolsPath = Path.ChangeExtension (assemblyItem.ItemSpec, "pdb"); + if (File.Exists (debugSymbolsPath)) { + storeAssemblyInfo.SymbolsFile = new FileInfo (debugSymbolsPath); + } + } + + storeGenerator.Add (storeAssemblyInfo); + } + + public Dictionary Generate (string outputDirectoryPath) => storeGenerator.Generate (outputDirectoryPath); +}