Файли
openidea/crates/ide-run/src/scope.rs
T
l0ng-ai ba9ce1d842
CI / Changes (push) Successful in 1s
Language smoke / Language smoke (go) (push) Failing after 6s
Language smoke / Language smoke (java) (push) Failing after 5s
Language smoke / Language smoke (kotlin) (push) Failing after 5s
Language smoke / Language smoke (python) (push) Failing after 4s
Language smoke / Language smoke (typescript) (push) Failing after 4s
Language smoke / Language smoke (rust) (push) Failing after 5s
CI / Rust (ubuntu-22.04) (push) Failing after 43s
CI / Plugins (SDK, official, fixtures) (push) Failing after 1m19s
CI / Remote server (linux musl) (push) Failing after 1m32s
CI / Cargo deny (push) Successful in 54s
CI / E2E (push) Successful in 15m57s
CI / Frontend (push) Successful in 24m49s
CI / Rust (macos-latest) (push) Canceled after 0s
CI / Rust (windows-latest) (push) Canceled after 0s
fix(workspace): unify nested project discovery and run ownership (#1716)
* fix(build): scope Kotlin bridge to linked Maven modules

* fix(run): resolve nested Go modules for context runs

* fix(gradle): discover nested builds and select compatible JVMs

* fix(workspace): share project discovery and module ownership

* fix(run): keep root toolchains for nested modules and resolve them once per module

- Python run configurations of a nested module without its own virtualenv use the closest one up to the project root.
- Gradle builds and runs fall back to the JDK matching the project's Java version when the Gradle version is unknown.
- Module-local interpreters and toolchains are resolved lazily, once per owning module.
- Tests build expected paths per segment so they hold on Windows.
2026-11-06 15:51:41 +08:00

126 рядки
5.5 KiB
Rust

//! A configuration's module and relative paths move together when projects are nested.
use crate::RunConfig;
use ide_core::{ipc_path::to_ipc, project_roots};
use std::path::{Path, PathBuf};
pub fn owner(root: &Path, config: &RunConfig) -> PathBuf {
if matches!(config.kind.as_str(), "dotnet" | "dotnet-test")
&& let Some(project) = config.extras.params.get("project").filter(|p| !p.is_empty())
{
return project_roots::resolve(root, Path::new(project)).parent().unwrap_or(root).to_path_buf();
}
let module = (config.kind == "go")
.then(|| config.extras.params.get("module"))
.flatten()
.map(String::as_str)
.or(config.extras.module.as_deref())
.map(Path::new)
.or(config.cwd.as_deref())
.unwrap_or(Path::new(""));
project_roots::resolve(root, module)
}
pub fn rebase(config: &mut RunConfig, from: &Path, relative_to: Option<&Path>) {
let path = |value: &str| {
let absolute = project_roots::resolve(from, Path::new(value));
to_ipc(&relative_to.map_or(absolute.clone(), |root| project_roots::relative(root, &absolute)))
};
let cwd = config.cwd.as_deref().unwrap_or(Path::new("")).to_path_buf();
config.cwd = Some(path(&to_ipc(&cwd)).into());
if config.extras.module.is_some() || !matches!(config.kind.as_str(), "application" | "spring-boot") {
config.extras.module = Some(path(config.extras.module.as_deref().unwrap_or("")));
}
if config.kind == "go" && config.extras.params.contains_key("goKind") {
config.extras.params.entry("module".into()).or_default();
}
for file in &mut config.extras.env_files {
*file = path(file);
}
for file in [&mut config.extras.stdin_file, &mut config.extras.output_file].into_iter().flatten() {
*file = path(file);
}
let keys: &[&str] = match config.kind.as_str() {
"go" => &["module", "outputDir"],
"dotnet" | "dotnet-test" => &["project", "targetPath"],
_ => &[],
};
for key in keys {
if let Some(value) = config.extras.params.get_mut(*key)
&& (*key == "module" || !value.is_empty())
{
*value = path(value);
}
}
if !matches!(config.kind.as_str(), "shell" | "compound") && Path::new(&config.command).components().count() > 1 {
// Executables are relative to the old cwd, unlike env/input/output files.
let cwd = project_roots::resolve(from, &cwd);
config.command = to_ipc(&project_roots::resolve(&cwd, Path::new(&config.command)));
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
#[test]
fn default_go_module_moves_with_member_but_unknown_jvm_module_stays_inferred() {
let temp = tempfile::tempdir().unwrap();
for (kind, params) in
[("go", serde_json::json!({"goKind":"build", "target":"."})), ("application", serde_json::json!({}))]
{
let mut config: RunConfig = serde_json::from_value(serde_json::json!({
"id":"run", "name":"run", "kind":kind, "command":"tool", "args":[], "cwd":null,
"env":{}, "detected":false, "isTest":false, "debugAdapter":null, "params":params
}))
.unwrap();
rebase(&mut config, temp.path(), None);
if kind == "go" {
assert_eq!(config.extras.params["module"], to_ipc(temp.path()));
} else {
assert_eq!(config.extras.module, None);
}
}
}
#[test]
fn workspace_members_rebase_every_typed_root_without_changing_targets() {
let temp = tempfile::tempdir().unwrap();
let member = temp.path().join("member with spaces");
for (kind, params) in [
("application", BTreeMap::<String, String>::new()),
(
"go",
BTreeMap::from([
("module".into(), "api".into()),
("target".into(), "./cmd/server".into()),
("outputDir".into(), "bin".into()),
]),
),
("dotnet", BTreeMap::from([("project".into(), "src/App.csproj".into())])),
] {
let mut config: RunConfig = serde_json::from_value(serde_json::json!({
"id":"run", "name":"run", "kind":kind, "command":"tool", "args":["unchanged"], "cwd":null,
"env":{}, "detected":true, "isTest":false, "debugAdapter":null,
"module":"app", "params":params, "envFiles":[".env"], "stdinFile":"input.txt", "outputFile":"out/result.txt"
})).unwrap();
rebase(&mut config, &member, None);
assert_eq!(config.cwd.as_deref(), Some(member.as_path()));
assert_eq!(config.extras.module.as_deref(), Some(to_ipc(&member.join("app")).as_str()));
assert_eq!(config.extras.env_files, [to_ipc(&member.join(".env"))]);
assert_eq!(config.extras.stdin_file, Some(to_ipc(&member.join("input.txt"))));
assert_eq!(config.extras.output_file, Some(to_ipc(&member.join("out/result.txt"))));
assert_eq!(config.args, ["unchanged"]);
if kind == "go" {
assert_eq!(config.extras.params["module"], to_ipc(&member.join("api")));
assert_eq!(config.extras.params["outputDir"], to_ipc(&member.join("bin")));
assert_eq!(config.extras.params["target"], "./cmd/server");
}
if kind == "dotnet" {
assert_eq!(config.extras.params["project"], to_ipc(&member.join("src/App.csproj")));
}
}
}
}