дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
fix(maven): resolve bare launchers on the IDE search path (#422)
Builds and run configurations started a bare mvn / gradle through the app's own PATH, which can be minimal when the app isn't launched from a shell, so launchers installed by mise, asdf or Homebrew weren't found. Bare names now resolve through the configuration's PATH override or the IDE search path (login shell PATH plus version-manager shims) on every platform, for build processes and run configurations alike. Co-authored-by: a-rookie-of-C-language <[email protected]>
Цей коміт міститься в:
1 джерело
86cf348310
коміт
f507f55d16
3 files changed
+53
-24
No files matched your search
@@ -34,6 +34,22 @@ fn kill_tree(child: &mut Child) {
|
||||
let _ = child.wait();
|
||||
}
|
||||
|
||||
/// `program` as spawnable: a bare name (`mvn`, `mvn.cmd`) resolves on the IDE's
|
||||
/// search path — the login shell's `PATH` plus version-manager shims — since the
|
||||
/// app's own `PATH` can be minimal and `Command` would not see `mvn` there.
|
||||
fn program_path(program: &str, env: &BTreeMap<String, String>) -> std::path::PathBuf {
|
||||
let path = std::path::Path::new(program);
|
||||
if !path.is_relative() || path.components().count() > 1 {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
let search = env
|
||||
.iter()
|
||||
.find(|(k, _)| k.eq_ignore_ascii_case("PATH"))
|
||||
.map(|(_, v)| std::ffi::OsString::from(v))
|
||||
.unwrap_or_else(ide_core::tool_path::search_path);
|
||||
ide_core::process::find_executable(program, &search).unwrap_or_else(|| path.to_path_buf())
|
||||
}
|
||||
|
||||
/// Runs `program args` in `cwd`, feeding each output line to `on_line`; kills
|
||||
/// the process group and returns `cancelled` when `cancel` fires.
|
||||
pub(crate) fn run(
|
||||
@@ -48,7 +64,8 @@ pub(crate) fn run(
|
||||
// Output is read as UTF-8: JVMs on Windows are told to write it (see `ide_core::output_encoding`).
|
||||
let encoding =
|
||||
ide_core::output_encoding::fix(program, args, env, |k| std::env::var(k).ok(), "UTF-8", cfg!(windows));
|
||||
let mut command = Command::new(program);
|
||||
let program = program_path(program, env);
|
||||
let mut command = Command::new(&program);
|
||||
command.args(args).current_dir(cwd).envs(env).envs(encoding.env).stdin(Stdio::null());
|
||||
command.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
#[cfg(unix)]
|
||||
@@ -57,7 +74,8 @@ pub(crate) fn run(
|
||||
command.process_group(0);
|
||||
}
|
||||
ide_core::process::prepare(&mut command);
|
||||
let mut child = command.spawn().map_err(|e| IdeError::unavailable(format!("cannot start {program}: {e}")))?;
|
||||
let mut child =
|
||||
command.spawn().map_err(|e| IdeError::unavailable(format!("cannot start {}: {e}", program.display())))?;
|
||||
let (tx, rx) = mpsc::channel::<(Stream, String)>();
|
||||
let pump = |stream: Box<dyn Read + Send>, kind: Stream, tx: mpsc::Sender<(Stream, String)>| {
|
||||
std::thread::spawn(move || {
|
||||
|
||||
@@ -139,7 +139,7 @@ pub(crate) fn invocation(config: &RunConfig, cwd: &Path) -> Invocation {
|
||||
if config.args.is_empty() && looks_like_shell && !cwd.join(command).is_file() {
|
||||
return shell_invocation(command, windows, std::env::var("COMSPEC").ok());
|
||||
}
|
||||
let program = resolve_program(command, cwd, &config.env, windows);
|
||||
let program = resolve_program(command, cwd, config, windows);
|
||||
let mut display = quote(command);
|
||||
for a in &config.args {
|
||||
display.push(' ');
|
||||
@@ -239,22 +239,18 @@ fn shell_invocation(command: &str, windows: bool, comspec: Option<String>) -> In
|
||||
}
|
||||
|
||||
/// The program to spawn: relative paths like `./gradlew` or `.venv/bin/python`
|
||||
/// against the run directory; on Windows a bare name through `PATH` × `PATHEXT`
|
||||
/// (`kotlinc` → `kotlinc.bat`), which `Command` would only try as `.exe`.
|
||||
fn resolve_program(command: &str, cwd: &Path, env: &BTreeMap<String, String>, windows: bool) -> PathBuf {
|
||||
/// against the run directory; a bare name through the configuration's search path
|
||||
/// (on Windows also `PATHEXT`: `kotlinc` → `kotlinc.bat`), so launchers installed
|
||||
/// outside the app's own `PATH` (`mvn` in a version manager's shims) still start.
|
||||
fn resolve_program(command: &str, cwd: &Path, config: &RunConfig, windows: bool) -> PathBuf {
|
||||
let path = Path::new(command);
|
||||
if path.is_relative() && path.components().count() > 1 {
|
||||
return cwd.join(path);
|
||||
}
|
||||
if windows && path.components().count() == 1 && path.is_relative() {
|
||||
let search = env
|
||||
.iter()
|
||||
.find(|(k, _)| k.eq_ignore_ascii_case("PATH"))
|
||||
.map(|(_, v)| std::ffi::OsString::from(v))
|
||||
.or_else(|| std::env::var_os("PATH"))
|
||||
.unwrap_or_default();
|
||||
if path.components().count() == 1 && path.is_relative() {
|
||||
let search = crate::wrap::search_path(config);
|
||||
let pathext = std::env::var("PATHEXT").ok();
|
||||
if let Some(found) = crate::which::find_executable_in(command, &search, true, pathext.as_deref()) {
|
||||
if let Some(found) = crate::which::find_executable_in(command, &search, windows, pathext.as_deref()) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
@@ -1032,14 +1028,28 @@ mod tests {
|
||||
let cwd = dir.path();
|
||||
let path = std::env::join_paths([&bin]).unwrap().to_string_lossy().into_owned();
|
||||
// Windows environment keys are case-insensitive (`Path`).
|
||||
let env = BTreeMap::from([("Path".to_owned(), path)]);
|
||||
assert_eq!(resolve_program("kotlinc", cwd, &env, true), bin.join("kotlinc.bat"));
|
||||
let config = |env: BTreeMap<String, String>| RunConfig {
|
||||
id: "c".into(),
|
||||
name: "c".into(),
|
||||
kind: "maven".into(),
|
||||
command: String::new(),
|
||||
args: Vec::new(),
|
||||
cwd: None,
|
||||
env,
|
||||
detected: false,
|
||||
is_test: false,
|
||||
debug_adapter: None,
|
||||
allow_parallel: false,
|
||||
extras: Default::default(),
|
||||
};
|
||||
let config = config(BTreeMap::from([("Path".to_owned(), path)]));
|
||||
assert_eq!(resolve_program("kotlinc", cwd, &config, true), bin.join("kotlinc.bat"));
|
||||
// Not found: left to the OS (`command not found` at spawn).
|
||||
assert_eq!(resolve_program("nope", cwd, &env, true), PathBuf::from("nope"));
|
||||
// Unix: bare names are left to `execvp`.
|
||||
assert_eq!(resolve_program("kotlinc", cwd, &env, false), PathBuf::from("kotlinc"));
|
||||
assert_eq!(resolve_program("nope", cwd, &config, true), PathBuf::from("nope"));
|
||||
// Unix: extensionless names only (`kotlinc.bat` does not match `kotlinc`).
|
||||
assert_eq!(resolve_program("kotlinc", cwd, &config, false), PathBuf::from("kotlinc"));
|
||||
// Relative paths resolve against the run directory on every platform.
|
||||
assert_eq!(resolve_program("./gradlew", cwd, &env, true), cwd.join("./gradlew"));
|
||||
assert_eq!(resolve_program(".venv/bin/python", cwd, &env, false), cwd.join(".venv/bin/python"));
|
||||
assert_eq!(resolve_program("./gradlew", cwd, &config, true), cwd.join("./gradlew"));
|
||||
assert_eq!(resolve_program(".venv/bin/python", cwd, &config, false), cwd.join(".venv/bin/python"));
|
||||
}
|
||||
}
|
||||
@@ -33,15 +33,16 @@ pub fn is_python(command: &str) -> bool {
|
||||
n == "py" || n.strip_prefix("python").is_some_and(|v| v.chars().all(|c| c.is_ascii_digit() || c == '.'))
|
||||
}
|
||||
|
||||
/// The search path the configuration runs with (its `PATH` override, else the IDE's).
|
||||
/// The search path the configuration runs with (its `PATH` override, else the IDE's:
|
||||
/// the login shell's and the version managers', so `mvn` / `gradle` installed by mise,
|
||||
/// asdf or Homebrew resolve when the app's own `PATH` is minimal).
|
||||
pub fn search_path(config: &RunConfig) -> OsString {
|
||||
config
|
||||
.env
|
||||
.iter()
|
||||
.find(|(k, _)| k.eq_ignore_ascii_case("PATH"))
|
||||
.map(|(_, v)| OsString::from(v))
|
||||
.or_else(|| std::env::var_os("PATH"))
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_else(ide_core::tool_path::search_path)
|
||||
}
|
||||
|
||||
/// `node_modules/.bin/<name>` in `dir` or an ancestor up to (and including) `root`.
|
||||
|
||||
Reference in new issue
Заблокувати користувача