Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ install an application JAR.

## Managing the cache

```{eval-rst}
.. autofunction:: cjdk.prune_jdks
.. versionadded:: 0.6.0
```

```{eval-rst}
.. autofunction:: cjdk.cache_directory
.. versionadded:: 0.6.0
```

```{eval-rst}
.. autofunction:: cjdk.clear_cache
.. versionadded:: 0.5.0
Expand Down
11 changes: 11 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,21 @@ See also the section on [versioning](versioning-scheme).

## [Unreleased]

### Added

- Add `cjdk prune` and `prune_jdks()` to remove obsolete cached JDKs, keeping
the newest of each vendor and major version (configurable with
`--per-vendor`/`--across-vendors` and `--per-major`/`--across-majors`). Pass
`--keep-none` (with `-j` for precision) to remove specific cached JDKs
outright.
- Add `cache_directory()` to the Python API.

### Changed

- `list_vendors()` and `ls-vendors` now filter vendors by OS and architecture,
defaulting to the current platform.
- `cjdk clear-cache` now prompts for confirmation before deleting; pass `--yes`
to skip the prompt, or `--dry-run` to preview. `prune` prompts the same way.

### Removed

Expand Down
27 changes: 27 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ shown was on macOS.)

## Managing the cache

### `prune`

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

For example, if both `zulu:26.0.0` and `zulu:26.0.1` are cached, the older one
is removed while the newest of each vendor and major version is kept:

```text
$ cjdk prune
```

To remove specific cached JDKs instead of keeping the newest of each, add
`--keep-none` and narrow the selection with `-j`. For example, to remove all
cached Zulu 26 JDKs:

```text
$ cjdk -j zulu:26 prune --keep-none
```

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

(cli-clear-cache)=

### `clear-cache`
Expand All @@ -127,4 +151,7 @@ shown was on macOS.)

```{eval-rst}
.. versionadded:: 0.5.0
.. versionchanged:: 0.6.0
Now prompts for confirmation before deleting. Pass ``--yes`` to skip the
prompt, or ``--dry-run`` to preview.
```
4 changes: 4 additions & 0 deletions src/cjdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT

from ._api import (
cache_directory,
cache_file,
cache_jdk,
cache_package,
Expand All @@ -11,6 +12,7 @@
java_home,
list_jdks,
list_vendors,
prune_jdks,
)
from ._exceptions import (
CjdkError,
Expand All @@ -21,6 +23,7 @@
from ._version import __version__ as __version__

__all__ = [
"cache_directory",
"cache_file",
"cache_jdk",
"cache_package",
Expand All @@ -33,4 +36,5 @@
"JdkNotFoundError",
"list_jdks",
"list_vendors",
"prune_jdks",
]
129 changes: 127 additions & 2 deletions src/cjdk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,24 +285,148 @@ def cache_package(
)


@click.command(short_help="Prune obsolete cached JDKs.")
@click.pass_context
@click.option(
"--keep-none",
is_flag=True,
help="Remove every matching JDK, keeping none. Combine with --jdk/-j to "
"remove specific JDKs, e.g. 'cjdk -j zulu:26 prune --keep-none'.",
)
@click.option(
"--per-vendor/--across-vendors",
default=True,
help="Prune each vendor separately (default), or pool all vendors "
"together so only the newest version survives.",
)
@click.option(
"--per-major/--across-majors",
default=True,
help="Keep the newest of each major version (default), or only the single "
"newest version overall.",
)
@click.option(
"--dry-run",
"-n",
is_flag=True,
help="Show what would be removed, without removing anything.",
)
@click.option(
"--yes",
"-y",
is_flag=True,
help="Do not prompt for confirmation before removing.",
)
def prune(
ctx: click.Context,
keep_none: bool,
per_vendor: bool,
per_major: bool,
dry_run: bool,
yes: bool,
) -> None:
"""
Remove obsolete cached JDKs, keeping only the newest of each.

By default, the newest cached version of each vendor and major version is
kept, and older versions are removed. For example, if both Zulu 26.0.0 and
26.0.1 are cached, 26.0.0 is removed.

Use --across-vendors and/or --across-majors to widen the grouping (removing
more), and the common --jdk/-j option to limit pruning to a particular
vendor or version. Only cached JDKs are removed; cached files, packages,
and the index are left untouched.

Pass --keep-none to remove every matching JDK instead of keeping the newest
of each. Combined with -j this is a precise way to remove specific JDKs
(e.g. 'cjdk -j zulu:26 prune --keep-none'); with no -j it removes all cached
JDKs (but, unlike 'clear-cache', leaves cached files and packages alone).

When pruning JDKs, ensure that no other processes are using cjdk or the
affected JDKs.

See 'cjdk --help' for the common options.
"""
if keep_none and not (per_vendor and per_major):
raise click.UsageError(
"--keep-none cannot be combined with --across-vendors or "
"--across-majors (there is nothing to group when keeping none)."
)
matched = _api.prune_jdks(
**ctx.obj,
per_vendor=per_vendor,
per_major=per_major,
keep_none=keep_none,
dry_run=True,
)
if not matched:
click.echo("Nothing to prune.")
return
_echo_jdk_list("JDKs to remove:", matched)
if dry_run:
return
if not yes:
click.confirm(f"Remove {len(matched)} JDK(s)?", abort=True)
removed = _api.prune_jdks(
**ctx.obj,
per_vendor=per_vendor,
per_major=per_major,
keep_none=keep_none,
)
click.echo(f"Removed {len(removed)} JDK(s).")


@click.command(short_help="Remove all cached files.")
@click.pass_context
def clear_cache(ctx: click.Context) -> None:
@click.option(
"--dry-run",
"-n",
is_flag=True,
help="Show what would be removed, without removing anything.",
)
@click.option(
"--yes",
"-y",
is_flag=True,
help="Do not prompt for confirmation before removing.",
)
def clear_cache(ctx: click.Context, dry_run: bool, yes: bool) -> None:
"""
Remove all cached JDKs, files, and packages from the cache directory.

This permanently deletes everything in the cache. Subsequent commands will
re-download any needed files.
re-download any needed files. To remove obsolete or specific JDKs while
leaving other cached items alone, use 'prune'.

Unless --yes is given, you are prompted to confirm before anything is
deleted.

When clearing the cache, ensure that no other processes are using cjdk or
the JDKs, files, or packages installed by cjdk.

See 'cjdk --help' for the common options (only --cache-dir is relevant).
"""
cache_dir = _api.cache_directory(**ctx.obj)
if not cache_dir.exists():
click.echo(f"Cache directory does not exist: {cache_dir}")
return
if dry_run:
click.echo(f"Would remove entire cache directory: {cache_dir}")
return
if not yes:
click.echo("This will permanently remove the entire cache directory:")
click.echo(f" {cache_dir}")
click.confirm("Proceed?", abort=True)
cleared = _api.clear_cache(**ctx.obj)
click.echo(f"Cleared cache: {cleared}")


def _echo_jdk_list(header: str, jdks: list[str]) -> None:
click.echo(header)
for jdk in jdks:
click.echo(f" {jdk}")


# Register current commands.
_cli.add_command(java_home)
_cli.add_command(exec)
Expand All @@ -311,6 +435,7 @@ def clear_cache(ctx: click.Context) -> None:
_cli.add_command(cache)
_cli.add_command(cache_file)
_cli.add_command(cache_package)
_cli.add_command(prune)
_cli.add_command(clear_cache)

# Register hidden/deprecated commands, for backwards compatibility.
Expand Down
Loading
Loading