Skip to content

Commit 2726a38

Browse files
authored
[Xamarin.Android.Build.Tasks] Add AndroidPackagingOptionsExclude (#7356)
Fixes: #6920 Certainly NuGet packages pull in Kotlin-related artifacts which aren't required at runtime. These artifacts contribute to `.apk` package size. The [recommendation for native Android developers][0] is to explicitly exclude these artifacts by using `packagingOptions`: packagingOptions { exclude 'DebugProbesKt.bin' } Xamarin.Android and .NET SDK for Android developers don't use Gradle, so the above snippet is not useful. Add support for a new `@(AndroidPackagingOptionsExclude)` item group. This contains a "search pattern" a'la the `searchPattern` parameter of [`Directory.EnumerateFiles(path, searchPattern)`][1], in which: * `*` matches 0 or more characters * `?` matches 1 character. The default items within `@(AndroidPackagingOptionsExclude)` are: * `DebugProbesKt.bin` * `*.kotlin_*` Files which match the search patterns within `@(AndroidPackagingOptionsExclude)` are *excluded* from `.aab` and `.apk` files, reducing app size. [0]: Kotlin/kotlinx.coroutines#2274 [1]: https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.enumeratefiles?view=net-6.0#system-io-directory-enumeratefiles(system-string-system-string)
1 parent 1526df0 commit 2726a38

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed

Documentation/guides/building-apps/build-items.md

+20
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,26 @@ used to specify the ABI that the library targets. Thus, if you add
204204
</ItemGroup>
205205
```
206206

207+
## AndroidPackagingOptionsExclude
208+
209+
A set of file glob compatible items which will allow for items to be
210+
excluded from the final package. The default values are as follows
211+
212+
```
213+
<ItemGroup>
214+
<AndroidPackagingOptionsExclude Include="DebugProbesKt.bin" />
215+
<AndroidPackagingOptionsExclude Include="$([MSBuild]::Escape('*.kotlin_*')" />
216+
</ItemGroup>
217+
```
218+
Items can use file blob characters for wildcards such as `*` and `?`.
219+
However these Items MUST use URL encoding or '$([MSBuild]::Escape(''))'.
220+
This is so MSBuild does not try to interpret them as actual file wildcards.
221+
222+
NOTE: `*`, `?` and `.` will be replaced in the `BuildApk` task with the
223+
appropriate RegEx expressions.
224+
225+
Added in Xamarin.Android 13.1 and .NET 7.
226+
207227
## AndroidResource
208228

209229
All files with an *AndroidResource* build action are compiled into

src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs

+33
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public class BuildApk : AndroidTask
7474

7575
public string[] DoNotPackageJavaLibraries { get; set; }
7676

77+
public string [] ExcludeFiles { get; set; }
78+
7779
public string Debug { get; set; }
7880

7981
public string AndroidSequencePointsMode { get; set; }
@@ -122,6 +124,8 @@ protected virtual void FixupArchive (ZipArchiveEx zip) { }
122124

123125
List<string> existingEntries = new List<string> ();
124126

127+
List<Regex> excludePatterns = new List<Regex> ();
128+
125129
void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath, bool debug, bool compress, IDictionary<string, CompressedAssemblyInfo> compressedAssembliesInfo, string assemblyStoreApkName)
126130
{
127131
ArchiveFileList files = new ArchiveFileList ();
@@ -248,6 +252,13 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
248252
Log.LogDebugMessage ($"Skipping {path} as the archive file is up to date.");
249253
continue;
250254
}
255+
// check for ignored items
256+
foreach (var pattern in excludePatterns) {
257+
if(pattern.IsMatch (path)) {
258+
Log.LogDebugMessage ($"Ignoring jar entry '{name}' from '{Path.GetFileName (jarFile)}'. Filename matched the exclude pattern '{pattern}'.");
259+
continue;
260+
}
261+
}
251262
if (string.Compare (Path.GetFileName (name), "AndroidManifest.xml", StringComparison.OrdinalIgnoreCase) == 0) {
252263
Log.LogDebugMessage ("Ignoring jar entry {0} from {1}: the same file already exists in the apk", name, Path.GetFileName (jarFile));
253264
continue;
@@ -293,6 +304,10 @@ public override bool RunTask ()
293304

294305
existingEntries.Clear ();
295306

307+
foreach (var pattern in ExcludeFiles ?? Array.Empty<string> ()) {
308+
excludePatterns.Add (FileGlobToRegEx (pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled));
309+
}
310+
296311
bool debug = _Debug;
297312
bool compress = !debug && EnableCompression;
298313
IDictionary<string, CompressedAssemblyInfo> compressedAssembliesInfo = null;
@@ -326,6 +341,24 @@ public override bool RunTask ()
326341
return !Log.HasLoggedErrors;
327342
}
328343

344+
static Regex FileGlobToRegEx (string fileGlob, RegexOptions options)
345+
{
346+
StringBuilder sb = new StringBuilder ();
347+
foreach (char c in fileGlob) {
348+
switch (c) {
349+
case '*': sb.Append (".*");
350+
break;
351+
case '?': sb.Append (".");
352+
break;
353+
case '.': sb.Append (@"\.");
354+
break;
355+
default: sb.Append (c);
356+
break;
357+
}
358+
}
359+
return new Regex (sb.ToString (), options);
360+
}
361+
329362
void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary<string, CompressedAssemblyInfo> compressedAssembliesInfo, string assemblyStoreApkName)
330363
{
331364
string sourcePath;

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs

+20
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,26 @@ public class Test
916916
}
917917
}
918918

919+
[Test]
920+
public void CheckExcludedFilesAreMissing ()
921+
{
922+
923+
var proj = new XamarinAndroidApplicationProject () {
924+
IsRelease = true,
925+
};
926+
proj.PackageReferences.Add (KnownPackages.Xamarin_Kotlin_StdLib_Common);
927+
using (var b = CreateApkBuilder ()) {
928+
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
929+
var apk = Path.Combine (Root, b.ProjectDirectory,
930+
proj.OutputPath, $"{proj.PackageName}-Signed.apk");
931+
string expected = $"Ignoring jar entry 'kotlin/Error.kotlin_metadata'";
932+
Assert.IsTrue (b.LastBuildOutput.ContainsText (expected), $"Error.kotlin_metadata should have been ignored.");
933+
using (var zip = ZipHelper.OpenZip (apk)) {
934+
Assert.IsFalse (zip.ContainsEntry ("Error.kotlin_metadata"), "Error.kotlin_metadata should have been ignored.");
935+
}
936+
}
937+
}
938+
919939
[Test]
920940
public void ExtractNativeLibsTrue ()
921941
{

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs

+4
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ public static class KnownPackages
499499
Id = "Xamarin.GooglePlayServices.Maps",
500500
Version = "117.0.1.2",
501501
};
502+
public static Package Xamarin_Kotlin_StdLib_Common = new Package {
503+
Id = "Xamarin.Kotlin.Stdlib.Common",
504+
Version = "1.6.20.1"
505+
};
502506
public static Package Acr_UserDialogs = new Package {
503507
Id = "Acr.UserDialogs",
504508
Version = "6.5.1",

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

+12
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
351351
</AllowedReferenceRelatedFileExtensions>
352352
</PropertyGroup>
353353

354+
<ItemGroup>
355+
<AndroidPackagingOptionsExclude Include="DebugProbesKt.bin" />
356+
<AndroidPackagingOptionsExclude Include="$([MSBuild]::Escape('*.kotlin_*'))" />
357+
</ItemGroup>
358+
354359
<!--
355360
*******************************************
356361
Imports
@@ -2037,6 +2042,11 @@ because xbuild doesn't support framework reference assemblies.
20372042
Outputs="$(_BuildApkEmbedOutputs)"
20382043
Condition="'$(EmbedAssembliesIntoApk)' == 'True'">
20392044
<!-- Put the assemblies and native libraries in the apk -->
2045+
<!--
2046+
NOTE: Adding Arguments to BuildApk or BuildBaseAppBundle
2047+
also need to have the args added to Xamarin.Android.Common.Debugging.targets
2048+
in monodroid.
2049+
-->
20402050
<BuildApk
20412051
Condition=" '$(AndroidPackageFormat)' != 'aab' "
20422052
AndroidNdkDirectory="$(_AndroidNdkDirectory)"
@@ -2067,6 +2077,7 @@ because xbuild doesn't support framework reference assemblies.
20672077
IncludeWrapSh="$(AndroidIncludeWrapSh)"
20682078
CheckedBuild="$(_AndroidCheckedBuild)"
20692079
RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)"
2080+
ExcludeFiles="@(AndroidPackagingOptionsExclude)"
20702081
UseAssemblyStore="$(AndroidUseAssemblyStore)">
20712082
<Output TaskParameter="OutputFiles" ItemName="ApkFiles" />
20722083
</BuildApk>
@@ -2100,6 +2111,7 @@ because xbuild doesn't support framework reference assemblies.
21002111
IncludeWrapSh="$(AndroidIncludeWrapSh)"
21012112
CheckedBuild="$(_AndroidCheckedBuild)"
21022113
RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)"
2114+
ExcludeFiles="@(AndroidPackagingOptionsExclude)"
21032115
UseAssemblyStore="$(AndroidUseAssemblyStore)">
21042116
<Output TaskParameter="OutputFiles" ItemName="BaseZipFile" />
21052117
</BuildBaseAppBundle>

0 commit comments

Comments
 (0)