Build and run

cargo make

cargo-make cargo-make-crates.io cargo-make-github cargo-make-lib.rs [cat-development-tools::testing][cat-development-tools::testing] cat-development-tools cat-command-line-utilities cat-development-tools::cargo-plugins cat-development-tools::build-utils

cargo make is a Rust task runner and build tool. The cargo-make task runner enables to define and configure sets of tasks and run them as a flow. A task is a command, script, rust code, or other sub tasks to execute. Tasks can have dependencies which are also tasks that will be executed before the task itself. With a simple toml based configuration file, you can define a multi platform build script that can run build, test, generate documentation, run bench tests, run security validations and more, executed by running a single command.

Install with

cargo install --force cargo-make
cargo make --version

automating-your-rust-workflows-with-cargo-make⮳.

cargo xtask

cargo-xtask cargo-xtask-crates.io cargo-xtask-github cargo-xtask-lib.rs

cargo-xtask⮳ adds free-form automation to a Rust project, a-la makenpm run or bespoke bash scripts.

The two distinguishing features of xtask are the following:

  • It doesn't require any other binaries besides cargo and rustc, it fully bootstraps from them.
  • Unlike bash, it can more easily be cross platform, as it doesn't use the shell.

Use devx

devx-cmd devx-cmd-crates.io devx-cmd-github devx-cmd-lib.rs devx-pre-commit devx-pre-commit-crates.io devx-pre-commit-github devx-pre-commit-lib.rs cat-development-tools

devx⮳ is a collection of utilities for writing your own dev scripts in Rust. The project is inspired by and intended for seamless usage with cargo-xtask⮳ idioms.

devx-cmd provides primitives for spawning child processes that are easier than std::process targeted when used in development scripts. devx-pre-commit creates git pre-commit hooks that enforce good practices.

Write cross-platform bash-like scripts in Rust with xshell

xshell xshell-crates.io xshell-github xshell-lib.rs cat-development-tools::build-utils cat-filesystem

"xshell is a swiss-army knife for writing cross-platform 'bash' scripts in Rust.

It doesn't use the shell directly, but rather re-implements parts of its scripting environment in Rust. The intended use-case is various bits of glue code, which could be written in bash or python. The original motivation is xtask development" (docs.rs).

The following example executes shell commands and interacts with the file system. It showcases:

  • Basic command execution,
  • Handling command arguments,
  • Working directory manipulation,
  • Using pipes,
  • Checking command status,
  • Conditional execution,
  • Capturing stderr,
  • Environment variable manipulation,
  • Path manipulations,
  • File removal.
// ANCHOR: example
//! This example demonstrates the usage of the `xshell` crate for shell
//! scripting in Rust.
//!
//! `xshell` provides a convenient way to execute shell commands, manipulate the
//! file system, and manage environment variables within a Rust program.

use xshell::Shell;
use xshell::cmd;

fn main() -> anyhow::Result<()> {
    // Create a new Shell instance.
    //
    // This provides the environment for running commands. It maintains a
    // logical working directory and an environment map. They are
    // independent from the current process's `std::env::current_dir` and
    // `std::env::var`, and only affect paths and commands passed to the
    // `Shell`.
    let sh = Shell::new()?;

    // Basic command execution:
    // The `cmd!` macro provides a convenient syntax
    // for creating a command (a `Cmd` struct).
    // `read` runs the command and return its `stdout` as a string.
    // You can also use `run`.
    let output = cmd!(sh, "echo Hello, xshell!").read()?;
    println!("Output: {}", output);

    // Run a command with arguments:
    // You don't have to worry about escaping the arguments.
    let file_paths = ["test.txt"];
    let temp_dir = "temp";
    cmd!(
        sh,
        "echo We will create {file_paths...} in the {temp_dir} directory."
    )
    .run()?;
    // Note how the so-called splat syntax `...` is used to interpolate an
    // iterable of arguments. This is a convenient way to pass multiple
    // or optional arguments to a command.

    // Working directory manipulation:
    let current_dir = sh.current_dir();
    println!("Current directory: {}", current_dir.display());

    // Temporarily changes the working directory of this Shell.
    //
    // Returns a RAII guard which reverts the working directory to the old value
    // when dropped.
    let guard = sh.push_dir(temp_dir);
    let cwd = sh.current_dir();
    println!("New current directory: {}", cwd.display());

    // Write to a file. Paths are relative to the current directory of the
    // Shell. Creates the file and all intermediate directories
    // if they don't exist.
    sh.write_file("test.txt", "Some Content")?;

    // Conditional execution of a commmand:
    if cmd!(sh, "test -f test.txt").quiet().run().is_ok() {
        println!("'test.txt' exists");
    } else {
        println!("'test.txt' does not exist");
    }
    // `quiet()` prevents echoing the command itself to `stderr`.
    // This is useful when you don't want to see the command in the output.

    // Path manipulation.
    let file_path = sh.current_dir().join("text.txt");
    println!("File path: {}", file_path.display());

    // Check existence of the file.
    if sh.path_exists(&file_path) {
        // Read the entire contents of a file into a string.
        if let Ok(file_content) = sh.read_file(&file_path) {
            println!("File content: {}", file_content);
        }
        // Remove the file.
        sh.remove_path(file_path)?;
    }

    // Reverts the working directory to its old value when the guard is dropped.
    drop(guard);

    // Checking command status:
    let status = cmd!(sh, "true").run();
    println!("Command status: {:?}", status);

    let failed_status = cmd!(sh, "false").run();
    println!("Failed command status: {:?}", failed_status);
    // Capture `stderr` with `read_stderr`:
    let err_result = cmd!(sh, "cat nonexistent_file").read_stderr();
    println!("Standard error: {}", err_result.unwrap_err());

    // Environment variables:
    // `xshell` maintains its own environment map, independent of the system's.
    sh.set_var("MY_VAR", "my_value");
    println!("Set MY_VAR to {}", sh.var("MY_VAR")?);
    let env_var = cmd!(sh, "echo $MY_VAR").read()?;
    println!("MY_VAR environment variable: {}", env_var);

    // Change the working directory permanently:
    let temp_dir = tempfile::tempdir()?;
    let temp_path = temp_dir.path();
    sh.change_dir(temp_path);

    Ok(())
}
// ANCHOR_END: example

#[test]
fn test() -> anyhow::Result<()> {
    main()?;
    Ok(())
}
// TODO echo $MY_VAR does not work?
TopicRust Crates
General Build Toolscargo build (built-in) compiles your project. cargo check checks your code for errors without compiling.
Cross-Compilationcross simplifies cross-compilation.
Packaging, Distributioncargo-deb creates Debian packages. cargo-rpm creates RPM packages. create-dmg creates macOS disk images.
Build Script Helperscc helps with compiling C/C++ code in build scripts. pkg-config finds system libraries.
Code GenerationUse build scripts.
Link-Time Optimization (LTO) ConfigurationConfigured in Cargo.toml
Incremental Compilation ManagementHandled by cargo directly.