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
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[versions]
gratatouille-runtime = "0.2.1"
gratatouille-build-time = "0.2.2-SNAPSHOT-69754c55a2efca3d61b42618af2fbf395df6c471"
gratatouille-runtime = "0.2.3"
gratatouille-build-time = "0.2.3"
# Version of KGP we are using to build tapmoc
kgp-latest = "2.3.0"
ksp = "2.3.3"
Expand Down
2 changes: 1 addition & 1 deletion librarian.root.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
java.compatibility=11
kotlin.compatibility=2.0.21
kotlin.compatibility=1.9.0

kdoc.olderVersions=
kdoc.projects=:tapmoc-gradle-plugin,:tapmoc-tasks
Expand Down
12 changes: 12 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,20 @@ pluginManagement {
includeGroupByRegex("com\\.gradleup\\..*")
}
}
exclusiveContent {
forRepository { gradlePluginPortal() }
filter {
includeModule("org.gradle.toolchains.foojay-resolver-convention", "org.gradle.toolchains.foojay-resolver-convention.gradle.plugin")
includeModule("org.gradle.toolchains", "foojay-resolver")
}
}
}
}

plugins {
// Auto-provisions the JDK toolchains
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}

include(":tapmoc-gradle-plugin")
include(":tapmoc-tasks")
39 changes: 39 additions & 0 deletions tapmoc-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,49 @@ dependencies {

gratatouille {
addDependencies = false
// for included builds
pluginLocalPublication("com.gradleup.tapmoc")
// for publishToMavenLocal
pluginMarker("com.gradleup.tapmoc")
}

/**
* Gradle 8.0 does not run on recent JDKs (it supports up to Java 19), so its tests live in a separate
* source set that runs on a Java 17 toolchain.
*/
val gradle8TestCompilation = kotlin.target.compilations.create("gradle8Test") {
associateWith(kotlin.target.compilations.getByName("test"))
}

dependencies {
add(gradle8TestCompilation.implementationConfigurationName, gradleTestKit())
add(gradle8TestCompilation.implementationConfigurationName, kotlin("test"))
}

val gradle8Test = tasks.register<Test>("gradle8Test") {
group = "verification"
testClassesDirs = gradle8TestCompilation.output.classesDirs
classpath = gradle8TestCompilation.output.allOutputs +
gradle8TestCompilation.runtimeDependencyFiles +
kotlin.target.compilations.getByName("test").output.allOutputs
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
})
}

tasks.named("check") {
dependsOn(gradle8Test)
}

val cleanTestProjects = tasks.register<Delete>("cleanTestProjects") {
description = "Deletes the test projects left over by previous (failed) test runs."
delete(layout.buildDirectory.map { buildDir ->
buildDir.asFile.listFiles().orEmpty().filter { it.name.startsWith("testProject-") }
})
}

tasks.withType<Test>().configureEach {
dependsOn(cleanTestProjects)
dependsOn("publishAllPublicationsToLocalRepository")
dependsOn(":tapmoc-tasks:publishAllPublicationsToLocalRepository")
}
Expand Down
14 changes: 14 additions & 0 deletions tapmoc-gradle-plugin/src/gradle8Test/kotlin/Gradle8Tests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import kotlin.test.Test
import org.junit.Assert.assertTrue

class Gradle8Tests {
@Test
fun dependencyMismatchIsDetectedWithGradle8_0() {
withTestProject("gradle-8-0") {
gradleRunner(it, "build").withGradleVersion("8.0").buildAndFail().apply {
println(output.toString())
assertTrue(output.contains("targets class file version 55.0 (Java 11) which is newer than supported <= 52 (Java 8)."))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,17 @@ interface TapmocExtension {
*/
fun checkKotlinStdlibs(severity: Severity)


/**
* This is equivalent to calling `checkJavaClassFiles(runtimeConfiguration, severity)` and `checkKotlinMetadata(apiConfiguration, severity)`.
*
* Where:
* - `runtimeConfiguration` is the outgoing variant containing the runtime dependencies (typically, "apiElements").
* - `apiConfiguration` is the outgoing variant containing the API dependencies (typically, "runtimeElements").
*
* The actual name of the configurations is guessed depending on the applied plugins (Jvm/kmp/android/etc...).
* Tapmoc "reacts" to known configurations such as "apiElements", "runtimeElements", "jvmApiElements", etc... but might not be aware of
* all of them. For an example, if you're using custom Android variant names or custom KMP JVM targets.
*
* If the guessing didn't work, call [checkJavaClassFiles] and [checkKotlinMetadata] manually.
* In those cases, call [checkJavaClassFiles] and [checkKotlinMetadata] manually.
*
* Note: this doesn't call `checkKotlinStdlibs(runtimeConfigurationm, severity)` as kotlin-stdlib is usually upgraded at runtime. One notable exception is Gradle plugins.
* If you are developing a Gradle plugin, you may want to call `checkKotlinStdlibs(severity)`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.DefaultNamedDomainObjectCollection
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin
Expand Down Expand Up @@ -55,20 +56,20 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm

private fun configurationFor(configuration: String): NamedDomainObjectProvider<Configuration> {
val name = lowerCameCase("tapmoc", configuration)
var tapmocConfiguration = try {
val existing: NamedDomainObjectProvider<Configuration>? = try {
project.configurations.named(name)
} catch (_: UnknownDomainObjectException) {
null
}
if (tapmocConfiguration == null) {
tapmocConfiguration = project.configurations.register(name) {
it.isCanBeConsumed = false
it.isCanBeResolved = true
it.isVisible = false
it.extendsFrom(project.configurations.getByName(configuration))
}
if (existing != null) {
return existing
}
return project.configurations.register(name) {
it.isCanBeConsumed = false
it.isCanBeResolved = true
it.isVisible = false
it.extendsFrom(project.configurations.getByName(configuration))
}
return tapmocConfiguration
}

private fun fileCollectionFor(configuration: String): FileCollection {
Expand All @@ -86,7 +87,7 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm
}

override fun checkJavaClassFiles(severity: Severity) {
reactToPlugins(
reactToConfigurations(
onApi = {},
onRuntime = { checkJavaClassFiles(it, severity)}
)
Expand All @@ -104,7 +105,7 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm
}

override fun checkKotlinMetadata(severity: Severity) {
reactToPlugins(
reactToConfigurations(
onApi = {checkKotlinMetadata(it, severity) },
onRuntime = {}
)
Expand All @@ -131,7 +132,7 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm
}

override fun checkKotlinStdlibs(severity: Severity) {
reactToPlugins(
reactToConfigurations(
onApi = { },
onRuntime = { checkKotlinStdlibs(it, severity) }
)
Expand All @@ -141,58 +142,49 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm
checkDependencies(Severity.ERROR)
}

private fun reactToPlugins(onApi: (String) -> Unit, onRuntime: (String) -> Unit) {
var hasJava = false
var hasKotlinJvm = false
var hasKotlinMultiplatform = false

project.pluginManager.withPlugin("java") {
if (!hasKotlinJvm) {
onApi("apiElements")
onRuntime("runtimeElements")
}
hasJava = true
}
project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
if (!hasJava) {
onApi("apiElements")
onRuntime("runtimeElements")
private fun reactToConfigurations(onApi: (String) -> Unit, onRuntime: (String) -> Unit) {
val visitedConfigurations = mutableSetOf<String>()

/**
* Uses internal APIs. Before that, I tried:
* - determining the "good" configurations based on their attributes, and it's a mess that creates resolution errors
* - reacting to plugins being applied but in some cases, the configurations are not always registered from `Plugin::apply`
*
* Instead, "react" to known configuration being added. This is all a giant spaghetti plate, but I have no clue how to make
* things better at this point.
* If this doesn't work, there is always the explicit API that takes a configuration name.
*
* See https://github.com/gradle/gradle/issues/25262
*/
val onConfiguration = { name: String ->
if (visitedConfigurations.add(name)) {
when (name) {
"apiElements" -> onApi(name)
"runtimeElements" -> onRuntime(name)
"jvmApiElements" -> onApi(name)
"jvmRuntimeElements" -> onRuntime(name)
"releaseApiElements" -> onApi(name)
"releaseRuntimeElements" -> onRuntime(name)
"debugApiElements" -> onApi(name)
"debugRuntimeElements" -> onRuntime(name)
}
}
hasKotlinJvm = true
}
project.pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") {
onApi("jvmApiElements")
onRuntime("jvmRuntimeElements")

hasKotlinMultiplatform = true
}
project.pluginManager.withPlugin("com.android.library") {
onApi("releaseApiElements")
onRuntime("releaseRuntimeElements")
// Handle the existing configurations from a snapshot: the callbacks create configurations
// and Gradle 8.0 throws a ConcurrentModificationException if that happens while whenElementKnown() replays them.
project.configurations.names.toList().forEach(onConfiguration)

hasKotlinMultiplatform = true
}

project.afterEvaluate {
if (!hasJava && !hasKotlinJvm && !hasKotlinMultiplatform) {
val task = project.tasks.findByName("tapmocError")
if (task == null) {
val task2 = project.tasks.register("tapmocError") {
it.doFirst {
error("Tapmoc: checkDependencies() didn't find any supported plugin. Please call `checkJavaClassFiles()` and `checkKotlinMetadata()` instead.")
}
}
addToCheckTask(task2)
}
}
@Suppress("UNCHECKED_CAST")
(project.configurations as DefaultNamedDomainObjectCollection<Configuration>).whenElementKnown {
onConfiguration(it.name)
}
}

@Suppress("DEPRECATION")
override fun checkDependencies(severity: Severity) {
reactToPlugins(
onApi = { checkKotlinMetadata(it, severity) },
onRuntime = { checkJavaClassFiles(it, severity) }
)
checkJavaClassFiles(severity)
checkKotlinMetadata(severity)
}

@Deprecated(
Expand Down
39 changes: 39 additions & 0 deletions tapmoc-gradle-plugin/src/test/kotlin/TestUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import java.io.File
import java.util.Properties
import java.util.UUID
import org.gradle.testkit.runner.GradleRunner

fun gradleRunner(dir: File, vararg args: String): GradleRunner {
return GradleRunner.create()
.withProjectDir(dir)
.withDebug(false)
.withArguments(*args)
.forwardOutput()
}

fun withTestProject(name: String, block: (File) -> Unit) {
val src = File("testProjects/$name")
// Must stay directly under "build/": the test projects reference "../../../build/m2".
// The directory is unique so that tests can run in parallel.
val dst = File("build/testProject-$name-${UUID.randomUUID()}")

src.copyRecursively(dst)

dst.walk().onLeave {
if (it.isDirectory && it.name == "build") {
it.deleteRecursively()
}
}.count() // count is just used to collect the sequence

val currentVersion = Properties().apply {
File("../librarian.root.properties").reader().use {
load(it)
}
}
listOf("build.gradle.kts", "build.gradle").map { dst.resolve(it) }.first { it.exists() }.let {
it.writeText(it.readText().replace("PLACEHOLDER", currentVersion.get("pom.version").toString()))
}
block(dst)
// Keep the directory around if the test fails so it can be inspected
dst.deleteRecursively()
}
36 changes: 0 additions & 36 deletions tapmoc-gradle-plugin/src/test/kotlin/Tests.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import java.io.File
import java.util.Properties
import kotlin.test.Test
import org.gradle.testkit.runner.GradleRunner
import org.junit.Assert.assertTrue

class Tests {
private fun gradleRunner(dir: File, vararg args: String): GradleRunner {
return GradleRunner.create()
.withProjectDir(dir)
.withDebug(false)
.withArguments(*args)
.forwardOutput()
}

@Test
fun wrongJavaBytecodeIsDetected() {
withTestProject("java") {
Expand Down Expand Up @@ -48,28 +37,3 @@ class Tests {
}
}
}


private fun withTestProject(name: String, block: (File) -> Unit) {
val src = File("testProjects/$name")
val dst = File("build/testProject")
dst.deleteRecursively()

src.copyRecursively(dst)

dst.walk().onLeave {
if (it.isDirectory && it.name == "build") {
it.deleteRecursively()
}
}.count() // count is just used to collect the sequence

val currentVersion = Properties().apply {
File("../librarian.root.properties").reader().use {
load(it)
}
}
dst.resolve("build.gradle.kts").let {
it.writeText(it.readText().replace("PLACEHOLDER", currentVersion.get("pom.version").toString()))
}
block(dst)
}
16 changes: 16 additions & 0 deletions tapmoc-gradle-plugin/testProjects/gradle-8-0/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import tapmoc.Severity

plugins {
id("java")
id("com.gradleup.tapmoc").version("PLACEHOLDER")
}

extensions.getByType(tapmoc.TapmocExtension::class.java).apply {
java(8)
checkDependencies(Severity.ERROR)
}

dependencies {
// jetty 11 targets Java 11 (class file version 55)
implementation("org.eclipse.jetty:jetty-util:11.0.20")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pluginManagement {
listOf(repositories, dependencyResolutionManagement.repositories).forEach {
it.mavenCentral()
it.maven("../../../build/m2")
}
}
Loading