on
The ideal build tool
Recently some coworkers at the company I work are having a wonderful time using just. just is a project I have a lot of respect for because it knows exactly what it is and is extremely good at it. It's a command runner, meant to aid in abstracting on top of disparate tools. It is not a build tool. There is a place for just but it's as a meta-layer on top of other build tools. Something a platform team might mandate to allow them to write simple shared CI scripts rather than tool-specific ones.
For almost all other cases a real build tool is preferred. just doesn't solve the same problems that a real build tool solves, instead the way many teams adopt it sweeps those problems under the rug, waiting to strike at a later date.
For the purposes of this document we're going to consider what a "build tool" really is and what the ideal build tool would look like. I have a lot of background in gradle so much of the language will mirror theirs.
At a minimum a build tool should:
- Allow the defininition arbitrary programmatic tasks
- Allow the declaration of dependencies between tasks
- Allow reusable task types to be defined
- Allow the user interface to be platform-agnostic
- Perform all operations necessary to perform the task from the CLI (read: single-entry)
Some nice-to-haves:
- Allow the definition of task inputs/outputs
- Express dependencies on inputs/outputs instead of tasks
- Express a reproducible build and cache portions of the task graph
- Be language/tool agnostic, allowing polyglot monorepos to grow naturally
From this list I would be expected to be a big fan of the python/skylark build tools (bazel, pants, buck, please) and while I am in theory the ergonomics of them as well as the evolution from "simple" projects to complex is not a pleasant on-ramp.
Gradle to me presents the best programmatic model. One that allows developers to begin writing bad build scrips that get the job done and evolving into something fast, correct and composable. Gradle has it's own sharp edges however. The JVM is a large hurdle for certain projects, the historical baggage of it's API surface means there's 1000 ways to do anything most of those ways are wrong, and Gradle being so strongly associated with Java means that many of it's values are biased towards solving those problems rather than creating a more generic build tool.
The Ideal Build Tool
In my dreams there's a build tool (let's call it mikey) that can work for all languages, it works on top of the lowest level compiler each language has (ex: rustc over cargo) and is written in a cross platform, quickly compiling language, simple but expressive. Unfortunately Golang meets all of these capabilities but for my own sanity I would choose something like lisette so that the programming model for the end-user could be more pleasant.
As far as a DSL parser, a mikey.build file would mostly be stock-lisette, more like a library/framework than a new language of it's own. Plugins would work similarly to gradle but would be strictly versioned and sandboxed purely to their own folders with inputs/outputs well defined. There would be a dynamic plugin system hidden from the user to allow the definition of arbitrary tasks and then an evolution into formal task definitions, etc.
When you went into a repo you would only have to type
mikey build
or
mikey test
or
mikey deploy
And you would automatcally be prompted for the values you needed to configure/provide with a property resolution strategy allowing user-level, org level, system level overrides.