Table of contents

  1. Introducing Werk 💅
    1. Just immediately, Why?!
      1. Why not Make?
      2. Why not just?
      3. Why not $toolname?
  2. Overview
    1. Example building C program
    2. Example building a Rust project with Cargo
  3. Remarks
    1. In defense of getting sidetracked
    2. Rust


Introducing Werk 💅

I made a thing.

werk is a simplistic build system, similar to make, and a command runner, similar to just. It tries to be very easy to use, and religiously portable.

This is a general overview. For a more detailed explanation of syntax and behavior, please consult the project's README.

The source code for werk is available on GitHub.

Check out the examples.

Caution: werk is alpha software. Features may be buggy or missing. It may eat your code. Use at your own risk. Commit everything before trying it out. Consider trying it out in a virtual machine first. Don't blame me if you didn't.

Just immediately, Why?!

The motivation for werk is that make is annoying. Make is a very sophisticated tool that solves a bunch of problems I don't have, and doesn't solve many problems that I do have.

My personal use case - highly specific, but far from unique:

Why not Make?

Here's an incomplete list of problems I have with make:

Why not just?

Here's a 100% exhaustive list of problems I have with just:

Why not $toolname?

Here's a loose collection of problems I have with other purportedly cross-platform tools:

Overview

werk parses a Werkfile containing recipes, determines what the user wants to build, what the dependencies are, and runs programs to rebuild any outdated build products in the right order.

Consult the documentation for many, many more details.

Why a new language? I tried various things, including expression build rules declaratively in TOML. werk actually still supports this mode, mainly for testing. But it quickly becomes very unwieldy to express the kind of things you really want to express in build scripts.

It sucks, I know, but I promise it's fairly pleasant and readable.

Example building C program

Here's a minimal Werkfile to build a C program. It showcases build recipes (with depfiles), command recipes ("workflows"), and various forms of string interpolation. See also the language reference.

let cc = which "clang"
let ld = cc

build "%.o" {
  from "{%}.c"
  depfile "{%}.c.d"
  run "{cc} -c -o <out> <in>"
}

build "%.c.d" {
  from "{%}.c"
  run "{cc} -MM -MT <in> -MF <out> <in>"
}

build "example{EXE_SUFFIX}" {
  from glob "*.c" | map "{:.c=.o}"
  run "{ld} <in*> -o <out>"
}

task build {
  build "example{EXE_SUFFIX}"
  info "Build complete!"
}

Example run:

$ werk build
[ ok ] /foo.o
[ ok ] /main.o
[ ok ] /example.exe
[ ok ] build
[info] Build complete!

Example building a Rust project with Cargo

let cargo = which "cargo"

build "debug/my-program{EXE_SUFFIX}" {
  # Werk understands depfiles emitted by Cargo
  depfile "debug/my-program.d"

  # This will only run if any of the source files discovered by Cargo
  # have changed since the last run.
  run "{cargo} --profile=dev -p my-program"
}

task clean {
  run "{cargo} clean"
}

task build {
  build "debug/my-program{EXE_SUFFIX}"
}

Example run:

$ werk build
[ ok ] /debug/my-program.exe
[ ok ] build

Remarks

werk is a side project. Contributions are welcome, but ultimately it is designed to address my personal use cases. If you believe it should also address your use case, please feel free to get in touch.

In defense of getting sidetracked

Did I have to make werk? Not really. I could have made GNU Make work for my purposes. In fact, I did for a long time. On top of that, I'm trying to deliver a video game, one of the notoriously hardest thing to actually finish.

The most challenging thing about finishing a video game as a solo developer is to stay motivated. There are many problems that are hard to solve, and that's a big part of the fun, but it's also a constant pull on my creativity. That's both exciting and incredibly draining.

Toughing it out and powering through does not work. What works is to take a break, step away from the trenches, and let the creative juices stew in the background for a while.

Doing little side projects that feel useful - and hopefully contribute a bit to the broader ecosystem - is a distraction, but it's also a way to stay healthy and productive, and combats the feeling of isolation.

Rust

As you may have guessed, werk is written in Rust, and it leans into async/await for managing concurrency. It's no secret that Rust is my current favorite language, and I use it for almost everything. I want to share why.

How was Rust helpful?

There's a couple of uses of unsafe in the entire project, all trivial one-liners with guaranteed semantics (transparent reference casting), mostly related to converting between werk_fs::Path and str.

Shout-out to the dependencies that made things significantly easier, and their authors: which, globset, winnow, clap, and smol.