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:
l0ng-ai
2026-11-04 12:35:01 +08:00
committed by GitHub
parent 0a70b0af01
commit 60969eccb6
9 changed files with 400 additions and 23 deletions
+6 -4
View File
@@ -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);
+40
View File
@@ -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();
+95 -5
View File
@@ -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 {
+84 -3
View File
@@ -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());
}
}
+2 -5
View File
@@ -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);
}
}
+30
View File
@@ -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)
}
+123
View File
@@ -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};
+18 -4
View File
@@ -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", () => {
+2 -2
View File
@@ -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 },