Skip to content

Commit

Permalink
Use separator for activation script result
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Sep 13, 2024
1 parent c142481 commit eadcfec
Show file tree
Hide file tree
Showing 16 changed files with 110 additions and 122 deletions.
9 changes: 2 additions & 7 deletions vscode/src/ruby/asdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,10 @@ import { VersionManager, ActivationResult } from "./versionManager";
export class Asdf extends VersionManager {
async activate(): Promise<ActivationResult> {
const asdfUri = await this.findAsdfInstallation();
const activationScript =
"STDERR.print({env: ENV.to_h,yjit:!!defined?(RubyVM::YJIT),version:RUBY_VERSION}.to_json)";

const result = await this.runScript(
`. ${asdfUri.fsPath} && asdf exec ruby -W0 -rjson -e '${activationScript}'`,
const parsedResult = await this.runEnvActivationScript(
`. ${asdfUri.fsPath} && asdf exec ruby`,
);

const parsedResult = this.parseWithErrorHandling(result.stderr);

return {
env: { ...process.env, ...parsedResult.env },
yjit: parsedResult.yjit,
Expand Down
8 changes: 5 additions & 3 deletions vscode/src/ruby/chruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import * as vscode from "vscode";

import { WorkspaceChannel } from "../workspaceChannel";

import { ActivationResult, VersionManager } from "./versionManager";
import {
ActivationResult,
VersionManager,
ACTIVATION_SEPARATOR,
} from "./versionManager";

interface RubyVersion {
engine?: string;
version: string;
}

const ACTIVATION_SEPARATOR = "ACTIVATION_SEPARATOR";

// A tool to change the current Ruby version
// Learn more: https://github.com/postmodern/chruby
export class Chruby extends VersionManager {
Expand Down
8 changes: 2 additions & 6 deletions vscode/src/ruby/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ import { VersionManager, ActivationResult } from "./versionManager";
// GEM_HOME and GEM_PATH as needed to find the correct Ruby runtime.
export class Custom extends VersionManager {
async activate(): Promise<ActivationResult> {
const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";

const result = await this.runScript(
`${this.customCommand()} && ruby -W0 -rjson -e '${activationScript}'`,
const parsedResult = await this.runEnvActivationScript(
`${this.customCommand()} && ruby`,
);

const parsedResult = this.parseWithErrorHandling(result.stderr);
return {
env: { ...process.env, ...parsedResult.env },
yjit: parsedResult.yjit,
Expand Down
8 changes: 2 additions & 6 deletions vscode/src/ruby/mise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@ export class Mise extends VersionManager {
async activate(): Promise<ActivationResult> {
const miseUri = await this.findMiseUri();

const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";

// The exec command in Mise is called `x`
const result = await this.runScript(
`${miseUri.fsPath} x -- ruby -W0 -rjson -e '${activationScript}'`,
const parsedResult = await this.runEnvActivationScript(
`${miseUri.fsPath} x -- ruby`,
);

const parsedResult = this.parseWithErrorHandling(result.stderr);
return {
env: { ...process.env, ...parsedResult.env },
yjit: parsedResult.yjit,
Expand Down
8 changes: 1 addition & 7 deletions vscode/src/ruby/none.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,8 @@ export class None extends VersionManager {
}

async activate(): Promise<ActivationResult> {
const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";
const parsedResult = await this.runEnvActivationScript(this.rubyPath);

const result = await this.runScript(
`${this.rubyPath} -W0 -rjson -e '${activationScript}'`,
);

const parsedResult = this.parseWithErrorHandling(result.stderr);
return {
env: { ...process.env, ...parsedResult.env },
yjit: parsedResult.yjit,
Expand Down
9 changes: 1 addition & 8 deletions vscode/src/ruby/rbenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@ import { VersionManager, ActivationResult } from "./versionManager";
// Learn more: https://github.com/rbenv/rbenv
export class Rbenv extends VersionManager {
async activate(): Promise<ActivationResult> {
const activationScript =
"STDERR.print({env: ENV.to_h,yjit:!!defined?(RubyVM::YJIT),version:RUBY_VERSION}.to_json)";

const result = await this.runScript(
`rbenv exec ruby -W0 -rjson -e '${activationScript}'`,
);

const parsedResult = this.parseWithErrorHandling(result.stderr);
const parsedResult = await this.runEnvActivationScript("rbenv exec ruby");

return {
env: { ...process.env, ...parsedResult.env },
Expand Down
8 changes: 2 additions & 6 deletions vscode/src/ruby/rvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ import { ActivationResult, VersionManager } from "./versionManager";
// - https://rvm.io
export class Rvm extends VersionManager {
async activate(): Promise<ActivationResult> {
const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";

const installationPath = await this.findRvmInstallation();
const result = await this.runScript(
`${installationPath.fsPath} -W0 -rjson -e '${activationScript}'`,
const parsedResult = await this.runEnvActivationScript(
installationPath.fsPath,
);

const parsedResult = this.parseWithErrorHandling(result.stderr);
const activatedKeys = Object.entries(parsedResult.env)
.map(([key, value]) => `${key}=${value}`)
.join(" ");
Expand Down
8 changes: 2 additions & 6 deletions vscode/src/ruby/shadowenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,11 @@ export class Shadowenv extends VersionManager {
);
}

const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";

try {
const result = await this.runScript(
`shadowenv exec -- ruby -W0 -rjson -e '${activationScript}'`,
const parsedResult = await this.runEnvActivationScript(
"shadowenv exec -- ruby",
);

const parsedResult = this.parseWithErrorHandling(result.stderr);
return {
env: { ...process.env, ...parsedResult.env },
yjit: parsedResult.yjit,
Expand Down
21 changes: 21 additions & 0 deletions vscode/src/ruby/versionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ export interface ActivationResult {
version: string;
}

export const ACTIVATION_SEPARATOR = "RUBY_LSP_ACTIVATION_SEPARATOR";

export abstract class VersionManager {
public activationScript = [
`STDERR.print("${ACTIVATION_SEPARATOR}" + `,
"{ env: ENV.to_h, yjit: !!defined?(RubyVM:: YJIT), version: RUBY_VERSION }.to_json + ",
`"${ACTIVATION_SEPARATOR}")`,
].join("");

protected readonly outputChannel: WorkspaceChannel;
protected readonly workspaceFolder: vscode.WorkspaceFolder;
protected readonly bundleUri: vscode.Uri;

private readonly customBundleGemfile?: string;

constructor(
Expand Down Expand Up @@ -45,6 +54,18 @@ export abstract class VersionManager {
// language server
abstract activate(): Promise<ActivationResult>;

protected async runEnvActivationScript(activatedRuby: string) {
const result = await this.runScript(
`${activatedRuby} -W0 -rjson -e '${this.activationScript}'`,
);

const activationContent = new RegExp(
`${ACTIVATION_SEPARATOR}(.*)${ACTIVATION_SEPARATOR}`,
).exec(result.stderr);

return this.parseWithErrorHandling(activationContent![1]);
}

protected parseWithErrorHandling(json: string) {
try {
return JSON.parse(json);
Expand Down
31 changes: 15 additions & 16 deletions vscode/src/test/suite/ruby/asdf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import sinon from "sinon";
import { Asdf } from "../../../ruby/asdf";
import { WorkspaceChannel } from "../../../workspaceChannel";
import * as common from "../../../common";
import { ACTIVATION_SEPARATOR } from "../../../ruby/versionManager";

suite("Asdf", () => {
if (os.platform() === "win32") {
Expand All @@ -26,16 +27,15 @@ suite("Asdf", () => {
};
const outputChannel = new WorkspaceChannel("fake", common.LOG_CHANNEL);
const asdf = new Asdf(workspaceFolder, outputChannel);
const activationScript =
"STDERR.print({env: ENV.to_h,yjit:!!defined?(RubyVM::YJIT),version:RUBY_VERSION}.to_json)";
const envStub = {
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
};

const execStub = sinon.stub(common, "asyncExec").resolves({
stdout: "",
stderr: JSON.stringify({
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
}),
stderr: `${ACTIVATION_SEPARATOR}${JSON.stringify(envStub)}${ACTIVATION_SEPARATOR}`,
});

const findInstallationStub = sinon
Expand All @@ -47,7 +47,7 @@ suite("Asdf", () => {

assert.ok(
execStub.calledOnceWithExactly(
`. ${os.homedir()}/.asdf/asdf.sh && asdf exec ruby -W0 -rjson -e '${activationScript}'`,
`. ${os.homedir()}/.asdf/asdf.sh && asdf exec ruby -W0 -rjson -e '${asdf.activationScript}'`,
{
cwd: workspacePath,
shell: "/bin/bash",
Expand Down Expand Up @@ -76,16 +76,15 @@ suite("Asdf", () => {
};
const outputChannel = new WorkspaceChannel("fake", common.LOG_CHANNEL);
const asdf = new Asdf(workspaceFolder, outputChannel);
const activationScript =
"STDERR.print({env: ENV.to_h,yjit:!!defined?(RubyVM::YJIT),version:RUBY_VERSION}.to_json)";
const envStub = {
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
};

const execStub = sinon.stub(common, "asyncExec").resolves({
stdout: "",
stderr: JSON.stringify({
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
}),
stderr: `${ACTIVATION_SEPARATOR}${JSON.stringify(envStub)}${ACTIVATION_SEPARATOR}`,
});

const findInstallationStub = sinon
Expand All @@ -99,7 +98,7 @@ suite("Asdf", () => {

assert.ok(
execStub.calledOnceWithExactly(
`. ${os.homedir()}/.asdf/asdf.fish && asdf exec ruby -W0 -rjson -e '${activationScript}'`,
`. ${os.homedir()}/.asdf/asdf.fish && asdf exec ruby -W0 -rjson -e '${asdf.activationScript}'`,
{
cwd: workspacePath,
shell: "/opt/homebrew/bin/fish",
Expand Down
17 changes: 9 additions & 8 deletions vscode/src/test/suite/ruby/custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import sinon from "sinon";
import { Custom } from "../../../ruby/custom";
import { WorkspaceChannel } from "../../../workspaceChannel";
import * as common from "../../../common";
import { ACTIVATION_SEPARATOR } from "../../../ruby/versionManager";

suite("Custom", () => {
test("Invokes custom script and then Ruby", async () => {
Expand All @@ -24,25 +25,25 @@ suite("Custom", () => {
const outputChannel = new WorkspaceChannel("fake", common.LOG_CHANNEL);
const custom = new Custom(workspaceFolder, outputChannel);

const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";
const envStub = {
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
};

const execStub = sinon.stub(common, "asyncExec").resolves({
stdout: "",
stderr: JSON.stringify({
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
}),
stderr: `${ACTIVATION_SEPARATOR}${JSON.stringify(envStub)}${ACTIVATION_SEPARATOR}`,
});

const commandStub = sinon
.stub(custom, "customCommand")
.returns("my_version_manager activate_env");
const { env, version, yjit } = await custom.activate();

assert.ok(
execStub.calledOnceWithExactly(
`my_version_manager activate_env && ruby -W0 -rjson -e '${activationScript}'`,
`my_version_manager activate_env && ruby -W0 -rjson -e '${custom.activationScript}'`,
{
cwd: uri.fsPath,
shell: vscode.env.shell,
Expand Down
32 changes: 16 additions & 16 deletions vscode/src/test/suite/ruby/mise.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import sinon from "sinon";
import { Mise } from "../../../ruby/mise";
import { WorkspaceChannel } from "../../../workspaceChannel";
import * as common from "../../../common";
import { ACTIVATION_SEPARATOR } from "../../../ruby/versionManager";

suite("Mise", () => {
if (os.platform() === "win32") {
Expand All @@ -28,16 +29,15 @@ suite("Mise", () => {
const outputChannel = new WorkspaceChannel("fake", common.LOG_CHANNEL);
const mise = new Mise(workspaceFolder, outputChannel);

const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";
const envStub = {
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
};

const execStub = sinon.stub(common, "asyncExec").resolves({
stdout: "",
stderr: JSON.stringify({
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
}),
stderr: `${ACTIVATION_SEPARATOR}${JSON.stringify(envStub)}${ACTIVATION_SEPARATOR}`,
});
const findStub = sinon
.stub(mise, "findMiseUri")
Expand All @@ -54,7 +54,7 @@ suite("Mise", () => {

assert.ok(
execStub.calledOnceWithExactly(
`${os.homedir()}/.local/bin/mise x -- ruby -W0 -rjson -e '${activationScript}'`,
`${os.homedir()}/.local/bin/mise x -- ruby -W0 -rjson -e '${mise.activationScript}'`,
{
cwd: workspacePath,
shell: vscode.env.shell,
Expand Down Expand Up @@ -84,17 +84,17 @@ suite("Mise", () => {
const outputChannel = new WorkspaceChannel("fake", common.LOG_CHANNEL);
const mise = new Mise(workspaceFolder, outputChannel);

const activationScript =
"STDERR.print({ env: ENV.to_h, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }.to_json)";
const envStub = {
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
};

const execStub = sinon.stub(common, "asyncExec").resolves({
stdout: "",
stderr: JSON.stringify({
env: { ANY: "true" },
yjit: true,
version: "3.0.0",
}),
stderr: `${ACTIVATION_SEPARATOR}${JSON.stringify(envStub)}${ACTIVATION_SEPARATOR}`,
});

const misePath = path.join(workspacePath, "mise");
fs.writeFileSync(misePath, "fakeMiseBinary");

Expand All @@ -113,7 +113,7 @@ suite("Mise", () => {

assert.ok(
execStub.calledOnceWithExactly(
`${misePath} x -- ruby -W0 -rjson -e '${activationScript}'`,
`${misePath} x -- ruby -W0 -rjson -e '${mise.activationScript}'`,
{
cwd: workspacePath,
shell: vscode.env.shell,
Expand Down
Loading

0 comments on commit eadcfec

Please sign in to comment.