Automating your Rust workflows with cargo-make - Part 2 of 5 - Extending tasks, platform overrides and aliases

In my previous article I presented a short overview of what is cargo-make and talked a bit on what is a task and what are dependencies.

In this article we will talk more about how to make your tasks cross platform, how to extend and change predefined tasks and how to redirect tasks using aliases.


Extending Tasks

cargo-make comes with many predefined tasks and flows that can be used without redefining them in your project.

However in some cases, you would like to change them a bit to fit your needs without rewriting the entire task.

Let’s take for example the format task which is predefined internally inside cargo-make as follows:

[tasks.format]
description = "Runs the cargo rustfmt plugin."
install_crate = "rustfmt"
command = "cargo"
args = ["fmt", "--", "--write-mode=overwrite"]

If for example you do not want to use the override mode, but instead use the default mode, you can just change the args of the task in your external Makefile.toml as follows:

[tasks.format]
args = ["fmt"]

When cargo-make starts up, it will load the external Makefile.toml (if exists) and the internal definitions and will merge them. Since the external file overrides the internal definitions, only the args attribute for the format task which was redefined, will override the args attribute which was defined internally, and the actual result would be:

[tasks.format]
description = "Runs the cargo rustfmt plugin."
install_crate = "rustfmt"
command = "cargo"
args = ["fmt"]

External makefiles extending process is more complex and powerful, but it will be explained in future article and not this one.


Platform Overrides

In the previous article we gave examples of tasks running scripts. The problem with scripts is that they are not portable. In most cases (but not all), they can work for both mac and linux but not for windows and vice versa.

The platform override feature uses the task extending capability to change specific attributes for specific OS and help us create cross platform tasks and flows.

Let’s take an example of a simple task that just prints the directory contents using the ls command.

[tasks.dir]
script = ["ls"]

This would work for linux but not for windows. For windows we would like to use the dir command instead. For that we can use a platform override.

A platform override is the ability to extend a task for a specific OS (linux, mac and windows). In order to extend a task for a specific OS, you just need to define the task with the OS type as a suffix as follows:

[tasks.dir.windows]
script = ["dir"]

Now we have a task named dir that invoked the command ‘ls’ for linux and mac and for windows we extend the task and override only the script attribute and redefine it to invoke the ‘dir’ command.

Just like in the previous section, platform override tasks only extend the original task, they do not replace. Therefore you can simply replace only the attributes you want instead of redefining the entire task.

For example:

[tasks.common-format]
description = "Runs the cargo rustfmt plugin."
install_crate = "rustfmt"
command = "cargo"
args = ["fmt", "--", "--write-mode=overwrite"]
[tasks.common-format.mac]
args = ["fmt"]

In this example, the common-format runs with the overwrite mode, but in mac we run with default mode.


Aliases

Tasks can only be invoked once but sometimes you may have scenarios where you need to invoke them several times during a flow. Also sometimes you wish to invoke different tasks based on the platform (OS) you are running on. For these reasons, you have aliases.

When a task defines the alias attribute, it basically replaces its definition with the definition of the task it is pointing to. All task attributes are removed and are replaced.

For example:

[tasks.flow]
dependencies = ["A", "B"]

[tasks.A]
dependencies = ["echo-hello"]

[tasks.B]
dependencies = ["echo-never"]

[tasks.echo-hello]
script = [
"echo hello"
]

[tasks.echo-never]
alias="echo-hello"
script = [
"echo never"
]

In this example, task echo-never is pointing to task echo-hello. Its name is echo-never but its content is actually copied from echo-hello. When running task flow which depends on tasks A and B, each one of those depends on a different echo task and both echo tasks are invoked, which would result in the following output:

[cargo-make] info - Task: flow
[cargo-make] info - Setting Up Env.
[cargo-make] info - Running Task: echo-hello
[cargo-make] info - Execute Command: "sh" "/tmp/cargo-make/HP0UD7pgoX.sh"
hello
[cargo-make] info - Running Task: A
[cargo-make] info - Running Task: echo-never
[cargo-make] info - Execute Command: "sh" "/tmp/cargo-make/TuuZJkqCE2.sh"
hello
[cargo-make] info - Running Task: B
[cargo-make] info - Running Task: flow

This example showed us of one way to force a task to be invoked multiple times, however this is not the only way to use aliases.

The alias attribute is special as it also comes in OS level in the form of linux_alias, windows_alias, mac_alias. You can use the OS level alias to point to different tasks based on your OS instead of overriding it.

For example:

[tasks.ls]
script = ["ls"]
windows_alias = "dir"
[tasks.dir]
script = ["dir"]

This is very similar to the platform override example, but now, instead of overriding the task for windows and changing a specific attribute (script), we redirect to a completely different task.

We can use this to disable tasks for specific platforms, for example:

[tasks.bash-script]
# do something that only works for linux and mac and it is not
# not supported and not needed for windows.
script = ["..."]
# we do not want these dependencies invoked for windows
dependencies = ["task1", "task2"]
#for windows, we redirect to an empty task that does nothing
windows_alias = "empty"
[tasks.empty]
# empty task that does nothing

In this example, we didn’t just prevent the script from being called, we also prevented the dependencies from being invoked as well.


That’s it for now, in the next article I’ll be talking about environment variables, conditions, running sub tasks and how to mix them to create conditional sub flows.