Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jetbrains.jdk: add updateScript and a test #301653

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ in {
jellyfin = handleTest ./jellyfin.nix {};
jenkins = handleTest ./jenkins.nix {};
jenkins-cli = handleTest ./jenkins-cli.nix {};
jetbrains-jdk = runTest ./jetbrains-jdk;
jibri = handleTest ./jibri.nix {};
jirafeau = handleTest ./jirafeau.nix {};
jitsi-meet = handleTest ./jitsi-meet.nix {};
Expand Down
34 changes: 34 additions & 0 deletions nixos/tests/jetbrains-jdk/BrowserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import com.jetbrains.cef.JCefAppConfig;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import org.cef.CefApp;
import org.cef.CefSettings;
import org.cef.browser.CefRendering;

public class BrowserTest {
private static void fail(String message) {
throw new RuntimeException(message);
}

public static void main(String[] args) throws InterruptedException {
var started = CefApp.startup(args);
var config = JCefAppConfig.getInstance();

var settings = config.getCefSettings();
var app = CefApp.getInstance(settings);
var latch = new CountDownLatch(1);
app.onInitialization(s -> latch.countDown());
latch.await(30, TimeUnit.SECONDS);
if (latch.getCount() > 0) {
fail("CEF initialization timed out");
}

var client = app.createClient();
var browser = client.createBrowser("chrome://system", CefRendering.DEFAULT, false);
var frame = new JFrame("browser test");
frame.add(browser.getUIComponent());
frame.setSize(640, 480);
frame.setVisible(true);
}
}
25 changes: 25 additions & 0 deletions nixos/tests/jetbrains-jdk/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Tests if opening a JCEF window with chrome://about by building
# and running a simple Java application using JBR.
{ lib, ... }:
{
name = "jetbrains-jdk-jcef";

meta.maintainers = with lib.maintainers; [ liff ];

enableOCR = true;

nodes.machine =
{ lib, pkgs, ... }:
let
jbr-browser-test = pkgs.callPackage ./jbr-browser-test.nix { };
in
{
services.cage.program = lib.getExe jbr-browser-test;
imports = [ ../common/wayland-cage.nix ];
};

testScript = ''
machine.wait_for_unit('graphical.target')
machine.wait_for_text('CHROME VERSION', timeout=90)
'';
}
30 changes: 30 additions & 0 deletions nixos/tests/jetbrains-jdk/jbr-browser-test.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
stdenv,
lib,
makeWrapper,
jetbrains,
}:

stdenv.mkDerivation {
name = "jbr-browser-test";

src = ./BrowserTest.java;
dontUnpack = true;

nativeBuildInputs = [ makeWrapper ];
buildInputs = [ jetbrains.jdk ];

buildPhase = ''
cp $src BrowserTest.java
javac BrowserTest.java
'';

installPhase = ''
mkdir --parents $out/{lib,bin}
cp *.class $out/lib/
makeWrapper ${lib.getBin jetbrains.jdk}/bin/java $out/bin/jbr-browser-test \
--append-flags "-cp $out/lib BrowserTest"
'';

meta.mainProgram = "jbr-browser-test";
}
32 changes: 20 additions & 12 deletions pkgs/development/compilers/jetbrains-jdk/default.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{ lib
, stdenv
, fetchFromGitHub
, testers
, nixosTests
, jetbrains
, jdk
, git
Expand Down Expand Up @@ -35,29 +37,26 @@ let
"x86_64-linux" = "x64";
}.${stdenv.hostPlatform.system} or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
cpu = stdenv.hostPlatform.parsed.cpu.name;

versionInfo = lib.importJSON ./version-info.json;

in
jdk.overrideAttrs (oldAttrs: rec {
pname = "jetbrains-jdk" + lib.optionalString withJcef "-jcef";
javaVersion = "21.0.4";
build = "598.4";
# To get the new tag:
# git clone https://github.com/jetbrains/jetbrainsruntime
# cd jetbrainsruntime
# git checkout jbr-release-${javaVersion}b${build}
# git log --simplify-by-decoration --decorate=short --pretty=short | grep "jbr-" --color=never | cut -d "(" -f2 | cut -d ")" -f1 | awk '{print $2}' | sort -t "-" -k 2 -g | tail -n 1 | tr -d ","
openjdkTag = "jbr-21.0.4+8";
inherit (versionInfo)
javaVersion
build
openjdkTag
SOURCE_DATE_EPOCH;
version = "${javaVersion}-b${build}";

src = fetchFromGitHub {
owner = "JetBrains";
repo = "JetBrainsRuntime";
rev = "jb${version}";
hash = "sha256-YF5Z1A4qmD9Z4TE6f2i8wv9ZD+NqHGY5Q0oIVQiC3Bg=";
inherit (versionInfo) rev hash;
};

BOOT_JDK = jdk.home;
# run `git log -1 --pretty=%ct` in jdk repo for new value on update
SOURCE_DATE_EPOCH = 1726275531;

patches = [ ];

Expand Down Expand Up @@ -152,5 +151,14 @@ jdk.overrideAttrs (oldAttrs: rec {

passthru = oldAttrs.passthru // {
home = "${jetbrains.jdk}/lib/openjdk";
tests = {
version = testers.testVersion {
package = jetbrains.jdk;
command = "java --version";
version = javaVersion;
};
jcef = nixosTests.jetbrains-jdk;
};
updateScript = [ ./update.py jetbrains.idea-ultimate.src jetbrains.idea-ultimate.version ];
};
})
102 changes: 102 additions & 0 deletions pkgs/development/compilers/jetbrains-jdk/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3
#!nix-shell -p nix-prefetch-github
#!nix-shell -p "python3.withPackages(p: with p; [ python-dotenv ])"
#
# Usage: ./update <idea-ultimate tarball> <idea version>
#
# The first argument is a path to an IntelliJ IDEA Ultimate tarball and the second argument is version, like 2023.3.1
# of that tarball. You can get a path to one with `nix-build -A jetbrains.idea-ultimate.src`.
#
# The tarball contains a file with JBR version information at <prefix>/jbr/release. From that file we resolve the
# Git tag and a few other bits of metadata of the JBR version used by that version of IDEA, and with that we can
# pre-fetch the source and get the hash.
#
import json
import os.path
import re
import subprocess
import sys
import tarfile
from datetime import datetime
from io import TextIOWrapper
from json.decoder import JSONDecodeError
from pathlib import PurePath

import dotenv.parser

# Size of the tarfile buffer
BUFFER_SIZE = 4 * 1024 * 1024

# Regular expression for extracting different components from an `IMPLEMENTOR_VERSION` line, like:
# JBR-17.0.10+1-1087.23-jcef
# └┬────┘ ╿ └┬────┘
# │ │ │
# │ │ │
# │ │ ┕ build
# │ ┕ javaBuild
# ┕ javaVersion
#
VERSION_LINE = re.compile(r"""JBR-(?P<javaVersion>[0-9.]+)\+(?P<javaBuild>\d+)-(?P<build>\d+\.\d+)(-\w+)?""")


def extract_jbr_release(intellij_tarball: str) -> dict[str, str]:
"""Extracts jbr/release from the source tarball and returns a dict with the contents of that file."""

with tarfile.open(name=intellij_tarball, mode="r", bufsize=BUFFER_SIZE) as src:
member = next(member for member in src if PurePath(member.name).match("*/jbr/release"))

with src.extractfile(member) as release:
return {b.key: b.value for b in dotenv.parser.parse_stream(TextIOWrapper(release))}


def prefetch(rev: str) -> str:
argv = ["nix-prefetch-github", "--meta", "--rev", tag, "JetBrains", "JetBrainsRuntime"]
prefetched = json.loads(subprocess.check_output(argv).decode("utf-8"))
src = prefetched["src"]
meta = prefetched["meta"]
commit_timestamp = datetime.fromisoformat(f"{meta['commitDate']}T{meta['commitTimeOfDay']}Z")
return dict(
hash=src["hash"],
SOURCE_DATE_EPOCH=int(commit_timestamp.timestamp()),
)


if __name__ == "__main__":
idea_tarball = sys.argv[1]
idea_version = sys.argv[2]
version_info_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "version-info.json")

try:
with open(version_info_file, mode="rb") as input:
version_info = json.load(input)
except (FileNotFoundError, JSONDecodeError):
version_info = dict()

if version_info.get("ideaVersion") == idea_version:
print(f"Up to date at {version_info['rev']} for IntelliJ IDEA {idea_version}.", file=sys.stderr)
sys.exit(0)

print(f"Updating JBR release to match IDEA {idea_version}…", file=sys.stderr)

release = extract_jbr_release(idea_tarball)

version_info = {"ideaVersion": idea_version}

version_info |= VERSION_LINE.fullmatch(release["IMPLEMENTOR_VERSION"]).groupdict()

java_version = version_info["javaVersion"]
java_build = version_info["javaBuild"]
build = version_info["build"]
tag = f"jb{java_version}-b{build}"

version_info["openjdkTag"] = f"jbr-{java_version}+{java_build}"

version_info["rev"] = tag
version_info |= prefetch(tag)

with open(version_info_file, "w") as out:
json.dump(version_info, out, indent=4)
out.write("\n")

print(f"JBR updated to {tag}", file=sys.stderr)
10 changes: 10 additions & 0 deletions pkgs/development/compilers/jetbrains-jdk/version-info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"ideaVersion": "2024.2.1",
"javaVersion": "21.0.3",
"javaBuild": "13",
"build": "509.11",
"openjdkTag": "jbr-21.0.3+13",
"rev": "jb21.0.3-b509.11",
"hash": "sha256-zTstmrH4KteR40BVDRfxOrl8aUQ26acE+ywscBd8sw8=",
"SOURCE_DATE_EPOCH": 1723453663
}