Tutorial
The tutorial aims to introduce you to August by writing a build script for a Rust project.
In the same directory as your Cargo.toml
, create a file called main.august
.
Writing a Unit
August build scripts consist of units which specify a set of commands to run.
To create a unit that prints your Cargo.toml
and builds your crate, add the following:
print_file
can usually be categorised under a namespace.
Therefore, it is possible to replace this line with it's fully qualified variant:
Some commands have aliases such as exec
which can be rewritten as:
Let's also create a task to run some tests:
Dependencies
One of the advantages of a task-based build system is that a unit can be dependent on another.
In this case, Test
must be run before Build
.
As such, it can be said that Build
depends on Test
.
This can be described in graph format:
flowchart LR
Build -->|depends on|Test
To communicate this to August, we can use the depends_on
command:
Something that should be noted is that depends_on
can be placed anywhere in the unit,
and if multiple depends_on
commands are found, their contents will be aggregated.
depends_on
defines the key rule that powers all build systems,
a unit should only be run after all of the units it depends on have also been run.
More Complex Dependencies
Consider the following build script:
unit Build {
depends_on(Test, Clean)
print_file("Cargo.toml")
~(cargo build)
}
unit Test {
depends_on(Clean)
~(cargo fmt)
~(cargo clippy)
~(cargo test)
}
unit Clean {
~(cargo clean)
}
flowchart TB
Build --> Test
Build --> Clean
Test --> Clean
When executing Build
, August will try to resolve both of its dependencies at once.
As such, Test
starts it has wait for Clean
to complete, but does not ever launch Clean
.
This is because units have the property that will only get executed as a dependency, once per run.
For those with a familiarity of how a breadth-first search looks, its basically the same thing.
An illegal construction would look like this:
flowchart TB
Build --> Test
Clean --> Build
Test --> Clean
This is circular dependency and is impossible to resolve as Build
gets blocked on Test
which is blocked on Clean
which is blocked on Build
which is blocked on Test
!
August's decision in this case is just to deadlock and pray you Ctrl+C it.
In future, verification measures may be introduced when reading the script to detect these.
Reusablity
To return to our original example, let's talk about DRY build scripts.
August makes it possible call units sequentially as if they were commands thanks to do
.
The following example may feel a bit contrived, but it exists to demonstrate this use case.
unit Build {
depends_on(Test)
print_file("Cargo.toml")
~(cargo build)
}
unit Test {
do(Other)
~(cargo test)
}
unit Other {
~(cargo fmt)
~(cargo clippy)
}
do
allows a unit to be run multiple times.
Generally, units intended to be run via do
should not have any dependencies,
as they are meant to action more like functions that always provide the same result.
However, do
will still call a unit's dependencies if they haven't been resolved,
but will not rerun them on subsequent calls.
do
also runs units in sequential if provided multiple, so:
Running Units
Continuing with the script from the last section, to run the Build
unit, use the august
CLI:
main.august
, use the -s
flag:
The August CLI comes with two special built in cases, august build
and august test
.
To bind to these CLI subcommand, we use expose declarations.
Full Script
expose Build as build
expose Test as test
unit Build {
depends_on(Test)
print_file("Cargo.toml")
~(cargo build)
}
unit Test {
do(Other)
~(cargo test)
}
unit Other {
~(cargo fmt)
~(cargo clippy)
}
More Resources
Congrats! You have completed the tutorial.
You can continue to these resources for more information: