diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs index bd6cc842f2d..7af793f51a3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs @@ -60,11 +60,6 @@ public class BuildApk : AndroidTask public ITaskItem[] BundleNativeLibraries { get; set; } - public ITaskItem[] TypeMappings { get; set; } - - [Required] - public ITaskItem [] DalvikClasses { get; set; } - [Required] public string [] SupportedAbis { get; set; } @@ -72,16 +67,8 @@ public class BuildApk : AndroidTask public bool BundleAssemblies { get; set; } - public ITaskItem[] JavaSourceFiles { get; set; } - - public ITaskItem[] JavaLibraries { get; set; } - public string[] DoNotPackageJavaLibraries { get; set; } - public string [] ExcludeFiles { get; set; } - - public string [] IncludeFiles { get; set; } - public string Debug { get; set; } public string AndroidSequencePointsMode { get; set; } @@ -101,10 +88,6 @@ public class BuildApk : AndroidTask public bool UseAssemblyStore { get; set; } - public string ZipFlushFilesLimit { get; set; } - - public string ZipFlushSizeLimit { get; set; } - public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit; [Required] @@ -134,39 +117,20 @@ bool _Debug { SequencePointsMode sequencePointsMode = SequencePointsMode.None; - public ITaskItem[] LibraryProjectJars { get; set; } HashSet uncompressedFileExtensions; - // Do not use trailing / in the path - public string RootPath { get; set; } = ""; - - public string DalvikPath { get; set; } = ""; - protected virtual CompressionMethod UncompressedMethod => CompressionMethod.Store; protected virtual void FixupArchive (ZipArchiveFileListBuilder zip) { } List existingEntries = new List (); - List excludePatterns = new List (); - - List includePatterns = new List (); - void ExecuteWithAbi (DSOWrapperGenerator.Config dsoWrapperConfig, string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary> compressedAssembliesInfo, string assemblyStoreApkName) { ArchiveFileList files = new ArchiveFileList (); using (var apk = new ZipArchiveFileListBuilder (apkOutputPath, File.Exists (apkOutputPath) ? FileMode.Open : FileMode.Create)) { - // Add classes.dx - CompressionMethod dexCompressionMethod = GetCompressionMethod (".dex"); - foreach (var dex in DalvikClasses) { - string apkName = dex.GetMetadata ("ApkName"); - string dexPath = string.IsNullOrWhiteSpace (apkName) ? Path.GetFileName (dex.ItemSpec) : apkName; - AddFileToArchiveIfNewer (apk, dex.ItemSpec, DalvikPath + dexPath, compressionMethod: dexCompressionMethod); - apk.Flush (); - } - if (EmbedAssemblies) { AddAssemblies (dsoWrapperConfig, apk, debug, compress, compressedAssembliesInfo, assemblyStoreApkName); apk.Flush (); @@ -178,12 +142,6 @@ void ExecuteWithAbi (DSOWrapperGenerator.Config dsoWrapperConfig, string [] supp AddNativeLibraries (files, supportedAbis); AddAdditionalNativeLibraries (files, supportedAbis); - if (TypeMappings != null) { - foreach (ITaskItem typemap in TypeMappings) { - AddFileToArchiveIfNewer (apk, typemap.ItemSpec, RootPath + Path.GetFileName(typemap.ItemSpec), compressionMethod: UncompressedMethod); - } - } - foreach (var file in files) { var item = Path.Combine (file.archivePath.Replace (Path.DirectorySeparatorChar, '/')); existingEntries.Remove (item); @@ -196,63 +154,6 @@ void ExecuteWithAbi (DSOWrapperGenerator.Config dsoWrapperConfig, string [] supp apk.AddFileAndFlush (file.filePath, item, compressionMethod: compressionMethod); } - var jarFiles = (JavaSourceFiles != null) ? JavaSourceFiles.Where (f => f.ItemSpec.EndsWith (".jar", StringComparison.OrdinalIgnoreCase)) : null; - if (jarFiles != null && JavaLibraries != null) - jarFiles = jarFiles.Concat (JavaLibraries); - else if (JavaLibraries != null) - jarFiles = JavaLibraries; - - var libraryProjectJars = MonoAndroidHelper.ExpandFiles (LibraryProjectJars) - .Where (jar => !MonoAndroidHelper.IsEmbeddedReferenceJar (jar)); - - var jarFilePaths = libraryProjectJars.Concat (jarFiles != null ? jarFiles.Select (j => j.ItemSpec) : Enumerable.Empty ()); - jarFilePaths = MonoAndroidHelper.DistinctFilesByContent (jarFilePaths); - - foreach (var jarFile in jarFilePaths) { - using (var stream = File.OpenRead (jarFile)) - using (var jar = ZipArchive.Open (stream)) { - foreach (var jarItem in jar) { - if (jarItem.IsDirectory) - continue; - var name = jarItem.FullName; - if (!PackagingUtils.CheckEntryForPackaging (name)) { - continue; - } - var path = RootPath + name; - existingEntries.Remove (path); - if (apk.SkipExistingEntry (jarItem, path)) { - Log.LogDebugMessage ($"Skipping {path} as the archive file is up to date."); - continue; - } - // check for ignored items - bool exclude = false; - bool forceInclude = false; - foreach (var include in includePatterns) { - if (include.IsMatch (path)) { - forceInclude = true; - break; - } - } - if (!forceInclude) { - foreach (var pattern in excludePatterns) { - if (pattern.IsMatch (path)) { - Log.LogDebugMessage ($"Ignoring jar entry '{name}' from '{Path.GetFileName (jarFile)}'. Filename matched the exclude pattern '{pattern}'."); - exclude = true; - break; - } - } - } - if (exclude) - continue; - if (string.Compare (Path.GetFileName (name), "AndroidManifest.xml", StringComparison.OrdinalIgnoreCase) == 0) { - Log.LogDebugMessage ("Ignoring jar entry {0} from {1}: the same file already exists in the apk", name, Path.GetFileName (jarFile)); - continue; - } - - apk.AddJavaEntryAndFlush (jarFile, jarItem.FullName, path); - } - } - } FixupArchive (apk); OutputApkFiles = apk.ApkFiles.ToArray (); @@ -280,13 +181,6 @@ public override bool RunTask () existingEntries.Clear (); - foreach (var pattern in ExcludeFiles ?? Array.Empty ()) { - excludePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)); - } - foreach (var pattern in IncludeFiles ?? Array.Empty ()) { - includePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)); - } - bool debug = _Debug; bool compress = !debug && EnableCompression; IDictionary> compressedAssembliesInfo = null; @@ -311,24 +205,6 @@ public override bool RunTask () return !Log.HasLoggedErrors; } - static Regex FileGlobToRegEx (string fileGlob, RegexOptions options) - { - StringBuilder sb = new StringBuilder (); - foreach (char c in fileGlob) { - switch (c) { - case '*': sb.Append (".*"); - break; - case '?': sb.Append ("."); - break; - case '.': sb.Append (@"\."); - break; - default: sb.Append (c); - break; - } - } - return new Regex (sb.ToString (), options); - } - void AddRuntimeConfigBlob (DSOWrapperGenerator.Config dsoWrapperConfig, ZipArchiveFileListBuilder apk) { // We will place rc.bin in the `lib` directory next to the blob, to make startup slightly faster, as we will find the config file right after we encounter diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs index 7e23d4eeffe..db57c4eb989 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs @@ -12,7 +12,9 @@ namespace Xamarin.Android.Tasks; /// /// Takes a list of files and adds them to an APK archive. If the APK archive already -/// exists, files are only added if they were changed. +/// exists, files are only added if they were changed. Note *ALL* files to be in the final +/// APK must be passed in via @(FilesToAddToArchive). This task will determine any unchanged files +/// and skip them, as well as remove any existing files in the APK that are no longer required. /// public class BuildArchive : AndroidTask { @@ -34,34 +36,17 @@ public class BuildArchive : AndroidTask public string? ZipFlushSizeLimit { get; set; } - readonly HashSet uncompressedFileExtensions; - readonly CompressionMethod uncompressedMethod = CompressionMethod.Store; + HashSet? uncompressedFileExtensions; + HashSet UncompressedFileExtensionsSet => uncompressedFileExtensions ??= ParseUncompressedFileExtensions (); - public BuildArchive () - { - uncompressedFileExtensions = new HashSet (StringComparer.OrdinalIgnoreCase); - - foreach (var extension in UncompressedFileExtensions?.Split ([';', ','], StringSplitOptions.RemoveEmptyEntries) ?? []) { - var ext = extension.Trim (); - - if (string.IsNullOrEmpty (ext)) { - continue; - } - - if (ext [0] != '.') { - ext = $".{ext}"; - } - - uncompressedFileExtensions.Add (ext); - } + CompressionMethod uncompressedMethod = CompressionMethod.Store; + public override bool RunTask () + { // Nothing needs to be compressed with app bundles. BundleConfig.json specifies the final compression mode. if (string.Compare (AndroidPackageFormat, "aab", true) == 0) uncompressedMethod = CompressionMethod.Default; - } - public override bool RunTask () - { var refresh = true; // If we have an input apk but no output apk, copy it to the output @@ -251,6 +236,27 @@ CompressionMethod GetCompressionMethod (ITaskItem item) return result; } - return uncompressedFileExtensions.Contains (Path.GetExtension (item.ItemSpec)) ? uncompressedMethod : CompressionMethod.Default; + return UncompressedFileExtensionsSet.Contains (Path.GetExtension (item.ItemSpec)) ? uncompressedMethod : CompressionMethod.Default; + } + + HashSet ParseUncompressedFileExtensions () + { + var uncompressedFileExtensions = new HashSet (StringComparer.OrdinalIgnoreCase); + + foreach (var extension in UncompressedFileExtensions?.Split ([';', ','], StringSplitOptions.RemoveEmptyEntries) ?? []) { + var ext = extension.Trim (); + + if (string.IsNullOrEmpty (ext)) { + continue; + } + + if (ext [0] != '.') { + ext = $".{ext}"; + } + + uncompressedFileExtensions.Add (ext); + } + + return uncompressedFileExtensions; } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectDalvikFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectDalvikFilesForArchive.cs new file mode 100644 index 00000000000..0856f7e44e3 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CollectDalvikFilesForArchive.cs @@ -0,0 +1,46 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks; + +/// +/// Collects Dalvik classes to be added to the final archive. +/// +public class CollectDalvikFilesForArchive : AndroidTask +{ + public override string TaskPrefix => "CDF"; + + public string AndroidPackageFormat { get; set; } = ""; + + [Required] + public ITaskItem [] DalvikClasses { get; set; } = []; + + [Output] + public ITaskItem [] FilesToAddToArchive { get; set; } = []; + + public override bool RunTask () + { + var dalvikPath = AndroidPackageFormat.Equals ("aab", StringComparison.InvariantCultureIgnoreCase) ? "dex/" : ""; + var files = new List (); + + foreach (var dex in DalvikClasses) { + var apkName = dex.GetMetadata ("ApkName"); + var dexPath = string.IsNullOrWhiteSpace (apkName) ? Path.GetFileName (dex.ItemSpec) : apkName; + + var item = new TaskItem (dex.ItemSpec); + item.SetMetadata ("ArchivePath", dalvikPath + dexPath); + + files.Add (item); + } + + FilesToAddToArchive = files.ToArray (); + + return !Log.HasLoggedErrors; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectJarContentFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectJarContentFilesForArchive.cs new file mode 100644 index 00000000000..09f5c366157 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CollectJarContentFilesForArchive.cs @@ -0,0 +1,151 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Xamarin.Tools.Zip; + +namespace Xamarin.Android.Tasks; + +/// +/// Collects extra files from inside Jar libraries to be added to the final archive. +/// +public class CollectJarContentFilesForArchive : AndroidTask +{ + public override string TaskPrefix => "CJC"; + + public string AndroidPackageFormat { get; set; } = ""; + + public string [] ExcludeFiles { get; set; } = []; + + public string [] IncludeFiles { get; set; } = []; + + public ITaskItem [] JavaSourceFiles { get; set; } = []; + + public ITaskItem [] JavaLibraries { get; set; } = []; + + public ITaskItem [] LibraryProjectJars { get; set; } = []; + + List excludePatterns = new List (); + + List includePatterns = new List (); + + [Output] + public ITaskItem [] FilesToAddToArchive { get; set; } = []; + + public override bool RunTask () + { + var rootPath = AndroidPackageFormat.Equals ("aab", StringComparison.InvariantCultureIgnoreCase) ? "root/" : ""; + + foreach (var pattern in ExcludeFiles) { + excludePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + + foreach (var pattern in IncludeFiles) { + includePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + + // Grab distinct .jar files from: + // - JavaSourceFiles + // - JavaLibraries + // - LibraryProjectJars + var jarFiles = (JavaSourceFiles != null) ? JavaSourceFiles.Where (f => f.ItemSpec.EndsWith (".jar", StringComparison.OrdinalIgnoreCase)) : null; + + if (jarFiles != null && JavaLibraries != null) + jarFiles = jarFiles.Concat (JavaLibraries); + else if (JavaLibraries != null) + jarFiles = JavaLibraries; + + var libraryProjectJars = MonoAndroidHelper.ExpandFiles (LibraryProjectJars) + .Where (jar => !MonoAndroidHelper.IsEmbeddedReferenceJar (jar)); + + var jarFilePaths = libraryProjectJars.Concat (jarFiles != null ? jarFiles.Select (j => j.ItemSpec) : Enumerable.Empty ()); + jarFilePaths = MonoAndroidHelper.DistinctFilesByContent (jarFilePaths); + + // Find files in the .jar files that match our include patterns to be added to the archive + var files = new List (); + + foreach (var jarFile in jarFilePaths) { + using (var stream = File.OpenRead (jarFile)) + using (var jar = ZipArchive.Open (stream)) { + foreach (var jarItem in jar) { + if (jarItem.IsDirectory) + continue; + var name = jarItem.FullName; + if (!PackagingUtils.CheckEntryForPackaging (name)) { + continue; + } + + var path = rootPath + name; + + // check for ignored items + bool exclude = false; + bool forceInclude = false; + foreach (var include in includePatterns) { + if (include.IsMatch (path)) { + forceInclude = true; + break; + } + } + + if (!forceInclude) { + foreach (var pattern in excludePatterns) { + if (pattern.IsMatch (path)) { + Log.LogDebugMessage ($"Ignoring jar entry '{name}' from '{Path.GetFileName (jarFile)}'. Filename matched the exclude pattern '{pattern}'."); + exclude = true; + break; + } + } + } + + if (exclude) + continue; + + if (string.Compare (Path.GetFileName (name), "AndroidManifest.xml", StringComparison.OrdinalIgnoreCase) == 0) { + Log.LogDebugMessage ("Ignoring jar entry {0} from {1}: the same file already exists in the apk", name, Path.GetFileName (jarFile)); + continue; + } + + // An item's ItemSpec should be unique so use both the jar file name and the entry name + var item = new TaskItem ($"{jarFile}#{jarItem.FullName}"); + item.SetMetadata ("ArchivePath", path); + item.SetMetadata ("JavaArchiveEntry", jarItem.FullName); + + files.Add (item); + } + } + } + + FilesToAddToArchive = files.ToArray (); + + return !Log.HasLoggedErrors; + } + + static Regex FileGlobToRegEx (string fileGlob, RegexOptions options) + { + StringBuilder sb = new StringBuilder (); + foreach (char c in fileGlob) { + switch (c) { + case '*': + sb.Append (".*"); + break; + case '?': + sb.Append ("."); + break; + case '.': + sb.Append (@"\."); + break; + default: + sb.Append (c); + break; + } + } + return new Regex (sb.ToString (), options); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectTypeMapFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectTypeMapFilesForArchive.cs new file mode 100644 index 00000000000..cf9651386d5 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CollectTypeMapFilesForArchive.cs @@ -0,0 +1,45 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks; + +/// +/// Collects TypeMap to be added to the final archive. +/// +public class CollectTypeMapFilesForArchive : AndroidTask +{ + public override string TaskPrefix => "CTM"; + + public string AndroidPackageFormat { get; set; } = ""; + + public ITaskItem [] TypeMappings { get; set; } = []; + + [Output] + public ITaskItem [] FilesToAddToArchive { get; set; } = []; + + public override bool RunTask () + { + if (TypeMappings.Length == 0) + return true; + + var rootPath = AndroidPackageFormat.Equals ("aab", StringComparison.InvariantCultureIgnoreCase) ? "root/" : ""; + var files = new List (); + + foreach (var tm in TypeMappings) { + var item = new TaskItem (tm.ItemSpec); + item.SetMetadata ("ArchivePath", rootPath + Path.GetFileName (tm.ItemSpec)); + + files.Add (item); + } + + FilesToAddToArchive = files.ToArray (); + + return !Log.HasLoggedErrors; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index a42b16360d4..b1cd70fe69d 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -41,8 +41,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + + + @@ -2083,8 +2086,6 @@ because xbuild doesn't support framework reference assemblies. <_AndroidCreatePackagePerAbi>False <_ApkOutputPath>$(_BaseZipIntermediate) - <_RootPath>root/ - <_DalvikPath>dex/ @@ -2093,6 +2094,22 @@ because xbuild doesn't support framework reference assemblies. also need to have the args added to Xamarin.Android.Common.Debugging.targets in monodroid. --> + + + + + + + + + IntermediateOutputPath="$(IntermediateOutputPath)"> @@ -2198,8 +2204,6 @@ because xbuild doesn't support framework reference assemblies. <_AndroidCreatePackagePerAbi>False <_ApkOutputPath>$(_BaseZipIntermediate) - <_RootPath>root/ - <_DalvikPath>dex/ <_BundleAssemblies>$(BundleAssemblies) <_BundleNativeLibraries>$(_BundleResultNativeLibraries) @@ -2210,7 +2214,30 @@ because xbuild doesn't support framework reference assemblies. - + + + + + + + + + + + + + + IntermediateOutputPath="$(IntermediateOutputPath)">