mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(java): preserve mapped toolchain paths and refresh Maven run settings (#1147)
* fix(sdk): keep the drive letter of a Maven home found on a mapped drive Maven discovery canonicalized mvn on PATH and each Maven home to resolve symlinks (Homebrew's bin/mvn). On Windows, a home on a mapped network drive or a subst drive then became \\?\UNC\server\share\… (or another drive), the Maven home setting picked from the list stored that path, and cmd.exe can't start mvn.cmd from it: every Maven run failed with "The system cannot find the path specified". Discovery now keeps the path as found when canonicalizing would change its drive letter. Refs #822 * fix(sdk): keep the drive letter of a JDK home on a mapped drive A JDK on a mapped network drive was canonicalized to \\?\UNC\server\share\… too, so Maven ran with that JAVA_HOME and mvn.cmd failed with "The system cannot find the path specified" even with a local Maven home. JDK homes (configured, JAVA_HOME, the java on PATH) now keep their drive letter like Maven homes do. * fix(maven): resolve current toolchain settings for each run * fix(sdk): distinguish mapped roots from external launcher links * test(run): preserve explicit Maven choices across save and reload * fix(maven): keep edited nested test launchers when refreshing defaults
This commit is contained in:
@@ -41,7 +41,7 @@ pub fn configs(host: &Host, window: &str) -> IdeResult<Vec<RunConfig>> {
|
||||
}
|
||||
|
||||
/// A project's detected configurations (minus removed ones and unlinked Maven projects), then
|
||||
/// its saved ones; toolchains from Languages & Frameworks applied. Gradle choices stay as saved:
|
||||
/// its saved ones; toolchains from Languages & Frameworks applied. Build-tool choices stay as saved:
|
||||
/// derived defaults must not be written back as explicit environment.
|
||||
fn detected_and_saved(
|
||||
root: &std::path::Path,
|
||||
@@ -56,7 +56,7 @@ fn detected_and_saved(
|
||||
.filter(|d| !suppressed.contains(&d.id) && !saved.iter().any(|u| u.id == d.id))
|
||||
.collect();
|
||||
all.extend(saved.iter().cloned());
|
||||
for config in all.iter_mut().filter(|c| c.kind != "gradle") {
|
||||
for config in all.iter_mut().filter(|c| !matches!(c.kind.as_str(), "gradle" | "maven")) {
|
||||
ide_sdk::apply::apply_to_run_configs(std::slice::from_mut(config), root, settings);
|
||||
}
|
||||
all
|
||||
@@ -75,7 +75,7 @@ pub fn save_configs(
|
||||
let configs: Vec<RunConfig> = configs
|
||||
.iter()
|
||||
.map(|c| match c.kind.as_str() {
|
||||
"gradle" => c.clone(),
|
||||
"gradle" | "maven" => c.clone(),
|
||||
_ => ide_sdk::apply::without_derived(c, &project.root, &settings),
|
||||
})
|
||||
.collect();
|
||||
@@ -156,7 +156,9 @@ pub async fn start(host: &Host, window: &str, config: RunConfig, on_output: Emit
|
||||
let root = &root;
|
||||
// Configurations made in the UI name bare tools (`godot`) that may live outside PATH.
|
||||
let mut config = config;
|
||||
if matches!(config.kind.as_str(), "gradle" | "tomcat") {
|
||||
if config.kind == "maven" {
|
||||
ide_sdk::apply::prepare_maven_run(&mut config, root, &settings);
|
||||
} else if matches!(config.kind.as_str(), "gradle" | "tomcat") {
|
||||
ide_sdk::apply::apply_to_run_configs(std::slice::from_mut(&mut config), root, &settings);
|
||||
} else {
|
||||
ide_sdk::apply::resolve_run_command(&mut config, root, &settings);
|
||||
|
||||
@@ -81,6 +81,46 @@ async fn gradle_defaults_are_not_saved_as_explicit_run_choices() {
|
||||
assert_eq!(saved.command, gradle.command);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn maven_defaults_are_resolved_at_launch_and_explicit_choices_survive_reload() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = dunce::canonicalize(tmp.path()).unwrap();
|
||||
std::fs::write(root.join("pom.xml"), "<project/>").unwrap();
|
||||
let home = root.join("maven");
|
||||
std::fs::create_dir_all(home.join("bin")).unwrap();
|
||||
let launcher = home.join("bin").join(if cfg!(windows) { "mvn.cmd" } else { "mvn" });
|
||||
std::fs::write(&launcher, "").unwrap();
|
||||
let host = host(&root.join("app"));
|
||||
host.trust.decide(&root, true, false).unwrap();
|
||||
host.open_project("main", &root).unwrap();
|
||||
for (key, value) in [
|
||||
("languages.java.maven.home", home.to_string_lossy().into_owned()),
|
||||
("languages.java.maven.userSettings", "settings.xml".into()),
|
||||
] {
|
||||
ops::project::set_settings(
|
||||
&host,
|
||||
"main",
|
||||
ide_workspace::settings::SettingsScope::Project,
|
||||
key,
|
||||
&serde_json::to_string(&value).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let mut config = ops::run::configs(&host, "main").unwrap().into_iter().find(|c| c.id == "maven:test").unwrap();
|
||||
assert_eq!(config.command, "mvn");
|
||||
assert_eq!(config.args, ["test"]);
|
||||
assert!(config.env.is_empty());
|
||||
config.command = launcher.to_string_lossy().into_owned();
|
||||
config.args = vec!["-s".into(), "own-settings.xml".into(), "test".into()];
|
||||
config.env.insert("JAVA_HOME".into(), "own-jdk".into());
|
||||
config.detected = false;
|
||||
ops::run::save_configs(&host, "main", &[config.clone()], &[]).unwrap();
|
||||
let saved = ops::run::configs(&host, "main").unwrap().into_iter().find(|c| c.id == config.id).unwrap();
|
||||
assert_eq!(saved.command, config.command);
|
||||
assert_eq!(saved.args, config.args);
|
||||
assert_eq!(saved.env, config.env);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn workspace_cmake_before_launch_tasks_resolve_in_the_member() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -192,6 +192,88 @@ pub fn maven_options(root: &Path, s: &Settings) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
fn maven_settings_prefixes(args: &[String]) -> Vec<usize> {
|
||||
let mut prefixes = vec![0];
|
||||
let mut i = 0;
|
||||
while let Some(arg) = args.get(i) {
|
||||
if arg == "-s" && args.get(i + 1).is_some() {
|
||||
i += 2;
|
||||
} else if arg.starts_with("-Dmaven.repo.local=") {
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
prefixes.push(i);
|
||||
}
|
||||
prefixes
|
||||
}
|
||||
|
||||
fn generated_maven_goal(config: &RunConfig) -> bool {
|
||||
config.id.starts_with("maven:goal:") && config.extras.params.is_empty()
|
||||
}
|
||||
|
||||
fn maven_goal_prefix(config: &RunConfig) -> Option<usize> {
|
||||
let (_, goals) = config.id.split_once(".xml:")?;
|
||||
maven_settings_prefixes(&config.args)
|
||||
.into_iter()
|
||||
.find(|&start| (start + 1..=config.args.len()).any(|end| config.args[start..end].join(" ") == goals))
|
||||
}
|
||||
|
||||
/// Applies the current Maven settings at launch, including temporary configurations
|
||||
/// made before build-tool defaults stopped being stored in their command and arguments.
|
||||
pub fn prepare_maven_run(config: &mut RunConfig, root: &Path, s: &Settings) {
|
||||
let mut detected = config.detected;
|
||||
if generated_maven_goal(config) {
|
||||
// The id records the user's original goals; only the settings prefix before them is derived.
|
||||
if let Some(prefix) = maven_goal_prefix(config) {
|
||||
config.args.drain(..prefix);
|
||||
}
|
||||
let name = config.command.rsplit(['/', '\\']).next().unwrap_or_default().to_ascii_lowercase();
|
||||
if !matches!(name.as_str(), "mvnw" | "mvnw.cmd" | "mvnw.bat") {
|
||||
config.command = "mvn".into();
|
||||
if maven_command(root, s).is_none() {
|
||||
let cwd = config.cwd.as_ref().map_or_else(|| root.to_path_buf(), |dir| root.join(dir.as_ref()));
|
||||
for dir in cwd.ancestors().take_while(|dir| dir.starts_with(root)) {
|
||||
let wrapper = dir.join(if cfg!(windows) { "mvnw.cmd" } else { "mvnw" });
|
||||
if wrapper.is_file() {
|
||||
config.command = wrapper.to_string_lossy().into_owned();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if config.detected || config.id.starts_with("context:") {
|
||||
let saved = ide_run::load_user_configs(root).unwrap_or_default();
|
||||
let id = config
|
||||
.id
|
||||
.strip_prefix("context:target:context:")
|
||||
.or_else(|| config.id.strip_prefix("context:"))
|
||||
.unwrap_or(&config.id);
|
||||
let id = id.strip_prefix("workspace:").and_then(|id| id.split_once(':')).map_or(id, |(_, id)| id);
|
||||
let source = ide_run::detect(root)
|
||||
.into_iter()
|
||||
.chain(saved.iter().cloned())
|
||||
.filter(|base| base.kind == "maven" && (id == base.id || id.starts_with(&format!("{}:", base.id))))
|
||||
.max_by_key(|base| base.id.len())
|
||||
.filter(|base| !saved.iter().any(|user| user.id == base.id));
|
||||
if let Some(base) = source.filter(|_| config.extras.params.is_empty()) {
|
||||
if let Some(prefix) = maven_settings_prefixes(&config.args)
|
||||
.into_iter()
|
||||
.find(|&start| config.args[start..].starts_with(&base.args))
|
||||
{
|
||||
config.args.drain(..prefix);
|
||||
}
|
||||
config.command = base.command;
|
||||
} else {
|
||||
detected = false;
|
||||
}
|
||||
}
|
||||
let original = config.detected;
|
||||
config.detected = detected;
|
||||
apply_to_run_configs(std::slice::from_mut(config), root, s);
|
||||
config.detected = original;
|
||||
}
|
||||
|
||||
/// The Maven local repository from settings: the configured one, else
|
||||
/// `<localRepository>` of the configured user `settings.xml`; `None` = Maven's default.
|
||||
pub fn maven_local_repository(root: &Path, s: &Settings) -> Option<PathBuf> {
|
||||
@@ -666,6 +748,9 @@ fn sets_maven_option(args: &[String], group: &[String]) -> bool {
|
||||
Some(flag) => {
|
||||
let key = flag.split_once('=').map_or(flag, |(k, _)| k);
|
||||
args.iter().any(|a| a.split_once('=').map_or(a.as_str(), |(k, _)| k) == key)
|
||||
|| args
|
||||
.windows(2)
|
||||
.any(|a| a[0] == "-D" && format!("-D{}", a[1].split('=').next().unwrap_or_default()) == key)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
@@ -815,11 +900,10 @@ pub fn apply_to_run_configs(configs: &mut [RunConfig], root: &Path, s: &Settings
|
||||
c.env.entry("JAVA_HOME".into()).or_insert_with(|| jdk.home.to_string_lossy().into_owned());
|
||||
}
|
||||
if c.kind == "maven" {
|
||||
// A configuration made or saved in the UI names the literal `mvn` /
|
||||
// `mvn.cmd`; without a wrapper it cannot start when Maven lives on a
|
||||
// version manager's shims or a configured home (#328).
|
||||
if let Some(mvn) = &mvn
|
||||
&& (c.detected || matches!(c.command.as_str(), "mvn" | "mvn.cmd" | "mvn.bat"))
|
||||
&& (c.detected
|
||||
|| generated_maven_goal(c)
|
||||
|| matches!(c.command.as_str(), "mvn" | "mvn.cmd" | "mvn.bat"))
|
||||
{
|
||||
c.command = mvn.to_string_lossy().into_owned();
|
||||
}
|
||||
@@ -870,9 +954,15 @@ pub fn without_derived(config: &RunConfig, root: &Path, s: &Settings) -> RunConf
|
||||
if out.kind == "maven" {
|
||||
let options = maven_options(root, s);
|
||||
let mut probe = out.clone();
|
||||
let mut prefix = generated_maven_goal(&out).then(|| maven_goal_prefix(&out)).flatten();
|
||||
for group in maven_option_groups(&options) {
|
||||
if let Some(at) = probe.args.windows(group.len()).position(|w| w == group) {
|
||||
if let Some(at) = probe.args.windows(group.len()).position(|w| w == group)
|
||||
&& prefix.is_none_or(|limit| at + group.len() <= limit)
|
||||
{
|
||||
probe.args.drain(at..at + group.len());
|
||||
if let Some(limit) = &mut prefix {
|
||||
*limit -= group.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
if applied(&probe).args == out.args {
|
||||
|
||||
@@ -545,7 +545,7 @@ fn jdk() -> Vec<Sdk> {
|
||||
|
||||
/// A JDK as the SDK pickers list it (`Eclipse Adoptium 25.0.1`).
|
||||
pub fn jdk_sdk(j: &crate::java::Jdk) -> Sdk {
|
||||
let java_home = std::env::var_os("JAVA_HOME").and_then(|h| dunce::canonicalize(h).ok());
|
||||
let java_home = std::env::var_os("JAVA_HOME").and_then(|h| crate::launch_path(Path::new(&h)).ok());
|
||||
let release = std::fs::read_to_string(j.home.join("release")).unwrap_or_default();
|
||||
let version = release_value(&release, "JAVA_VERSION").unwrap_or_else(|| j.major.to_string());
|
||||
let name = match release_value(&release, "IMPLEMENTOR") {
|
||||
@@ -595,7 +595,7 @@ fn maven() -> Vec<Sdk> {
|
||||
} else {
|
||||
which("mvn")
|
||||
};
|
||||
if let Some(mvn) = on_path.and_then(|p| dunce::canonicalize(p).ok())
|
||||
if let Some(mvn) = on_path.and_then(|p| crate::launch_path(&p).ok())
|
||||
&& let Some(home) = mvn.parent().and_then(Path::parent)
|
||||
{
|
||||
found.push((home.to_path_buf(), "path"));
|
||||
@@ -629,7 +629,7 @@ fn maven() -> Vec<Sdk> {
|
||||
let mut out = Vec::new();
|
||||
for (dir, source) in found {
|
||||
let Some(home) = maven_home(&dir) else { continue };
|
||||
let home = dunce::canonicalize(&home).unwrap_or(home);
|
||||
let home = crate::launch_path(&home).unwrap_or(home);
|
||||
if !seen.insert(home.clone()) {
|
||||
continue;
|
||||
}
|
||||
@@ -645,3 +645,84 @@ fn maven() -> Vec<Sdk> {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(all(test, windows))]
|
||||
mod maven_path_tests {
|
||||
use super::*;
|
||||
use crate::launch_path;
|
||||
use std::process::Command;
|
||||
|
||||
struct Subst {
|
||||
name: String,
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
impl Subst {
|
||||
fn new(target: &Path) -> Self {
|
||||
for letter in (b'D'..=b'Z').rev() {
|
||||
let name = format!("{}:", char::from(letter));
|
||||
let root = PathBuf::from(format!("{name}\\"));
|
||||
if root.exists() {
|
||||
continue;
|
||||
}
|
||||
let output = Command::new("subst").arg(&name).arg(target).output().unwrap();
|
||||
if output.status.success() {
|
||||
return Self { name, root };
|
||||
}
|
||||
}
|
||||
panic!("no free drive for Maven path verification");
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Subst {
|
||||
fn drop(&mut self) {
|
||||
let _ = Command::new("subst").args([&self.name, "/D"]).output();
|
||||
}
|
||||
}
|
||||
|
||||
fn install(home: &Path) {
|
||||
std::fs::create_dir_all(home.join("bin")).unwrap();
|
||||
std::fs::create_dir_all(home.join("lib")).unwrap();
|
||||
std::fs::write(home.join("bin/mvn.cmd"), "@echo off\n").unwrap();
|
||||
std::fs::write(home.join("lib/maven-core-3.9.16.jar"), "").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mapped_maven_home_and_launcher_keep_their_drive() {
|
||||
let mapped = tempfile::tempdir().unwrap();
|
||||
install(&mapped.path().join("apache-maven"));
|
||||
let drive = Subst::new(mapped.path());
|
||||
let home = drive.root.join("apache-maven");
|
||||
let launcher = home.join("bin/mvn.cmd");
|
||||
assert_ne!(dunce::canonicalize(&home).unwrap(), home);
|
||||
assert_eq!(launch_path(&home).unwrap(), home);
|
||||
assert_eq!(launch_path(&launcher).unwrap(), launcher);
|
||||
assert_eq!(maven_home(&home), Some(home.clone()));
|
||||
assert_eq!(maven_version(&home).as_deref(), Some("3.9.16"));
|
||||
let jdk = drive.root.join("jdk");
|
||||
std::fs::create_dir_all(jdk.join("bin")).unwrap();
|
||||
std::fs::write(jdk.join("bin/java.exe"), "").unwrap();
|
||||
std::fs::write(jdk.join("release"), "JAVA_VERSION=\"17.0.9\"\n").unwrap();
|
||||
assert_eq!(crate::java::jdk_at(&jdk).unwrap().home, jdk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_launcher_link_outside_the_mapped_root_still_finds_its_install() {
|
||||
let mapped = tempfile::tempdir().unwrap();
|
||||
let real = tempfile::tempdir().unwrap();
|
||||
install(real.path());
|
||||
let drive = Subst::new(mapped.path());
|
||||
let link = mapped.path().join("shims");
|
||||
let output = Command::new(std::env::var_os("COMSPEC").unwrap_or_else(|| "cmd.exe".into()))
|
||||
.args(["/D", "/C", "mklink", "/J"])
|
||||
.arg(&link)
|
||||
.arg(real.path().join("bin"))
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
let launcher = launch_path(&drive.root.join("shims/mvn.cmd")).unwrap();
|
||||
assert_eq!(launcher, dunce::canonicalize(real.path().join("bin/mvn.cmd")).unwrap());
|
||||
let home = launcher.parent().and_then(Path::parent).and_then(maven_home).unwrap();
|
||||
assert_eq!(home, dunce::canonicalize(real.path()).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,7 @@ pub fn jdk_at(home: &Path) -> Option<Jdk> {
|
||||
if !home.join("bin").join(crate::exe("java")).is_file() {
|
||||
return None;
|
||||
}
|
||||
Some(Jdk {
|
||||
major: parse_release(&release)?,
|
||||
home: dunce::canonicalize(home).unwrap_or_else(|_| home.to_path_buf()),
|
||||
})
|
||||
Some(Jdk { major: parse_release(&release)?, home: crate::launch_path(home).unwrap_or_else(|_| home.to_path_buf()) })
|
||||
}
|
||||
|
||||
fn children(dir: &Path) -> Vec<PathBuf> {
|
||||
@@ -90,7 +87,7 @@ fn scan() -> Vec<Jdk> {
|
||||
let path = std::env::var_os("PATH").unwrap_or_default();
|
||||
if let Some(java) = std::env::split_paths(&path).map(|d| d.join(crate::exe("java"))).find(|j| j.is_file()) {
|
||||
// Through symlinks (`/usr/bin/java`, Homebrew, SDKMAN `current`) to `<home>/bin/java`.
|
||||
if let Some(home) = dunce::canonicalize(java).ok().and_then(|j| j.parent()?.parent().map(Path::to_path_buf)) {
|
||||
if let Some(home) = crate::launch_path(&java).ok().and_then(|j| j.parent()?.parent().map(Path::to_path_buf)) {
|
||||
homes.push(home);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,36 @@ pub(crate) fn exe(name: &str) -> String {
|
||||
if cfg!(windows) { format!("{name}.exe") } else { name.to_owned() }
|
||||
}
|
||||
|
||||
/// `path` with symlinks resolved (Homebrew's `bin/mvn` → its Cellar), except that a Windows
|
||||
/// mapped drive letter is kept when the target stays inside that drive: canonicalizing gives
|
||||
/// `\\?\UNC\server\share\…` or another drive, and cmd.exe can't start a script from a
|
||||
/// `\\?\` path (`mvn.cmd`, or `mvn.cmd` with such a `JAVA_HOME`), nor is it the path the user knows.
|
||||
pub(crate) fn launch_path(path: &Path) -> std::io::Result<PathBuf> {
|
||||
let real = dunce::canonicalize(path)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::path::{Component, Prefix};
|
||||
let drive = |p: &Path| match p.components().next() {
|
||||
Some(Component::Prefix(x)) => match x.kind() {
|
||||
Prefix::Disk(d) | Prefix::VerbatimDisk(d) => Some(d.to_ascii_uppercase()),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
if let Some(d) = drive(path)
|
||||
&& drive(&real) != Some(d)
|
||||
{
|
||||
let root = PathBuf::from(format!("{}:\\", char::from(d)));
|
||||
if let Ok(mapped) = dunce::canonicalize(&root)
|
||||
&& let Ok(relative) = real.strip_prefix(mapped)
|
||||
{
|
||||
return Ok(root.join(relative));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(real)
|
||||
}
|
||||
|
||||
pub(crate) fn home_dir() -> Option<PathBuf> {
|
||||
std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")).map(PathBuf::from)
|
||||
}
|
||||
|
||||
@@ -284,6 +284,129 @@ fn saved_maven_goal_configs_take_the_configured_launcher() {
|
||||
assert_eq!(configs[2].command, "./mvnw");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_launch_refreshes_legacy_defaults_but_keeps_user_overrides() {
|
||||
use crate::apply::prepare_maven_run;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::fs::write(tmp.path().join("pom.xml"), "<project/>").unwrap();
|
||||
let home = tmp.path().join("new-maven");
|
||||
std::fs::create_dir_all(home.join("bin")).unwrap();
|
||||
let launcher = home.join("bin").join(if cfg!(windows) { "mvn.cmd" } else { "mvn" });
|
||||
std::fs::write(&launcher, "").unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.home = home.to_string_lossy().into_owned();
|
||||
s.languages.java.maven.user_settings = "new-settings.xml".into();
|
||||
s.languages.java.maven.local_repository = "new-repo".into();
|
||||
let options = maven_options(tmp.path(), &s);
|
||||
|
||||
for id in [
|
||||
"maven:goal:pom.xml:test",
|
||||
"maven:test",
|
||||
"context:maven:test:src/test/java/ExampleTest.java",
|
||||
"context:target:context:maven:test:src/test/java/ExampleTest.java:ExampleTest#example",
|
||||
"workspace:abc:maven:test",
|
||||
"context:target:context:workspace:abc:maven:test:src/test/java/ExampleTest.java:ExampleTest#example",
|
||||
] {
|
||||
let mut config = run(
|
||||
"maven",
|
||||
"//?/UNC/server/old-maven/bin/mvn.cmd",
|
||||
&["-s", "old-settings.xml", "-Dmaven.repo.local=old-repo", "test", "-Dtest=ExampleTest#example"],
|
||||
);
|
||||
config.id = id.into();
|
||||
config.detected = !id.starts_with("maven:goal:");
|
||||
let original = config.clone();
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(config.command, launcher.to_string_lossy(), "{id}");
|
||||
assert_eq!(
|
||||
config.args,
|
||||
[options.clone(), vec!["test".into(), "-Dtest=ExampleTest#example".into()]].concat(),
|
||||
"{id}"
|
||||
);
|
||||
// Rerunning the stored configuration reads settings again, without changing the stored input.
|
||||
s.languages.java.maven.local_repository = "other-repo".into();
|
||||
let mut rerun = original;
|
||||
prepare_maven_run(&mut rerun, tmp.path(), &s);
|
||||
assert!(rerun.args.contains(&format!("-Dmaven.repo.local={}", tmp.path().join("other-repo").display())));
|
||||
s.languages.java.maven.local_repository = "new-repo".into();
|
||||
}
|
||||
|
||||
for args in [
|
||||
vec!["-s", "own-settings.xml", "-Dmaven.repo.local=own-repo", "test"],
|
||||
vec!["--settings=own-settings.xml", "-D", "maven.repo.local=own-repo", "test"],
|
||||
] {
|
||||
let mut config = run("maven", "mvn", &args);
|
||||
config.detected = false;
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(config.command, launcher.to_string_lossy());
|
||||
assert_eq!(config.args, args);
|
||||
}
|
||||
|
||||
let mut explicit = run("maven", "/custom/mvn", &["-s", "own-settings.xml", "-Dmaven.repo.local=own-repo", "test"]);
|
||||
explicit.detected = false;
|
||||
prepare_maven_run(&mut explicit, tmp.path(), &s);
|
||||
assert_eq!(explicit.command, "/custom/mvn");
|
||||
assert_eq!(explicit.args, ["-s", "own-settings.xml", "-Dmaven.repo.local=own-repo", "test"]);
|
||||
|
||||
// A gutter configuration derived from a user-edited default must keep that user's choices.
|
||||
let mut saved = explicit.clone();
|
||||
saved.id = "maven:test".into();
|
||||
ide_run::save_user_configs(tmp.path(), &[saved]).unwrap();
|
||||
explicit.id = "context:maven:test:src/test/java/ExampleTest.java".into();
|
||||
explicit.detected = true;
|
||||
prepare_maven_run(&mut explicit, tmp.path(), &s);
|
||||
assert_eq!(explicit.command, "/custom/mvn");
|
||||
assert_eq!(explicit.args, ["-s", "own-settings.xml", "-Dmaven.repo.local=own-repo", "test"]);
|
||||
|
||||
std::fs::create_dir(tmp.path().join("child")).unwrap();
|
||||
std::fs::write(tmp.path().join("child/pom.xml"), "<project/>").unwrap();
|
||||
let mut nested = explicit.clone();
|
||||
nested.id = "maven:test:child".into();
|
||||
nested.detected = false;
|
||||
ide_run::save_user_configs(tmp.path(), &[nested]).unwrap();
|
||||
explicit.id = "context:maven:test:child:src/test/java/ExampleTest.java".into();
|
||||
prepare_maven_run(&mut explicit, tmp.path(), &s);
|
||||
assert_eq!(explicit.command, "/custom/mvn", "an edited child must not fall back to the parent's defaults");
|
||||
assert_eq!(explicit.args, ["-s", "own-settings.xml", "-Dmaven.repo.local=own-repo", "test"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_goal_keeps_explicit_settings_and_nested_wrapper() {
|
||||
use crate::apply::prepare_maven_run;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir(tmp.path().join("child")).unwrap();
|
||||
let wrapper = tmp.path().join("child").join(if cfg!(windows) { "mvnw.cmd" } else { "mvnw" });
|
||||
std::fs::write(&wrapper, "").unwrap();
|
||||
let mut config = run("maven", "old-maven", &["-s", "old-settings.xml", "-s", "own-settings.xml", "test"]);
|
||||
config.id = "maven:goal:child/pom.xml:-s own-settings.xml test".into();
|
||||
config.cwd = Some("child".into());
|
||||
config.detected = false;
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.user_settings = "new-settings.xml".into();
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(config.command, wrapper.to_string_lossy());
|
||||
assert_eq!(config.args, ["-s", "own-settings.xml", "test"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisting_a_maven_goal_keeps_explicit_options_equal_to_the_defaults() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.user_settings = tmp.path().join("settings.xml").to_string_lossy().into_owned();
|
||||
s.languages.java.maven.local_repository = tmp.path().join("repo").to_string_lossy().into_owned();
|
||||
let options = maven_options(tmp.path(), &s);
|
||||
for goals in [vec![options[2].clone(), "test".into()], [options.clone(), vec!["test".into()]].concat()] {
|
||||
let mut config = run("maven", "mvn", &[]);
|
||||
config.id = format!("maven:goal:pom.xml:{}", goals.join(" "));
|
||||
config.detected = false;
|
||||
config.args = goals.clone();
|
||||
apply_to_run_configs(std::slice::from_mut(&mut config), tmp.path(), &s);
|
||||
let kept = without_derived(&config, tmp.path(), &s);
|
||||
assert_eq!(kept.args, goals);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn language_server_settings_disable_add_and_override() {
|
||||
use crate::apply::{language_servers, server_launch_overrides, server_protocol_overrides, with_custom_servers};
|
||||
|
||||
@@ -210,11 +210,25 @@ describe("Maven runs", () => {
|
||||
});
|
||||
expect(root.cwd).toBeNull();
|
||||
expect(root.isTest).toBe(false);
|
||||
// Settings › Maven options go before the goals.
|
||||
const configured = { ...core, options: ["-s", "/m2/settings.xml"] };
|
||||
const configured = {
|
||||
...core,
|
||||
command: "//?/UNC/server/share/maven/bin/mvn.cmd",
|
||||
options: ["-s", "/old/settings.xml", "-Dmaven.repo.local=/old/repository"],
|
||||
};
|
||||
const goal = mavenGoalConfig(configured, ["test"], maven, {
|
||||
offline: false,
|
||||
skipTests: true,
|
||||
profiles: {},
|
||||
});
|
||||
expect(goal.command).toBe("mvn");
|
||||
expect(goal.args).toEqual(["test", "-DskipTests"]);
|
||||
expect(
|
||||
mavenGoalConfig(configured, ["test"], maven, { offline: false, skipTests: true, profiles: {} }).args,
|
||||
).toEqual(["-s", "/m2/settings.xml", "test", "-DskipTests"]);
|
||||
mavenGoalConfig(configured, ["-s", "/own/settings.xml", "test"], maven, {
|
||||
offline: false,
|
||||
skipTests: false,
|
||||
profiles: {},
|
||||
}).args,
|
||||
).toEqual(["-s", "/own/settings.xml", "test"]);
|
||||
});
|
||||
|
||||
it("parses typed command lines", () => {
|
||||
|
||||
@@ -426,12 +426,12 @@ export function mavenGoalConfig(
|
||||
model: Pick<MavenModel, "profiles">,
|
||||
options: MavenRunOptions,
|
||||
): RunConfig {
|
||||
const args = [...project.options, ...mavenArgs(goals, model, options)];
|
||||
const args = mavenArgs(goals, model, options);
|
||||
return {
|
||||
id: `maven:goal:${project.pom}:${goals.join(" ")}`,
|
||||
name: mavenConfigName(project, goals),
|
||||
kind: "maven",
|
||||
command: project.command,
|
||||
command: /(?:^|[/\\])mvnw(?:\.cmd|\.bat)?$/i.test(project.command) ? project.command : "mvn",
|
||||
args,
|
||||
cwd: project.dir || null,
|
||||
env: { ...project.env },
|
||||
|
||||
Reference in New Issue
Block a user