| 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' |
| } |
| } |
| } |