January 30 2020, 4:31PM
How do you organize Rust projects with multiple binaries so that the build
output winds up in a common subdirectory? Should you be looking for a solution
other than cargo? Regardless of whether you are using nested crates within a
workspace or simply a mixture of
.rs files under
src/bin/, you absolutely
should be looking for something other than cargo. What you need is a proper
task runner and the most portable task runner ships with every unix
flavored operating system;
People seem to conflate task runners with build tools. Build tools generate
artifacts such as binaries or libraries whereas task runners act as the glue for
teams to share ways to achieve particular chores. Some people use tools like
make to do both jobs and the crossed responsibility brings a lot of pain and
maintenance burden. People need to be aware of the many nuances of
as the fact that tabs for indenting are semantic, rules for tasks need to be
.PHONY if there is a target they relate to, and so on. Others end up
hybrid domain specific language that mixes a bit of programming and
configuration to specify how tasks are run, e.g.
gulp. You don't need any of
I'll call this script
bin/build. We will assume there are several crates in a
workspace for this example and that we use
git since cargo bootstraps projects
with it by default.
#!/bin/sh -eux ROOT=$(git rev-parse --show-toplevel) cd "$ROOT" mkdir -p dist/bin for crate in crate1 crate2 crate3; do cd "$crate" cargo build --release cp target/release/$crate "$ROOT/dist/bin/" cd "$ROOT" done
This script is dead-simple. It shoots to the root of the project, makes the
dist and its subdirectory
bin. We have a list of crates in a
loop we iterate across but we could make this dynamic, as well. Then, in each
crate we create a release build and copy the binary from the project up to the
common subdirectory. Then, we shoot back to the root directory again and repeat.
All we have to do now to do now is make the script executable and call it:
$ chmod +x bin/build $ bin/build
You don't need to let scripts grow out of control, either. What's awesome about keeping scripts, and, more generally, programs small means you can compose things like this:
init might do some stubbing or setup work and
run might launch a
service, whatever those tasks may be.
sh is POSIX compliant, which means it allows us to write highly portable, and
therefore shareable, scripts. Like anything there are ways things can go wrong
but you can address this by using the linter
shellcheck. Every shell script you
write should have the following
Which says to use
sh instead of, say,
bash. shellcheck will actually
recommend things intelligently based on which shell you specify.
bash is not
ideal here because support for particular features differs between versions and
we are aiming to have something pretty much anyone on a team can use at a
moment's notice so long as they are using linux, bsd, darwin, or any other *nix
flavor. This prelude also turns on some common flags.
(3) can be optionally dropped if you don't want to expose details or want cleaner output.
The last convention is to keep scripts in a common
bin directory at the root
of your project which enhances discoverability of scripts for others. Allowing
people to make less guesses about which directory is the single source of truth
for automation scripts helps people move faster. If they want a chore done, they
can see what's present under
bin, or if they need to add a chore they know
exactly where it's added for every project. The reason for why its called
is that they are executables!
In summary, for shell script success all you need is:
shand some options set