Skip to content
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
1 change: 0 additions & 1 deletion .github/workflows/publish-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ jobs:
- uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda #v3.4.2
- run: |
./gradlew librarianPublishToMavenCentral
gh release create $GITHUB_REF_NAME --title $GITHUB_REF_NAME --verify-tag --notes-from-tag
env:
LIBRARIAN_SONATYPE_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
LIBRARIAN_SONATYPE_USERNAME: ${{ secrets.OSSRH_USER }}
Expand Down
6 changes: 5 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ plugins {
alias(libs.plugins.kgp).apply(false)
alias(libs.plugins.librarian).apply(false)
alias(libs.plugins.compat.patrouille).apply(false)
alias(libs.plugins.gratatouille).apply(false)
alias(libs.plugins.ksp).apply(false)
}

Librarian.root(project)
Librarian.root(project)


Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
public abstract interface class compat/patrouille/CompatPatrouilleExtension {
public abstract fun checkApiDependencies ()V
public abstract fun java (I)V
public abstract fun kotlin (Ljava/lang/String;)V
}
Expand All @@ -25,5 +26,6 @@ public final class compat/patrouille/internal/Project_androidKt {

public final class compat/patrouille/internal/Project_kotlinKt {
public static final fun getKotlinExtension (Lorg/gradle/api/Project;)Lorg/jetbrains/kotlin/gradle/dsl/KotlinProjectExtension;
public static final fun isKmp (Ljava/lang/Object;)Z
}

3 changes: 3 additions & 0 deletions compat-patrouille-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
id("org.jetbrains.kotlin.jvm")
id("java-gradle-plugin")
id("com.gradleup.compat.patrouille")
id("com.gradleup.gratatouille")
}

Librarian.module(project)
Expand All @@ -13,6 +14,7 @@ dependencies {
compileOnly(libs.gradle.api)
compileOnly(libs.agp)
compileOnly(libs.kgp)
gratatouille(project(":compat-patrouille-tasks"))
}

configurations.getByName("api").dependencies.removeIf {
Expand All @@ -33,3 +35,4 @@ gradlePlugin {
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,13 @@ interface CompatPatrouilleExtension {
* Examples: "1.9.0", "1.9.22", "2.0.21", "2.1.20",...
*/
fun kotlin(version: String)

/**
* Registers a `compatPatrouilleCheckApiDependencies` task that walks all the api dependencies
* and checks that the metadata version in the `META-INF/${lib}.kotlin_module` file is compatible with
* the specified kotlin version.
* This is version n + 1 thanks to kotlinc n + 1 forward compatibility.
*/
fun checkApiDependencies()
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package compat.patrouille

import compat.patrouille.internal.CompatPatrouilleExtensionImpl
import compat.patrouille.internal.isKmp
import compat.patrouille.internal.kotlinExtensionOrNull
import compat.patrouille.task.registerCheckApiDependenciesTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion

class CompatPatrouillePlugin: Plugin<Project> {
class CompatPatrouillePlugin : Plugin<Project> {
override fun apply(target: Project) {
target.extensions.create(CompatPatrouilleExtension::class.java, "compatPatrouille", CompatPatrouilleExtensionImpl::class.java, target)
target.extensions.create(
CompatPatrouilleExtension::class.java,
"compatPatrouille",
CompatPatrouilleExtensionImpl::class.java,
target
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,52 @@ package compat.patrouille.internal
import compat.patrouille.CompatPatrouilleExtension
import compat.patrouille.configureJavaCompatibility
import compat.patrouille.configureKotlinCompatibility
import compat.patrouille.task.registerCheckApiDependenciesTask
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion

internal abstract class CompatPatrouilleExtensionImpl(private val project: Project) : CompatPatrouilleExtension {
var kotlinVersion: String? = null

internal abstract class CompatPatrouilleExtensionImpl(private val project: Project): CompatPatrouilleExtension {
override fun java(version: Int) {
project.configureJavaCompatibility(version)
}

override fun kotlin(version: String) {
val c = version.split(".")
require(c.size == 3) {
"Cannot parse Kotlin version $version. Expected format is X.Y.Z."
}
kotlinVersion = version
project.configureKotlinCompatibility(version)
}

override fun checkApiDependencies() {
val kotlin = project.extensions.findByName("kotlin")
val isKmp = if (kotlin != null) {
isKmp(kotlin)
} else {
false
}
val apiElementsConfigurationName = if (isKmp) {
"jvmApiElements"
} else {
"apiElements"
}
val configuration = project.configurations.create("compatPatrouilleCheck") {
it.isCanBeConsumed = false
it.isCanBeResolved = true
it.isVisible = false
}
configuration.extendsFrom(project.configurations.getByName(apiElementsConfigurationName))
val checkApiDependencies = project.registerCheckApiDependenciesTask(
taskName = "compatPatrouilleCheckApiDependencies",
compileClasspath = configuration,
kotlinVersion = project.provider { kotlinVersion ?: project.getKotlinPluginVersion() }
)

project.tasks.named("check").configure {
it.dependsOn(checkApiDependencies)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package compat.patrouille.internal

import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension

internal val Project.kotlinExtensionOrNull: KotlinProjectExtension? get() = extensions.findByName("kotlin") as KotlinProjectExtension?

val Project.kotlinExtension: KotlinProjectExtension get() = kotlinExtensionOrNull ?: error("no 'kotlin' extension found")

/**
* This function is very simple but extracted to a separate file to avoid class loading issues
* if KGP is not in the classpath
*/
fun isKmp(extension: Any): Boolean {
return extension is KotlinMultiplatformExtension
}
10 changes: 10 additions & 0 deletions compat-patrouille-tasks/api/compat-patrouille-tasks.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public final class compat/patrouille/task/CheckApiDependenciesEntryPoint {
public static final field Companion Lcompat/patrouille/task/CheckApiDependenciesEntryPoint$Companion;
public fun <init> ()V
public static final fun run (Ljava/util/List;Ljava/lang/String;Ljava/io/File;)V
}

public final class compat/patrouille/task/CheckApiDependenciesEntryPoint$Companion {
public final fun run (Ljava/util/List;Ljava/lang/String;Ljava/io/File;)V
}

25 changes: 25 additions & 0 deletions compat-patrouille-tasks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import com.gradleup.librarian.gradle.Librarian

plugins {
id("org.jetbrains.kotlin.jvm")
id("com.gradleup.compat.patrouille")
id("com.gradleup.gratatouille")
id("com.google.devtools.ksp")
}

Librarian.module(project)

dependencies {
implementation(libs.gratatouille.runtime)
implementation(libs.kotlinx.metadata)
}

compatPatrouille {
java(11)
}

gratatouille {
codeGeneration {
classLoaderIsolation()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package compat.patrouille.task

import gratatouille.GClasspath
import gratatouille.GOutputFile
import gratatouille.GTask
import kotlinx.metadata.jvm.JvmMetadataVersion
import kotlinx.metadata.jvm.KotlinModuleMetadata
import kotlinx.metadata.jvm.UnstableMetadataApi
import java.io.File
import java.util.zip.ZipInputStream

@OptIn(UnstableMetadataApi::class)
@GTask
internal fun checkApiDependencies(
compileClasspath: GClasspath,
kotlinVersion: String,
output: GOutputFile
) {
val c = kotlinVersion.split(".")
require(c.size == 3) {
"Cannot parse Kotlin version $kotlinVersion. Expected format is X.Y.Z."
}
var supportedMajor = c[0].toInt()
var supportedMinor = c[1].toInt()

if (supportedMajor == 1 && supportedMinor == 9) {
// 1.9 can read 2.0 metadata
supportedMajor = 2
supportedMinor = 0
} else {
// n + 1 forward compatibility in the general case
supportedMinor += 1
}

val supportedVersion = JvmMetadataVersion(supportedMajor, supportedMinor, 0)

compileClasspath.forEach { fileWithPath ->
fileWithPath.file.forEachModuleInfoFile { name, bytes ->
val metadata = KotlinModuleMetadata.read(bytes)
if (metadata.version > supportedVersion) {
error("${fileWithPath.file.path}:$name contains unsupported metadata ${metadata.version} (expected: $kotlinVersion)")
}
}
}

output.writeText("Nothing to see here, this file is just a marker that the task executed successfully.")
}

private fun File.forEachModuleInfoFile(block: (String, ByteArray) -> Unit) {
ZipInputStream(inputStream()).use { zis ->
var entry = zis.nextEntry
while (entry != null) {
if (entry.name.matches(Regex("META-INF/.*\\.kotlin_module"))) {
block(entry.name, zis.readBytes())
}
entry = zis.nextEntry
}
}
}

9 changes: 8 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
[versions]
gratatouille = "0.0.9"

[libraries]
gradle-api = "dev.gradleplugins:gradle-api:8.0"
agp = "com.android.tools.build:gradle:8.2.0"
kgp = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0"
gratatouille-runtime = { module = "com.gradleup.gratatouille:gratatouille-runtime", version.ref = "gratatouille" }
kotlinx-metadata = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.9.0"

[plugins]
kgp = { id = "org.jetbrains.kotlin.jvm", version = "2.1.20" }
kgp = { id = "org.jetbrains.kotlin.jvm", version = "2.1.21" }
ksp = { id = "com.google.devtools.ksp", version = "2.1.21-2.0.1" }
librarian = { id = "com.gradleup.librarian", version = "0.0.8-SNAPSHOT-b703634bb1858f471503e23d2c23d7aeae9d6120" }
compat-patrouille = { id = "com.gradleup.compat.patrouille", version = "0.0.0-SNAPSHOT-08185b86d960562682116f21e348d6a9bf0c63d6" }
gratatouille = { id = "com.gradleup.gratatouille", version.ref = "gratatouille" }
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ pluginManagement {
}
}

include(":compat-patrouille-gradle-plugin")
include(":compat-patrouille-gradle-plugin")
include(":compat-patrouille-tasks")
5 changes: 5 additions & 0 deletions tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
alias(libs.plugins.kgp).apply(false)
alias(libs.plugins.compat.patrouille).apply(false)
}

15 changes: 15 additions & 0 deletions tests/check-api-dependencies/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.gradleup.compat.patrouille")
}

compatPatrouille {
java(11)
kotlin("1.9.0")
checkApiDependencies()
}

dependencies {
// api("org.jetbrains.kotlin:kotlin-stdlib:2.1.21")
api(libs.kotlinx.metadata)
}
1 change: 1 addition & 0 deletions tests/check-api-dependencies/src/main/kotlin/hello.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
val hello = "world"
1 change: 1 addition & 0 deletions tests/gradle
1 change: 1 addition & 0 deletions tests/gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions tests/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pluginManagement {
listOf(repositories, dependencyResolutionManagement.repositories).forEach {
it.mavenCentral()
}
}

includeBuild("../")
include(":check-api-dependencies")
Loading