2016-03-05

Building With Cargo

So far, we've been building our program manually, using the rustc command directly. This is neither convenient nor extensible. If we update the Rust version included in the vendor/rust directory, we need to rebuild libcore manually. And if we ever wanted to use 3rd party libraries, we'd have to manage those manually, too.

There's a good solution for this problem though, and it's called Cargo. Cargo is a build tool and dependency manager for Rust that is included with the Rust compiler. Unless you (or the package maintainer, if you're using a package manager) did something weird, you should already have it, if you have Rust installed on your system.

Before we can use Cargo to build our program, we need to create a Cargo project for libcore. Since libcore is part of Rust itself, and the core Rust repository doesn't use Cargo (yet), we need to handle that manually.[1]

Fortunately, this is pretty straight-forward. All we need is a directory that contains a simple Cargo.toml and a symbolic link to libcore's source code (vendor/rust/src/libcore).[2]

Here's what the Cargo.toml looks like:

[package]
name    = "core"
version = "0.1.0"
authors = ["Hanno Braun <mail@hannobraun.de>"]
            

That takes care of libcore! Now we need to set up Cargo for the blink program. This took a bit of re-shuffling of the repository contents:

  1. The source code in blink/ has been moved into blink/src/.
  2. I took this opportunity to clean up some filenames, as I didn't want to deviate from the standard set by Cargo unnecessarily. I've renamend main.rs to program.rs and init.rs (which is the program's entry point) to main.rs.
  3. The custom target specification (target.json) and the linker script (linker-script.ld) have been moved into the blink/ directory.

With this reorganization, all that was left was to add a Cargo.toml for the blink project:

[package]
name    = "blink"
version = "0.1.0"
authors = ["Hanno Braun <mail@hannobraun.de>"]

[dependencies]
core = { path = "../core" }

            

As you can see, it references the core library via the Cargo project that we set up. From now on, Cargo will rebuild the core library automatically, if necessary, so we no longer have to do it manually.

You can build most Cargo projects by running cargo build, but in our case, that's not enough. Our program needs the custom target specification to work, so we must tell Cargo about that by running cargo build --target=target.json. For that reason, I've decided to keep the build scripts and just adapt them to use Cargo.

Here's the updated compile script:

#!/usr/bin/env bash

cd blink
cargo build --target=target.json                
            

And here's the updated upload script:

#!/usr/bin/env bash

DEVICE=/dev/ttyACM0

mkdir -p output

./compile &&

arm-none-eabi-objcopy \
    -O binary \
    blink/target/target/debug/blink \
    output/blink.bin &&

(
    cd uploader
    cargo run -- $DEVICE upload-file ../output/blink.bin)
            

Please remember that you need to erase the flash memory by pressing the ERASE button on the Arduino Due, before you can upload the program.

And that's all there is to it. With Cargo, we have a more robust build system, and could even start to use some 3rd party libraries, if we wanted. As always, the full code is available on GitHub. See you next time!