-
Notifications
You must be signed in to change notification settings - Fork 81
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
Start KSP dual implementation #722
Conversation
f6181b4
to
6e11193
Compare
) | ||
addAll(mode.symbolProcessorProviders) | ||
} | ||
kspWithCompilation = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we force KSP to compile in-process during the kotlinc invocation, just for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we force KSP to compile in-process during the kotlinc invocation, just for testing.
Maybe worth adding this as a code comment? Unless that's already documented on the KCT side
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's on the kdoc iirc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added one in d51fc90
(#722)
pluginOptions += | ||
listOf( | ||
PluginOption( | ||
pluginId = anvilCommandLineProcessor.pluginId, | ||
optionName = "src-gen-dir", | ||
optionValue = File(workingDir, "build/anvil").absolutePath | ||
), | ||
PluginOption( | ||
pluginId = anvilCommandLineProcessor.pluginId, | ||
optionName = "generate-dagger-factories", | ||
optionValue = generateDaggerFactories.toString() | ||
), | ||
PluginOption( | ||
pluginId = anvilCommandLineProcessor.pluginId, | ||
optionName = "generate-dagger-factories-only", | ||
optionValue = generateDaggerFactoriesOnly.toString() | ||
) | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gated these off now that they may not be necessary in all compilations
@@ -61,6 +65,13 @@ class AnvilCommandLineProcessor : CommandLineProcessor { | |||
"@MergeComponent or @MergeSubcomponent.", | |||
required = false, | |||
allowMultipleOccurrences = false | |||
), | |||
CliOption( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this option so we could correctly gate the modes, but have not wired it up in the gradle plugin yet. I figure we can wait on that until everything's fully implemented
@@ -0,0 +1,9 @@ | |||
package com.squareup.anvil.compiler.api | |||
|
|||
public interface AnvilApplicabilityChecker { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal with this is to be able to hoist up the applicability checks to shared locations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High level looks great so far!
I wasn't quite able to make it through the whole review yet, leaving some initial questions and comments for now.
@@ -34,7 +38,7 @@ class AnvilCommandLineProcessor : CommandLineProcessor { | |||
optionName = srcGenDirName, | |||
valueDescription = "<file-path>", | |||
description = "Path to directory in which Anvil specific code should be generated", | |||
required = true, | |||
required = false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this optional now? Do we lose the ability to configure this with KSP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need it at all in KSP
), | ||
CliOption( | ||
optionName = compilationModeName, | ||
valueDescription = "<embedded|ksp>", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add constants for both of these values? Looks like we could probably utilize the existing types you created in AnvilCompilationMode
or at least house the values there if we don't want them directly tied to the Type
names. Just wanna make sure these can all be updated in one place if they change since they're already referenced in a couple places (CommandLineOptions
, AnvilComponentRegistrar
, and based on the enum name in AnvilCompilation
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted in 4ed03de
@@ -77,6 +88,8 @@ class AnvilCommandLineProcessor : CommandLineProcessor { | |||
configuration.put(generateDaggerFactoriesOnlyKey, value.toBoolean()) | |||
disableComponentMergingName -> | |||
configuration.put(disableComponentMergingKey, value.toBoolean()) | |||
compilationModeName -> | |||
configuration.put(compilationModeKey, value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know if CommandLineProcessor
automatically validates inputs based on the provided value description? I didn't see anything from a quick search, but if it's not handled automatically I think we should add a quick check that the input matches one of the expected values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not validate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some + a stronger typed parsing in 4ed03de
) | ||
addAll(mode.symbolProcessorProviders) | ||
} | ||
kspWithCompilation = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we force KSP to compile in-process during the kotlinc invocation, just for testing.
Maybe worth adding this as a code comment? Unless that's already documented on the KCT side
} | ||
kspWithCompilation = true | ||
kspArgs["generate-dagger-factories"] = generateDaggerFactories.toString() | ||
kspArgs["generate-dagger-factories-only"] = generateDaggerFactoriesOnly.toString() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we not use PluginOption for these still / why is there this API difference for KSP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KSP uses its own API yeah. Modeled after how annotation processing options are
block: Result.() -> Unit = { }, | ||
): Result { | ||
block: KotlinCompilation.Result.() -> Unit = { }, | ||
): KotlinCompilation.Result { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Any reason to make these Result
usages more qualified? I see we're still importing com.tschuchort.compiletesting.KotlinCompilation.Result
and there are other usages left unqualified. I'm good with either style but I think it's worth keeping them consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because kotlin.Result is an implicit import and they cause issues. This is why I changed the name (and updated in the KCT update PR)
} | ||
|
||
@AutoService(CodeGenerator::class) | ||
internal class AnvilGenerator : CodeGenerator { |
There was a problem hiding this comment.
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 matching this up with the compilation mode naming?
internal class AnvilGenerator : CodeGenerator { | |
internal class EmbeddedGenerator : CodeGenerator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 074d5cb
(#722)
|
||
override fun processChecked(resolver: Resolver): List<KSAnnotated> { | ||
resolver | ||
.getSymbolsWithAnnotation(contributesBindingFqName.asString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting to get familiar with the KSP apis as part of this review and I see there's also Resolver#getAllFiles()
used in some of the samples, which looks very similar to our previous logic around iterating over all project files. Does getSymbolsWithAnnotation
handle checking all files as well as inner classes that may be annotated? It seems that way but just want to clarify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this API is the preferred one and handles all that logic for us in a heavily cached and optimized way
env.logger.error( | ||
"Only classes can be annotated with @ContributesBinding.", annotated | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice
spec.writeTo( | ||
env.codeGenerator, | ||
aggregating = false, | ||
originatingKSFiles = listOf(clazz.containingFile!!) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's awesome that this is already built into the API (for both KSP and KotlinPoet) 🙌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup one of the first things I implemented for KP's KSP support 😁
import com.squareup.anvil.compiler.qualifierFqName | ||
import org.jetbrains.kotlin.name.FqName | ||
|
||
internal fun <T : KSAnnotation> List<T>.checkNoDuplicateScopeAndBoundType( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of me is wondering if we should add some light layer of abstraction to share the larger utility extensions between ksp and embedded/reference impls, but I think this is fine for now and we can do some combining later if it there ends up being enough duplication to be worth it.
@@ -13,6 +13,10 @@ dependencyResolutionManagement { | |||
String keyString = key.toString() | |||
if (keyString.startsWith("override_")) { | |||
overrides.put(keyString, value.toString()) | |||
if (keyString.endsWith("_kotlin")) { | |||
// TODO hardcoded to match what's in libs.versions.toml, but kinda ugly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to change in this PR but in a followup could you add more detail around why we need to do this / when it will no longer be necessary? Is this to work around Kotlin 1.8 support?
This implements dual support for anvil code gen and KSP code gen using
ContributesBinding
andAnvilAnnotationDetectorCheck
.The general design is like this
Where the common code gen implementation lives in the parent object and frontend-specific impls live in nested classes. The impl is agnostic to KSP and Anvil+PSI.
To support this, I've also added new testing support to anvil compilation tests for KSP. This primarily runs through a new
AnvilCompilationMode
API, which supports two modes:Embedded
(the traditional anvil way) andKsp
. This PR's a little larger due to this ground work, but future PRs should be simpler as we continue to replicate some of these common utilities and infra.