diff --git a/.github/workflows/build-pull-request.yaml b/.github/workflows/build-pull-request.yaml index 6f860ef..410bada 100644 --- a/.github/workflows/build-pull-request.yaml +++ b/.github/workflows/build-pull-request.yaml @@ -8,3 +8,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - run: | ./gradlew build + ./gradlew -p tests build + ./gradlew -p test-kgp-1-9 verifyCompatibility + sed -i.bak 's/.version("1.9.0")/.version("2.2.0")/' test-kgp-1-9/build.gradle.kts + ./gradlew -p test-kgp-1-9 verifyCompatibility diff --git a/.gitignore b/.gitignore index 4e67eeb..e5c299a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .kotlin .fleet .gradle +*.bak # Build outputs build diff --git a/test-kgp-1-9/build.gradle.kts b/test-kgp-1-9/build.gradle.kts new file mode 100644 index 0000000..cb3651c --- /dev/null +++ b/test-kgp-1-9/build.gradle.kts @@ -0,0 +1,135 @@ +import cast.cast +import com.nfeld.jsonpathkt.kotlinx.resolvePathOrNull +import java.util.zip.ZipInputStream +import kotlinx.metadata.jvm.KotlinModuleMetadata +import kotlinx.metadata.jvm.UnstableMetadataApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.jsonPrimitive +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes + +plugins { + id("org.jetbrains.kotlin.jvm").version("1.9.0") + id("com.gradleup.compat.patrouille") + id("maven-publish") +} + +buildscript { + dependencies { + classpath("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0") + classpath("com.eygraber:jsonpathkt-kotlinx:3.0.2") + classpath("net.mbonnin.cast:cast:0.0.1") + classpath("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.9.0") + classpath("org.ow2.asm:asm:9.8") + } +} + +compatPatrouille { + java(11) + kotlin("1.9.0") +} +abstract class VerifyCompatibility : DefaultTask() { + @get:InputFiles + abstract val m2Dir: ConfigurableFileCollection + + /** + * Verifies that: + * - Gradle metadata contains has "org.gradle.jvm.version" set to "11" + * - The .class files have a version of 55 (java 11). + * - The .kotlin_module files have a Kotlin version of 1.9.0. + * + * A future version could test KMP. + * .klib files can be read using "org.jetbrains.kotlin:kotlin-util-klib". + * Not sure how to read the commonMetadata .jar artifact, though; it's a .jar that contains .kn data. + * Interestingly, when using KMP, "org.gradle.jvm.version" is not set, not really sure why. + */ + @OptIn(UnstableMetadataApi::class) + @TaskAction + fun taskAction() { + val moduleFiles = m2Dir.files.filter { it.extension == "module" }.toList() + + check(moduleFiles.size == 1) { + "Expected exactly one module file, got ${moduleFiles.size}: $moduleFiles" + } + + moduleFiles.single().readText().let { + Json.parseToJsonElement(it) + }.resolvePathOrNull("$.variants.*.attributes[\"org.gradle.jvm.version\"]")!! + .cast() + .map { + it.jsonPrimitive.content + } + .forEach { + check(it == "11") { + "Expected all modules to be built with Java 11, got $it" + } + } + + val jarFiles = m2Dir.files.filter { it.extension == "jar" }.toList() + check(moduleFiles.size == 1) { + "Expected exactly one jar file, got ${jarFiles.size}: $jarFiles" + } + jarFiles.forEach { jarFile -> + ZipInputStream(jarFile.inputStream()).use { zis -> + var entry = zis.nextEntry + while (entry != null) { + if (entry.name.endsWith(".class")) { + + ClassReader(zis).accept(object : ClassVisitor(Opcodes.ASM9) { + override fun visit( + version: Int, + access: Int, + name: String?, + signature: String?, + superName: String?, + interfaces: Array? + ) { + check(version == 55) { + "Expected class files be of version 55, got $version" + } + } + }, 0) + } else if (entry.name.endsWith(".kotlin_module")) { + val metadata = KotlinModuleMetadata.read(zis.readAllBytes()) + metadata.version.apply { + check("$major.$minor.$patch" == "1.9.0") { + "Expected Kotlin version 1.9.0, got $major.$minor.$patch." + } + } + } + entry = zis.nextEntry + } + } + } + } +} + +group = "com.example" +version = "0.0.0-SNAPSHOT" +publishing { + repositories { + maven { + name = "test" + url = uri(file("build/m2")) + } + } + publications { + create("maven") { + from(components["java"]) + } + } +} + +tasks.register("cleanM2", Delete::class.java) { + delete(file("build/m2")) +} +tasks.named("publishAllPublicationsToTestRepository") { + dependsOn("cleanM2") +} +tasks.register("verifyCompatibility", VerifyCompatibility::class.java) { + m2Dir.from(fileTree(layout.buildDirectory.dir("m2"))) + dependsOn("publishAllPublicationsToTestRepository") +} + diff --git a/test-kgp-1-9/gradle b/test-kgp-1-9/gradle new file mode 120000 index 0000000..3337596 --- /dev/null +++ b/test-kgp-1-9/gradle @@ -0,0 +1 @@ +../gradle \ No newline at end of file diff --git a/test-kgp-1-9/gradlew b/test-kgp-1-9/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/test-kgp-1-9/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/test-kgp-1-9/settings.gradle.kts b/test-kgp-1-9/settings.gradle.kts new file mode 100644 index 0000000..3eafbaf --- /dev/null +++ b/test-kgp-1-9/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + listOf(repositories, dependencyResolutionManagement.repositories).forEach { + it.mavenCentral() + } +} + + +includeBuild("../") diff --git a/test-kgp-1-9/src/main/kotlin/hello.kt b/test-kgp-1-9/src/main/kotlin/hello.kt new file mode 100644 index 0000000..1a90b56 --- /dev/null +++ b/test-kgp-1-9/src/main/kotlin/hello.kt @@ -0,0 +1 @@ +val hello = "Hello" diff --git a/tests/check-api-dependencies/build.gradle.kts b/tests/check-api-dependencies/build.gradle.kts index d21a74d..e472c5d 100644 --- a/tests/check-api-dependencies/build.gradle.kts +++ b/tests/check-api-dependencies/build.gradle.kts @@ -10,6 +10,7 @@ compatPatrouille { } dependencies { + // Uncomment to make `compatPatrouilleCheckApiDependencies` fail because incompatible metadata was exposed to consumers. // api("org.jetbrains.kotlin:kotlin-stdlib:2.1.21") api(libs.kotlinx.metadata) }