Rakko

Add a Command

Add a custom command for tasks such as cleanup, setup, or a development server.

Add a custom command for work that does not fit an action. An action runs once and reports an outcome. A development server runs until you stop it, while a cleanup task removes files rather than reporting findings. A command lets you write that behavior and expose it beside your actions.

This guide adds clean, a command that removes the build output of the project. You need a working harness. If you do not have one yet, follow Get Started first.

If you want to run several actions together, form a group. Rakko then runs the members and reports their results for you.

Add the Contract Crate

The command uses the types of the contract crate, rakko. Add it to the dependencies of the harness:

[dependencies]
rakko = "0.1"
rakko-cli = "0.1"
rakko-format-rust = "0.1"
rakko-lint-rust = "0.1"
rakko-test-rust = "0.1"

rakko-cli also exports Clawless, the command-line framework. Your harness does not need a separate Clawless dependency.

Write the Command

Put the command in its own module so that main.rs stays a short list of mounts. Create tools/rakko/src/clean.rs with these imports:

use rakko::{Context, Documented, Name, action_name};
use rakko_cli::Command;
use rakko_cli::clawless::CommandResult;
use rakko_cli::clawless::context::Context as ClawlessContext;

A command is a type that implements the trait Command of rakko-cli. Add the command below the imports:

/// Removes the build output of the project
///
/// Cargo writes its build output into the directory `target` in the root of
/// the project. The command removes that directory, and it says so when there
/// is nothing to remove.
#[derive(Debug, Documented)]
pub(crate) struct Clean;

impl Command for Clean {
    type Args = ();

    fn name(&self) -> Name {
        action_name!("clean")
    }

    async fn run(
        &self,
        project: &Context,
        clawless: &ClawlessContext,
        _args: &Self::Args,
    ) -> CommandResult {
        let target = project.root().get().join("target");

        if !target.try_exists()? {
            clawless.output().message("nothing to remove").await?;
            return Ok(());
        }

        std::fs::remove_dir_all(&target)?;
        clawless.output().message("removed target").await?;

        Ok(())
    }
}

The code has these parts:

  • The Documented derive uses the first paragraph of the doc comment as help text. Write that paragraph as one sentence.
  • The name is what users type after mise run rakko --. It starts with a lowercase ASCII letter and contains lowercase ASCII letters, digits, and single hyphens. action_name! rejects invalid names at compile time.
  • Args is the unit type because clean takes no arguments. Commands declare arguments in the same way as actions. Build an Action shows how those declarations become flags.
  • run is asynchronous code that receives the project context and the Clawless context. Use the Clawless context to write output.

run returns success or an error. Success produces exit code 0. An error produces exit code 2 and a report of the error and its causes. The ? operator stops the command when a step fails, such as removing the directory.

Mount the Command

Declare the module in tools/rakko/src/main.rs, and give the command to mount:

mod clean;

use clean::Clean;

/// Builds the command line of the project and runs it
fn main() -> std::process::ExitCode {
    rakko_cli::builder()
        .mount(rakko_format_rust::FormatRust)
        .mount(rakko_lint_rust::LintRust)
        .mount(rakko_test_rust::TestRust)
        .mount(Clean)
        .run()
}

Actions, groups, and commands share one namespace. If two mounted entries have the same name, the harness stops at startup and reports the duplicate. The names doctor and help are reserved.

Run the Command

Run the command like an action:

mise run rakko -- clean

The help of the harness lists the command beside the actions, with the sentence of its doc comment:

mise run rakko -- --help

Custom commands use the same environment checks as actions. If the tool pins do not match the mounted actions, the command stops and asks you to run mise run rakko -- doctor --fix.

Run Actions From a Command

A command can run actions, for example to repeat a check when files change. Pass the required actions from the harness to the command. Commands cannot look up mounted actions themselves.

Run each action through ErasedAction::run in rakko. This path also skips an action when its tool has no build for the current platform.

doctor reads tool declarations from mounted actions and group members. If only a custom command holds an action, doctor cannot see it. Mount that action as well so that doctor --fix includes its tools.