Skip to content

Commit 008e945

Browse files
authored
Android: Test apk size reduction (#5963)
For #5961 ## Implementation - Add the outer AndroidAppModule in the compileModuleDeps instead of moduleDeps - Fix some packaging mappings to use run resolved deps - Fix the compile only classpath to pass the compileModuleDeps classpaths too - By having instrumented tests inherit the `androidDefaultProguardFileNames` from the parent module, the android tests are working. It's not clear to me why, I found it due to some trial and error. To achieve this kind of inheritance without major rewiring I've moved the method to `AndroidAppModule` ## Other fixes and cleanups - Somehow androidtodo had a wrong namespace, maybe a find and replace mishap - Removed an unnecessary dep from androidtodo test module ## Outcome This avoids packaging the main (app) module dependencies and source code into the test apk. ## Result (Jetsnack) <img width="1853" height="1076" alt="image" src="https://github.com/user-attachments/assets/daba647b-8632-4ef2-b399-bc6fd14e6be6" /> ## Future work I had some difficulties reaching this point and I think the 13MB reduction is very satisfiable at this stage. In the gradle's case, it appears some classes are erased by R8 but I haven't pinpointed exactly how it does it.
1 parent 3dd89db commit 008e945

File tree

4 files changed

+68
-39
lines changed

4 files changed

+68
-39
lines changed

example/thirdparty/androidtodo/build.mill

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ object app
133133
def mvnDeps = super.mvnDeps() ++ composeDeps ++ Seq(
134134
mvn"junit:junit:4.13.2",
135135
mvn"androidx.arch.core:core-testing:2.2.0",
136-
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
137136
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
138137
mvn"androidx.navigation:navigation-testing:2.8.5",
139138
mvn"androidx.test.espresso:espresso-core:3.6.1",
@@ -185,7 +184,6 @@ object app
185184
// Dependencies for Android unit tests
186185
mvn"junit:junit:4.13.2",
187186
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
188-
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
189187
// AndroidX Test - Instrumented testing
190188
mvn"androidx.test:core-ktx:1.6.1",
191189
mvn"androidx.test.ext:junit-ktx:1.2.1",
@@ -227,7 +225,7 @@ object `shared-test` extends AndroidKotlinModule, AndroidHiltSupport {
227225

228226
def androidEnableCompose = true
229227

230-
def androidNamespace = "com.example.android.architecture.blueprints.todoapp.daemon.test"
228+
def androidNamespace = "com.example.android.architecture.blueprints.todoapp.shared.test"
231229

232230
def kotlinSymbolProcessors: T[Seq[Dep]] = Seq(
233231
mvn"androidx.room:room-compiler:2.7.1",
@@ -237,7 +235,6 @@ object `shared-test` extends AndroidKotlinModule, AndroidHiltSupport {
237235
def mvnDeps = super.mvnDeps() ++ Seq(
238236
mvn"junit:junit:4.13.2",
239237
mvn"androidx.arch.core:core-testing:2.2.0",
240-
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
241238
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
242239
mvn"androidx.test:core-ktx:1.6.1",
243240
mvn"androidx.test.ext:junit-ktx:1.2.1",

libs/androidlib/src/mill/androidlib/AndroidAppModule.scala

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ trait AndroidAppModule extends AndroidModule { outer =>
822822

823823
}
824824

825-
def knownProguardRules: T[String] = Task {
825+
def androidKnownProguardRules: T[String] = Task {
826826
// TODO need also pick proguard files from
827827
// [[moduleDeps]]
828828
androidUnpackArchives()
@@ -833,15 +833,13 @@ trait AndroidAppModule extends AndroidModule { outer =>
833833
.mkString("\n")
834834
}
835835

836-
override def androidProguard: T[PathRef] = Task {
837-
val inheritedProguardFile = super.androidProguard()
838-
val proguardFile = Task.dest / "proguard-rules.pro"
839-
840-
os.write(proguardFile, os.read(inheritedProguardFile.path))
841-
842-
os.write.append(proguardFile, knownProguardRules())
843-
844-
PathRef(proguardFile)
836+
/**
837+
* File names that are provided by the Android SDK in `androidSdkModule().androidProguardPath().path`
838+
*
839+
* For now, it's only used by [[AndroidR8AppModule]]
840+
*/
841+
def androidDefaultProguardFileNames: Task[Seq[String]] = Task.Anon {
842+
Seq.empty[String]
845843
}
846844

847845
// uses the d8 tool to generate the dex file, when minification is disabled
@@ -896,6 +894,9 @@ trait AndroidAppModule extends AndroidModule { outer =>
896894

897895
override def androidIsDebug: T[Boolean] = Task { true }
898896

897+
override def moduleDeps: Seq[JavaModule] = Seq.empty
898+
override def compileModuleDeps: Seq[JavaModule] = Seq(outer)
899+
899900
override def resolutionParams: Task[ResolutionParams] = Task.Anon(outer.resolutionParams())
900901

901902
override def androidApplicationId: String = s"${outer.androidApplicationId}.test"
@@ -913,6 +914,10 @@ trait AndroidAppModule extends AndroidModule { outer =>
913914

914915
def androidResources: T[Seq[PathRef]] = Task.Sources("src/androidTest/res")
915916

917+
override def androidDefaultProguardFileNames: Task[Seq[String]] = Task.Anon {
918+
outer.androidDefaultProguardFileNames()
919+
}
920+
916921
override def testFramework: T[String] = Task {
917922
"androidx.test.runner.AndroidJUnitRunner"
918923
}
@@ -1063,15 +1068,15 @@ trait AndroidAppModule extends AndroidModule { outer =>
10631068
* as its apk is installed separately
10641069
*/
10651070
def androidTransitiveTestClasspath: T[Seq[PathRef]] = Task {
1066-
Task.traverse(transitiveModuleCompileModuleDeps) {
1071+
Task.traverse(transitiveRunModuleDeps) {
10671072
m =>
10681073
Task.Anon(m.localRunClasspath())
10691074
}().flatten
10701075
}
10711076

10721077
/** The instrumented dex should just contain the test dependencies and locally tested files */
10731078
override def androidPackagedClassfiles: T[Seq[PathRef]] = Task {
1074-
(testClasspath() ++ androidTransitiveTestClasspath())
1079+
androidTransitiveTestClasspath()
10751080
.map(_.path).filter(os.isDir)
10761081
.flatMap(os.walk(_))
10771082
.filter(os.isFile)
@@ -1080,7 +1085,7 @@ trait AndroidAppModule extends AndroidModule { outer =>
10801085
}
10811086

10821087
override def androidPackagedDeps: T[Seq[PathRef]] = Task {
1083-
androidResolvedMvnDeps()
1088+
androidResolvedRunMvnDeps()
10841089
}
10851090

10861091
/**

libs/androidlib/src/mill/androidlib/AndroidModule.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,11 @@ trait AndroidModule extends JavaModule { outer =>
193193

194194
/**
195195
* Gets all the compiled Android resources (typically in res/ directory)
196-
* from the [[transitiveModuleCompileModuleDeps]]
196+
* from the [[transitiveModuleRunModuleDeps]]
197197
* @return a sequence of PathRef to the compiled resources
198198
*/
199199
def androidTransitiveCompiledResources: T[Seq[PathRef]] = Task {
200-
Task.traverse(transitiveModuleCompileModuleDeps) {
200+
Task.traverse(transitiveModuleRunModuleDeps) {
201201
case m: AndroidModule =>
202202
Task.Anon(m.androidCompiledModuleResources())
203203
case _ =>
@@ -264,7 +264,7 @@ trait AndroidModule extends JavaModule { outer =>
264264
override def compileClasspath: T[Seq[PathRef]] = Task {
265265
// TODO process metadata shipped with Android libs. It can have some rules with Target SDK, for example.
266266
// TODO support baseline profiles shipped with Android libs.
267-
androidDepsClasspath() ++ androidTransitiveLibRClasspath()
267+
androidDepsClasspath() ++ androidTransitiveLibRClasspath() ++ androidTransitiveModuleRClasspath()
268268
}
269269

270270
/**
@@ -522,6 +522,24 @@ trait AndroidModule extends JavaModule { outer =>
522522
}().flatten
523523
}
524524

525+
def androidTransitiveModuleRClasspath: T[Seq[PathRef]] = Task {
526+
Task.traverse(compileModuleDepsChecked) {
527+
case m: AndroidModule =>
528+
Task.Anon(Seq(m.androidProcessedResources()))
529+
case _ =>
530+
Task.Anon(Seq.empty[PathRef])
531+
}().flatten
532+
}
533+
534+
def androidTransitiveCompileOnlyClasspath: T[Seq[PathRef]] = Task {
535+
Task.traverse(compileModuleDepsChecked) {
536+
case m: AndroidModule =>
537+
Task.Anon(Seq(m.compile().classes))
538+
case _ =>
539+
Task.Anon(Seq.empty[PathRef])
540+
}().flatten
541+
}
542+
525543
/**
526544
* Namespace of the Android module.
527545
* Used in manifest package and also used as the package to place the generated R sources

libs/androidlib/src/mill/androidlib/AndroidR8AppModule.scala

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ trait AndroidR8AppModule extends AndroidAppModule {
5454
}
5555

5656
def androidLibraryProguardConfigs: Task[Seq[PathRef]] = Task {
57-
androidUnpackArchives()
57+
androidUnpackRunArchives()
5858
// TODO need also collect rules from other modules,
5959
// but Android lib module doesn't yet exist
6060
.flatMap(_.proguardRules)
@@ -71,6 +71,27 @@ trait AndroidR8AppModule extends AndroidAppModule {
7171
androidDefaultProguardFiles() ++ androidProjectProguardFiles() ++ androidLibraryProguardConfigs()
7272
}
7373

74+
/**
75+
* Creates a file for letting know R8 that [[compileModuleDeps]] and
76+
* [[compileMvnDeps]] are in compile classpath only and not packaged with the apps.
77+
* Useful for dependencies that are provided in devices and compile only module deps
78+
* such as for avoiding to package main sources in the androidTest apk.
79+
*/
80+
def androidR8CompileOnlyClasspath: T[Option[PathRef]] = Task {
81+
val resolvedCompileMvnDeps =
82+
androidResolvedCompileMvnDeps() ++ androidTransitiveCompileOnlyClasspath() ++ androidTransitiveModuleRClasspath()
83+
if (!resolvedCompileMvnDeps.isEmpty) {
84+
val compiledMvnDepsFile = Task.dest / "compile-only-classpath.txt"
85+
os.write.over(
86+
compiledMvnDepsFile,
87+
resolvedCompileMvnDeps.map(_.path.toString()).mkString("\n")
88+
)
89+
Some(PathRef(compiledMvnDepsFile))
90+
} else
91+
None
92+
93+
}
94+
7495
/** Concatenates all rules into one file */
7596
override def androidProguard: T[PathRef] = Task {
7697
val inheritedProguardFile = super.androidProguard()
@@ -98,14 +119,6 @@ trait AndroidR8AppModule extends AndroidAppModule {
98119
)
99120
}
100121

101-
/**
102-
* File names that are provided by the Android SDK in `androidSdkModule().androidProguardPath().path`
103-
* @return
104-
*/
105-
def androidDefaultProguardFileNames: Task[Seq[String]] = Task.Anon {
106-
Seq.empty[String]
107-
}
108-
109122
private def androidDefaultProguardFiles: Task[Seq[PathRef]] = Task.Anon {
110123
val dest = Task.dest
111124
androidDefaultProguardFileNames().map { fileName =>
@@ -242,18 +255,14 @@ trait AndroidR8AppModule extends AndroidAppModule {
242255

243256
r8ArgsBuilder ++= pgArgs
244257

245-
val resolvedCompileMvnDeps = androidResolvedCompileMvnDeps()
246-
if (!resolvedCompileMvnDeps.isEmpty) {
247-
val compiledMvnDepsFile = Task.dest / "compiled-mvndeps.txt"
248-
os.write.over(
249-
compiledMvnDepsFile,
250-
androidResolvedCompileMvnDeps().map(_.path.toString()).mkString("\n")
251-
)
252-
r8ArgsBuilder ++= Seq(
258+
val compileOnlyClasspath = androidR8CompileOnlyClasspath()
259+
260+
r8ArgsBuilder ++= compileOnlyClasspath.toSeq.flatMap(compiledMvnDepsFile =>
261+
Seq(
253262
"--classpath",
254-
"@" + compiledMvnDepsFile.toString
263+
"@" + compiledMvnDepsFile.path.toString
255264
)
256-
}
265+
)
257266

258267
r8ArgsBuilder ++= androidR8Args()
259268

0 commit comments

Comments
 (0)