How to detect the current OS from Gradle - gradle

I found this answer about how to do it with Groovy:
Detecting the platform (Window or Linux) by Groovy/Grails:
if (System.properties['os.name'].toLowerCase().contains('windows')) {
println "it's Windows"
} else {
println "it's not Windows"
}
Is there a better way?

Actually, I looked at the Gradle project, and this looks a little cleaner as it uses Ant's existing structure:
import org.apache.tools.ant.taskdefs.condition.Os
task checkWin() << {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
println "*** Windows "
}
}
I found this in the following Gradle branch, and it seems to work nicely. gradle/gradle-core/branches/RB-0.3/build.gradle

Mid 2020 Update:
Still incubating:
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
Early 2019 Update: current() removed.
org.gradle.nativeplatform.platform.OperatingSystem.getDisplayName()
org.gradle.nativeplatform.platform.OperatingSystem.isLinux()
Keep in mind that it's still incubating though.
Mid 2018 Update: just like it was mentioned in comments, now this class moved to a different package, so one should use org.gradle.nativeplatform.platform.OperatingSystem.current()
As of mid 2015, Peter Kahn's answer is still valid. Environment-based profile activation is still something done relatively easier in Maven. But keep in mind that org.apache.tools.ant.taskdefs.condition.Os.isFamily is not exclusive in the sense that if it returns true with one particular parameter it is not necessarily means that it returns false for any other parameter. For instance:
import org.apache.tools.ant.taskdefs.condition.Os
task detect {
doLast {
println(Os.isFamily(Os.FAMILY_WINDOWS))
println(Os.isFamily(Os.FAMILY_MAC))
println(Os.isFamily(Os.FAMILY_UNIX))
}
}
It will return true both for Os.FAMILY_MAC and Os.FAMILY_UNIX on MacOS. Usually it is not something you need in build scripts.
There is though another way to achieve this using Gradle 2+ API, namely:
import org.gradle.internal.os.OperatingSystem;
task detect {
doLast {
println(OperatingSystem.current().isMacOsX())
println(OperatingSystem.current().isLinux())
}
}
Check out the documentation for the org.gradle.nativeplatform.platform.OperatingSystem interface. It is worth to mention that this interface is marked with incubating annotation, that is, "the feature is currently a work-in-progress and may change at any time". The "internal" namespace in the implementation also gives us a hint that we should use this knowing that this can change.
But personally I'd go with this solution. It's just that it's better to write a wrapper class so as not to mess up in case something will change in the future.

One can differentiate the build environment in between Linux, Unix, Windows and OS X - while the Gradle nativeplatform.platform.OperatingSystem differentiates the target environment (incl. FreeBSD and Solaris).
import org.gradle.internal.os.OperatingSystem
OperatingSystem os = OperatingSystem.current();
println "*** Building on ${os.familyName} / ${os.name} / ${os.version} / ${System.getProperty("os.arch")}."
println "*** Building on ${os.toString()}."
if (os.isLinux()) {
// Consider Linux.
} else if (os.isUnix()) {
// Consider UNIX.
} else if (os.isWindows()) {
// Consider Windows.
} else if (os.isMacOsX()) {
// Consider OS X.
} else {
// Unknown OS.
}
One can also use an Ant task (source):
import org.apache.tools.ant.taskdefs.condition.Os
task checkWin() << {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Consider Windows.
}
}

Or you can define osName as a string...
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.osName = "Linux";
break;
case OperatingSystem.MAC_OS:
project.ext.osName = "macOS";
break;
case OperatingSystem.WINDOWS:
project.ext.osName = "Windows";
break;
}
... and use it later - to include a native library for example:
run {
systemProperty "java.library.path", "lib/$osName"
}
But it wouldn't change anything since OperatingSystem works exactly like your code:
public static OperatingSystem forName(String os) {
String osName = os.toLowerCase();
if (osName.contains("Windows")) {
return WINDOWS;
} else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) {
return MAC_OS;
} else if (osName.contains("sunos") || osName.contains("solaris")) {
return SOLARIS;
} else if (osName.contains("linux")) {
return LINUX;
} else if (osName.contains("freebsd")) {
return FREE_BSD;
} else {
// Not strictly true
return UNIX;
}
}
Source: https://github.com/gradle/gradle/blob/master/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
Edit:
You can do the same for the architecture:
project.ext.osArch = OperatingSystem.current().getArch();
if ("x86".equals(project.ext.osArch)) {
project.ext.osArch = "i386";
}
and:
run {
systemProperty "java.library.path", "lib/$osName/$osArch"
}
Just be aware that getArch() will return:
"ppc" on PowerPC
"amd64" on 64b
"i386" OR "x86" on 32b.
getArch() will return "x86" on Solaris or "i386" for any other platform.
Edit 2:
Or if you want to avoid any import, you can simply do it yourself:
def getOsName(project) {
final String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("linux")) {
return ("linux");
} else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) {
return ("macos");
} else if (osName.contains("windows")) {
return ("windows");
} else if (osName.contains("sunos") || osName.contains("solaris")) {
return ("solaris");
} else if (osName.contains("freebsd")) {
return ("freebsd");
}
return ("unix");
}
def getOsArch(project) {
final String osArch = System.getProperty("os.arch");
if ("x86".equals(osArch)) {
return ("i386");
}
else if ("x86_64".equals(osArch)) {
return ("amd64");
}
else if ("powerpc".equals(osArch)) {
return ("ppc");
}
return (osArch);
}

Gradle doesn't provide a public API for detecting the operating system. Hence the os. system properties are your best bet.

I don't like detecting the OS in Gradle through properties or an Ant task, and the OperatingSystem class no longer contains the current() method.
So, in my opinion, the cleanest way to detect the OS would be:
Import DefaultNativePlatform:
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
Then use DefaultNativePlatform in your task:
if (DefaultNativePlatform.getCurrentOperatingSystem().isWindows()) {
println 'Windows'
}
Mind that this method is not ideal as it is using the Gradle internal API.
It was tested with Gradle 4.10.

Without any imports I got those values from System class like here:
def osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH)
def osArch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH)
def osVersion = System.getProperty("os.version").toLowerCase(Locale.ENGLISH)

This worked for me in 2022/2023 with Gradle 7.5
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
OperatingSystem os = DefaultNativePlatform.currentOperatingSystem;
os.isLinux()
os.isWindows()
os.isMacOsX()

According to #shabunc answer, some of your options are:
org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
org.apache.tools.ant.taskdefs.condition.Os
org.gradle.nativeplatform.platform.OperatingSystem
org.gradle.internal.os.OperatingSystem
Number 3 and 4 are basically the same interface, in which OperatingSystem.current() is actually OperatingSystem.isCurrent() (because of Groovy). So it did not work for me.
Number 2 is a viable option but not that elegant IMHO.
So this is how you can use option 1 (as an example was not included):
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
def os = DefaultNativePlatform.currentOperatingSystem
def arch = DefaultNativePlatform.currentArchitecture
def version = "1.0.0"
switch (true) {
case os.windows && arch.i386:
implementation "com.example:example-win32-x86:${version}"
break
case os.windows && arch.amd64:
implementation "com.example:example-win32-x86-amd64:${version}"
break
case os.macOsX && arch.amd64:
implementation "com.example:example-darwin-x86-amd64:${version}"
break
case os.linux && arch.i386:
implementation "com.example:example-linux-x86:${version}"
break
case os.linux && arch.amd64:
implementation "com.example:example-linux-x86-amd64:${version}"
break
default:
println "No suitable driver found for " +
"current OS (${os.displayName}) and " +
"architecture (${arch.displayName})"
}

Related

Jenkins Pipeline - If/Else Statement with Matrix Name vars

I dont know why i cant get this to work but could use some help.
I have a Jenkins pipeline with a matrix defined, one of the axis is defined as OS for windows or linux.
In one of my stage's i want to call different things if the OS is linux or windows, but i keep getting an error
java.lang.NoSuchMethodError: No such DSL method '$' found among steps [ArtifactoryGradleBuild, MavenDescriptorStep, acceptGitLabMR, addEmbeddableBadgeConfiguration, addGitLabMRComment,
My Matrix is defined like this
matrix {
agent {
label ("${DUT} && ${OS}")
}
axes {
axis {
name 'BUILD_TYPE'
values 'Internal'
}
axis {
name 'DUT'
values 'DUT'
}
axis {
name 'OS'
values 'windows', 'linux'
}
}
And my stage is defined like this
stage ("Cleaning System") {
steps {
script {
if (${OS} == 'windows') {
stage ('Cleaning Up Windows System') {
echo "WINDOWS CLEAN"
}
}
if (${OS} == 'linux') {
stage ('Cleaning Up Linux System') {
echo "LINUX CLEAN"
}
}
}
}
}
It seems like it doesnt know the ${var} syntax inside a script block? Or what am i doing wrong?
Thanks!
You dont' require a $ in the comparison:
It should be :
if (OS == 'windows') {
...
}
if (OS == 'linux') {
...
}

exit declarative pipeline prematurely based on script output

While I am aware of this question clean way to exit declarative Jenkins pipeline as success I am to green to understand how to put it to use (from where does the skipBuild variable come?).
I have a script that determines whether the pipeline should continue or not but I am unsure how to piece it together (I am free to construct the script as needed).
pipeline {
agent {
docker { image 'python:3-alpine' }
}
stage('Should I continue') {
steps {
python should_i_continue.py
}
when { ? == true }
stages {
...
}
}
}
I am aware that the capabilities increase tenfold if I use a scripted pipeline but I wonder if it is possible to do what I want with a declarative one?
You can use any custom variable, which you will set as true|false based on some condition in the steps of your pipeline and all stages that need to be executed based on that condition have to have following format:
stage('Should Continue?') {
setBuildStatus("Build complete", "SUCCESS");
when {
expression {skipBuild == true }
}
}
In other words to provide you a bit clean picture, check this abstract example:
node {
skipBuild = false
stage('Checkout') {
...
your checkout code here
...
}
stage('Build something') {
...
some code goes here
skipBuild = true
...
}
stage('Should Continue?') {
setBuildStatus("Build complete", "SUCCESS");
when {
expression {skipBuild == true }
}
}
}

How to ask Gradle if a specific input is not up-to-date

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()
}
}

Gradle Environment variables. Load from file

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
}
}
}

How to define and call custom methods in build.gradle?

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;

Resources