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

Remove reflection-based schema parser #2223

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[Unreleased]: https://github.com/cashapp/redwood/compare/0.13.0...HEAD

New:
- Source-based schema parser is now the default. Can be disabled in your schema module with `redwood { useFir = false }`.
- Source-based schema parser is now the default. The `useFir` Gradle property has been removed.
- Introduce a `LoadingStrategy` interface to manage `LazyList` preloading.

Changed:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.android.build.gradle.BaseExtension
import com.diffplug.gradle.spotless.SpotlessExtension
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import com.vanniktech.maven.publish.SonatypeHost
import java.io.File
import kotlinx.validation.ApiValidationExtension
import kotlinx.validation.ExperimentalBCVApi
import org.gradle.accessors.dm.LibrariesForLibs
Expand All @@ -43,22 +44,26 @@ import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.gradle.api.plugins.AppliedPlugin
import org.gradle.api.plugins.JavaApplication
import org.gradle.api.plugins.JavaPlugin.TEST_TASK_NAME
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.AbstractTestTask
import org.gradle.api.tasks.testing.Test
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
import org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED
import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED
import org.jetbrains.dokka.gradle.DokkaTaskPartial
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
Expand Down Expand Up @@ -318,7 +323,10 @@ private class RedwoodBuildExtensionImpl(private val project: Project) : RedwoodB
}
}
Tooling -> {
project.plugins.apply("org.jetbrains.kotlin.jvm")
project.applyKotlinJvm {
// Not every project needs this, but it's cheap to do everywhere.
project.injectTestSourcesForParsing(this)
}
// Needed for lint in downstream Android projects to analyze this dependency.
project.plugins.apply("com.android.lint")
}
Expand Down Expand Up @@ -379,6 +387,24 @@ private class RedwoodBuildExtensionImpl(private val project: Project) : RedwoodB
}
}

private fun Project.injectTestSourcesForParsing(kotlin: KotlinJvmProjectExtension) {
// In order to simplify writing test schemas, inject the test sources and
// test classpath as properties into the test runtime. This allows testing
// the FIR-based parser on sources written inside the test case. Cool!
tasks.named(TEST_TASK_NAME, Test::class.java).configure {
val compilation = kotlin.target.compilations.getByName(TEST_COMPILATION_NAME)

val sources = compilation.defaultSourceSet.kotlin.sourceDirectories.files
it.systemProperty("redwood.internal.sources", sources.joinToString(File.pathSeparator))

val classpath = project.configurations.getByName(compilation.compileDependencyConfigurationName).files
it.systemProperty("redwood.internal.classpath", classpath.joinToString(File.pathSeparator))

// FIR is heap hungry.
it.maxHeapSize = "2g"
}
}

override fun targets(group: TargetGroup) {
targets(ModifiedTargetGroup(group, emptyMap()))
}
Expand Down Expand Up @@ -635,6 +661,12 @@ private fun Project.withKotlinPlugins(block: KotlinProjectExtension.() -> Unit)
pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform", handler)
}

private fun Project.applyKotlinJvm(block: KotlinJvmProjectExtension.() -> Unit) {
pluginManager.apply("org.jetbrains.kotlin.jvm")
val kotlin = extensions.getByType(KotlinJvmProjectExtension::class.java)
kotlin.block()
}

private fun Project.applyKotlinMultiplatform(block: KotlinMultiplatformExtension.() -> Unit) {
pluginManager.apply("org.jetbrains.kotlin.multiplatform")
val kotlin = extensions.getByType(KotlinMultiplatformExtension::class.java)
Expand Down
1 change: 0 additions & 1 deletion redwood-gradle-plugin/api/redwood-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public abstract class app/cash/redwood/gradle/RedwoodSchemaExtension {
public fun <init> ()V
public abstract fun getApiTracking ()Lorg/gradle/api/provider/Property;
public abstract fun getType ()Lorg/gradle/api/provider/Property;
public abstract fun getUseFir ()Lorg/gradle/api/provider/Property;
}

public final class app/cash/redwood/gradle/RedwoodSchemaPlugin : org/gradle/api/Plugin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ internal abstract class RedwoodSchemaApiCheckTask @Inject constructor(
@get:Classpath
abstract val toolClasspath: ConfigurableFileCollection

@get:Input
abstract val useFir: Property<Boolean>

@get:Classpath
abstract val sources: ConfigurableFileCollection

Expand Down Expand Up @@ -75,7 +72,6 @@ internal abstract class RedwoodSchemaApiCheckTask @Inject constructor(
val queue = workerExecutor.noIsolation()
queue.submit(RedwoodSchemaApiWorker::class.java) {
it.toolClasspath.from(toolClasspath)
it.useFir.set(useFir)
it.sources.setFrom(sources)
it.classpath.setFrom(classpath)
it.schemaType.set(schemaType)
Expand All @@ -91,9 +87,6 @@ internal abstract class RedwoodSchemaApiGenerateTask @Inject constructor(
@get:Classpath
abstract val toolClasspath: ConfigurableFileCollection

@get:Input
abstract val useFir: Property<Boolean>

@get:Classpath
abstract val sources: ConfigurableFileCollection

Expand All @@ -111,7 +104,6 @@ internal abstract class RedwoodSchemaApiGenerateTask @Inject constructor(
val queue = workerExecutor.noIsolation()
queue.submit(RedwoodSchemaApiWorker::class.java) {
it.toolClasspath.from(toolClasspath)
it.useFir.set(useFir)
it.sources.setFrom(sources)
it.classpath.setFrom(classpath)
it.schemaType.set(schemaType)
Expand All @@ -123,7 +115,6 @@ internal abstract class RedwoodSchemaApiGenerateTask @Inject constructor(

private interface RedwoodSchemaApiParameters : WorkParameters {
val toolClasspath: ConfigurableFileCollection
val useFir: Property<Boolean>
val sources: ConfigurableFileCollection
val classpath: ConfigurableFileCollection
val schemaType: Property<String>
Expand All @@ -147,9 +138,6 @@ private abstract class RedwoodSchemaApiWorker @Inject constructor(
add(parameters.apiFile.get().asFile.absolutePath)
add("--fix-with")
add(REDWOOD_API_GENERATE_TASK_NAME)
if (parameters.useFir.get()) {
add("--use-fir")
}
for (source in parameters.sources.files) {
add("--source")
add(source.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,4 @@ public abstract class RedwoodSchemaExtension {
* The default is true.
*/
public abstract val apiTracking: Property<Boolean>

/** Set to true to use the new, FIR-based schema parser. */
public abstract val useFir: Property<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ internal abstract class RedwoodSchemaJsonTask @Inject constructor(
@get:Classpath
abstract val toolClasspath: ConfigurableFileCollection

@get:Input
abstract val useFir: Property<Boolean>

@get:Classpath
abstract val sources: ConfigurableFileCollection

Expand All @@ -58,7 +55,6 @@ internal abstract class RedwoodSchemaJsonTask @Inject constructor(
val queue = workerExecutor.noIsolation()
queue.submit(RedwoodSchemaJsonWorker::class.java) {
it.toolClasspath.from(toolClasspath)
it.useFir.set(useFir)
it.sources.setFrom(sources)
it.classpath.setFrom(classpath)
it.schemaType.set(schemaType)
Expand Down Expand Up @@ -90,9 +86,6 @@ private abstract class RedwoodSchemaJsonWorker @Inject constructor(
add("json")
add("--out")
add(parameters.outputDir.get().asFile.absolutePath)
if (parameters.useFir.get()) {
add("--use-fir")
}
for (source in parameters.sources.files) {
add("--source")
add(source.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package app.cash.redwood.gradle

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin.JAR_TASK_NAME
import org.gradle.jvm.tasks.Jar
import org.gradle.language.base.plugins.LifecycleBasePlugin.BUILD_GROUP
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP
Expand All @@ -32,7 +30,6 @@ public class RedwoodSchemaPlugin : Plugin<Project> {
RedwoodSchemaExtension::class.java,
).apply {
apiTracking.convention(true)
useFir.convention(true)
}

var applied = false
Expand Down Expand Up @@ -66,16 +63,10 @@ public class RedwoodSchemaPlugin : Plugin<Project> {
it.toolClasspath.from(toolingConfiguration)
it.outputDir.set(project.redwoodGeneratedDir("schema-json"))
it.schemaType.set(extension.type)
it.useFir.set(extension.useFir)
it.sources.setFrom(sources)
it.classpath.from(classpath)
if (!extension.useFir.get()) {
it.classpath.from(compilation.output.classesDirs)
}
}
project.tasks.named(JAR_TASK_NAME, Jar::class.java).configure {
it.from(generateJson)
}
compilation.defaultSourceSet.resources.srcDir(generateJson)

// Wait for build script to run before checking if API tracking is still enabled.
project.afterEvaluate {
Expand All @@ -89,12 +80,8 @@ public class RedwoodSchemaPlugin : Plugin<Project> {
it.toolClasspath.from(toolingConfiguration)
it.apiFile.set(apiFile)
it.schemaType.set(extension.type)
it.useFir.set(extension.useFir)
it.sources.setFrom(sources)
it.classpath.from(classpath)
if (!extension.useFir.get()) {
it.classpath.from(compilation.output.classesDirs)
}
}

val apiCheck =
Expand All @@ -105,12 +92,8 @@ public class RedwoodSchemaPlugin : Plugin<Project> {
it.toolClasspath.from(toolingConfiguration)
it.apiFile.set(apiFile)
it.schemaType.set(extension.type)
it.useFir.set(extension.useFir)
it.sources.setFrom(sources)
it.classpath.from(classpath)
if (!extension.useFir.get()) {
it.classpath.from(compilation.output.classesDirs)
}

// Dummy output required to skip task if no inputs have changed.
it.dummyOutputFile.set(project.layout.buildDirectory.file("tmp/redwoodApiCheckDummy.txt"))
Expand Down
1 change: 1 addition & 0 deletions redwood-tooling-codegen/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
implementation libs.kotlinPoet
implementation libs.clikt

testImplementation testFixtures(projects.redwoodToolingSchema)
testImplementation projects.testApp.schema
testImplementation projects.testApp.schema.compose
testImplementation libs.junit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import app.cash.redwood.schema.Property
import app.cash.redwood.schema.Schema
import app.cash.redwood.schema.Widget
import app.cash.redwood.tooling.schema.FqType
import app.cash.redwood.tooling.schema.ProtocolSchemaSet
import app.cash.redwood.tooling.schema.parseTestSchema
import assertk.all
import assertk.assertThat
import assertk.assertions.contains
Expand All @@ -45,7 +45,7 @@ class ComposeGenerationTest {
)

@Test fun `scoped and unscoped children`() {
val schema = ProtocolSchemaSet.parse(ScopedAndUnscopedSchema::class).schema
val schema = parseTestSchema(ScopedAndUnscopedSchema::class).schema

val fileSpec = generateComposable(schema, schema.widgets.single())
assertThat(fileSpec.toString()).all {
Expand All @@ -55,7 +55,7 @@ class ComposeGenerationTest {
}

@Test fun `scope is annotated with layout scope marker`() {
val schema = ProtocolSchemaSet.parse(ScopedAndUnscopedSchema::class).schema
val schema = parseTestSchema(ScopedAndUnscopedSchema::class).schema

val fileSpec = generateModifierScope(schema, FqType(listOf("example", "RowScope")))
assertThat(fileSpec.toString()).contains(
Expand Down Expand Up @@ -87,7 +87,7 @@ class ComposeGenerationTest {
)

@Test fun `default is supported for all property types`() {
val schema = ProtocolSchemaSet.parse(DefaultSchema::class).schema
val schema = parseTestSchema(DefaultSchema::class).schema

val fileSpec = generateComposable(schema, schema.widgets.single())
assertThat(fileSpec.toString()).all {
Expand All @@ -111,7 +111,7 @@ class ComposeGenerationTest {
)

@Test fun `layout modifier is the last non child parameter`() {
val schema = ProtocolSchemaSet.parse(MultipleChildSchema::class).schema
val schema = parseTestSchema(MultipleChildSchema::class).schema

val fileSpec = generateComposable(schema, schema.widgets.single())
assertThat(fileSpec.toString()).contains(
Expand All @@ -138,7 +138,7 @@ class ComposeGenerationTest {
@Test fun deprecation() {
// NOTE: There's no way to deprecate a parameter to a function.

val schema = ProtocolSchemaSet.parse(DeprecatedSchema::class).schema
val schema = parseTestSchema(DeprecatedSchema::class).schema

val fileSpec = generateComposable(schema, schema.widgets.single())
assertThat(fileSpec.toString()).contains(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import app.cash.redwood.schema.Modifier
import app.cash.redwood.schema.Property
import app.cash.redwood.schema.Schema
import app.cash.redwood.schema.Widget
import app.cash.redwood.tooling.schema.ProtocolSchemaSet
import app.cash.redwood.tooling.schema.parseTestSchema
import assertk.all
import assertk.assertAll
import assertk.assertThat
Expand Down Expand Up @@ -64,7 +64,7 @@ class DeprecatedGenerationTest {
)

@Test fun modifierInterface() {
val schema = ProtocolSchemaSet.parse(DeprecatedSchema::class).schema
val schema = parseTestSchema(DeprecatedSchema::class).schema

val modifier = schema.modifiers.single()
val fileSpec = generateModifierInterface(schema, modifier)
Expand Down Expand Up @@ -92,7 +92,7 @@ class DeprecatedGenerationTest {
}

@Test fun widget() {
val schema = ProtocolSchemaSet.parse(DeprecatedSchema::class).schema
val schema = parseTestSchema(DeprecatedSchema::class).schema

val widget = schema.widgets.single()
val fileSpec = generateWidget(schema, widget)
Expand Down Expand Up @@ -143,7 +143,7 @@ class DeprecatedGenerationTest {
fun protocolCodegen(
@TestParameter type: ProtocolCodegenType,
) {
val schema = ProtocolSchemaSet.parse(DeprecatedSchema::class)
val schema = parseTestSchema(DeprecatedSchema::class)
assertAll {
for (fileSpec in schema.generateFileSpecs(type)) {
assertThat(fileSpec.toString())
Expand All @@ -155,7 +155,7 @@ class DeprecatedGenerationTest {
@Test fun codegen(
@TestParameter type: CodegenType,
) {
val schema = ProtocolSchemaSet.parse(DeprecatedSchema::class)
val schema = parseTestSchema(DeprecatedSchema::class)
assertAll {
for (fileSpec in schema.generateFileSpecs(type)) {
assertThat(fileSpec.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package app.cash.redwood.tooling.codegen

import app.cash.redwood.schema.Modifier
import app.cash.redwood.schema.Schema
import app.cash.redwood.tooling.schema.ProtocolSchemaSet
import app.cash.redwood.tooling.schema.parseTestSchema
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.isEqualTo
Expand Down Expand Up @@ -46,7 +46,7 @@ class ModifierGenerationTest {
data class ContentDescription(val text: String)

@Test fun `simple names do not collide`() {
val schema = ProtocolSchemaSet.parse(SimpleNameCollisionSchema::class).schema
val schema = parseTestSchema(SimpleNameCollisionSchema::class).schema

val topType = schema.modifiers.single { it.type.flatName == "ModifierGenerationTestContentDescription" }
val topTypeSpec = generateModifierInterface(schema, topType)
Expand All @@ -70,7 +70,7 @@ class ModifierGenerationTest {
object ScopedModifier

@Test fun `layout modifier functions are stable`() {
val schema = ProtocolSchemaSet.parse(ScopedModifierSchema::class).schema
val schema = parseTestSchema(ScopedModifierSchema::class).schema

val modifier = schema.modifiers.single { it.type.names.last() == "ScopedModifier" }
val scope = modifier.scopes.single { it.names.last() == "ModifierScope" }
Expand Down
Loading