дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
feat(maven): support toolchains file and active profiles (#2151)
Adds Maven toolchains file (-t) and active profiles (-P) settings, emitted before the goals. They combine with the Maven tool window's profile checkboxes, a configuration's own -t or -P overrides them, and repeated launches do not duplicate them. The settings do not reach jdtls's import: no toolchains file or explicit profile deactivation there. Co-authored-by: misswell <[email protected]>
Цей коміт міститься в:
1 джерело
4139cb64c2
коміт
8427e48975
16 files changed
+373
-47
No files matched your search
@@ -76,7 +76,7 @@ pub fn load_linked(
|
||||
gradle_settings: &ide_core::settings::GradleSettings,
|
||||
) -> BuildModel {
|
||||
let maven = maven::has_projects(root, settings).then(|| {
|
||||
let profiles = sync::maven_profiles(cache_dir);
|
||||
let profiles = settings.profiles(&sync::maven_profiles(cache_dir));
|
||||
let repo = maven_repo.or_else(maven::default_local_repo);
|
||||
let mut m = maven::load_with_linked(root, repo, &profiles, &settings.linked_projects);
|
||||
maven::UnlinkedProjects::from_model(&m, settings).filter(&mut m);
|
||||
@@ -222,6 +222,30 @@ mod tests {
|
||||
assert!(gradle_external_roots(&root, &temp.path().join("cache"), &settings).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_profiles_and_checkbox_choices_control_modules_before_sync() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
std::fs::create_dir(root.join("extra")).unwrap();
|
||||
std::fs::write(root.join("extra/pom.xml"), "<project><artifactId>extra</artifactId></project>").unwrap();
|
||||
std::fs::write(
|
||||
root.join("pom.xml"),
|
||||
r#"<project><artifactId>root</artifactId><packaging>pom</packaging>
|
||||
<profiles><profile><id>extra</id><modules><module>extra</module></modules></profile></profiles></project>"#,
|
||||
)
|
||||
.unwrap();
|
||||
let cache = root.join("cache");
|
||||
let settings = ide_core::settings::MavenSettings { active_profiles: "extra".into(), ..Default::default() };
|
||||
let modules = |settings: &ide_core::settings::MavenSettings| {
|
||||
load_linked(root, &cache, None, settings, &Default::default()).maven.unwrap().projects[0].modules.clone()
|
||||
};
|
||||
assert!(modules(&Default::default()).is_empty());
|
||||
assert_eq!(modules(&settings), ["extra/pom.xml"]);
|
||||
assert_eq!(maven::load_linked(root, &settings).projects[0].modules, ["extra/pom.xml"]);
|
||||
save_maven_profiles(&cache, &["!extra".into()]).unwrap();
|
||||
assert!(modules(&settings).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_build_systems_and_build_files() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -1063,7 +1063,7 @@ pub fn load(root: &Path) -> MavenModel {
|
||||
|
||||
/// [`load`] with the POMs `settings` link (Add Maven Projects).
|
||||
pub fn load_linked(root: &Path, settings: &ide_core::settings::MavenSettings) -> MavenModel {
|
||||
load_with_linked(root, default_local_repo(), &[], &settings.linked_projects)
|
||||
load_with_linked(root, default_local_repo(), &settings.profiles(&[]), &settings.linked_projects)
|
||||
}
|
||||
|
||||
pub(crate) fn load_with_repo(root: &Path, local_repo: Option<PathBuf>) -> MavenModel {
|
||||
|
||||
@@ -304,7 +304,7 @@ pub fn sync_maven(
|
||||
let mut model = maven::load_with_linked(
|
||||
root,
|
||||
maven::default_local_repo(),
|
||||
&options.profiles,
|
||||
&options.maven_settings.profiles(&options.profiles),
|
||||
&options.maven_settings.linked_projects,
|
||||
);
|
||||
maven::UnlinkedProjects::from_model(&model, &options.maven_settings).filter(&mut model);
|
||||
|
||||
@@ -926,6 +926,8 @@ pub struct MavenSettings {
|
||||
pub home: String,
|
||||
/// User `settings.xml` (`-s`); empty = `~/.m2/settings.xml`.
|
||||
pub user_settings: String,
|
||||
/// Toolchains file (`-t`); empty = `~/.m2/toolchains.xml`.
|
||||
pub toolchains: String,
|
||||
/// Local repository (`-Dmaven.repo.local`); empty = from `settings.xml`.
|
||||
pub local_repository: String,
|
||||
/// Run Maven through the Maven Daemon (`mvnd`) for faster builds. mvnd embeds
|
||||
@@ -937,9 +939,25 @@ pub struct MavenSettings {
|
||||
/// Thread count of Maven builds (`-T`): a number of threads (`4`) or a core
|
||||
/// multiplier (`2C`, `2.5C`); empty = Maven's default (one thread per module).
|
||||
pub threads: String,
|
||||
/// Active Maven profiles (`-P`), comma-separated; empty leaves profiles to the POM/settings.
|
||||
pub active_profiles: String,
|
||||
}
|
||||
|
||||
impl MavenSettings {
|
||||
/// Settings profiles followed by the Maven tool window's choices.
|
||||
pub fn profiles(&self, choices: &[String]) -> Vec<String> {
|
||||
let mut profiles = Vec::new();
|
||||
for profile in self.active_profiles.split(',').chain(choices.iter().map(String::as_str)).map(str::trim) {
|
||||
if !profile.is_empty() && !profiles.iter().any(|p| p == profile) {
|
||||
profiles.push(profile.to_owned());
|
||||
}
|
||||
}
|
||||
let excluded: Vec<String> =
|
||||
profiles.iter().filter_map(|p| p.strip_prefix(['!', '-']).map(str::to_owned)).collect();
|
||||
profiles.retain(|p| !excluded.contains(p));
|
||||
profiles
|
||||
}
|
||||
|
||||
/// The `-T` value when it is one Maven accepts: a thread count (`4`) or a
|
||||
/// core multiplier with an optional decimal fraction (`1C`, `2.5C`).
|
||||
/// Anything else is dropped: a bad `-T` would make every Maven run fail.
|
||||
@@ -1399,6 +1417,24 @@ mod maven_linkage_tests {
|
||||
assert_eq!(threads("2..5C").as_deref(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_profiles_combine_settings_and_tool_window_choices() {
|
||||
let settings = MavenSettings { active_profiles: " a, b, ,a ".into(), ..Default::default() };
|
||||
assert_eq!(settings.profiles(&["!b".into(), "c".into(), "a".into()]), ["a", "!b", "c"]);
|
||||
let settings = MavenSettings { active_profiles: "!a".into(), ..Default::default() };
|
||||
assert_eq!(settings.profiles(&["a".into()]), ["!a"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_profiles_and_toolchains_round_trip_and_default_to_empty() {
|
||||
let old: MavenSettings = serde_json::from_str(r#"{"home":"/opt/maven"}"#).unwrap();
|
||||
assert!(old.toolchains.is_empty() && old.active_profiles.is_empty());
|
||||
let settings =
|
||||
MavenSettings { toolchains: "config files/toolchains.xml".into(), active_profiles: "a,b,!c".into(), ..old };
|
||||
let encoded = serde_json::to_string(&settings).unwrap();
|
||||
assert_eq!(serde_json::from_str::<MavenSettings>(&encoded).unwrap(), settings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_count_round_trips_and_defaults_to_empty() {
|
||||
// Old settings files predate the thread count; `serde(default)` keeps them loadable.
|
||||
|
||||
@@ -408,33 +408,34 @@ pub fn gradle_env(
|
||||
}
|
||||
|
||||
/// Maven options from settings, placed before the goals: `-s <settings.xml>`,
|
||||
/// `-Dmaven.repo.local=<dir>` and `-T <count>` (the thread count).
|
||||
/// `-t <toolchains.xml>`, `-Dmaven.repo.local=<dir>`, `-T <count>` and `-P <profiles>`.
|
||||
pub fn maven_options(root: &Path, s: &Settings) -> Vec<String> {
|
||||
let m = &s.languages.java.maven;
|
||||
let mut out = Vec::new();
|
||||
if let Some(file) = setting_path(root, &m.user_settings) {
|
||||
out.extend(["-s".to_owned(), file.to_string_lossy().into_owned()]);
|
||||
}
|
||||
if let Some(file) = setting_path(root, &m.toolchains) {
|
||||
out.extend(["-t".to_owned(), file.to_string_lossy().into_owned()]);
|
||||
}
|
||||
if let Some(dir) = setting_path(root, &m.local_repository) {
|
||||
out.push(format!("-Dmaven.repo.local={}", dir.to_string_lossy()));
|
||||
}
|
||||
if let Some(threads) = m.thread_count() {
|
||||
out.extend(["-T".to_owned(), threads.to_owned()]);
|
||||
}
|
||||
if let Some(profiles) = non_empty(&m.active_profiles) {
|
||||
out.extend(["-P".to_owned(), profiles.to_owned()]);
|
||||
}
|
||||
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" || arg == "-T") && args.get(i + 1).is_some() {
|
||||
i += 2;
|
||||
} else if arg.starts_with("-Dmaven.repo.local=") {
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
while i < args.len() {
|
||||
let Some(n) = maven_option_len(&args[i..]) else { break };
|
||||
i += n;
|
||||
prefixes.push(i);
|
||||
}
|
||||
prefixes
|
||||
@@ -1110,43 +1111,62 @@ fn is_python_command(cmd: &str) -> bool {
|
||||
|
||||
const PY_RUNNERS: [&str; 3] = ["uv", "poetry", "pdm"];
|
||||
|
||||
/// [`maven_options`] as the groups they are passed in (`-s <file>`, `-Dmaven.repo.local=…`,
|
||||
/// `-T <count>`).
|
||||
fn maven_option_flag(arg: &str) -> Option<&'static str> {
|
||||
for (short, long) in
|
||||
[("-s", "--settings"), ("-t", "--toolchains"), ("-T", "--threads"), ("-P", "--activate-profiles")]
|
||||
{
|
||||
if arg.starts_with(short) || arg == long || arg.starts_with(&format!("{long}=")) {
|
||||
return Some(short);
|
||||
}
|
||||
}
|
||||
arg.starts_with("-Dmaven.repo.local=").then_some("-Dmaven.repo.local")
|
||||
}
|
||||
|
||||
fn maven_option_len(args: &[String]) -> Option<usize> {
|
||||
let arg = args.first()?;
|
||||
maven_option_flag(arg)?;
|
||||
let separate = matches!(
|
||||
arg.as_str(),
|
||||
"-s" | "--settings" | "-t" | "--toolchains" | "-T" | "--threads" | "-P" | "--activate-profiles"
|
||||
);
|
||||
Some(if separate { 2.min(args.len()) } else { 1 })
|
||||
}
|
||||
|
||||
/// [`maven_options`] grouped with each option's value.
|
||||
fn maven_option_groups(options: &[String]) -> Vec<&[String]> {
|
||||
let mut groups = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < options.len() {
|
||||
let n = if options[i] == "-s" || options[i] == "-T" { 2.min(options.len() - i) } else { 1 };
|
||||
let n = maven_option_len(&options[i..]).unwrap_or(1);
|
||||
groups.push(&options[i..i + n]);
|
||||
i += n;
|
||||
}
|
||||
groups
|
||||
}
|
||||
|
||||
/// Whether `args` set what `group` sets: a user settings file, the local
|
||||
/// repository, or the thread count.
|
||||
/// Whether the configuration sets the same option, including attached and long forms.
|
||||
fn sets_maven_option(args: &[String], group: &[String]) -> bool {
|
||||
match group.first().map(String::as_str) {
|
||||
Some("-s") => args.iter().any(|a| a == "-s" || a == "--settings" || a.starts_with("--settings=")),
|
||||
// `-T 4`, `--threads 4`, `-T4` and `--threads=4` all set the thread count.
|
||||
Some("-T") => args.iter().any(|a| {
|
||||
a == "-T" || a == "--threads" || a.starts_with("--threads=") || (a.starts_with("-T") && a.len() > 2)
|
||||
}),
|
||||
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,
|
||||
}
|
||||
let Some(flag) = group.first().and_then(|a| maven_option_flag(a)) else { return true };
|
||||
args.iter().any(|a| maven_option_flag(a) == Some(flag))
|
||||
|| (flag == "-Dmaven.repo.local"
|
||||
&& args.windows(2).any(|a| a[0] == "-D" && a[1].starts_with("maven.repo.local=")))
|
||||
}
|
||||
|
||||
/// Settings › Maven's options ahead of the goals, each unless the configuration sets it itself.
|
||||
fn add_maven_options(args: &mut Vec<String>, options: &[String]) {
|
||||
let missing: Vec<String> =
|
||||
maven_option_groups(options).into_iter().filter(|g| !sets_maven_option(args, g)).flatten().cloned().collect();
|
||||
/// Settings options precede the goals. Tool-window checkbox profiles are additive;
|
||||
/// profiles explicitly typed in the configuration replace the Settings profiles.
|
||||
fn add_maven_options(args: &mut Vec<String>, options: &[String], additive_profiles: bool) {
|
||||
let missing: Vec<String> = maven_option_groups(options)
|
||||
.into_iter()
|
||||
.filter(|g| {
|
||||
if additive_profiles && g[0] == "-P" {
|
||||
!args.windows(g.len()).any(|w| w == *g)
|
||||
} else {
|
||||
!sets_maven_option(args, g)
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect();
|
||||
args.splice(0..0, missing);
|
||||
}
|
||||
|
||||
@@ -1315,7 +1335,11 @@ pub fn apply_to_run_configs(configs: &mut [RunConfig], root: &Path, s: &Settings
|
||||
{
|
||||
c.command = mvn.to_string_lossy().into_owned();
|
||||
}
|
||||
add_maven_options(&mut c.args, &mvn_options);
|
||||
let additive_profiles = generated_maven_goal(c)
|
||||
&& c.id.split_once(".xml:").is_some_and(|(_, goals)| {
|
||||
!goals.split_whitespace().any(|a| maven_option_flag(a) == Some("-P"))
|
||||
});
|
||||
add_maven_options(&mut c.args, &mvn_options, additive_profiles);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,15 @@ use std::path::{Path, PathBuf};
|
||||
const PREFIX: &str = "jdtls-maven-settings-";
|
||||
|
||||
/// Points jdtls at a user `settings.xml` activating `profiles` (`-P` entries of the
|
||||
/// last Maven sync), written to `dir`. Through `settings`, so a running server
|
||||
/// last Maven sync), combined with Settings profiles and written to `dir`. Through `settings`, so a running server
|
||||
/// re-imports its Maven projects instead of restarting. `!id` entries can't be
|
||||
/// expressed in `settings.xml` and are left to the POMs' own activation.
|
||||
pub fn configure_jdtls(cfg: &mut LanguageServerConfig, root: &Path, s: &Settings, profiles: &[String], dir: &Path) {
|
||||
if cfg.name != "jdtls" {
|
||||
return;
|
||||
}
|
||||
let active: Vec<&str> =
|
||||
profiles.iter().map(|p| p.trim()).filter(|p| !p.is_empty() && !p.starts_with('!')).collect();
|
||||
let profiles = s.languages.java.maven.profiles(profiles);
|
||||
let active: Vec<&str> = profiles.iter().map(String::as_str).filter(|p| !p.starts_with(['!', '-'])).collect();
|
||||
let Some(file) = (!active.is_empty()).then(|| write_settings(root, s, &active, dir)).flatten() else {
|
||||
remove_generated(dir, None);
|
||||
return;
|
||||
@@ -192,6 +192,30 @@ mod tests {
|
||||
assert!(with_active_profiles("<settings></settings>", &["a&b"]).unwrap().contains("a&b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jdtls_combines_settings_profiles_and_checkbox_choices() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.user_settings = "config files/settings.xml".into();
|
||||
s.languages.java.maven.active_profiles = " a, c, !d, a ".into();
|
||||
std::fs::create_dir_all(tmp.path().join("config files")).unwrap();
|
||||
std::fs::write(tmp.path().join("config files/settings.xml"), "<settings><mirrors/></settings>").unwrap();
|
||||
let mut cfg = jdtls();
|
||||
configure_jdtls(&mut cfg, tmp.path(), &s, &["b".into(), "!c".into(), "d".into()], &tmp.path().join("cache"));
|
||||
let text = std::fs::read_to_string(user_settings(&cfg).unwrap()).unwrap();
|
||||
assert!(text.contains("<mirrors/>"));
|
||||
assert_eq!(text.matches("<activeProfile>a</activeProfile>").count(), 1);
|
||||
assert!(text.contains("<activeProfile>b</activeProfile>"));
|
||||
assert!(!text.contains("<activeProfile>c</activeProfile>"));
|
||||
assert!(!text.contains("<activeProfile>d</activeProfile>"));
|
||||
|
||||
let mut cfg = jdtls();
|
||||
configure_jdtls(&mut cfg, tmp.path(), &s, &[], &tmp.path().join("cache"));
|
||||
let text = std::fs::read_to_string(user_settings(&cfg).unwrap()).unwrap();
|
||||
assert!(text.contains("<activeProfile>a</activeProfile>"));
|
||||
assert!(text.contains("<activeProfile>c</activeProfile>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jdtls_imports_with_the_synced_profiles() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -343,20 +343,27 @@ fn project_jdk_and_maven_settings() {
|
||||
s.languages.java.jdk = "jdk17/bin/java".into();
|
||||
s.languages.java.maven.home = maven.to_string_lossy().into_owned();
|
||||
s.languages.java.maven.user_settings = "/m2/settings.xml".into();
|
||||
s.languages.java.maven.toolchains = "/m2/toolchains.xml".into();
|
||||
s.languages.java.maven.local_repository = "/m2/repo".into();
|
||||
assert_eq!(project_jdk(tmp.path(), &s).map(|j| j.major), Some(17));
|
||||
assert_eq!(maven_command(tmp.path(), &s), Some(maven.join("bin/mvn")));
|
||||
assert_eq!(maven_options(tmp.path(), &s), ["-s", "/m2/settings.xml", "-Dmaven.repo.local=/m2/repo"]);
|
||||
assert_eq!(
|
||||
maven_options(tmp.path(), &s),
|
||||
["-s", "/m2/settings.xml", "-t", "/m2/toolchains.xml", "-Dmaven.repo.local=/m2/repo"]
|
||||
);
|
||||
|
||||
let mut configs = vec![run("maven", "./mvnw", &["package"]), run("gradle", "./gradlew", &["build"])];
|
||||
apply_to_run_configs(&mut configs, tmp.path(), &s);
|
||||
let home = project_jdk(tmp.path(), &s).unwrap().home.to_string_lossy().into_owned();
|
||||
assert_eq!(configs[0].command, maven.join("bin/mvn").to_string_lossy());
|
||||
assert_eq!(configs[0].args, ["-s", "/m2/settings.xml", "-Dmaven.repo.local=/m2/repo", "package"]);
|
||||
assert_eq!(
|
||||
configs[0].args,
|
||||
["-s", "/m2/settings.xml", "-t", "/m2/toolchains.xml", "-Dmaven.repo.local=/m2/repo", "package"]
|
||||
);
|
||||
assert_eq!(configs[0].env["JAVA_HOME"], home);
|
||||
assert_eq!(configs[1].env["JAVA_HOME"], home);
|
||||
apply_to_run_configs(&mut configs, tmp.path(), &s);
|
||||
assert_eq!(configs[0].args.len(), 4, "applied once");
|
||||
assert_eq!(configs[0].args.len(), 6, "applied once");
|
||||
|
||||
s.languages.java.jdk = "missing".into();
|
||||
assert_eq!(project_jdk(tmp.path(), &s), None);
|
||||
@@ -448,6 +455,122 @@ fn maven_daemon_uses_the_runner_jre_version() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_options_carry_active_profiles_into_the_command_line() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.active_profiles = "dev, integration".into();
|
||||
assert_eq!(maven_options(tmp.path(), &s), ["-P", "dev, integration"]);
|
||||
s.languages.java.maven.active_profiles = " ".into();
|
||||
assert!(maven_options(tmp.path(), &s).is_empty());
|
||||
|
||||
// A run configuration's own -P wins; repeated application does not duplicate the settings group.
|
||||
s.languages.java.maven.active_profiles = "dev".into();
|
||||
let mut configs = vec![run("maven", "./mvnw", &["package"])];
|
||||
apply_to_run_configs(&mut configs, tmp.path(), &s);
|
||||
assert_eq!(configs[0].args, ["-P", "dev", "package"]);
|
||||
apply_to_run_configs(&mut configs, tmp.path(), &s);
|
||||
assert_eq!(configs[0].args, ["-P", "dev", "package"]);
|
||||
let mut configured = vec![run("maven", "./mvnw", &["-P", "prod", "package"])];
|
||||
apply_to_run_configs(&mut configured, tmp.path(), &s);
|
||||
assert_eq!(configured[0].args, ["-P", "prod", "package"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_profile_and_toolchain_overrides_accept_every_option_form() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.active_profiles = "a".into();
|
||||
s.languages.java.maven.toolchains = "config files/toolchains.xml".into();
|
||||
let toolchains = tmp.path().join("config files/toolchains.xml").to_string_lossy().into_owned();
|
||||
assert_eq!(maven_options(tmp.path(), &s), ["-t", &toolchains, "-P", "a"]);
|
||||
for own in [
|
||||
vec!["-P", "b,!c", "-t", "own file.xml"],
|
||||
vec!["-Pb,!c", "-town file.xml"],
|
||||
vec!["--activate-profiles", "b,!c", "--toolchains", "own file.xml"],
|
||||
vec!["--activate-profiles=b,!c", "--toolchains=own file.xml"],
|
||||
] {
|
||||
let mut args = own.clone();
|
||||
args.push("test");
|
||||
let mut config = run("maven", "mvn", &args);
|
||||
config.detected = false;
|
||||
for _ in 0..3 {
|
||||
apply_to_run_configs(std::slice::from_mut(&mut config), tmp.path(), &s);
|
||||
assert_eq!(config.args, args, "configuration options win: {own:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_goal_profiles_combine_settings_and_checkboxes_but_keep_explicit_overrides() {
|
||||
use crate::apply::prepare_maven_run;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.active_profiles = "a".into();
|
||||
s.languages.java.maven.toolchains = "config files/toolchains.xml".into();
|
||||
let mut config = run("maven", "mvn", &["test", "-Pb,!c"]);
|
||||
config.id = "maven:goal:pom.xml:test".into();
|
||||
config.detected = false;
|
||||
let mut expected = maven_options(tmp.path(), &s);
|
||||
expected.extend(["test".into(), "-Pb,!c".into()]);
|
||||
for _ in 0..3 {
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(config.args, expected);
|
||||
apply_to_run_configs(std::slice::from_mut(&mut config), tmp.path(), &s);
|
||||
assert_eq!(config.args, expected);
|
||||
}
|
||||
s.languages.java.maven.active_profiles = "new".into();
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(&config.args[2..], ["-P", "new", "test", "-Pb,!c"]);
|
||||
|
||||
for own in
|
||||
[vec!["-P", "prod"], vec!["-Pprod"], vec!["--activate-profiles", "prod"], vec!["--activate-profiles=prod"]]
|
||||
{
|
||||
let mut args = vec!["test"];
|
||||
args.extend(own);
|
||||
let mut config = run("maven", "mvn", &args);
|
||||
config.id = format!("maven:goal:pom.xml:{}", args.join(" "));
|
||||
config.detected = false;
|
||||
for _ in 0..3 {
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(&config.args[2..], args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_launch_refreshes_attached_and_long_settings_prefixes() {
|
||||
use crate::apply::prepare_maven_run;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::fs::write(tmp.path().join("pom.xml"), "<project/>").unwrap();
|
||||
let mut s = Settings::default();
|
||||
s.languages.java.maven.active_profiles = "new".into();
|
||||
s.languages.java.maven.toolchains = "new toolchains.xml".into();
|
||||
for prefix in [
|
||||
vec!["-told file.xml", "-Pa"],
|
||||
vec!["--toolchains", "old file.xml", "--activate-profiles", "a"],
|
||||
vec!["--toolchains=old file.xml", "--activate-profiles=a"],
|
||||
] {
|
||||
for id in
|
||||
["maven:goal:pom.xml:test", "maven:test", "context:maven:test:ExampleTest", "workspace:abc:maven:test"]
|
||||
{
|
||||
let mut args = prefix.clone();
|
||||
args.push("test");
|
||||
let mut config = run("maven", "mvn", &args);
|
||||
config.id = id.into();
|
||||
config.detected = !id.starts_with("maven:goal:");
|
||||
let mut expected = maven_options(tmp.path(), &s);
|
||||
expected.push("test".into());
|
||||
for _ in 0..3 {
|
||||
prepare_maven_run(&mut config, tmp.path(), &s);
|
||||
assert_eq!(config.args, expected, "{id}: {prefix:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_options_carry_the_thread_count_into_the_command_line() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -5610,6 +5610,8 @@ export type MavenSettings = {
|
||||
home?: string,
|
||||
/** User `settings.xml` (`-s`); empty = `~/.m2/settings.xml`. */
|
||||
userSettings?: string,
|
||||
/** Toolchains file (`-t`); empty = `~/.m2/toolchains.xml`. */
|
||||
toolchains?: string,
|
||||
/** Local repository (`-Dmaven.repo.local`); empty = from `settings.xml`. */
|
||||
localRepository?: string,
|
||||
/**
|
||||
@@ -5627,6 +5629,8 @@ export type MavenSettings = {
|
||||
* multiplier (`2C`, `2.5C`); empty = Maven's default (one thread per module).
|
||||
*/
|
||||
threads?: string,
|
||||
/** Active Maven profiles (`-P`), comma-separated; empty leaves profiles to the POM/settings. */
|
||||
activeProfiles?: string,
|
||||
};
|
||||
|
||||
/** Tools › MCP Server: agents use the IDE's built-in tools. */
|
||||
|
||||
@@ -184,6 +184,19 @@ describe("Maven tree", () => {
|
||||
describe("Maven runs", () => {
|
||||
const core = maven.projects[1]!;
|
||||
|
||||
it.each([["-P", "prod"], ["-Pprod"], ["--activate-profiles", "prod"], ["--activate-profiles=prod"]])(
|
||||
"keeps explicit Maven profiles %j",
|
||||
(...profiles) => {
|
||||
const goals = ["test", ...profiles];
|
||||
const config = mavenGoalConfig(core, goals, maven, {
|
||||
offline: false,
|
||||
skipTests: false,
|
||||
profiles: { ci: true, dev: false },
|
||||
});
|
||||
expect(config.args).toEqual(goals);
|
||||
},
|
||||
);
|
||||
|
||||
it("passes goals, offline, skip tests and changed profiles", () => {
|
||||
expect(profilesArg(maven.profiles, {})).toBeNull();
|
||||
expect(profilesArg(maven.profiles, { ci: true, dev: false })).toBe("!dev,ci");
|
||||
|
||||
@@ -455,7 +455,11 @@ export function mavenArgs(
|
||||
if (options.offline && !has("-o") && !has("--offline")) args.push("-o");
|
||||
if (options.skipTests && !has("-DskipTests")) args.push("-DskipTests");
|
||||
const profiles = profilesArg(model.profiles, options.profiles);
|
||||
if (profiles && !args.some((a) => a.startsWith("-P"))) args.push(`-P${profiles}`);
|
||||
if (
|
||||
profiles &&
|
||||
!args.some((a) => a.startsWith("-P") || a === "--activate-profiles" || a.startsWith("--activate-profiles="))
|
||||
)
|
||||
args.push(`-P${profiles}`);
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,36 @@ describe("Settings dialog", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("persists Maven toolchains and profiles in project settings", async () => {
|
||||
useProject.setState({ project: { root: "/project" } as never });
|
||||
const dialog = await openDialog("build.maven");
|
||||
fireEvent.change(within(dialog).getByLabelText("Toolchains file"), {
|
||||
target: { value: "config files/toolchains.xml" },
|
||||
});
|
||||
fireEvent.change(within(dialog).getByLabelText("Active profiles"), { target: { value: "a, b,!c" } });
|
||||
await act(async () => fireEvent.click(within(dialog).getByRole("button", { name: "Apply" })));
|
||||
expect(settingsSet.mock.calls).toEqual(
|
||||
expect.arrayContaining([
|
||||
["project", "languages.java.maven.toolchains", '"config files/toolchains.xml"'],
|
||||
["project", "languages.java.maven.activeProfiles", '"a, b,!c"'],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("finds the Maven active profiles setting", async () => {
|
||||
useProject.setState({ project: { root: "/project" } as never });
|
||||
const dialog = await openDialog("build.maven");
|
||||
fireEvent.change(within(dialog).getByLabelText("Search settings"), { target: { value: "Active profiles" } });
|
||||
expect(within(dialog).getByLabelText("Active profiles")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("finds the Maven toolchains setting", async () => {
|
||||
useProject.setState({ project: { root: "/project" } as never });
|
||||
const dialog = await openDialog("build.maven");
|
||||
fireEvent.change(within(dialog).getByLabelText("Search settings"), { target: { value: "Toolchains" } });
|
||||
expect(within(dialog).getByLabelText("Toolchains file")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("searches pages and highlights matching option labels", async () => {
|
||||
const dialog = await openDialog("appearance");
|
||||
fireEvent.change(within(dialog).getByLabelText("Search settings"), { target: { value: "soft wrap" } });
|
||||
|
||||
@@ -48,12 +48,14 @@ describe("languages settings", () => {
|
||||
runnerJre: "/opt/jdk-21",
|
||||
home: "/opt/maven",
|
||||
userSettings: "",
|
||||
toolchains: "",
|
||||
localRepository: "",
|
||||
unlinkedProjects: [],
|
||||
linkedProjects: [],
|
||||
daemon: false,
|
||||
mvndHome: "/opt/mvnd",
|
||||
threads: "2C",
|
||||
activeProfiles: "",
|
||||
});
|
||||
expect(validateSettings({}).languages).toEqual(DEFAULT_LANGUAGES);
|
||||
expect(validateLanguages({ java: { maven: { threads: 4 } } }).java.maven.threads).toBe("");
|
||||
|
||||
@@ -494,6 +494,7 @@ export function GradlePage() {
|
||||
export function MavenPage() {
|
||||
const [daemon, setDaemon] = useSetting<boolean>("languages.java.maven.daemon");
|
||||
const [threads, setThreads] = useSetting<string>("languages.java.maven.threads");
|
||||
const [activeProfiles, setActiveProfiles] = useSetting<string>("languages.java.maven.activeProfiles");
|
||||
const id = useId();
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
@@ -527,6 +528,14 @@ export function MavenPage() {
|
||||
file
|
||||
hint="Passed as -s (mirrors, servers, proxies)."
|
||||
/>
|
||||
<PathInput
|
||||
k="languages.java.maven.toolchains"
|
||||
label="Toolchains file"
|
||||
placeholder="~/.m2/toolchains.xml"
|
||||
title="Select Maven toolchains.xml"
|
||||
file
|
||||
hint="Passed as -t; selects the JDKs Maven compiles with."
|
||||
/>
|
||||
<PathInput
|
||||
k="languages.java.maven.localRepository"
|
||||
label="Local repository"
|
||||
@@ -534,6 +543,21 @@ export function MavenPage() {
|
||||
title="Select Local Repository"
|
||||
hint="Passed as -Dmaven.repo.local."
|
||||
/>
|
||||
<Row
|
||||
label="Active profiles"
|
||||
htmlFor={`${id}-profiles`}
|
||||
hint="Comma-separated profiles (-P), combined with Maven tool window choices; !id excludes a profile. A run configuration’s own -P replaces these profiles."
|
||||
>
|
||||
<Input
|
||||
id={`${id}-profiles`}
|
||||
mono
|
||||
className={styles.pathInput}
|
||||
value={activeProfiles}
|
||||
placeholder="dev,integration"
|
||||
onChange={(e) => setActiveProfiles(e.target.value)}
|
||||
aria-label="Active profiles"
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
label="Thread count"
|
||||
htmlFor={id}
|
||||
|
||||
@@ -175,6 +175,7 @@ export interface LanguagesValid {
|
||||
runnerJre: string;
|
||||
home: string;
|
||||
userSettings: string;
|
||||
toolchains: string;
|
||||
localRepository: string;
|
||||
unlinkedProjects: string[];
|
||||
linkedProjects: string[];
|
||||
@@ -182,6 +183,8 @@ export interface LanguagesValid {
|
||||
mvndHome: string;
|
||||
/** `-T`: a thread count (4) or a core multiplier (2C, 2.5C); empty = Maven's default. */
|
||||
threads: string;
|
||||
/** Comma-separated `-P` profiles; empty = POM/settings defaults. */
|
||||
activeProfiles: string;
|
||||
};
|
||||
};
|
||||
/** Preview font size in px; `null` follows the editor font size. */
|
||||
@@ -242,12 +245,14 @@ export const DEFAULT_LANGUAGES: LanguagesValid = {
|
||||
runnerJre: "",
|
||||
home: "",
|
||||
userSettings: "",
|
||||
toolchains: "",
|
||||
localRepository: "",
|
||||
unlinkedProjects: [],
|
||||
linkedProjects: [],
|
||||
daemon: false,
|
||||
mvndHome: "",
|
||||
threads: "",
|
||||
activeProfiles: "",
|
||||
},
|
||||
},
|
||||
markdown: { previewFontSize: null },
|
||||
@@ -400,12 +405,14 @@ export function validateLanguages(raw: unknown): LanguagesValid {
|
||||
runnerJre: text(maven.runnerJre, d.java.maven.runnerJre),
|
||||
home: text(maven.home, d.java.maven.home),
|
||||
userSettings: text(maven.userSettings, d.java.maven.userSettings),
|
||||
toolchains: text(maven.toolchains, d.java.maven.toolchains),
|
||||
localRepository: text(maven.localRepository, d.java.maven.localRepository),
|
||||
unlinkedProjects: folders(maven.unlinkedProjects),
|
||||
linkedProjects: folders(maven.linkedProjects),
|
||||
daemon: bool(maven.daemon, d.java.maven.daemon),
|
||||
mvndHome: text(maven.mvndHome, d.java.maven.mvndHome),
|
||||
threads: text(maven.threads, d.java.maven.threads),
|
||||
activeProfiles: text(maven.activeProfiles, d.java.maven.activeProfiles),
|
||||
},
|
||||
},
|
||||
markdown: { previewFontSize: previewSize(markdown.previewFontSize) },
|
||||
|
||||
@@ -514,5 +514,11 @@
|
||||
"Choose the projects to link to this workspace.": "Choose the projects to link to this workspace.",
|
||||
"Already linked": "Already linked",
|
||||
"Searching for projects…": "Searching for projects…",
|
||||
"Search stopped at the entry limit. More projects may exist; choose a smaller folder to search again.": "Search stopped at the entry limit. More projects may exist; choose a smaller folder to search again."
|
||||
"Search stopped at the entry limit. More projects may exist; choose a smaller folder to search again.": "Search stopped at the entry limit. More projects may exist; choose a smaller folder to search again.",
|
||||
"Toolchains file": "Toolchains file",
|
||||
"Passed as -t; selects the JDKs Maven compiles with.": "Passed as -t; selects the JDKs Maven compiles with.",
|
||||
"Select Maven toolchains.xml": "Select Maven toolchains.xml",
|
||||
"dev,integration": "dev,integration",
|
||||
"Comma-separated profiles (-P), combined with Maven tool window choices; !id excludes a profile. A run configuration’s own -P replaces these profiles.": "Comma-separated profiles (-P), combined with Maven tool window choices; !id excludes a profile. A run configuration’s own -P replaces these profiles.",
|
||||
"Active profiles": "Active profiles"
|
||||
}
|
||||
@@ -3322,7 +3322,7 @@
|
||||
"e.g. -Xmx1g -Dkey=value --add-opens java.base/java.lang=ALL-UNNAMED": "例如 -Xmx1g -Dkey=value --add-opens java.base/java.lang=ALL-UNNAMED",
|
||||
"Main class": "主类",
|
||||
"e.g. com.example.Application": "例如 com.example.Application",
|
||||
"Active profiles": "活动配置文件",
|
||||
"Active profiles": "活动 profiles",
|
||||
"e.g. dev,local": "例如 dev,local",
|
||||
"Comma separated; passed as --spring.profiles.active": "以逗号分隔;作为 --spring.profiles.active 传递",
|
||||
"Comma separated; passed as -Dspring-boot.run.profiles": "以逗号分隔;作为 -Dspring-boot.run.profiles 传递",
|
||||
@@ -6850,5 +6850,10 @@
|
||||
"Choose the projects to link to this workspace.": "选择要关联到此工作区的项目。",
|
||||
"Already linked": "已关联",
|
||||
"Searching for projects…": "正在查找项目…",
|
||||
"Search stopped at the entry limit. More projects may exist; choose a smaller folder to search again.": "搜索已达到条目数量上限,可能还有更多项目。请选择较小的文件夹重新搜索。"
|
||||
"Search stopped at the entry limit. More projects may exist; choose a smaller folder to search again.": "搜索已达到条目数量上限,可能还有更多项目。请选择较小的文件夹重新搜索。",
|
||||
"Toolchains file": "Toolchains 文件",
|
||||
"Passed as -t; selects the JDKs Maven compiles with.": "以 -t 传入,用于选择 Maven 编译使用的 JDK。",
|
||||
"Select Maven toolchains.xml": "选择 Maven toolchains.xml",
|
||||
"dev,integration": "开发,集成",
|
||||
"Comma-separated profiles (-P), combined with Maven tool window choices; !id excludes a profile. A run configuration’s own -P replaces these profiles.": "以逗号分隔的 profiles(-P),与 Maven 工具窗口中的选择合并;!id 排除指定 profile。运行配置自身的 -P 会替代这些 profiles。"
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача