Skip to content

Conversation

@keys-i
Copy link

@keys-i keys-i commented Dec 24, 2025

This PR refactors sdkman_cli_native into a more modular, Rust-idiomatic layout while keeping CLI behaviour stable. The main change is structural: src/main.rs becomes the single entrypoint for parsing and dispatch, each subcommand lives in src/commands/, and shared helpers move into src/utils/ with small unit tests next to the utilities they validate. This reduces coupling, improves maintainability, and makes it easier to add new commands without touching unrelated areas.

  • Single entrypoint in src/main.rs for CLI parsing and dispatch only
  • Command implementations moved into src/commands/ as focused modules
  • Shared helpers consolidated in src/utils/ with small unit tests
  • Reduced coupling between command logic and filesystem/parsing concerns
  • More idiomatic usage of Path/PathBuf and consistent error propagation

Motivation

As the CLI grows and more edge cases are covered, the current structure makes it easy for command logic, parsing, and filesystem handling to become intertwined. That increases duplication, makes platform-specific issues harder to fix once, and pushes tests toward end-to-end execution even when validating a small parsing rule. This refactor tightens module boundaries so each layer has a clear job and can be tested in isolation, especially around file and path behaviour on different platforms.

  • Adding a command should not require changes scattered across the codebase
  • Shared logic should be implemented once and reused consistently
  • Unit tests should validate parsing and path behaviour without full CLI runs
  • Platform quirks should be handled centrally rather than per-command

New project structure

Path Role Contains
src/main.rs Single entrypoint CLI parsing, context setup, dispatch
src/commands/ Command layer one module per command, plus routing
src/utils/ Shared utilities fs helpers, parsing, paths, errors, unit tests

Design overview

src/main.rs becomes the only place where the application starts and the only place where CLI arguments are interpreted at a high level. It builds any minimal runtime context required and then hands off to a dispatcher in src/commands. The dispatcher selects the appropriate command module and runs it. Commands contain subcommand behaviour but do not own low-level file reading, parsing, or path normalization; those details live in src/utils.

Concern main.rs commands/ utils/
Parse CLI args Yes No No
Dispatch to command Yes Yes No
Command behaviour No Yes No
FS read/write helpers No No Yes
Parsing and validation No No Yes
Path normalization/display No No Yes
Unit tests for helpers No No Yes

Commands module

Commands are organized as small modules under src/commands/, typically one file per subcommand. This improves discoverability and keeps each command’s logic focused. Shared behaviour is pulled into src/utils/ so commands remain thin and consistent.

  • src/commands/mod.rs defines dispatch and wiring
  • src/commands/<command>.rs implements the command logic
  • Commands call utils for shared filesystem/parsing/path behaviour
Improvement Outcome
One module per command easier review and maintenance
Shared helpers in utils fewer inconsistencies and duplicated logic
Consistent interfaces simpler dispatch and error propagation

Utils module and tests

src/utils/ becomes the home for shared helpers used across commands, such as reading version/config files, parsing their contents, and handling paths consistently. Unit tests live alongside these utilities to validate tricky behaviour close to the implementation.

  • Filesystem helpers: safe reads, existence checks, predictable error context
  • Parsing helpers: trimming/validation rules for version-like inputs
  • Path helpers: cross-platform normalization and display helpers
  • Error helpers: consistent context so failures stay actionable and testable
Area Example cases Where tested
Version parsing missing, empty, whitespace-only, invalid content src/utils unit tests
Path behaviour Windows temp paths, invalid paths surfaced clearly src/utils unit tests
Error stability message includes operation, path, and reason src/utils unit tests
End-to-end behaviour CLI output and exit code existing integration tests

Rust-idiomatic improvements

This refactor nudges the codebase toward idiomatic Rust by making paths first-class and keeping error handling consistent. Utilities accept &Path and return structured results with contextual errors, and commands orchestrate intent rather than re-implementing mechanics.

Area Change Benefit
Paths Path/PathBuf over string paths fewer platform bugs
Errors consistent propagation with context clearer debugging and stable tests
Boundaries commands avoid low-level details reduced coupling, easier refactors
Helpers smaller, composable functions better reuse and unit testing

Behaviour and compatibility

This PR is intended as a structural refactor rather than a feature change. The command set, flags, output shape, and exit codes should remain the same. Any differences should be limited to clearer, more consistent error context for file and path failures, without changing the meaning of failures.

  • No intended CLI behaviour changes
  • Existing tests should continue to pass, with added unit coverage in utils
  • Centralized handling should reduce platform-specific regressions

Reviewer notes

This change is easiest to review in chunks: directory restructure and dispatch wiring, command migrations, utils consolidation, then the new unit tests. The key thing to verify is that main.rs is only wiring, commands live in src/commands, and shared logic lives in src/utils with tests close to the helpers.

Review focus What to check
Entry point main.rs does parsing and dispatch only
Commands each subcommand isolated in src/commands
Utilities shared helpers centralized in src/utils
Tests unit tests cover edge cases without full CLI execution
Behaviour outputs and exit codes remain stable

making code more modular and rust idomatic code by distributing in subpackages

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
it has its own test cases to verify behaviour and a rust header doc

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…nd directory utils

it has its own test cases to verify behaviour and a rust header doc

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…r to existing commands

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…and more modular

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…bpackage which cleanly implements each command

yet to add tests but added snapshot files to test it effectively

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…issues

SDKMAN_DIR_ENV_VAR renamed to SDKMAN_DIR and linking can be changed

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…all back to the cli

need to add test cases later and made code more efficient

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
… fall back to the cli

need to add test cases later and made code more efficient

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
… fall back to the cli

need to add test cases later and made code more efficient

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…I endpoints

- updated snapshots and fixed default behaviour for clap command which disrupts tests

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
… format and other checks to ensure best practices

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
… well as fixing help tests to work for github actions

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
…windows machines as well

Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
Signed-off-by: Keys <70819367+keys-i@users.noreply.github.com>
@keys-i
Copy link
Author

keys-i commented Dec 24, 2025

I also wanted to float one bigger-picture thought after working through this refactor. Would it be worth considering a longer-term direction where SDKMAN leans toward a single, first-class native binary as the main interface, rather than relying on multiple shell wrappers for different behaviours?

  • A single binary could make behaviour more consistent across shells and platforms
  • It could reduce wrapper drift (where shell scripts and the native CLI gradually diverge)
  • Versioning, error messages, and telemetry/debug output would be easier to standardize
  • Installation and upgrades could become simpler if the binary is the canonical entrypoint
  • It might also make it easier to extend functionality without having to mirror changes across scripts

I understand the wrappers have real value today (compatibility, bootstrapping, user expectations), so I am not suggesting removing them abruptly. More that this PR nudged the codebase toward a structure that would support that direction cleanly if the project ever wanted it.

@marc0der
Copy link
Member

marc0der commented Dec 31, 2025

Hi @keys-i, thanks for putting all that effort into this refactor. I can see the quality and it's appreciated.

That said, I can't merge this. Before starting work like this, please jump on our Discord and chat with us first, as our contributing guidelines clearly state. This is a massive architectural change, and a quick conversation would have shown that it's not aligned with where we're heading.

The reason is simple: we're doing a gradual, command-by-command rollout (check Discussion #18 if you want the details). We're in the process of strangling the old bash commands, where each new command supersedes the old. At the same time, our users are blissfully unaware that they are being migrated to Rust 🎉 Your structure assumes a different approach and architecture than what we're building toward.

If you see specific pain points in the current approach, let's talk about those in Discord and figure out how to fix them incrementally. We'd love your help with that.

Cheers!

@keys-i
Copy link
Author

keys-i commented Jan 3, 2026

Hi @keys-i, thanks for putting all that effort into this refactor. I can see the quality and it's appreciated.

That said, I can't merge this. Before starting work like this, please jump on our Discord and chat with us first, as our contributing guidelines clearly state. This is a massive architectural change, and a quick conversation would have shown that it's not aligned with where we're heading.

The reason is simple: we're doing a gradual, command-by-command rollout (check Discussion #18 if you want the details). We're in the process of strangling the old bash commands, where each new command supersedes the old. At the same time, our users are blissfully unaware that they are being migrated to Rust 🎉 Your structure assumes a different approach and architecture than what we're building toward.

If you see specific pain points in the current approach, let's talk about those in Discord and figure out how to fix them incrementally. We'd love your help with that.

Cheers!

Totally fair, and thanks for the thoughtful feedback.

Sorry I didn’t sync on Discord first. I missed the expectation in the contributing guidelines and I get why that matters, especially for architecture changes. I also just read through Discussion #18 and I understand the command by command strangler rollout you’re doing, with users staying unaware of the migration.

I’ll stop pushing this refactor direction and I’m happy to close the PR (or leave it open as a reference if you prefer). If you can point me to the right Discord channel (or an invite link), I’ll jump in and align before doing any more work.

If you’d like, I can help in a way that fits the rollout approach, for example:

  • pick one command and migrate it end to end in the current structure
  • improve test coverage or harnesses around the gradual replacement path
  • tackle specific pain points you’re seeing in the existing layout without reshaping the repo

If there are a couple of high priority commands or issues that would be most useful next, tell me what to grab and I’ll work incrementally from there.

Cheers,
@keys-i

@marc0der
Copy link
Member

marc0der commented Jan 5, 2026

Hey @keys-i, thanks so much for understanding.

Let's close this PR for now and continue the discussion on Discord:
https://discord.gg/rjxGCkuB

We could open a new discussion item in the Discussions channel to identify where we can improve what we currently have. My first thoughts go to improving the integration test harness (and associated helpers). Also, we currently don't support CYGWIN on Windows, which would be a nice addition. I'd say these as the best places to begin.

@marc0der
Copy link
Member

marc0der commented Jan 6, 2026

I'm closing this PR for now, but we still have it here on GH for reference.

@marc0der marc0der closed this Jan 6, 2026
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