• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

localstack / localstack / 22519085314

27 Feb 2026 11:47PM UTC coverage: 86.962% (+0.006%) from 86.956%
22519085314

push

github

web-flow
SNS: update store typing (#13866)

3 of 3 new or added lines in 1 file covered. (100.0%)

388 existing lines in 19 files now uncovered.

69828 of 80297 relevant lines covered (86.96%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.1
/localstack-core/localstack/packages/java.py
1
import logging
1✔
2
import os
1✔
3

4
import requests
1✔
5

6
from localstack.constants import USER_AGENT_STRING
1✔
7
from localstack.packages import InstallTarget, Package
1✔
8
from localstack.packages.core import ArchiveDownloadAndExtractInstaller
1✔
9
from localstack.utils.files import rm_rf
1✔
10
from localstack.utils.http import get_proxies
1✔
11
from localstack.utils.platform import Arch, get_arch, is_linux, is_mac_os
1✔
12
from localstack.utils.run import run
1✔
13

14
LOG = logging.getLogger(__name__)
1✔
15

16
# Default version if not specified
17
DEFAULT_JAVA_VERSION = "11"
1✔
18

19
# Supported Java LTS versions mapped with Eclipse Temurin build semvers
20
JAVA_VERSIONS = {
1✔
21
    "8": "8u432-b06",
22
    "11": "11.0.25+9",
23
    "17": "17.0.13+11",
24
    "21": "21.0.5+11",
25
    "24": "24.0.1+9",
26
}
27

28

29
class JavaInstallerMixin:
1✔
30
    """
31
    Mixin class for packages that depend on Java. It introduces methods that install Java and help build environment.
32
    """
33

34
    def _prepare_installation(self, target: InstallTarget) -> None:
1✔
35
        java_package.install(target=target)
×
36

37
    def get_java_home(self) -> str | None:
1✔
38
        """
39
        Returns path to JRE installation.
40
        """
41
        return java_package.get_installer().get_java_home()
×
42

43
    def get_java_lib_path(self) -> str | None:
1✔
44
        """
45
        Returns the path to the Java shared library.
46
        """
47
        if java_home := self.get_java_home():
1✔
48
            if is_mac_os():
1✔
49
                # location in JRE versions 12+, see https://bugs.openjdk.org/browse/JDK-8210931
UNCOV
50
                jli_path = os.path.join(java_home, "lib", "libjli.dylib")
×
51
                if os.path.isfile(jli_path):
×
UNCOV
52
                    return jli_path
×
53
                # location in JRE versions 11-
UNCOV
54
                return os.path.join(java_home, "lib", "jli", "libjli.dylib")
×
55
            return os.path.join(java_home, "lib", "server", "libjvm.so")
1✔
UNCOV
56
        return None
×
57

58
    def get_java_env_vars(
1✔
59
        self, path: str | None = None, ld_library_path: str | None = None
60
    ) -> dict[str, str]:
61
        """
62
        Returns environment variables pointing to the Java installation. This is useful to build the environment where
63
        the application will run.
64

65
        :param path: If not specified, the value of PATH will be obtained from the environment
66
        :param ld_library_path: If not specified, the value of LD_LIBRARY_PATH will be obtained from the environment
67
        :return: dict consisting of two items:
68
            - JAVA_HOME: path to JRE installation
69
            - PATH: the env path variable updated with JRE bin path
70
        """
71
        java_home = self.get_java_home()
×
UNCOV
72
        java_bin = f"{java_home}/bin"
×
73

74
        path = path or os.environ["PATH"]
×
75

76
        library_path = ld_library_path or os.environ.get("LD_LIBRARY_PATH")
×
77
        # null paths (e.g. `:/foo`) have a special meaning according to the manpages
78
        if library_path is None:
×
UNCOV
79
            full_library_path = f"{java_home}/lib:{java_home}/lib/server"
×
80
        else:
UNCOV
81
            full_library_path = f"{java_home}/lib:{java_home}/lib/server:{library_path}"
×
82

UNCOV
83
        return {
×
84
            "JAVA_HOME": java_home,  # type: ignore[dict-item]
85
            "LD_LIBRARY_PATH": full_library_path,
86
            "PATH": f"{java_bin}:{path}",
87
        }
88

89

90
class JavaPackageInstaller(ArchiveDownloadAndExtractInstaller):
1✔
91
    def __init__(self, version: str):
1✔
92
        super().__init__("java", version, extract_single_directory=True)
1✔
93

94
    def _get_install_marker_path(self, install_dir: str) -> str:
1✔
95
        if is_mac_os():
1✔
UNCOV
96
            return os.path.join(install_dir, "Contents", "Home", "bin", "java")
×
97
        return os.path.join(install_dir, "bin", "java")
1✔
98

99
    def _get_download_url(self) -> str:
1✔
100
        # Note: Eclipse Temurin does not provide Mac aarch64 Java 8 builds.
101
        # See https://adoptium.net/en-GB/supported-platforms/
102
        try:
1✔
103
            LOG.debug("Determining the latest Java build version")
1✔
104
            return self._download_url_latest_release()
1✔
UNCOV
105
        except Exception as exc:  # noqa
×
UNCOV
106
            LOG.debug(
×
107
                "Unable to determine the latest Java build version. Using pinned versions: %s", exc
108
            )
UNCOV
109
            return self._download_url_fallback()
×
110

111
    def _post_process(self, target: InstallTarget) -> None:
1✔
112
        target_directory = self._get_install_dir(target)
1✔
113
        minimal_jre_path = os.path.join(target.value, self.name, f"{self.version}.minimal")
1✔
114
        rm_rf(minimal_jre_path)
1✔
115

116
        # If jlink is not available, use the environment as is
117
        if not os.path.exists(os.path.join(target_directory, "bin", "jlink")):
1✔
UNCOV
118
            LOG.warning("Skipping JRE optimisation because jlink is not available")
×
UNCOV
119
            return
×
120

121
        # Build a custom JRE with only the necessary bits to minimise disk footprint
122
        LOG.debug("Optimising JRE installation")
1✔
123

124
        base_modules = [
1✔
125
            # Required modules
126
            "java.base",
127
            "java.desktop",
128
            "java.instrument",
129
            "java.management",
130
            "java.naming",
131
            "java.scripting",
132
            "java.sql",
133
            "java.xml",
134
            "jdk.compiler",
135
            # jdk.unsupported contains sun.misc.Unsafe which is required by some dependencies
136
            "jdk.unsupported",
137
            # Additional cipher suites
138
            "jdk.crypto.cryptoki",
139
            # Archive support
140
            "jdk.zipfs",
141
            # Required by MQ broker
142
            "jdk.httpserver",
143
            "jdk.management",
144
            "jdk.management.agent",
145
            # Required by Spark and Hadoop
146
            "java.security.jgss",
147
            "jdk.security.auth",
148
            # Include required locales
149
            "jdk.localedata",
150
        ]
151

152
        # Add version-specific modules, not all versions require/support the same set
153
        version_specific_modules = {
1✔
154
            "24": ["jdk.incubator.vector"],  # Required for Trino latest version
155
        }
156

157
        modules = base_modules + version_specific_modules.get(self.version, [])
1✔
158
        modules_str = ",".join(modules)
1✔
159

160
        cmd = (
1✔
161
            "bin/jlink "
162
            # Add modules
163
            f"--add-modules {modules_str} "
164
            # Include required locales
165
            "--include-locales en "
166
            # Supplementary args
167
            "--compress 2 --strip-debug --no-header-files --no-man-pages "
168
            # Output directory
169
            "--output " + minimal_jre_path
170
        )
171
        run(cmd, cwd=target_directory)
1✔
172

173
        rm_rf(target_directory)
1✔
174
        os.rename(minimal_jre_path, target_directory)
1✔
175

176
    def get_java_home(self) -> str | None:
1✔
177
        """
178
        Get JAVA_HOME for this installation of Java.
179
        """
180
        installed_dir = self.get_installed_dir()
1✔
181
        if is_mac_os():
1✔
UNCOV
182
            return os.path.join(installed_dir, "Contents", "Home")  # type: ignore[arg-type]
×
183
        return installed_dir
1✔
184

185
    @property
1✔
186
    def arch(self) -> str | None:
1✔
187
        return (
1✔
188
            "x64" if get_arch() == Arch.amd64 else "aarch64" if get_arch() == Arch.arm64 else None
189
        )
190

191
    @property
1✔
192
    def os_name(self) -> str | None:
1✔
193
        return "linux" if is_linux() else "mac" if is_mac_os() else None
1✔
194

195
    def _download_url_latest_release(self) -> str:
1✔
196
        """
197
        Return the download URL for latest stable JDK build.
198
        """
199
        endpoint = (
1✔
200
            f"https://api.adoptium.net/v3/assets/latest/{self.version}/hotspot?"
201
            f"os={self.os_name}&architecture={self.arch}&image_type=jdk"
202
        )
203
        # Override user-agent because Adoptium API denies service to `requests` library
204
        response = requests.get(
1✔
205
            endpoint, headers={"user-agent": USER_AGENT_STRING}, proxies=get_proxies()
206
        ).json()
207
        return response[0]["binary"]["package"]["link"]
1✔
208

209
    def _download_url_fallback(self) -> str:
1✔
210
        """
211
        Return the download URL for pinned JDK build.
212
        """
213
        semver = JAVA_VERSIONS[self.version]
×
214
        tag_slug = f"jdk-{semver}"
×
215
        semver_safe = semver.replace("+", "_")
×
216

217
        # v8 uses a different tag and version scheme
UNCOV
218
        if self.version == "8":
×
UNCOV
219
            semver_safe = semver_safe.replace("-", "")
×
UNCOV
220
            tag_slug = f"jdk{semver}"
×
221

UNCOV
222
        return (
×
223
            f"https://github.com/adoptium/temurin{self.version}-binaries/releases/download/{tag_slug}/"
224
            f"OpenJDK{self.version}U-jdk_{self.arch}_{self.os_name}_hotspot_{semver_safe}.tar.gz"
225
        )
226

227

228
class JavaPackage(Package[JavaPackageInstaller]):
1✔
229
    def __init__(self, default_version: str = DEFAULT_JAVA_VERSION):
1✔
230
        super().__init__(name="Java", default_version=default_version)
1✔
231

232
    def get_versions(self) -> list[str]:
1✔
233
        return list(JAVA_VERSIONS.keys())
1✔
234

235
    def _get_installer(self, version: str) -> JavaPackageInstaller:
1✔
236
        return JavaPackageInstaller(version)
1✔
237

238

239
java_package = JavaPackage()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc