Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f04918a
Update to gradle 9.0.0
wmdietl Aug 4, 2025
5e6d033
Merge branch 'main-eisop' into gradle-9.0.0
wmdietl Sep 19, 2025
ac92643
Work around various configuration cache issues
wmdietl Sep 19, 2025
99c4b85
Enable the gradle configuration cache
wmdietl Sep 19, 2025
580575d
Update versions of plugins
wmdietl Sep 19, 2025
c1c2c5a
Increase memory for gradle
wmdietl Sep 19, 2025
3cc6290
Changed formatting by spotless update
wmdietl Sep 19, 2025
85d60b4
Address new PreferInstanceofOverGetKind Error Prone check
wmdietl Sep 19, 2025
d467bca
Test whether the subproject also needs to be Gradle 9.0.0
wmdietl Sep 19, 2025
996e14c
Add warning-mode all in CI
wmdietl Sep 19, 2025
03d6eae
Missed the one I care about...
wmdietl Sep 19, 2025
0ca6a14
Update Error Prone to 2.42.0
wmdietl Sep 20, 2025
241d0aa
Test whether things work with jspecify/jspecify
wmdietl Sep 20, 2025
611af72
Merge branch 'main-eisop' into gradle-9.0.0
wmdietl Sep 23, 2025
e0f35e8
Executing initialize-project in settings.gradle conflicts with the co…
wmdietl Sep 23, 2025
20e0ae4
Don't use the configuration cache for publishing
wmdietl Oct 2, 2025
0b7b570
Adapt more to the configuration cache, needs more checking
wmdietl Oct 2, 2025
03ee21e
Merge branch 'main-eisop' into gradle-9.0.0
wmdietl Feb 3, 2026
62af002
Update to Gradle 9.3.1
wmdietl Feb 3, 2026
f776cda
Update more versions
wmdietl Feb 3, 2026
0930273
Only enable Error Prone on supported JDKs
wmdietl Feb 3, 2026
862be3c
CI tests on JDK 17, 21, and 25
wmdietl Feb 3, 2026
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
17 changes: 10 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ env:

jobs:
build-and-test:
name: Build and Test
name: Build and Test on JDK ${{ matrix.java_version }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: jspecify-reference-checker
strategy:
matrix:
java_version: [17, 21, 25]
steps:
- name: Check out jspecify-reference checker
uses: actions/checkout@v5
Expand Down Expand Up @@ -47,20 +50,20 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
java-version: ${{ matrix.java_version }}
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build and Test (EISOP release)
if: ${{ env.EISOP_RELEASE == 'true' }}
# If a released CF is needed, use the following:
run: ./gradlew build conformanceTests demoTest --include-build ../jspecify
run: ./gradlew build conformanceTests demoTest --include-build ../jspecify --warning-mode all
env:
SHALLOW: 1
JSPECIFY_CONFORMANCE_TEST_MODE: details
- name: Build and Test (EISOP build)
if: ${{ env.EISOP_RELEASE != 'true' }}
# If a cloned EISOP CF is needed, use the following:
run: ./gradlew build conformanceTests demoTest --include-build ../jspecify --include-build ../checker-framework
run: ./gradlew build conformanceTests demoTest --include-build ../jspecify --include-build ../checker-framework --warning-mode all
env:
SHALLOW: 1
JSPECIFY_CONFORMANCE_TEST_MODE: details
Expand All @@ -73,11 +76,11 @@ jobs:
- name: Run Samples Tests (EISOP release)
if: ${{ always() && env.EISOP_RELEASE == 'true' }}
# If a released CF is needed, use the following:
run: ./gradlew jspecifySamplesTest --include-build ../jspecify
run: ./gradlew jspecifySamplesTest --include-build ../jspecify --warning-mode all
- name: Run Samples Tests (EISOP build)
if: ${{ always() && env.EISOP_RELEASE != 'true' }}
# If a cloned EISOP CF is needed, use the following:
run: ./gradlew jspecifySamplesTest --include-build ../jspecify --include-build ../checker-framework
run: ./gradlew jspecifySamplesTest --include-build ../jspecify --include-build ../checker-framework --warning-mode all

publish-snapshot:
name: Publish Conformance Test Framework Snapshot
Expand All @@ -95,7 +98,7 @@ jobs:
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Publish snapshot
run: ./gradlew publishConformanceTestFrameworkPublicationToSonatypeRepository
run: ./gradlew publishConformanceTestFrameworkPublicationToSonatypeRepository --warning-mode all --no-configuration-cache
env:
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.sonatype_username }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.sonatype_password }}
186 changes: 113 additions & 73 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import java.nio.file.Files
import org.gradle.plugins.ide.eclipse.model.Output
import org.gradle.plugins.ide.eclipse.model.SourceFolder

Expand All @@ -6,11 +7,11 @@ plugins {
id 'java'
id 'maven-publish'

id 'com.diffplug.spotless' version '6.25.0'
id 'com.diffplug.spotless' version '8.2.1'
id 'io.github.gradle-nexus.publish-plugin' version '2.0.0'
id 'net.ltgt.errorprone' version '4.1.0'
id 'net.ltgt.errorprone' version '5.0.0'
// To show task list as a tree, run: ./gradlew <taskname> taskTree
id 'com.dorongold.task-tree' version '4.0.0'
id 'com.dorongold.task-tree' version '4.0.1'
}

// Nexus Publish plugin requires a group/version at the root project.
Expand Down Expand Up @@ -59,6 +60,8 @@ ext {

// Location of the jspecify/jdk clone, relative to this directory
jspecifyJdkHome = '../jdk'

errorproneSupportsCurrentJdk = Integer.valueOf(JavaVersion.current().getMajorVersion()) >= 21
}

configurations {
Expand Down Expand Up @@ -96,8 +99,18 @@ dependencies {

conformanceTestSuite libs.jspecify.conformanceTests

errorproneJavac libs.errorProne.javac
errorprone libs.errorProne.core
if (errorproneSupportsCurrentJdk) {
errorproneJavac libs.errorProne.javac
errorprone libs.errorProne.core
}
}

configurations {
checkerFrameworkRuntimeClasspath {
canBeResolved = true
canBeConsumed = false
extendsFrom implementation
}
}

// If built with `--include-build path/to/checker-framework` then
Expand All @@ -112,17 +125,12 @@ if (jspecify != null) {
assemble.dependsOn(jspecify.task(':assemble'))
}

// Enable exec/javaexec
interface InjectedExecOps {
@Inject
ExecOperations getExecOps()
}

tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-Xlint:all")
// ErrorProne makes suppressing these easier
options.compilerArgs.add("-Xlint:-fallthrough")

options.errorprone.enabled = errorproneSupportsCurrentJdk
options.errorprone.disable("BadImport")
options.errorprone.disable("VoidUsed")

Expand All @@ -143,10 +151,7 @@ tasks.withType(JavaCompile).configureEach {
.collect { "--add-exports=jdk.compiler/com.sun.tools.javac.$it=ALL-UNNAMED" })
}

tasks.register('includeJSpecifyJDK') {
group = 'Build'
dependsOn 'compileJava'

tasks.register('copyJSpecifyJDK', Copy) {
def srcDir = "${jspecifyJdkHome}/src"
// This directory needs to be stored at the top-level of the resulting .jar file.
// org.checkerframework.framework.stub.AnnotationFileElementTypes will then load
Expand All @@ -156,39 +161,50 @@ tasks.register('includeJSpecifyJDK') {
inputs.dir file(srcDir)
outputs.dir file(dstDir)

def injected = project.objects.newInstance(InjectedExecOps)

doLast {
FileTree srcTree = fileTree(dir: srcDir)
NavigableSet<String> specFiles = new TreeSet<>();
srcTree.visit { FileVisitDetails fvd ->
if (!fvd.file.isDirectory() && fvd.file.name.matches('.*\\.java')) {
fvd.getFile().readLines().any { line ->
if (line.contains('org.jspecify')) {
specFiles.add(fvd.file.absolutePath)
return true;
}
FileTree srcTree = fileTree(dir: srcDir)
NavigableSet<String> specFiles = new TreeSet<>();
srcTree.visit { FileVisitDetails fvd ->
if (!fvd.file.isDirectory() && fvd.file.name.matches('.*\\.java')) {
fvd.getFile().readLines().any { line ->
if (line.contains('org.jspecify')) {
specFiles.add(fvd.file.absolutePath)
return true;
}
}
}
String absoluteSrcDir = file(srcDir).absolutePath
int srcPrefixSize = absoluteSrcDir.size()
copy {
from(srcDir)
into(dstDir)
for (String specFile : specFiles) {
include specFile.substring(srcPrefixSize)
}
}
injected.execOps.javaexec {
classpath = sourceSets.main.runtimeClasspath
standardOutput = System.out
errorOutput = System.err
}
String absoluteSrcDir = file(srcDir).absolutePath
int srcPrefixSize = absoluteSrcDir.size()

mainClass = 'org.checkerframework.framework.stubifier.JavaStubifier'
args dstDir
}
from(srcDir)
into(dstDir)
for (String specFile : specFiles) {
include specFile.substring(srcPrefixSize)
}
}

tasks.register('includeJSpecifyJDK', JavaExec) {
group = 'Build'

if (checkerFramework != null) {
dependsOn(checkerFramework.task(":checker:assembleForJavac"))
}
dependsOn 'copyJSpecifyJDK'

// See comment in copyJSpecifyJDK.
def dstDir = "${buildDir}/generated/resources/annotated-jdk/src/"

inputs.dir file(dstDir)
outputs.dir file(dstDir)

// We only need the CF on the classpath.
classpath = configurations.checkerFrameworkRuntimeClasspath

standardOutput = System.out
errorOutput = System.err

mainClass = 'org.checkerframework.framework.stubifier.JavaStubifier'
args dstDir
}

processResources.dependsOn(includeJSpecifyJDK)
Expand Down Expand Up @@ -230,10 +246,16 @@ tasks.register('jspecifySamplesTest', Test) {
description = 'Run the checker against the JSpecify samples.'
group = 'verification'
shouldRunAfter test
outputs.upToDateWhen { false }
include '**/NullSpecTest$Lenient.class'
include '**/NullSpecTest$Strict.class'

inputs.files(unzipConformanceTestSuite)
testClassesDirs = files('build/classes/java/test')
classpath = sourceSets.test.runtimeClasspath

// TODO: does this actually run on the correct tests? Should this unzip again??
def conformanceTests = layout.buildDirectory.dir("conformanceTests").get()
inputs.dir(conformanceTests)
}

tasks.register('unzipConformanceTestSuite', Copy) {
Expand All @@ -245,24 +267,33 @@ tasks.register('unzipConformanceTestSuite', Copy) {
tasks.register('conformanceTests', Test) {
group = 'verification'
include '**/ConformanceTest.class'
dependsOn 'unzipConformanceTestSuite'

shouldRunAfter test
outputs.upToDateWhen { false }

testClassesDirs = files('build/classes/java/test')
classpath = sourceSets.test.runtimeClasspath

def conformanceTests = layout.buildDirectory.dir("conformanceTests").get()
def deps = fileTree("${conformanceTests}/deps").join(":")

// Conformance tests
inputs.files(unzipConformanceTestSuite)
inputs.dir(conformanceTests)
inputs.files("tests/ConformanceTest-report.txt")
doFirst {
systemProperties([
"JSpecifyConformanceTest.inputs": "${unzipConformanceTestSuite.destinationDir}/assertions/org/jspecify/conformance/tests",
"JSpecifyConformanceTest.inputs": "${conformanceTests}/assertions/org/jspecify/conformance/tests",
"JSpecifyConformanceTest.report": "tests/ConformanceTest-report.txt",
"JSpecifyConformanceTest.deps" : fileTree("${unzipConformanceTestSuite.destinationDir}/deps").join(":")
"JSpecifyConformanceTest.deps" : deps
])
}

// Conformance tests run on the samples directory
inputs.files("tests/ConformanceTestOnSamples-report.txt")
doFirst {
systemProperties([
"JSpecifyConformanceTest.samples.inputs": "${unzipConformanceTestSuite.destinationDir}/samples",
"JSpecifyConformanceTest.samples.inputs": "${conformanceTests}/samples",
"JSpecifyConformanceTest.samples.report": "tests/ConformanceTestOnSamples-report.txt"
])
}
Expand All @@ -272,9 +303,10 @@ tasks.named('check').configure {
dependsOn('conformanceTests')
}

clean.doFirst {
tasks.register('deleteTestsBuild') {
delete "${rootDir}/tests/build/"
}
clean.dependsOn('deleteTestsBuild')

tasks.register('demoTest', Exec) {
group = 'verification'
Expand All @@ -284,10 +316,15 @@ tasks.register('demoTest', Exec) {
executable '/bin/sh'
args 'demo', 'SimpleSample.java'
ignoreExitValue = true
errorOutput = new ByteArrayOutputStream()
def outputFile = layout.buildDirectory.file('demoTest-output.txt')
outputs.file(outputFile)
doFirst {
errorOutput = Files.newOutputStream(outputFile.get().asFile.toPath())
}
doLast {
if (!errorOutput.toString().contains("SimpleSample.java:7: error:")) {
throw new AssertionError("`./demo SimpleSample.java` did not run correctly. Error output:\n$errorOutput")
def outputFileText = outputFile.get().asFile.text
if (!outputFileText.contains("SimpleSample.java:7: error:")) {
throw new AssertionError("`./demo SimpleSample.java` did not contain the expected error. Error output:\n${outputFileText}")
}
}
}
Expand All @@ -297,28 +334,31 @@ tasks.register('demoTest', Exec) {
google-java-format depends on checker-qual, which is built by a subproject.
On a clean build, the checker-qual JAR file doesn't exist yet, so Spotless throws an error.
The file doesn't have to be correct; it just has to be a JAR file.
So here, before the spotless block, we create a meaningless JAR file at that location if it doesn't already exist.
So here, before the spotless block, we create a meaningless JAR file at that location if it doesn't already exist.
See https://github.com/jspecify/jspecify-reference-checker/issues/81
*/

if (checkerFramework != null) {
def cfQualJar =
checkerFramework.projectDir.toPath()
.resolve("checker-qual/build/libs/checker-qual-${libs.versions.checkerFramework.get()}.jar")
def injected = project.objects.newInstance(InjectedExecOps)

if (!cfQualJar.toFile().exists()) {
mkdir(cfQualJar.parent)
injected.execOps.exec {
executable 'jar'
args = [
'cf',
cfQualJar,
buildFile.path // Use this build script file!
]
}
}
}
/* This work-around isn't compatible with the configuration cache.
We don't need it, b/c we depend on EISOP CF and Spotless depends on typetools CF.
*/
/*
if (checkerFramework != null) {
def cfQualJar =
checkerFramework.projectDir.toPath()
.resolve("checker-qual/build/libs/checker-qual-${libs.versions.checkerFramework.get()}.jar")
def injected = project.objects.newInstance(InjectedExecOps)
if (!cfQualJar.toFile().exists()) {
mkdir(cfQualJar.parent)
injected.execOps.exec {
executable 'jar'
args = [
'cf',
cfQualJar,
buildFile.path // Use this build script file!
]
}
}
}
*/

spotless {
java {
Expand All @@ -329,7 +369,7 @@ spotless {
groovyGradle {
target '**/*.gradle'
greclipse()
indentWithSpaces(4)
leadingTabsToSpaces(4)
trimTrailingWhitespace()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public void checkConformance(Path testDirectory, ImmutableList<Path> testDeps, P
switch (Mode.fromEnvironment()) {
case DETAILS:
System.out.print(testResults.report(true));
// fall-through
// fall-through

case COMPARE:
assertThat(testResults.report(false)).isEqualTo(asCharSource(testReport, UTF_8).read());
Expand Down
5 changes: 5 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
org.jspecify\:jspecify=1.0.0
org.jspecify.conformance\:conformance-test-framework=0.0.0-SNAPSHOT
org.jspecify.conformance\:conformance-tests=0.0.0-SNAPSHOT

org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
org.gradle.caching=true
org.gradle.configuration-cache=true
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Loading
Loading