Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to AGP 8 APIs and off of legacy variants API #1136

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

ZacSweers
Copy link
Contributor

This migrates the plugin to newer AGP 8.0 APIs and using AndroidComponents APIs. This offers a few benefits:

  • Lazier evaluation across the board
  • Depending on artifact outputs rather than tasks

Note that MergeResources doesn't yet have a public artifact, but does have an internal one. Since Paparazzi uses multiple other internal artifact APIs, I figured this was fair game to use.

I did leave one extra note as well - there's a compile*WithJavac task gated on a BaseJavaPlugin block, but this task is actually created by KGP. Didn't adjust it in this PR to reduce scope, but FYI.

Copy link
Contributor

@TWiStErRob TWiStErRob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice, I'm glad you made it work cleaner than our previous try. It's always nice to learn something new from PRs.

Tip for future reviewers: use IDEA diff with "Ignore whitespaces", GitHub mostly red/green because of indent changes.

Comment on lines +223 to +225
project.tasks
.matching { it.name == "compile${testVariantSlug}KotlinAndroid" }
.configureEach { it.dependsOn(writeResourcesTask) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, this has a similar effect to afterEvaluate { tasks.named("...").configure {, just way way way better in terms of laziness, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep it's a lazier approach and a little more correct, as afterEvaluate doesn't necessarily guarantee it since there can be multiple afterEvaluate levels

import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.TestedExtension
import com.android.build.gradle.api.BaseVariant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For historical context, this was tried before:
Related PR: #980
and comment: #960 (comment)

but it looks like you made it work with less hacking. 👏

Comment on lines +211 to 213
.matching {
it.name == "compile${testVariantSlug}JavaWithJavac"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this

Suggested change
.matching {
it.name == "compile${testVariantSlug}JavaWithJavac"
}
.matching { it.name == "compile${testVariantSlug}JavaWithJavac" }

for consistency with other similar structures below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just ran spotless and checked in what it output. Not going to deviate from that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, interesting...

Comment on lines 258 to 259
}.configureEach { rawTest ->
val test = rawTest as Test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you know this also works? (yay Smart Casts!)

Suggested change
}.configureEach { rawTest ->
val test = rawTest as Test
}.configureEach { test ->
test as Test

It might be simpler, but maybe too magical?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the practical approach

@@ -829,7 +831,7 @@ class PaparazziPluginTest {
assertThat(resourceFileContents[1]).isEqualTo("intermediates/merged_res/debug")
assertThat(resourceFileContents[4]).isEqualTo("intermediates/assets/debug")
assertThat(resourceFileContents[5]).isEqualTo("app.cash.paparazzi.plugin.test,com.example.mylibrary,app.cash.paparazzi.plugin.test.module1,app.cash.paparazzi.plugin.test.module2")
assertThat(resourceFileContents[6]).isEqualTo("src/main/res,src/debug/res")
assertThat(resourceFileContents[6]).isEqualTo("build/generated/res/resValues/debug,src/debug/res,src/main/res")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mind the order of debug vs main: https://github.com/cashapp/paparazzi/pull/980/files#r1254387829

It's important because of variant folder precedence, I think.

Related (depending on where the reversal happens): #980 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main is not a flavor/build type, so I don't see why this matters? This order is determined by AGP's artifact API, not something we can change anyway

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinzheng-ap, can you please explain in why the order matters (if it does)?

(From the earlier PR I understood it's because Paparazzi/layoutlib is emulating a full-on Android build/runtime, so the folder order determines which resources are loaded at runtime. Consider having a same named resource in src/main/res/values/x.xml and src/debug/res/values/x.xml and what'll be rendered on the screenshot vs on device.)

not something we can change anyway

It's up to Paparazzi how it interprets and uses the AGP (internal?) APIs.

// exhaustive to avoid potential breaking changes in future AGP releases
else -> throw IllegalStateException("${extension.javaClass.name} from $plugin is not supported in Paparazzi")
val extension = project.extensions.getByType(AndroidComponentsExtension::class.java)
check(extension is LibraryAndroidComponentsExtension || extension is ApplicationAndroidComponentsExtension || extension is DynamicFeatureAndroidComponentsExtension) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still necessary?

Is there maybe a one-to-one mapping between plugin ID and androidComponents { } type we can rely on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a TestAndroidComponentsExtension option out there, and possibly others. This was just a straightforward reimpl of the previous check

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't include enough context in the highlight:

val supportedPlugins = listOf("com.android.application", "com.android.library", "com.android.dynamic-feature")
...
supportedPlugins.forEach { plugin ->
    project.plugins.withId(plugin) {
        val extension = project.extensions.getByType(AndroidComponentsExtension::class.java)
        // At this point we can be quite sure extension is not `TestAndroidComponentsExtension`,
        // because that would imply someone applied `com.android.test` plugin,
        // which is mutually exclusive to all the others in `supportedPlugins`.

anyway, for a 1:1 conversion 👍🏻

@jrodbx
Copy link
Collaborator

jrodbx commented Jan 23, 2024

@ZacSweers mind: 1) rebasing and 2) inlining configureVariant for a better diff before another review?

@jrodbx jrodbx modified the milestones: 1.4, 1.3.3 Jan 23, 2024
@jrodbx jrodbx added the waiting on user Waiting on information from OP label Jan 25, 2024
@jrodbx jrodbx modified the milestones: 1.3.3, 1.3.4 Feb 26, 2024
@jrodbx jrodbx modified the milestones: 1.3.4, 1.4 Apr 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting on user Waiting on information from OP
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants