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

Start KSP dual implementation #722

Merged
merged 18 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler-utils/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation libs.inject

testFixturesApi libs.kotlin.compileTesting
testFixturesApi libs.kotlin.compileTesting.ksp
testFixturesImplementation project(':compiler')
testFixturesImplementation libs.dagger2.compiler
testFixturesImplementation libs.dagger2
Expand All @@ -38,6 +39,7 @@ dependencies {
compileOnly libs.dagger2.compiler
compileOnly libs.junit
compileOnly libs.kotlin.compileTesting
compileOnly libs.kotlin.compileTesting.ksp
compileOnly libs.truth
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,22 @@ public val KClass<*>.fqName: FqName get() = FqName(
}
)

/** @see String.safePackageString */
@ExperimentalAnvilApi
public fun FqName.safePackageString(
dotPrefix: Boolean = false,
dotSuffix: Boolean = true
): String = toString().safePackageString(isRoot, dotPrefix, dotSuffix)

/**
* This function should only be used for package names. If the FqName is the root (no package at
* all), then this function returns an empty string whereas `toString()` would return "<root>". For
* a more convenient string concatenation the returned result can be prefixed and suffixed with an
* additional dot. The root package never will use a prefix or suffix.
*/
@ExperimentalAnvilApi
public fun FqName.safePackageString(
public fun String.safePackageString(
isRoot: Boolean = isEmpty(),
dotPrefix: Boolean = false,
dotSuffix: Boolean = true
): String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import java.io.ByteArrayOutputStream

@ExperimentalAnvilApi
public fun ClassDescriptor.asClassName(): ClassName =
Expand Down Expand Up @@ -98,14 +97,6 @@ public fun TypeName.withJvmSuppressWildcardsIfNeeded(
}
}

private fun FileSpec.writeToString(): String {
val stream = ByteArrayOutputStream()
stream.writer().use {
writeTo(it)
}
return stream.toString()
}

private fun FileSpec.Builder.suppressWarnings() {
addAnnotation(
AnnotationSpec
Expand All @@ -132,7 +123,16 @@ public fun FileSpec.Companion.buildFile(
fileName: String,
generatorComment: String = "Generated by Anvil.\nhttps://github.com/square/anvil",
block: FileSpec.Builder.() -> Unit
): String =
): String = createAnvilSpec(packageName, fileName, generatorComment, block)
.toString()

@ExperimentalAnvilApi
public fun FileSpec.Companion.createAnvilSpec(
packageName: String,
fileName: String,
generatorComment: String = "Generated by Anvil.\nhttps://github.com/square/anvil",
block: FileSpec.Builder.() -> Unit
): FileSpec =
builder(packageName, fileName)
.apply {
// Suppress any deprecation warnings.
Expand All @@ -142,4 +142,3 @@ public fun FileSpec.Companion.buildFile(
}
.addFileComment(generatorComment)
.build()
.writeToString()
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,25 @@ public fun ClassReference.generateClassName(
separator: String = "_",
suffix: String = ""
): ClassId {
val className = enclosingClassesWithSelf().joinToString(separator = separator) { it.shortName }
return ClassId(packageFqName, FqName(className + suffix), false)
return asClassName().generateClassName(separator, suffix).asClassId()
}

@ExperimentalAnvilApi
public fun ClassName.generateClassName(
separator: String = "_",
suffix: String = ""
): ClassName {
val className = simpleNames.joinToString(separator = separator)
return ClassName(packageName, className + suffix)
}

@ExperimentalAnvilApi
public fun ClassName.asClassId(local: Boolean = false): ClassId = ClassId(
FqName(packageName),
FqName(simpleNames.joinToString(".")),
local
)

@ExperimentalAnvilApi
public fun ClassReference.asClassName(): ClassName = classId.asClassName()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package com.squareup.anvil.compiler.internal.testing

import com.google.auto.value.processor.AutoAnnotationProcessor
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.squareup.anvil.annotations.ExperimentalAnvilApi
import com.squareup.anvil.compiler.AnvilCommandLineProcessor
import com.squareup.anvil.compiler.AnvilComponentRegistrar
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.internal.testing.AnvilCompilationMode.Embedded
import com.squareup.anvil.compiler.internal.testing.AnvilCompilationMode.Ksp
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.KotlinCompilation.Result
import com.tschuchort.compiletesting.PluginOption
import com.tschuchort.compiletesting.SourceFile
import com.tschuchort.compiletesting.addPreviousResultToClasspath
import com.tschuchort.compiletesting.kspArgs
import com.tschuchort.compiletesting.kspWithCompilation
import com.tschuchort.compiletesting.symbolProcessorProviders
import dagger.internal.codegen.ComponentProcessor
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.config.JvmTarget
import java.io.File
import java.io.OutputStream
import java.nio.file.Files
import java.util.Locale
import java.util.ServiceLoader

/**
* A simple API over a [KotlinCompilation] with extra configuration support for Anvil.
Expand All @@ -37,7 +44,7 @@ public class AnvilCompilation internal constructor(
generateDaggerFactoriesOnly: Boolean = false,
disableComponentMerging: Boolean = false,
enableExperimentalAnvilApis: Boolean = true,
codeGenerators: List<CodeGenerator> = emptyList(),
mode: AnvilCompilationMode = Embedded(emptyList()),
enableAnvil: Boolean = true,
) = apply {
checkNotCompiled()
Expand All @@ -48,41 +55,68 @@ public class AnvilCompilation internal constructor(
if (!enableAnvil) return@apply

kotlinCompilation.apply {
val anvilComponentRegistrar = AnvilComponentRegistrar()
// Deprecation tracked in https://github.com/square/anvil/issues/672
@Suppress("DEPRECATION")
componentRegistrars = listOf(
AnvilComponentRegistrar().also { it.addCodeGenerators(codeGenerators) }
)
componentRegistrars = listOf(anvilComponentRegistrar)
if (enableDaggerAnnotationProcessor) {
annotationProcessors = listOf(ComponentProcessor(), AutoAnnotationProcessor())
}

val anvilCommandLineProcessor = AnvilCommandLineProcessor()
commandLineProcessors = listOf(anvilCommandLineProcessor)

pluginOptions = listOf(
PluginOption(
pluginId = anvilCommandLineProcessor.pluginId,
optionName = "src-gen-dir",
optionValue = File(workingDir, "build/anvil").absolutePath
),
pluginOptions = mutableListOf(
PluginOption(
pluginId = anvilCommandLineProcessor.pluginId,
optionName = "generate-dagger-factories",
optionValue = generateDaggerFactories.toString()
),
PluginOption(
pluginId = anvilCommandLineProcessor.pluginId,
optionName = "generate-dagger-factories-only",
optionValue = generateDaggerFactoriesOnly.toString()
optionName = "disable-component-merging",
optionValue = disableComponentMerging.toString()
),
PluginOption(
pluginId = anvilCommandLineProcessor.pluginId,
optionName = "disable-component-merging",
optionValue = disableComponentMerging.toString()
optionName = "compilation-mode",
optionValue = mode.type.name.lowercase(Locale.US)
)
)

when (mode) {
is Embedded -> {
anvilComponentRegistrar.addCodeGenerators(mode.codeGenerators)
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()
)
)
}
Comment on lines +85 to +103
Copy link
Collaborator Author

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

is Ksp -> {
symbolProcessorProviders += buildList {
addAll(
ServiceLoader.load(
SymbolProcessorProvider::class.java,
SymbolProcessorProvider::class.java.classLoader
)
)
addAll(mode.symbolProcessorProviders)
}
kspWithCompilation = true
Copy link
Collaborator Author

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.

Copy link
Member

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

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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)

kspArgs["generate-dagger-factories"] = generateDaggerFactories.toString()
kspArgs["generate-dagger-factories-only"] = generateDaggerFactoriesOnly.toString()
Copy link
Member

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?

Copy link
Collaborator Author

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

}
}

if (enableExperimentalAnvilApis) {
kotlincArguments += listOf(
"-opt-in=kotlin.RequiresOptIn",
Expand Down Expand Up @@ -202,11 +236,11 @@ public fun compileAnvil(
workingDir: File? = null,
enableExperimentalAnvilApis: Boolean = true,
previousCompilationResult: Result? = null,
codeGenerators: List<CodeGenerator> = emptyList(),
mode: AnvilCompilationMode = Embedded(emptyList()),
moduleName: String? = null,
jvmTarget: JvmTarget? = null,
block: Result.() -> Unit = { },
): Result {
block: KotlinCompilation.Result.() -> Unit = { },
): KotlinCompilation.Result {
Copy link
Member

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

Copy link
Collaborator Author

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)

return AnvilCompilation()
.apply {
kotlinCompilation.apply {
Expand Down Expand Up @@ -234,7 +268,7 @@ public fun compileAnvil(
generateDaggerFactoriesOnly = generateDaggerFactoriesOnly,
disableComponentMerging = disableComponentMerging,
enableExperimentalAnvilApis = enableExperimentalAnvilApis,
codeGenerators = codeGenerators,
mode = mode,
)
.compile(*sources)
.also(block)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.squareup.anvil.compiler.internal.testing

import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.internal.testing.AnvilCompilationMode.Type.EMBEDDED
import com.squareup.anvil.compiler.internal.testing.AnvilCompilationMode.Type.KSP

sealed class AnvilCompilationMode(val type: Type) {
enum class Type {
EMBEDDED, KSP
}

data class Embedded(
val codeGenerators: List<CodeGenerator> = emptyList()
) : AnvilCompilationMode(EMBEDDED)
data class Ksp(
val symbolProcessorProviders: List<SymbolProcessorProvider> = emptyList()
) : AnvilCompilationMode(KSP)
}
4 changes: 4 additions & 0 deletions compiler/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ dependencies {
implementation libs.dagger2
implementation libs.jsr250
implementation libs.kotlinpoet
implementation libs.kotlinpoet.ksp

compileOnly libs.auto.service.annotations
compileOnly libs.kotlin.compiler
compileOnly libs.ksp.api

kapt libs.auto.service.processor

testImplementation testFixtures(project(":compiler-utils"))
testImplementation libs.dagger2.compiler
testImplementation libs.kotlin.annotationProcessingEmbeddable
testImplementation libs.kotlin.compileTesting
testImplementation libs.kotlin.compileTesting.ksp
testImplementation libs.ksp.compilerPlugin
testImplementation libs.kotlin.compiler
testImplementation libs.kotlin.test
testImplementation libs.truth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ internal const val disableComponentMergingName = "disable-component-merging"
internal val disableComponentMergingKey =
CompilerConfigurationKey.create<Boolean>("anvil $disableComponentMergingName")

internal const val compilationModeName = "compilation-mode"
internal val compilationModeKey =
CompilerConfigurationKey.create<String>("anvil $compilationModeName")

/**
* Parses arguments from the Gradle plugin for the compiler plugin.
*/
Expand All @@ -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,
Copy link
Member

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?

Copy link
Collaborator Author

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

allowMultipleOccurrences = false
),
CliOption(
Expand All @@ -61,6 +65,13 @@ class AnvilCommandLineProcessor : CommandLineProcessor {
"@MergeComponent or @MergeSubcomponent.",
required = false,
allowMultipleOccurrences = false
),
CliOption(
Copy link
Collaborator Author

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

optionName = compilationModeName,
valueDescription = "<embedded|ksp>",
Copy link
Member

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Extracted in 4ed03de

description = "Controls whether Anvil is running as an embedded plugin or as KSP.",
required = false,
allowMultipleOccurrences = false
)
)

Expand All @@ -77,6 +88,8 @@ class AnvilCommandLineProcessor : CommandLineProcessor {
configuration.put(generateDaggerFactoriesOnlyKey, value.toBoolean())
disableComponentMergingName ->
configuration.put(disableComponentMergingKey, value.toBoolean())
compilationModeName ->
configuration.put(compilationModeKey, value)
Copy link
Member

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It does not validate

Copy link
Collaborator Author

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

}
}
}
Loading
Loading