Gradle java project replace single line in file during build - java

I have a simple Gradle build script to compile and package (similar to the application plugin) my Java application. The only thing I do not accomplish is to replace the current version number in a simple .properties file.
I have created a file 'src/main/resources/app-info.properties' with a single line 'application.version = #version#'. No I want to replace this version string whenever the file is copied to the build folder (think this happens during the build task).
I already tried a simple solution with ants ReplaceTokens. This one replaced the version but also broke my .png files in the resources..
So is there a simple solution to just replace tokens in one single file during the build task (or whatever task handles the copy to the build folder)?
Thank you for any help!
Ben
====== Edit based on the comment from Opal =====
Based on the hint I have added the following:
import org.apache.tools.ant.filters.ReplaceTokens
// ...
build {
from('src/main/resources') {
include '*.properties'
filter(ReplaceTokens, tokens: [version : project.version])
}
}
Which throws this error:
Could not find method from() for arguments [src/main/resources, build_vbjud9ah7v3pj5e7c5bkm490b$_run_closure6_closure12#43ead1a8] on root project
Seems like I am on the wrong task?
====== Edit for completeness adding the solution based on Opals suggest =====
Thanks man, the following is the working solution!
processResources {
from('src/main/resources') {
include '*.properties'
filter(ReplaceTokens, tokens: [version : project.version])
}
}

Books and blogs alike, including the answer from Opal all recommend using a vivid mixture of exclude/include, from() and filter(). And of course, so did I on my first attempt to replace the text {{app javascript library}} in a index.html file to the path of a JavaScript library which depended on a simple project property setting.
The problem that hit me was that my 'war' task produced duplicated index.html files in the war archive and getting rid of the problem, using the pattern described previously, resulted in one huge unreadable hack.
Then I found a really straight forward solution. The following example is from my own build script and you have to customize it a bit to suite your needs:
war {
eachFile { copyDetails ->
if (copyDetails.path == 'index.html') {
filter { line ->
line.replace('{{app javascript library}}', "lib/someLib.js")
}
}
}
}

Paste sample code. What You need to do is to include file for replacement and exclude other files from replacement. Here is sample usage. Search for ReplaceTokens and You'll see what am I talking about.
You need to add filtering to processResources task. Sample code:
processResources {
def profile = project.properties['profile']
def replace_tokens = profile ? filter_tokens[profile] : filter_tokens['default']
exclude '**/log4j-test.xml'
from('src/main/resources') {
exclude '**/*.ttf'
filter(ReplaceTokens, tokens: replace_tokens)
}
from('src/main/resources') {
include '**/*.ttf'
}
}
Above ttf (binary) files are excluded from filtering but copied. replace_tokens is a filter taken from map defined in other part of the script.

Related

Gradle - Write Task Output Into A File

I am working with Gradle 7.1, and I am trying to write some of the tasks resuts into a file.
Specifically, I would like to write the output of dependencies task into a file after each jar task execution.
Looking for some solutions, I understand that at first I need to have jar.finalizedBy(dependencies) in order fot it to run.
However, I can't find how to redirect the dependencies's specific output into a file. All the solutions that I have found discuss Exec tasks, which dependencies isn't.
I am looking for somehing like dependencies.doFirst(///REDIRECT HERE).
You can make dependencies task write to file by attaching a StandardOutputListener:
tasks.named('dependencies').configure {
it.logging.addStandardOutputListener(new StandardOutputListener() {
#Override
void onOutput(CharSequence charSequence) {
project.file("$buildDir/dependencies_task_output.txt") << charSequence
}
})
}
This can also be done with any other Gradle task.

Is there any way to automatically setting windows path in a string in groovy?

My project root directory is:
D:/Project/Node_Project
I am using a gradle plugin to install nodejs temporarily in my project root directory so that some nodejs command can run in the project while the thoject builds. The plugin is as below:
plugins {
id "com.github.node-gradle.node" version "2.2.4"
}
node {
download = true
version = "10.10.0"
distBaseUrl = 'https://nodejs.org/dist'
workDir = file("${project.buildDir}/nodejs")
}
So, nodejs is getting installed inside the project in the location:
D:/Project/Node_Project/build/nodejs/node-v10.10.0-win-x64
Now, I am using a .execute(String[] "path to set at environment variable", String path of file to be executed which is in the project root directory) method to run a windows command with node dependency. Code below:
cmd = "node connect.js"
def process = cmd.execute(["PATH=${project.projectDir}/build/nodejs/node-v10.10.0-win-x64"],null)
In the above .execute method, is there a way to auto-populate the "build/nodejs/node-v10.10.0-win-x64" part of the string instead of hardcoding it into the method?
Something like:
def process = cmd.execute(["PATH=${project.projectDir}/.*"],null)
Syntax of .execute method:
https://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/String.html#execute(java.lang.String[],%20java.io.File)
All the codes are inside "build.gradle" file. Please help!
I asked why you don't just write a task of type NodeTask, but I understand that you like to run a it in the background, which you can't do with that.
You could list the content of a directory and use that as part of the command. But you could also just grab it from the extension provided by the plugin.
This is not documented and it might break in future releases of the plugin, but you can do something like this (Groovy DSL):
task connectJS {
dependsOn nodeSetup
doFirst {
def connectProcess = "$node.variant.nodeExec $projectDir/src/js/connect.js".execute()
// Blocking readers (if async, pipe to a log file instead)
connectProcess.in.eachLine { logger.info(it) }
connectProcess.err.eachLine { logger.err(it) }
}
}

Create makefile-like wildcard targets in Gradle

Use case: I have a bunch of images that have to be processed by a script before I build my app. In makefile I can simply define:
processed/%.png: original/%.png
script/process.sh $< $#
How do I implement this in Gradle? Specifically, I want it to work like in Makefile, that is only the modified original images will be processed again.
You can implement this behaviour as an incremental task, using IncrementalTaskInputs as its input parameter. This API docs contain an example how to use it and here is an example in another the documentation. Both of them do almost exactly what you need.
An incremental task action is one that accepts a single
IncrementalTaskInputs parameter. The task can then provide an action
to execute for all input files that are out of date with respect to
the previous execution of the task, and a separate action for all
input files that have been removed since the previous execution.
In the case where Gradle is unable to determine which input files need
to be reprocessed, then all of the input files will be reported as
IncrementalTaskInputs.outOfDate(org.gradle.api.Action).
Inside your task, call the script using an exec task. Your Gradle script could then look like this:
task processRawFiles(type: ProcessRawFiles)
class ProcessRawFiles extends DefaultTask {
#InputDirectory
File inputDir = project.file('src/raw')
#OutputDirectory
File outputDir = project.file('build/processed')
#TaskAction
void execute(IncrementalTaskInputs inputs) {
if (!inputs.incremental)
project.delete(outputDir.listFiles())
inputs.outOfDate { InputFileDetails change ->
File saveTo = new File(outputDir, change.file.name)
project.exec {
commandLine 'script/process.sh', change.file.absolutePath, saveTo.absolutePath
}
}
inputs.removed { InputFileDetails change ->
File toDelete = new File(outputDir, change.file.name)
if (toDelete.exists())
toDelete.delete()
}
}
}
This task looks for the images in src/raw. It will removed files from build directory and call your script on any files that are out of date or newly added.
Your specific case might be more complicated if you have the images scattered across multiple directories. In that case you will have to use #InputFiles instead of #InputDirectory. But the incremental task should still work.

Right way to exclude R.java from javadoc using gradle

I'm generating javadoc for my Android project with this gradle task:
android.applicationVariants.all { variant ->
task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
description "Generates Javadoc for $variant.name."
source = variant.javaCompile.source
classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath())
exclude '**/BuildConfig.java'
exclude '**/R.java'
options.links("http://docs.oracle.com/javase/7/docs/api/");
options.linksOffline("http://d.android.com/reference","${android.sdkDirectory}/docs/reference");
options {
failOnError false
}
destinationDir = file("${project.projectDir}/javadoc")
}
}
It excludes R.java, so i don't get R.html in output dir.
However, i'm getting very annoying errors cannot find symbol class R in the process of generating doc for my usual java classes, in the line import com.mypackagename.R. I use common android things like R.string.string_res, so i can't remove this import.
Is there a proper way to include symbol R to index, but not include it to a javadoc, or, at least, simply to supress this error?
You can try to add next two lines to your code:
classpath += files("build/generated/source/r/${variant.flavorName}/release")
classpath += files("build/generated/source/buildConfig/${variant.flavorName}/release")
But in this case your task should depend on one of the tasks which generates R classes.

Gradle strange behavior while extending sourceSets with Map variable

We are developing a Java project that is able to instrument (change) class files at build time. We defined a Gradle task that invokes a java based Ant task which takes an inputDir (e.g. build/classes) and an outputDir (e.g. build/classes-instrumented) and possible other parameters. The task gets invoked separately for main and test class files after compilation. Since the "normal" java sourceSet is not a good fit, our first thought was to implement our own sourceSet but couldn't find an easy way. A reasonable alternative, similar to ANTLR etc, seemed to be extra variables. Since I needed several, I went for a Map.
sourceSets.all { ext.instrumentation = [:] }
sourceSets.all {
instrumentation.inputDir = null
instrumentation.outputDir = null
instrumentation.classPath = null
}
def postfix = '-instrumented'
Below you see how we initialize the variables.
sourceSets {
main {
instrumentation.inputDir = sourceSets.main.output.classesDir
instrumentation.outputDir = instrumentation.inputDir + postfix
instrumentation.classPath = sourceSets.main.output + configurations.compile
}
test {
instrumentation.inputDir = sourceSets.test.output.classesDir
instrumentation.outputDir = instrumentation.inputDir + postfix
}
}
However it fails with "Could not find method main() for arguments [build_f2cvmoa3v4hnjefifhpuk6ira$_run_closure5_closure23#12a14b74] on root
project 'Continuations'."
We are using Gradle 2.1
I have the following questions:
any idea why the first one fails?
Is the extra variable a reasonable solution to approach the problem?
Thanks a lot for your help
solution: install last version.
I had the same problem, I read gradle documentation of gradle 3, but gradle 2.7 was installed.
checked gradle version 2.7
then read gradle 2.7 doc https://docs.gradle.org/2.7/userguide/tutorial_java_projects.html#N103CD , but found no info about sourceSet in java plugin for that version
installed gradle 3 --> problem solved

Categories