Skip to content

Commit

Permalink
Automatically downgrade plugin artifact when possible
Browse files Browse the repository at this point in the history
  • Loading branch information
bnorm committed Mar 13, 2022
1 parent a38f430 commit 49b134d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 44 deletions.
65 changes: 23 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.bnorm.power/kotlin-power-assert-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.bnorm.power/kotlin-power-assert-plugin)

Kotlin Compiler Plugin which high-jacks Kotlin assert function calls and
transforms them similar to [Groovy's Power Assert feature][groovy-power-assert].
This plugin uses the IR backend for the Kotlin compiler and supports all
platforms: JVM, JS, and Native!
Kotlin Compiler Plugin which high-jacks Kotlin assert function calls and transforms them similar
to [Groovy's Power Assert feature][groovy-power-assert]. This plugin uses the IR backend for the Kotlin compiler and
supports all platforms: JVM, JS, and Native!

## Example

Expand Down Expand Up @@ -37,8 +36,7 @@ java.lang.AssertionError: Incorrect length
at <stacktrace>
```

With `kotlin-power-assert` included, the error message for the previous example
will be transformed:
With `kotlin-power-assert` included, the error message for the previous example will be transformed:

```text
java.lang.AssertionError: Incorrect length
Expand Down Expand Up @@ -70,21 +68,18 @@ assert(

## Beyond Assert

The plugin by default will transform `assert` function calls but can also
transform other functions like `require`, `check`, `assertTrue`, and many, many
more.
The plugin by default will transform `assert` function calls but can also transform other functions like `require`
, `check`, `assertTrue`, and many, many more.

Functions which can be transformed have specific requirements. A function must
have a form which allows taking a `String` or `() -> String` value as the last
parameter. This can either be as an overload or the original function.
Functions which can be transformed have specific requirements. A function must have a form which allows taking
a `String` or `() -> String` value as the last parameter. This can either be as an overload or the original function.

For example, the `assert` function has 2 definitions:
* `fun assert(value: Boolean)`
* `fun assert(value: Boolean, lazyMessage: () -> Any)`

If the first function definition is called, it will be transformed into calling
the second definition with the diagram message supplied as the last parameter.
If the second definition is called, it will be transformed into calling the same
If the first function definition is called, it will be transformed into calling the second definition with the diagram
message supplied as the last parameter. If the second definition is called, it will be transformed into calling the same
function but with the diagram message appended to the last parameter.

This transformed function call doesn't need to throw an exception either. See
Expand All @@ -102,8 +97,8 @@ plugins {
}
```

The Gradle plugin allows configuring the functions which should be transformed
with a list of fully-qualified function names.
The Gradle plugin allows configuring the functions which should be transformed with a list of fully-qualified function
names.

```kotlin
// Kotlin DSL
Expand All @@ -121,30 +116,18 @@ kotlinPowerAssert {

## Compatibility

The Kotlin compiler plugin API is unstable and each new version of Kotlin can
bring breaking changes to the APIs used by this compiler plugin. Make sure you
are using the correct version of this plugin for whatever version of Kotlin
used. Check the table below to find when support for a particular version of
Kotlin was first introduced. If a version of Kotlin or this plugin is not listed
it can be assumed to maintain compatibility with the next oldest version listed.

| Kotlin Version | Plugin Version |
| -------------- | -------------- |
| 1.3.60 | 0.1.0 |
| 1.3.70 | 0.3.0 |
| 1.4.0 | 0.4.0 |
| 1.4.20 | 0.6.0 |
| 1.4.30 | 0.7.0 |
| 1.5.0 | 0.8.0 |
| 1.5.10 | 0.9.0 |
| 1.5.20 | 0.10.0 |
| 1.6.0 | 0.11.0 |
The Kotlin compiler plugin API is unstable and each new version of Kotlin can bring breaking changes to the APIs used by
this compiler plugin. The Gradle plugin will automatically attempt to downgrade the compiler plugin artifact version if
the Gradle plugin is able to determine the current Kotlin compiler version. This way you can always use the latest
version of the Gradle plugin without worrying which version of Kotlin you are using.

Note that all new features and bug fixes for the plugin will not be back-ported to older Kotlin versions. For the best
experience with this compiler plugin, make sure to be running the latest stable version of Kotlin.

## Kotlin IR

This plugin supports all IR based compiler backends: JVM, JS, and Native! Only
Kotlin/JS still uses the legacy compiler backend by default, use the following
to make sure IR is enabled.
This plugin supports all IR based compiler backends: JVM, JS, and Native! Only Kotlin/JS still uses the legacy compiler
backend by default, use the following to make sure IR is enabled.

```kotlin
target {
Expand All @@ -157,8 +140,7 @@ target {

### Function Call Tracing

Similar to Rust's `dbg!` macro, functions which take arbitrary parameters can
be transformed. For example:
Similar to Rust's `dbg!` macro, functions which take arbitrary parameters can be transformed. For example:

```kotlin
fun <T> dbg(value: T): T = value
Expand Down Expand Up @@ -207,8 +189,7 @@ assertSoftly {
}
```

A working example is [available][soft-assert-example] in this repository in the
sample directory.
A working example is [available][soft-assert-example] in this repository in the sample directory.

[groovy-power-assert]: https://groovy-lang.org/testing.html#_power_assertions
[kotlin-power-assert-gradle]: https://plugins.gradle.org/plugin/com.bnorm.power.kotlin-power-assert
Expand Down
1 change: 1 addition & 0 deletions kotlin-power-assert-gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ plugins {
dependencies {
implementation(kotlin("stdlib"))
implementation(kotlin("gradle-plugin-api"))
compileOnly(kotlin("gradle-plugin"))
}

buildConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@
package com.bnorm.power

import org.gradle.api.Project
import org.gradle.api.logging.LogLevel
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
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion

class PowerAssertGradlePlugin : KotlinCompilerPluginSupportPlugin {
private var pluginVersion: String = BuildConfig.PLUGIN_VERSION

override fun apply(target: Project): Unit = with(target) {
extensions.create("kotlinPowerAssert", PowerAssertGradleExtension::class.java)

// Try and determine the Kotlin version being used to provide a compatible version of kotlin-power-assert
pluginVersion = runCatching {
determineCompatiblePluginVersion(target)
}.getOrDefault(BuildConfig.PLUGIN_VERSION)
}

override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true
Expand All @@ -35,13 +44,13 @@ class PowerAssertGradlePlugin : KotlinCompilerPluginSupportPlugin {
override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(
groupId = BuildConfig.PLUGIN_GROUP_ID,
artifactId = BuildConfig.PLUGIN_ARTIFACT_ID,
version = BuildConfig.PLUGIN_VERSION
version = pluginVersion
)

override fun getPluginArtifactForNative(): SubpluginArtifact = SubpluginArtifact(
groupId = BuildConfig.PLUGIN_GROUP_ID,
artifactId = BuildConfig.PLUGIN_ARTIFACT_ID + "-native",
version = BuildConfig.PLUGIN_VERSION
version = pluginVersion
)

override fun applyToCompilation(
Expand All @@ -55,4 +64,33 @@ class PowerAssertGradlePlugin : KotlinCompilerPluginSupportPlugin {
}
}
}

private fun determineCompatiblePluginVersion(project: Project): String {
val version = project.getKotlinPluginVersion()
val (major, minor, patch) = "(\\d+)\\.(\\d+)\\.(\\d+).*".toRegex().matchEntire(version)?.destructured
?: throw IllegalArgumentException("Cannot parse Kotlin compiler version: $version")
val kotlinCompilerVersion = KotlinVersion(major.toInt(), minor.toInt(), patch.toInt())
val pluginVersion = when {
/* 0.0.0 <= */ kotlinCompilerVersion < KotlinVersion(1, 3, 60) ->
throw IllegalArgumentException("Unsupported Kotlin version $kotlinCompilerVersion")
/* 1.3.60 <= */ kotlinCompilerVersion < KotlinVersion(1, 3, 70) -> "0.2.0"
/* 1.3.70 <= */ kotlinCompilerVersion < KotlinVersion(1, 4, 0) -> "0.3.1"
/* 1.4.0 <= */ kotlinCompilerVersion < KotlinVersion(1, 4, 20) -> "0.5.3"
/* 1.4.20 <= */ kotlinCompilerVersion < KotlinVersion(1, 4, 30) -> "0.6.1"
/* 1.4.30 <= */ kotlinCompilerVersion < KotlinVersion(1, 5, 0) -> "0.7.0"
/* 1.5.0 <= */ kotlinCompilerVersion < KotlinVersion(1, 5, 10) -> "0.8.1"
/* 1.5.10 <= */ kotlinCompilerVersion < KotlinVersion(1, 5, 20) -> "0.9.0"
/* 1.5.20 <= */ kotlinCompilerVersion < KotlinVersion(1, 6, 0) -> "0.10.0"
/* 1.6.0 <= */ else -> BuildConfig.PLUGIN_VERSION
}

if (pluginVersion != BuildConfig.PLUGIN_VERSION) {
project.logger.log(
LogLevel.WARN,
"Downgrading compiler plugin artifact version to $pluginVersion to be compatible with Kotlin version $kotlinCompilerVersion"
)
}

return pluginVersion
}
}

0 comments on commit 49b134d

Please sign in to comment.