blob: 844686d0acb4edc4ebe2595c845b069723037eb7 [file] [log] [blame]
import de.undercouch.gradle.tasks.download.Download
plugins {
// https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow (v5 requires Gradle 5)
id 'com.github.johnrengelman.shadow' version '6.1.0'
// https://plugins.gradle.org/plugin/de.undercouch.download
id "de.undercouch.download" version "4.1.1"
id 'java'
// https://github.com/tbroyer/gradle-errorprone-plugin
id "net.ltgt.errorprone" version "2.0.1"
// https://plugins.gradle.org/plugin/org.ajoberstar.grgit
id 'org.ajoberstar.grgit' version '4.1.0' apply false
// https://github.com/n0mer/gradle-git-properties ; target is: generateGitProperties
id "com.gorylenko.gradle-git-properties" version "2.3.1"
}
apply plugin: "de.undercouch.download"
import org.ajoberstar.grgit.Grgit
repositories {
jcenter()
mavenCentral()
}
gitProperties {
// logically belongs in framework, but only checker resources are copied to .jar file
gitPropertiesResourceDir = file("${project.rootDir}/checker/src/main/resources")
}
ext {
release = false
// On a Java 8 JVM, use error-prone javac and source/target 8.
// On a Java 9+ JVM, use the host javac, default source/target, and required module flags.
isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8
errorproneJavacVersion = '9+181-r4173-1'
parentDir = file("${rootDir}/../").absolutePath
annotationTools = "${parentDir}/annotation-tools"
afu = "${annotationTools}/annotation-file-utilities"
stubparser = "${parentDir}/stubparser"
stubparserJar = "${stubparser}/javaparser-core/target/stubparser-3.20.2.1.jar"
jtregHome = "${parentDir}/jtreg"
formatScriptsHome = "${project(':checker').projectDir}/bin-devel/.run-google-java-format"
plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts"
htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools"
javadocMemberLevel = JavadocMemberLevel.PROTECTED
// The local git repository, typically in the .git directory, but not for worktrees.
// This value is always overwritten, but Gradle needs the variable to be initialized.
localRepo = ".git"
}
// Keep in sync with check in org.checkerframework.framework.source.SourceChecker.init
// and with text in #installation
switch (JavaVersion.current()) {
case JavaVersion.VERSION_1_9:
case JavaVersion.VERSION_1_10:
case JavaVersion.VERSION_12:
logger.warn("The Checker Framework has only been tested with JDK 8 and 11." +
" Found version " + JavaVersion.current().majorVersion);
break;
case JavaVersion.VERSION_1_8:
case JavaVersion.VERSION_11:
break; // Supported versions
default:
throw new GradleException("Build the Checker Framework with JDK 8 or JDK 11." +
" Found version " + JavaVersion.current().majorVersion);
}
task setLocalRepo(type:Exec) {
commandLine 'git', 'worktree', 'list'
standardOutput = new ByteArrayOutputStream()
doLast {
String worktreeList = standardOutput.toString()
localRepo = worktreeList.substring(0, worktreeList.indexOf(" ")) + "/.git"
}
}
// No group so it does not show up in the output of `gradlew tasks`
task installGitHooks(type: Copy, dependsOn: 'setLocalRepo') {
description 'Copies git hooks to .git directory'
from files("checker/bin-devel/git.post-merge", "checker/bin-devel/git.pre-commit")
rename('git\\.(.*)', '$1')
into localRepo + "/hooks"
}
allprojects {
apply plugin: 'java'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: "de.undercouch.download"
apply plugin: 'net.ltgt.errorprone'
group 'org.checkerframework'
// Increment the minor version (second number) rather than just the patch
// level (third number) if:
// * any new checkers have been added, or
// * backward-incompatible changes have been made to APIs or elsewhere.
version '3.13.1-SNAPSHOT'
repositories {
mavenCentral()
}
configurations {
javacJar
// Holds the combined classpath of all subprojects including the subprojects themselves.
allProjects
// Exclude checker-qual dependency added by Error Prone to avoid a circular dependency.
annotationProcessor.exclude group:'org.checkerframework', module:'checker-qual'
}
configurations {
checkerFatJar
}
dependencies {
if (isJava8) {
javacJar group: 'com.google.errorprone', name: 'javac', version: "$errorproneJavacVersion"
}
errorproneJavac("com.google.errorprone:javac:$errorproneJavacVersion")
allProjects subprojects
checkerFatJar project(path: ':checker', configuration: 'shadow')
}
// After all the tasks have been created, modify some of them.
afterEvaluate {
// Add the fat checker.jar to the classpath of every Javadoc task. This allows Javadoc in
// any module to reference classes in any other module.
// Also, build and use ManualTaglet as a taglet.
tasks.withType(Javadoc) {
def tagletVersion = isJava8 ? 'taglet' : 'tagletJdk11'
dependsOn(':checker:shadowJar')
dependsOn(":framework-test:${tagletVersion}Classes")
doFirst {
options.encoding = 'UTF-8'
if (!name.equals("javadocDoclintAll")) {
options.memberLevel = javadocMemberLevel
}
classpath += rootProject.configurations.getByName('checkerFatJar').asFileTree
if (isJava8) {
classpath += configurations.javacJar
}
options.taglets 'org.checkerframework.taglet.ManualTaglet'
options.tagletPath(project(':framework-test').sourceSets."${tagletVersion}".output.classesDirs.getFiles() as File[])
// We want to link to Java 9 documentation of the compiler classes since we use Java 9
// versions of those classes and Java 8 for everything else. Because the compiler classes are not
// a part of the main documentation of Java 8, javadoc links to the Java 9 versions.
// TODO, this creates broken links to the com.sun.tools.javac package.
options.links = ['https://docs.oracle.com/javase/8/docs/api/', 'https://docs.oracle.com/javase/9/docs/api/']
// This file is looked for by Javadoc.
file("${destinationDir}/resources/fonts/").mkdirs()
ant.touch(file: "${destinationDir}/resources/fonts/dejavu.css")
options.addStringOption('source', '8')
// "-Xwerror" requires Javadoc everywhere. Currently, CI jobs require Javadoc only
// on changed lines. Enable -Xwerror in the future when all Javadoc exists.
// options.addBooleanOption('Xwerror', true)
options.addStringOption('Xmaxwarns', '99999')
}
}
// Add standard javac options
tasks.withType(JavaCompile) { compilationTask ->
dependsOn(':installGitHooks')
// Put source files in deterministic order, for debugging.
compilationTask.source = compilationTask.source.sort()
sourceCompatibility = 8
targetCompatibility = 8
// Because the target is 8, all of the public compiler classes are accessible, so
// --add-exports are not required, (nor are they allowed with target 8). See
// https://openjdk.java.net/jeps/247 for details on compiling for older versions.
// When sourceCompatibilty is changed to 11, then the following will be required.
// options.compilerArgs += [
// "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
// "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
// ]
// This is equivalent to writing "exports jdk.compiler/... to ALL-UNNAMED" in the
// module-info.java of jdk.compiler, so corresponding --add-opens are only required for
// reflective access to private members.
//
// From https://openjdk.java.net/jeps/261, Section titled: "Breaking encapsulation"
// "The effect of each instance [of --add-exports] is to add a qualified export of the
// named package from the source module to the target module. This is, essentially, a
// command-line form of an exports clause in a module declaration[...].
// [...]
// The --add-exports option enables access to the public types of a specified package.
// It is sometimes necessary to go further and enable access to all non-public elements
// via the setAccessible method of the core reflection API. The --add-opens option can
// be used, at run time, to do this."
options.failOnError = true
options.deprecation = true
options.compilerArgs += [
'-g',
'-Werror',
// -options: To not get a warning about missing bootstrap classpath for Java 8 (once we use Java 9).
// -fallthrough: Don't check fallthroughs. Instead, use Error Prone. Its
// warnings are suppressible with a "// fall through" comment.
"-Xlint:-options,-fallthrough",
"-Xlint",
]
options.encoding = 'UTF-8'
options.fork = true
if (isJava8) {
options.forkOptions.jvmArgs += ["-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()]
}
// Error prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency.
// TODO: enable Error Prone on test classes.
if (compilationTask.name.equals('compileJava') && !project.name.startsWith('checker-qual')) {
// Error Prone must be available in the annotation processor path
options.annotationProcessorPath = configurations.errorprone
// Enable Error Prone
options.errorprone.enabled = true
options.errorprone.disableWarningsInGeneratedCode = true
options.errorprone.errorproneArgs = [
// Many compiler classes are interned.
'-Xep:ReferenceEquality:OFF',
// These might be worth fixing.
'-Xep:DefaultCharset:OFF',
// Not useful to suggest Splitter; maybe clean up.
'-Xep:StringSplitter:OFF',
// Too broad, rejects seemingly-correct code.
'-Xep:EqualsGetClass:OFF',
// Not a real problem
'-Xep:MixedMutabilityReturnType:OFF',
// Don't want to add a dependency to ErrorProne.
'-Xep:AnnotateFormatMethod:OFF',
// Warns for every use of "@checker_framework.manual"
'-Xep:InvalidBlockTag:OFF',
// Recommends writing @InlineMe which is an Error-Prone-specific annotation
'-Xep:InlineMeSuggester:OFF',
// -Werror halts the build if Error Prone issues a warning, which ensures that
// the errors get fixed. On the downside, Error Prone (or maybe the compiler?)
// stops as soon as it issues one warning, rather than outputting them all.
// https://github.com/google/error-prone/issues/436
'-Werror',
]
} else {
options.errorprone.enabled = false
}
}
}
}
task cloneAndBuildDependencies(type: Exec, group: 'Build') {
description 'Clones (or updates) and builds all dependencies'
executable 'checker/bin-devel/build.sh'
}
task maybeCloneAndBuildDependencies() {
// No group so it does not show up in the output of `gradlew tasks`
description 'Clones (or updates) and builds all dependencies if they are not present.'
onlyIf {
!file(stubparserJar).exists()
// The jdk repository is cloned via the copyAndMinimizeAnnotatedJdkFiles task that is run if
// the repository does not exist when building checker.jar.
}
doFirst {
if (file(stubparser).exists()) {
exec {
workingDir stubparser
executable 'git'
args = ['pull', '-q']
ignoreExitValue = true
}
exec {
workingDir stubparser
executable "${stubparser}/.travis-build-without-test.sh"
}
} else {
executable 'checker/bin-devel/build.sh'
}
}
doLast {
if (!file(stubparserJar).exists()) {
exec {
workingDir ${stubparser}/javaparser-core/target
executable 'ls'
ignoreExitValue = true
}
throw new RuntimeException("Can't find stubparser jar: " + stubparserJar)
}
}
}
task version(group: 'Documentation') {
description 'Print Checker Framework version'
doLast {
println version
}
}
/**
* Creates a task that runs the checker on the main source set of each subproject. The task is named
* "check${taskName}", for example "checkPurity" or "checkNullness".
*
* @param projectName name of the project
* @param taskName short name (often the checker name) to use as part of the task name
* @param checker fully qualified name of the checker to run
* @param args list of arguments to pass to the checker
*/
def createCheckTypeTask(projectName, taskName, checker, args = []) {
project("${projectName}").tasks.create(name: "check${taskName}", type: JavaCompile, dependsOn: ':checker:shadowJar') {
description "Run the ${taskName} Checker on the main sources."
group 'Verification'
// Always run the task.
outputs.upToDateWhen { false }
source = project("${projectName}").sourceSets.main.java
classpath = files(project("${projectName}").compileJava.classpath,project(':checker-qual').sourceSets.main.output)
destinationDir = file("${buildDir}")
options.annotationProcessorPath = files(project(':checker').tasks.shadowJar.archivePath)
options.compilerArgs += [
'-processor', "${checker}",
'-proc:only',
'-Xlint:-processing',
'-Xmaxerrs', '10000',
'-Xmaxwarns', '10000',
'-ArequirePrefixInWarningSuppressions',
'-AwarnUnneededSuppressions',
]
options.compilerArgs += args
options.forkOptions.jvmArgs += ["-Xmx2g"]
if (isJava8) {
options.compilerArgs += [
"-source",
"8",
"-target",
"8"
]
} else {
options.fork = true
options.forkOptions.jvmArgs += [
"--add-opens", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
]
}
}
}
/**
* Returns a list of all the Java files that should be formatted for the given project. These are:
*
* All java files in the main sourceSet.
* All java files in the tests directory that compile.
*
* @param projectName name of the project to format
* @return a list of all Java files that should be formatted for projectName
*/
List<String> getJavaFilesToFormat(projectName) {
List<File> javaFiles = new ArrayList<>();
project(':' + projectName).sourceSets.forEach { set ->
javaFiles.addAll(set.java.files)
}
// Collect all java files in tests directory
fileTree("${project(projectName).projectDir}/tests").visit { details ->
// If you change this, also change checker/bin-devel/git.pre-commit
if (!details.path.contains("nullness-javac-errors")
&& !details.path.contains("returnsreceiverdelomboked")
&& !details.path.contains("build")
&& details.name.endsWith('.java')) {
javaFiles.add(details.file)
}
}
// Collect all java files in jtreg directory
fileTree("${project(projectName).projectDir}/jtreg").visit { details ->
if (!details.path.contains("nullness-javac-errors") && details.name.endsWith('.java')) {
javaFiles.add(details.file)
}
}
// Collect all java files in jtregJdk11 directory
fileTree("${project(projectName).projectDir}/jtregJdk11").visit { details ->
if (!details.path.contains("nullness-javac-errors") && details.name.endsWith('.java')) {
javaFiles.add(details.file)
}
}
List<String> args = new ArrayList<>(javaFiles.size());
for (File f : javaFiles) {
args += project(projectName).relativePath(f)
}
return args
}
task htmlValidate(type: Exec, group: 'Format') {
description 'Validate that HTML files are well-formed'
executable 'html5validator'
args = [
"--ignore",
"/api/",
"/build/",
"/docs/manual/manual.html",
"/checker/jdk/nullness/src/java/lang/ref/package.html"
]
}
// `gradle allJavadoc` builds the Javadoc for all modules in `docs/api`.
// This is what is published to checkerframework.org.
// `gradle javadoc` builds the Javadoc for each sub-project in <subproject>/build/docs/javadoc/ .
// It's needed to create the Javadoc jars that we release in Maven Central.
// To make javadoc for only one subproject, run `./gradlew javadoc`
// in the subproject or `./gradlew :checker:javadoc` at the top level.
task allJavadoc(type: Javadoc, group: 'Documentation') {
description = 'Generates API documentation that includes all the modules.'
dependsOn(':checker:shadowJar', 'getPlumeScripts', 'getHtmlTools')
destinationDir = file("${rootDir}/docs/api")
source(project(':checker-util').sourceSets.main.allJava, project(':checker-qual').sourceSets.main.allJava, project(':checker').sourceSets.main.allJava, project(':framework').sourceSets.main.allJava,
project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava)
classpath = configurations.allProjects
if (isJava8) {
classpath += configurations.javacJar
}
doLast {
exec {
// Javadoc for to com.sun.tools.java.* is not publicly available, so these links are broken.
// This command removes those links.
workingDir "${rootDir}/docs/api"
executable "${plumeScriptsHome}/preplace"
args += ['<a href="https://docs.oracle.com/javase/9/docs/api/com/sun/tools/javac/.*?>(.*?)</a>', '\\1']
}
copy {
from 'docs/logo/Checkmark/CFCheckmark_favicon.png'
rename('CFCheckmark_favicon.png', 'favicon-checkerframework.png')
into "${rootDir}/docs/api"
}
exec {
workingDir "${rootDir}/docs/api"
executable "${htmlToolsHome}/html-add-favicon"
args += ['.', 'favicon-checkerframework.png']
}
}
}
// See documentation for allJavadoc task.
javadoc.dependsOn(allJavadoc)
configurations {
requireJavadoc
}
dependencies {
requireJavadoc "org.plumelib:require-javadoc:1.0.2"
}
task requireJavadoc(type: JavaExec, group: 'Documentation') {
description = 'Ensures that Javadoc documentation exists in source code.'
main = "org.plumelib.javadoc.RequireJavadoc"
classpath = configurations.requireJavadoc
args "checker/src/main/java", "dataflow/src/main/java", "framework-test/src/main/java", "framework/src/main/java", "javacutil/src/main/java"
}
/**
* Creates a task named taskName that runs javadoc with the -Xdoclint:all option.
*
* @param taskName the name of the task to create
* @param taskDescription description of the task
* @param memberLevel the JavadocMemberLevel to use
* @return the new task
*/
def createJavadocTask(taskName, taskDescription, memberLevel) {
tasks.create(name: taskName, type: Javadoc) {
description = taskDescription
destinationDir = file("${rootDir}/docs/tmpapi")
destinationDir.mkdirs()
subprojects.forEach {
if (!it.name.startsWith("checker-qual-android")) {
source += it.sourceSets.main.allJava
}
}
classpath = configurations.allProjects
destinationDir.deleteDir()
options.memberLevel = memberLevel
options.addBooleanOption('Xdoclint:all', true)
options.addStringOption('Xmaxwarns', '99999')
// options.addStringOption('skip', 'ClassNotToCheck|OtherClass')
}
}
createJavadocTask('javadocDoclintAll', 'Runs javadoc with -Xdoclint:all option.', JavadocMemberLevel.PRIVATE)
task manual(group: 'Documentation') {
description 'Build the manual'
doLast {
exec {
commandLine "make", "-C", "docs/manual", "all"
}
}
}
// No group so it does not show up in the output of `gradlew tasks`
task downloadJtreg(type: Download) {
description "Downloads and unpacks jtreg."
onlyIf { !(new File("${jtregHome}/lib/jtreg.jar").exists()) }
// src 'https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz'
// If ci.adoptopenjdk.net is down, use this copy.
src 'https://checkerframework.org/jtreg-4.2.0-tip.tar.gz'
dest new File(buildDir, 'jtreg-4.2.0-tip.tar.gz')
overwrite true
retries 3
doLast {
copy {
from tarTree(dest)
into "${jtregHome}/.."
}
exec {
commandLine('chmod', '+x', "${jtregHome}/bin/jtdiff", "${jtregHome}/bin/jtreg")
}
}
}
// See alternate implementation getCodeFormatScriptsInGradle below.
// No group so it does not show up in the output of `gradlew tasks`
task getCodeFormatScripts() {
description 'Obtain or update the run-google-java-format scripts'
if (file(formatScriptsHome).exists()) {
exec {
workingDir formatScriptsHome
executable 'git'
args = ['pull', '-q']
ignoreExitValue = true
}
} else {
exec {
workingDir "${formatScriptsHome}/../"
executable 'git'
args = ['clone', '-q', '--depth', '1', 'https://github.com/plume-lib/run-google-java-format.git', '.run-google-java-format']
}
}
}
// This implementation is preferable to the above because it does work in Gradle rather than in bash.
// However, it fails in the presence of worktrees: https://github.com/ajoberstar/grgit/issues/97 .
// No group so it does not show up in the output of `gradlew tasks`
task getCodeFormatScriptsInGradle {
description "Obtain the run-google-java-format scripts"
doLast {
if (! new File(formatScriptsHome).exists()) {
// There is no support for the --depth argument:
// https://github.com/ajoberstar/grgit/issues/155 https://bugs.eclipse.org/bugs/show_bug.cgi?id=475615
def rgjfGit = Grgit.clone(dir: formatScriptsHome, uri: 'https://github.com/plume-lib/run-google-java-format.git')
} else {
def rgjfGit = Grgit.open(dir: formatScriptsHome)
rgjfGit.pull()
}
}
}
// No group so it does not show up in the output of `gradlew tasks`
task getPlumeScripts() {
description 'Obtain or update plume-scripts'
if (file(plumeScriptsHome).exists()) {
exec {
workingDir plumeScriptsHome
executable 'git'
args = ['pull', '-q']
ignoreExitValue = true
}
} else {
exec {
workingDir "${plumeScriptsHome}/../"
executable 'git'
args = ['clone', '-q', '--depth', '1', 'https://github.com/plume-lib/plume-scripts.git', '.plume-scripts']
}
}
}
// No group so it does not show up in the output of `gradlew tasks`
task getHtmlTools() {
description 'Obtain or update html-tools'
if (file(htmlToolsHome).exists()) {
exec {
workingDir htmlToolsHome
executable 'git'
args = ['pull', '-q']
ignoreExitValue = true
}
} else {
exec {
workingDir "${htmlToolsHome}/../"
executable 'git'
args = ['clone', '-q', '--depth', '1', 'https://github.com/plume-lib/html-tools.git', '.html-tools']
}
}
}
// No group so it does not show up in the output of `gradlew tasks`
task pythonIsInstalled(type: Exec) {
description "Check that the python3 executable is installed."
executable = "python3"
args "--version"
}
task tags {
group 'Emacs'
description 'Create Emacs TAGS table'
doLast {
exec {
commandLine "etags", "-i", "checker/TAGS", "-i", "checker-qual/TAGS", "-i", "checker-util/TAGS", "-i", "dataflow/TAGS", "-i", "framework/TAGS", "-i", "framework-test/TAGS", "-i", "javacutil/TAGS", "-i", "docs/manual/TAGS"
}
exec {
commandLine "make", "-C", "docs/manual", "tags"
}
}
}
subprojects {
configurations {
errorprone
}
dependencies {
// https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core
// If you update this:
// * Temporarily comment out "-Werror" elsewhere in this file
// * Repeatedly run `./gradlew clean compileJava` and fix all errors
// * Uncomment "-Werror"
// * Don't edit framework/build.gradle to use the same version number
// (it is updated when the annotated version of Guava is updated).
errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.7.1'
}
task checkFormat(type: Exec, dependsOn: [getCodeFormatScripts, pythonIsInstalled], group: 'Format') {
description 'Check whether the source code is properly formatted'
// checker-qual-android project has no source, so skip
onlyIf {!project.name.startsWith('checker-qual-android') }
executable 'python3'
doFirst {
args += "${formatScriptsHome}/check-google-java-format.py"
args += getJavaFilesToFormat(project.name)
}
ignoreExitValue = true
doLast {
if (execResult.exitValue != 0) {
throw new RuntimeException('Found improper formatting, try running: ./gradlew reformat"')
}
}
}
task reformat(type: Exec, dependsOn: [getCodeFormatScripts, pythonIsInstalled], group: 'Format') {
description 'Format the Java source code'
// checker-qual-android project has no source, so skip
onlyIf {!project.name.startsWith('checker-qual-android') }
executable 'python3'
doFirst {
args += "${formatScriptsHome}/run-google-java-format.py"
args += getJavaFilesToFormat(project.name)
}
}
shadowJar {
// If you add an external dependency, then do the following:
// 1. Before adding the dependency, run ./gradlew copyJarsToDist.
// 2. Copy checker/dist/checker.jar elsewhere.
// 3. Add the dependency, then run ./gradlew clean copyJarsToDist.
// 4. Unzip both jars and compare the contents.
// 5. Add relocate lines below for the packages.
// 6. Do steps 3-5 until all new classes are in org/checkerframework/.
// Relocate packages that might conflict with user's classpath.
relocate 'org.apache', 'org.checkerframework.org.apache'
relocate 'org.relaxng', 'org.checkerframework.org.relaxng'
relocate 'org.plumelib', 'org.checkerframework.org.plumelib'
relocate 'org.codehaus', 'org.checkerframework.org.codehaus'
relocate 'org.objectweb.asm', 'org.checkerframework.org.objectweb.asm'
relocate 'io.github.classgraph', 'org.checkerframework.io.github.classgraph'
relocate 'nonapi.io.github.classgraph', 'org.checkerframework.nonapi.io.github.classgraph'
// relocate 'sun', 'org.checkerframework.sun'
relocate 'com.google', 'org.checkerframework.com.google'
relocate 'plume', 'org.checkerframework.plume'
exclude '**/module-info.class'
doFirst {
if (release) {
// Only relocate JavaParser during a release:
relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser'
}
}
}
if (!project.name.startsWith('checker-qual-android')) {
task tags(type: Exec) {
description 'Create Emacs TAGS table'
commandLine "bash", "-c", "find . \\( -name build \\) -prune -o -name '*.java' -print | sort-directory-order | xargs ctags -e -f TAGS"
}
}
java {
withJavadocJar()
withSourcesJar()
}
// Things in this block reference definitions in the subproject that do not exist,
// until the project is evaluated.
afterEvaluate {
// Adds manifest to all Jar files
tasks.withType(Jar) {
includeEmptyDirs = false
if (archiveFileName.get().startsWith("checker-qual") || archiveFileName.get().startsWith("checker-util")) {
metaInf {
from './LICENSE.txt'
}
} else {
metaInf {
from "${rootDir}/LICENSE.txt"
}
}
manifest {
attributes("Implementation-Version": "${project.version}")
attributes("Implementation-URL": "https://checkerframework.org")
if (! archiveFileName.get().endsWith("source.jar")) {
attributes('Automatic-Module-Name': "org.checkerframework." + project.name.replaceAll('-', '.'))
}
if (archiveFileName.get().startsWith("checker-qual") || archiveFileName.get().startsWith("checker-util")) {
attributes("Bundle-License": "MIT")
} else {
attributes("Bundle-License": "(GPL-2.0-only WITH Classpath-exception-2.0)")
}
}
}
// Add tasks to run various checkers on all the main source sets.
// These pass and are run by typecheckTests.
createCheckTypeTask(project.name, 'Formatter',
'org.checkerframework.checker.formatter.FormatterChecker')
createCheckTypeTask(project.name, 'Interning',
'org.checkerframework.checker.interning.InterningChecker',
['-Astubs=javax-lang-model-element-name.astub'])
createCheckTypeTask(project.name, 'NullnessOnlyAnnotatedFor',
'org.checkerframework.checker.nullness.NullnessChecker',
['-AskipUses=com.sun.*', '-AuseConservativeDefaultsForUncheckedCode=source'])
createCheckTypeTask(project.name, 'Purity',
'org.checkerframework.framework.util.PurityChecker')
createCheckTypeTask(project.name, 'Signature',
'org.checkerframework.checker.signature.SignatureChecker')
// These pass on some subprojects, which the `typecheck` task runs.
// TODO: Incrementally add @AnnotatedFor on more classes.
createCheckTypeTask(project.name, 'Nullness',
'org.checkerframework.checker.nullness.NullnessChecker',
['-AskipUses=com.sun.*'])
// Add jtregTests to framework and checker modules
if (project.name.is('framework') || project.name.is('checker')) {
tasks.create(name: 'jtregTests', dependsOn: ':downloadJtreg', group: 'Verification') {
description 'Run the jtreg tests.'
dependsOn('compileJava')
dependsOn('compileTestJava')
dependsOn('shadowJar')
String jtregOutput = "${buildDir}/jtreg"
String name = 'all'
String tests = '.'
doLast {
exec {
executable "${jtregHome}/bin/jtreg"
args = [
"-dir:${projectDir}/jtreg",
"-workDir:${jtregOutput}/${name}/work",
"-reportDir:${jtregOutput}/${name}/report",
"-verbose:error,fail",
// Don't add debugging information
// "-javacoptions:-g",
"-keywords:!ignore",
"-samevm",
"-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}",
// Required for checker/jtreg/nullness/PersistUtil.java and other tests
"-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}",
]
if (isJava8) {
// Use Error Prone javac and source/target 8
args += [
"-vmoptions:-Xbootclasspath/p:${configurations.javacJar.asPath}",
"-javacoptions:-Xbootclasspath/p:${configurations.javacJar.asPath}",
"-javacoptions:-source 8",
"-javacoptions:-target 8"
]
} else {
args += [
// checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java
// uses the jdk.jdeps module.
"-javacoptions:--add-modules jdk.jdeps",
"-javacoptions:--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED",
"-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
]
}
if (project.name.is('framework')) {
// Do not check for the annotated JDK
args += [
"-javacoptions:-ApermitMissingJdk"
]
} else if (project.name.is('checker')) {
args += [
"-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}",
]
}
// Location of jtreg tests
args += "${tests}"
}
}
}
}
// Create a task for each JUnit test class whose name is the same as the JUnit class name.
sourceSets.test.allJava.filter { it.path.contains('test/junit') }.forEach { file ->
String junitClassName = file.name.replaceAll(".java", "")
tasks.create(name: "${junitClassName}", type: Test) {
description "Run ${junitClassName} tests."
include "**/${name}.class"
}
}
// Configure JUnit tests
tasks.withType(Test) {
if (isJava8) {
jvmArgs "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString()
} else {
jvmArgs += [
"--illegal-access=warn",
"--add-opens", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
]
}
maxParallelForks = Integer.MAX_VALUE
if (project.name.is('checker')) {
dependsOn('copyJarsToDist')
}
if (project.hasProperty('emit.test.debug')) {
systemProperties += ["emit.test.debug": 'true']
}
testLogging {
showStandardStreams = true
// Always run the tests
outputs.upToDateWhen { false }
// Show the found unexpected diagnostics and expected diagnostics not found.
exceptionFormat "full"
events "failed"
}
// After each test, print a summary.
afterSuite { desc, result ->
if (desc.getClassName() != null) {
long mils = result.getEndTime() - result.getStartTime()
double seconds = mils / 1000.0
println "Testsuite: ${desc.getClassName()}\n" +
"Tests run: ${result.testCount}, " +
"Failures: ${result.failedTestCount}, " +
"Skipped: ${result.skippedTestCount}, " +
"Time elapsed: ${seconds} sec\n"
}
}
}
// Create a nonJunitTests task per project
tasks.create(name: 'nonJunitTests', group: 'Verification') {
description 'Run all Checker Framework tests except for the Junit tests and inference tests.'
if (project.name.is('framework') || project.name.is('checker')) {
dependsOn('jtregTests')
}
if (project.name.is('framework')) {
dependsOn('loaderTests')
}
if (project.name.is('checker')) {
if (!isJava8) {
dependsOn('jtregJdk11Tests')
}
dependsOn('nullnessExtraTests', 'commandLineTests', 'tutorialTests')
}
if (project.name.is('dataflow')) {
dependsOn('liveVariableTest')
dependsOn('issue3447Test')
}
}
// Create an inferenceTests task per project
tasks.create(name: 'inferenceTests', group: 'Verification') {
description 'Run inference tests.'
if (project.name.is('checker')) {
dependsOn('wholeProgramInferenceTests', 'wpiManyTests', 'wpiPlumeLibTests')
}
}
// Create a typecheck task per project (dogfooding the Checker Framework on itself).
// This isn't a test of the Checker Framework as the test and nonJunitTests tasks are.
// Tasks such as 'checkInterning' are constructed by createCheckTypeTask.
tasks.create(name: 'typecheck', group: 'Verification') {
description 'Run the Checker Framework on itself'
dependsOn('checkFormatter', 'checkInterning', 'checkPurity', 'checkSignature')
if (project.name.is('framework') || project.name.is('checker')) {
dependsOn('checkNullnessOnlyAnnotatedFor', 'checkCompilerMessages')
} else {
dependsOn('checkNullness')
}
}
// Create an allTests task per project.
// allTests = test + nonJunitTests + inferenceTests + typecheck
tasks.create(name: 'allTests', group: 'Verification') {
description 'Run all Checker Framework tests'
// The 'test' target is just the JUnit tests.
dependsOn('test', 'nonJunitTests', 'inferenceTests', 'typecheck')
}
task javadocPrivate(dependsOn: javadoc) {
doFirst {
javadocMemberLevel = JavadocMemberLevel.PRIVATE
}
doLast {
javadocMemberLevel = JavadocMemberLevel.PROTECTED
}
}
}
}
assemble.dependsOn(':checker:copyJarsToDist')
task checkBasicStyle(group: 'Format') {
description 'Check basic style guidelines, mostly whitespace. Not related to Checkstyle tool.'
String[] ignoreDirectories = ['.git',
'.gradle',
'.html-tools',
'.idea',
'.plume-scripts',
'.run-google-java-format',
'annotated',
'api',
'plume-bib',
'bootstrap',
'build',
'jdk']
String[] ignoreFilePatterns = [
'*.aux',
'*.bib',
'*.class',
'*.dvi',
'*.expected',
'*.gif',
'*.jar',
'*.jtr',
'*.log',
'*.out',
'*.patch',
'*.pdf',
'*.png',
'*.sty',
'*.toc',
'*.xcf',
'*~',
'#*#',
'CFLogo.ai',
'logfile.log.rec.index',
'manual.html',
'manual.html-e',
'junit.*.properties',
'securerandom.*',
'checker/dist/META-INF/maven/org.apache.bcel/bcel/pom.xml',
'checker/dist/META-INF/maven/org.apache.commons/commons-text/pom.xml',
'framework/src/main/resources/git.properties']
doLast {
FileTree tree = fileTree(dir: projectDir)
for (String dir : ignoreDirectories) {
tree.exclude "**/${dir}/**"
}
for (String file : ignoreFilePatterns) {
tree.exclude "**/${file}"
}
boolean failed = false
tree.visit {
if (!it.file.isDirectory()) {
boolean blankLineAtEnd = false
String fileName = it.file.getName()
boolean checkTabs = !fileName.equals("Makefile")
it.file.eachLine { line ->
if (line.endsWith(' ')) {
println("Trailing whitespace: ${it.file.absolutePath}")
failed = true
}
if (checkTabs && line.contains('\t')) {
println("Contains tab (use spaces): ${it.file.absolutePath}")
failed = true
checkTabs = false
}
if (!line.startsWith('\\') &&
(line.matches('^.* (else|finally|try)\\{}.*$')
|| line.matches('^.*}(catch|else|finally) .*$')
|| line.matches('^.* (catch|for|if|while)\\('))) {
// This runs on non-java files, too.
println("Missing space: ${it.file.absolutePath}")
failed = true
}
if (line.isEmpty()) {
blankLineAtEnd = true;
} else {
blankLineAtEnd = false;
}
}
if (blankLineAtEnd) {
println("Blank line at end of file: ${it.file.absolutePath}")
failed = true
}
RandomAccessFile file
try {
file = new RandomAccessFile(it.file, 'r')
int end = file.length() - 1;
if (end > 0) {
file.seek(end)
byte last = file.readByte()
if (last != '\n') {
println("Missing newline at end of file: ${it.file.absolutePath}")
failed = true
}
}
} finally {
if (file != null) {
file.close()
}
}
}
}
if (failed) {
throw new GradleException("Files do not meet basic style guidelines.")
}
}
}
assemble.mustRunAfter(clean)
task buildAll(group: 'Build') {
description 'Build all jar files, including source and javadoc jars'
dependsOn(allJavadoc)
subprojects { Project subproject ->
dependsOn("${subproject.name}:assemble")
dependsOn("${subproject.name}:javadocJar")
dependsOn("${subproject.name}:sourcesJar")
}
dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar')
}
task releaseBuild(group: 'Build') {
description 'Build everything required for a release'
dependsOn(clean)
doFirst {
release = true
}
// Use finalizedBy rather than dependsOn so that release is set to true before any of the tasks are run.
finalizedBy(buildAll)
}
// No group so it does not show up in the output of `gradlew tasks`
task releaseAndTest {
description 'Build everything required for a release and run allTests'
dependsOn(releaseBuild)
subprojects { Project subproject ->
dependsOn("${subproject.name}:allTests")
}
}
// Don't create an empty checker-framework-VERSION.jar
jar.onlyIf {false}
/**
* Adds the shared pom information to the given publication.
* @param publication MavenPublication
*/
final sharedPublicationConfiguration(publication) {
publication.pom {
url = 'https://checkerframework.org'
developers {
// These are the lead developers/maintainers, not all the developers or contributors.
developer {
id = 'mernst'
name = 'Michael Ernst'
email = 'mernst@cs.washington.edu'
url = 'https://homes.cs.washington.edu/~mernst/'
organization = 'University of Washington'
organizationUrl = 'https://www.cs.washington.edu/'
}
developer {
id = 'smillst'
name = 'Suzanne Millstein'
email = 'smillst@cs.washington.edu'
organization = 'University of Washington'
organizationUrl = 'https://www.cs.washington.edu/'
}
}
scm {
url = 'https://github.com/typetools/checker-framework.git'
connection = 'scm:git:git://github.com/typetools/checker-framework.git'
developerConnection = 'scm:git:ssh://git@github.com/typetools/checker-framework.git'
}
}
}