How I created a JDK 19 Loom GitHub Action
All you need to know about creating github actions following a JDK19 Loom setup
I'm currently working on a project, which I'm using to compare what Kotlin
and Java
provide in terms of performance.
Having said this, I want to share with you how I created a GitHub
action for Project Loom
. Since Project Loom is about to come out officially, this is great news, but it hasn't really yet and support for it isn't the best yet. From a quick search and following the lead on InfoQ magazine online these are currently the best predictions and the suggestions, quoting, of Mark Reinhold
:
2022/06/09 Rampdown Phase One
2022/07/21 Rampdown Phase Two
2022/08/11 Initial Release Candidate
2022/08/25 Final Release Candidate
2022/09/20 General Availability
This means that, odds are, that an official release of Project Loom
might only come at the end of the year. This whole thing is an SDK. Unlike coroutines which works as a DSL
(Domain
Specific
Language
) running on top of the JDK
, Project Loom
is a JDK
on its own. It uses concepts such as Fibers, Virtual Threads
, Tail-Calls
and Continuation
that I want to explore. By the way Continuation
is described as the Co
in Coroutines
.
Working locally you may simply download the JDK
from their website and then proceed as you would for other manually installed JDK
's. However for GitHub pipelines, this can be quite challenging. The pipelines use actions and I couldn't find a single one that would suit my needs, and so I did one myself. To be honest I never really quite understood what an action actually does. I never really looked at the inner workings of a GitHub action and so this is why I'm sharing this with you now. I based my own action on another action called GitHub Action for GraalVM
. And of course I followed the official GitHub
tutorial about this.
I named it loom-action, and you can find it on GitHub. There are several ways to start an action, and it basically just runs code, and it does follow a particular kind of architecture. You can start a Docker Container and you can run Node
code as an example. Out of all available options known to me, I chose to implement my action code using Node
TS
. In order to do that I first defined my action in a YAML format:
name: 'JEsperancinhaOrg Loom Action'
description: 'Allows usage of the Java Loom JDK in GitHub Pipelines'
author: 'João Esperancinha <jofisaes@gmail.com>'
branding:
icon: 'package'
color: 'green'
inputs:
loom:
required: false
description: 'Loom version (release, latest, dev).'
value: '19-loom+6-625'
runs:
using: 'node16'
main: 'dist/index.js'
What this means is that I'm creating an action that will install JDK 19
in the pipeline. I made it possible to configure just one input at the moment and that is loom with a default version 19-loom+6-625
. This is of course the Loom JDK
version we want to use. In the runs command I'm telling the action to run the index.js
file in order to make everything happen. If you haven't seen it already the index.js exists in folder dist. This whole parameter is configurable but the code must run standalone. This means that the action does not compile code. We need to give it the completely compiled code. This is the reason that both the compiled and the source code are checked in for this repo. In order to use this action fully, we can use it in our git action like this:
- name: JEsperancinhaOrg Loom Action
uses: JEsperancinhaOrg/loom-action@0.0.0-alfa-j
with:
loom: '19-loom+6-625'
Note that my alfa versions are not yet ready to be used in Production
. Not according to my criteria at least. I still need to test how this works with Gradle and Maven. But here you have just a way that this could work now.
If you know Node JS
, it should be fairly easy for you to understand how I generate the compiled code. If not and because it is not the goal of this coffee session, then you can find plenty of tutorials on the web. I used NCC
for this project. So this is the code:
import * as core from "@actions/core";
import http from "https";
import fs from "fs";
import path from "path";
import tar from "tar-fs";
import zlib from "zlib";
const confLoom = core.getInput("loom")
const loom = confLoom ? confLoom : "19-loom+6-625"
const file = fs.createWriteStream("openjdk-19.tar.gz");
let downloadFile = "https://download.java.net/java/early_access/loom/6/openjdk-" + loom + "_linux-x64_bin.tar.gz";
console.log("Downloading file at " + downloadFile + ".")
http.get(downloadFile, function (response) {
response.pipe(file);
file.on("finish", () => {
file.close();
console.log("Download Completed");
fs.createReadStream("openjdk-19.tar.gz")
.pipe(zlib.createGunzip())
.pipe(tar.extract("loom-jdk"))
.on("finish", () => {
console.log("Unzipped JDK Loom");
let loomJdkJdk19 = "loom-jdk/jdk-19";
const absolutePath = path.resolve(loomJdkJdk19);
console.log("Setting:")
console.log("JAVA_HOME=" + absolutePath)
core.exportVariable('JAVA_HOME', absolutePath);
let newPath = absolutePath + "/bin:" + process.env.PATH;
console.log("PATH=" + newPath)
core.exportVariable('PATH', newPath);
});
});
});
And yes, I'm using console and not a better logger. I know, but that wasn't the goal of this action. I really had to invest my time learning actually how it works. Better versions will come later on 😁. For now, it's important to focus on the library @actions/core. This library is not the only one made specifically to git actions, but it is the most used one, and it is essential to understand a few things about it. We get the loom value from core.getInput("loom"). It should at this point come with value 19-loom+6-625
. If not, I still try to assign it with this default value. It then follows a chain of streams that will download the configured version to file openjdk-19.tar.gz
. After that, we just unpack this file in the root, and finally we set the matching JAVA_HOME
and PATH
environment values with the new values with core.exportVariable('JAVA_HOME', absolutePath);
and core.exportVariable('PATH', newPath);
. This is essentially it and as you can see, there is no mythology here or difficulties.
There was a small issue with the exportVariable. I noticed that this function behaved quite strangely when two JDK actions are configured in the pipeline. In my case, I was making tests with GraalVM JDK 17 at the same time I was trying to get my action to work. Quite remarkably, it always placed the JDK17
first in the PATH
. So this tells me that exportVariable isn't really a setter. It does something else if there are more actions on the same pipeline that use the same environment variables. But anyway, why would I want to have two JDK's
in the same pipeline? I guess I'm just curious. There are better places to install JDK's
with a GitHub
action, but just like many of my improvement points mentioned above, more versions will come, they will be better, and they will be improved. But no matter how I improve them, the core of the functionality is already there.
Thank you!
I hope you enjoyed this article as much as I did making it!
Please leave a review, comments or any feedback you want to give on any of the socials in the links bellow.
I’m very grateful if you want to help me make this article better.
I have placed all the source code of this application on GitHub.
Thank you for reading!