-
Notifications
You must be signed in to change notification settings - Fork 608
Add Wire Binary Compatibility plugin #3306
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
fe5b6f0
8bf29b0
30528fc
cfd8a4b
cabbb63
5ec88c2
af75cfc
b6bcc37
54295d9
3df3a26
88ef84b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
swankjesse marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import com.vanniktech.maven.publish.GradlePlugin | ||
| import com.vanniktech.maven.publish.JavadocJar | ||
| import com.vanniktech.maven.publish.MavenPublishBaseExtension | ||
|
|
||
| plugins { | ||
| id("java-gradle-plugin") | ||
| `kotlin-dsl` | ||
| kotlin("jvm") | ||
| id("org.jetbrains.dokka") | ||
| id("com.vanniktech.maven.publish.base") | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation(kotlin("gradle-plugin-api")) | ||
| implementation(project(":wire-binary-compatibility-kotlin-plugin")) | ||
| implementation(libs.kotlin.gradlePlugin) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect this dependency on the kotlin gradle plugin should be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated, thank you!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you got the change slightly backwards - the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with that change made I have no other concerns, thanks for doing this!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree with what Kats has said. While this plugin only makes sense in the context of a Kotlin project, we don't want to force a Kotlin version update on anyone if we can avoid it, so we should use
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coming back to this -- thanks for the pointers @staktrace / @autonomousapps! I've swapped around the change. The
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect that you could do it as |
||
| } | ||
|
|
||
| gradlePlugin { | ||
| plugins { | ||
| create("wireBinaryCompatibility") { | ||
| id = "com.squareup.wire.binarycompatibility" | ||
| displayName = "Wire Binary Compatibility" | ||
| description = "Rewrites Wire callsites to be resilient to API changes" | ||
| implementationClass = "com.squareup.wire.binarycompatibility.gradle.WireBinaryCompatibility" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| configure<MavenPublishBaseExtension> { | ||
| configure( | ||
| GradlePlugin( | ||
| javadocJar = JavadocJar.Empty() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, why publish an empty javadoc jar? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn’t a library for external use, but Maven Central requires Javadoc |
||
| ) | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /* | ||
| * Copyright (C) 2025 Square, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.squareup.wire.binarycompatibility.gradle | ||
|
|
||
| import org.gradle.api.provider.Provider | ||
| import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation | ||
| import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin | ||
| import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact | ||
| import org.jetbrains.kotlin.gradle.plugin.SubpluginOption | ||
|
|
||
| @Suppress("unused") // Created reflectively by Gradle. | ||
| class WireBinaryCompatibilityPlugin : KotlinCompilerPluginSupportPlugin { | ||
| override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true | ||
|
|
||
| override fun getCompilerPluginId(): String = "com.squareup.wire.binarycompatibility.kotlin" | ||
|
|
||
| override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( | ||
| groupId = "com.squareup.wire.binarycompatibility", | ||
| artifactId = "wire-binary-compatibility-kotlin-plugin", | ||
| version = "2.6.0-SNAPSHOT", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hard-coding the version here seems wrong?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've moved these maven coordinates to a simple BuildConfig object, and fixed the version (another copy-paste miss) -- this is the only place these values are referenced, the intent is to just keep this lightweight! |
||
| ) | ||
|
|
||
| override fun applyToCompilation( | ||
| kotlinCompilation: KotlinCompilation<*>, | ||
| ): Provider<List<SubpluginOption>> { | ||
| return kotlinCompilation.target.project.provider { | ||
| listOf() // No options. | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| plugins { | ||
| kotlin("jvm") | ||
| id("org.jetbrains.dokka") | ||
| } | ||
|
|
||
| dependencies { | ||
| testImplementation(project(":wire-binary-compatibility-kotlin-plugin")) | ||
| testImplementation(kotlin("compiler-embeddable")) | ||
| testImplementation(kotlin("test-junit")) | ||
| testImplementation(libs.assertk) | ||
| testImplementation(libs.kotlin.compile.testing) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| /* | ||
| * Copyright (C) 2025 Square, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.squareup.wire.binarycompatibility.kotlin | ||
|
|
||
| import assertk.assertThat | ||
| import assertk.assertions.contains | ||
| import assertk.assertions.containsExactly | ||
| import assertk.assertions.containsExactlyInAnyOrder | ||
| import assertk.assertions.isEmpty | ||
| import assertk.assertions.isFalse | ||
| import assertk.assertions.isTrue | ||
| import com.tschuchort.compiletesting.JvmCompilationResult | ||
| import com.tschuchort.compiletesting.KotlinCompilation | ||
| import com.tschuchort.compiletesting.SourceFile | ||
| import java.lang.reflect.Modifier | ||
| import kotlin.test.Test | ||
| import kotlin.test.assertEquals | ||
| import kotlin.test.assertFailsWith | ||
| import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar | ||
| import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi | ||
|
|
||
| @OptIn(ExperimentalCompilerApi::class) | ||
| class WireBinaryCompatibilityKotlinPluginTest { | ||
| @Test | ||
| fun rewriteConstructorCallToBuilderCall() { | ||
| val result = compile( | ||
| sourceFile = SourceFile.kotlin( | ||
| "Sample.kt", | ||
| """ | ||
| package com.squareup.wire | ||
|
|
||
| val log = mutableListOf<String>() | ||
|
|
||
| fun callConstructor() { | ||
| log += "${'$'}{Money(5, "USD")}" | ||
| } | ||
|
|
||
| fun callConstructorWithDefaultParameters() { | ||
| log += "${'$'}{Money(amount = 5)}" | ||
| log += "${'$'}{Money(currencyCode = "USD")}" | ||
| } | ||
|
|
||
| data class Money( | ||
| val amount: Long? = null, | ||
| val currencyCode: String? = null, | ||
| ) : Message { | ||
|
|
||
| class Builder : Message.Builder { | ||
| var amount: Long? = null | ||
| var currencyCode: String? = null | ||
|
|
||
| fun amount(amount: Long) : Builder { | ||
| log += "calling amount()!" | ||
| this.amount = amount | ||
| return this | ||
| } | ||
|
|
||
| fun currencyCode(currencyCode: String) : Builder { | ||
| log += "calling currencyCode()!" | ||
| this.currencyCode = currencyCode | ||
| return this | ||
| } | ||
| fun build() : Money = Money(amount, currencyCode) | ||
| } | ||
| } | ||
|
|
||
| interface Message { | ||
| interface Builder | ||
| } | ||
| """, | ||
| ), | ||
| ) | ||
| assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode, result.messages) | ||
|
|
||
| val testClass = result.classLoader.loadClass("com.squareup.wire.SampleKt") | ||
| val log = testClass.getMethod("getLog") | ||
| .invoke(null) as MutableList<String> | ||
|
|
||
| testClass.getMethod("callConstructor").invoke(null) | ||
| assertThat(log).containsExactly( | ||
| "calling amount()!", | ||
| "calling currencyCode()!", | ||
| "Money(amount=5, currencyCode=USD)", | ||
| ) | ||
| log.clear() | ||
|
|
||
| testClass.getMethod("callConstructorWithDefaultParameters").invoke(null) | ||
| assertThat(log).containsExactly( | ||
| "calling amount()!", | ||
| "Money(amount=5, currencyCode=null)", | ||
| "calling currencyCode()!", | ||
| "Money(amount=null, currencyCode=USD)", | ||
| ) | ||
| log.clear() | ||
| } | ||
| } | ||
|
|
||
| @ExperimentalCompilerApi | ||
| fun compile( | ||
| sourceFiles: List<SourceFile>, | ||
| plugin: CompilerPluginRegistrar = WireBinaryCompatibilityCompilerPluginRegistrar(), | ||
| ): JvmCompilationResult { | ||
| return KotlinCompilation().apply { | ||
| sources = sourceFiles | ||
| compilerPluginRegistrars = listOf(plugin) | ||
| inheritClassPath = true | ||
| kotlincArguments += "-Xverify-ir=error" | ||
| kotlincArguments += "-Xverify-ir-visibility" | ||
| }.compile() | ||
| } | ||
|
|
||
| @ExperimentalCompilerApi | ||
| fun compile( | ||
| sourceFile: SourceFile, | ||
| plugin: CompilerPluginRegistrar = WireBinaryCompatibilityCompilerPluginRegistrar(), | ||
| ): JvmCompilationResult { | ||
| return compile(listOf(sourceFile), plugin) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import com.vanniktech.maven.publish.JavadocJar | ||
| import com.vanniktech.maven.publish.KotlinJvm | ||
| import com.vanniktech.maven.publish.MavenPublishBaseExtension | ||
|
|
||
| plugins { | ||
| kotlin("jvm") | ||
| id("com.vanniktech.maven.publish.base") | ||
| } | ||
|
|
||
| dependencies { | ||
| compileOnly(kotlin("compiler-embeddable")) | ||
| compileOnly(kotlin("stdlib")) | ||
| } | ||
|
|
||
| configure<MavenPublishBaseExtension> { | ||
| configure( | ||
| KotlinJvm( | ||
| javadocJar = JavadocJar.Empty() | ||
| ) | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # We want the stdlib as a compileOnly dependency. | ||
| kotlin.stdlib.default.dependency=false |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filename is copypasta, should probably be updated
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One more name fixup needed here |
||
| * Copyright (C) 2025 Square, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.squareup.wire.binarycompatibility.kotlin | ||
|
|
||
| import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension | ||
| import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
| import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar | ||
| import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi | ||
| import org.jetbrains.kotlin.config.CommonConfigurationKeys | ||
| import org.jetbrains.kotlin.config.CompilerConfiguration | ||
| import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI | ||
| @OptIn( | ||
| ExperimentalCompilerApi::class, | ||
| UnsafeDuringIrConstructionAPI::class, | ||
| ) | ||
| class WireBinaryCompatibilityCompilerPluginRegistrar : CompilerPluginRegistrar() { | ||
| override val supportsK2: Boolean | ||
| get() = true | ||
|
|
||
| override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { | ||
| val messageCollector = configuration.get( | ||
| CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, | ||
| MessageCollector.NONE, | ||
| ) | ||
| IrGenerationExtension.registerExtension( | ||
| extension = WireBinaryCompatibilityIrGenerationExtension(messageCollector), | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* | ||
| * Copyright (C) 2025 Square, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.squareup.wire.binarycompatibility.kotlin | ||
|
|
||
| import org.jetbrains.kotlin.compiler.plugin.CliOption | ||
| import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor | ||
| import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi | ||
|
|
||
| @OptIn(ExperimentalCompilerApi::class) | ||
| class WireBinaryCompatibilityCommandLineProcessor : CommandLineProcessor { | ||
| override val pluginId = "com.squareup.wire.binarycompatibility.kotlin" | ||
|
|
||
| override val pluginOptions: List<CliOption> = listOf() | ||
| } |
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.
oops
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.
Oops indeed! Let me remove that