Skip to content

Commit

Permalink
[XABT] Break BuildApk into individual tasks for each content type.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Dec 11, 2024
1 parent 894ecea commit e4eaea9
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 177 deletions.
124 changes: 0 additions & 124 deletions src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,15 @@ 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; }

public bool EmbedAssemblies { get; set; }

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; }
Expand All @@ -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]
Expand Down Expand Up @@ -134,39 +117,20 @@ bool _Debug {

SequencePointsMode sequencePointsMode = SequencePointsMode.None;

public ITaskItem[] LibraryProjectJars { get; set; }
HashSet<string> 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<string> existingEntries = new List<string> ();

List<Regex> excludePatterns = new List<Regex> ();

List<Regex> includePatterns = new List<Regex> ();

void ExecuteWithAbi (DSOWrapperGenerator.Config dsoWrapperConfig, string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>> 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 ();
Expand All @@ -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);
Expand All @@ -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<string> ());
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 ();
Expand Down Expand Up @@ -280,13 +181,6 @@ public override bool RunTask ()

existingEntries.Clear ();

foreach (var pattern in ExcludeFiles ?? Array.Empty<string> ()) {
excludePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled));
}
foreach (var pattern in IncludeFiles ?? Array.Empty<string> ()) {
includePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled));
}

bool debug = _Debug;
bool compress = !debug && EnableCompression;
IDictionary<AndroidTargetArch, Dictionary<string, CompressedAssemblyInfo>> compressedAssembliesInfo = null;
Expand All @@ -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
Expand Down
54 changes: 30 additions & 24 deletions src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ namespace Xamarin.Android.Tasks;

/// <summary>
/// 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.
/// </summary>
public class BuildArchive : AndroidTask
{
Expand All @@ -34,34 +36,17 @@ public class BuildArchive : AndroidTask

public string? ZipFlushSizeLimit { get; set; }

readonly HashSet<string> uncompressedFileExtensions;
readonly CompressionMethod uncompressedMethod = CompressionMethod.Store;
HashSet<string>? uncompressedFileExtensions;
HashSet<string> UncompressedFileExtensionsSet => uncompressedFileExtensions ??= ParseUncompressedFileExtensions ();

public BuildArchive ()
{
uncompressedFileExtensions = new HashSet<string> (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
Expand Down Expand Up @@ -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<string> ParseUncompressedFileExtensions ()
{
var uncompressedFileExtensions = new HashSet<string> (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;
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Collects Dalvik classes to be added to the final archive.
/// </summary>
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<ITaskItem> ();

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;
}
}
Loading

0 comments on commit e4eaea9

Please sign in to comment.