Auto merge of #14019 - Veykril:ts-bin-og, r=Veykril

Substitute VSCode variables more generally
This commit is contained in:
bors 2023-01-24 13:27:19 +00:00
commit e86bac92f4
5 changed files with 87 additions and 85 deletions

View file

@ -1,6 +1,6 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import * as os from "os"; import * as os from "os";
import { Config, substituteVSCodeVariables } from "./config"; import { Config } from "./config";
import { log, isValidExecutable } from "./util"; import { log, isValidExecutable } from "./util";
import { PersistentState } from "./persistent_state"; import { PersistentState } from "./persistent_state";
import { exec } from "child_process"; import { exec } from "child_process";
@ -31,58 +31,12 @@ export async function bootstrap(
return path; return path;
} }
async function patchelf(dest: vscode.Uri): Promise<void> {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Patching rust-analyzer for NixOS",
},
async (progress, _) => {
const expression = `
{srcStr, pkgs ? import <nixpkgs> {}}:
pkgs.stdenv.mkDerivation {
name = "rust-analyzer";
src = /. + srcStr;
phases = [ "installPhase" "fixupPhase" ];
installPhase = "cp $src $out";
fixupPhase = ''
chmod 755 $out
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $out
'';
}
`;
const origFile = vscode.Uri.file(dest.fsPath + "-orig");
await vscode.workspace.fs.rename(dest, origFile, { overwrite: true });
try {
progress.report({ message: "Patching executable", increment: 20 });
await new Promise((resolve, reject) => {
const handle = exec(
`nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`,
(err, stdout, stderr) => {
if (err != null) {
reject(Error(stderr));
} else {
resolve(stdout);
}
}
);
handle.stdin?.write(expression);
handle.stdin?.end();
});
} finally {
await vscode.workspace.fs.delete(origFile);
}
}
);
}
async function getServer( async function getServer(
context: vscode.ExtensionContext, context: vscode.ExtensionContext,
config: Config, config: Config,
state: PersistentState state: PersistentState
): Promise<string | undefined> { ): Promise<string | undefined> {
const explicitPath = serverPath(config); const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
if (explicitPath) { if (explicitPath) {
if (explicitPath.startsWith("~/")) { if (explicitPath.startsWith("~/")) {
return os.homedir() + explicitPath.slice("~".length); return os.homedir() + explicitPath.slice("~".length);
@ -131,9 +85,6 @@ async function getServer(
); );
return undefined; return undefined;
} }
function serverPath(config: Config): string | null {
return process.env.__RA_LSP_SERVER_DEBUG ?? substituteVSCodeVariables(config.serverPath);
}
async function isNixOs(): Promise<boolean> { async function isNixOs(): Promise<boolean> {
try { try {
@ -146,3 +97,48 @@ async function isNixOs(): Promise<boolean> {
return false; return false;
} }
} }
async function patchelf(dest: vscode.Uri): Promise<void> {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Patching rust-analyzer for NixOS",
},
async (progress, _) => {
const expression = `
{srcStr, pkgs ? import <nixpkgs> {}}:
pkgs.stdenv.mkDerivation {
name = "rust-analyzer";
src = /. + srcStr;
phases = [ "installPhase" "fixupPhase" ];
installPhase = "cp $src $out";
fixupPhase = ''
chmod 755 $out
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $out
'';
}
`;
const origFile = vscode.Uri.file(dest.fsPath + "-orig");
await vscode.workspace.fs.rename(dest, origFile, { overwrite: true });
try {
progress.report({ message: "Patching executable", increment: 20 });
await new Promise((resolve, reject) => {
const handle = exec(
`nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`,
(err, stdout, stderr) => {
if (err != null) {
reject(Error(stderr));
} else {
resolve(stdout);
}
}
);
handle.stdin?.write(expression);
handle.stdin?.end();
});
} finally {
await vscode.workspace.fs.delete(origFile);
}
}
);
}

View file

@ -1,5 +1,6 @@
import * as path from "path"; import * as Is from "vscode-languageclient/lib/common/utils/is";
import * as os from "os"; import * as os from "os";
import * as path from "path";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { Env } from "./client"; import { Env } from "./client";
import { log } from "./util"; import { log } from "./util";
@ -47,7 +48,7 @@ export class Config {
} }
private refreshLogging() { private refreshLogging() {
log.setEnabled(this.traceExtension); log.setEnabled(this.traceExtension ?? false);
log.info("Extension version:", this.package.version); log.info("Extension version:", this.package.version);
const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function)); const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function));
@ -163,18 +164,24 @@ export class Config {
* ``` * ```
* So this getter handles this quirk by not requiring the caller to use postfix `!` * So this getter handles this quirk by not requiring the caller to use postfix `!`
*/ */
private get<T>(path: string): T { private get<T>(path: string): T | undefined {
return this.cfg.get<T>(path)!; return substituteVSCodeVariables(this.cfg.get<T>(path));
} }
get serverPath() { get serverPath() {
return this.get<null | string>("server.path") ?? this.get<null | string>("serverPath"); return this.get<null | string>("server.path") ?? this.get<null | string>("serverPath");
} }
get serverExtraEnv(): Env { get serverExtraEnv(): Env {
const extraEnv = const extraEnv =
this.get<{ [key: string]: string | number } | null>("server.extraEnv") ?? {}; this.get<{ [key: string]: string | number } | null>("server.extraEnv") ?? {};
return Object.fromEntries( return substituteVariablesInEnv(
Object.entries(extraEnv).map(([k, v]) => [k, typeof v !== "string" ? v.toString() : v]) Object.fromEntries(
Object.entries(extraEnv).map(([k, v]) => [
k,
typeof v !== "string" ? v.toString() : v,
])
)
); );
} }
get traceExtension() { get traceExtension() {
@ -216,13 +223,13 @@ export class Config {
if (sourceFileMap !== "auto") { if (sourceFileMap !== "auto") {
// "/rustc/<id>" used by suggestions only. // "/rustc/<id>" used by suggestions only.
const { ["/rustc/<id>"]: _, ...trimmed } = const { ["/rustc/<id>"]: _, ...trimmed } =
this.get<Record<string, string>>("debug.sourceFileMap"); this.get<Record<string, string>>("debug.sourceFileMap") ?? {};
sourceFileMap = trimmed; sourceFileMap = trimmed;
} }
return { return {
engine: this.get<string>("debug.engine"), engine: this.get<string>("debug.engine"),
engineSettings: this.get<object>("debug.engineSettings"), engineSettings: this.get<object>("debug.engineSettings") ?? {},
openDebugPane: this.get<boolean>("debug.openDebugPane"), openDebugPane: this.get<boolean>("debug.openDebugPane"),
sourceFileMap: sourceFileMap, sourceFileMap: sourceFileMap,
}; };
@ -247,37 +254,27 @@ export class Config {
} }
} }
const VarRegex = new RegExp(/\$\{(.+?)\}/g); export function substituteVSCodeVariables<T>(resp: T): T {
if (Is.string(resp)) {
export function substituteVSCodeVariableInString(val: string): string { return substituteVSCodeVariableInString(resp) as T;
return val.replace(VarRegex, (substring: string, varName) => { } else if (resp && Is.array<any>(resp)) {
if (typeof varName === "string") {
return computeVscodeVar(varName) || substring;
} else {
return substring;
}
});
}
export function substituteVSCodeVariables(resp: any): any {
if (typeof resp === "string") {
return substituteVSCodeVariableInString(resp);
} else if (resp && Array.isArray(resp)) {
return resp.map((val) => { return resp.map((val) => {
return substituteVSCodeVariables(val); return substituteVSCodeVariables(val);
}); }) as T;
} else if (resp && typeof resp === "object") { } else if (resp && typeof resp === "object") {
const res: { [key: string]: any } = {}; const res: { [key: string]: any } = {};
for (const key in resp) { for (const key in resp) {
const val = resp[key]; const val = resp[key];
res[key] = substituteVSCodeVariables(val); res[key] = substituteVSCodeVariables(val);
} }
return res; return res as T;
} else if (typeof resp === "function") { } else if (Is.func(resp)) {
return null; throw new Error("Unexpected function type in substitution");
} }
return resp; return resp;
} }
// FIXME: Merge this with `substituteVSCodeVariables` above
export function substituteVariablesInEnv(env: Env): Env { export function substituteVariablesInEnv(env: Env): Env {
const missingDeps = new Set<string>(); const missingDeps = new Set<string>();
// vscode uses `env:ENV_NAME` for env vars resolution, and it's easier // vscode uses `env:ENV_NAME` for env vars resolution, and it's easier
@ -355,6 +352,17 @@ export function substituteVariablesInEnv(env: Env): Env {
return resolvedEnv; return resolvedEnv;
} }
const VarRegex = new RegExp(/\$\{(.+?)\}/g);
function substituteVSCodeVariableInString(val: string): string {
return val.replace(VarRegex, (substring: string, varName) => {
if (Is.string(varName)) {
return computeVscodeVar(varName) || substring;
} else {
return substring;
}
});
}
function computeVscodeVar(varName: string): string | null { function computeVscodeVar(varName: string): string | null {
const workspaceFolder = () => { const workspaceFolder = () => {
const folders = vscode.workspace.workspaceFolders ?? []; const folders = vscode.workspace.workspaceFolders ?? [];

View file

@ -2,7 +2,7 @@ import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node"; import * as lc from "vscode-languageclient/node";
import * as ra from "./lsp_ext"; import * as ra from "./lsp_ext";
import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config"; import { Config, substituteVSCodeVariables } from "./config";
import { createClient } from "./client"; import { createClient } from "./client";
import { isRustDocument, isRustEditor, log, RustEditor } from "./util"; import { isRustDocument, isRustEditor, log, RustEditor } from "./util";
import { ServerStatusParams } from "./lsp_ext"; import { ServerStatusParams } from "./lsp_ext";
@ -152,9 +152,7 @@ export class Ctx {
throw new Error(message); throw new Error(message);
} }
); );
const newEnv = substituteVariablesInEnv( const newEnv = Object.assign({}, process.env, this.config.serverExtraEnv);
Object.assign({}, process.env, this.config.serverExtraEnv)
);
const run: lc.Executable = { const run: lc.Executable = {
command: this._serverPath, command: this._serverPath,
options: { env: newEnv }, options: { env: newEnv },

View file

@ -84,7 +84,7 @@ async function getDebugConfiguration(
debugEngine = vscode.extensions.getExtension(engineId); debugEngine = vscode.extensions.getExtension(engineId);
if (debugEngine) break; if (debugEngine) break;
} }
} else { } else if (debugOptions.engine) {
debugEngine = vscode.extensions.getExtension(debugOptions.engine); debugEngine = vscode.extensions.getExtension(debugOptions.engine);
} }

View file

@ -117,7 +117,7 @@ export function isValidExecutable(path: string): boolean {
const res = spawnSync(path, ["--version"], { encoding: "utf8" }); const res = spawnSync(path, ["--version"], { encoding: "utf8" });
const printOutput = res.error && (res.error as any).code !== "ENOENT" ? log.warn : log.debug; const printOutput = res.error ? log.warn : log.info;
printOutput(path, "--version:", res); printOutput(path, "--version:", res);
return res.status === 0; return res.status === 0;