Open PaulWoitaschek opened 2 years ago
Ok, took a shot at this (branch), and it's a bit tricky.
We can read the buildFeatures.androidResources flag off the LibraryBuildFeatures extension and use that to:
but...if the view/composable under snapshot is part of a larger view/composable hierarchy which looks up resources, then things will still crash because of 1. Ultimately variant.mergeResourcesProvider is not present when androidResources = false, but we kinda need it, in order to use its output directory in order to read the other transitive resources.
So, I think:
Technically, this is now fixed by the work done in https://github.com/cashapp/paparazzi/issues/524, but let's move this and close in an upcoming release, once we deem the new mechanism stable and the old code deleted.
I do not see this working out-of-the-box with 1.3.1. Stacktrace is mostly the same
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property mergeResourcesTask has not been initialized
at com.android.build.gradle.internal.scope.MutableTaskContainer.getMergeResourcesTask(MutableTaskContainer.kt:67)
at com.android.build.gradle.internal.api.BaseVariantImpl.getMergeResourcesProvider(BaseVariantImpl.java:394)
at com.android.build.gradle.internal.api.LibraryVariantImpl_Decorated.getMergeResourcesProvider(Unknown Source)
at app.cash.paparazzi.gradle.PaparazziPlugin$setupPaparazzi$1.invoke(PaparazziPlugin.kt:119)
at app.cash.paparazzi.gradle.PaparazziPlugin$setupPaparazzi$1.invoke(PaparazziPlugin.kt:115)
at app.cash.paparazzi.gradle.PaparazziPlugin.setupPaparazzi$lambda$7(PaparazziPlugin.kt:115)
at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:123)
at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1.run(DefaultCollectionCallbackActionDecorator.java:110)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction.execute(DefaultCollectionCallbackActionDecorator.java:107)
at org.gradle.internal.ImmutableActionSet$SetWithFewActions.execute(ImmutableActionSet.java:285)
at org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:262)
at org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:251)
at com.android.build.gradle.LibraryExtension.addVariant(LibraryExtension.kt:102)
at com.android.build.gradle.internal.ApiObjectFactory.create(ApiObjectFactory.java:119)
... 188 more
Seems like the legacy codepath is only ignored at runtime and not within the plugin which still wires its folders through the task. So that's why we have to wait until the legacy codepath is fully deleted.
@PaulWoitaschek I delved deeper into this issue, and noticed currently we cannot support the module with androidresources=false
, even if deleting legacy codepath for resource loading.
The reason is today Paparazzi is using ComposeView
to render snapshot for compose and ComposeView
is using android resource internally
java.lang.NoClassDefFoundError: androidx/customview/poolingcontainer/R$id
at androidx.customview.poolingcontainer.PoolingContainer.<clinit>(PoolingContainer.kt:121)
at androidx.compose.ui.platform.ViewCompositionStrategy$DisposeOnDetachedFromWindowOrReleasedFromPool.installFor(ViewCompositionStrategy.android.kt:97)
at androidx.compose.ui.platform.AbstractComposeView.<init>(ComposeView.android.kt:125)
at androidx.compose.ui.platform.ComposeView.<init>(ComposeView.android.kt:418)
at androidx.compose.ui.platform.ComposeView.<init>(ComposeView.android.kt:414)
at app.cash.paparazzi.Paparazzi.snapshot(Paparazzi.kt:199)
at app.cash.paparazzi.Paparazzi.snapshot$default(Paparazzi.kt:198)
at app.cash.paparazzi.sample.HelloComposeTest.compose(HelloComposeTest.kt:25)
But there is no issue for compose preview in Android Studio, so we need to investigate further to update how compose is rendered in Paparazzi
cc @jrodbx @JakeWharton
It looks like this is blocking SDK and Junit5 efforts, due to missing R.classes, so I'm moving this up a milestone.
I've started to explore how Android Studio does this, via ResourceClassGenerator, but don't have all the pieces figured out yet.
Here's a WIP branch if anyone is interested in collaborating on this.
@jrodbx If my memory is correct, Layoutlib
is loaded with its custom class loader, and find alternative if resources are not found. But I do not quite remember what the alternatives are.
The step I was stuck at before was not able to inject our own class loader when Junit is running.
Keep on collaborating it
I hit this same error with paparazzi 1.3.4
, AGP 8.5.1
(updated stacktrace below)
I looked at WIP branch .
I definitely don't understand everything going here, but just noting down some thoughts, sorry if this is redundant. 😅
I have android.buildFeatures.androidResources = false
in lib/build.gradle.kts
( :lib
doesn't contain any android resources directly)
and android.nonTransitiveRClass=true
(the default)
and if I set android.testOptions.unitTests.isIncludeAndroidResources = true
in lib/build.gradle.kts
then in lib/src/test/kotlin
, then this will print a non-zero id, so all the transitive R classes are still accessible to my unit tests out of the box.
@Test
fun accessTransitiveR(){
println(androidx.core.R.string.call_notification_screening_text)
}
and com/android/tools/test_config.properties
has android_resource_apk
property which gives the location of the zip with all the transitive resources.
So even with android.buildFeatures.androidResources = false
seems like all the pieces of the resources for all transitive deps are there and can be had without access to mergeResourcesTask
?
(I am sure there is a lot I am missing here, and don't know all the resources magic that paparazzi is doing here)
updated stacktrace:
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property mergeResourcesTask has not been initialized
at com.android.build.gradle.internal.scope.MutableTaskContainer.getMergeResourcesTask(MutableTaskContainer.kt:67)
at com.android.build.gradle.internal.api.BaseVariantImpl.getMergeResourcesProvider(BaseVariantImpl.java:399)
at com.android.build.gradle.internal.api.LibraryVariantImpl_Decorated.getMergeResourcesProvider(Unknown Source)
at app.cash.paparazzi.gradle.PaparazziPlugin$setupPaparazzi$1$writeResourcesTask$1.invoke(PaparazziPlugin.kt:160)
at app.cash.paparazzi.gradle.PaparazziPlugin$setupPaparazzi$1$writeResourcesTask$1.invoke(PaparazziPlugin.kt:144)
at app.cash.paparazzi.gradle.PaparazziPlugin$setupPaparazzi$1.invoke$lambda$2(PaparazziPlugin.kt:147)
at org.gradle.api.internal.DefaultMutationGuard$1.execute(DefaultMutationGuard.java:45)
at org.gradle.api.internal.DefaultMutationGuard$1.execute(DefaultMutationGuard.java:45)
Description We have several gradle modules that are pure compose. Our project default is
android.library.defaults.buildfeatures.androidresources=false
. If you run paparrazi on a module without androidResources, it crashes in the configuration phase.