Skip to content

Commit 56d77b9

Browse files
committed
Auto merge of rust-lang#17275 - roife:fix-issue-17012, r=Veykril
Fix inconsistent cwd of `run` and `debug` command in client Fix rust-lang#17012. Also related to rust-lang#13022 and rust-lang#15993. When the `kind` of runnable is `bin`, Cargo would use the workspace root as the cwd for the `run` command; otherwise, Cargo defaults to the package root as the cwd for `run`. Initially, r-a assumed the workspace root as the cwd for all runnables in `debug` command, which led to issue rust-lang#13022. In this case, during unit testing, the `run` command would use the package root while `debug` would use the workspace root, causing inconsistency. PR rust-lang#15993 addressed this problem by using the package root as the cwd for `debug` command. However, it also resulted in an inconsistency: when executing the `run` command within the main fn of a package (whose target is `bin`), Cargo would use the workspace root, whereas `debug` would use the package root, leading to issue rust-lang#17012. The preferable approach is to determine the cwd based on the runnable's type. To resolve this, this PR introduces a new `cwd` field within `CargoRunnable`, allowing r-a to decide the appropriate cwd depending on the specific kind of the runnable.
2 parents 6259991 + 3c7a13d commit 56d77b9

File tree

8 files changed

+122
-33
lines changed

8 files changed

+122
-33
lines changed

src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs

+7
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,11 @@ pub(crate) fn handle_runnables(
860860
if cmd == "run" && spec.target_kind != TargetKind::Bin {
861861
continue;
862862
}
863+
let cwd = if cmd != "test" || spec.target_kind == TargetKind::Bin {
864+
spec.workspace_root.clone()
865+
} else {
866+
spec.cargo_toml.parent().to_path_buf()
867+
};
863868
let mut cargo_args =
864869
vec![cmd.to_owned(), "--package".to_owned(), spec.package.clone()];
865870
let all_targets = cmd != "run" && !is_crate_no_std;
@@ -876,6 +881,7 @@ pub(crate) fn handle_runnables(
876881
kind: lsp_ext::RunnableKind::Cargo,
877882
args: lsp_ext::CargoRunnable {
878883
workspace_root: Some(spec.workspace_root.clone().into()),
884+
cwd: Some(cwd.into()),
879885
override_cargo: config.override_cargo.clone(),
880886
cargo_args,
881887
cargo_extra_args: config.cargo_extra_args.clone(),
@@ -893,6 +899,7 @@ pub(crate) fn handle_runnables(
893899
kind: lsp_ext::RunnableKind::Cargo,
894900
args: lsp_ext::CargoRunnable {
895901
workspace_root: None,
902+
cwd: None,
896903
override_cargo: config.override_cargo,
897904
cargo_args: vec!["check".to_owned(), "--workspace".to_owned()],
898905
cargo_extra_args: config.cargo_extra_args,

src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs

+2
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ pub struct CargoRunnable {
441441
pub override_cargo: Option<String>,
442442
#[serde(skip_serializing_if = "Option::is_none")]
443443
pub workspace_root: Option<PathBuf>,
444+
#[serde(skip_serializing_if = "Option::is_none")]
445+
pub cwd: Option<PathBuf>,
444446
// command, --package and --lib stuff
445447
pub cargo_args: Vec<String>,
446448
// user-specified additional cargo args, like `--release`.

src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,10 @@ pub(crate) fn runnable(
13601360
let config = snap.config.runnables();
13611361
let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
13621362
let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
1363+
let cwd = match runnable.kind {
1364+
ide::RunnableKind::Bin { .. } => workspace_root.clone().map(|it| it.into()),
1365+
_ => spec.as_ref().map(|it| it.cargo_toml.parent().into()),
1366+
};
13631367
let target = spec.as_ref().map(|s| s.target.clone());
13641368
let (cargo_args, executable_args) =
13651369
CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg);
@@ -1372,6 +1376,7 @@ pub(crate) fn runnable(
13721376
kind: lsp_ext::RunnableKind::Cargo,
13731377
args: lsp_ext::CargoRunnable {
13741378
workspace_root: workspace_root.map(|it| it.into()),
1379+
cwd,
13751380
override_cargo: config.override_cargo,
13761381
cargo_args,
13771382
cargo_extra_args: config.cargo_extra_args,

src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs

+93
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ fn main() {}
260260
"executableArgs": ["test_eggs", "--exact", "--show-output"],
261261
"cargoExtraArgs": [],
262262
"overrideCargo": null,
263+
"cwd": server.path().join("foo"),
263264
"workspaceRoot": server.path().join("foo")
264265
},
265266
"kind": "cargo",
@@ -279,6 +280,7 @@ fn main() {}
279280
{
280281
"args": {
281282
"overrideCargo": null,
283+
"cwd": server.path().join("foo"),
282284
"workspaceRoot": server.path().join("foo"),
283285
"cargoArgs": [
284286
"test",
@@ -325,6 +327,7 @@ fn main() {}
325327
"executableArgs": [],
326328
"cargoExtraArgs": [],
327329
"overrideCargo": null,
330+
"cwd": server.path().join("foo"),
328331
"workspaceRoot": server.path().join("foo")
329332
},
330333
"kind": "cargo",
@@ -336,6 +339,7 @@ fn main() {}
336339
"executableArgs": [],
337340
"cargoExtraArgs": [],
338341
"overrideCargo": null,
342+
"cwd": server.path().join("foo"),
339343
"workspaceRoot": server.path().join("foo")
340344
},
341345
"kind": "cargo",
@@ -415,6 +419,7 @@ mod tests {
415419
"args": {
416420
"overrideCargo": null,
417421
"workspaceRoot": server.path().join(runnable),
422+
"cwd": server.path().join(runnable),
418423
"cargoArgs": [
419424
"test",
420425
"--package",
@@ -432,6 +437,94 @@ mod tests {
432437
}
433438
}
434439

440+
// The main fn in packages should be run from the workspace root
441+
#[test]
442+
fn test_runnables_cwd() {
443+
if skip_slow_tests() {
444+
return;
445+
}
446+
447+
let server = Project::with_fixture(
448+
r#"
449+
//- /foo/Cargo.toml
450+
[workspace]
451+
members = ["mainpkg", "otherpkg"]
452+
453+
//- /foo/mainpkg/Cargo.toml
454+
[package]
455+
name = "mainpkg"
456+
version = "0.1.0"
457+
458+
//- /foo/mainpkg/src/main.rs
459+
fn main() {}
460+
461+
//- /foo/otherpkg/Cargo.toml
462+
[package]
463+
name = "otherpkg"
464+
version = "0.1.0"
465+
466+
//- /foo/otherpkg/src/lib.rs
467+
#[test]
468+
fn otherpkg() {}
469+
"#,
470+
)
471+
.root("foo")
472+
.server()
473+
.wait_until_workspace_is_loaded();
474+
475+
server.request::<Runnables>(
476+
RunnablesParams { text_document: server.doc_id("foo/mainpkg/src/main.rs"), position: None },
477+
json!([
478+
"{...}",
479+
{
480+
"label": "cargo test -p mainpkg --all-targets",
481+
"kind": "cargo",
482+
"args": {
483+
"overrideCargo": null,
484+
"workspaceRoot": server.path().join("foo"),
485+
"cwd": server.path().join("foo"),
486+
"cargoArgs": [
487+
"test",
488+
"--package",
489+
"mainpkg",
490+
"--all-targets"
491+
],
492+
"cargoExtraArgs": [],
493+
"executableArgs": []
494+
},
495+
},
496+
"{...}",
497+
"{...}"
498+
]),
499+
);
500+
501+
server.request::<Runnables>(
502+
RunnablesParams { text_document: server.doc_id("foo/otherpkg/src/lib.rs"), position: None },
503+
json!([
504+
"{...}",
505+
{
506+
"label": "cargo test -p otherpkg --all-targets",
507+
"kind": "cargo",
508+
"args": {
509+
"overrideCargo": null,
510+
"workspaceRoot": server.path().join("foo"),
511+
"cwd": server.path().join("foo").join("otherpkg"),
512+
"cargoArgs": [
513+
"test",
514+
"--package",
515+
"otherpkg",
516+
"--all-targets"
517+
],
518+
"cargoExtraArgs": [],
519+
"executableArgs": []
520+
},
521+
},
522+
"{...}",
523+
"{...}"
524+
]),
525+
);
526+
}
527+
435528
#[test]
436529
fn test_format_document() {
437530
if skip_slow_tests() {

src/tools/rust-analyzer/docs/dev/lsp-extensions.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp/ext.rs hash: 422dcc22c2e56166
2+
lsp/ext.rs hash: 1babf76a3c2cef3b
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:
@@ -377,6 +377,7 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
377377
```typescript
378378
{
379379
workspaceRoot?: string;
380+
cwd?: string;
380381
cargoArgs: string[];
381382
cargoExtraArgs: string[];
382383
executableArgs: string[];

src/tools/rust-analyzer/editors/code/src/debug.ts

+10-20
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as vscode from "vscode";
33
import * as path from "path";
44
import type * as ra from "./lsp_ext";
55

6-
import { Cargo, type ExecutableInfo, getRustcId, getSysroot } from "./toolchain";
6+
import { Cargo, getRustcId, getSysroot } from "./toolchain";
77
import type { Ctx } from "./ctx";
88
import { prepareEnv } from "./run";
99
import { unwrapUndefinable } from "./undefinable";
@@ -12,7 +12,6 @@ const debugOutput = vscode.window.createOutputChannel("Debug");
1212
type DebugConfigProvider = (
1313
config: ra.Runnable,
1414
executable: string,
15-
cargoWorkspace: string,
1615
env: Record<string, string>,
1716
sourceFileMap?: Record<string, string>,
1817
) => vscode.DebugConfiguration;
@@ -134,7 +133,7 @@ async function getDebugConfiguration(
134133
}
135134

136135
const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv);
137-
const { executable, workspace: cargoWorkspace } = await getDebugExecutableInfo(runnable, env);
136+
const executable = await getDebugExecutable(runnable, env);
138137
let sourceFileMap = debugOptions.sourceFileMap;
139138
if (sourceFileMap === "auto") {
140139
// let's try to use the default toolchain
@@ -148,13 +147,7 @@ async function getDebugConfiguration(
148147
}
149148

150149
const provider = unwrapUndefinable(knownEngines[debugEngine.id]);
151-
const debugConfig = provider(
152-
runnable,
153-
simplifyPath(executable),
154-
cargoWorkspace,
155-
env,
156-
sourceFileMap,
157-
);
150+
const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap);
158151
if (debugConfig.type in debugOptions.engineSettings) {
159152
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
160153
for (var key in settingsMap) {
@@ -176,21 +169,20 @@ async function getDebugConfiguration(
176169
return debugConfig;
177170
}
178171

179-
async function getDebugExecutableInfo(
172+
async function getDebugExecutable(
180173
runnable: ra.Runnable,
181174
env: Record<string, string>,
182-
): Promise<ExecutableInfo> {
175+
): Promise<string> {
183176
const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env);
184-
const executableInfo = await cargo.executableInfoFromArgs(runnable.args.cargoArgs);
177+
const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
185178

186179
// if we are here, there were no compilation errors.
187-
return executableInfo;
180+
return executable;
188181
}
189182

190183
function getCCppDebugConfig(
191184
runnable: ra.Runnable,
192185
executable: string,
193-
cargoWorkspace: string,
194186
env: Record<string, string>,
195187
sourceFileMap?: Record<string, string>,
196188
): vscode.DebugConfiguration {
@@ -200,7 +192,7 @@ function getCCppDebugConfig(
200192
name: runnable.label,
201193
program: executable,
202194
args: runnable.args.executableArgs,
203-
cwd: cargoWorkspace || runnable.args.workspaceRoot,
195+
cwd: runnable.args.cwd || runnable.args.workspaceRoot || ".",
204196
sourceFileMap,
205197
env,
206198
// See https://github.com/rust-lang/rust-analyzer/issues/16901#issuecomment-2024486941
@@ -213,7 +205,6 @@ function getCCppDebugConfig(
213205
function getCodeLldbDebugConfig(
214206
runnable: ra.Runnable,
215207
executable: string,
216-
cargoWorkspace: string,
217208
env: Record<string, string>,
218209
sourceFileMap?: Record<string, string>,
219210
): vscode.DebugConfiguration {
@@ -223,7 +214,7 @@ function getCodeLldbDebugConfig(
223214
name: runnable.label,
224215
program: executable,
225216
args: runnable.args.executableArgs,
226-
cwd: cargoWorkspace || runnable.args.workspaceRoot,
217+
cwd: runnable.args.cwd || runnable.args.workspaceRoot || ".",
227218
sourceMap: sourceFileMap,
228219
sourceLanguages: ["rust"],
229220
env,
@@ -233,7 +224,6 @@ function getCodeLldbDebugConfig(
233224
function getNativeDebugConfig(
234225
runnable: ra.Runnable,
235226
executable: string,
236-
cargoWorkspace: string,
237227
env: Record<string, string>,
238228
_sourceFileMap?: Record<string, string>,
239229
): vscode.DebugConfiguration {
@@ -244,7 +234,7 @@ function getNativeDebugConfig(
244234
target: executable,
245235
// See https://github.com/WebFreak001/code-debug/issues/359
246236
arguments: quote(runnable.args.executableArgs),
247-
cwd: cargoWorkspace || runnable.args.workspaceRoot,
237+
cwd: runnable.args.cwd || runnable.args.workspaceRoot || ".",
248238
env,
249239
valuesFormatting: "prettyPrinters",
250240
};

src/tools/rust-analyzer/editors/code/src/lsp_ext.ts

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ export type Runnable = {
226226
kind: "cargo";
227227
args: {
228228
workspaceRoot?: string;
229+
cwd?: string;
229230
cargoArgs: string[];
230231
cargoExtraArgs: string[];
231232
executableArgs: string[];

src/tools/rust-analyzer/editors/code/src/toolchain.ts

+2-12
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,11 @@ import { unwrapUndefinable } from "./undefinable";
99

1010
interface CompilationArtifact {
1111
fileName: string;
12-
workspace: string;
1312
name: string;
1413
kind: string;
1514
isTest: boolean;
1615
}
1716

18-
export interface ExecutableInfo {
19-
executable: string;
20-
workspace: string;
21-
}
22-
2317
export interface ArtifactSpec {
2418
cargoArgs: string[];
2519
filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[];
@@ -74,7 +68,6 @@ export class Cargo {
7468
artifacts.push({
7569
fileName: message.executable,
7670
name: message.target.name,
77-
workspace: path.dirname(message.manifest_path),
7871
kind: message.target.kind[0],
7972
isTest: message.profile.test,
8073
});
@@ -93,7 +86,7 @@ export class Cargo {
9386
return spec.filter?.(artifacts) ?? artifacts;
9487
}
9588

96-
async executableInfoFromArgs(args: readonly string[]): Promise<ExecutableInfo> {
89+
async executableFromArgs(args: readonly string[]): Promise<string> {
9790
const artifacts = await this.getArtifacts(Cargo.artifactSpec(args));
9891

9992
if (artifacts.length === 0) {
@@ -103,10 +96,7 @@ export class Cargo {
10396
}
10497

10598
const artifact = unwrapUndefinable(artifacts[0]);
106-
return {
107-
executable: artifact.fileName,
108-
workspace: artifact.workspace,
109-
};
99+
return artifact.fileName;
110100
}
111101

112102
private async runCargo(

0 commit comments

Comments
 (0)