diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 0debf3270f61..73c3a48b4c5a 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -28,6 +28,13 @@ pub enum InvocationStrategy { PerWorkspace, } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum InvocationLocation { + Root(AbsPathBuf), + #[default] + Workspace, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { CargoCommand { @@ -39,13 +46,13 @@ pub enum FlycheckConfig { features: Vec, extra_args: Vec, extra_env: FxHashMap, - invocation_strategy: InvocationStrategy, }, CustomCommand { command: String, args: Vec, extra_env: FxHashMap, invocation_strategy: InvocationStrategy, + invocation_location: InvocationLocation, }, } @@ -275,7 +282,7 @@ impl FlycheckActor { } fn check_command(&self) -> Command { - let (mut cmd, args, invocation_strategy) = match &self.config { + let (mut cmd, args) = match &self.config { FlycheckConfig::CargoCommand { command, target_triple, @@ -285,7 +292,6 @@ impl FlycheckActor { extra_args, features, extra_env, - invocation_strategy, } => { let mut cmd = Command::new(toolchain::cargo()); cmd.arg(command); @@ -309,18 +315,40 @@ impl FlycheckActor { } } cmd.envs(extra_env); - (cmd, extra_args, invocation_strategy) + (cmd, extra_args) } - FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy } => { + FlycheckConfig::CustomCommand { + command, + args, + extra_env, + invocation_strategy, + invocation_location, + } => { let mut cmd = Command::new(command); cmd.envs(extra_env); - (cmd, args, invocation_strategy) + + match invocation_location { + InvocationLocation::Workspace => { + match invocation_strategy { + InvocationStrategy::Once => { + cmd.current_dir(&self.root); + } + InvocationStrategy::PerWorkspace => { + // FIXME: cmd.current_dir(&affected_workspace); + cmd.current_dir(&self.root); + } + } + } + InvocationLocation::Root(root) => { + cmd.current_dir(root); + } + } + + (cmd, args) } }; - match invocation_strategy { - InvocationStrategy::PerWorkspace => cmd.current_dir(&self.root), - InvocationStrategy::Once => cmd.args(args), - }; + + cmd.args(args); cmd } diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs index 0bb9bd65dccf..b5f837d3c6b5 100644 --- a/crates/project-model/src/build_scripts.rs +++ b/crates/project-model/src/build_scripts.rs @@ -21,7 +21,8 @@ use semver::Version; use serde::Deserialize; use crate::{ - cfg_flag::CfgFlag, CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, Package, + cfg_flag::CfgFlag, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, + InvocationStrategy, Package, }; #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -55,10 +56,7 @@ impl BuildScriptOutput { } impl WorkspaceBuildScripts { - fn build_command( - config: &CargoConfig, - workspace_root: Option<&path::Path>, - ) -> io::Result { + fn build_command(config: &CargoConfig, current_dir: &path::Path) -> io::Result { let mut cmd = match config.run_build_script_command.as_deref() { Some([program, args @ ..]) => { let mut cmd = Command::new(program); @@ -94,14 +92,11 @@ impl WorkspaceBuildScripts { } } - if let Some(workspace_root) = workspace_root { - cmd.current_dir(workspace_root); - } - cmd } }; + cmd.current_dir(current_dir); cmd.envs(&config.extra_env); if config.wrap_rustc_in_build_scripts { // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use @@ -124,19 +119,21 @@ impl WorkspaceBuildScripts { ) -> io::Result { const RUST_1_62: Version = Version::new(1, 62, 0); - let workspace_root: &path::Path = &workspace.workspace_root().as_ref(); + let current_dir = match &config.invocation_location { + InvocationLocation::Root(root) if config.run_build_script_command.is_some() => { + root.as_path() + } + _ => &workspace.workspace_root(), + } + .as_ref(); - match Self::run_per_ws( - Self::build_command(config, Some(workspace_root))?, - workspace, - progress, - ) { + match Self::run_per_ws(Self::build_command(config, current_dir)?, workspace, progress) { Ok(WorkspaceBuildScripts { error: Some(error), .. }) if toolchain.as_ref().map_or(false, |it| *it >= RUST_1_62) => { // building build scripts failed, attempt to build with --keep-going so // that we potentially get more build data - let mut cmd = Self::build_command(config, Some(workspace_root))?; + let mut cmd = Self::build_command(config, current_dir)?; cmd.args(&["-Z", "unstable-options", "--keep-going"]).env("RUSTC_BOOTSTRAP", "1"); let mut res = Self::run_per_ws(cmd, workspace, progress)?; res.error = Some(error); @@ -154,7 +151,17 @@ impl WorkspaceBuildScripts { progress: &dyn Fn(String), ) -> io::Result> { assert_eq!(config.invocation_strategy, InvocationStrategy::Once); - let cmd = Self::build_command(config, None)?; + + let current_dir = match &config.invocation_location { + InvocationLocation::Root(root) => root, + InvocationLocation::Workspace => { + return Err(io::Error::new( + io::ErrorKind::Other, + "Cannot run build scripts from workspace with invocation strategy `once`", + )) + } + }; + let cmd = Self::build_command(config, current_dir.as_path().as_ref())?; // NB: Cargo.toml could have been modified between `cargo metadata` and // `cargo check`. We shouldn't assume that package ids we see here are // exactly those from `config`. diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index d8f6d62349eb..b4c2ba436772 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashMap; use serde::Deserialize; use serde_json::from_value; -use crate::{utf8_stdout, ManifestPath}; +use crate::{utf8_stdout, InvocationLocation, ManifestPath}; use crate::{CfgOverrides, InvocationStrategy}; /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo @@ -107,6 +107,7 @@ pub struct CargoConfig { /// Extra env vars to set when invoking the cargo command pub extra_env: FxHashMap, pub invocation_strategy: InvocationStrategy, + pub invocation_location: InvocationLocation, } impl CargoConfig { diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index d116ff3c0104..575581fa543a 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -164,3 +164,10 @@ pub enum InvocationStrategy { #[default] PerWorkspace, } + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum InvocationLocation { + Root(AbsPathBuf), + #[default] + Workspace, +} diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 01180fcf4c3e..0ec5320997ca 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -332,7 +332,9 @@ impl ProjectWorkspace { config: &CargoConfig, progress: &dyn Fn(String), ) -> Vec> { - if let InvocationStrategy::PerWorkspace = config.invocation_strategy { + if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace) + || config.run_build_script_command.is_some() + { return workspaces.iter().map(|it| it.run_build_scripts(config, progress)).collect(); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3669fda926a7..85322f12a834 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -69,10 +69,16 @@ config_data! { cargo_autoreload: bool = "true", /// Run build scripts (`build.rs`) for more precise code analysis. cargo_buildScripts_enable: bool = "true", + /// Specifies the working directory for running build scripts. + /// - "workspace": run build scripts for a workspace in the workspace's root directory. + /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`. + /// - "root": run build scripts in the project's root directory. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + cargo_buildScripts_invocationLocation: InvocationLocation = "\"workspace\"", /// Specifies the invocation strategy to use when running the build scripts command. - /// If `per_workspace` is set, the command will be executed for each workspace from the - /// corresponding workspace root. - /// If `once` is set, the command will be executed once in the project root. + /// If `per_workspace` is set, the command will be executed for each workspace. + /// If `once` is set, the command will be executed once. /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` /// is set. cargo_buildScripts_invocationStrategy: InvocationStrategy = "\"per_workspace\"", @@ -129,10 +135,17 @@ config_data! { /// /// Set to `"all"` to pass `--all-features` to Cargo. checkOnSave_features: Option = "null", + /// Specifies the working directory for running checks. + /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories. + // FIXME: Ideally we would support this in some way + /// This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`. + /// - "root": run checks in the project's root directory. + /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// is set. + checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"", /// Specifies the invocation strategy to use when running the checkOnSave command. - /// If `per_workspace` is set, the command will be executed for each workspace from the - /// corresponding workspace root. - /// If `once` is set, the command will be executed once in the project root. + /// If `per_workspace` is set, the command will be executed for each workspace. + /// If `once` is set, the command will be executed once. /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` /// is set. checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", @@ -1074,6 +1087,12 @@ impl Config { InvocationStrategy::Once => project_model::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace, }, + invocation_location: match self.data.cargo_buildScripts_invocationLocation { + InvocationLocation::Root => { + project_model::InvocationLocation::Root(self.root_path.clone()) + } + InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, + }, run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(), extra_env: self.data.cargo_extraEnv.clone(), } @@ -1097,10 +1116,6 @@ impl Config { if !self.data.checkOnSave_enable { return None; } - let invocation_strategy = match self.data.checkOnSave_invocationStrategy { - InvocationStrategy::Once => flycheck::InvocationStrategy::Once, - InvocationStrategy::PerWorkspace => flycheck::InvocationStrategy::PerWorkspace, - }; let flycheck_config = match &self.data.checkOnSave_overrideCommand { Some(args) if !args.is_empty() => { let mut args = args.clone(); @@ -1109,7 +1124,18 @@ impl Config { command, args, extra_env: self.check_on_save_extra_env(), - invocation_strategy, + invocation_strategy: match self.data.checkOnSave_invocationStrategy { + InvocationStrategy::Once => flycheck::InvocationStrategy::Once, + InvocationStrategy::PerWorkspace => { + flycheck::InvocationStrategy::PerWorkspace + } + }, + invocation_location: match self.data.checkOnSave_invocationLocation { + InvocationLocation::Root => { + flycheck::InvocationLocation::Root(self.root_path.clone()) + } + InvocationLocation::Workspace => flycheck::InvocationLocation::Workspace, + }, } } Some(_) | None => FlycheckConfig::CargoCommand { @@ -1139,7 +1165,6 @@ impl Config { }, extra_args: self.data.checkOnSave_extraArgs.clone(), extra_env: self.check_on_save_extra_env(), - invocation_strategy, }, }; Some(flycheck_config) @@ -1618,6 +1643,13 @@ enum InvocationStrategy { PerWorkspace, } +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +enum InvocationLocation { + Root, + Workspace, +} + #[derive(Deserialize, Debug, Clone)] #[serde(untagged)] enum LifetimeElisionDef { @@ -2036,8 +2068,16 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "type": "string", "enum": ["per_workspace", "once"], "enumDescriptions": [ - "The command will be executed for each workspace from the corresponding workspace root.", - "The command will be executed once in the project root." + "The command will be executed for each workspace.", + "The command will be executed once." + ], + }, + "InvocationLocation" => set! { + "type": "string", + "enum": ["workspace", "root"], + "enumDescriptions": [ + "The command will be executed in the corresponding workspace root.", + "The command will be executed in the project root." ], }, _ => panic!("missing entry for {}: {}", ty, default), diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index cc7600a2fa98..e1f651786dee 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -473,8 +473,10 @@ impl GlobalState { }; let sender = self.flycheck_sender.clone(); - let (FlycheckConfig::CargoCommand { invocation_strategy, .. } - | FlycheckConfig::CustomCommand { invocation_strategy, .. }) = config; + let invocation_strategy = match config { + FlycheckConfig::CargoCommand { .. } => flycheck::InvocationStrategy::PerWorkspace, + FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy, + }; self.flycheck = match invocation_strategy { flycheck::InvocationStrategy::Once => vec![FlycheckHandle::spawn( diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index e5d4395c345c..502833de72c1 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -24,13 +24,22 @@ Automatically refresh project info via `cargo metadata` on -- Run build scripts (`build.rs`) for more precise code analysis. -- +[[rust-analyzer.cargo.buildScripts.invocationLocation]]rust-analyzer.cargo.buildScripts.invocationLocation (default: `"workspace"`):: ++ +-- +Specifies the working directory for running build scripts. +- "workspace": run build scripts for a workspace in the workspace's root directory. + This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`. +- "root": run build scripts in the project's root directory. +This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` +is set. +-- [[rust-analyzer.cargo.buildScripts.invocationStrategy]]rust-analyzer.cargo.buildScripts.invocationStrategy (default: `"per_workspace"`):: + -- Specifies the invocation strategy to use when running the build scripts command. -If `per_workspace` is set, the command will be executed for each workspace from the -corresponding workspace root. -If `once` is set, the command will be executed once in the project root. +If `per_workspace` is set, the command will be executed for each workspace. +If `once` is set, the command will be executed once. This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` is set. -- @@ -128,13 +137,22 @@ List of features to activate. Defaults to Set to `"all"` to pass `--all-features` to Cargo. -- +[[rust-analyzer.checkOnSave.invocationLocation]]rust-analyzer.checkOnSave.invocationLocation (default: `"workspace"`):: ++ +-- +Specifies the working directory for running checks. +- "workspace": run checks for workspaces in the corresponding workspaces' root directories. + This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`. +- "root": run checks in the project's root directory. +This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` +is set. +-- [[rust-analyzer.checkOnSave.invocationStrategy]]rust-analyzer.checkOnSave.invocationStrategy (default: `"per_workspace"`):: + -- Specifies the invocation strategy to use when running the checkOnSave command. -If `per_workspace` is set, the command will be executed for each workspace from the -corresponding workspace root. -If `once` is set, the command will be executed once in the project root. +If `per_workspace` is set, the command will be executed for each workspace. +If `once` is set, the command will be executed once. This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` is set. -- diff --git a/editors/code/package.json b/editors/code/package.json index 62ac1e60b00a..6771cad28a79 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -432,8 +432,21 @@ "default": true, "type": "boolean" }, + "rust-analyzer.cargo.buildScripts.invocationLocation": { + "markdownDescription": "Specifies the working directory for running build scripts.\n- \"workspace\": run build scripts for a workspace in the workspace's root directory.\n This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.\n- \"root\": run build scripts in the project's root directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "default": "workspace", + "type": "string", + "enum": [ + "workspace", + "root" + ], + "enumDescriptions": [ + "The command will be executed in the corresponding workspace root.", + "The command will be executed in the project root." + ] + }, "rust-analyzer.cargo.buildScripts.invocationStrategy": { - "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each workspace from the\ncorresponding workspace root.\nIf `once` is set, the command will be executed once in the project root.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", "default": "per_workspace", "type": "string", "enum": [ @@ -441,8 +454,8 @@ "once" ], "enumDescriptions": [ - "The command will be executed for each workspace from the corresponding workspace root.", - "The command will be executed once in the project root." + "The command will be executed for each workspace.", + "The command will be executed once." ] }, "rust-analyzer.cargo.buildScripts.overrideCommand": { @@ -570,8 +583,21 @@ } ] }, + "rust-analyzer.checkOnSave.invocationLocation": { + "markdownDescription": "Specifies the working directory for running checks.\n- \"workspace\": run checks for workspaces in the corresponding workspaces' root directories.\n This falls back to \"root\" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`.\n- \"root\": run checks in the project's root directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "default": "workspace", + "type": "string", + "enum": [ + "workspace", + "root" + ], + "enumDescriptions": [ + "The command will be executed in the corresponding workspace root.", + "The command will be executed in the project root." + ] + }, "rust-analyzer.checkOnSave.invocationStrategy": { - "markdownDescription": "Specifies the invocation strategy to use when running the checkOnSave command.\nIf `per_workspace` is set, the command will be executed for each workspace from the\ncorresponding workspace root.\nIf `once` is set, the command will be executed once in the project root.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "markdownDescription": "Specifies the invocation strategy to use when running the checkOnSave command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", "default": "per_workspace", "type": "string", "enum": [ @@ -579,8 +605,8 @@ "once" ], "enumDescriptions": [ - "The command will be executed for each workspace from the corresponding workspace root.", - "The command will be executed once in the project root." + "The command will be executed for each workspace.", + "The command will be executed once." ] }, "rust-analyzer.checkOnSave.noDefaultFeatures": {