Skip to content

Commit 581a433

Browse files
authored
Android: exclude the main apk deps from the test apk (#6073)
This is related to #6064 ### Improvements - Aligning base module and androidTest dependencies is [no longer necessary](https://github.com/vaslabs-ltd/compose-samples-with-mill/pull/6/files#diff-35fe6f53838971f482104d05f316647dead2c9fff13e5df4f7f2c7ad200e6a0dL95) - Properly passing compile classpath to R8 makes the R8 workaround error -> warning mapping [not necessary](https://github.com/vaslabs-ltd/compose-samples-with-mill/pull/6/files#diff-35fe6f53838971f482104d05f316647dead2c9fff13e5df4f7f2c7ad200e6a0dL85) - The test apk size is now greatly reduced (from >20MB to <1MB in most cases) - With the new AndroidR8InstrumentedTestsModule , androidTests apk can also be minified as the `androidGeneratedMinifyKeepRules` is only available in R8 modules | App | Mill Size | Gradle Size | |------ | ----------- | ------------------| | Jetchat | 760KB | 1019KB | | JetNews|742KB|999KB| | Architecture Samples | 4.3MB | 5.6MB | ### Fixes - Cyclic dependency issue when compiling r classes - Jetchat android tests ### Implementation details Introduced a new `AndroidR8InstrumentedTestsModule` which builds the test apk excluding the dependency tree of the base module (base apk) with 2 important tasks: - `androidR8CompileOnlyClasspath` incorporates the whole base apk (but as a jar file) - `androidResolvedPackagableMvnDeps` excludes the dependencies of the base module via `androidPackagableMvnDeps` and is used in place of `resolvedRunMvnDeps` . ### Extensions To do the dependency resolution again by excluding the dependency tree, without breaking the examples, I needed a way to pass the bom to the `defaultResolver().classpath` . Thus I extended the methods needed and fixed any MiMa issues with `@unroll` , although I haven't used it before, this is what I've found from looking around, so please let me know if this was a mistake ### Further work Testing a few apps, there was a case where a test dependency was pulling `com.google.android.material:material` which in turn depends on appcompat android resources, so aapt2 linking was failing. I tried to see if `aapt2 link` `-I` argument would be of any help, but it seems I'm missing something, it appears only one artifact can be passed to it, tried several usage variations without luck Gradle incorporates these resources without an issue, so it means we need to find a way to link the resources against the base module resources and at the same time avoid duplicate contents I think it's a very niche use case, as typically runtime usage of these resources will be done in the base apk. For now, I added the appcompat explicitly in the androitTest mvnDeps so the linking goes through
1 parent 077bbf1 commit 581a433

File tree

8 files changed

+240
-153
lines changed

8 files changed

+240
-153
lines changed

example/thirdparty/android-compose-samples/build.mill

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,6 @@ object Jetchat extends mill.api.Module {
273273
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2",
274274
mvn"androidx.activity:activity-compose:1.10.1",
275275
mvn"androidx.core:core-ktx:1.16.0",
276-
mvn"androidx.appcompat:appcompat:1.7.0",
277276
mvn"androidx.compose.runtime:runtime-livedata",
278277
mvn"androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0",
279278
mvn"androidx.lifecycle:lifecycle-runtime-compose:2.9.0",
@@ -288,8 +287,7 @@ object Jetchat extends mill.api.Module {
288287
mvn"androidx.databinding:viewbinding:8.13.0",
289288
mvn"androidx.compose.ui:ui-tooling",
290289
mvn"androidx.compose.ui:ui-tooling-preview",
291-
mvn"androidx.compose.ui:ui-test-manifest",
292-
mvn"com.google.android.material:material:1.6.0"
290+
mvn"androidx.compose.ui:ui-test-manifest"
293291
)
294292

295293
def androidSdkModule = mill.api.ModuleRef(androidSdkModule0)
@@ -298,12 +296,6 @@ object Jetchat extends mill.api.Module {
298296

299297
def androidMinSdk = Versions.androidMinSdk
300298

301-
/*
302-
* FIXME Currently broken with
303-
* Message 2) profileScreen_back_conversationScreen(com.example.compose.jetchat.NavigationTest)
304-
* android.content.res.Resources$NotFoundException: String resource ID #0x7f0c002b
305-
* Message android.content.res.Resources$NotFoundException: String resource ID #0x7f0c002b
306-
*/
307299
object androidTest extends AndroidAppKotlinInstrumentedTests, AndroidR8AppModule {
308300
override def bomMvnDeps = super.mvnDeps() ++ Seq(
309301
mvn"androidx.compose:compose-bom:2025.08.00"
@@ -317,9 +309,6 @@ object Jetchat extends mill.api.Module {
317309
true
318310
}
319311

320-
// FIXME: R8 should compile without missing classes errors
321-
override def androidR8Args = Seq("--map-diagnostics", "error", "warning")
322-
323312
def mvnDeps = super.mvnDeps() ++ Seq(
324313
mvn"junit:junit:4.13.2",
325314
mvn"androidx.test:core:1.6.1",
@@ -330,10 +319,8 @@ object Jetchat extends mill.api.Module {
330319
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2",
331320
mvn"androidx.compose.ui:ui-test",
332321
mvn"androidx.compose.ui:ui-test-junit4",
333-
mvn"androidx.collection:collection-ktx:1.5.0",
334-
mvn"androidx.savedstate:savedstate-ktx:1.3.0",
335-
mvn"androidx.appcompat:appcompat:1.7.0",
336-
mvn"androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0"
322+
// FIXME temporary workaround to avoid linking error
323+
mvn"androidx.appcompat:appcompat:1.7.0"
337324
)
338325

339326
}

example/thirdparty/androidtodo/build.mill

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ object app
6767

6868
def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq(
6969
mvn"androidx.core:core-ktx:1.15.0",
70-
mvn"androidx.appcompat:appcompat:1.7.0",
7170
mvn"androidx.annotation:annotation:1.9.1",
7271
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0",
7372
mvn"com.jakewharton.timber:timber:5.0.1",
@@ -145,7 +144,7 @@ object app
145144

146145
object androidTest
147146
extends AndroidAppKotlinInstrumentedTests,
148-
AndroidR8AppModule,
147+
AndroidR8InstrumentedTestsModule,
149148
AndroidHiltSupport {
150149

151150
def moduleDeps = super.moduleDeps ++ Seq(`shared-test`)
@@ -164,7 +163,6 @@ object app
164163
}
165164

166165
override def androidProjectProguardFiles = Task.Sources(
167-
"proguard-rules.pro",
168166
"proguardTest-rules.pro"
169167
)
170168
override def androidDefaultProguardFileNames: Task[Seq[String]] = Task.Anon {
@@ -184,6 +182,7 @@ object app
184182
// Dependencies for Android unit tests
185183
mvn"junit:junit:4.13.2",
186184
mvn"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0",
185+
mvn"androidx.appcompat:appcompat:1.7.0",
187186
// AndroidX Test - Instrumented testing
188187
mvn"androidx.test:core-ktx:1.6.1",
189188
mvn"androidx.test.ext:junit-ktx:1.2.1",

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import mill.javalib.*
1212
import os.{Path, RelPath, zip}
1313
import os.RelPath.stringRelPathValidated
1414
import upickle.*
15-
import scala.concurrent.duration.*
1615

16+
import scala.concurrent.duration.*
1717
import scala.jdk.OptionConverters.RichOptional
1818
import scala.xml.*
1919
import mill.api.daemon.internal.bsp.BspBuildTarget
2020
import mill.api.daemon.internal.EvaluatorApi
2121
import mill.javalib.testrunner.TestResult
22+
2223
import scala.util.Properties.isWin
2324

2425
/**
@@ -134,12 +135,13 @@ trait AndroidAppModule extends AndroidModule { outer =>
134135
)
135136

136137
/**
137-
* Collect files from META-INF folder of classes.jar (not META-INF of aar in case of Android library).
138+
* Collect files from META-INF folder of [[androidPackagedDeps]] (not META-INF of aar in case of Android library).
139+
* to include in the apk
138140
*/
139141
def androidLibsClassesJarMetaInf: T[Seq[PathRef]] = Task {
140142
// ^ not the best name for the method, but this is to distinguish between META-INF of aar and META-INF
141143
// of classes.jar included in aar
142-
compileClasspath()
144+
androidPackagedDeps()
143145
.filter(ref =>
144146
ref.path.ext == "jar" &&
145147
ref != androidSdkModule().androidJarPath()
@@ -178,7 +180,6 @@ trait AndroidAppModule extends AndroidModule { outer =>
178180
}
179181
})
180182
.map(PathRef(_))
181-
.toSeq
182183
}
183184

184185
/**
@@ -189,7 +190,7 @@ trait AndroidAppModule extends AndroidModule { outer =>
189190
def androidPackageableExtraFiles: T[Seq[AndroidPackageableExtraFile]] =
190191
Task { Seq.empty[AndroidPackageableExtraFile] }
191192

192-
def androidPackageMetaInfoFiles: T[Seq[AndroidPackageableExtraFile]] = Task {
193+
def androidPackagedMetaInfFiles: T[Seq[AndroidPackageableExtraFile]] = Task {
193194
def metaInfRoot(p: os.Path): os.Path = {
194195
var current = p
195196
while (!current.endsWith(os.rel / "META-INF")) {
@@ -239,7 +240,7 @@ trait AndroidAppModule extends AndroidModule { outer =>
239240
(androidPackageableExtraFile.source.path, androidPackageableExtraFile.destination.asSubPath)
240241
)
241242

242-
val metaInf = androidPackageMetaInfoFiles().map(asZipSource)
243+
val metaInf = androidPackagedMetaInfFiles().map(asZipSource)
243244

244245
val nativeDeps = androidPackageableNativeDeps().map(asZipSource)
245246

@@ -894,9 +895,6 @@ trait AndroidAppModule extends AndroidModule { outer =>
894895

895896
override def androidIsDebug: T[Boolean] = Task { true }
896897

897-
override def moduleDeps: Seq[JavaModule] = Seq.empty
898-
override def compileModuleDeps: Seq[JavaModule] = Seq(outer)
899-
900898
override def resolutionParams: Task[ResolutionParams] = Task.Anon(outer.resolutionParams())
901899

902900
override def androidApplicationId: String = s"${outer.androidApplicationId}.test"
@@ -962,7 +960,7 @@ trait AndroidAppModule extends AndroidModule { outer =>
962960
}
963961

964962
private def androidxTestManifests: Task[Seq[PathRef]] = Task {
965-
androidUnpackArchives().flatMap {
963+
androidUnpackRunArchives().flatMap {
966964
unpackedArchive =>
967965
unpackedArchive.manifest.map(_.path)
968966
}.filter {
@@ -1040,8 +1038,8 @@ trait AndroidAppModule extends AndroidModule { outer =>
10401038
val device = androidTestInstall().apply()
10411039

10421040
val instrumentOutput = os.proc(
1043-
(
1044-
androidSdkModule().adbExe().path,
1041+
Seq(
1042+
androidSdkModule().adbExe().path.toString,
10451043
"-s",
10461044
device,
10471045
"shell",
@@ -1084,10 +1082,6 @@ trait AndroidAppModule extends AndroidModule { outer =>
10841082
.map(PathRef(_))
10851083
}
10861084

1087-
override def androidPackagedDeps: T[Seq[PathRef]] = Task {
1088-
androidResolvedRunMvnDeps()
1089-
}
1090-
10911085
/**
10921086
* The instrumented tests are packaged with testClasspath which already contains the
10931087
* user compiled classes

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ trait AndroidModule extends JavaModule { outer =>
525525
sources = androidLibsRClasses().map(_.path),
526526
compileClasspath = Seq.empty,
527527
javacOptions = jOpts.compiler,
528-
incrementalCompilation = zincIncrementalCompilation()
528+
incrementalCompilation = true
529529
),
530530
javaHome = javaHome().map(_.path),
531531
javaRuntimeOptions = jOpts.runtime,

0 commit comments

Comments
 (0)