Skip to content

Commit

Permalink
Merge pull request #1486 from tgodzik/add-path-back
Browse files Browse the repository at this point in the history
improvement: Search PATH for Java to use
  • Loading branch information
tgodzik committed Apr 25, 2024
2 parents 916813c + 892e8bc commit f2e5247
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -16,6 +16,8 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: coursier/setup-action@v1
with:
jvm: temurin:17
- uses: actions/setup-node@v2
with:
node-version: "16"
Expand Down
32 changes: 32 additions & 0 deletions packages/metals-languageclient/src/__tests__/getJavaHome.test.ts
@@ -1,4 +1,14 @@
import path from "path";
import { OutputChannel } from "../interfaces/OutputChannel";

class MockOutput implements OutputChannel {
append(value: string): void {
console.log(value);
}
appendLine(value: string): void {
console.log(value);
}
}

const exampleJavaVersionString = `openjdk "17.0.1" 2021-10-19
OpenJDK Runtime Environment (build 17.0.1+12-39)
Expand Down Expand Up @@ -26,6 +36,28 @@ describe("getJavaHome", () => {
const javaHome = await require("../getJavaHome").getJavaHome("17");
expect(javaHome).toBe(JAVA_HOME);
});

// needs to run on a machine with an actual JAVA_HOME set up
it("reads from real JAVA_HOME", async () => {
process.env = { ...originalEnv };
delete process.env.PATH;
const javaHome = await require("../getJavaHome").getJavaHome(
"17",
new MockOutput()
);
expect(javaHome).toBeDefined();
});

// needs to run on a machine with an actual java on PATH set up
it("reads from real PATH", async () => {
process.env = { ...originalEnv };
delete process.env.JAVA_HOME;
const javaHome = await require("../getJavaHome").getJavaHome(
"17",
new MockOutput()
);
expect(javaHome).toBeDefined();
});
});

function mockExistsFs(javaLinks: { binPath: String }[]): void {
Expand Down
Expand Up @@ -33,7 +33,8 @@ describe("setupCoursier", () => {
});

it("should not find coursier if not present in PATH", async () => {
expect(await validateCoursier("path/fake")).toBeUndefined();
process.env = {};
expect(await validateCoursier()).toBeUndefined();
});

it("should fetch coursier correctly", async () => {
Expand Down
24 changes: 23 additions & 1 deletion packages/metals-languageclient/src/getJavaHome.ts
@@ -1,6 +1,8 @@
import path from "path";
import { spawn } from "promisify-child-process";
import { OutputChannel } from "./interfaces/OutputChannel";
import { findOnPath } from "./util";
import { realpathSync } from "fs";

export type JavaVersion = "11" | "17" | "21";

Expand All @@ -19,7 +21,7 @@ export async function getJavaHome(
outputChannel: OutputChannel
): Promise<string | undefined> {
const fromEnvValue = await fromEnv(javaVersion, outputChannel);
return fromEnvValue;
return fromEnvValue || (await fromPath(javaVersion, outputChannel));
}

const versionRegex = /\"\d\d/;
Expand Down Expand Up @@ -52,6 +54,26 @@ async function validateJavaVersion(
return false;
}

export async function fromPath(
javaVersion: JavaVersion,
outputChannel: OutputChannel
): Promise<string | undefined> {
let javaExecutable = findOnPath(["java"]);
if (javaExecutable) {
let realJavaPath = realpathSync(javaExecutable);
outputChannel.appendLine(
`Found java executable under ${javaExecutable} that resolves to ${realJavaPath}`
);
const possibleJavaHome = path.dirname(path.dirname(realJavaPath));
const isValid = await validateJavaVersion(
possibleJavaHome,
javaVersion,
outputChannel
);
if (isValid) return possibleJavaHome;
}
}

export async function fromEnv(
javaVersion: JavaVersion,
outputChannel: OutputChannel
Expand Down
37 changes: 6 additions & 31 deletions packages/metals-languageclient/src/setupCoursier.ts
Expand Up @@ -8,6 +8,7 @@ import { JavaVersion, getJavaHome } from "./getJavaHome";
import { OutputChannel } from "./interfaces/OutputChannel";
import path from "path";
import fs from "fs";
import { findOnPath } from "./util";

const coursierVersion = "v2.1.8";
// https://github.com/coursier/launchers contains only launchers with the most recent version
Expand All @@ -25,13 +26,11 @@ export async function setupCoursier(
};

const resolveCoursier = async () => {
const envPath = process.env["PATH"];
const isWindows = process.platform === "win32";
const defaultCoursier = isWindows
? path.resolve(coursierFetchPath, "cs.exe")
: path.resolve(coursierFetchPath, "cs");
const possibleCoursier: string | undefined = await validateCoursier(
envPath,
defaultCoursier
);

Expand Down Expand Up @@ -86,10 +85,8 @@ export async function setupCoursier(
}

export async function validateCoursier(
pathEnv?: string | undefined,
defaultCoursier?: string | undefined
): Promise<string | undefined> {
const isWindows = process.platform === "win32";
const validate = async (coursier: string) => {
try {
const coursierVersion = await spawn(coursier, ["version"]);
Expand All @@ -111,33 +108,11 @@ export async function validateCoursier(
}
};

if (pathEnv) {
const possibleCoursier = pathEnv
.split(path.delimiter)
.flatMap((p) => {
try {
if (fs.statSync(p).isDirectory()) {
return fs.readdirSync(p).map((sub) => path.resolve(p, sub));
} else return [p];
} catch (e) {
return [];
}
})
.find(
(p) =>
(!isWindows && p.endsWith(path.sep + "cs")) ||
(!isWindows && p.endsWith(path.sep + "coursier")) ||
(isWindows && p.endsWith(path.sep + "cs.bat")) ||
(isWindows && p.endsWith(path.sep + "cs.exe"))
);

return (
(possibleCoursier && (await validate(possibleCoursier))) ||
(await validateDefault())
);
}

return validateDefault();
const possibleCoursier = findOnPath(["cs", "coursier"]);
return (
(possibleCoursier && (await validate(possibleCoursier))) ||
(await validateDefault())
);
}

export async function fetchCoursier(
Expand Down
33 changes: 33 additions & 0 deletions packages/metals-languageclient/src/util.ts
@@ -1,10 +1,43 @@
import { TaskEither } from "fp-ts/lib/TaskEither";
import { pipe } from "fp-ts/lib/function";
import * as E from "fp-ts/lib/Either";
import path from "path";
import fs from "fs";

export function toPromise<E, A>(te: TaskEither<E, A>): Promise<A> {
return te().then(
(res) =>
new Promise((resolve, reject) => pipe(res, E.fold(reject, resolve)))
);
}

export function findOnPath(names: string[]) {
const envPath = process.env["PATH"] || process.env["Path"];
const isWindows = process.platform === "win32";

if (envPath) {
const possibleExecutable = envPath
.split(path.delimiter)
.flatMap((p) => {
try {
if (fs.statSync(p).isDirectory()) {
return fs.readdirSync(p).map((sub) => path.resolve(p, sub));
} else return [p];
} catch (e) {
return [];
}
})
.find((p) => {
if (isWindows) {
return names.some(
(name) =>
p.endsWith(path.sep + name + ".bat") ||
p.endsWith(path.sep + name + ".exe")
);
} else {
return names.some((name) => p.endsWith(path.sep + name));
}
});
return possibleExecutable;
}
}

0 comments on commit f2e5247

Please sign in to comment.