Skip to content

Commit 15d26da

Browse files
committed
Try to sanitize paths on Windows
1 parent 129859e commit 15d26da

File tree

4 files changed

+60
-47
lines changed

4 files changed

+60
-47
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ jobs:
8686
# We need some Ruby installed for the environment activation tests
8787
- name: Set up Ruby
8888
uses: ruby/setup-ruby@v1
89+
with:
90+
bundler-cache: true
8991

9092
- name: Download shadowenv
9193
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'

vscode/src/ruby/chruby.ts

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,41 @@ export class Chruby extends VersionManager {
7070
};
7171
}
7272

73+
// Run the activation script using the Ruby installation we found so that we can discover gem paths
74+
protected async runActivationScript(rubyExecutableUri: vscode.Uri): Promise<{
75+
defaultGems: string;
76+
gemHome: string;
77+
yjit: boolean;
78+
version: string;
79+
}> {
80+
// Typically, GEM_HOME points to $HOME/.gem/ruby/version_without_patch. For example, for Ruby 3.2.2, it would be
81+
// $HOME/.gem/ruby/3.2.0. However, chruby overrides GEM_HOME to use the patch part of the version, resulting in
82+
// $HOME/.gem/ruby/3.2.2. In our activation script, we check if a directory using the patch exists and then prefer
83+
// that over the default one.
84+
const script = [
85+
"user_dir = Gem.user_dir",
86+
"paths = Gem.path",
87+
"if paths.length > 2",
88+
" paths.delete(Gem.default_dir)",
89+
" paths.delete(Gem.user_dir)",
90+
" if paths[0]",
91+
" user_dir = paths[0] if Dir.exist?(paths[0])",
92+
" end",
93+
"end",
94+
"newer_gem_home = File.join(File.dirname(user_dir), RUBY_VERSION)",
95+
"gems = (Dir.exist?(newer_gem_home) ? newer_gem_home : user_dir)",
96+
"data = { defaultGems: Gem.default_dir, gemHome: gems, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }",
97+
"STDERR.print(JSON.dump(data))",
98+
].join(";");
99+
100+
const result = await asyncExec(
101+
`${rubyExecutableUri.fsPath} -W0 -rjson -e '${script}'`,
102+
{ cwd: this.bundleUri.fsPath },
103+
);
104+
105+
return this.parseWithErrorHandling(result.stderr);
106+
}
107+
73108
// Returns the full URI to the Ruby executable
74109
protected async findRubyUri(rubyVersion: RubyVersion): Promise<vscode.Uri> {
75110
if (/\d+\.\d+\.\d+/.exec(rubyVersion.version)) {
@@ -179,39 +214,4 @@ export class Chruby extends VersionManager {
179214

180215
throw new Error("No .ruby-version file was found");
181216
}
182-
183-
// Run the activation script using the Ruby installation we found so that we can discover gem paths
184-
private async runActivationScript(rubyExecutableUri: vscode.Uri): Promise<{
185-
defaultGems: string;
186-
gemHome: string;
187-
yjit: boolean;
188-
version: string;
189-
}> {
190-
// Typically, GEM_HOME points to $HOME/.gem/ruby/version_without_patch. For example, for Ruby 3.2.2, it would be
191-
// $HOME/.gem/ruby/3.2.0. However, chruby overrides GEM_HOME to use the patch part of the version, resulting in
192-
// $HOME/.gem/ruby/3.2.2. In our activation script, we check if a directory using the patch exists and then prefer
193-
// that over the default one.
194-
const script = [
195-
"user_dir = Gem.user_dir",
196-
"paths = Gem.path",
197-
"if paths.length > 2",
198-
" paths.delete(Gem.default_dir)",
199-
" paths.delete(Gem.user_dir)",
200-
" if paths[0]",
201-
" user_dir = paths[0] if Dir.exist?(paths[0])",
202-
" end",
203-
"end",
204-
"newer_gem_home = File.join(File.dirname(user_dir), RUBY_VERSION)",
205-
"gems = (Dir.exist?(newer_gem_home) ? newer_gem_home : user_dir)",
206-
"data = { defaultGems: Gem.default_dir, gemHome: gems, yjit: !!defined?(RubyVM::YJIT), version: RUBY_VERSION }",
207-
"STDERR.print(JSON.dump(data))",
208-
].join(";");
209-
210-
const result = await asyncExec(
211-
`${rubyExecutableUri.fsPath} -W0 -rjson -e '${script}'`,
212-
{ cwd: this.bundleUri.fsPath },
213-
);
214-
215-
return this.parseWithErrorHandling(result.stderr);
216-
}
217217
}

vscode/src/ruby/rubyInstaller.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,27 @@ export class RubyInstaller extends Chruby {
4545
Searched in ${possibleInstallationUris.map((uri) => uri.fsPath).join(", ")}`,
4646
);
4747
}
48+
49+
protected async runActivationScript(rubyExecutableUri: vscode.Uri): Promise<{
50+
defaultGems: string;
51+
gemHome: string;
52+
yjit: boolean;
53+
version: string;
54+
}> {
55+
const { defaultGems, gemHome, yjit, version } =
56+
await super.runActivationScript(rubyExecutableUri);
57+
58+
return {
59+
defaultGems: this.standardizePath(defaultGems),
60+
gemHome: this.standardizePath(gemHome),
61+
yjit,
62+
version,
63+
};
64+
}
65+
66+
// Sometimes Windows paths are prefixed by `//?/` and use forward slashes as separators. We need to both remove the
67+
// prefix and convert the path to use backslashes before we insert anything into the environment PATH.
68+
private standardizePath(path: string): string {
69+
return path.replace("//?/", "").replace(/\//g, "\\");
70+
}
4871
}

vscode/src/test/suite/client.test.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import { Telemetry, TelemetryApi, TelemetryEvent } from "../../telemetry";
2929
import Client from "../../client";
3030
import { WorkspaceChannel } from "../../workspaceChannel";
3131
import { RUBY_VERSION } from "../rubyVersion";
32-
import { asyncExec } from "../../common";
3332

3433
const [major, minor, _patch] = RUBY_VERSION.split(".");
3534

@@ -153,17 +152,6 @@ async function launchClient(workspaceUri: vscode.Uri) {
153152
await ruby.activateRuby();
154153
ruby.env.RUBY_LSP_BYPASS_TYPECHECKER = "true";
155154

156-
try {
157-
await asyncExec("gem install ruby-lsp", {
158-
env: ruby.env,
159-
cwd: workspaceUri.fsPath,
160-
});
161-
} catch (error: any) {
162-
assert.fail(
163-
`Failed to install Ruby LSP: ${error.message}\n${fakeLogger.receivedMessages}`,
164-
);
165-
}
166-
167155
const telemetry = new Telemetry(context, new FakeApi());
168156
const client = new Client(
169157
context,

0 commit comments

Comments
 (0)