Skip to content

Commit

Permalink
feature: Run local coursier if available to avoid using bootstraped one
Browse files Browse the repository at this point in the history
This might help out in corporate environements since it will require installing coursier only once and Metals will be able to use it.

In later steps I will try to automatically download coursier or native image via metals if not available.
  • Loading branch information
tgodzik committed Apr 11, 2023
1 parent 958665d commit 509aba3
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 46 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- uses: coursier/setup-action@v1
- uses: actions/setup-node@v2
with:
node-version: "16"
Expand Down
7 changes: 4 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"stopOnEntry": false,
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/packages/metals-vscode"
],
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/out/**/*.js"],
"outFiles": ["${workspaceRoot}/packages/metals-vscode/out/**/*.js"],
"preLaunchTask": "npm: watch"
}
]
Expand Down
14 changes: 14 additions & 0 deletions packages/metals-languageclient/src/__tests__/fetchMetals.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { calcServerDependency } from "../fetchMetals";
import { validateCoursier } from "../fetchMetals";

describe("fetchMetals", () => {
describe("calcServerDependency", () => {
Expand Down Expand Up @@ -34,4 +35,17 @@ describe("fetchMetals", () => {
expect(calcServerDependency(customVersion)).toBe(customVersion);
});
});

it("should find coursier in PATH", () => {
const pathEnv = process.env["PATH"];
if (pathEnv) {
expect(validateCoursier(pathEnv)).toBeDefined();
} else {
fail("PATH environment variable is not defined");
}
});

it("should not find coursier if not present in PATH", () => {
expect(validateCoursier("path/fake")).toBeUndefined();
});
});
133 changes: 95 additions & 38 deletions packages/metals-languageclient/src/fetchMetals.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,113 @@
import * as semver from "semver";
import path from "path";
import fs from "fs";
import { ChildProcessPromise, spawn } from "promisify-child-process";
import { JavaConfig } from "./getJavaConfig";
import { OutputChannel } from "./interfaces/OutputChannel";
import { spawnSync } from "child_process";

interface FetchMetalsOptions {
serverVersion: string;
serverProperties: string[];
javaConfig: JavaConfig;
}

export function fetchMetals({
serverVersion,
serverProperties,
javaConfig: { javaPath, javaOptions, extraEnv, coursierPath },
}: FetchMetalsOptions): ChildProcessPromise {
export function fetchMetals(
{
serverVersion,
serverProperties,
javaConfig: { javaPath, javaOptions, extraEnv, coursierPath },
}: FetchMetalsOptions,
output: OutputChannel
): ChildProcessPromise {
const fetchProperties = serverProperties.filter(
(p) => !p.startsWith("-agentlib")
);

const serverDependency = calcServerDependency(serverVersion);
return spawn(
javaPath,
[
...javaOptions,
...fetchProperties,
"-Dfile.encoding=UTF-8",
"-jar",
coursierPath,
"fetch",
"-p",
"--ttl",
// Use infinite ttl to avoid redunant "Checking..." logs when using SNAPSHOT
// versions. Metals SNAPSHOT releases are effectively immutable since we
// never publish the same version twice.
"Inf",
serverDependency,
"-r",
"bintray:scalacenter/releases",
"-r",
"sonatype:public",
"-r",
"sonatype:snapshots",
"-p",
],
{
env: {
COURSIER_NO_TERM: "true",
...extraEnv,
...process.env,
},
stdio: ["ignore"], // Due to Issue: #219

const coursierArgs = [
"fetch",
"-p",
"--ttl",
// Use infinite ttl to avoid redunant "Checking..." logs when using SNAPSHOT
// versions. Metals SNAPSHOT releases are effectively immutable since we
// never publish the same version twice.
"Inf",
serverDependency,
"-r",
"bintray:scalacenter/releases",
"-r",
"sonatype:public",
"-r",
"sonatype:snapshots",
"-p",
];

const path = process.env["PATH"];
let possibleCoursier: string | undefined;
if (path) {
possibleCoursier = validateCoursier(path);
}

function spawnDefault(): ChildProcessPromise {
return spawn(
javaPath,
[
...javaOptions,
...fetchProperties,
"-Dfile.encoding=UTF-8",
"-jar",
coursierPath,
].concat(coursierArgs),
{
env: {
COURSIER_NO_TERM: "true",
...extraEnv,
...process.env,
},
}
);
}

if (possibleCoursier) {
const coursier: string = possibleCoursier;
output.appendLine(`Using coursier located at ${coursier}`);
return spawn(coursier, coursierArgs);
} else {
return spawnDefault();
}
}

export function validateCoursier(pathEnv: string): string | undefined {
const isWindows = process.platform === "win32";
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") || p.endsWith(path.sep + "cs.exe")))
);
if (possibleCoursier) {
const coursierVersion = spawnSync(possibleCoursier, ["version"]);
if (coursierVersion.status !== 0) {
console.log(coursierVersion);
console.log("Coursier found at " + possibleCoursier + " is invalid.");
return undefined;
} else {
return possibleCoursier;
}
);
}
}

export function calcServerDependency(serverVersion: string): string {
Expand Down
13 changes: 8 additions & 5 deletions packages/metals-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,14 @@ function fetchAndLaunchMetals(
extensionPath: context.extensionPath,
});

const fetchProcess = fetchMetals({
serverVersion,
serverProperties,
javaConfig,
});
const fetchProcess = fetchMetals(
{
serverVersion,
serverProperties,
javaConfig,
},
outputChannel
);

const title = `Downloading Metals v${serverVersion}`;
return trackDownloadProgress(title, outputChannel, fetchProcess).then(
Expand Down

0 comments on commit 509aba3

Please sign in to comment.