Skip to content

Commit 1e419ad

Browse files
authored
Check Java class files version (#49)
1 parent 15a7900 commit 1e419ad

File tree

13 files changed

+294
-8
lines changed

13 files changed

+294
-8
lines changed

tapmoc-gradle-plugin/build.gradle.kts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ dependencies {
5050
compileOnly(libs.gradle.api)
5151
implementation(libs.gratatouille.wiring.runtime)
5252
gratatouille(project(":tapmoc-tasks"))
53+
54+
testImplementation(gradleTestKit())
55+
testImplementation(kotlin("test"))
5356
}
5457

5558
gratatouille {
@@ -59,3 +62,14 @@ gratatouille {
5962
pluginLocalPublication("com.gradleup.tapmoc")
6063
}
6164

65+
tasks.withType<Test>().configureEach {
66+
dependsOn("publishAllPublicationsToLocalRepository")
67+
dependsOn(":tapmoc-tasks:publishAllPublicationsToLocalRepository")
68+
}
69+
70+
extensions.getByType<PublishingExtension>().repositories {
71+
maven {
72+
name = "local"
73+
url = rootDir.resolve("build/m2").toURI()
74+
}
75+
}

tapmoc-gradle-plugin/src/main/kotlin/tapmoc/internal/TapmocExtensionImpl.kt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ package tapmoc.internal
22

33
import org.gradle.api.DefaultTask
44
import org.gradle.api.NamedDomainObjectSet
5-
import tapmoc.TapmocExtension
6-
import tapmoc.Severity
7-
import tapmoc.configureJavaCompatibility
8-
import tapmoc.configureKotlinCompatibility
9-
import tapmoc.task.registerCheckKotlinMetadataTask
105
import org.gradle.api.Project
11-
import org.gradle.api.Task
126
import org.gradle.api.artifacts.Configuration
137
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
148
import org.gradle.api.attributes.Usage
159
import org.gradle.api.provider.Provider
1610
import org.gradle.api.tasks.TaskProvider
11+
import tapmoc.Severity
12+
import tapmoc.TapmocExtension
13+
import tapmoc.configureJavaCompatibility
14+
import tapmoc.configureKotlinCompatibility
15+
import tapmoc.task.registerCheckJavaClassFilesVersionTask
16+
import tapmoc.task.registerCheckKotlinMetadataTask
1717
import tapmoc.task.registerCheckKotlinStdlibVersionsTask
1818

1919
internal abstract class TapmocExtensionImpl(private val project: Project) : TapmocExtension {
2020
private var kotlinVersion: String? = null
21+
private var javaVersion: Int? = null
2122
private var kotlinMetadataSeverity = Severity.ERROR
2223
private var kotlinStdlibSeverity = Severity.ERROR
2324

@@ -28,6 +29,8 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm
2829

2930
init {
3031
val kotlinVersionProvider = project.provider { kotlinVersion ?: error("Tapmoc: please call Tapmoc::kotlin(version) to specify the target Kotlin version.") }
32+
val javaVersionProvider = project.provider { javaVersion ?: error("Tapmoc: please call Tapmoc::java(version) to specify the target Java version.") }
33+
3134
apiDependencies = project.configurations.register("tapmocApiDependencies") {
3235
it.isCanBeConsumed = false
3336
it.isCanBeResolved = true
@@ -66,9 +69,18 @@ internal abstract class TapmocExtensionImpl(private val project: Project) : Tapm
6669
)
6770
checkKotlinStdlib.configure { it.isEnabled = false }
6871
project.tasks.named("check").configure { it.dependsOn(checkKotlinStdlib) }
72+
73+
val checkJavaClassFilesVersionTask = project.registerCheckJavaClassFilesVersionTask(
74+
taskName = "tapmocCheckJavaClassFilesVersion",
75+
warningAsError = project.provider { kotlinStdlibSeverity == Severity.ERROR },
76+
javaVersion = javaVersionProvider,
77+
jarFiles = project.files(apiDependencies, runtimeDependencies)
78+
)
79+
project.tasks.named("check").configure { it.dependsOn(checkJavaClassFilesVersionTask) }
6980
}
7081

7182
override fun java(version: Int) {
83+
javaVersion = version
7284
project.configureJavaCompatibility(version)
7385
}
7486

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import java.io.File
2+
import java.util.Properties
3+
import kotlin.test.Test
4+
import org.gradle.testkit.runner.GradleRunner
5+
import org.junit.Assert.assertTrue
6+
7+
class Tests {
8+
@Test
9+
fun wrongJavaBytecodeIsDetected() {
10+
withTestProject("java") {
11+
val result = GradleRunner.create()
12+
.withProjectDir(it)
13+
.withDebug(false)
14+
.withArguments("build")
15+
.forwardOutput()
16+
.buildAndFail()
17+
18+
assertTrue(result.output.contains("targets class file version 55 (Java 11) which is newer than supported <= 52 (Java 8)."))
19+
}
20+
}
21+
22+
@Test
23+
fun metaInfIsExcluded() {
24+
withTestProject("java-meta-inf") {
25+
GradleRunner.create()
26+
.withProjectDir(it)
27+
.withDebug(false)
28+
.withArguments("build")
29+
.forwardOutput()
30+
.build()
31+
}
32+
}
33+
}
34+
35+
36+
37+
private fun withTestProject(name: String, block: (File) -> Unit) {
38+
val src = File("testProjects/$name")
39+
val dst = File("build/testProject")
40+
dst.deleteRecursively()
41+
42+
src.copyRecursively(dst)
43+
44+
dst.walk().onLeave {
45+
if (it.isDirectory && it.name == "build") {
46+
it.deleteRecursively()
47+
}
48+
}.count() // count is just used to collect the sequence
49+
50+
val currentVersion = Properties().apply {
51+
File("../librarian.root.properties").reader().use {
52+
load(it)
53+
}
54+
}
55+
dst.resolve("build.gradle.kts").let {
56+
it.writeText(it.readText().replace("PLACEHOLDER", currentVersion.get("pom.version").toString()))
57+
}
58+
block(dst)
59+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import tapmoc.Severity
2+
3+
buildscript {
4+
dependencies {
5+
classpath("com.gradleup.tapmoc:tapmoc-gradle-plugin:PLACEHOLDER")
6+
}
7+
}
8+
9+
plugins {
10+
id("java")
11+
}
12+
13+
pluginManager.apply("com.gradleup.tapmoc")
14+
extensions.getByType(tapmoc.TapmocExtension::class.java).apply {
15+
java(8)
16+
checkDependencies(Severity.ERROR)
17+
}
18+
19+
dependencies {
20+
implementation("com.squareup.okhttp3:okhttp-jvm:5.3.2")
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pluginManagement {
2+
listOf(repositories, dependencyResolutionManagement.repositories).forEach {
3+
it.mavenCentral()
4+
it.maven("../../../build/m2")
5+
}
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.example;
2+
3+
public class Hello {
4+
public static String world = "Hello World";
5+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import javax.tools.DiagnosticCollector
2+
import javax.tools.JavaFileObject
3+
import javax.tools.StandardLocation
4+
import javax.tools.ToolProvider
5+
import tapmoc.Severity
6+
7+
buildscript {
8+
dependencies {
9+
classpath("com.gradleup.tapmoc:tapmoc-gradle-plugin:PLACEHOLDER")
10+
}
11+
}
12+
13+
plugins {
14+
id("java")
15+
}
16+
17+
pluginManager.apply("com.gradleup.tapmoc")
18+
extensions.getByType(tapmoc.TapmocExtension::class.java).apply {
19+
java(8)
20+
kotlin("2.0.0") // This should be a no-op
21+
checkDependencies(Severity.ERROR)
22+
}
23+
24+
abstract class GenerateClasses: DefaultTask() {
25+
@get:OutputDirectory
26+
abstract val output: DirectoryProperty
27+
28+
@get:InputFile
29+
abstract val source: RegularFileProperty
30+
31+
@TaskAction
32+
fun taskAction() {
33+
val javac = ToolProvider.getSystemJavaCompiler()
34+
val diagnostics = DiagnosticCollector<JavaFileObject>()
35+
javac.getStandardFileManager(diagnostics, null, null).use { fm ->
36+
fm.setLocation(StandardLocation.CLASS_OUTPUT, listOf(output.get().asFile))
37+
38+
val javaSourceFiles = listOf(source.asFile.get())
39+
val fileObjects = fm.getJavaFileObjectsFromFiles(javaSourceFiles)
40+
41+
val options = buildList {
42+
add("--release")
43+
add("11") // Compile using a higher version
44+
}
45+
46+
val task = javac.getTask(null, fm, diagnostics, options, null, fileObjects)
47+
48+
val success = task.call()
49+
if (!success) {
50+
error("Java compilation failed.")
51+
}
52+
}
53+
}
54+
}
55+
56+
val generateClasses = tasks.register("javaClasses", GenerateClasses::class.java) {
57+
output.set(file("build/javaClasses"))
58+
source.set(file("Hello.java"))
59+
}
60+
61+
val jar = tasks.register("javaJar", Jar::class.java) {
62+
from(generateClasses)
63+
archiveClassifier = "higher"
64+
}
65+
66+
dependencies {
67+
implementation(files(jar))
68+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pluginManagement {
2+
listOf(repositories, dependencyResolutionManagement.repositories).forEach {
3+
it.mavenCentral()
4+
it.maven("../../../build/m2")
5+
}
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import com.example.Hello;
2+
3+
import java.util.ArrayList;
4+
public class Main {
5+
public static void main(String[] args) {
6+
7+
ArrayList<String> list = new ArrayList<String>();
8+
list.add("foo");
9+
// This must fail to compile because it's not in JDK 11
10+
// list.removeFirst();
11+
12+
System.out.println(Hello.world);
13+
}
14+
}

tapmoc-tasks/api/tapmoc-tasks.api

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
public final class tapmoc/task/CheckJavaClassFilesVersionEntryPoint {
2+
public static final field Companion Ltapmoc/task/CheckJavaClassFilesVersionEntryPoint$Companion;
3+
public fun <init> ()V
4+
public static final fun run (Ljava/util/function/BiConsumer;ZLjava/util/List;ILjava/io/File;)V
5+
}
6+
7+
public final class tapmoc/task/CheckJavaClassFilesVersionEntryPoint$Companion {
8+
public final fun run (Ljava/util/function/BiConsumer;ZLjava/util/List;ILjava/io/File;)V
9+
}
10+
111
public final class tapmoc/task/CheckKotlinMetadataEntryPoint {
212
public static final field Companion Ltapmoc/task/CheckKotlinMetadataEntryPoint$Companion;
313
public fun <init> ()V

0 commit comments

Comments
 (0)