mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
fix(java): Apply the Maven settings to jdtls and Lombok, find more Maven installs
Review fixes for the Project JDK / Maven settings: - jdtls gets the user settings.xml as java.configuration.maven.userSettings, so the editor resolves dependencies through the same mirrors and servers as sync and builds. - The configured local repository (else <localRepository> of the chosen settings.xml) is where the static POM model looks for parent POMs / BOMs and where the Lombok agent is looked for. - Maven discovery finds mvn.cmd on PATH on Windows and the homes Maven Wrapper 3.3+ downloads (dists/<name>/<hash> without a nested folder). - A Project JDK pointing at the JDK's bin directory is accepted. - Detected run configurations reload when language settings change, so Maven and Gradle runs use the new JDK, Maven home and options. - Chinese text for the new settings.
This commit is contained in:
1 parent
825f190030
commit
48ebbe15e7
12 files changed
+78
-21
No files matched your search
@@ -19,7 +19,7 @@ fn main() {
|
||||
eprintln!("synced in {:?}: {r:?}", started.elapsed());
|
||||
}
|
||||
let started = std::time::Instant::now();
|
||||
let model = ide_build::load(&root, &cache);
|
||||
let model = ide_build::load(&root, &cache, None);
|
||||
println!("{}", serde_json::to_string_pretty(&model).unwrap());
|
||||
eprintln!("loaded in {:?}", started.elapsed());
|
||||
}
|
||||
@@ -672,7 +672,7 @@ mod tests {
|
||||
);
|
||||
write(&root, "core/pom.xml", "<project><artifactId>core</artifactId></project>");
|
||||
write(&root, "core/src/main/java/A.java", "class A {}");
|
||||
let model = crate::load(&root, &tmp.path().join("cache"));
|
||||
let model = crate::load(&root, &tmp.path().join("cache"), None);
|
||||
let p = plan(&root, &model, BuildKind::Build, None).unwrap();
|
||||
assert_eq!(p.system, BuildSystem::Maven);
|
||||
assert_eq!(p.title, "demo");
|
||||
@@ -686,7 +686,7 @@ mod tests {
|
||||
let g = tmp.path().join("g");
|
||||
write(&g, "settings.gradle", "include 'app'");
|
||||
write(&g, "app/build.gradle", "apply plugin: 'java'");
|
||||
let model = crate::load(&g, &tmp.path().join("cache2"));
|
||||
let model = crate::load(&g, &tmp.path().join("cache2"), None);
|
||||
let p = plan(&g, &model, BuildKind::Build, Some(&g.join("app/src/A.java"))).unwrap();
|
||||
assert_eq!(p.system, BuildSystem::Gradle);
|
||||
assert_eq!(p.steps[0].args, ["--console=plain", ":app:classes", ":app:testClasses"]);
|
||||
|
||||
@@ -18,7 +18,7 @@ pub use sync::{SyncOptions, SyncProgress, sync_gradle, sync_maven};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -36,9 +36,10 @@ pub struct BuildModel {
|
||||
}
|
||||
|
||||
/// Static model of the builds under `root`, merged with the sync results cached in `cache_dir`.
|
||||
pub fn load(root: &Path, cache_dir: &Path) -> BuildModel {
|
||||
/// Parent POMs are looked up in `maven_repo` (the configured local repository), else the default one.
|
||||
pub fn load(root: &Path, cache_dir: &Path, maven_repo: Option<PathBuf>) -> BuildModel {
|
||||
let maven = maven::is_maven_project(root).then(|| {
|
||||
let mut m = maven::load(root);
|
||||
let mut m = maven::load_with_repo(root, maven_repo.or_else(maven::default_local_repo));
|
||||
sync::apply_maven_cache(root, cache_dir, &mut m);
|
||||
m
|
||||
});
|
||||
@@ -68,10 +69,10 @@ mod tests {
|
||||
let cache = tmp.path().join("cache");
|
||||
let root = tmp.path().join("p");
|
||||
std::fs::create_dir_all(root.join("svc")).unwrap();
|
||||
assert_eq!(load(&root, &cache), BuildModel::default());
|
||||
assert_eq!(load(&root, &cache, None), BuildModel::default());
|
||||
std::fs::write(root.join("svc/pom.xml"), "<project><artifactId>svc</artifactId></project>").unwrap();
|
||||
std::fs::write(root.join("build.gradle"), "").unwrap();
|
||||
let m = load(&root, &cache);
|
||||
let m = load(&root, &cache, None);
|
||||
assert_eq!(m.maven.unwrap().projects[0].pom, "svc/pom.xml");
|
||||
assert_eq!(m.gradle.unwrap().projects[0].path, ":");
|
||||
assert!(affects_build(Path::new("a/pom.xml")) && affects_build(Path::new("settings.gradle.kts")));
|
||||
|
||||
@@ -412,7 +412,7 @@ struct Resolver {
|
||||
}
|
||||
|
||||
/// `~/.m2/repository`, or `<localRepository>` from `~/.m2/settings.xml`.
|
||||
fn default_local_repo() -> Option<PathBuf> {
|
||||
pub(crate) fn default_local_repo() -> Option<PathBuf> {
|
||||
let m2 = dirs::home_dir()?.join(".m2");
|
||||
if let Some(settings) = std::fs::read_to_string(m2.join("settings.xml")).ok().and_then(|t| xml::parse(&t))
|
||||
&& let Some(repo) = settings.text_of("localRepository")
|
||||
|
||||
@@ -56,10 +56,10 @@ fn goroot(root: &Path, s: &Settings) -> Option<PathBuf> {
|
||||
setting_path(root, &s.languages.go.goroot)
|
||||
}
|
||||
|
||||
/// The project JDK (`languages.java.jdk`): a JDK home, or its `bin/java`.
|
||||
/// The project JDK (`languages.java.jdk`): a JDK home, its `bin` or its `bin/java`.
|
||||
pub fn project_jdk(root: &Path, s: &Settings) -> Option<crate::java::Jdk> {
|
||||
let p = setting_path(root, &s.languages.java.jdk)?;
|
||||
let jdk = crate::java::jdk_at(&p).or_else(|| crate::java::jdk_at(p.parent()?.parent()?));
|
||||
let jdk = p.ancestors().take(3).find_map(crate::java::jdk_at);
|
||||
if jdk.is_none() {
|
||||
tracing::warn!("project JDK {} is not a JDK home", p.display());
|
||||
}
|
||||
@@ -92,6 +92,19 @@ pub fn maven_options(root: &Path, s: &Settings) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let m = &s.languages.java.maven;
|
||||
setting_path(root, &m.local_repository).or_else(|| {
|
||||
let text = std::fs::read_to_string(setting_path(root, &m.user_settings)?).ok()?;
|
||||
let repo = crate::java::xml_value(&crate::lombok::strip_xml_comments(&text), "localRepository")?;
|
||||
let home = crate::home_dir().map(|h| h.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let repo = repo.replace("${user.home}", &home);
|
||||
(!repo.is_empty() && !repo.contains("${")).then(|| PathBuf::from(repo))
|
||||
})
|
||||
}
|
||||
|
||||
/// TypeScript `lib` directory for the selected version; `None` = the server's bundled one.
|
||||
pub fn tsdk(root: &Path, s: &Settings) -> Option<PathBuf> {
|
||||
match s.languages.typescript.version.trim() {
|
||||
@@ -206,6 +219,14 @@ pub fn configure_server(cfg: &mut LanguageServerConfig, root: &Path, server_root
|
||||
// The jdtls launcher forwards `--jvm-arg=` values to the JVM it starts.
|
||||
let args = s.languages.java.jdtls_vm_args.split_whitespace().map(|a| format!("--jvm-arg={a}"));
|
||||
cfg.args.extend(args);
|
||||
// m2e (the Maven import) reads mirrors, servers and `<localRepository>` from it.
|
||||
if let Some(file) = setting_path(root, &s.languages.java.maven.user_settings) {
|
||||
let file = file.to_string_lossy();
|
||||
merge_init_options(
|
||||
cfg,
|
||||
json!({ "settings": { "java": { "configuration": { "maven": { "userSettings": file } } } } }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(lib) = tsdk(server_root, s).or_else(|| tsdk(root, s)) {
|
||||
|
||||
@@ -437,7 +437,13 @@ fn maven_home(dir: &Path) -> Option<PathBuf> {
|
||||
|
||||
fn maven() -> Vec<Sdk> {
|
||||
let mut found: Vec<(PathBuf, &'static str)> = Vec::new();
|
||||
if let Some(mvn) = which("mvn").and_then(|p| dunce::canonicalize(p).ok())
|
||||
// Windows' Maven ships `mvn.cmd` only (`which` looks for `mvn.exe`).
|
||||
let on_path = if cfg!(windows) {
|
||||
std::env::split_paths(&search_path()).map(|d| d.join("mvn.cmd")).find(|p| p.is_file())
|
||||
} else {
|
||||
which("mvn")
|
||||
};
|
||||
if let Some(mvn) = on_path.and_then(|p| dunce::canonicalize(p).ok())
|
||||
&& let Some(home) = mvn.parent().and_then(Path::parent)
|
||||
{
|
||||
found.push((home.to_path_buf(), "path"));
|
||||
@@ -454,10 +460,12 @@ fn maven() -> Vec<Sdk> {
|
||||
for d in subdirs(&home.join(".sdkman/candidates/maven")) {
|
||||
found.push((d, "sdkman"));
|
||||
}
|
||||
// Distributions the Maven wrapper downloaded: `dists/apache-maven-3.9.6-bin/<hash>/apache-maven-3.9.6`.
|
||||
// Distributions the Maven wrapper downloaded: `dists/apache-maven-3.9.6-bin/<hash>/apache-maven-3.9.6`,
|
||||
// or since wrapper 3.3 the home itself, `dists/apache-maven-3.9.6/<hash>`.
|
||||
for dist in subdirs(&home.join(".m2/wrapper/dists")) {
|
||||
for hash in subdirs(&dist) {
|
||||
found.extend(subdirs(&hash).into_iter().map(|d| (d, "wrapper")));
|
||||
found.push((hash, "wrapper"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,14 @@ pub fn agent_in_args<'a>(args: impl IntoIterator<Item = &'a str>) -> Option<Path
|
||||
}
|
||||
|
||||
/// The Lombok agent for the project at `root`: the user's own (`own`, from the JVM
|
||||
/// options) wins, then `managed` (the IDE-installed jar), then the build caches.
|
||||
pub fn lombok_agent(root: &Path, own: Option<PathBuf>, managed: Option<PathBuf>) -> LombokAgent {
|
||||
/// options) wins, then `managed` (the IDE-installed jar), then the build caches
|
||||
/// (`maven_repo`, the configured Maven local repository, else the default one).
|
||||
pub fn lombok_agent(
|
||||
root: &Path,
|
||||
own: Option<PathBuf>,
|
||||
managed: Option<PathBuf>,
|
||||
maven_repo: Option<PathBuf>,
|
||||
) -> LombokAgent {
|
||||
if let Some(jar) = own {
|
||||
let version = jar
|
||||
.file_stem()
|
||||
@@ -82,7 +88,7 @@ pub fn lombok_agent(root: &Path, own: Option<PathBuf>, managed: Option<PathBuf>)
|
||||
}
|
||||
let home = home_dir();
|
||||
let mut dirs = Vec::new();
|
||||
if let Some(repo) = maven_repository(home.as_deref()) {
|
||||
if let Some(repo) = maven_repo.or_else(|| maven_repository(home.as_deref())) {
|
||||
dirs.push(repo.join("org").join("projectlombok").join("lombok"));
|
||||
}
|
||||
if let Some(gradle) =
|
||||
@@ -137,7 +143,7 @@ fn maven_repository(home: Option<&Path>) -> Option<PathBuf> {
|
||||
Some(custom.unwrap_or_else(|| m2.join("repository")))
|
||||
}
|
||||
|
||||
fn strip_xml_comments(text: &str) -> String {
|
||||
pub(crate) fn strip_xml_comments(text: &str) -> String {
|
||||
let mut out = String::with_capacity(text.len());
|
||||
let mut rest = text;
|
||||
while let Some(i) = rest.find("<!--") {
|
||||
@@ -249,10 +255,10 @@ mod tests {
|
||||
let own = agent_in_args(["--jvm-arg=-Xmx2g", "--jvm-arg=-javaagent:/opt/lombok-1.18.26.jar"]);
|
||||
assert_eq!(own.as_deref(), Some(Path::new("/opt/lombok-1.18.26.jar")));
|
||||
assert_eq!(agent_in_args(["-javaagent:/opt/other.jar"]), None);
|
||||
let agent = lombok_agent(tmp.path(), own, Some("/managed/lombok.jar".into()));
|
||||
let agent = lombok_agent(tmp.path(), own, Some("/managed/lombok.jar".into()), None);
|
||||
assert_eq!(agent.jar(), Some(Path::new("/opt/lombok-1.18.26.jar")));
|
||||
assert!(agent.outdated(), "its version is read from the file name");
|
||||
let plain = lombok_agent(tmp.path(), agent_in_args(["-javaagent:/opt/lombok.jar"]), None);
|
||||
let plain = lombok_agent(tmp.path(), agent_in_args(["-javaagent:/opt/lombok.jar"]), None, None);
|
||||
assert_eq!(plain.problem(), None, "a user agent is never reported missing");
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ fn java_env(project_jdk: Option<&ide_sdk::java::Jdk>, release: Option<u32>) -> B
|
||||
/// The build model with the launch settings applied: the JDK (`JAVA_HOME`), and
|
||||
/// the Maven home and options from Settings › Maven.
|
||||
pub(crate) fn load(root: &Path, cache: &Path, settings: &Settings) -> BuildModel {
|
||||
let mut model = ide_build::load(root, cache);
|
||||
let mut model = ide_build::load(root, cache, ide_sdk::apply::maven_local_repository(root, settings));
|
||||
let jdk = ide_sdk::apply::project_jdk(root, settings);
|
||||
if let Some(m) = &mut model.maven {
|
||||
let mvn = ide_sdk::apply::maven_command(root, settings);
|
||||
|
||||
@@ -52,11 +52,12 @@ pub async fn java_resolve_launch(
|
||||
});
|
||||
info.java_exec = Some(java.map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| "java".to_owned()));
|
||||
if info.error_count > 0 {
|
||||
let vm_args = settings.languages.java.jdtls_vm_args;
|
||||
let vm_args = &settings.languages.java.jdtls_vm_args;
|
||||
let lombok = ide_sdk::lombok::lombok_agent(
|
||||
&project.root,
|
||||
ide_sdk::lombok::agent_in_args(vm_args.split_whitespace()),
|
||||
ide_tools::managed_bundle(&state.data_dir, "lombok"),
|
||||
ide_sdk::apply::maven_local_repository(&project.root, &settings),
|
||||
);
|
||||
info.hints.extend(lombok.problem());
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ impl AppState {
|
||||
server_root,
|
||||
ide_sdk::lombok::agent_in_args(cfg.args.iter().map(String::as_str)),
|
||||
ide_tools::managed_bundle(&data_dir, "lombok"),
|
||||
ide_sdk::apply::maven_local_repository(&root, &effective),
|
||||
);
|
||||
if let Some(problem) = lombok.problem() {
|
||||
tracing::warn!("{problem}");
|
||||
|
||||
@@ -386,6 +386,10 @@ export function activate(): () => void {
|
||||
const paths = [...e.created, ...e.modified, ...e.removed];
|
||||
if (paths.some((p) => CONFIG_FILES.test(p))) scheduleReload();
|
||||
}),
|
||||
// Detected configs carry the selected toolchains (interpreter, JDK, Maven home and options).
|
||||
onIdeEvent("settingsChanged", (e) => {
|
||||
if (e.key === "languages" || e.key.startsWith("languages.")) scheduleReload();
|
||||
}),
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -345,6 +345,7 @@
|
||||
"Compare with Clipboard": "与剪贴板比较",
|
||||
"Compare with Local": "与本地比较",
|
||||
"Compilation failed: fix the errors and reload again.": "编译失败:请修复错误后重新加载。",
|
||||
"Compiles and runs the project: the default jdtls runtime, Run/Debug, and JAVA_HOME for Maven and Gradle.": "用于编译和运行项目:jdtls 的默认运行时、运行/调试,以及 Maven 和 Gradle 的 JAVA_HOME。",
|
||||
"Compiles the module of the current file": "编译当前文件所在的模块",
|
||||
"Compiles the project's sources and tests with its build system": "使用项目的构建系统编译其源代码和测试",
|
||||
"Completions": "补全",
|
||||
@@ -766,6 +767,7 @@
|
||||
"Fragment": "片段",
|
||||
"Frames": "帧",
|
||||
"From Template…": "从模板…",
|
||||
"From settings.xml, else ~/.m2/repository": "取自 settings.xml,否则为 ~/.m2/repository",
|
||||
"Function": "函数",
|
||||
"Function component": "函数组件",
|
||||
"Function with return type": "带返回类型的函数",
|
||||
@@ -1001,6 +1003,7 @@
|
||||
"Local History of all projects will be deleted.": "所有项目的本地历史记录都将被删除。",
|
||||
"Local History: Put Label…": "本地历史记录:添加标签…",
|
||||
"Local History: Show History": "本地历史记录:显示历史记录",
|
||||
"Local repository": "本地仓库",
|
||||
"Locate Duplicates": "查找重复项",
|
||||
"Locate Duplicates…": "查找重复项…",
|
||||
"Location": "位置",
|
||||
@@ -1036,8 +1039,10 @@
|
||||
"Matches": "匹配项",
|
||||
"Maven actions": "Maven 操作",
|
||||
"Maven command line": "Maven 命令行",
|
||||
"Maven home path": "Maven 主路径",
|
||||
"Maven projects": "Maven 项目",
|
||||
"Maven sync finished with errors": "Maven 同步完成,但有错误",
|
||||
"Maven wrapper, else mvn on PATH": "Maven wrapper,否则使用 PATH 中的 mvn",
|
||||
"Maximize": "最大化",
|
||||
"Maximize Tool Window": "最大化工具窗口",
|
||||
"Maximum local history size per project": "每个项目的最大本地历史记录大小",
|
||||
@@ -1301,6 +1306,8 @@
|
||||
"Parses (hit / incr. / full)": "解析(命中/增量/完整)",
|
||||
"Partially staged: only staged hunks are committed": "部分暂存:仅提交已暂存的块",
|
||||
"Passed": "已通过",
|
||||
"Passed as -Dmaven.repo.local.": "以 -Dmaven.repo.local 传入。",
|
||||
"Passed as -s (mirrors, servers, proxies).": "以 -s 传入(镜像、服务器、代理)。",
|
||||
"Passed to rustfmt when reformatting, e.g. --edition 2024": "重新格式化时传递给 rustfmt,例如 --edition 2024",
|
||||
"Passed to the JVM running jdtls (java.jdt.ls.vmargs), e.g. -Xmx2g. A Lombok agent is added automatically when the project uses Lombok.": "传递给运行 jdtls 的 JVM (java.jdt.ls.vmargs),例如 -Xmx2g。项目使用 Lombok 时会自动添加 Lombok 代理。",
|
||||
"Password": "密码",
|
||||
@@ -1394,6 +1401,7 @@
|
||||
"Project Errors": "项目错误",
|
||||
"Project Files": "项目文件",
|
||||
"Project GOPATH": "项目 GOPATH",
|
||||
"Project JDK": "项目 JDK",
|
||||
"Project Production Files": "项目生产文件",
|
||||
"Project Settings": "项目设置",
|
||||
"Project Structure": "项目结构",
|
||||
@@ -1669,6 +1677,7 @@
|
||||
"Runs Maven with -o": "使用 -o 运行 Maven",
|
||||
"Runs Optimize Imports when the file has no errors, unless it would change the caret line.": "文件没有错误时运行“优化导入”,除非这会更改光标所在行。",
|
||||
"Runs npm scripts and Node-based language servers (vtsls, eslint, pyright…).": "运行 npm 脚本和基于 Node 的语言服务器(vtsls、eslint、pyright…)。",
|
||||
"Runs sync, builds and Maven run configurations. A chosen home is used instead of the project's mvnw.": "用于同步、构建和 Maven 运行配置。选定后将代替项目的 mvnw。",
|
||||
"Runs the language server's Organize Imports action.": "运行语言服务器的“整理导入”操作。",
|
||||
"Runs the language server's formatter before an explicit save (Actions on Save).": "在显式保存前运行语言服务器的格式化程序(保存时操作)。",
|
||||
"Runs this project": "运行此项目",
|
||||
@@ -1727,6 +1736,10 @@
|
||||
"Select Base Interpreter": "选择基础解释器",
|
||||
"Select Folders…": "选择文件夹…",
|
||||
"Select GOPATH": "选择 GOPATH",
|
||||
"Select JDK Home Directory": "选择 JDK 主目录",
|
||||
"Select Local Repository": "选择本地仓库",
|
||||
"Select Maven Home Directory": "选择 Maven 主目录",
|
||||
"Select Maven settings.xml": "选择 Maven settings.xml",
|
||||
"Select Next Tab": "选择下一个标签页",
|
||||
"Select Opened File": "选择已打开的文件",
|
||||
"Select Previous Tab": "选择上一个标签页",
|
||||
@@ -2140,6 +2153,7 @@
|
||||
"Used for new files.": "用于新文件。",
|
||||
"User": "用户",
|
||||
"User name or email": "用户名或电子邮件",
|
||||
"User settings file": "用户设置文件",
|
||||
"User:": "用户:",
|
||||
"Uses HostName, User, Port, IdentityFile and ProxyJump from ~/.ssh/config, and keys of the SSH agent.": "使用 ~/.ssh/config 中的 HostName、User、Port、IdentityFile 和 ProxyJump,以及 SSH 代理中的密钥。",
|
||||
"VCS Operations": "VCS 操作",
|
||||
@@ -2251,6 +2265,7 @@
|
||||
"occurrences ·": "个匹配项 ·",
|
||||
"or click a line number to set a breakpoint.": "或点击行号设置断点。",
|
||||
"or pick one in the toolbar.": "或在工具栏中选择一个。",
|
||||
"per module, the JDK of its Java version": "按模块使用与其 Java 版本匹配的 JDK",
|
||||
"pip output": "pip 输出",
|
||||
"places the caret. A line with": "放置光标。包含",
|
||||
"println! with Debug": "使用 Debug 的 println!",
|
||||
|
||||
Reference in new issue
Block a user