I wrote a custom gradle task class (say PrintNameTask) that accepts some input parameter (say name).
Then if I define a printName task of type PrintNameTask like below:
task printName(type: PrintNameTask) {
name = project.name
}
and invoke it from command prompt like below I can see the passed name printed out
$gradle printName -Pname=myName
myName
However if I invoke any other task like clean or build the build fails because there is no property called name passed. This is fair enough as my printName is a configure closure and is evaluated all the times.
To address this I tried to change the configure closure into a task action closure like below:
task printName(type: PrintNameTask) << {
// What should I put on here?
name = ???
// or
name ???
}
But it was no way to make it work. I tried project.name, getProperty("name") and a few more other combinations but nothing worked. All I get back is:
* What went wrong:
A problem was found with the configuration of task ':printName'.
No value has been specified for property 'name'.
This kind of requirement looks to me quite basic and it is a bit frustrating that tons of books and documentation are published but they only shows trivial examples. Maybe is just me but at the point of asking this question my initial gradle enthusiasm is more than half gone. Anyway thank you in advance for your inputs.
Configure the task in the following way:
task printName(type: PrintNameTask) {
name = project.hasProperty('name') ? project.name : '' // or null
}
Since this closure is evaluated at configuration phase it's executed every time the script is processed. You just need to check if the property is present.
Related
I have dynamic defined a task in build.gradle, that inherits from another task.
In the following I'm using kotlin-DSL, but I'm happy with solutions for groovy dsl as well.
task.register<GenerateTask>("generateCode") {
// stuff
outputDir.set("$rootDir")
}
As you can see I had to define the output as rootDir.
However I'm setting options to only create part of the output.
All the generated code is in a specificPackage meaning it will all end up in:
$rootDir/src/main/my/generated/package/name
$rootDir/src/test/my/generated/package/name
However due to outputDir being set it assumes the whole rootdir is an output and warns me accordingly.
Now I would like to tell gradle, that this task only produces those two packages.
I tried overwriting outputs but it is readonly.
I tried overwriting outputs.files but it is readonly.
I tried using outputs.files.setFrom() as described here, but this seems no longer to be valid.
Is there a way to set outputDir, but not add the value it to outputs or a way to clear the outputs list?
I have a build.gradle with the following contents:
task myTask {
inputs.file("input.txt").optional()
doLast { println "input.txt exists = " + file("input.txt").exists() }
}
If input.txt doesn't exist, it fails with:
File '/Users/skissane/testgradle/input.txt' specified for property '$1' does not exist.
What I am trying to do, is run a custom script–which is written in Groovy, and runs inside the Gradle build under doLast, not as an external process–which takes the input.txt file as input, and the script's behaviour and output will change based on what is in that input file. But it is an optional input file – the script will still generate output (albeit different output) even if the input file doesn't exist.
Things I have tried so far:
Remove .optional(), change it to .optional(true): no difference in results
Instead of .optional(), wrap it in if (file("input.txt").exists()) {: this works, but seems ugly. Why doesn't .optional() work?
Have I misunderstood what .optional() is meant to do? Because another answer suggests it is the right way to solve my problem, but it isn't working.
(I am using Gradle 6.8.3. I tried upgrading to the latest Gradle 7.2, the same problem occurs, although 7.2 has more detailed error messages.)
optional() can't be used to mark the file itself as optional. optional() just means that the input property is optional, and the task is still valid if no files at all are specified; but if a file is specified, it must exist.
As such, optional() isn't really useful in this kind of custom task declared directly in build.gradle. It is really intended for defining new task types in plugins, when one defines a new task input property other than inputs, and wants to make it optional to declare files for that property. It is the property itself which is made optional, not the files in it. On a custom task, declaring inputs as optional is pointless because it is already optional to begin with.
Right now (as of version 7.2), Gradle doesn't have any way to mark a file as an optional input, other than through if (file("input.txt").exists()) {. Hopefully they might add that feature in some future Gradle version.
(Thanks to James Justinic who answered my post about this on Gradle forums.)
I've seen this question posted numerous times, but I've not yet found an answer that matches my specific use case.
I'm created a bunch of config files from a template, after iterating over a config file containing environment properties.
The problem is that if I amend src/properties/ENV.gradle with some new values, it's ignored when I call the task on the basis that it's already "UP-TO-DATE". I'm not using doFirst or doLast, which is one of the common reasons this happens.
My workaround so far is to include
outputs.upToDateWhen { false }
which forces the config files to be recreated every single time regardless of what Gradle calculates, but it feels like an awful hack. Is there a better way?
Here's the code:
task createConfigs
def addTemplateTasks(aProject, env, config) {
aProject.with {
task "templateCopy_${env}"(type: Copy) {
from "src/templates"
include "server.config.template"
into "${buildDir}"
rename { file -> "server.config.${env}" }
expand(regionConfig)
}
createConfigs.dependsOn "templateCopy_${env}"
}
}
def envFile = new File("${project.projectDir}/src/properties/ENV.gradle")
def envConfig = new ConfigSlurper().parse(envFile.text)
envConfig.each { env, config ->
addTemplateTasks(project, env, config)
}
SOLUTION:
My case was a bit specific in that I was expecting the Copy task to notice "config" changing, without telling it explicitly care about it (see accepted answer below). Here's the change that fixed the problem:
def addTemplateTasks(aProject, env, config) {
aProject.with {
task "templateCopy_${env}"(type: Copy) {
inputs.property("environmentConfig", config)
The first arg (in quotes) is the name for this property, the second is the actual value.
Now, when I change a property in the ENV.gradle file and run "gradle -i", I see this:
Task ':component-foo:templateCopy_DEV' is not up-to-date because:
Value of input property 'environmentConfig' has changed for task ':component-foo:templateCopy_DEV'
The only input of your copy task, is the src/templates directory. Unless the content of src/templates doesn't change an the copied outputs are still present, your task will be considered UP-TO-DATE.
The problem is that your copy task actually has a second input which Gradle doesn't know about: Your config/regionConfig object.
Try to add the config object (or a hash of the objects values) to the inputs of the copy task using inputs.property or inputs.properties.
I've been struggling with this for a day and a half or so. I'm trying to replicate the following Ant concept in Gradle:
<target name="test">
...
<runexe name="<filename> params="<params>" />
...
</target>
where runexe is declared elsewhere as
<macrodef name="runexe" >
...
</macrodef>
and might also be a taskdef or a scriptdef i.e. I'd like to be able to call a reusable, pre-defined block of code and pass it the necessary parameters from within Gradle tasks. I've tried many things. I can create a task that runs the exe without any trouble:
task runexe(type: Exec){
commandLine 'cmd', '/c', 'dir', '/B'
}
task test(dependsOn: 'runexe') {
runexe {
commandLine 'cmd', '/c', 'dir', '/N', 'e:\\utilities\\'
}
}
test << {
println "Testing..."
// I want to call runexe here.
...
}
and use dependsOn to have it run. However this doesn't allow me to run runexe precisely when I need to. I've experimented extensively with executable, args and commandLine. I've played around with exec and tried several different variations found here and around the 'net. I've also been working with the free books available from the Gradle site.
What I need to do is read a list of files from a directory and pass each file to the application with some other arguments. The list of files won't be known until execution time i.e. until the script reads them, the list can vary and the call needs to be made repeatedly.
My best option currently appears to be what I found here, which may be fine, but it just seems that there should be a better way. I understand that tasks are meant to be called once and that you can't call a task from within another task or pass one parameters but I'd dearly like to know what the correct approach to this is in Gradle. I'm hoping that one of the Gradle designers might be kind enough to enlighten me as this is a question asked frequently all over the web and I'm yet to find a clear answer or a solution that I can make work.
If your task needs to read file names, then I suggest to use the provided API instead of executing commands. Also using exec will make it OS specific, therefore not necessarily portable on different OS.
Here's how to do it:
task hello {
doLast {
def tree = fileTree(dir: '/tmp/test/txt')
def array = []
tree.each {
array << it
print "${it.getName()} added to array!\n"
}
}
}
I ultimately went with this, mentioned above. I have exec {} working well in several places and it seems to be the best option for this use case.
To please an overzealous moderator, that means this:
def doMyThing(String target) {
exec {
executable "something.sh"
args "-t", target
}
}
as mentioned above. This provides the same ultimate functionality.
I have a copy task that is trying to copy files extracted in the 'dependsOn' task. The destination folder contains a timestamp which is retrieved from a properties file.
task copyFilesToBuild(type: Copy, dependsOn:unzipExtFile) {
def tmpTimestampFile = file("$buildDir/tmp/timestamp.properties")
if (tmpTimestampFile.exists()) {
stampProp = new Properties()
stampProp.load(new FileInputStream(tmpTimestampFile))
def timestampFromFile=stampProp.getProperty('propfileTimestamp')
def unzippedExtFilesDir = file("$buildDir/tmp/unzipped/static/js/app")
def appBuildDir = file("$buildDir/apptmp/war/app/app_$timestampFromFile/sub")
from unzippedExtFilesDir
into appBuildDir
}
}
First time through my build (after a clean) it says 'UP TO DATE" (debug output says it's skipping the task as it has no source files.)
I have tried adding closures to defer evaluation, (from { unzippedExtFilesDir }) and tried putting the body of the task in a doFirst, but neither had any effect. I've also tried using the dependsOn task name (unzipExtFile) directly in the 'from' statement but that also did not work.
Second time the build is run this task does get run, so it appears to be a timing thing between the configuration and execution, but I'm running out of ideas. Any suggestions would be welcome.
The code is reading the timestamp at configuration time, before any task has run. (You can see this by adding a println statement.) Because no timestamp file exists at this time (in particular on the first run), no source location (from ...) gets configured, and the task is considered up-to-date because there is nothing to copy. Putting the whole timestamp logic into the into block (into { ...; appBuildDir }) should solve the problem.