Rust task runner and build tool.
- Overview
- Installation
- Usage
- Simple Example
- Tasks, Dependencies, and Aliases
- Commands, Scripts, and Sub Tasks
- Default Tasks and Extending
- Extending Tasks
- Environment Variables
- Setting Up Working Directory
- Ignoring Errors
- Conditions
- Installing Dependencies
- Workspace Support
- Toolchain
- Init and End tasks
- Catching Errors
- Cargo Alias Tasks
- Profiles
- Private Tasks
- Deprecated Tasks
- Watch
- Functions
- Continuous Integration
- Predefined Flows
- Minimal Version
- Performance Tuning
- Command Groups (Subcommands)
- Diff Changes
- Unstable Features
- CLI Options
- Plugins
- Shell Completion
- Global Configuration
- Makefile Definition
- Task Naming Conventions
- Articles
- Badge
- Roadmap
- Editor Support
- Contributing
- Release History
- License
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.
In order to install, just run the following command
cargo install --force cargo-make
This will install cargo-make in your ~/.cargo/bin
.
Make sure to add ~/.cargo/bin
directory to your PATH
variable.
You will have two executables available: cargo-make
and makers
- cargo-make - This is a cargo plugin invoked using cargo make ...
- makers - A standalone executable which provides same features and cli arguments as cargo-make, but is invoked directly and not as a cargo plugin.
See Cli Options section for full CLI instructions.
In order to install with minimal features (for example, no TLS support), run the following:
cargo install --no-default-features --force cargo-make
sudo pacman -S cargo-make
Binary releases are available in the github releases page.
The following binaries are available for each release:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
- aarch64-apple-darwin
When using cargo-make, all tasks are defined and configured via toml files.
Below are simple instructions to get you started off quickly.
In order to run a set of tasks, you first must define them in a toml file.
For example, if we would like to have a script which:
- Formats the code
- Cleans old target directory
- Runs build
- Runs tests
By default, cargo-make reads tasks from Makefile.toml
if it exists.
We will create a Makefile.toml
file as follows:
[tasks.format]
install_crate = "rustfmt"
command = "cargo"
args = ["fmt", "--", "--emit=files"]
[tasks.clean]
command = "cargo"
args = ["clean"]
[tasks.build]
command = "cargo"
args = ["build"]
dependencies = ["clean"]
[tasks.test]
command = "cargo"
args = ["test"]
dependencies = ["clean"]
[tasks.my-flow]
dependencies = [
"format",
"build",
"test"
]
We would execute the flow with the following command:
cargo make my-flow
The output would look something like this:
[cargo-make] INFO - cargo make 0.37.21
[cargo-make] INFO - Build File: Makefile.toml
[cargo-make] INFO - Task: my-flow
[cargo-make] INFO - Setting Up Env.
[cargo-make] INFO - Running Task: format
[cargo-make] INFO - Execute Command: "cargo" "fmt" "--" "--emit=files"
[cargo-make] INFO - Running Task: clean
[cargo-make] INFO - Execute Command: "cargo" "clean"
[cargo-make] INFO - Running Task: build
[cargo-make] INFO - Execute Command: "cargo" "build"
Compiling bitflags v0.9.1
Compiling unicode-width v0.1.4
Compiling quote v0.3.15
Compiling unicode-segmentation v1.1.0
Compiling strsim v0.6.0
Compiling libc v0.2.24
Compiling serde v1.0.8
Compiling vec_map v0.8.0
Compiling ansi_term v0.9.0
Compiling unicode-xid v0.0.4
Compiling synom v0.11.3
Compiling rand v0.3.15
Compiling term_size v0.3.0
Compiling atty v0.2.2
Compiling syn v0.11.11
Compiling textwrap v0.6.0
Compiling clap v2.25.0
Compiling serde_derive_internals v0.15.1
Compiling toml v0.4.2
Compiling serde_derive v1.0.8
Compiling cargo-make v0.1.2 (file:///home/ubuntu/workspace)
Finished dev [unoptimized + debuginfo] target(s) in 79.75 secs
[cargo-make] INFO - Running Task: test
[cargo-make] INFO - Execute Command: "cargo" "test"
Compiling cargo-make v0.1.2 (file:///home/ubuntu/workspace)
Finished dev [unoptimized + debuginfo] target(s) in 5.1 secs
Running target/debug/deps/cargo_make-d5f8d30d73043ede
running 10 tests
test log::tests::create_info ... ok
test log::tests::get_level_error ... ok
test log::tests::create_verbose ... ok
test log::tests::get_level_info ... ok
test log::tests::get_level_other ... ok
test log::tests::get_level_verbose ... ok
test installer::tests::is_crate_installed_false ... ok
test installer::tests::is_crate_installed_true ... ok
test command::tests::validate_exit_code_error ... ok
test log::tests::create_error ... ok
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
[cargo-make] INFO - Running Task: my-flow
[cargo-make] INFO - Build done in 72 seconds.
We now created a build script that can run on any platform.
The tasks can be stored in any toml file. Invoke cargo-make with --makefile other-filename.toml
to start processing using other-filename.toml
.
cargo-make can be invoked as a cargo plugin via cargo make
command, or as a standalone executable via makers
command.
Important Note: if you are running this example in a cargo workspace, you will need to add the following to the top of the file:
[env]
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
More on workspace support in the relevant sections in this document.
In many cases, certain tasks depend on other tasks.
For example you would like to format the code before running build and run the build before running tests.
Such flow can be defined as follows:
[tasks.format]
install_crate = "rustfmt"
command = "cargo"
args = ["fmt", "--", "--emit=files"]
[tasks.build]
command = "cargo"
args = ["build"]
dependencies = ["format"]
[tasks.test]
command = "cargo"
args = ["test"]
dependencies = ["build"]
When you run:
cargo make --makefile ./my_build.toml test
It will try to run test, see that it has dependencies and those have other dependencies.
Therefore it will create an execution plan for the tasks based on the tasks and their dependencies.
In our case it will invoke format -> build -> test.
The same task will never be executed twice. So, if we have, for example:
[tasks.A]
dependencies = ["B", "C"]
[tasks.B]
dependencies = ["D"]
[tasks.C]
dependencies = ["D"]
[tasks.D]
script = "echo hello"
In this example, A depends on B and C, and both B and C are dependent on D.
Task D, however, will not be invoked twice.
The output of the execution will look something like this:
[cargo-make] INFO - Task: A
[cargo-make] INFO - Setting Up Env.
[cargo-make] INFO - Running Task: D
[cargo-make] INFO - Execute Command: "sh" "/tmp/cargo-make/CNuU47tIix.sh"
hello
[cargo-make] INFO - Running Task: B
[cargo-make] INFO - Running Task: C
[cargo-make] INFO - Running Task: A
As you can see, 'hello' was printed once by task D as it was only invoked once.
But what if we want to run D twice?
Simple answer would be to duplicate task D, have B depend on D, and C depend on D2, which is a copy of D.
But duplicating can lead to bugs and to huge makefiles, so we have aliases for that.
An alias task has its own name and points to another task.
All of the definitions of the alias task are ignored.
So now, if we want to have D execute twice, we can do the following:
[tasks.A]
dependencies = ["B", "C"]
[tasks.B]
dependencies = ["D"]
[tasks.C]
dependencies = ["D2"]
[tasks.D]
script = "echo hello"
[tasks.D2]
alias="D"
Now C depends on D2, and D2 is an alias for D.
Execution output of such make file would like as follows:
[cargo-make] INFO - Task: A
[cargo-make] INFO - Setting Up Env.
[cargo-make] INFO - Running Task: D
[cargo-make] INFO - Execute Command: "sh" "/tmp/cargo-make/HP0UD7pgoX.sh"
hello
[cargo-make] INFO - Running Task: B
[cargo-make] INFO - Running Task: D2
[cargo-make] INFO - Execute Command: "sh" "/tmp/cargo-make/TuuZJkqCE2.sh"
hello
[cargo-make] INFO - Running Task: C
[cargo-make] INFO - Running Task: A
Now you can see that 'hello' was printed twice.
Tasks may also depend on tasks in other files.
To do this, specify the dependency with the object format, providing the path.
cargo-make will use this path as it would any other supplied on the command line:
If a filename is supplied, it searches that file.
Otherwise it search for the default Makefile.toml
on that path.
[tasks.install]
command = "mv"
args = ["src/B/out", "src/C/static"]
dependencies = [
{ name = "compile", path = "src/B" },
{ name = "clean", path = "src/C/tasks.toml" },
]
The run_task attribute will tell a task to invoke another task in a new execution plan. This will also result in dependencies being invoked multiple times.
It is also possible to define platform specific aliases, for example:
[tasks.my_task]
linux_alias = "linux_my_task"
windows_alias = "windows_my_task"
mac_alias = "mac_my_task"
[tasks.linux_my_task]
[tasks.mac_my_task]
[tasks.windows_my_task]
If platform specific alias is found and matches current platform it will take precedence over the non platform alias definition.
For example:
[tasks.my_task]
linux_alias = "run"
alias = "do_nothing"
[tasks.run]
script = "echo hello"
[tasks.do_nothing]
If you run task my_task on windows or mac, it will invoke the do_nothing task.
However, if executed on a linux platform, it will invoke the run task.
As a side note, cargo-make will attempt to invoke the task dependencies in the order that they were defined, unless they are defined also as sub dependencies.
The actual operation that a task invokes can be defined in 3 ways.
The below explains each one:
- run_task - Invokes another task with the name defined in this attribute. Unlike dependencies which are invoked before the current task, the task defined in the run_task is invoked after the current task.
- command - The command attribute defines what executable to invoke. You can use the args attribute to define what command line arguments to provide as part of the command.
- script - Invokes the script. You can change the executable used to invoke the script using the script_runner attribute. If not defined, the default platform runner is used (
cmd
for Windows,sh
for others).
Only one of the definitions will be used.
If multiple attributes are defined (for example both command and script), the task will fail during invocation.
The script attribute may hold non OS scripts, for example rust code to be compiled and executed.
In order to use non OS script runners, you must define the special script_runner with the @ prefix.
The following runners are currently supported:
- @duckscript - Executes the defined duckscript code. See example
- @rust - Compiles and executes the defined rust code. See example
- @shell - For Windows platforms, it will try to convert the shell commands to Windows batch commands (only basic scripts are supported) and execute the script; for other platforms, the script will be executed as-is. See example
Below are some basic examples of each action type.
In this example, if we execute the flow task, it will invoke the echo task defined in the run_task attribute.
[tasks.echo]
script = "echo hello world"
[tasks.flow]
run_task = "echo"
A more complex example below demonstrates the ability to define multiple task names and optional conditions attached to each task.
The first task for which the conditions are met (or if no conditions are defined at all), will be invoked.
If no task conditions are met, no sub task will be invoked.
More on conditions can be found the conditions section
[tasks.test1]
command = "echo"
args = ["running test1"]
[tasks.test2]
command = "echo"
args = ["running test2"]
[tasks.test3]
command = "echo"
args = ["running test3"]
[tasks.test-default]
command = "echo"
args = ["running test-default"]
[tasks.test-routing]
run_task = [
{ name = "test1", condition = { platforms = ["windows", "linux"], channels = ["beta", "stable"] } },
{ name = "test2", condition = { platforms = ["mac"], rust_version = { min = "1.20.0", max = "1.30.0" } } },
{ name = "test3", condition_script = [ "somecommand" ] },
{ name = "test-default" }
]
It is also possible to run the sub task as a forked sub process using the fork attribute.
This prevents any environment changes done in the sub task to impact the rest of the flow in the parent process.
Example of invoking the sub task in a forked sub process:
[tasks.echo]
command = "echo"
args = ["hello world"]
[tasks.fork-example]
run_task = { name = "echo", fork = true }
The name attribute can hold either a single task name or a list of tasks.
In case of a list, the tasks would be invoked one after the other in sequence.
For example, below simple-multi and routing-multi both demonstrate different ways to define multi task invocations via run_task:
[tasks.echo1]
command = "echo"
args = ["1"]
[tasks.echo2]
command = "echo"
args = ["2"]
[tasks.simple-multi]
run_task = { name = ["echo1", "echo2"] }
[tasks.routing-multi]
run_task = [
{ name = ["echo1", "echo2"] },
]
You can also setup a cleanup task to run after the sub task even if the sub task failed.
This is only supported in combination with fork=true attribute.
For example:
[tasks.echo1]
command = "echo"
args = ["1"]
[tasks.echo2]
command = "echo"
args = ["2"]
[tasks.fail]
script = "exit 1"
[tasks.cleanup]
command = "echo"
args = ["cleanup"]
[tasks.cleanup-example]
run_task = { name = ["echo1", "echo2", "fail"], fork = true, cleanup_task = "cleanup" }
In order to run multiple tasks in parallel, add parallel = true to the run_task
object.
For example:
[tasks.echo1]
command = "echo"
args = ["1"]
[tasks.echo2]
comman