Skip to content

Commit

Permalink
Merge pull request #1385 from tgodzik/coursier-local
Browse files Browse the repository at this point in the history
feature: Run local coursier if available to avoid using bootstraped one
  • Loading branch information
tgodzik authored Apr 17, 2023
2 parents 958665d + 4fa06f1 commit e7b29b5
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 91 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", async () => {
const pathEnv = process.env["PATH"];
if (pathEnv) {
expect(await validateCoursier(pathEnv)).toBeDefined();
} else {
fail("PATH environment variable is not defined");
}
});

it("should not find coursier if not present in PATH", async () => {
expect(await validateCoursier("path/fake")).toBeUndefined();
});
});
142 changes: 104 additions & 38 deletions packages/metals-languageclient/src/fetchMetals.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,122 @@
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";

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

export function fetchMetals({
serverVersion,
serverProperties,
javaConfig: { javaPath, javaOptions, extraEnv, coursierPath },
}: FetchMetalsOptions): ChildProcessPromise {
/**
* Without the additional object, the promises will be flattened and
* we will not be able to stream the output.
*/
interface PackedChildPromise {
promise: ChildProcessPromise;
}

export async function fetchMetals(
{
serverVersion,
serverProperties,
javaConfig: { javaPath, javaOptions, extraEnv, coursierPath },
}: FetchMetalsOptions,
output: OutputChannel
): Promise<PackedChildPromise> {
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 = await 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 {
promise: spawn(coursier, coursierArgs),
};
} else {
return { promise: spawnDefault() };
}
}

export async function validateCoursier(
pathEnv: string
): Promise<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")) ||
(isWindows && p.endsWith(path.sep + "cs.exe"))
);
if (possibleCoursier) {
const coursierVersion = await spawn(possibleCoursier, ["version"]);
if (coursierVersion.code !== 0) {
return undefined;
} else {
return possibleCoursier;
}
);
}
}

export function calcServerDependency(serverVersion: string): string {
Expand Down
107 changes: 57 additions & 50 deletions packages/metals-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,62 +183,69 @@ 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(
(classpath) => {
return launchMetals(
outputChannel,
context,
classpath,
serverProperties,
javaConfig,
serverVersion
);
},
(reason) => {
if (reason instanceof Error) {
outputChannel.appendLine(
"Downloading Metals failed with the following:"
return fetchProcess
.then((childProcess) => {
return trackDownloadProgress(title, outputChannel, childProcess.promise);
})
.then(
(classpath) => {
return launchMetals(
outputChannel,
context,
classpath,
serverProperties,
javaConfig,
serverVersion
);
outputChannel.appendLine(reason.message);
}
const msg = (() => {
const proxy =
`See https://scalameta.org/metals/docs/editors/vscode/#http-proxy for instructions ` +
`if you are using an HTTP proxy.`;
if (process.env.FLATPAK_SANDBOX_DIR) {
return (
`Failed to download Metals. It seems you are running Visual Studio Code inside the ` +
`Flatpak sandbox, which is known to interfere with the download of Metals. ` +
`Please, try running Visual Studio Code without Flatpak.`
);
} else {
return (
`Failed to download Metals, make sure you have an internet connection, ` +
`the Metals version '${serverVersion}' is correct and the Java Home '${javaHome}' is valid. ` +
`You can configure the Metals version and Java Home in the settings.` +
proxy
},
(reason) => {
if (reason instanceof Error) {
outputChannel.appendLine(
"Downloading Metals failed with the following:"
);
outputChannel.appendLine(reason.message);
}
})();
outputChannel.show();
window
.showErrorMessage(msg, openSettingsAction, downloadJava)
.then((choice) => {
if (choice === openSettingsAction) {
commands.executeCommand(workbenchCommands.openSettings);
} else if (choice === downloadJava) {
showInstallJavaAction(outputChannel);
const msg = (() => {
const proxy =
`See https://scalameta.org/metals/docs/editors/vscode/#http-proxy for instructions ` +
`if you are using an HTTP proxy.`;
if (process.env.FLATPAK_SANDBOX_DIR) {
return (
`Failed to download Metals. It seems you are running Visual Studio Code inside the ` +
`Flatpak sandbox, which is known to interfere with the download of Metals. ` +
`Please, try running Visual Studio Code without Flatpak.`
);
} else {
return (
`Failed to download Metals, make sure you have an internet connection, ` +
`the Metals version '${serverVersion}' is correct and the Java Home '${javaHome}' is valid. ` +
`You can configure the Metals version and Java Home in the settings.` +
proxy
);
}
});
}
);
})();
outputChannel.show();
window
.showErrorMessage(msg, openSettingsAction, downloadJava)
.then((choice) => {
if (choice === openSettingsAction) {
commands.executeCommand(workbenchCommands.openSettings);
} else if (choice === downloadJava) {
showInstallJavaAction(outputChannel);
}
});
}
);
}

function launchMetals(
Expand Down

0 comments on commit e7b29b5

Please sign in to comment.