Skip to content

Add prune command for removing cached JDKs#46

Open
ctrueden wants to merge 2 commits into
cachedjdk:mainfrom
ctrueden:rm-and-prune
Open

Add prune command for removing cached JDKs#46
ctrueden wants to merge 2 commits into
cachedjdk:mainfrom
ctrueden:rm-and-prune

Conversation

@ctrueden

Copy link
Copy Markdown
Contributor

The clear-cache command clears everything. But I often find myself wanting to remove "stale" versions of JDKs without starting from scratch. This commit adds two new commands for doing so:

  • cjdk rm - to target specifically named cached JDKs
  • cjdk prune - to remove older JDKs when newer ones are also cached

Because both commands delete local files, both are gated behind confirmation prompts unless -y/--yes is given, and the existing cjdk clear-cache is also now gated as such to avoid someone naively deleting all their cached JDKs (which happened to me this morning 😅).

I vibe-coded it with Claude, and also pushed it all as one commit. Apologies if that makes review more burdensome—although I have read through the entire patch and it looks clean and pretty minimal to me. The change of def clear_cache(ctx: click.Context) -> None adding dry_run: bool, yes: bool parameters without default values is, however, a breaking change.

The clear-cache command clears everything. But I often find myself
wanting to remove "stale" versions of JDKs without starting from
scratch. This commit adds two new commands for doing so:

* cjdk rm - to target specifically named cached JDKs
* cjdk prune - to remove older JDKs when newer ones are also cached

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@marktsuchida

Copy link
Copy Markdown
Collaborator

I was thinking of a prune myself, so 👍, but what is the motivation for rm? That feels out of place for cache semantics and more like a package manager (you can uv remove a package but you can't uv cache rm a specific package, for example). Can you convince me it's not YAGNI?

@ctrueden

ctrueden commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Well... I sort of am using cjdk like a package manager now, in the sense that whenever possible, I try to install and use OpenJDKs managed by cjdk. I have built several shell commands around it, as shown here:

So I jlist to see what I have, jswitch to change versions—j21, j17, etc. most frequently, but not always; e.g. I might need to target a more precise flavor to test specific things.

It all works great, but over time, I end up with "too many versions" installed, and I want to remove some redundant ones, but what is "redundant" is a judgment call—it doesn't always align with the prune behavior, depending on what I'm testing. Having rm is a precision tool for full control; without it, I'll end up doing it manually, which is rather tedious.

But if this sort of usage isn't aligned with your vision for cjdk, I can just write another tool like jrm or whatnot that does the delete outside of cjdk.

@marktsuchida

Copy link
Copy Markdown
Collaborator

Got it. Maybe it can be a flag to prune? That is, a flag that modifies the behavior of prune so that it no longer keeps the latest-among-matching? Basically I don't want to prevent this kind of usage but I don't want to give the impression that it is at all a routine thing to do (for most users). It feels okay if it's an "advanced prune".

Name it prune --keep-none or prune --clear, perhaps (better suggestions welcome). Without -j, it makes prune equivalent to clear-cache except that it leaves alone non-jdk items.

Alternatively, we could teach clear-cache to honor --jdk. But there would be a mismatch regarding what it does for non-jdk items.

@ctrueden ctrueden changed the title Add new commands for removing cached JDKs Add prune command for removing cached JDKs Jul 2, 2026
@ctrueden

ctrueden commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

I pushed another commit implementing your suggestion: now there is only --prune, but it has a --keep-none flag so that people can effectively do targeted rm should the need arise.

I discussed with Claude whether it might be worth generalizing the --keep-none flag to something like --keep 1/--keep 0/--keep 2, or --strategy keep-none/--strategy delete-oldest/etc., for better future-proofing.

We opted not to do so for now. Here is Claude's opinion on why not:

On generalizing --keep-none

My recommendation: ship --keep-none as-is. If you ever generalize, go --keep N, not --strategy. But don't do either now. Here's the reasoning.

The current design already has a clean two-axis model, and it's worth naming explicitly because it's what should drive the decision:

  • Grouping — --per-vendor/--per-major define the buckets.
  • Survivors per bucket — currently "the newest 1," or with --keep-none, "zero."

--keep N is the honest generalization of the survivors axis: --keep 1 (default), --keep 0 (= keep-none), --keep 2 (keep two newest per bucket). It layers orthogonally on the grouping axis and stays coherent even crossed with --across-majors ("2 newest overall per vendor"). That's the right extension shape if you extend at all.

--strategy keep-none|delete-oldest|… is the one I'd actively avoid. It reads as future-proof but it flattens two genuinely orthogonal things (grouping × survivor-count) into a single enum, which trends toward a combinatorial menu of named strategies. It also imports a "policy engine" mental model that a JDK cache shouldn't carry. If you ever need something that truly doesn't fit the grouping/survivors model — time-based ("older than 90 days") or usage-based eviction — that's a different command and a fresh design conversation, not a prune --strategy value.

Why not do --keep N now, though:

  1. No lock-in, so no future-proofing cost to waiting. --keep-none doesn't paint you into a corner. If a concrete request for "keep the 2 newest" ever appears, you add --keep N and make --keep-none a documented alias for --keep 0 (or keep it as sugar). Flags are cheap to add; the current name blocks nothing. That removes the main argument for generalizing preemptively — you lose nothing by deferring.
  2. N>1 is genuinely YAGNI. Nobody has asked for "keep the 2 newest," and it's exactly the kind of speculative knob Mark was pushing back on. Adding it now invites the same YAGNI critique that started this thread.
  3. It cuts against the framing you're trying to preserve. Mark's worry is "don't make this look routine." A numeric curation dial (--keep 3) points the opposite direction — it invites fiddly hand-management of the cache. --keep-none is a single, blunt "advanced" override, which is exactly the impression you want.
  4. --keep-none is more legible than --keep 0. Scanning --help, jdk -j zulu:26 prune --keep-none says what it does; --keep 0 makes you decode it. Minor, but it reinforces (3).

Rather than a standalone rm command with package-manager semantics,
express "remove specific cached JDKs" as an advanced prune: --keep-none
removes every matching JDK instead of keeping the newest of each, and
combines with -j for precision. This keeps the cache semantics honest
and shrinks the command/API surface.

- Drop the rm command and the public remove_jdks(); fold both into
  prune / prune_jdks(keep_none=True).
- Error when --keep-none is combined with --across-(vendors|majors)
  (grouping is meaningless when keeping none).
- Update docs, changelog, and tests accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants