Merge pull request #20 from bmuschko/refactoring

Reorganization of build code into plugins and general cleanup
This commit is contained in:
Bruce Eckel 2016-11-03 16:51:55 -07:00 committed by GitHub
commit 070ba92f1d
12 changed files with 413 additions and 379 deletions

2
.gitignore vendored
View File

@ -72,7 +72,7 @@ io/X.file
/files/Cheese.txt
/files/StreamInAndOut.txt
/.gradle/
.gradle
/*.iml
/.idea/

View File

@ -1,390 +1,23 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
maven {
url 'https://plugins.gradle.org/m2/'
}
}
dependencies {
classpath 'me.champeau.gradle:jmh-gradle-plugin:0.3.1'
classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M2'
}
}
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
id 'me.champeau.gradle.jmh' version '0.3.1'
}
import org.gradle.internal.jvm.Jvm
import org.apache.tools.ant.util.TeeOutputStream
boolean debug = false
class Tags {
Boolean hasMainMethod = false
Boolean compileTimeError = false
Boolean throwsException = false
Boolean errorOutputExpected = false
Boolean validateByHand = false
Boolean ignoreOutput = false // This tag isn't used in the build...
String fileRoot
String mainClass
String javaCmd = null
List<String> args = []
List<String> jVMArgs = []
String javap = null
String runFirst = null
String outputLine = null
private String block
def Tags(File file) {
block = file.text
hasMainMethod = block.contains('main(String[] args)')
def firstLine = block.substring(0, block.indexOf("\n"))
fileRoot = (firstLine.split("/")[-1] - ".java").trim() // Remove \r if it exists
mainClass = fileRoot
javaCmd = extract('java')
if(javaCmd) {
def pieces = javaCmd.split()
mainClass = pieces[0]
if(pieces.size() > 1)
for(p in pieces[1..-1])
if(p.startsWith("-"))
jVMArgs << p
else
args << p
}
compileTimeError = hasTag('CompileTimeError')
throwsException = hasTag('ThrowsException')
errorOutputExpected = hasTag('ErrorOutputExpected')
validateByHand = hasTag('ValidateByHand')
ignoreOutput = hasTag('IgnoreOutput')
javap = extract('javap') // Includes only arguments to command
runFirst = extract('RunFirst:')
outputLine = extractOutputLine()
}
private def hasTag(String marker) {
return block.contains("// {" + marker + "}")
}
def extractOutputLine() {
def matcher = (block =~ /(?m)^(\/\* Output:.*)$/)
if (matcher) {
return matcher[0][1]
} else {
return null
}
}
private def extract(String marker) {
// Assume some whitespace is after marker
if(!block.contains("// {${marker} "))
return null
def matcher = (block =~ /\/\/ \{${marker}\s+([^}]+)/)
if (matcher) {
def matched = matcher[0][1].trim()
return matched.replaceAll("\n?//", "")
} else {
println "Searching for: " + matcher
println block
System.exit(1)
}
}
public boolean hasTags() {
return compileTimeError ||
throwsException ||
errorOutputExpected ||
validateByHand ||
ignoreOutput ||
javaCmd ||
args ||
jVMArgs ||
javap ||
runFirst
}
public String toString() {
String result = ""
block.eachLine{ ln ->
if(ln.startsWith("//") || ln.startsWith("package "))
result += ln + "\n"
}
"""
hasMainMethod
compileTimeError
throwsException
errorOutputExpected
validateByHand
ignoreOutput
fileRoot
mainClass
javaCmd
args
jVMArgs
javap
runFirst
""".split().each { str ->
if(this[str])
result += str + ": " + this[str] + "\n"
}
result
}
}
ext {
junitJupiterVersion = '5.0.0-M2'
}
compileJava {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
apply plugin: 'com.mindviewinc.example-output-verification'
subprojects {
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'me.champeau.gradle.jmh'
apply plugin: 'java'
apply plugin: 'org.junit.platform.gradle.plugin'
//apply plugin: 'checkstyle'
//apply plugin: 'findbugs'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
dependencies {
// Logging:
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.+'
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.+'
// You can also use the JDK's built-in logging as the back end:
// compile group: 'org.slf4j', name: 'slf4j-jdk14', version: '1.7.5'
// JUnit testing:
compile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
testCompile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
testRuntime "org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}"
}
junitPlatform {
includeClassNamePattern '.*'
}
// See: http://blog.jessitron.com/2012/07/using-checkstyle-in-gradle.html
// https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
// http://checkstyle.sourceforge.net/reports/google-java-style.html
/* checkstyle {
// configFile = new File(rootDir, "checkstyle.xml")
toolVersion = '6.7'
}*/
/* findbugsMain {
reports {
xml.enabled = false
html.enabled = true
}
ignoreFailures = true
}
findbugsJmh {
reports {
xml.enabled = false
html.enabled = true
}
ignoreFailures = true
}
*/
sourceSets {
main {
java {
srcDir projectDir
exclude "tests/**"
}
resources {
srcDir projectDir
include '*.xml'
}
}
jmh {
java {
srcDir projectDir
}
}
test {
java {
srcDir new File(projectDir, "tests")
}
}
}
jmh {
jmhVersion = '1.15'
duplicateClassesStrategy = 'warn'
failOnError = true
// See https://github.com/melix/jmh-gradle-plugin
// for other options
}
List createdTasks = []
projectDir.eachFileRecurse { file ->
if (file.name.endsWith('.java')) {
Tags tags = new Tags(file)
if(debug && tags.hasTags()) println tags
// Exclude java sources that will not compile
if (tags.compileTimeError) {
sourceSets.main.java.excludes.add(file.name)
} else {
JavaExec javaTask = null
// Add tasks for java sources with main methods
if (tags.hasMainMethod || tags.javaCmd) {
javaTask = tasks.create(name: tags.fileRoot, type: JavaExec, dependsOn: tags.runFirst) {
main = tags.mainClass
classpath = sourceSets.main.runtimeClasspath
args = tags.args
jvmArgs = tags.jVMArgs
}
} else if (tags.javap) {
// Create task for running javap
javaTask = tasks.create(name: "${tags.fileRoot}", type: JavaExec, dependsOn: tags.runFirst) {
main = "com.sun.tools.javap.Main"
classpath = sourceSets.main.runtimeClasspath + files(Jvm.current().toolsJar)
// Assuming javap represents all the args and there's no need to jVMArgs
args tags.javap.split()
}
}
if (javaTask) {
def baseName = file.name.substring(0, file.name.lastIndexOf('.'))
File outFile = new File(file.parentFile, baseName + '.out')
File errFile = new File(file.parentFile, baseName + '.err')
javaTask.configure {
ignoreExitValue = tags.validateByHand || tags.throwsException
doFirst {
if(outFile.exists())
outFile.delete()
if(tags.outputLine)
outFile << tags.outputLine + "\n"
standardOutput = new TeeOutputStream(new FileOutputStream(outFile, true), System.out)
errorOutput = new TeeOutputStream(new FileOutputStream(errFile), System.err)
}
doLast {
if(outFile.size() == 0)
outFile.delete()
else if(!outFile.text.contains("/* Output:"))
outFile.delete()
if(errFile.size() == 0) errFile.delete()
}
}
if (!tags.validateByHand) {
// Only add tasks that we know we can run successfully to the task list
createdTasks.add(javaTask)
}
}
}
}
}
task run(dependsOn: createdTasks)
/* Store test output in $projectName/tests
JUnit 5's junitPlatformTest runs as a "javaExec" rather than a "test",
so we can't hook into the before/after test behavior.
*/
tasks.findByPath(":$name:junitPlatformTest").configure {
File testDir = new File(project.name + "/tests")
if(testDir.exists()) {
File outFile = new File(testDir, 'report.txt')
doFirst {
standardOutput = new TeeOutputStream(new FileOutputStream(outFile, true), System.out)
}
doLast {
if(outFile.size() == 0)
outFile.delete()
else if(outFile.text.contains("0 tests found"))
outFile.delete()
}
}
}
apply from: "$rootProject.projectDir/gradle/java.gradle"
apply from: "$rootProject.projectDir/gradle/junit-jupiter.gradle"
apply from: "$rootProject.projectDir/gradle/jmh.gradle"
apply from: "$rootProject.projectDir/gradle/code-analysis.gradle"
apply plugin: 'com.mindviewinc.tagging'
}
project(':validating') {
jmh {
include = 'validating.jmh.*'
}
}
project(':understandingcollections') {
dependencies {
compile project(':typeinfo')
compile project(':collections')
}
jmh {
include = 'understandingcollections.jmh.*'
}
}
project(':threads') {
dependencies {
compile project(':enums')
}
}
project(':strings') {
dependencies {
compile project(':generics')
}
}
project(':serialization') {
configurations.all {
resolutionStrategy {
force 'xml-apis:xml-apis:1.0.b2'
}
}
dependencies {
compile 'com.io7m.xom:xom:1.2.10'
}
}
project(':interfaces') {
dependencies {
compile project(':polymorphism')
}
}
project(':hiding') {
dependencies {
compile project(':com')
}
}
project(':generics') {
dependencies {
compile project(':typeinfo')
}
}
project(':collections') {
dependencies {
compile project(':typeinfo')
}
}
configure(subprojects - project(':onjava')) {
dependencies {
compile project(':onjava')
compile group: 'com.google.guava', name: 'guava', version: '19.0'
compile "org.openjdk.jmh:jmh-core:${jmh.jmhVersion}"
compile 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M2'
}
}
task verify(type:Exec) {
description 'Uses Python tool to verify example output'
commandLine 'python', '_verify_output.py'
doFirst {
println("execute 'gradlew run' first")
}
}
apply from: 'gradle/subprojects.gradle'

View File

@ -0,0 +1,18 @@
package com.mindviewinc.plugins
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.tasks.Exec
class ExampleOutputVerificationPlugin implements Plugin<Project> {
void apply(Project project) {
project.tasks.create('verify', Exec) {
description 'Uses Python tool to verify example output'
commandLine 'python', '_verify_output.py'
doFirst {
println("execute 'gradlew run' first")
}
}
}
}

View File

@ -0,0 +1,84 @@
package com.mindviewinc.plugins
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.tasks.JavaExec
import org.gradle.internal.jvm.Jvm
import org.apache.tools.ant.util.TeeOutputStream
class TaggingPlugin implements Plugin<Project> {
private final static String DEBUG_PROJECT_PROPERTY_KEY = 'debug'
void apply(Project project) {
boolean debug = project.hasProperty(DEBUG_PROJECT_PROPERTY_KEY) ? Boolean.valueOf(project.getProperty(DEBUG_PROJECT_PROPERTY_KEY)) : false
List createdTasks = []
project.projectDir.eachFileRecurse { file ->
if (file.name.endsWith('.java')) {
Tags tags = new Tags(file)
if(debug && tags.hasTags()) println tags
// Exclude java sources that will not compile
if (tags.compileTimeError) {
project.sourceSets.main.java.excludes.add(file.name)
} else {
JavaExec javaTask = null
// Add tasks for java sources with main methods
if (tags.hasMainMethod || tags.javaCmd) {
javaTask = project.tasks.create(name: tags.fileRoot, type: JavaExec, dependsOn: tags.runFirst) {
main = tags.mainClass
classpath = project.sourceSets.main.runtimeClasspath
args = tags.args
jvmArgs = tags.jVMArgs
}
} else if (tags.javap) {
// Create task for running javap
javaTask = project.tasks.create(name: "${tags.fileRoot}", type: JavaExec, dependsOn: tags.runFirst) {
main = "com.sun.tools.javap.Main"
classpath = project.sourceSets.main.runtimeClasspath + project.files(Jvm.current().toolsJar)
// Assuming javap represents all the args and there's no need to jVMArgs
args tags.javap.split()
}
}
if (javaTask) {
def baseName = file.name.substring(0, file.name.lastIndexOf('.'))
File outFile = new File(file.parentFile, baseName + '.out')
File errFile = new File(file.parentFile, baseName + '.err')
javaTask.configure {
ignoreExitValue = tags.validateByHand || tags.throwsException
doFirst {
if(outFile.exists())
outFile.delete()
if(tags.outputLine)
outFile << tags.outputLine + "\n"
standardOutput = new TeeOutputStream(new FileOutputStream(outFile, true), System.out)
errorOutput = new TeeOutputStream(new FileOutputStream(errFile), System.err)
}
doLast {
if(outFile.size() == 0)
outFile.delete()
else if(!outFile.text.contains("/* Output:"))
outFile.delete()
if(errFile.size() == 0) errFile.delete()
}
}
if (!tags.validateByHand) {
// Only add tasks that we know we can run successfully to the task list
createdTasks.add(javaTask)
}
}
}
}
}
project.tasks.create('run') {
dependsOn createdTasks
}
}
}

View File

@ -0,0 +1,108 @@
package com.mindviewinc.plugins
class Tags {
Boolean hasMainMethod = false
Boolean compileTimeError = false
Boolean throwsException = false
Boolean errorOutputExpected = false
Boolean validateByHand = false
Boolean ignoreOutput = false // This tag isn't used in the build...
String fileRoot
String mainClass
String javaCmd = null
List<String> args = []
List<String> jVMArgs = []
String javap = null
String runFirst = null
String outputLine = null
private String block
def Tags(File file) {
block = file.text
hasMainMethod = block.contains('main(String[] args)')
def firstLine = block.substring(0, block.indexOf("\n"))
fileRoot = (firstLine.split("/")[-1] - ".java").trim() // Remove \r if it exists
mainClass = fileRoot
javaCmd = extract('java')
if(javaCmd) {
def pieces = javaCmd.split()
mainClass = pieces[0]
if(pieces.size() > 1)
for(p in pieces[1..-1])
if(p.startsWith("-"))
jVMArgs << p
else
args << p
}
compileTimeError = hasTag('CompileTimeError')
throwsException = hasTag('ThrowsException')
errorOutputExpected = hasTag('ErrorOutputExpected')
validateByHand = hasTag('ValidateByHand')
ignoreOutput = hasTag('IgnoreOutput')
javap = extract('javap') // Includes only arguments to command
runFirst = extract('RunFirst:')
outputLine = extractOutputLine()
}
private def hasTag(String marker) {
return block.contains("// {" + marker + "}")
}
def extractOutputLine() {
def matcher = (block =~ /(?m)^(\/\* Output:.*)$/)
if (matcher) {
return matcher[0][1]
} else {
return null
}
}
private def extract(String marker) {
// Assume some whitespace is after marker
if(!block.contains("// {${marker} "))
return null
def matcher = (block =~ /\/\/ \{${marker}\s+([^}]+)/)
if (matcher) {
def matched = matcher[0][1].trim()
return matched.replaceAll("\n?//", "")
} else {
println "Searching for: " + matcher
println block
System.exit(1)
}
}
public boolean hasTags() {
return compileTimeError ||
throwsException ||
errorOutputExpected ||
validateByHand ||
ignoreOutput ||
javaCmd ||
args ||
jVMArgs ||
javap ||
runFirst
}
public String toString() {
String result = ""
block.eachLine{ ln ->
if(ln.startsWith("//") || ln.startsWith("package "))
result += ln + "\n"
}
"""
hasMainMethod
compileTimeError
throwsException
errorOutputExpected
validateByHand
ignoreOutput
fileRoot
mainClass
javaCmd
args
jVMArgs
javap
runFirst
""".split().each { str ->
if(this[str])
result += str + ": " + this[str] + "\n"
}
result
}
}

View File

@ -0,0 +1 @@
implementation-class=com.mindviewinc.plugins.ExampleOutputVerificationPlugin

View File

@ -0,0 +1 @@
implementation-class=com.mindviewinc.plugins.TaggingPlugin

View File

@ -0,0 +1,27 @@
//apply plugin: 'checkstyle'
//apply plugin: 'findbugs'
// See: http://blog.jessitron.com/2012/07/using-checkstyle-in-gradle.html
// https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
// http://checkstyle.sourceforge.net/reports/google-java-style.html
/* checkstyle {
// configFile = new File(rootDir, "checkstyle.xml")
toolVersion = '6.7'
}*/
/* findbugsMain {
reports {
xml.enabled = false
html.enabled = true
}
ignoreFailures = true
}
findbugsJmh {
reports {
xml.enabled = false
html.enabled = true
}
ignoreFailures = true
}
*/

39
gradle/java.gradle Normal file
View File

@ -0,0 +1,39 @@
apply plugin: 'java'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
sourceSets {
main {
java {
srcDir projectDir
exclude "tests/**"
}
resources {
srcDir projectDir
include '*.xml'
}
}
test {
java {
srcDir file("tests")
}
}
}
repositories {
jcenter()
}
dependencies {
// Logging:
compile 'org.slf4j:slf4j-api:1.7.21'
compile 'ch.qos.logback:logback-classic:1.1.7'
// You can also use the JDK's built-in logging as the back end:
// compile group: 'org.slf4j:slf4j-jdk14:1.7.21'
}

17
gradle/jmh.gradle Normal file
View File

@ -0,0 +1,17 @@
apply plugin: 'me.champeau.gradle.jmh'
sourceSets {
jmh {
java {
srcDir projectDir
}
}
}
jmh {
jmhVersion = '1.15'
duplicateClassesStrategy = 'warn'
failOnError = true
// See https://github.com/melix/jmh-gradle-plugin
// for other options
}

View File

@ -0,0 +1,36 @@
import org.apache.tools.ant.util.TeeOutputStream
apply plugin: 'org.junit.platform.gradle.plugin'
ext {
junitJupiterVersion = '5.0.0-M2'
}
dependencies {
testCompile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
testRuntime "org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}"
}
junitPlatform {
includeClassNamePattern '.*'
}
/* Store test output in $projectName/tests
JUnit 5's junitPlatformTest runs as a "javaExec" rather than a "test",
so we can't hook into the before/after test behavior.
*/
tasks.findByPath(":$name:junitPlatformTest").configure {
File testDir = file("tests")
if(testDir.exists()) {
File outFile = new File(testDir, 'report.txt')
doFirst {
standardOutput = new TeeOutputStream(new FileOutputStream(outFile, true), System.out)
}
doLast {
if(outFile.size() == 0)
outFile.delete()
else if(outFile.text.contains("0 tests found"))
outFile.delete()
}
}
}

70
gradle/subprojects.gradle Normal file
View File

@ -0,0 +1,70 @@
project(':validating') {
jmh {
include = 'validating.jmh.*'
}
}
project(':understandingcollections') {
dependencies {
compile project(':typeinfo')
compile project(':collections')
}
jmh {
include = 'understandingcollections.jmh.*'
}
}
project(':threads') {
dependencies {
compile project(':enums')
}
}
project(':strings') {
dependencies {
compile project(':generics')
}
}
project(':serialization') {
configurations.all {
resolutionStrategy {
force 'xml-apis:xml-apis:1.0.b2'
}
}
dependencies {
compile 'com.io7m.xom:xom:1.2.10'
}
}
project(':interfaces') {
dependencies {
compile project(':polymorphism')
}
}
project(':hiding') {
dependencies {
compile project(':com')
}
}
project(':generics') {
dependencies {
compile project(':typeinfo')
}
}
project(':collections') {
dependencies {
compile project(':typeinfo')
}
}
configure(subprojects - project(':onjava')) {
dependencies {
compile project(':onjava')
compile 'com.google.guava:guava:19.0'
compileOnly "org.openjdk.jmh:jmh-core:${jmh.jmhVersion}"
}
}