I'm trying to configure a task that will generate a single report from a collection of binary junit test results, but I'm unable to create a FileCollection containing the paths in which all the result files are located.
My task is defined like this
task aggregateTestREports(type: org.gradle.api.tasks.tests.TestSupport) {
destinationDir = reportDir
testResultsDirs = ???
}
Where the ??? is the part that I'm not getting to work.
I can use the following code to get a list of the output.bin files in our build structure, but I need to transform this into the list of the directories the files are in.
fileTree(dir: '.', includes: ['**/test-results/binary/test/output.bin'])
I've tried creating a custom class from the base class like this and passing the results of that line to the testOutputBinFiles parameter and calculating the files dynamically
class AggregateTestReport extends TestReport {
#Input
def testOutputBinFiles
def getTestResultDirs() {
def parents = []
testOutputBinFiles.each {
File file -> parents << file.parentFile.absoluteFile
}
parents
}
}
but this gives me an error that the return value is not compatible with a FileCollection
The docs for FileCollection indicate that the only way to get a new one is to use the files() function, but it isn't available from within the custom class to get a new file.
Due to an issue with the way the tests in my project are written taking on the dependency that is introduced by dnault's answer causes our tests to fail. Ultimately as we fix the issue in our project that causes this, his solution will work so I accepted that answer. For completeness, my stopgap solution ended up being
def resultPaths(FileCollection testOutputBinFiles) {
def parents = []
testOutputBinFiles.each {
File file -> parents << file.parentFile.absoluteFile
}
parents
}
task aggregateTestReports(type: org.gradle.api.tasks.testing.TestReport) {
destinationDir = reportDir
reportOn resultPaths(fileTree(dir: '.', includes: ['**/test-results/binary/test/output.bin']))
}
You can declare a task of type TestReport with a 'reportOn' property listing the Test tasks to aggregate:
task allTests(type: TestReport) {
destinationDir = file("${buildDir}/reports/allTests")
reportOn test, myCustomTestTask
}
This approach is outlined in section 7.3.3 of "Gradle in Action" by Benjamin Muschko (highly recommended reading).
Related
Need: To create a run task each, for multiple programs within the same project
Based on the solution suggested in this LINK. I tried as shown below.
Working Code:
task runCustom1(type: JavaExec) {
group = 'Z_Custom_Run'
description = 'Testing for Gradle Run'
classpath sourceSets.main.runtimeClasspath
main = "pkg01.TestGradleRun"
}
task runCustom2(type: JavaExec) {
group = 'Z_Custom_Run'
description = 'Testing for Gradle Run'
classpath sourceSets.main.runtimeClasspath
main = "pkg01.TestGradleRun2"
}
But above method is cumbersome, as I have to generate for many programs & hence tried the below, to see if I can keep the code compact. But it gives an error as shown below.
Trial Code:
def customRunTask(String className, String packagePath){
return tasks.create("run${className}", JavaExec){
group = 'zCustomRun'
description = 'Testing for Gradle Run'
classpath sourceSets.main.runtimeClasspath
main = packagePath
}
}
artifacts {
archives customRunTask("Test1","pkg01.TestGradleRun"),
customRunTask("Test2","pkg01.TestGradleRun2")
}
Error:
A problem occurred evaluating root project 'testJavaFeatures'.
> Cannot convert the provided notation to an object of type ConfigurablePublishArtifact: task ':runTest1'.
The following types/formats are supported:
- Instances of ConfigurablePublishArtifact.
- Instances of PublishArtifact.
- Instances of AbstractArchiveTask, for example jar.
- Instances of Provider<RegularFile>.
- Instances of Provider<Directory>.
- Instances of Provider<File>.
- Instances of RegularFile.
- Instances of Directory.
- Instances of File.
- Maps with 'file' key
Since I am not too conversant with Gradle, seek guidance from the experts on how to fix the error & get it working
you were almost there ... the below should work
def customRunTask(String className, String packagePath){
return tasks.create("run${className}", JavaExec){
group = 'zCustomRun'
description = 'run ${packagePath}.${className}'
classpath sourceSets.main.runtimeClasspath
main = packagePath + '.' + className
}
}
customRunTask('ClassA', 'com.pkg1')
customRunTask('ClassB', 'com.pkg2')
(and remove the artifacts section, from your file)
Not sure what you try to do, but you can generate similar tasks en masse very easily:
List mainClassNames = [ 'pkg01.TestGradleRun', 'pkg01.TestGradleRun2' ]
mainClassNames.each{ name ->
task "runCustom-$name"(type: JavaExec) {
group = 'Z_Custom_Run'
description = "Testing for Gradle Run for $name"
classpath sourceSets.main.runtimeClasspath
main = name
}
}
artifacts {
archives mainClassNames.collect{ ":runCustom-$it" }
}
I'm quite new to Gradle so the answer might be simple, so I apologize if the answer is simple: I have a testing tool that needs to fetch it's version and compare it to the version of the application it is testing. However , the version of my tool is in my build.graddle as
version '1.0'
I tried different way to access it ( such as) :
task generateSources {
File outDir
outDir = file("$buildDir/classes/test/tests/Version.java")
doFirst {
outDir.exists() || outDir.mkdirs()
new File(outDir).write("public class Version { public static final String VERSION = \"$project.version\"; }")
}
}
compileJava.dependsOn generateSources
compileJava.source generateSources.outputs.files, sourceSets.main.java
I found this piece of code to output the version to another java file, but I fail to see how I'd be able to retrieve that info afterwards ( I mean, my tests are defined in src and I would need to point to a file that doesn't exist at compilation -- correct me if I'm wrong here).
Any idea on how I could accomplish this task?
First of all, you are trying to create java source file in your build/classes (it should contain compiled classes, not sources) directory, but you have to do it in your sources, otherwise it won't be compiled. And if you need this new class to be vailable not for tests, then use src/main/java, not src/test/java/
But anyway, I suppose for your case it's much easier to use some properties file for that and replace some token within it during build. That will allow you to make some static logic to get this property value and use it yet before running the build. So all you need is:
1- to have some properties file in your resources src/main/resources (for example app.properties), where should version variable be stored, with it's value like APP_VERSION_TOKEN
version=%APP_VERSION_TOKEN%
2- configure you Gradle processResources to replace tokens, something like this:
processResources {
filesMatching('**/app.properties') {
filter {
it.replace('%APP_VERSION_TOKEN%', version)
}
}
}
3- make some method to read this file and return the value of the property and use it where you need.
And that's all. For unit tests you can have another file with the same name under src/test/resource with the unchanging value you need for testing.
I am looking for a solution to exclude certain files marked with a particular annotation to be packaged in jar (can be compiled but not part of jar created).
I have tried the following steps
Create a ClassLoader using : sourceSets.main.output + configurations.runtime
Check recursively within the compiled classes, use ClassLoader.loadClass to load the class and check if annotation is present (Class.isAnnotationPresent)
Any pointers would be helpful.
I was able to implement this long time back but forgot I had posted the question here.
The solution I used to was actually quite simple -
Using gradle jar task -
jar {
excludes = excludedFiles(sourceSets.main.allSource.files)
baseName = artifactName
version = artifactVersion
}
And define the excludedFiles function to look up the files in the source directory provided -
def excludedFiles(Collection<File> files) {
List<String> classes = new ArrayList<>()
files.each { file ->
if (file.isDirectory()) {
excludedFiles(Arrays.asList(file.listFiles()))
}
else {
if (file.text.contains("#YourAnnotation") && file.text.contains("import foo.bar.YourAnnotation")) {
classes += getClassName(file.absolutePath)
}
}
}
return classes
}
Hope this helps.
I want to replace few lines in my Config.java file before the code gets compiled. All I was able to find is to parse file through filter during copying it. As soon as I have to copy it I had to save it somewhere - thats why I went for solution: copy to temp location while replacing lines > delete original file > copy duplicated file back to original place > delete temp file. Is there better solution?
May be you should try something like ant's replaceregexp:
task myCopy << {
ant.replaceregexp(match:'aaa', replace:'bbb', flags:'g', byline:true) {
fileset(dir: 'src/main/java/android/app/cfg', includes: 'TestingConfigCopy.java')
}
}
This task will replace all occurances of aaa with bbb. Anyway, it's just an example, you can modify it under your purposes or try some similar solution with ant.
To complement lance-java's answer, I found this idiom more simple if there's only one value you are looking to change:
task generateSources(type: Copy) {
from 'src/replaceme/java'
into "$buildDir/generated-src"
filter { line -> line.replaceAll('xxx', 'aaa') }
}
Caveat: Keep in mind that the Copy task will only run if the source files change. If you want your replacement to happen based on other conditions, you need to use Gradle's incremental build features to specify that.
I definitely wouldn't overwrite the original file
I like to keep things directory based rather than filename based so if it were me, I'd put Config.java in it's own folder (eg src/replaceme/java)
I'd create a generated-src directory under $buildDir so it's deleted via the clean task.
You can use the Copy task and ReplaceTokens filter. Eg:
apply plugin: 'java'
task generateSources(type: Copy) {
from 'src/replaceme/java'
into "$buildDir/generated-src"
filter(ReplaceTokens, tokens: [
'xxx': 'aaa',
'yyy': 'bbb'
])
}
// the following lines are important to wire the task in with the compileJava task
compileJava.source "$buildDir/generated-src"
compileJava.dependsOn generateSources
If you do wish to overwrite the original file, using a temp file strategy, the following will create the filtered files, copy them over the original, and then delete the temp files.
task copyAtoB(dependsOn: [existingTask]) {
doLast {
copy {
from("folder/a") {
include "*.java"
}
// Have to use a new path for modified files
into("folder/b")
filter {
String line ->
line.replaceAll("changeme", "to this")
}
}
}
}
task overwriteFilesInAfromB(dependsOn: [copyAtoB]) {
doLast {
copy {
from("folder/b") {
include "*.java"
}
into("folder/a")
}
}
}
// Finally, delete the files in folder B
task deleteB(type: Delete, dependsOn: overwriteFilesInAfromB) {
delete("folder/b")
}
nextTask.dependsOn(deleteB)
I have a typical Antlr 4.5 project with two grammar files: MyLexer.g4 and MyParser.g4. From them, Antlr generates 6 output files: MyLexer.java, MyLexer.tokens, MyParser.java, MyParser.tokens, MyParserBaseListener.java and MyParserListener.java. The gradle tasks are all working correctly so that the output files are all generated, compiled and tested as expected.
The problem is that gradle sees the 6 target files as always being out of date, so every run or debug session has to regenerate them and therefore has to recompile the main java project even if none of the source files have changed.
The gradle task which generates the file has the output spec defined as the folder into which the 6 output files are generated. I think that I need a way to define it as being the 6 specific files rather than the output folder. I just don't know the syntax to do that.
Here's the pertinent part of my build.gradle file:
ext.antlr4 = [
antlrSource: "src/main/antlr",
destinationDir: "src/main/java/com/myantlrquestion/core/antlr/generated",
grammarpackage: "com.myantlrquestion.core.antlr.generated"
]
task makeAntlrOutputDir << {
file(antlr4.destinationDir).mkdirs()
}
task compileAntlrGrammars(type: JavaExec, dependsOn: makeAntlrOutputDir) {
// Grammars are conveniently sorted alphabetically. I assume that will remain true.
// That ensures that files named *Lexer.g4 are listed and therefore processed before the corresponding *Parser.g4
// It matters because the Lexer must be processed first since the Parser needs the .tokens file from the Lexer.
// Also note that the output file naming convention for combined grammars is slightly different from separate Lexer and Parser grammars.
def grammars = fileTree(antlr4.antlrSource).include('**/*.g4')
def target = file("${antlr4.destinationDir}")
inputs.files grammars
// TODO: This output spec is incorrect, so this task is never considered up to date.
// TODO: Tweak the outputs collection so it is correct with combined grammars as well as separate Lexer and Parser grammars.
outputs.dir target
main = 'org.antlr.v4.Tool'
classpath = configurations.antlr4
// Antlr command line args are at https://theantlrguy.atlassian.net/wiki/display/ANTLR4/ANTLR+Tool+Command+Line+Options
args = ["-o", target,
"-lib", target,
//"-listener", //"-listener" is the default
//"-no-visitor", //"-no-visitor" is the default
"-package", antlr4.grammarpackage,
grammars.files
].flatten()
// include optional description and group (shown by ./gradlew tasks command)
description = 'Generates Java sources from ANTLR4 grammars.'
group = 'Build'
}
compileJava {
dependsOn compileAntlrGrammars
// this next line isn't technically needed unless the antlr4.destinationDir is not under buildDir, but it doesn't hurt either
source antlr4.destinationDir
}
task cleanAntlr {
delete antlr4.destinationDir
}
clean.dependsOn cleanAntlr
I discovered the problem was not that the target files were out of date, but rather, due to a bug in the cleanAntlr task, they were being deleted every time that any gradle task was run. The problem was that all of the code in cleanAntlr was being run during gradle's initialization and configuration phase, even if the cleanAntlr task itself wasn't being executed.
Originally, the task was defined as:
task cleanAntlr {
delete antlr4.destinationDir
}
clean.dependsOn cleanAntlr
The solution was to define it like this: (Note the "<<" after the task name.)
task cleanAntlr << {
delete antlr4.destinationDir
}
clean.dependsOn cleanAntlr
... or, for additional clarity, use this more verbose, but functionally equivalent task definition:
task cleanAntlr {
doLast() {
// Be sure to wrap the execution phase code inside doLast().
// Otherwise it will run during the initialization or configuration phase, even when an unrelated task is is run.
// It would also run when the NetBeas IDE first loaded the project.
//println 'Deleting Antlr Directory: ' + antlr4.destinationDir
delete antlr4.destinationDir
}
}
clean.dependsOn cleanAntlr
With that bug fixed, the original outputs specification for the compileAntlrGrammars task works correctly. There is no need to specify each individual output file. This is explained quite well in section 15.9.2 of https://gradle.org/docs/current/userguide/more_about_tasks.html.
def grammars = fileTree(antlr4.antlrSource).include('**/*.g4')
def target = file("${antlr4.destinationDir}")
inputs.files grammars
outputs.dir target
Could you please try the following piece of code:
generatedFiles = ['MyLexer.java',] // and so on..
generatedFiles.each { f -> outputs.file("$target/$f") }