diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index b63a3fefc..d9fff1675 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -3,6 +3,7 @@ """ import dataclasses +import glob import hashlib import logging import os @@ -26,21 +27,26 @@ log = logging.getLogger(__name__) GRADLE_ALLOWED_HOSTS = ("services.gradle.org", "github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com") +DEFAULT_GRADLE_VERSION = "8.14.2" GRADLE_SHA256 = "7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999" VSCODE_JAVA_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com") +DEFAULT_VSCODE_JAVA_VERSION = "1.53.0-873" VSCODE_JAVA_SHA256_BY_PLATFORM = { - "osx-arm64": "bc00c2699d4b8d478eb9a1621db9d6d3a12ea0dcc247a9cd8040e8ac19c03933", - "osx-x64": "03ae1db1a22c15561a620f1b722d6797d35d4faaa7c4666dbe6ca2715089852f", - "linux-arm64": "e15bc9b2a665d3453203402621b5441062aa41b0ec2d140661f439326fd248c1", - "linux-x64": "7660b7b527be6fda46a917966b34d828e7416d5cc84287b29b88e7b99c1737f9", - "win-x64": "ef195b45bd260976ad2e84618f4044b5d7248deed41d647573f0ee22c4233df3", + "osx-arm64": "3a9acf30b682df2f0b895728ad8f84725a95b326e2265f17bf9b087acb08dd0d", + "osx-x64": "73823bd3b0765bb9b483ba45216c229065df33e16a40623a7da5d92ae32e1471", + "linux-arm64": "92d42123b2282f970517a62cdb4ea2e5d7ffb255e665537f40641f6961a148fc", + "linux-x64": "24e9e605cd40b523fe62be350fe8550dafdaa6881010b2c595b26e425f2ee400", + "win-x64": "13aeda95e0494442a951f752c7334d97bb7a49991ae5a8b1a6cdb6a8dfcac128", } INTELLICODE_ALLOWED_HOSTS = ( "visualstudioexptteam.gallery.vsassets.io", "marketplace.visualstudio.com", "download.visualstudio.microsoft.com", ) +DEFAULT_INTELLICODE_VERSION = "1.2.30" INTELLICODE_SHA256 = "7f61a7f96d101cdf230f96821be3fddd8f890ebfefb3695d18beee43004ae251" +DEFAULT_ECLIPSE_LAUNCHER_VERSION = "1.7.100.v20251111-0406" +DEFAULT_JRE_VERSION = "21.0.10" @dataclasses.dataclass @@ -85,7 +91,7 @@ class EclipseJDTLS(SolidLanguageServer): gradle_java_home: "/path/to/jdk" # set to override Gradle's JDK use_system_java_home: true # set to true to use system JAVA_HOME for JDTLS gradle_version: "8.14.2" - vscode_java_version: "1.42.0-561" + vscode_java_version: "1.53.0-873" intellicode_version: "1.2.30" ``` """ @@ -148,13 +154,14 @@ def _setup_runtime_dependencies( Setup runtime dependencies for EclipseJDTLS and return the paths. """ platformId = PlatformUtils.get_platform_id() - gradle_version = custom_settings.get("gradle_version", "8.14.2") - vscode_java_version = custom_settings.get("vscode_java_version", "1.42.0-561") + gradle_version = custom_settings.get("gradle_version", DEFAULT_GRADLE_VERSION) + vscode_java_version = custom_settings.get("vscode_java_version", DEFAULT_VSCODE_JAVA_VERSION) vscode_java_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}" - intellicode_version = custom_settings.get("intellicode_version", "1.2.30") - default_gradle_version = gradle_version == "8.14.2" - default_vscode_java_version = vscode_java_version == "1.42.0-561" - default_intellicode_version = intellicode_version == "1.2.30" + intellicode_version = custom_settings.get("intellicode_version", DEFAULT_INTELLICODE_VERSION) + eclipse_launcher_version = custom_settings.get("eclipse_launcher_version", DEFAULT_ECLIPSE_LAUNCHER_VERSION) + is_default_gradle_version = gradle_version == DEFAULT_GRADLE_VERSION + is_default_vscode_java_version = vscode_java_version == DEFAULT_VSCODE_JAVA_VERSION + is_default_intellicode_version = intellicode_version == DEFAULT_INTELLICODE_VERSION runtime_dependencies: dict[str, dict[str, dict[str, object]]] = { "gradle": { @@ -162,7 +169,7 @@ def _setup_runtime_dependencies( "url": f"https://services.gradle.org/distributions/gradle-{gradle_version}-bin.zip", "archiveType": "zip", "relative_extraction_path": ".", - "sha256": GRADLE_SHA256 if default_gradle_version else None, + "sha256": GRADLE_SHA256 if is_default_gradle_version else None, "allowed_hosts": GRADLE_ALLOWED_HOSTS, } }, @@ -171,67 +178,62 @@ def _setup_runtime_dependencies( "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-arm64-{vscode_java_version}.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if default_vscode_java_version else None, + "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if is_default_vscode_java_version else None, "allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS, }, "osx-arm64": { "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-arm64-{vscode_java_version}.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if default_vscode_java_version else None, + "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-arm64"] if is_default_vscode_java_version else None, "allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS, - "jre_home_path": "extension/jre/21.0.7-macosx-aarch64", - "jre_path": "extension/jre/21.0.7-macosx-aarch64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", + "jre_home_path": f"extension/jre/{DEFAULT_JRE_VERSION}-macosx-aarch64", + "jre_path": f"extension/jre/{DEFAULT_JRE_VERSION}-macosx-aarch64/bin/java", + "jdtls_launcher_jar_path": f"extension/server/plugins/org.eclipse.equinox.launcher_{eclipse_launcher_version}.jar", "jdtls_readonly_config_path": "extension/server/config_mac_arm", }, "osx-x64": { "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-x64-{vscode_java_version}.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-x64"] if default_vscode_java_version else None, + "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["osx-x64"] if is_default_vscode_java_version else None, "allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS, - "jre_home_path": "extension/jre/21.0.7-macosx-x86_64", - "jre_path": "extension/jre/21.0.7-macosx-x86_64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", + "jre_home_path": f"extension/jre/{DEFAULT_JRE_VERSION}-macosx-x86_64", + "jre_path": f"extension/jre/{DEFAULT_JRE_VERSION}-macosx-x86_64/bin/java", + "jdtls_launcher_jar_path": f"extension/server/plugins/org.eclipse.equinox.launcher_{eclipse_launcher_version}.jar", "jdtls_readonly_config_path": "extension/server/config_mac", }, "linux-arm64": { "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-arm64-{vscode_java_version}.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-arm64"] if default_vscode_java_version else None, + "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-arm64"] if is_default_vscode_java_version else None, "allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS, - "jre_home_path": "extension/jre/21.0.7-linux-aarch64", - "jre_path": "extension/jre/21.0.7-linux-aarch64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", + "jre_home_path": f"extension/jre/{DEFAULT_JRE_VERSION}-linux-aarch64", + "jre_path": f"extension/jre/{DEFAULT_JRE_VERSION}-linux-aarch64/bin/java", + "jdtls_launcher_jar_path": f"extension/server/plugins/org.eclipse.equinox.launcher_{eclipse_launcher_version}.jar", "jdtls_readonly_config_path": "extension/server/config_linux_arm", }, "linux-x64": { "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-x64-{vscode_java_version}.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-x64"] if default_vscode_java_version else None, + "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["linux-x64"] if is_default_vscode_java_version else None, "allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS, - "jre_home_path": "extension/jre/21.0.7-linux-x86_64", - "jre_path": "extension/jre/21.0.7-linux-x86_64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", + "jre_home_path": f"extension/jre/{DEFAULT_JRE_VERSION}-linux-x86_64", + "jre_path": f"extension/jre/{DEFAULT_JRE_VERSION}-linux-x86_64/bin/java", + "jdtls_launcher_jar_path": f"extension/server/plugins/org.eclipse.equinox.launcher_{eclipse_launcher_version}.jar", "jdtls_readonly_config_path": "extension/server/config_linux", }, "win-x64": { "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-win32-x64-{vscode_java_version}.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["win-x64"] if default_vscode_java_version else None, + "sha256": VSCODE_JAVA_SHA256_BY_PLATFORM["win-x64"] if is_default_vscode_java_version else None, "allowed_hosts": VSCODE_JAVA_ALLOWED_HOSTS, - "jre_home_path": "extension/jre/21.0.7-win32-x86_64", - "jre_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe", - "lombok_jar_path": "extension/lombok/lombok-1.18.36.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar", + "jre_home_path": f"extension/jre/{DEFAULT_JRE_VERSION}-win32-x86_64", + "jre_path": f"extension/jre/{DEFAULT_JRE_VERSION}-win32-x86_64/bin/java.exe", + "jdtls_launcher_jar_path": f"extension/server/plugins/org.eclipse.equinox.launcher_{eclipse_launcher_version}.jar", "jdtls_readonly_config_path": "extension/server/config_win", }, }, @@ -241,7 +243,7 @@ def _setup_runtime_dependencies( "alternate_url": f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/{intellicode_version}/vspackage", "archiveType": "zip", "relative_extraction_path": "intellicode", - "sha256": INTELLICODE_SHA256 if default_intellicode_version else None, + "sha256": INTELLICODE_SHA256 if is_default_intellicode_version else None, "allowed_hosts": INTELLICODE_ALLOWED_HOSTS, "intellicode_jar_path": "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar", "intellisense_members_path": "extension/dist/bundledModels/java_intellisense-members", @@ -273,17 +275,17 @@ def _setup_runtime_dependencies( os.makedirs(vscode_java_path, exist_ok=True) jre_home_path = str(PurePath(vscode_java_path, cast(str, dependency["jre_home_path"]))) jre_path = str(PurePath(vscode_java_path, cast(str, dependency["jre_path"]))) - lombok_jar_path = str(PurePath(vscode_java_path, cast(str, dependency["lombok_jar_path"]))) jdtls_launcher_jar_path = str(PurePath(vscode_java_path, cast(str, dependency["jdtls_launcher_jar_path"]))) jdtls_readonly_config_path = str(PurePath(vscode_java_path, cast(str, dependency["jdtls_readonly_config_path"]))) + lombok_dir = str(PurePath(vscode_java_path, "extension", "lombok")) if not all( [ os.path.exists(vscode_java_path), os.path.exists(jre_home_path), os.path.exists(jre_path), - os.path.exists(lombok_jar_path), os.path.exists(jdtls_launcher_jar_path), os.path.exists(jdtls_readonly_config_path), + bool(glob.glob(os.path.join(lombok_dir, "lombok-*.jar"))), ] ): FileUtils.download_and_extract_archive_verified( @@ -296,10 +298,14 @@ def _setup_runtime_dependencies( os.chmod(jre_path, 0o755) + lombok_jars = glob.glob(os.path.join(lombok_dir, "lombok-*.jar")) + if len(lombok_jars) != 1: + raise RuntimeError(f"Expected exactly one lombok jar in {lombok_dir}, found: {lombok_jars}") + lombok_jar_path = lombok_jars[0] + assert os.path.exists(vscode_java_path) assert os.path.exists(jre_home_path) assert os.path.exists(jre_path) - assert os.path.exists(lombok_jar_path) assert os.path.exists(jdtls_launcher_jar_path) assert os.path.exists(jdtls_readonly_config_path) @@ -698,7 +704,7 @@ def _get_initialize_params(self, repository_absolute_path: str) -> InitializePar }, "workspaceCacheLimit": 90, "runtimes": [ - {"name": "JavaSE-21", "path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64", "default": True} + {"name": "JavaSE-21", "path": "static/vscode-java/extension/jre/21.0.10-linux-x86_64", "default": True} ], }, "trace": {"server": "verbose"}, diff --git a/src/solidlsp/ls_utils.py b/src/solidlsp/ls_utils.py index 1dad11f3e..3a4acd018 100644 --- a/src/solidlsp/ls_utils.py +++ b/src/solidlsp/ls_utils.py @@ -8,6 +8,7 @@ import os import platform import shutil +import stat import subprocess import tarfile import uuid @@ -387,6 +388,9 @@ def _extract_zip_archive(archive_path: str, target_path: str) -> None: continue os.makedirs(os.path.dirname(extracted_path), exist_ok=True) + if os.path.exists(extracted_path): + # Some archives are re-extracted over an older read-only install. + os.chmod(extracted_path, os.stat(extracted_path).st_mode | stat.S_IWUSR) with zip_ref.open(zip_info, "r") as source_file, open(extracted_path, "wb") as output_file: shutil.copyfileobj(source_file, output_file) diff --git a/test/solidlsp/util/test_ls_utils.py b/test/solidlsp/util/test_ls_utils.py index cad603c33..87659efb2 100644 --- a/test/solidlsp/util/test_ls_utils.py +++ b/test/solidlsp/util/test_ls_utils.py @@ -1,6 +1,8 @@ from __future__ import annotations import hashlib +import stat +import zipfile from pathlib import Path from unittest.mock import patch @@ -40,3 +42,20 @@ def test_download_file_verified_writes_decoded_response_body(tmp_path: Path) -> ) assert target_path.read_bytes() == payload + + +def test_extract_zip_archive_overwrites_existing_read_only_file(tmp_path: Path) -> None: + """Existing read-only files should be made writable before extraction overwrites them.""" + archive_path = tmp_path / "archive.zip" + extracted_file = tmp_path / "extract" / "nested" / "file.txt" + + with zipfile.ZipFile(archive_path, "w") as zip_file: + zip_file.writestr("nested/file.txt", "new content") + + extracted_file.parent.mkdir(parents=True) + extracted_file.write_text("old content") + extracted_file.chmod(extracted_file.stat().st_mode & ~stat.S_IWUSR) + + FileUtils._extract_zip_archive(str(archive_path), str(tmp_path / "extract")) + + assert extracted_file.read_text() == "new content"