Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add list-jdks and list-vendors commands #22

Merged
merged 25 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
499285f
Fix typos in documentation
ctrueden Feb 2, 2023
68e3fff
Add function to return all matching versions
ctrueden Feb 2, 2023
4cd6301
Add a command to list matching JDKs
ctrueden Feb 2, 2023
ee3b519
Add a command to list available vendors
ctrueden Feb 2, 2023
e94730e
Rename the listing commands
ctrueden Aug 1, 2024
fb15d4b
Rename cache-jdk command to cache
ctrueden Aug 1, 2024
b0a3f9c
Add type alias for the JDK index dict
ctrueden Aug 1, 2024
3da11f4
Eliminate resolve_jdk_version code duplication
ctrueden Aug 1, 2024
cfc613e
Constrain ls to already-cached JDKs by default
ctrueden Aug 1, 2024
5461c0f
Merge the ibm-semuru-openj9-java<##> vendors
ctrueden Aug 1, 2024
b84b13c
Ruff up the code
ctrueden Aug 1, 2024
98a2bef
Guard against weird index structures
ctrueden Aug 2, 2024
d38d3de
Split out helper methods for ls and ls-vendors
ctrueden Aug 2, 2024
5377c92
Let ls operate on all vendors by default
ctrueden Aug 2, 2024
d53ba08
Make the version comparison more robust
ctrueden Aug 2, 2024
4c3ed9c
Remove unused available_vendors function
ctrueden Aug 8, 2024
0f60b55
Move index postprocessing to helper function
ctrueden Aug 8, 2024
b670165
Add tests for new functions and parameters
ctrueden Aug 8, 2024
ac6b76d
Update CLI documentation
ctrueden Aug 8, 2024
33bfbc3
Update the changelog
ctrueden Aug 8, 2024
f73ad23
CI: Don't cancel other OS tests on failure
marktsuchida Aug 9, 2024
0273f7b
Move error generation to the right place
marktsuchida Aug 9, 2024
e6a6f28
Edit test for non-Linux
marktsuchida Aug 9, 2024
fd8a044
Return, do not print, in Python API
marktsuchida Aug 9, 2024
0d35cf5
Add new Python APIs to docs
marktsuchida Aug 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:

test:
strategy:
fail-fast: false
matrix:
runner: [ubuntu-latest, macos-latest, windows-latest]
name: test-${{ matrix.runner }}
Expand Down
12 changes: 12 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ SPDX-License-Identifier: MIT

# Python API

## Querying the JDK index

```{eval-rst}
.. autofunction:: cjdk.list_vendors
.. versionadded:: 0.4.0
```

```{eval-rst}
.. autofunction:: cjdk.list_jdks
.. versionadded:: 0.4.0
```

## Working with cached JDKs

```{eval-rst}
Expand Down
10 changes: 9 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ See also the section on [versioning](versioning-scheme).

## [Unreleased]

- No notable changes yet.
### Added

- Python API functions `list_jdks()` and `list_vendors()`.
- Command line commands `ls` and `ls-vendors`.
- Light postprocessing of vendor names, notably `ibm-semeru-openj9`.

### Changed

- Command line command `cache-jdk` renamed to `cache`.

## [0.3.0] - 2022-07-09

Expand Down
27 changes: 24 additions & 3 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ More details about the choices and defaults for [`VENDOR`](./vendors.md),
[`VERSION`](./versions.md), and [`--cache_dir`](./cachedir.md) are available on
separate pages.

## Querying the JDK index

### `ls`

```{command-output} cjdk ls --help
```

```{eval-rst}
.. versionadded:: 0.4.0
```

### `ls-vendors`

```{command-output} cjdk ls-vendors --help
```

```{eval-rst}
.. versionadded:: 0.4.0
```

## Working with cached JDKs

### `exec`
Expand Down Expand Up @@ -53,13 +73,14 @@ $ cjdk --jdk temurin-jre:17.0.3 java-home
(The output will depend on your operating system and configuration; the example
shown was on macOS.)

### `cache-jdk`
### `cache`

```{command-output} cjdk cache-jdk --help
```{command-output} cjdk cache --help
```

```{eval-rst}
.. versionadded:: 0.2.0
.. versionchanged:: 0.4.0
Renamed from ``cache-jdk``.
```

## Caching arbitrary files and packages
Expand Down
12 changes: 11 additions & 1 deletion src/cjdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
# Copyright 2022 Board of Regents of the University of Wisconsin System
# SPDX-License-Identifier: MIT

from ._api import cache_file, cache_jdk, cache_package, java_env, java_home
from ._api import (
cache_file,
cache_jdk,
cache_package,
java_env,
java_home,
list_jdks,
list_vendors,
)
from ._version import __version__ as __version__

__all__ = [
Expand All @@ -11,4 +19,6 @@
"cache_package",
"java_env",
"java_home",
"list_jdks",
"list_vendors",
]
51 changes: 47 additions & 4 deletions src/cjdk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,43 @@ def main(ctx, jdk, cache_dir, index_url, index_ttl, os, arch, progress):
)


@click.command(short_help="List available JDK vendors.")
@click.pass_context
def ls_vendors(ctx):
"""
Print the list of available JDK vendors.
"""
vendors = _api.list_vendors(**ctx.obj)
if vendors:
print("\n".join(vendors))


@click.command(short_help="List cached or available JDKs matching criteria.")
@click.pass_context
@click.option(
"--cached/--available",
default=True,
help="Show only already-cached JDKs, or show all available JDKs from the index (default cached only).",
)
def ls(ctx, cached: bool = False):
"""
Print the list of JDKs matching the given criteria.

See 'cjdk --help' for the common options used to specify the criteria.
"""
jdks = _api.list_jdks(**ctx.obj, cached_only=cached)
if jdks:
print("\n".join(jdks))


@click.command(short_help="Ensure the requested JDK is cached.")
@click.pass_context
def cache_jdk(ctx):
def cache(ctx):
"""
Download and extract the requested JDK if it is not already cached.

Usually there is no need to invoke this command on its own, but it may be
useful if you want any potentil JDK download to happen at a controlled
useful if you want any potential JDK download to happen at a controlled
point in time.

See 'cjdk --help' for the common options used to specify the JDK and how it
Expand All @@ -80,6 +109,15 @@ def cache_jdk(ctx):
_api.cache_jdk(**ctx.obj)


@click.command(hidden=True)
@click.pass_context
def cache_jdk(ctx):
"""
Deprecated. Use cache function instead.
"""
_api.cache_jdk(**ctx.obj)


@click.command(
short_help="Print the Java home directory for the requested JDK."
)
Expand Down Expand Up @@ -108,7 +146,7 @@ def exec(ctx, prog, args):
"""
Run PROG with the environment variables set for the requested JDK.

The JDK is download if not already cached.
The JDK is downloaded if not already cached.

See 'cjdk --help' for the common options used to specify the JDK and how it
is obtained.
Expand Down Expand Up @@ -221,12 +259,17 @@ def cache_package(ctx, url, name, sha1, sha256, sha512):
)


# Register current commands.
main.add_command(java_home)
main.add_command(exec)
main.add_command(cache_jdk)
main.add_command(ls_vendors)
main.add_command(ls)
main.add_command(cache)
main.add_command(cache_file)
main.add_command(cache_package)

# Register hidden/deprecated commands, for backwards compatibility.
main.add_command(cache_jdk)

if __name__ == "__main__":
main()
137 changes: 134 additions & 3 deletions src/cjdk/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,78 @@
import os
from contextlib import contextmanager

from . import _conf, _install, _jdk
from . import _cache, _conf, _index, _install, _jdk

__all__ = [
"cache_file",
"cache_jdk",
"cache_package",
"java_env",
"java_home",
"cache_file",
"cache_package",
"list_jdks",
"list_vendors",
]


def list_vendors(**kwargs):
"""
Return the list of available JDK vendors.

Parameters
----------
None

Other Parameters
----------------
index_url : str, optional
Alternative URL for the JDK index.

Returns
-------
list[str]
The available JDK vendors.
"""
return sorted(_get_vendors(**kwargs))


def list_jdks(*, vendor=None, version=None, cached_only=True, **kwargs):
"""
Return the list of JDKs matching the given criteria.

Parameters
----------
vendor : str, optional
JDK vendor name, such as "adoptium".
version : str, optional
JDK version expression, such as "17+".
cached_only : bool, optional
If True, list only already-cached JDKs.
If False, list all matching JDKs in the index.

Other Parameters
----------------
jdk : str, optional
JDK vendor and version, such as "adoptium:17+". Cannot be specified
together with `vendor` or `version`.
cache_dir : pathlib.Path or str, optional
Override the root cache directory.
index_url : str, optional
Alternative URL for the JDK index.
os : str, optional
Operating system for the JDK (default: current operating system).
arch : str, optional
CPU architecture for the JDK (default: current architecture).

Returns
-------
list[str]
JDKs (vendor:version) matching the criteria.
"""
return _get_jdks(
vendor=vendor, version=version, cached_only=cached_only, **kwargs
)


def cache_jdk(*, vendor=None, version=None, **kwargs):
"""
Download and extract the given JDK if it is not already cached.
Expand Down Expand Up @@ -196,6 +257,76 @@ def cache_package(name, url, **kwargs):
)


def _get_vendors(**kwargs):
conf = _conf.configure(**kwargs)
index = _index.jdk_index(conf)
return {
vendor.replace("jdk@", "")
for osys in index
for arch in index[osys]
for vendor in index[osys][arch]
}


def _get_jdks(*, vendor=None, version=None, cached_only=True, **kwargs):
conf = _conf.configure(
vendor=vendor,
version=version,
fallback_to_default_vendor=False,
**kwargs,
)
if conf.vendor is None:
# Search across all vendors.
kwargs.pop("jdk", None) # It was already parsed.
return [
jdk
for v in sorted(_get_vendors())
for jdk in _get_jdks(
vendor=v,
version=conf.version,
cached_only=cached_only,
**kwargs,
)
]
index = _index.jdk_index(conf)
jdks = _index.available_jdks(index, conf)
versions = _index._get_versions(jdks, conf)
matched = _index._match_versions(conf.vendor, versions, conf.version)

if cached_only:
# Filter matches by existing key directories.
def is_cached(v):
url = _index.jdk_url(index, conf, v)
key = (_jdk._JDK_KEY_PREFIX, _cache._key_for_url(url))
keydir = _cache._key_directory(conf.cache_dir, key)
return keydir.exists()

matched = {k: v for k, v in matched.items() if is_cached(v)}

class VersionElement:
def __init__(self, value):
self.value = value
self.is_int = isinstance(value, int)

def __eq__(self, other):
if self.is_int and other.is_int:
return self.value == other.value
return str(self.value) == str(other.value)

def __lt__(self, other):
if self.is_int and other.is_int:
return self.value < other.value
return str(self.value) < str(other.value)

def version_key(version_tuple):
return tuple(VersionElement(elem) for elem in version_tuple[0])

return [
f"{conf.vendor}:{v}"
for k, v in sorted(matched.items(), key=version_key)
]


def _make_hash_checker(hashes):
checks = [
(hashes.pop("sha1", None), hashlib.sha1),
Expand Down
8 changes: 4 additions & 4 deletions src/cjdk/_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def atomic_file(
ttl,
timeout_for_fetch_elsewhere=10,
timeout_for_read_elsewhere=2.5,
):
) -> Path:
"""
Retrieve cached file for key, fetching with fetchfunc if necessary.

Expand Down Expand Up @@ -150,7 +150,7 @@ def permanent_directory(
return keydir


def _file_exists_and_is_fresh(file, ttl):
def _file_exists_and_is_fresh(file, ttl) -> bool:
if not file.is_file():
return False
now = time.time()
Expand Down Expand Up @@ -185,11 +185,11 @@ def _create_key_tmpdir(cache_dir, key):
shutil.rmtree(tmpdir)


def _key_directory(cache_dir, key):
def _key_directory(cache_dir: Path, key) -> Path:
return cache_dir / "v0" / Path(*key)


def _key_tmpdir(cache_dir, key):
def _key_tmpdir(cache_dir: Path, key) -> Path:
return cache_dir / "v0" / Path("fetching", *key)


Expand Down
Loading
Loading