Why I Created lazymake: Solving Make's UX Problem
Source: Dev.to
Typical workflow with a new project
- Open the
Makefile - Scroll through 500 + lines of cryptic rules
- Try to understand what
build,build‑prod, andbuild‑alldo - End up running the wrong target and breaking something
- Write in Slack: “Guys, what command do I need to run to deploy to staging?”
I found myself keeping separate notes with the most‑used commands for each project – which felt odd, because the whole point of a Makefile is to centralise those commands.
The Breaking Point
The moment I decided something had to change was when I was onboarded to a new project. My senior colleague, an excellent engineer, simply said:
“Look at the Makefile, everything is clear there.”
I opened it and was horrified:
- ~600 lines of code
- Variables everywhere
- Conditional logic and nested dependencies
I couldn’t figure out how to run the project locally without mentally parsing hundreds of lines of syntax. It seemed madness.
I thought about tools like lazygit that give a nice TUI for Git, and wondered why nothing similar existed for Makefiles. That sparked the idea for my own TUI application.
Development
I started with a simple idea: show a list of targets, let the user pick one, run it, and display the output. A very basic TUI built with Bubble Tea. The first version was only 200–300 lines of Go and already worked.
From there I kept adding features to solve the pain points I’d experienced over the years.
Dependency Graph
The first major feature was a dependency graph. Now, when I need to run a target I can see:
- The complete dependency tree
- Execution order
- Critical path (the slowest chain that determines overall execution time)
- Parallelisation opportunities – what can be run with
make -j
Press g on any target to view the graph:
This alone made the project valuable: I can now see exactly what a target does instead of guessing from the Makefile. It also catches circular dependencies that might accidentally slip in.
Variable Inspector
Make’s variable system is powerful but opaque. Questions like:
- What does
$(LDFLAGS)expand to? - Which targets use
$(VERSION)?
required grepping the whole file.
With lazymake you can press v to open a full‑featured browser that shows every variable, its definition, and where it’s used:
What used to be a 20‑minute debugging session is now a 20‑second lookup.
Safety
I’ve often seen colleagues (myself included) accidentally run destructive commands because we didn’t understand what a target does.
To mitigate this, lazymake includes a pattern‑matching safety system that flags dangerous commands:
rm -rf / # CRITICAL – requires confirmation
DROP DATABASE # CRITICAL – requires confirmation
git push --force # WARNING – shows alert
terraform destroy # WARNING – context‑aware
The system is context‑aware. For example, rm -rf inside a target named clean-test-cache is acceptable, but the same command in a target called nuke-everything triggers a confirmation prompt.
Has it saved me from bad consequences? Yes – several times.
TL;DR
- lazymake provides a TUI for exploring Makefile targets, dependencies, and variables.
- It visualises the dependency graph, highlights critical paths, and suggests parallelisation.
- The variable inspector makes the opaque variable system transparent.
- Built‑in safety checks prevent accidental execution of destructive commands.
All of this turns a frustrating, manual search through a massive Makefile into a fast, interactive experience.
Highlighting
This wasn’t initially on my feature list. But Makefiles can contain literally any language — bash, Python, Go, anything. So to read multi‑line recipes I integrated Chroma for automatic code detection and highlighting:
It detects the language by shebangs, command patterns, or manual hints. Makes reading complex recipes much easier.
What I Didn’t Expect
I added some features during early testing. For example, performance tracking. It automatically times execution and reports if something became slower than usual (> 25 % of normal execution time). This way I caught several regressions.
I also added workspace management. You can press w and see all Makefiles in the project. Turns out, this can be useful in monorepos.
In addition, I added execution history. The last 5 targets are shown first. From my own experience, I noticed that in most cases I run the same commands anyway.
What Was Difficult
Parsing Makefiles
Makefiles aren’t a programming language nor a simple configuration file. Variable expansion, pattern rules, conditional includes — all of this is quite complex.
At first I thought to just use Make itself:
make -pn | grep "^[a-zA-Z].*:"
But I also needed to extract comments, dependencies, and the like from Makefiles. So I still wrote my own parser. It’s not perfect, of course, but it seems to do its job.
Real‑time Output
Bubble Tea helped me get streaming output of commands in a TUI.
Cross‑platform Support
I found it quite difficult to make the application work well on macOS, Linux, Bash, Zsh, etc. Terminal capabilities differ greatly. Honestly, I’m still not entirely sure that everything works everywhere as it should, but mostly it seems to work. :)
Try It Yourself
# macOS/Linux
brew install rshelekhov/tap/lazymake
# Or via Go
go install github.com/rshelekhov/lazymake/cmd/lazymake@latest
# Then simply:
lazymake
Works with any Makefile, runs even without configuration (though I still added the ability to configure the program for your needs).
I want to develop the project further, but for this I need to understand what users need. If you’re interested in the project and thinking of starting to use it, tell me: what’s your biggest pain point with Makefiles?
If this seems useful, star the repository ⭐ and let me know what you think. I’m always happy to hear feedback (or complaints—they’re useful too).



