If you are familiar with Bitrise you probably already used the Script Step to do something in your CI workflow. There are multiple options for the language of your script, by default it is a bash script, but the description of the step also mentions Go, Ruby or Python. Although it does not mention Java or Kotlin, I will show you in the next few minutes how to do it!
In this article I will showcase different possible options, each has its pros and cons, it will be up to you to choose the best for your own needs.
Introducing JShell
As an Android developer, I can say without boasting that my Java skills are quite good, but due to the fact that Android supports up to Java 8, my Java knowledge is less polished above it. I can imagine that there are other fellow developers who are in the same shoes. Therefore I try to develop myself and read a lot in software engineering. Today I stumbled upon JShell, which is a quite nice Java 9 feature. To put it in a nutshell, you can add Java code input to it, and it automatically produces the output. This is where I realized, I could use it on the CI too.
Setup
It is quite easy to set up a Script step to use JShell. You just have to make sure the Script step will use $JAVA_HOME/bin/jshell to execute the script content. You can set this up in the Step’s Config section via the Execute with/ runner binary field.
As you can see on the screenshot below, I added a proof of concept line of code, to print Hello World!
This is what the result will look like:
Caveats
- Probably you already saw that I cheated a bit. By default (as of 2021 February) the JDK is set to Java 8. Bummer! So, before I can use JShell, I have to switch to a newer JDK. Luckily, it is possible with Bitrise, and it is quite painless too — just add a Script Step with the following content:
sudo update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
sudo update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
export JAVA_HOME='/usr/lib/jvm/java-11-openjdk-amd64'
envman add --key JAVA_HOME --value '/usr/lib/jvm/java-11-openjdk-amd64'
This is for Linux, but you can find the details for macOS here
- You have to switch back when you are building your Android app, otherwise you might have issues with building.
- You can update to Java 11, where you can run Java files with one command.
Writing your CI scripts in your app
As seen in the above example, we can run Java code with the Script Step, although it is quite cumbersome. A better approach is to add your CI script files to your project, where you can:
- Compile them easily
- Create tests for them
You have to do the following in Android Studio:
- Go to File/New/New Module
- From the module type picker choose “Java or Kotlin Library”
- Add a name for the module, add a name for your class and choose Java as a language
And you are set! Just make sure you have the content you want. In my example, for just proving the concept, I will print a string to the console:
package io.bitrise.ci;
public class MyClass {
public static void main(String... args) {
System.out.println("Hello again!");
}
}
Note: you can add Java files to your iOS project too, but it is less convenient as you might have to do some steps manually and probably most iOS developers would choose another language.
Compiling and running tests in Bitrise
If you want to compile and run the tests on your newly added module, you can do it simply with a Gradle Runner Step. Just make sure that you set:
- “Gradle task” to run to <modulename>:<taskname>. Example: Ci:build
Note: build will both compile and run the tests - “gradlew file path”: this is important, by default it should be ./gradlew
- “Optional path to the Gradle build file to use” you can leave this empty, but of course tailor this one and the previous one to your needs
How to run your Java classes in Bitrise?
đź“Ś Option 1: Compile them and run them in a script step
Here is a full example:
rootpath=$BITRISE_SOURCE_DIR/ci/src/main/java
javac $rootpath/io/bitrise/ci/MyClass.java
java -classpath $rootpath io.bitrise.ci.MyClass
Explanation
- In the first line, I just create a variable to avoid needing to type the path twice.
- (Optional) In the second line, I just compile the given class in the second line, make sure you type the path correctly. This can be omitted if you previously compiled the classes with the Gradle Runner step. In that case, the classes can be found in the build/classes/java folder.
- Run the given java class.
And enjoy the result:
Caveats
- This script is sensitive for package/path refactoring, so if you modify your package names in your CI module, you have to update the script itself
- It could be quite cumbersome if you have to compile and run multiple classes, so I recommend having a single point of entry
đź“Ś Option 2: Running Single-file Programs without Compiling in Java 11
Java 11 introduced the feature to run Java files with a single command. See an example here:
java ./ci/src/main/java/io/bitrise/ci/MyClass.java
That is really nice and it is much simpler than Option 1, except of course you have to switch to Java 11 to do this. You can find how to switch at the beginning of the article. So in the end, this is not simpler, but if you will be using Java 11 features in your CI script files anyway, then this is a better option for you.
Caveats
- Like in Option 1, this script is sensitive for package/path refactoring, so if you modify your package names in your CI module, you have to update the script itself
- Switching to Java 11 can be cumbersome
What about Kotlin? đź‘€
Most Android developers would pin me the question, why Java, why not Kotlin? I have some good news and some bad news for you:
- The good news is that you can do it!
- The bad is that you have to manually install Kotlin
Let's see how to do it.
Adding Kotlin code to your library
When you are adding a new “Java or Kotlin library” module you can choose Kotlin as a language. In this case, you can add
- Kotlin classes to your library (.kt files)
- Kotlin script files (.kts files)
Just make sure you have the right dependencies in your build.gradle file in the library module:
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.4.30"
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
Again, for the proof of concept I created a Kotlin class (KotlinClass.kt):
package io.bitrise.ci
class KotlinClass {
companion object {
@JvmStatic
fun main(args: Array<String>) {
println("Hello kotlin class!")
}
}
}
Please note that the main method should be static, so you have to annotate it with @JvmStatic and make it a companion object.
And a Kotlin script:
package io.bitrise.ci
println("Hello Kotlin Script!")
Â
Installing Kotlin
In the official Kotlin site there are some options on how to do it, please feel free to choose the one that you like the most. In most cases, Android projects will use a Linux stack, so I will show that as an example. In my example, I used SDKMAN to install Kotlin. Here is the code, which should be inserted in a Script step:
curl -s https://get.sdkman.io | bash
source "/root/.sdkman/bin/sdkman-init.sh"
sdk install kotlin
envman add --key PATH --value "$PATH"
Explanation:
- Download SDKMAN
- Initialize SDKMAN
- Install Kotlin with SDKMAN. This will add the path of the kotlin executable to your PATH environment variable
- (Optional) You will need to export that environment variable with “envman” if you would like to use Kotlin in multiple steps
Now let's get to the point, run them!
It is quite easy to run them, see this example how to run a Kotlin library:
kotlinc ./ci/src/main/java/io/bitrise/ci/KotlinClass.kt -d KotlinClassLib.jar
kotlin -classpath KotlinClassLib.jar io.bitrise.ci.KotlinClass
And here is how to run the Kotlin scripts:
kotlinc -script ./ci/src/main/java/io/bitrise/ci/KotlinScript.kts
Caveats
- You have to install Kotlin, which takes time
- Like the scripts written for Java, if you refactor packages or paths you have to update the script itself
Summary
Should I do any of this?
Well, depends on… If you have a challenging task to complete in the CI and you are not a big fan of bash, well you can try this. Of course, for simple tasks, I would not recommend these, even if you are not familiar with bash. Bash is quite universal, and not too complex, so maybe it is worth learning it instead of switching to Java/Kotlin. As Maslow said, “if all you have is a hammer, everything looks like a nail”.
On the other hand, if you are doing complex things, I definitely recommend it, as you can add test cases quite easily, and you can write it in the language which probably you know more.
Could this be better?
Definitely, Bitrise could have done specific things to remove the boilerplate and simplify your life, for example, pre-installing Kotlin, or create a new Script step that will automatically change Java versions to and back. I would be interested in your opinion on which path/language would you choose? Kotlin or Java?
Closing thoughts
It was very interesting for me to try these things out, I recommend you to always try to think outside of the box a bit, and find new paths to resolve your problems. Hope you enjoyed my article, if you have any questions or comments, feel free to tweet us here, we are more than happy to help you!