Writing your scripts in Java and Kotlin with Bitrise

Learn how you can use different languages in Bitrise thanks to the versatile fan-favorite Script Step. An article by Richard Bogdan, our very own software engineer.

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

  1. 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'
Copy code

This is for Linux, but you can find the details for macOS here

  1. You have to switch back when you are building your Android app, otherwise you might have issues with building.
  2. 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!");
   }
}
Copy code

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
Copy code

Explanation

  1. In the first line, I just create a variable to avoid needing to type the path twice.
  2. (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.
  3. 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
Copy code

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"
   }
}
Copy code

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!")
       }
   }
}
Copy code

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!")
Copy code

 

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"
Copy code

Explanation:

  1. Download SDKMAN
  2. Initialize SDKMAN
  3. Install Kotlin with SDKMAN. This will add the path of the kotlin executable to your PATH environment variable
  4. (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
Copy code

And here is how to run the Kotlin scripts:


kotlinc -script ./ci/src/main/java/io/bitrise/ci/KotlinScript.kts
Copy code

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!

Get Started for free

Start building now, choose a plan later.

Sign Up

Get started for free

Start building now, choose a plan later.