diff --git a/Documentation/guides/building-apps/build-items.md b/Documentation/guides/building-apps/build-items.md index 1191532ceb2..305d34ca957 100644 --- a/Documentation/guides/building-apps/build-items.md +++ b/Documentation/guides/building-apps/build-items.md @@ -204,6 +204,26 @@ used to specify the ABI that the library targets. Thus, if you add ``` +## AndroidPackagingOptionsExclude + +A set of file glob compatible items which will allow for items to be +excluded from the final package. The default values are as follows + +``` + + + + +``` +Items can use file blob characters for wildcards such as `*` and `?`. +However these Items MUST use URL encoding or '$([MSBuild]::Escape(''))'. +This is so MSBuild does not try to interpret them as actual file wildcards. + +NOTE: `*`, `?` and `.` will be replaced in the `BuildApk` task with the +appropriate RegEx expressions. + +Added in Xamarin.Android 13.1 and .NET 7. + ## AndroidResource All files with an *AndroidResource* build action are compiled into diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs index acb9ab380d8..28cca622f9b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs @@ -74,6 +74,8 @@ public class BuildApk : AndroidTask public string[] DoNotPackageJavaLibraries { get; set; } + public string [] ExcludeFiles { get; set; } + public string Debug { get; set; } public string AndroidSequencePointsMode { get; set; } @@ -122,6 +124,8 @@ protected virtual void FixupArchive (ZipArchiveEx zip) { } List existingEntries = new List (); + List excludePatterns = new List (); + void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary compressedAssembliesInfo, string assemblyStoreApkName) { ArchiveFileList files = new ArchiveFileList (); @@ -248,6 +252,13 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut Log.LogDebugMessage ($"Skipping {path} as the archive file is up to date."); continue; } + // check for ignored items + 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}'."); + 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; @@ -293,6 +304,10 @@ public override bool RunTask () existingEntries.Clear (); + foreach (var pattern in ExcludeFiles ?? Array.Empty ()) { + excludePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + bool debug = _Debug; bool compress = !debug && EnableCompression; IDictionary compressedAssembliesInfo = null; @@ -326,6 +341,24 @@ 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 AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary compressedAssembliesInfo, string assemblyStoreApkName) { string sourcePath; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index 6a4b9917b44..3e480fd8d6c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -915,6 +915,26 @@ public class Test } } + [Test] + public void CheckExcludedFilesAreMissing () + { + + var proj = new XamarinAndroidApplicationProject () { + IsRelease = true, + }; + proj.PackageReferences.Add (KnownPackages.Xamarin_Kotlin_StdLib_Common); + using (var b = CreateApkBuilder ()) { + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + var apk = Path.Combine (Root, b.ProjectDirectory, + proj.OutputPath, $"{proj.PackageName}-Signed.apk"); + string expected = $"Ignoring jar entry 'kotlin/Error.kotlin_metadata'"; + Assert.IsTrue (b.LastBuildOutput.ContainsText (expected), $"Error.kotlin_metadata should have been ignored."); + using (var zip = ZipHelper.OpenZip (apk)) { + Assert.IsFalse (zip.ContainsEntry ("Error.kotlin_metadata"), "Error.kotlin_metadata should have been ignored."); + } + } + } + [Test] public void ExtractNativeLibsTrue () { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs index 3757f53f860..3cac7b400be 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs @@ -499,6 +499,10 @@ public static class KnownPackages Id = "Xamarin.GooglePlayServices.Maps", Version = "117.0.1.2", }; + public static Package Xamarin_Kotlin_StdLib_Common = new Package { + Id = "Xamarin.Kotlin.Stdlib.Common", + Version = "1.6.20.1" + }; public static Package Acr_UserDialogs = new Package { Id = "Acr.UserDialogs", Version = "6.5.1", diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 686fb70c67d..5abab6d0d89 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -351,6 +351,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + + + + + + @@ -2096,6 +2107,7 @@ because xbuild doesn't support framework reference assemblies. IncludeWrapSh="$(AndroidIncludeWrapSh)" CheckedBuild="$(_AndroidCheckedBuild)" RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)" + ExcludeFiles="@(AndroidPackagingOptionsExclude)" UseAssemblyStore="$(AndroidUseAssemblyStore)">