Skip to content

Commit

Permalink
Use full path of java executable + Refactor CI/CD to test on Mac & Ja…
Browse files Browse the repository at this point in the history
…va 21 (#81)

* Handle case where Java 21 is installed and we need 17 by using full path of java executable

Related to nvuillam/vscode-groovy-lint#217 (comment)

* Factorization

* cspell

* changelog

* Also test java 21

* Upgrade CI

* skip java 8 for Mac

* fail-fast: false

* murf

* smurf

* Add quotes only if spaces in overridden javaExecutable

* Upgrade njre & show its debugs in CI

* yamllint

* Fix

* Java 8 on Mac is not supported: Set default minimum java version to 11 on Mac

* Exclude mac + 8 from tests

* Delete circle CI config

* Fix case with spaces

* Revert "Delete circle CI config"

This reverts commit 2ac4004.

* Remove java14

* circle ci

* Arrange tests

* upgrade njre

* skip java 14 on mac tests

* fix test case
  • Loading branch information
nvuillam authored May 8, 2024
1 parent 60bd763 commit adb6999
Show file tree
Hide file tree
Showing 8 changed files with 838 additions and 280 deletions.
14 changes: 7 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ jobs:
./node_modules/.bin/nyc report --reporter text-lcov > coverage.lcov
curl -s https://codecov.io/bash | bash
# Java 14 & Node latest
java-14-node-latest:
# Java 17 & Node latest
java-17-node-latest:
docker:
- image: circleci/openjdk:14.0.2-buster-node
- image: circleci/openjdk:17.0.1-buster-node
steps:
- checkout
- run:
Expand All @@ -130,10 +130,10 @@ jobs:
name: Testing
command: npm run test:debug

# Java 14 & Node latest
java-17-node-latest:
# Java 21 & Node latest
java-21-node-latest:
docker:
- image: circleci/openjdk:17.0.1-buster-node
- image: cimg/openjdk:21.0.2-node
steps:
- checkout
- run:
Expand Down Expand Up @@ -167,6 +167,6 @@ workflows:
# - debian-jdk-11-node-12
# - debian-jdk-12-node-12 JDK12 image not working because of apt-get not found ... TODO: investigate
- no-java-node-latest
- java-14-node-latest
- java-17-node-latest
- java-21-node-latest
- windows
6 changes: 5 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
"Vas",
"Vuillamy",
"badgemarkdownfile",
"cimg",
"circleci",
"classpath",
"classpaths",
"codecov",
"customarg",
"cvfm",
"danunafig",
"distrib",
"djukxe",
"javac",
"javacaller",
Expand All @@ -56,6 +58,8 @@
"socio",
"someflag",
"someflagwithvalue",
"unhack"
"temurin",
"unhack",
"unittests"
]
}
80 changes: 80 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Test

on: [push, pull_request]

permissions: read-all

concurrency:
group: ${{ github.ref_name }}-${{ github.workflow }}
cancel-in-progress: true

jobs:
test:
strategy:
fail-fast: false
matrix:
node_version: ['18', '20']
java_version: ['8', '11', '17', '21']
java_distrib: [temurin]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- node_version: '18'
java_version: '17'
java_distrib: adopt
os: ubuntu-latest
exclude:
# excludes node 8 on macOS
- os: macos-latest
java_version: '8'
name: Test
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: ${{ matrix.java_distrib }}
java-version: ${{ matrix.java_version }}
- name: Install dependencies and link
run: npm ci
- name: Run tests
env:
DEBUG: java-caller,njre
run: npm run test

coverage:
name: Test - No Java - CodeCov
strategy:
matrix:
debian_version: [bookworm]
node_version: ['18']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
container:
image: "node:${{ matrix.node_version }}-${{ matrix.debian_version }}"
timeout-minutes: 15
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Install dependencies and link
run: npm ci
- name: Run tests
env:
DEBUG: "java-caller"
run: npm run test:coverage
- name: Build coverage report
run: ./node_modules/.bin/nyc report --reporter text-lcov > coverage.lcov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
file: coverage.lcov
flags: unittests
verbose: true
dry_run: true
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## Unreleased

## [4.0.0] 2024-05-08

- When java used has been installed by JavaCaller, use full java executable path to perform the calls
- Before 4.00: Update PATH + `java -cp /home/circleci/project/test/java/dist com.nvuillam.javacaller.JavaCallerTester`
- Since 4.0.0: Update PATH + `/home/circleci/.java-caller/jre/jdk-20.0.2+9/bin/java -cp /home/circleci/project/test/java/dist com.nvuillam.javacaller.JavaCallerTester`
- For example handles issue where Java 21 is installed and you need to run Java 17 with JavaCaller
- Refactor CI/CD
- Add additional tests in GitHub Actions
- Test in more contexts (Mac, Java 21...)
- Java 8 and 14 on Mac are not supported: Set default minimum java version to 11 on Mac

## [3.3.1] 2024-04-28

- Upgrade tar dependency to avoid CVE
Expand Down
63 changes: 39 additions & 24 deletions lib/java-caller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const semver = require("semver");

class JavaCaller {
"use strict";
minimumJavaVersion = 8;
minimumJavaVersion = os.platform() === "darwin" ? 11 : 8; // Mac starts at 11
maximumJavaVersion;
javaType;
rootPath = ".";
Expand All @@ -30,6 +30,7 @@ class JavaCaller {

javaHome;
javaBin;
javaExecutableFromNodeJavaCaller;

prevPath;
prevJavaHome;
Expand Down Expand Up @@ -59,6 +60,7 @@ class JavaCaller {
this.rootPath = opts.rootPath || this.rootPath;
this.javaCallerSupportDir = `${os.homedir() + path.sep}.java-caller`;
this.javaExecutable = opts.javaExecutable || process.env.JAVA_CALLER_JAVA_EXECUTABLE || this.javaExecutable;
this.javaExecutableFromNodeJavaCaller = null;
this.additionalJavaArgs = opts.additionalJavaArgs || this.additionalJavaArgs;
this.output = opts.output || this.output;
}
Expand All @@ -85,35 +87,35 @@ class JavaCaller {
this.commandJavaArgs = (runOptions.javaArgs || []).concat(this.additionalJavaArgs);

let javaExe = runOptions.windowless ? this.javaExecutableWindowless : this.javaExecutable;
if (javaExe.toLowerCase().includes(".exe") && !javaExe.includes(`'`)) {
if (javaExe.toLowerCase().includes(".exe") && javaExe.includes(" ") && !javaExe.includes(`'`)) {
// Java executable has been overridden by caller : use it
javaExe = `"${path.resolve(javaExe)}"`;
} else if (javaExe === "java" || javaExe === "javaw") {
// Check if matching java version is present, install and update PATH if it is not
await this.manageJavaInstall();
}

const javaExeToUse = this.javaExecutableFromNodeJavaCaller ?? javaExe;
const classPathStr = this.buildClasspathStr();

const javaArgs = this.buildArguments(classPathStr, (userArguments || []).concat(this.commandJavaArgs));
let stdout = "";
let stderr = "";
let child;
const prom = new Promise((resolve) => {
// Spawn java command line
debug(`Java command: ${javaExe} ${javaArgs.join(" ")}`);
debug(`Java command: ${javaExeToUse} ${javaArgs.join(" ")}`);
const spawnOptions = {
detached: runOptions.detached,
cwd: javaExe === "java" || javaExe === "javaw" ? runOptions.cwd : undefined,
cwd: javaExeToUse === "java" || javaExeToUse === "javaw" ? runOptions.cwd : undefined,
env: Object.assign({}, process.env),
stdio: this.output === "console" ? "inherit" : runOptions.detached ? "ignore" : "pipe",
windowsHide: true,
windowsVerbatimArguments: runOptions.windowsVerbatimArguments,
};
if (javaExe.includes(" ")) {
if (javaExeToUse.includes(" ")) {
spawnOptions.shell = true;
}
child = spawn(javaExe, javaArgs, spawnOptions);
child = spawn(javaExeToUse, javaArgs, spawnOptions);

// Gather stdout and stderr if they must be returned
if (spawnOptions.stdio === "pipe") {
Expand Down Expand Up @@ -220,6 +222,10 @@ class JavaCaller {

// Install Java if the found java version is not matching the requirements
async manageJavaInstall() {
if (this.javaExecutable !== 'java' && this.javaExecutable !== 'javaw') {
// Do not search/install java if its path is sent as argument
return;
}
if (await this.getInstallInCache()) {
return;
}
Expand All @@ -239,15 +245,15 @@ class JavaCaller {
this.javaHome = javaVersionHome;
this.javaBin = javaVersionBin;
await this.addJavaInPath();
this.setJavaExecutableFromNodeJavaCaller(this.javaBin);
return;
}

// Inform user that the installation is pending
const requiredMsg =
this.minimumJavaVersion !== this.maximumJavaVersion
? `Java ${this.javaType ? this.javaType : "jre or jdk"} between ${this.minimumJavaVersion} and ${
this.maximumJavaVersion
} is required `
? `Java ${this.javaType ? this.javaType : "jre or jdk"} between ${this.minimumJavaVersion} and ${this.maximumJavaVersion
} is required `
: `Java ${this.javaType ? this.javaType : "jre or jdk"} ${this.minimumJavaVersion} is required`;
console.log(requiredMsg);
const javaVersionToInstall = this.maximumJavaVersion || this.minimumJavaVersion;
Expand Down Expand Up @@ -288,6 +294,7 @@ class JavaCaller {
this.javaHome = java_home;
this.javaBin = java_bin;
await this.addJavaInPath();
this.setJavaExecutableFromNodeJavaCaller(this.javaBin);
return true;
}
}
Expand Down Expand Up @@ -327,26 +334,25 @@ class JavaCaller {
return await fse.readdir(javaInstallsTopDir)
.then((items) =>
items
.filter((item) => fse.statSync(path.join(javaInstallsTopDir, item)).isDirectory())
.map((folder) => {
const version = semver.coerce(folder)
return { version, folder }
})
.filter(({ version, folder }) => this.checkMatchingJavaVersion(version.major, folder))
.map(({ version, folder }) => {
const home = path.join(javaInstallsTopDir, folder);
const bin = path.join(home, this.getPlatformBinPath());
return { version, folder, home, bin }
})
.find(({ bin }) => fse.existsSync(bin))
.filter((item) => fse.statSync(path.join(javaInstallsTopDir, item)).isDirectory())
.map((folder) => {
const version = semver.coerce(folder)
return { version, folder }
})
.filter(({ version, folder }) => this.checkMatchingJavaVersion(version.major, folder))
.map(({ version, folder }) => {
const home = path.join(javaInstallsTopDir, folder);
const bin = path.join(home, this.getPlatformBinPath());
return { version, folder, home, bin }
})
.find(({ bin }) => fse.existsSync(bin))
)
.then((match) => {
if (!match) return {};
const { version, folder, home, bin } = match

debug(
`Found matching java bin: ${bin} for ${this.javaType ? this.javaType : "jre or jdk"} ${this.minimumJavaVersion}${
this.maximumJavaVersion && this.maximumJavaVersion !== this.minimumJavaVersion ? " -> " + this.maximumJavaVersion : "+"
`Found matching java bin: ${bin} for ${this.javaType ? this.javaType : "jre or jdk"} ${this.minimumJavaVersion}${this.maximumJavaVersion && this.maximumJavaVersion !== this.minimumJavaVersion ? " -> " + this.maximumJavaVersion : "+"
}`
);
this.addInCache(version.major, folder, home, bin);
Expand Down Expand Up @@ -415,6 +421,15 @@ class JavaCaller {
return binPath;
}

setJavaExecutableFromNodeJavaCaller(javaBinPath) {
this.javaExecutableFromNodeJavaCaller = path.join(
javaBinPath,
os.platform() === "win32" ? "java.exe" : "java");
if (this.javaExecutableFromNodeJavaCaller.includes(" ") && !this.javaExecutableFromNodeJavaCaller.startsWith('"')) {
this.javaExecutableFromNodeJavaCaller = `"${path.resolve(this.javaExecutableFromNodeJavaCaller)}"`
}
}

fail(reason) {
console.error(reason);
this.status = 666;
Expand Down
Loading

0 comments on commit adb6999

Please sign in to comment.