As part of my project, I need to read files from a directory and do some operations all these in build script. For each file, the operation is the same(reading some SQL queries and execute it). I think its a repetitive task and better to write inside a method. Since I'm new to Gradle, I don't know how it should be. Please help.
One approach given below:
ext.myMethod = { param1, param2 ->
// Method body here
}
Note that this gets created for the project scope, ie. globally available for the project, which can be invoked as follows anywhere in the build script using myMethod(p1, p2) which is equivalent to project.myMethod(p1, p2)
The method can be defined under different scopes as well, such as within tasks:
task myTask {
ext.myMethod = { param1, param2 ->
// Method body here
}
doLast {
myMethod(p1, p2) // This will resolve 'myMethod' defined in task
}
}
If you have defined any methods in any other file *.gradle - ext.method() makes it accessible project wide. For example here is a
versioning.gradle
// ext makes method callable project wide
ext.getVersionName = { ->
try {
def branchout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
standardOutput = branchout
}
def branch = branchout.toString().trim()
if (branch.equals("master")) {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags'
standardOutput = stdout
}
return stdout.toString().trim()
} else {
return branch;
}
}
catch (ignored) {
return null;
}
}
build.gradle
task showVersion << {
// Use inherited method
println 'VersionName: ' + getVersionName()
}
Without ext.method() format , the method will only be available within the *.gradle file it is declared. This is the same with properties.
You can define methods in the following way:
// Define an extra property
ext.srcDirName = 'src/java'
// Define a method
def getSrcDir(project) {
return project.file(srcDirName)
}
You can find more details in gradle documentation Chapter 62. Organizing Build Logic
An example with a root object containing methods.
hg.gradle file:
ext.hg = [
cloneOrPull: { source, dest, branch ->
if (!dest.isDirectory())
hg.clone(source, dest, branch)
else
hg.pull(dest)
hg.update(dest, branch)
},
clone: { source, dest, branch ->
dest.mkdirs()
exec {
commandLine 'hg', 'clone', '--noupdate', source, dest.absolutePath
}
},
pull: { dest ->
exec {
workingDir dest.absolutePath
commandLine 'hg', 'pull'
}
},
]
build.gradle file
apply from: 'hg.gradle'
hg.clone('path/to/repo')
Somehow, maybe because it's five years since the OP, but none of the
ext.someMethod = { foo ->
methodBody
}
approaches are working for me. Instead, a simple function definition seems to be getting the job done in my gradle file:
def retrieveEnvvar(String envvar_name) {
if ( System.getenv(envvar_name) == "" ) {
throw new InvalidUserDataException("\n\n\nPlease specify environment variable ${envvar_name}\n")
} else {
return System.getenv(envvar_name)
}
}
And I call it elsewhere in my script with no prefix, ie retrieveEnvvar("APP_PASSWORD")
This is 2020 so I'm using Gradle 6.1.1.
#ether_joe the top-voted answer by #InvisibleArrow above does work however you must define the method you call before you call it - i.e. earlier in the build.gradle file.
You can see an example here. I have used this approach with Gradle 6.5 and it works.
With Kotlin DSL (build.gradle.kts) you can define regular functions and use them.
It doesn't matter whether you define your function before the call site or after it.
println(generateString())
fun generateString(): String {
return "Black Forest"
}
tasks.create("MyTask") {
println(generateString())
}
If you want to import and use a function from another script, see this answer and this answer.
In my react-native in build.gradle
def func_abc(y){return "abc"+y;}
then
def x = func_abc("y");
If you want to check:
throw new GradleException("x="+x);
or
println "x="+x;
Related
I can take credentials like explained in the example taken from here - https://jenkins.io/doc/book/pipeline/syntax/#environment
stage('Example') {
environment {
CREDS = credentials('MY_CREDS_DEV')
}
steps {
sh 'echo hello'
}
}
But what I want to do is to get credentials based on some condition.
For example I have MY_CREDS_DEV and MY_CREDS_QA defined in Jenkins credentials. And I have a property ENV=dev defined in Jenkins 'Prepare an environment for the run' section.
I'd like to access credentials based on my environment, i.e. ENV property.
I tried to use CREDS = credentials('MY_CREDS_' + ${ENV}) and tried to extract strings concatenation to a separate function and call it like CREDS = credentials(concatenate(${ENV})) but I got Internal function call parameters must be strings.
So seems I can put only a string to credentials() function which basically means to hardcode it. But how can I choose which credentials to use - dev or qa?
Use CREDS = credentials('MY_CREDS_' + ENV) or CREDS = credentials("MY_CREDS_${ENV}"). ${ENV} will not become 'dev'but ${'dev'} and therefore is no string.
For completeness:
In fact - after playing aroung with the groovy console - it looks like ${ENV} will try to call a function called $ with the closure parameter {ENV} which in turn would return 'dev'. It would give the same result as ENV if you would have defined a function like:
def $(Closure closure) {
closure()
}
But most probably that's not what you wanted to do.
Got this working in Jenkins:2.190.2 with a little groovy. Haven't tested on earlier versions. Just happens to be the one I'm on now. Works fine with multiple stages.
pipeline {
agent {
label "xxxxx"
}
environment {
ROLE = getRole()
}
stages{
stage("write to s3 etc") {
environment {
AWS = credentials("${ROLE}")
}
steps {
script {
sh"""
aws s3 sync build/ "s3://xxxxxxxxxxxx"
"""
}
}
}
}
}
def getRole() {
def branchName = "${env.BRANCH_NAME}"
if (branchName == "xxxxxx") {
return 'some_credential_string'
}
else {
return 'some_other_credential_string'
}
}
If you would like to use different credentials based on the condition, this could be done with the following example:
stage ("Example") {
steps {
script {
if ( params.TEST_PARAMETER == "test_value1" ) {
withCredentials([string(credentialsId: env.CREDENTIALS_1, variable: 'SOME_VARIABLE')]) {
yourFunction()
}
}
else {
withCredentials([string(credentialsId: env.CREDENTIALS_2, variable: 'SOME_VARIABLE')]) {
yourFunction()
}
}
}
}
}
You would need to define yourFunction in the end of your jenkinsfile. In this case, when TEST_PARAMETER is test_value1 in the job, CREDENTIALS_1 will be used from Jenkins credentials list. When TEST_PARAMETER is different, CREDENTIALS_2 credentials will be used. You could have more options by modifying this to the case loop.
Hope this helps.
Is there a way in Gradle to do something like this?
task printIsSpecificInputUpToDate() {
inputs.property("file1", file("file1.log"))
inputs.property("file2", findProperty("file2.log"))
outputs.file(file("file3.log"))
// if one or more inputs is not up to date
doLast {
// find out if file1 is actually the input out of date
// NOTE: pseudo-code!
if (inputs.get("file1").isUpToDate()) {
onlyProcessFile2()
} else {
processFile1AndFile2()
}
}
}
If there is not, does that indicate that Gradle think this would be a bad pattern?
I think what you are looking for are Incremental tasks.
You need to define your own task class for this, but then you can query exactly for the changed files in your inputs:
#TaskAction
void execute(IncrementalTaskInputs inputs) {
println inputs.incremental ? 'CHANGED inputs considered out of date'
: 'ALL inputs considered out of date'
if (!inputs.incremental)
project.delete(outputDir.listFiles())
inputs.outOfDate { change ->
println "out of date: ${change.file.name}"
def targetFile = new File(outputDir, change.file.name)
targetFile.text = change.file.text.reverse()
}
inputs.removed { change ->
println "removed: ${change.file.name}"
def targetFile = new File(outputDir, change.file.name)
targetFile.delete()
}
}
I am new to Gradle.
Currently I have this task:
task fooTask {
doLast {
exec {
environment 'FOO_KEY', '1234567' // Load from file here!
commandLine 'fooScript.sh'
}
}
}
fooScript.sh
#!/bin/bash
echo $FOO_KEY
Everything works great. But I have env.file with all needed environment variables. This file is used in Docker builder.
env.file
FOO_KEY=1234567
Question: how can I use env.file together with Gradle environment to load all needed env. params?
What about this :
task fooTask {
doLast {
exec {
file('env.file').readLines().each() {
def (key, value) = it.tokenize('=')
environment key, value
}
commandLine 'fooScript.sh'
}
}
}
I give also my version (check if line is not empty and not a comment, also donot override env var):
file('.env').readLines().each() {
if (!it.isEmpty() && !it.startsWith("#")) {
def pos = it.indexOf("=")
def key = it.substring(0, pos)
def value = it.substring(pos + 1)
if (System.getenv(key) == null) {
environment key, value
}
}
}
But actually, I think they should add this feature as a exec plugin property! It's quite common now to use .env file.
The following code is the only one i've been able to produce and which satisfies two of the most importants requirements to provide an efficient "UNIX standard environment file import" in Android studio :
Loads a file which depends of the Build Type (at least : debug and release)
Exposes specified environment variables in the Android code, actually not as environment variables but as buildConfigFields content.
ext {
node_env = ""
}
android.applicationVariants.all { variant ->
if (variant.name == "debug") {
project.ext.set("node_env", "development")
} else if (variant.name == "release") {
project.ext.set("node_env", "production")
}
file("." + node_env + '.env').readLines().each() {
if (!it.isEmpty() && !it.startsWith("#")) {
def pos = it.indexOf("=")
def key = it.substring(0, pos)
def value = it.substring(pos + 1)
if (System.getProperty(key) == null) {
System.setProperty("env.$key", value)
}
}
}
if (variant.name == "release") {
android.signingConfigs.release.storeFile file(System.getProperty("env.ANDROID_APP_SIGNING_STOREFILE"))
android.signingConfigs.release.keyAlias System.getProperty("env.ANDROID_APP_SIGNING_KEYALIAS")
android.signingConfigs.release.storePassword System.getProperty("env.ANDROID_APP_SIGNING_STOREPASSWORD")
android.signingConfigs.release.keyPassword System.getProperty("env.ANDROID_APP_SIGNING_KEYPASSWORD")
}
android.defaultConfig.buildConfigField "String", "ANDROID_APP_URL", "\"${System.getProperty("env.ANDROID_APP_URL")}\""
}
Kotlin :
Log.i(TAG, BuildConfig.ANDROID_APP_URL)
Please let me know what you think of it as i'm not completly sure how it works, especially to select the good file to load.
There are plugins to load env vars from a .env file (e.g. this one)
So a sample build file will look something like this (Kotlin DSL)
plugins {
id("co.uzzu.dotenv.gradle") version "1.1.0"
}
tasks.withType<Test> {
useJUnitPlatform()
//will pass the env vars loaded by the plugin to the environment of the tests
environment = env.allVariables
}
I have ended up doing it in my gradlew file. A possible drawback is that the change tends to be overwritten on upgrades to gradle.
# Hack: export all variables in the .env file
#
ENV_FILE=../../.env
if [ ! -f $ENV_FILE ];then
echo "WARNING/DEV ENV Missing a ${ENV_FILE} file with environment variables (secrets)";
fi
for secret in `cat $ENV_FILE`;do export $secret;done
If you're using Spring Boot bootRun task or anything that has a runner
tasks.named('bootRun') {
doFirst {
file('.env').readLines().each() {
def (key, value) = it.tokenize('=')
environment key, value
}
}
}
I am trying to loop over a map, for each key read some property files, use filter to replace some tokens. My issue is, looping happens. At the end, instead of having 5 folders, I've just 1 folder inside config. Do I need to close any resources like file/inputstream before going on to the next?
task copyResByHost(type: Copy) {
java.util.HashMap hostMap = new HashMap();
hostMap.put("devserver01","env_dev.properties");
hostMap.put("devserver02","env_dev.properties");
hostMap.put("devserver03","env_dev.properties");
hostMap.put("devserver04","env_dev.properties");
hostMap.put("devserver05","env_dev.properties");
hostMap.each { key, value ->
from "$projectDir/resources/templates"
into("build/config/${key}")
def myProps = new Properties()
file("$projectDir/resources/properties/${value}").withInputStream {
myProps.load(it);
}
file("$projectDir/resources/properties/${key}.properties").withInputStream {
myProps.load(it);
}
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: myProps)
}
}
Can you let me know where am I making a mistake which is causing 1 folder only created instead of 5 inside config?
Thanks for your guidance.
from and into were being overwritten everytime causing only 1 folder to be created.
So code should be changed as follows:
hostMap.each { key, value ->
println "Creating configs for $key"
//inputs.dir '$projectDir/resources/templates'
// outputs.dir 'build/config/${key}'
doLast {
copy {
from("$projectDir/resources/templates")
into("build/config/${key}")
def myProps = new Properties()
new File("$projectDir/resources/properties/${value}").withInputStream { stream ->
myProps.load(stream);
}
new File("$projectDir/resources/properties/${key}.properties").withInputStream { stream ->
myProps.load(stream);
}
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: myProps)
}
}
}
from & into, file stream everything should appear within closure doLast { copy { } }
I have a build.gradle as follows
task setDockerHost {
group 'Docker'
description 'Gets the docker host ip from your OS'
def stdout = new ByteArrayOutputStream()
exec {
commandLine './src/main/resources/scripts/get-docker-host.sh', '60'
standardOutput = stdout
}
project.ext.set('DOCKERHOST', "$stdout")
}
tasks.withType(Test) {
doFirst { println "DockerHost is $project.DOCKERHOST" }
environment 'DOCKERHOST', "$project.DOCKERHOST"
outputs.upToDateWhen { false }
testLogging {
events 'passed', 'skipped', 'failed', 'standardOut'
}
reports.html.destination = file("${reporting.baseDir}/${name}")
}
I define a DOCKERHOST env variable as above and want to use in my groovy test class:
class MyClass extends Specification {
RESTClient client = new RESTClient("http://"+System.getenv('DOCKERHOST')+":9090/")
...
}
In the execution my println works: DockerHost is 192.168.99.100
But when I run the this test it throws:
I already tried replacing \n, \r and spaces by "". I also try removing the protocol from the URL (aka 192.168.99.10:9090) and it tells me that the same error occurs at index 0
How can I solve this?
I didn't figure it out in which char was the problem but I was able to solve it but replacing strings like crazy:
String url = ("http://" + System.getenv('DOCKERHOST') + ":9090/").replace("\n", "").replace("\r", "").replace(" ", "")
RESTClient client = new RESTClient(url)
I've spent like a day trying to figure this out... hopefully for someone else will be quicker.