potato face
CryingPotato

A Tale of Cargo Dist

#rust

TL;DR, if you run into OpenSSL issues when using Github Actions and cargo dist, add the following step to your worker:

- uses: knicknic/os-specific-run@v1.0.3
with:
macos: brew install openssl@1.1
linux: |
echo "Hi from linux"
sudo apt-get install libssl-dev
echo "Hi from linux second line"
windows: choco install openssl

I’ve recently been trying to learn clean Rust with batteries included i.e. things like good logging, backtraces and CLI functionality. The rough goal of this is to be able to start a project in Rust at roughly the same clip that I can create a CLI app in Python with click. Of course, the Rust version will be slower to build up to, but the hope is that having a template on top of which to build results in a smooth and consistent developer experience for, well, me.

The project I’ve been using as a candidate for this is autocommit - a tool that automatically stages and commits your changes with a semantically meaningful commit message. When you run autocommit create --path PATH --frequency FREQUENCY a new Cron job is created that executes autocommit run --path PATH every FREQUENCY minutes. autocommit run pulls the current diff of the repository, runs it through GPT-4 and uses the output as a commit message. There are still quite a few rough edges around autocommit, starting with the simple fact that you may not want to commit all your changes automatically, especially if you’re testing something with secrets locally. Regardless, it serves the purpose of being a simple test bed for and end-to-end lifecycle of a Rust CLI project.

Once I had the basic setup working and running on a few local repositories that I develop on, I set my sights on getting the package released to Cargo and Homebrew for easy installation. I’d previously heard about cargo-dist as a simple way to setup Rust projects for publishing packages to crates.io and pre-built binaries to Github releases. Having pre-built binaries set up seemed to be necessary to publish to Homebrew, and I wanted a nice workflow around creating these binaries without having to handroll and learn the Github release process.

Happily for me - I fell into the “Way Too Quickstart” section of the cargo-dist book, which meant that, theoretically, I just had to run

cargo install cargo-dist
cargo dist init # follow the prompt

# install tools
cargo install cargo-release

# cut a release
cargo release 0.1.0

That almost worked - I hadn’t actually created a crates.io account yet so cargo-release couldn’t publish my package. I went ahead and finished up that plumbing and entered the token when prompted, and… voila! we now have a fully working crate published, and a Github release created.

Wait, it was actually that easy? It actually worked on the first try? Of course not. Of course it didn’t. Why would you ever think that is a luxury afforded to software engineers - that working in the combinatorial complexity of build systems would ever create a situation where your config “just works”.

Ok, to be fair, this wasn’t cargo-dist’s fault. We did infact successfully publish a package to crates.io. The problem is our Github workflow to create build artifacts for the release failed (even though it looked like the release was successfully created) - a fact I only discovered after trying to click on one of the links on the release page. Let’s take a look at why our workflow failed:

 --- stderr
thread 'main' panicked at '

Could not find directory of OpenSSL installation, and this `-sys` crate cannot
proceed without this knowledge. If OpenSSL is installed and this crate had
trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
compilation process.

Make sure you also have the development packages of openssl installed.
For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.

If you're in a situation where you think the directory *should* be found
automatically, please open a bug at https://github.com/sfackler/rust-openssl
and include information about your system as well as this message.

Ah, OpenSSL - a true classic. Ok, this has to be a common problem right? Well, almost. A lot of the guidance online (like this and this) suggests that you install OpenSSL or use a vendored copy. After using a vendored copy failed, I decided to get my hands dirty with Github actions and figure out how to install OpenSSL. Asking trusty ChatGPT, I was instructed to add the following step to my .github/workflows/release.yml

- name: Install OpenSSL
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
choco install openssl
elif [[ "$RUNNER_OS" == "macOS" ]]; then
brew install openssl@1.1
else
sudo apt-get install libssl-dev
fi

This worked on Ubuntu, but it turns out Windows has a different format for if statements that it expects. A smarter man would have thought about my initial goal to publish to Homebrew, and dropped Windows support on the floor so quick. But I did not have that intellect - I was two hours deep into this process and would refuse anything but absolute victory. Diving deep into Github forums, I discovered a nice pre-packaged action that runs different commands based on a specific OS. Using it was as simple as replacing the ChatGPT generated step with

- uses: knicknic/os-specific-run@v1.0.3
with:
macos: brew install openssl@1.1
linux: |
echo "Hi from linux"
sudo apt-get install libssl-dev
echo "Hi from linux second line"
windows: choco install openssl

Neat! Now for the moment of truth: cargo release 0.1.3 (yes I had to bump three minor versions in this process because crates.io package versions are immutable) - and, it worked! The last step here was creating the Homebrew formula, which was relatively straightforward. If you’re interested in learning how to do that Federico Terzi has a blog post outlining how to go from here to a published Homebrew package.

Regardless of the OpenSSL snafu, cargo-dist was super smooth and as easy to use as advertised, and I’m likely going to stick with it as a release tool of choice (at least for simple binaries).