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]>
Цей коміт міститься в:
misswellтаmisswell authored and GitHub committed 2026-11-09 01:25:06 +08:00
1 джерело 4139cb64c2
коміт 8427e48975
16 files changed
+373 -47

No files matched your search

+25 -1
Переглянути файл
@@ -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();
+1 -1
Переглянути файл
@@ -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 {
+1 -1
Переглянути файл
@@ -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);
+36
Переглянути файл
@@ -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.
+58 -34
Переглянути файл
@@ -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);
}
_ => {}
}
+27 -3
Переглянути файл
@@ -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&amp;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();
+126 -3
Переглянути файл
@@ -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();
+4
Переглянути файл
@@ -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. */
+13
Переглянути файл
@@ -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");
+5 -1
Переглянути файл
@@ -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;
}
+30
Переглянути файл
@@ -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" } });
+2
Переглянути файл
@@ -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("");
+24
Переглянути файл
@@ -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}
+7
Переглянути файл
@@ -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) },
+7 -1
Переглянути файл
@@ -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"
}
+7 -2
Переглянути файл
@@ -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。"
}