Repository navigation
Description
RFC: Complete Bash-to-Rust Migration with Eval Protocol
Summary
This RFC proposes a three-phase plan to migrate all remaining Bash commands in sdkman-cli to Rust native binaries. The key enabler is an eval protocol that allows Rust binaries to emit shell environment mutations (PATH, JAVA_HOME, etc.) that the Bash wrapper evaluates on their behalf.
Background
SDKMAN's CLI is currently split between:
- sdkman-cli (Bash): The
sdk()shell function and ~23 Bash scripts - sdkman-cli-native (Rust): 6 native binaries (current, default, help, home, uninstall, version)
The migration has been stalled since 2024 because the remaining commands (use, install, list, update, upgrade, env) need to modify the calling shell's environment variables. A subprocess cannot directly modify its parent's environment, which is the fundamental blocker.
As discussed in #189, the solution is to have Rust binaries output eval-able shell code that the wrapper script executes.
Proposal
Eval Protocol Design
Rust binaries emit two types of output on stdout:
- Display lines (no prefix) — human-readable output, printed to terminal
- Eval lines (prefixed with
SDKMAN_EVAL:) — shell commands to be eval'd by the wrapper
Example for sdk use java 17.0.0-tem:
Using java version 17.0.0-tem in this shell.
SDKMAN_EVAL:export JAVA_HOME=/home/user/.sdkman/candidates/java/17.0.0-tem
SDKMAN_EVAL:export PATH=/home/user/.sdkman/candidates/java/17.0.0-tem/bin:/usr/bin:...
The Bash wrapper (sdkman-main.sh) would be updated to:
output=$("$native_command" "${@:2}" 2>&1)
display=$(echo "$output" | grep -v "^SDKMAN_EVAL:")
eval_lines=$(echo "$output" | grep "^SDKMAN_EVAL:" | sed 's/^SDKMAN_EVAL://')
echo "$display"
eval "$eval_lines"Feature Detection
Each native binary gets a --supports-eval-protocol flag so the shell wrapper can detect support. This ensures backward compatibility with older binaries.
Three Phases
Phase 1: Pure Filesystem Commands (no protocol needed)
| Command | Complexity | Operations |
|---|---|---|
flush |
Low | Delete tmp/metadata/version dirs, report freed space |
config |
Very Low | Open $SDKMAN_DIR/etc/config in $EDITOR |
cache |
Very Low | Validate candidates cache file |
These follow the exact same pattern as existing commands — no architectural changes required.
Phase 2: Eval Protocol + use Command
This is the critical phase that proves the protocol works:
- Add
--supports-eval-protocolflag to all binaries - Implement
usecommand with eval protocol output - Update
sdkman-main.shrouting to parse eval lines - Cross-shell testing (bash, zsh)
Phase 3: Network + Install Commands
With the protocol proven, migrate the remaining commands:
| Command | Key Dependencies | Challenge Level |
|---|---|---|
list |
reqwest (HTTP) | Medium — remote API + local scan |
update |
reqwest (HTTP) | Medium — fetch + diff cache |
install |
reqwest, zip/tar, sha2 | High — download, verify, extract, prompt |
upgrade |
(delegates to install) | Medium — iterate + batch |
env |
(delegates to use/install) | High — .sdkmanrc parsing, 4 subcommands |
New Rust Dependencies
| Crate | Purpose | Phase |
|---|---|---|
reqwest + rustls-tls |
HTTP client (no OpenSSL) | Phase 3 |
zip, tar, flate2 |
Archive extraction | Phase 3 |
sha2, md-5 |
Checksum verification | Phase 3 |
dialoguer |
Interactive prompts | Phase 3 |
Coordinated Release
The eval protocol requires a coordinated release of both sdkman-cli (updated shell wrapper) and sdkman-cli-native (new binaries). The existing sdkman_native_enable config flag provides a safe rollback mechanism.
What Remains as Bash
These components cannot be migrated to Rust and will permanently remain as shell scripts:
sdkman-init.sh— must be sourced by.bashrc/.zshrcto bootstrap the shell environmentsdkman-main.sh— thesdk()function dispatcher (will become thinner over time)
Alternatives Considered
- fd 3 (separate file descriptor): Cleaner separation but fragile to set up across bash/zsh/platforms.
- JSON output: Requires
jqdependency or slow pure-Bash JSON parsing. - Shim/proxy binary approach (like aqua): Would require a complete architectural redesign.
- GraalVM + Picocli (as suggested in Will there be a native version of
sdk? #189): Would add JVM build complexity. Rust is already established in this project.
Open Questions
- Should the eval prefix be
SDKMAN_EVAL:or something shorter likeEVAL:? - Should the shell wrapper detect eval protocol support per-binary, or assume all new binaries support it?
- How should the Rust
installcommand handle post-install hooks (download and source via eval, or interpret in Rust)? - Should we version the eval protocol in case it evolves?
Related
- Will there be a native version of
sdk? #189 — Original discussion about fully native SDKMAN - sdkman/sdkman-cli README — states Rust migration is the official direction
Activity
marc0der commentedon May 13, 2026
@youngledo the assumption is incorrect. The reason I stalled development of the Rust port until now was due to (a) the chatty nature of the existing bash CLI, and (b) fundamental problems with the underlying data model regarding vendors. I've spent a lot of time rewriting the entire backend to prepare the ground, which will allow the new Rust CLI to:
aptorgitdoes.The work is almost drawing to a close now, with only testing of the new backend and a final data migration pending.
It does, however, mean that we will pick up the Rust migration work again pretty soon, and I'll certainly use methods like the ones you propose above.
youngledo commentedon May 13, 2026
Thanks for the context @marc0der! Great to hear the backend rewrite is nearing completion. The offline-by-default model and first-class vendor support sound like exactly the right foundation for the Rust CLI. Looking forward to seeing the new backend land.