Skip to content

Clap 3 → Clap 4 Upgrade#3431

Merged
fibonacci1729 merged 4 commits intospinframework:mainfrom
fibonacci1729:clap4
Mar 25, 2026
Merged

Clap 3 → Clap 4 Upgrade#3431
fibonacci1729 merged 4 commits intospinframework:mainfrom
fibonacci1729:clap4

Conversation

@fibonacci1729
Copy link
Copy Markdown
Collaborator

Supersedes #3249

Summary

This PR upgrades the Spin CLI from clap 3.2 to clap 4.6.0, along with all associated API and behavioral changes required by the new major version.

Dependency Changes

Crate Old Version New Version
clap 3.2 4.6.0
clap_lex (not used) 0.7.5 (new)

Feature Flag Changes

  • spin-cli: ["deprecated", "derive", "env"]["derive", "env", "string", "wrap_help"]
    • Removed deprecated (clap 4 no longer has a deprecation compatibility layer)
    • Added string for String-based value handling
    • Added wrap_help for automatic terminal-width help wrapping
  • spin-trigger: Added wrap_help feature
  • spin-common: Added clap as a dependency (for shared CLAP_STYLES)

API Migration Changes

IntoAppCommandFactory

The IntoApp trait was renamed to CommandFactory in clap 4. All imports and usages updated.

takes_value = false Removed

In clap 4, boolean fields (bool) no longer need takes_value = false — this is the default behavior. Removed from all boolean flag definitions across:

  • UpCommand (insecure, direct_mounts, build)
  • PluginCommands (yes_to_all, override_compatibility_check, all, downgrade)
  • RegistryCommands (insecure, build, password_stdin)
  • TemplateCommands (verbose)
  • NewCommand (init, accept_defaults, no_vcs, allow_overwrite)
  • BuildCommand (skip_target_checks)
  • FactorsTriggerCommand (disable_cache, debug_info)

conflicts_with / requires Use Field Names

Clap 4 requires using the Rust field name (with underscores) instead of the kebab-case CLI name:

  • conflicts_with = "template-id"conflicts_with = "template_id"
  • requires = "tls-key"requires = "tls_key"
  • requires = "tls-cert"requires = "tls_cert"

Simplified long Attributes

Where the long name matches the field name's kebab-case form, explicit long = "..." has been simplified to just long:

  • long = "insecure"long (on fields named insecure)
  • long = "version"long, long = "downgrade"long, etc.
  • long = "init"long, long = "values-file"long, etc.

Command<'_> Lifetime Removed

Clap 4's Command no longer carries a lifetime parameter. All clap::Command<'_> references changed to clap::Command.

multiple_values(true)action(ArgAction::Append)

The multiple_values API was replaced with explicit ArgAction::Append for collecting multiple values.

.about(s.as_str()).about(&s)

Minor API change in how string references are passed to about().

Architectural Changes

SpinApp Moved to src/lib.rs

The SpinApp enum, TriggerCommands, and all supporting code (run(), build_info(), PluginHelpEntry, plugin_help_entries()) moved from src/bin/spin.rs to src/lib.rs. The binary (src/bin/spin.rs) is now a thin wrapper that calls spin_cli::run().

This enables other code (e.g., external.rs, maintenance.rs) to access SpinApp::command() via crate::SpinApp without needing the command to be passed as a parameter.

cmd Parameter Removed from Subcommands

Several subcommand run methods no longer take a clap::Command parameter:

  • DeployCommand::run(self, cmd)DeployCommand::run(self)
  • LoginCommand::run(self, cmd)LoginCommand::run(self)
  • MaintenanceCommands::run(&self, app)MaintenanceCommands::run(&self)
  • execute_external_subcommand(subcmd, cmd)execute_external_subcommand(args)

These now call crate::SpinApp::command() directly when needed.

UpCommand Refactored with Custom Parser

spin up has a unique requirement: it must accept both its own flags and arbitrary trigger-specific flags that it passes through. Clap 3's allow_hyphen_values = true handled this implicitly, but clap 4 requires explicit handling.

The solution:

  • UpCommand is now a newtype wrapper: pub struct UpCommand(UpCommandInner)
  • UpCommandInner (private) holds the actual #[derive(Parser)] fields
  • src/commands/up/parsing.rs (new module) implements custom Args, FromArgMatches, Parser, and CommandFactory for UpCommand
  • The custom parser uses clap_lex to split arguments: recognized flags go to UpCommandInner, everything else becomes trigger_args
  • trigger_args changed from #[clap(hide = true)] to #[clap(skip)] since it's populated by the custom parser
  • Added UpCommand::run_as_flag() for the spin build --up flow

Colored Help Output (CLAP_STYLES)

A new CLAP_STYLES constant in crates/common/src/cli.rs provides consistent colored terminal output:

  • Headers: Yellow
  • Usage: Green
  • Literals: Green
  • Placeholders: Green

Applied to SpinApp and FactorsTriggerCommand.

Help Heading Casing

Trigger option help headings changed from "TRIGGER OPTIONS" to "Trigger Options" to match clap 4's default styling conventions.

Files Changed

File Change Type
Cargo.toml Dependency versions and features
crates/common/Cargo.toml Added clap dependency
crates/common/src/cli.rs NewCLAP_STYLES constant
crates/common/src/lib.rs Added pub mod cli
crates/trigger/Cargo.toml Added wrap_help feature
crates/trigger/src/cli.rs API migration
crates/trigger-http/src/lib.rs requires field name fix
crates/expressions/src/template.rs Format string modernization
src/lib.rs Major — SpinApp + run() moved here
src/bin/spin.rs Major — simplified to thin wrapper
src/commands/up.rs Major — UpCommand/UpCommandInner refactor
src/commands/up/parsing.rs New — custom clap_lex parser
src/commands/build.rs API migration
src/commands/cloud.rs Removed cmd parameter
src/commands/external.rs Removed cmd parameter, uses SpinApp::command()
src/commands/maintenance.rs Removed cmd parameter, uses SpinApp::command()
src/commands/new.rs API migration
src/commands/plugins.rs API migration
src/commands/registry.rs API migration
src/commands/templates.rs API migration

CLI Behavior Changes

  • Help output is now colored with ANSI colors (yellow headers, green usage/literals)
  • Help text wraps to terminal width (via wrap_help feature)
  • No user-facing flag or option changes — all existing CLI flags and options remain the same
  • Trigger option headings use title case instead of all-caps

@itowlson
Copy link
Copy Markdown
Collaborator

UpCommand is now a newtype wrapper: pub struct UpCommand(UpCommandInner)
UpCommandInner (private) holds the actual #[derive(Parser)] fields
src/commands/up/parsing.rs (new module) implements custom Args, FromArgMatches, Parser, and CommandFactory for UpCommand

Ooh, this is clever. If this works then that's the lifeline we need!

@fibonacci1729 fibonacci1729 force-pushed the clap4 branch 3 times, most recently from a35c681 to ec065f5 Compare March 23, 2026 22:15
@itowlson itowlson mentioned this pull request Mar 24, 2026
Copy link
Copy Markdown
Collaborator

@itowlson itowlson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the way this gets around that accursed hyphen values problem. There are a couple of seemingly unrelated changes that I'm a bit sceptical about, and I would like to see the clever trick made a touch easier to grasp. But I'm stoked that we may finally be shot of Clap 3!

Incidentally it looks like this doesn't touch src/clap_markdown. It's possible we could use official clap_markdown - v3 vs v4 was the main reason for forking it. But I did do a bunch of tweaks post-forking and so it may be that the upstream crate won't work for us. Anyway something to be aware of.

@fibonacci1729
Copy link
Copy Markdown
Collaborator Author

fibonacci1729 commented Mar 24, 2026

I'm not sure why the lint workflow is failing. It's tripping over code that doesn't exist in this PR. Fixed by squashing. Not sure why that was happening.

Edit: Apparently not fixed.

Edit: Fixed via rebase

@fibonacci1729
Copy link
Copy Markdown
Collaborator Author

@itowlson in the last commit here, I removed the vendored clap-markdown code. The main difference AFAICT is that you modified the vendored code to sort the command output. Since upstream doesn't do this, I added a sorted_command function to reconstruct the command in sorted order (potentially something we can upstream). The output is byte-identical to what was produced previously.

@fibonacci1729 fibonacci1729 force-pushed the clap4 branch 3 times, most recently from cea70a5 to 3ac72ea Compare March 24, 2026 19:16
@itowlson
Copy link
Copy Markdown
Collaborator

I removed the vendored clap-markdown code. The main difference AFAICT is that you modified the vendored code to sort the command output. Since upstream doesn't do this, I added a sorted_command function to reconstruct the command in sorted order

Very nice - glad to see the back of that. Thank you!

@fibonacci1729 fibonacci1729 enabled auto-merge (squash) March 24, 2026 22:34
@fibonacci1729
Copy link
Copy Markdown
Collaborator Author

@itowlson Lastly, the last commit refactors the parsing code (its a bit more readable now IMO). Additionally added a test to ensure its correctly splitting up and trigger arguments. If that looks good, feel free to approve (I believe I addressed everything).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, weird. This didn't seem to cause errors in CI. But thanks for catching it!

@itowlson
Copy link
Copy Markdown
Collaborator

I think you might have merged rather than rebased and now there is duplicate stuff in the diff and the commit history looks weird?

image

@fibonacci1729
Copy link
Copy Markdown
Collaborator Author

Oops, yeah i goofed. Should be fixed now.

Copy link
Copy Markdown
Collaborator

@itowlson itowlson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. The parsing logic is much clearer now, and the test is reassuring! Brilliant work by you and Lann to get this done without changing the user experience!

Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
@fibonacci1729 fibonacci1729 merged commit fe9918a into spinframework:main Mar 25, 2026
17 checks passed
@fibonacci1729 fibonacci1729 deleted the clap4 branch March 25, 2026 01:49
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.

3 participants