From 28af43b7073fd3bf31b9d8d62921d2b2fc654c7e Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Sun, 20 Apr 2025 15:58:04 -0400 Subject: [PATCH 1/8] feat: add auto-fetch with cjdk --- .github/workflows/build.yml | 10 +++- dev-environment.yml | 1 + pyproject.toml | 2 + src/scyjava/_cjdk_fetch.py | 99 +++++++++++++++++++++++++++++++++++++ src/scyjava/_jvm.py | 39 ++++++++++++++- 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/scyjava/_cjdk_fetch.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b246a0..92cd367 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,13 @@ jobs: '3.10', '3.12' ] + java-version: ['11'] + include: + # one test without java to test cjdk fallback + - os: ubuntu-latest + python-version: '3.12' + java-version: '' + steps: - uses: actions/checkout@v2 @@ -35,8 +42,9 @@ jobs: python-version: ${{matrix.python-version}} - uses: actions/setup-java@v3 + if: matrix.java-version != '' with: - java-version: '11' + java-version: ${{matrix.java-version}} distribution: 'zulu' cache: 'maven' diff --git a/dev-environment.yml b/dev-environment.yml index d8d2568..2e0f6ee 100644 --- a/dev-environment.yml +++ b/dev-environment.yml @@ -37,5 +37,6 @@ dependencies: # Project from source - pip - pip: + - cjdk - git+https://github.com/ninia/jep.git@cfca63f8b3398daa6d2685428660dc4b2bfab67d - -e . diff --git a/pyproject.toml b/pyproject.toml index 1332f1a..9db6da7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,9 @@ dependencies = [ [project.optional-dependencies] # NB: Keep this in sync with dev-environment.yml! +cjdk = ["cjdk"] dev = [ + "scyjava[cjdk]", "assertpy", "build", "jep", diff --git a/src/scyjava/_cjdk_fetch.py b/src/scyjava/_cjdk_fetch.py new file mode 100644 index 0000000..a620aad --- /dev/null +++ b/src/scyjava/_cjdk_fetch.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import logging +import os +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path + +_logger = logging.getLogger(__name__) +_DEFAULT_MAVEN_URL = "tgz+https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz" # noqa: E501 +_DEFAULT_MAVEN_SHA = "a555254d6b53d267965a3404ecb14e53c3827c09c3b94b5678835887ab404556bfaf78dcfe03ba76fa2508649dca8531c74bca4d5846513522404d48e8c4ac8b" # noqa: E501 +_DEFAULT_JAVA_VENDOR = "zulu-jre" +_DEFAULT_JAVA_VERSION = "11" + + +def cjdk_fetch_java( + vendor: str = "", version: str = "", raise_on_error: bool = True +) -> None: + """Fetch java using cjdk and add it to the PATH.""" + try: + import cjdk + except ImportError as e: + if raise_on_error is True: + raise ImportError( + "No JVM found. Please install `cjdk` to use the fetch_java feature." + ) from e + _logger.info("cjdk is not installed. Skipping automatic fetching of java.") + return + + if not vendor: + vendor = os.getenv("JAVA_VENDOR", _DEFAULT_JAVA_VENDOR) + version = os.getenv("JAVA_VERSION", _DEFAULT_JAVA_VERSION) + + _logger.info(f"No JVM found, fetching {vendor}:{version} using cjdk...") + home = cjdk.java_home(vendor=vendor, version=version) + _add_to_path(str(home / "bin")) + os.environ["JAVA_HOME"] = str(home) + + +def cjdk_fetch_maven(url: str = "", sha: str = "", raise_on_error: bool = True) -> None: + """Fetch Maven using cjdk and add it to the PATH.""" + try: + import cjdk + except ImportError as e: + if raise_on_error is True: + raise ImportError( + "Please install `cjdk` to use the fetch_java feature." + ) from e + _logger.info("cjdk is not installed. Skipping automatic fetching of Maven.") + return + + # if url was passed as an argument, or env_var, use it with provided sha + # otherwise, use default values for both + if url := url or os.getenv("MAVEN_URL", ""): + sha = sha or os.getenv("MAVEN_SHA", "") + else: + url = _DEFAULT_MAVEN_URL + sha = _DEFAULT_MAVEN_SHA + + # fix urls to have proper prefix for cjdk + if url.startswith("http"): + if url.endswith(".tar.gz"): + url = url.replace("http", "tgz+http") + elif url.endswith(".zip"): + url = url.replace("http", "zip+http") + + # determine sha type based on length (cjdk requires specifying sha type) + # assuming hex-encoded SHA, length should be 40, 64, or 128 + kwargs = {} + if sha_len := len(sha): # empty sha is fine... we just don't pass it + sha_lengths = {40: "sha1", 64: "sha256", 128: "sha512"} + if sha_len not in sha_lengths: + raise ValueError( + "MAVEN_SHA be a valid sha1, sha256, or sha512 hash." + f"Got invalid SHA length: {sha_len}. " + ) + kwargs = {sha_lengths[sha_len]: sha} + + maven_dir = cjdk.cache_package("Maven", url, **kwargs) + if maven_bin := next(maven_dir.rglob("apache-maven-*/**/mvn"), None): + _add_to_path(maven_bin.parent, front=True) + else: + raise RuntimeError("Failed to find Maven executable in the downloaded package.") + + +def _add_to_path(path: Path | str, front: bool = False) -> None: + """Add a path to the PATH environment variable. + + If front is True, the path is added to the front of the PATH. + By default, the path is added to the end of the PATH. + If the path is already in the PATH, it is not added again. + """ + + current_path = os.environ.get("PATH", "") + if (path := str(path)) in current_path: + return + new_path = [path, current_path] if front else [current_path, path] + os.environ["PATH"] = os.pathsep.join(new_path) diff --git a/src/scyjava/_jvm.py b/src/scyjava/_jvm.py index 1a6c5ca..0b8f215 100644 --- a/src/scyjava/_jvm.py +++ b/src/scyjava/_jvm.py @@ -6,6 +6,7 @@ import logging import os import re +import shutil import subprocess import sys from functools import lru_cache @@ -16,6 +17,7 @@ import jpype.config from jgo import jgo +from scyjava._cjdk_fetch import cjdk_fetch_java, cjdk_fetch_maven import scyjava.config from scyjava.config import Mode, mode @@ -106,7 +108,7 @@ def jvm_version() -> str: return tuple(map(int, m.group(1).split("."))) -def start_jvm(options=None) -> None: +def start_jvm(options=None, *, fetch_java: bool | None = None) -> None: """ Explicitly connect to the Java virtual machine (JVM). Only one JVM can be active; does nothing if the JVM has already been started. Calling @@ -117,6 +119,13 @@ def start_jvm(options=None) -> None: :param options: List of options to pass to the JVM. For example: ['-Dfoo=bar', '-XX:+UnlockExperimentalVMOptions'] + :param fetch_java: + Whether to automatically fetch a JRE (and/or maven) using + [`cjdk`](https://github.com/cachedjdk/cjdk) if java and maven executables are + not found. Requires `cjdk` to be installed. See README for details. + - If `None` (default), then fetching will only occur if `cjdk` is available. + - If `True`, an exception will be raised if `cjdk` is not available. + - If `False`, no attempt to import `cjdk` is be made. """ # if JVM is already running -- break if jvm_started(): @@ -132,8 +141,14 @@ def start_jvm(options=None) -> None: # use the logger to notify user that endpoints are being added _logger.debug("Adding jars from endpoints {0}".format(endpoints)) + if fetch_java is not False and not is_jvm_available(): + cjdk_fetch_java(raise_on_error=fetch_java is True) + # get endpoints and add to JPype class path if len(endpoints) > 0: + if not shutil.which("mvn") and fetch_java is not False: + cjdk_fetch_maven(raise_on_error=fetch_java is True) + endpoints = endpoints[:1] + sorted(endpoints[1:]) _logger.debug("Using endpoints %s", endpoints) _, workspace = jgo.resolve_dependencies( @@ -340,6 +355,28 @@ def is_jvm_headless() -> bool: return bool(GraphicsEnvironment.isHeadless()) +def is_jvm_available() -> bool: + """ + Return True if the JVM is available, suppressing stderr on macos. + """ + from unittest.mock import patch + + subprocess_check_output = subprocess.check_output + + def _silent_check_output(*args, **kwargs): + # also suppress stderr on calls to subprocess.check_output + kwargs.setdefault("stderr", subprocess.DEVNULL) + return subprocess_check_output(*args, **kwargs) + + try: + with patch.object(subprocess, "check_output", new=_silent_check_output): + jpype.getDefaultJVMPath() + # on Darwin, may raise a CalledProcessError when invoking `/user/libexec/java_home` + except (jpype.JVMNotFoundException, subprocess.CalledProcessError): + return False + return True + + def is_awt_initialized() -> bool: """ Return true iff the AWT subsystem has been initialized. From aa913dd2bb2132bf36a59c2fe5da06b89429f194 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Sun, 20 Apr 2025 16:06:32 -0400 Subject: [PATCH 2/8] reorg --- src/scyjava/_cjdk_fetch.py | 38 ++++++++++++++++++++++++++++++++++++-- src/scyjava/_jvm.py | 38 +++++++------------------------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/scyjava/_cjdk_fetch.py b/src/scyjava/_cjdk_fetch.py index a620aad..3cdcfba 100644 --- a/src/scyjava/_cjdk_fetch.py +++ b/src/scyjava/_cjdk_fetch.py @@ -2,8 +2,12 @@ import logging import os +import shutil +import subprocess from typing import TYPE_CHECKING +import jpype + if TYPE_CHECKING: from pathlib import Path @@ -14,6 +18,34 @@ _DEFAULT_JAVA_VERSION = "11" +def ensure_jvm_available(raise_on_error: bool = True) -> None: + """Ensure that the JVM is available, or raise if `raise_on_error` is True.""" + if not is_jvm_available(): + cjdk_fetch_java(raise_on_error=raise_on_error) + if not shutil.which("mvn"): + cjdk_fetch_maven(raise_on_error=raise_on_error) + + +def is_jvm_available() -> bool: + """Return True if the JVM is available, suppressing stderr on macos.""" + from unittest.mock import patch + + subprocess_check_output = subprocess.check_output + + def _silent_check_output(*args, **kwargs): + # also suppress stderr on calls to subprocess.check_output + kwargs.setdefault("stderr", subprocess.DEVNULL) + return subprocess_check_output(*args, **kwargs) + + try: + with patch.object(subprocess, "check_output", new=_silent_check_output): + jpype.getDefaultJVMPath() + # on Darwin, may raise a CalledProcessError when invoking `/user/libexec/java_home` + except (jpype.JVMNotFoundException, subprocess.CalledProcessError): + return False + return True + + def cjdk_fetch_java( vendor: str = "", version: str = "", raise_on_error: bool = True ) -> None: @@ -25,7 +57,7 @@ def cjdk_fetch_java( raise ImportError( "No JVM found. Please install `cjdk` to use the fetch_java feature." ) from e - _logger.info("cjdk is not installed. Skipping automatic fetching of java.") + _logger.info("JVM not found. Please install `cjdk` fetch java automatically.") return if not vendor: @@ -47,7 +79,9 @@ def cjdk_fetch_maven(url: str = "", sha: str = "", raise_on_error: bool = True) raise ImportError( "Please install `cjdk` to use the fetch_java feature." ) from e - _logger.info("cjdk is not installed. Skipping automatic fetching of Maven.") + _logger.info( + "Maven not found. Please install `cjdk` fetch maven automatically." + ) return # if url was passed as an argument, or env_var, use it with provided sha diff --git a/src/scyjava/_jvm.py b/src/scyjava/_jvm.py index 0b8f215..a69a737 100644 --- a/src/scyjava/_jvm.py +++ b/src/scyjava/_jvm.py @@ -6,7 +6,6 @@ import logging import os import re -import shutil import subprocess import sys from functools import lru_cache @@ -17,7 +16,7 @@ import jpype.config from jgo import jgo -from scyjava._cjdk_fetch import cjdk_fetch_java, cjdk_fetch_maven +from scyjava._cjdk_fetch import ensure_jvm_available import scyjava.config from scyjava.config import Mode, mode @@ -120,10 +119,12 @@ def start_jvm(options=None, *, fetch_java: bool | None = None) -> None: List of options to pass to the JVM. For example: ['-Dfoo=bar', '-XX:+UnlockExperimentalVMOptions'] :param fetch_java: - Whether to automatically fetch a JRE (and/or maven) using + Whether to automatically fetch a JRE (and maven) using [`cjdk`](https://github.com/cachedjdk/cjdk) if java and maven executables are - not found. Requires `cjdk` to be installed. See README for details. + not found. Requires `cjdk` to be installed, either manually, or via the + `scyjava[cjdk]` extra. - If `None` (default), then fetching will only occur if `cjdk` is available. + (A log info will be issued if `cjdk` is not available.) - If `True`, an exception will be raised if `cjdk` is not available. - If `False`, no attempt to import `cjdk` is be made. """ @@ -141,14 +142,11 @@ def start_jvm(options=None, *, fetch_java: bool | None = None) -> None: # use the logger to notify user that endpoints are being added _logger.debug("Adding jars from endpoints {0}".format(endpoints)) - if fetch_java is not False and not is_jvm_available(): - cjdk_fetch_java(raise_on_error=fetch_java is True) + if fetch_java is not False: + ensure_jvm_available(raise_on_error=fetch_java is True) # get endpoints and add to JPype class path if len(endpoints) > 0: - if not shutil.which("mvn") and fetch_java is not False: - cjdk_fetch_maven(raise_on_error=fetch_java is True) - endpoints = endpoints[:1] + sorted(endpoints[1:]) _logger.debug("Using endpoints %s", endpoints) _, workspace = jgo.resolve_dependencies( @@ -355,28 +353,6 @@ def is_jvm_headless() -> bool: return bool(GraphicsEnvironment.isHeadless()) -def is_jvm_available() -> bool: - """ - Return True if the JVM is available, suppressing stderr on macos. - """ - from unittest.mock import patch - - subprocess_check_output = subprocess.check_output - - def _silent_check_output(*args, **kwargs): - # also suppress stderr on calls to subprocess.check_output - kwargs.setdefault("stderr", subprocess.DEVNULL) - return subprocess_check_output(*args, **kwargs) - - try: - with patch.object(subprocess, "check_output", new=_silent_check_output): - jpype.getDefaultJVMPath() - # on Darwin, may raise a CalledProcessError when invoking `/user/libexec/java_home` - except (jpype.JVMNotFoundException, subprocess.CalledProcessError): - return False - return True - - def is_awt_initialized() -> bool: """ Return true iff the AWT subsystem has been initialized. From c9cf3cae8a0bb5503a64d4c14243c8957a0a2aed Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Sun, 20 Apr 2025 16:09:07 -0400 Subject: [PATCH 3/8] move import --- src/scyjava/_jvm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scyjava/_jvm.py b/src/scyjava/_jvm.py index a69a737..13fe866 100644 --- a/src/scyjava/_jvm.py +++ b/src/scyjava/_jvm.py @@ -16,7 +16,6 @@ import jpype.config from jgo import jgo -from scyjava._cjdk_fetch import ensure_jvm_available import scyjava.config from scyjava.config import Mode, mode @@ -143,6 +142,8 @@ def start_jvm(options=None, *, fetch_java: bool | None = None) -> None: _logger.debug("Adding jars from endpoints {0}".format(endpoints)) if fetch_java is not False: + from scyjava._cjdk_fetch import ensure_jvm_available + ensure_jvm_available(raise_on_error=fetch_java is True) # get endpoints and add to JPype class path From ace4df58d2d4ec62c46220b7cf8e312e7f002439 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sun, 20 Apr 2025 18:19:20 -0500 Subject: [PATCH 4/8] Avoid usage of pipe symbol in type hints --- src/scyjava/_cjdk_fetch.py | 4 ++-- src/scyjava/_jvm.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/scyjava/_cjdk_fetch.py b/src/scyjava/_cjdk_fetch.py index 3cdcfba..8199335 100644 --- a/src/scyjava/_cjdk_fetch.py +++ b/src/scyjava/_cjdk_fetch.py @@ -4,7 +4,7 @@ import os import shutil import subprocess -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union import jpype @@ -118,7 +118,7 @@ def cjdk_fetch_maven(url: str = "", sha: str = "", raise_on_error: bool = True) raise RuntimeError("Failed to find Maven executable in the downloaded package.") -def _add_to_path(path: Path | str, front: bool = False) -> None: +def _add_to_path(path: Union[Path, str], front: bool = False) -> None: """Add a path to the PATH environment variable. If front is True, the path is added to the front of the PATH. diff --git a/src/scyjava/_jvm.py b/src/scyjava/_jvm.py index 13fe866..90f42d0 100644 --- a/src/scyjava/_jvm.py +++ b/src/scyjava/_jvm.py @@ -11,6 +11,7 @@ from functools import lru_cache from importlib import import_module from pathlib import Path +from typing import Optional import jpype import jpype.config @@ -106,7 +107,7 @@ def jvm_version() -> str: return tuple(map(int, m.group(1).split("."))) -def start_jvm(options=None, *, fetch_java: bool | None = None) -> None: +def start_jvm(options=None, *, fetch_java: Optional[bool] = None) -> None: """ Explicitly connect to the Java virtual machine (JVM). Only one JVM can be active; does nothing if the JVM has already been started. Calling From 64ffbeebbe9ff314fb9256046ffdf26ab5097f6b Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sun, 20 Apr 2025 18:38:09 -0500 Subject: [PATCH 5/8] CI: skip jep tests when running the javaless job * Include java version in job names, to disambiguate them. * As a hack for now, just fail if Java >= v17 or <8. --- .github/workflows/build.yml | 3 +-- bin/test.sh | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92cd367..29b8797 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ on: jobs: build-cross-platform: - name: test ${{matrix.os}} - ${{matrix.python-version}} + name: test ${{matrix.os}} - ${{matrix.python-version}} - ${{matrix.java-version}} runs-on: ${{ matrix.os }} strategy: matrix: @@ -33,7 +33,6 @@ jobs: python-version: '3.12' java-version: '' - steps: - uses: actions/checkout@v2 diff --git a/bin/test.sh b/bin/test.sh index da27567..db00333 100755 --- a/bin/test.sh +++ b/bin/test.sh @@ -73,7 +73,12 @@ then else argString="" fi -if [ "$(uname -s)" = "Darwin" ] +if ! java -version 2>&1 | grep -q '^openjdk version "\(1\.8\|9\|10\|11\|12\|13\|14\|15\|16\)\.' +then + echo "Skipping jep tests due to unsupported Java version:" + java -version || true + jepCode=0 +elif [ "$(uname -s)" = "Darwin" ] then echo "Skipping jep tests on macOS due to flakiness" jepCode=0 From ccb2271cf1e8056d8425a55c5d513080354101f0 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Sun, 20 Apr 2025 21:12:54 -0400 Subject: [PATCH 6/8] include cjdk by default --- pyproject.toml | 3 +-- src/scyjava/_cjdk_fetch.py | 46 +++++++++++--------------------------- src/scyjava/_jvm.py | 26 ++++++++++++--------- 3 files changed, 29 insertions(+), 46 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9db6da7..89b4200 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,13 +35,12 @@ requires-python = ">=3.8" dependencies = [ "jpype1 >= 1.3.0", "jgo", + "cjdk", ] [project.optional-dependencies] # NB: Keep this in sync with dev-environment.yml! -cjdk = ["cjdk"] dev = [ - "scyjava[cjdk]", "assertpy", "build", "jep", diff --git a/src/scyjava/_cjdk_fetch.py b/src/scyjava/_cjdk_fetch.py index 8199335..27c8035 100644 --- a/src/scyjava/_cjdk_fetch.py +++ b/src/scyjava/_cjdk_fetch.py @@ -6,6 +6,7 @@ import subprocess from typing import TYPE_CHECKING, Union +import cjdk import jpype if TYPE_CHECKING: @@ -18,12 +19,12 @@ _DEFAULT_JAVA_VERSION = "11" -def ensure_jvm_available(raise_on_error: bool = True) -> None: - """Ensure that the JVM is available, or raise if `raise_on_error` is True.""" +def ensure_jvm_available() -> None: + """Ensure that the JVM is available and Maven is installed.""" if not is_jvm_available(): - cjdk_fetch_java(raise_on_error=raise_on_error) + cjdk_fetch_java() if not shutil.which("mvn"): - cjdk_fetch_maven(raise_on_error=raise_on_error) + cjdk_fetch_maven() def is_jvm_available() -> bool: @@ -46,20 +47,8 @@ def _silent_check_output(*args, **kwargs): return True -def cjdk_fetch_java( - vendor: str = "", version: str = "", raise_on_error: bool = True -) -> None: +def cjdk_fetch_java(vendor: str = "", version: str = "") -> None: """Fetch java using cjdk and add it to the PATH.""" - try: - import cjdk - except ImportError as e: - if raise_on_error is True: - raise ImportError( - "No JVM found. Please install `cjdk` to use the fetch_java feature." - ) from e - _logger.info("JVM not found. Please install `cjdk` fetch java automatically.") - return - if not vendor: vendor = os.getenv("JAVA_VENDOR", _DEFAULT_JAVA_VENDOR) version = os.getenv("JAVA_VERSION", _DEFAULT_JAVA_VERSION) @@ -70,20 +59,8 @@ def cjdk_fetch_java( os.environ["JAVA_HOME"] = str(home) -def cjdk_fetch_maven(url: str = "", sha: str = "", raise_on_error: bool = True) -> None: +def cjdk_fetch_maven(url: str = "", sha: str = "") -> None: """Fetch Maven using cjdk and add it to the PATH.""" - try: - import cjdk - except ImportError as e: - if raise_on_error is True: - raise ImportError( - "Please install `cjdk` to use the fetch_java feature." - ) from e - _logger.info( - "Maven not found. Please install `cjdk` fetch maven automatically." - ) - return - # if url was passed as an argument, or env_var, use it with provided sha # otherwise, use default values for both if url := url or os.getenv("MAVEN_URL", ""): @@ -104,7 +81,7 @@ def cjdk_fetch_maven(url: str = "", sha: str = "", raise_on_error: bool = True) kwargs = {} if sha_len := len(sha): # empty sha is fine... we just don't pass it sha_lengths = {40: "sha1", 64: "sha256", 128: "sha512"} - if sha_len not in sha_lengths: + if sha_len not in sha_lengths: # pragma: no cover raise ValueError( "MAVEN_SHA be a valid sha1, sha256, or sha512 hash." f"Got invalid SHA length: {sha_len}. " @@ -114,8 +91,11 @@ def cjdk_fetch_maven(url: str = "", sha: str = "", raise_on_error: bool = True) maven_dir = cjdk.cache_package("Maven", url, **kwargs) if maven_bin := next(maven_dir.rglob("apache-maven-*/**/mvn"), None): _add_to_path(maven_bin.parent, front=True) - else: - raise RuntimeError("Failed to find Maven executable in the downloaded package.") + else: # pragma: no cover + raise RuntimeError( + "Failed to find Maven executable on system " + "PATH, and download via cjdk failed." + ) def _add_to_path(path: Union[Path, str], front: bool = False) -> None: diff --git a/src/scyjava/_jvm.py b/src/scyjava/_jvm.py index 90f42d0..617b9d6 100644 --- a/src/scyjava/_jvm.py +++ b/src/scyjava/_jvm.py @@ -107,7 +107,7 @@ def jvm_version() -> str: return tuple(map(int, m.group(1).split("."))) -def start_jvm(options=None, *, fetch_java: Optional[bool] = None) -> None: +def start_jvm(options=None, *, fetch_java: bool = True) -> None: """ Explicitly connect to the Java virtual machine (JVM). Only one JVM can be active; does nothing if the JVM has already been started. Calling @@ -119,14 +119,18 @@ def start_jvm(options=None, *, fetch_java: Optional[bool] = None) -> None: List of options to pass to the JVM. For example: ['-Dfoo=bar', '-XX:+UnlockExperimentalVMOptions'] :param fetch_java: - Whether to automatically fetch a JRE (and maven) using - [`cjdk`](https://github.com/cachedjdk/cjdk) if java and maven executables are - not found. Requires `cjdk` to be installed, either manually, or via the - `scyjava[cjdk]` extra. - - If `None` (default), then fetching will only occur if `cjdk` is available. - (A log info will be issued if `cjdk` is not available.) - - If `True`, an exception will be raised if `cjdk` is not available. - - If `False`, no attempt to import `cjdk` is be made. + If True (default), when a JVM/or maven cannot be located on the system, + [`cjdk`](https://github.com/cachedjdk/cjdk) will be used to download + a JRE distribution and set up the JVM. The following environment variables + may be used to configure the JRE and Maven distributions to download: + * `JAVA_VENDOR`: The vendor of the JRE distribution to download. + Defaults to "zulu-jre". + * `JAVA_VERSION`: The version of the JRE distribution to download. + Defaults to "11". + * `MAVEN_URL`: The URL of the Maven distribution to download. + Defaults to https://dlcdn.apache.org/maven/maven-3/3.9.9/ + * `MAVEN_SHA`: The SHA512 hash of the Maven distribution to download, if + providing a custom MAVEN_URL. """ # if JVM is already running -- break if jvm_started(): @@ -142,10 +146,10 @@ def start_jvm(options=None, *, fetch_java: Optional[bool] = None) -> None: # use the logger to notify user that endpoints are being added _logger.debug("Adding jars from endpoints {0}".format(endpoints)) - if fetch_java is not False: + if fetch_java: from scyjava._cjdk_fetch import ensure_jvm_available - ensure_jvm_available(raise_on_error=fetch_java is True) + ensure_jvm_available() # get endpoints and add to JPype class path if len(endpoints) > 0: From 0af3f650c310499a34c64654671ddef90267801d Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sun, 20 Apr 2025 20:17:20 -0500 Subject: [PATCH 7/8] Bump to setuptools 77.0.0 to use new license field And bump the minimum Python version to 3.9, since that version of setuptools requires it. And raise the test ceiling from Python 3.12 to 3.13. --- .github/workflows/build.yml | 7 +++---- dev-environment.yml | 2 +- environment.yml | 2 +- pyproject.toml | 10 +++++----- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29b8797..a6dbb29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,15 +22,14 @@ jobs: macos-latest ] python-version: [ - '3.8', - '3.10', - '3.12' + '3.9', + '3.13' ] java-version: ['11'] include: # one test without java to test cjdk fallback - os: ubuntu-latest - python-version: '3.12' + python-version: '3.9' java-version: '' steps: diff --git a/dev-environment.yml b/dev-environment.yml index 2e0f6ee..bbacab4 100644 --- a/dev-environment.yml +++ b/dev-environment.yml @@ -18,7 +18,7 @@ name: scyjava-dev channels: - conda-forge dependencies: - - python >= 3.8 + - python >= 3.9 # Project dependencies - jpype1 >= 1.3.0 - jgo diff --git a/environment.yml b/environment.yml index c1038c5..d3e3af9 100644 --- a/environment.yml +++ b/environment.yml @@ -19,7 +19,7 @@ name: scyjava channels: - conda-forge dependencies: - - python >= 3.8 + - python >= 3.9 # Project dependencies - jpype1 >= 1.3.0 - jgo diff --git a/pyproject.toml b/pyproject.toml index 89b4200..8da9830 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["setuptools>=61.2"] +requires = ["setuptools>=77.0.0"] build-backend = "setuptools.build_meta" [project] name = "scyjava" -version = "1.10.3.dev0" +version = "1.11.0.dev0" description = "Supercharged Java access from Python" -license = {text = "Unlicense"} +license = "Unlicense" authors = [{name = "SciJava developers", email = "ctrueden@wisc.edu"}] readme = "README.md" keywords = ["java", "maven", "cross-language"] @@ -16,11 +16,11 @@ classifiers = [ "Intended Audience :: Education", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: Microsoft :: Windows", "Operating System :: Unix", "Operating System :: MacOS", @@ -31,7 +31,7 @@ classifiers = [ ] # NB: Keep this in sync with environment.yml AND dev-environment.yml! -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "jpype1 >= 1.3.0", "jgo", From 3af5c6e0cdff27b25ff28bc56c7f14274af0baa5 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sun, 20 Apr 2025 20:20:50 -0500 Subject: [PATCH 8/8] Make ruff happy --- src/scyjava/_jvm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/scyjava/_jvm.py b/src/scyjava/_jvm.py index 617b9d6..2035e34 100644 --- a/src/scyjava/_jvm.py +++ b/src/scyjava/_jvm.py @@ -11,7 +11,6 @@ from functools import lru_cache from importlib import import_module from pathlib import Path -from typing import Optional import jpype import jpype.config