UCL Build
I had a bit of fun today with Codex and GPT-6 Astra, by asking it to make a make clone using UCL as the receipt syntax. The idea came to me when I was working on a Go project that used NPM and ESBuild, with the whole thing being built using Make:
.PHONY: prep
prep:
npm install
.PHONY: frontend
frontend: prep
./node_modules/.bin/esbuild --bundle assets/css/main.css --outfile=build/assets/css/main.css
.PHONY: backend
backend:
CGO_ENABLED=0 go build -o ./build/main .
cp -r ./views ./build/.
.PHONY: dev
dev: frontend backend
Since UCL was on the mind, along with wondering if a build system that could run the frontend and backend targets in parallel would be faster than Make, I came up with the concept of a theoretical build system which would use UCL as the receipt syntax:
stage prep [] {
os:! "npm install"
}
stage frontend [prep] {
os:! "./node_modules/.bin/esbuild --bundle assets/css/main.css --outfile=build/assets/css/main.css"
}
stage backend [] {
os:! "CGO_ENABLED=0 go build -o ./build/main ."
os:! "cp -r ./views ./build/."
}
stage dev [frontend backend] {}
But coding agents are a thing now, making it trivial to realise this concept. So I threw the problem to Codex to see how well something like this would perform compared to Make:
Prompt to Astra
This directory has a new Go project that includes UCL, code located at https://lmika.dev/lmika/ucl. What I would like you to do is make a build tool, similar to Make, which uses UCL as the build language. This is what I had in mind for a build receipt:
[receipt example above]
The build file will consist of stages, which will be a new UCL builtin. Each stage has a name (arg 0), a list of dependent stages (arg 1, as a string list), and a block (arg 2). The build tool is to build the dependency graph based on the dependent stages, setup a pool of goroutines, and run each stage in dependency order, running any stages that are not dependent on each other concurrently. So running ./ucl-build dev should result in this graph:
- dev
- frontend
- prep
- backend
Prep and backend can run concurrently as they are not dependent on each other, yet frontend cannot run until prep is finished, and dev cannot run until frontend is finished.
After about 5 minutes, it had something I could test.
In the end the improvements were pretty negligible: Make is pretty well optimised at this point. And this exercise was little more than a lark. Lord knows the world doesn’t need yet another make-clone. But it was fun being able to take this from concept to something real in a matter of minutes. I’d like to say that I wouldn’t have perused making this if coding agents weren’t around. But let’s be honest, I absolutely would’ve. 🙂