mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
feat(build): add Maven thread count setting (#1800)
Signed-off-by: Inf1mum <[email protected]>
This commit is contained in:
1 parent
ebac061a1f
commit
b5974ab3ac
10 files changed
+183
-9
No files matched your search
@@ -95,6 +95,24 @@ fn maven_sync_goals(effective: &Path, offline: bool) -> Vec<String> {
|
||||
"-Dverbose".into(),
|
||||
]
|
||||
}
|
||||
|
||||
/// The settings' Maven options for the sync runs: the thread count (`-T`, also
|
||||
/// `--threads`) is left out, because the sync pins its own `-T1` — its console
|
||||
/// output is parsed as one stream, which a parallel build interleaves.
|
||||
fn sync_maven_options(options: &[String]) -> Vec<String> {
|
||||
let mut out = Vec::with_capacity(options.len());
|
||||
let mut args = options.iter();
|
||||
while let Some(arg) = args.next() {
|
||||
if arg == "-T" || arg == "--threads" {
|
||||
args.next(); // the count itself
|
||||
} else if (arg.starts_with("-T") && arg.len() > 2) || arg.starts_with("--threads=") {
|
||||
// `--threads=4` / `-T4`: the count is part of the argument.
|
||||
} else {
|
||||
out.push(arg.clone());
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
const GRADLE_CACHE: &str = "gradle.json";
|
||||
const MAVEN_PROFILES: &str = "maven-profiles.json";
|
||||
|
||||
@@ -324,7 +342,7 @@ pub fn sync_maven(
|
||||
// `dependency:tree` output of concurrent modules, which the sync parses
|
||||
// as one stream. mvn is serial anyway, so this only pins the daemon.
|
||||
let mut args: Vec<String> = vec!["-B".into(), "-fae".into(), "-Dstyle.color=never".into(), "-T1".into()];
|
||||
args.extend(options.maven_options.iter().cloned());
|
||||
args.extend(sync_maven_options(&options.maven_options));
|
||||
if options.offline {
|
||||
args.push("-o".into());
|
||||
}
|
||||
@@ -831,6 +849,19 @@ mod tests {
|
||||
assert_eq!(error_summary("one\nlast line\n\n"), "last line");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_sync_leaves_out_the_settings_thread_count() {
|
||||
// The sync pins its own `-T1`; the user's `-T` (Settings › Maven) would win
|
||||
// over it on the mvnd command line and interleave the parsed output.
|
||||
let options = ["-s".to_owned(), "settings.xml".to_owned(), "-T".to_owned(), "4".to_owned()];
|
||||
assert_eq!(sync_maven_options(&options), ["-s", "settings.xml"]);
|
||||
let options = ["--threads".to_owned(), "2C".to_owned(), "-Dmaven.repo.local=/m2".to_owned()];
|
||||
assert_eq!(sync_maven_options(&options), ["-Dmaven.repo.local=/m2"]);
|
||||
assert_eq!(sync_maven_options(&["-T4".to_owned()]), Vec::<String>::new());
|
||||
assert_eq!(sync_maven_options(&["--threads=4".to_owned()]), Vec::<String>::new());
|
||||
assert!(sync_maven_options(&[]).is_empty());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn maven_sync_runs_the_daemon_command_serially() {
|
||||
|
||||
@@ -919,6 +919,24 @@ pub struct MavenSettings {
|
||||
/// Maven Daemon home (or its `mvnd` executable); empty = `$MVND_HOME`, else
|
||||
/// `mvnd` on `PATH` / common install dirs. Only used when `daemon` is on.
|
||||
pub mvnd_home: String,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl MavenSettings {
|
||||
/// 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.
|
||||
pub fn thread_count(&self) -> Option<&str> {
|
||||
let value = self.threads.trim();
|
||||
let number = value.strip_suffix('C').unwrap_or(value);
|
||||
let mut decimal = number.split('.');
|
||||
let well_formed = number.parse::<f64>().is_ok_and(|n| n > 0.0)
|
||||
&& decimal.clone().count() <= 2
|
||||
&& decimal.all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit()));
|
||||
well_formed.then_some(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
|
||||
@@ -1161,6 +1179,41 @@ mod maven_linkage_tests {
|
||||
assert!(encoded.contains("\"daemon\":true") && encoded.contains("mvndHome"));
|
||||
assert_eq!(serde_json::from_str::<MavenSettings>(&encoded).unwrap(), settings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_count_takes_counts_and_core_multipliers_only() {
|
||||
let threads = |t: &str| {
|
||||
let m = MavenSettings { threads: t.into(), ..Default::default() };
|
||||
m.thread_count().map(str::to_owned)
|
||||
};
|
||||
// Empty = Maven's default (one thread per module).
|
||||
assert_eq!(threads("").as_deref(), None);
|
||||
assert_eq!(threads(" ").as_deref(), None);
|
||||
// A thread count, and a core multiplier whose fraction may be decimal.
|
||||
assert_eq!(threads("4").as_deref(), Some("4"));
|
||||
assert_eq!(threads(" 2 ").as_deref(), Some("2"));
|
||||
assert_eq!(threads("1C").as_deref(), Some("1C"));
|
||||
assert_eq!(threads("2.0C").as_deref(), Some("2.0C"));
|
||||
assert_eq!(threads("0.5C").as_deref(), Some("0.5C"));
|
||||
// Everything else a `-T` would make every Maven run reject is dropped.
|
||||
assert_eq!(threads("abc").as_deref(), None);
|
||||
assert_eq!(threads("4x").as_deref(), None);
|
||||
assert_eq!(threads("0").as_deref(), None);
|
||||
assert_eq!(threads("-4").as_deref(), None);
|
||||
assert_eq!(threads("1e3").as_deref(), None);
|
||||
assert_eq!(threads("2..5C").as_deref(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_count_round_trips_and_defaults_to_empty() {
|
||||
// Old settings files predate the thread count; `serde(default)` keeps them loadable.
|
||||
let old: MavenSettings = serde_json::from_str(r#"{"home":"/opt/maven"}"#).unwrap();
|
||||
assert!(old.threads.is_empty() && old.thread_count().is_none());
|
||||
let settings = MavenSettings { threads: "2C".into(), ..old };
|
||||
let encoded = serde_json::to_string(&settings).unwrap();
|
||||
assert!(encoded.contains("\"threads\":\"2C\""));
|
||||
assert_eq!(serde_json::from_str::<MavenSettings>(&encoded).unwrap(), settings);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -370,8 +370,8 @@ pub fn gradle_env(
|
||||
env
|
||||
}
|
||||
|
||||
/// Maven options from settings, placed before the goals: `-s <settings.xml>`
|
||||
/// and `-Dmaven.repo.local=<dir>`.
|
||||
/// Maven options from settings, placed before the goals: `-s <settings.xml>`,
|
||||
/// `-Dmaven.repo.local=<dir>` and `-T <count>` (the thread count).
|
||||
pub fn maven_options(root: &Path, s: &Settings) -> Vec<String> {
|
||||
let m = &s.languages.java.maven;
|
||||
let mut out = Vec::new();
|
||||
@@ -381,6 +381,9 @@ pub fn maven_options(root: &Path, s: &Settings) -> Vec<String> {
|
||||
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()]);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -388,7 +391,7 @@ 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() {
|
||||
if (arg == "-s" || arg == "-T") && args.get(i + 1).is_some() {
|
||||
i += 2;
|
||||
} else if arg.starts_with("-Dmaven.repo.local=") {
|
||||
i += 1;
|
||||
@@ -1043,22 +1046,28 @@ 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=…`).
|
||||
/// [`maven_options`] as the groups they are passed in (`-s <file>`, `-Dmaven.repo.local=…`,
|
||||
/// `-T <count>`).
|
||||
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" { 2.min(options.len() - i) } else { 1 };
|
||||
let n = if options[i] == "-s" || options[i] == "-T" { 2.min(options.len() - i) } else { 1 };
|
||||
groups.push(&options[i..i + n]);
|
||||
i += n;
|
||||
}
|
||||
groups
|
||||
}
|
||||
|
||||
/// Whether `args` set what `group` sets: a user settings file, or the local repository.
|
||||
/// Whether `args` set what `group` sets: a user settings file, the local
|
||||
/// repository, or the thread count.
|
||||
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)
|
||||
|
||||
@@ -292,6 +292,41 @@ fn project_jdk_and_maven_settings() {
|
||||
assert_eq!(project_jdk(tmp.path(), &s), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_options_carry_the_thread_count_into_the_command_line() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut s = Settings::default();
|
||||
assert!(maven_options(tmp.path(), &s).is_empty(), "empty settings add nothing");
|
||||
|
||||
s.languages.java.maven.user_settings = "m2/settings.xml".into();
|
||||
s.languages.java.maven.local_repository = "m2/repo".into();
|
||||
s.languages.java.maven.threads = "4".into();
|
||||
let settings_file = tmp.path().join("m2/settings.xml").to_string_lossy().into_owned();
|
||||
let repo = tmp.path().join("m2/repo").to_string_lossy().into_owned();
|
||||
assert_eq!(
|
||||
maven_options(tmp.path(), &s),
|
||||
["-s", settings_file.as_str(), &format!("-Dmaven.repo.local={repo}"), "-T", "4"]
|
||||
);
|
||||
|
||||
// A core multiplier passes through; anything Maven would reject is dropped
|
||||
// rather than failing every build.
|
||||
s.languages.java.maven.threads = "2.5C".into();
|
||||
assert!(maven_options(tmp.path(), &s).ends_with(&["-T".to_owned(), "2.5C".to_owned()]));
|
||||
s.languages.java.maven.threads = "abc".into();
|
||||
assert!(!maven_options(tmp.path(), &s).iter().any(|a| a == "-T"));
|
||||
|
||||
// Run configurations get the options ahead of the goals, once per launch.
|
||||
s.languages.java.maven.threads = "2C".into();
|
||||
let mut configs = vec![run("maven", "./mvnw", &["package"]), run("maven", "./mvnw", &["-T", "8", "package"])];
|
||||
apply_to_run_configs(&mut configs, tmp.path(), &s);
|
||||
let args: Vec<String> = configs[0].args.clone();
|
||||
assert_eq!(args[args.len() - 3..].join(" "), "-T 2C package", "the settings' thread count leads the goals");
|
||||
let args: Vec<String> = configs[1].args.clone();
|
||||
assert_eq!(args[args.len() - 3..].join(" "), "-T 8 package", "the configuration's own -T wins");
|
||||
apply_to_run_configs(&mut configs, tmp.path(), &s);
|
||||
assert_eq!(configs[0].args.iter().filter(|a| a.as_str() == "-T").count(), 1, "applied once");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saved_maven_goal_configs_take_the_configured_launcher() {
|
||||
if cfg!(windows) {
|
||||
|
||||
@@ -5349,6 +5349,11 @@ export type MavenSettings = {
|
||||
* `mvnd` on `PATH` / common install dirs. Only used when `daemon` is on.
|
||||
*/
|
||||
mvndHome?: string,
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
threads?: string,
|
||||
};
|
||||
|
||||
export type MemoryCategory =
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("languages settings", () => {
|
||||
jdtlsVmArgs: " -Xmx2g ",
|
||||
jdk: 21,
|
||||
jdtlsJdk: " /opt/jdk-25 ",
|
||||
maven: { home: " /opt/maven ", daemon: "yes", mvndHome: " /opt/mvnd " },
|
||||
maven: { home: " /opt/maven ", daemon: "yes", mvndHome: " /opt/mvnd ", threads: " 2C " },
|
||||
},
|
||||
});
|
||||
expect(v.python).toEqual({ interpreter: "/usr/bin/python3", activateVirtualenv: true });
|
||||
@@ -45,8 +45,10 @@ describe("languages settings", () => {
|
||||
linkedProjects: [],
|
||||
daemon: false,
|
||||
mvndHome: "/opt/mvnd",
|
||||
threads: "2C",
|
||||
});
|
||||
expect(validateSettings({}).languages).toEqual(DEFAULT_LANGUAGES);
|
||||
expect(validateLanguages({ java: { maven: { threads: 4 } } }).java.maven.threads).toBe("");
|
||||
});
|
||||
|
||||
it("keeps the Ruby interpreter through settings normalization and defaults to Auto", () => {
|
||||
|
||||
@@ -477,6 +477,8 @@ 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 id = useId();
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<Section>
|
||||
@@ -506,6 +508,23 @@ export function MavenPage() {
|
||||
title="Select Local Repository"
|
||||
hint="Passed as -Dmaven.repo.local."
|
||||
/>
|
||||
<Row
|
||||
label="Thread count"
|
||||
htmlFor={id}
|
||||
hint="Passed as -T to Maven builds and run configurations: a thread count (4) or a core multiplier (2C, 2.5C) for parallel builds of multi-module projects. Empty runs Maven serially."
|
||||
>
|
||||
<span className={styles.pathRow}>
|
||||
<Input
|
||||
id={id}
|
||||
mono
|
||||
className={styles.pathInput}
|
||||
value={threads}
|
||||
placeholder="4, 2C"
|
||||
onChange={(e) => setThreads(e.target.value.trim())}
|
||||
aria-label="Thread count"
|
||||
/>
|
||||
</span>
|
||||
</Row>
|
||||
<Toggle
|
||||
label="Use Maven Daemon (mvnd)"
|
||||
checked={daemon}
|
||||
|
||||
@@ -144,7 +144,20 @@ export const EXECUTION_PAGES: PageSpec[] = [
|
||||
group: BUILD_GROUP,
|
||||
// IntelliJ IDEA: Build Tools › Maven, before Console / Debugger.
|
||||
order: 359,
|
||||
keywords: ["maven home", "mvn", "mvnw", "wrapper", "settings.xml", "user settings", "local repository", "m2"],
|
||||
keywords: [
|
||||
"maven home",
|
||||
"mvn",
|
||||
"mvnw",
|
||||
"wrapper",
|
||||
"settings.xml",
|
||||
"user settings",
|
||||
"local repository",
|
||||
"m2",
|
||||
"thread count",
|
||||
"threads",
|
||||
"-T",
|
||||
"parallel",
|
||||
],
|
||||
component: MavenPage,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -128,6 +128,8 @@ export interface LanguagesValid {
|
||||
linkedProjects: string[];
|
||||
daemon: boolean;
|
||||
mvndHome: string;
|
||||
/** `-T`: a thread count (4) or a core multiplier (2C, 2.5C); empty = Maven's default. */
|
||||
threads: string;
|
||||
};
|
||||
};
|
||||
/** Preview font size in px; `null` follows the editor font size. */
|
||||
@@ -181,6 +183,7 @@ export const DEFAULT_LANGUAGES: LanguagesValid = {
|
||||
linkedProjects: [],
|
||||
daemon: false,
|
||||
mvndHome: "",
|
||||
threads: "",
|
||||
},
|
||||
},
|
||||
markdown: { previewFontSize: null },
|
||||
@@ -331,6 +334,7 @@ export function validateLanguages(raw: unknown): LanguagesValid {
|
||||
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),
|
||||
},
|
||||
},
|
||||
markdown: { previewFontSize: previewSize(markdown.previewFontSize) },
|
||||
|
||||
@@ -1272,6 +1272,9 @@
|
||||
"Maven sync finished with errors": "Maven 同步完成,但有错误",
|
||||
"Maven wrapper, else mvn on PATH": "Maven wrapper,否则使用 PATH 中的 mvn",
|
||||
"MVND_HOME, else auto-detected": "MVND_HOME,否则自动检测",
|
||||
"Passed as -T to Maven builds and run configurations: a thread count (4) or a core multiplier (2C, 2.5C) for parallel builds of multi-module projects. Empty runs Maven serially.": "以 -T 传给 Maven 构建和运行配置:线程数(如 4)或内核倍数(如 2C、2.5C),用于多模块工程的并行构建。留空则串行运行 Maven。",
|
||||
"Thread count": "线程数",
|
||||
"4, 2C": "4、2C",
|
||||
"Maximize": "最大化",
|
||||
"Maximize Tool Window": "最大化工具窗口",
|
||||
"Maximum local history size per project": "每个项目的最大本地历史记录大小",
|
||||
|
||||
Reference in new issue
Block a user