I've been working with Makefiles for quite a while, and I often find myself using search (ctrl+f) to find the right target and run it. This doesn't always work because I'm naturally an inattentive person and forget about dependencies. I have to scroll to the beginning, look at the dependencies to finally figure out why everything is failing. Only after that do I start to understand what's happening and run the correct command.
And then I thought: "Is there really no way to make this convenient?" I didn't find an answer and decided to create my own solution.
The Problem, or Why Make Can Be Frustrating
Make has existed for a very long time and is quite popular (I can't find the source now, but somewhere I read that approximately 19% of popular repositories on GitHub use makefiles). But using makefiles itself is not convenient enough.
Here's, for example, my 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 thing and breaking something
- Write in Slack: "guys, what command do I need to run to deploy to staging?"
This happened to me often and I even started keeping notes with popular commands for each project I work on. This, of course, looked strange - after all, there's a Makefile, why do I need a separate file to work with the same targets?
The Breaking Point
The moment when I decided something had to be done about this - I was onboarded to a new project. My colleague, a senior developer, smart person and excellent engineer. He simply told me: "Look at the makefile, everything is clear there."
I opened it and was horrified - there were about 600 lines, variables were actively used, there was conditional logic and nested dependencies. I looked at this and couldn't understand how to run the project locally.
It seemed like madness to me then. They built this complex build system and the only way to understand it is to read and mentally parse these hundreds of lines of syntax.
I thought about lazygit, which I often use, and wondered why there's nothing similar for working with makefiles?
Then I decided I would make something similar myself. Besides, I had long wanted to make my own TUI application.
Development
I started with a simple idea - show a list of targets, let you choose one, run it and show the output. A pretty basic TUI.
I used Bubble Tea, since I had read about this library from one of the developers I follow on social media. The first version was 200-300 lines in Go and it even worked.
But then I started thinking about all the problems I'd had with Makefiles over the years and I kept adding features.
Dependency Graph
This was the first big feature. Now, when I need to run a target in Make, I understand why and in what order things run, what things can be run in parallel.
Now you can press g on any target and see:
- the complete dependency tree
- execution order
- critical path (the slowest chain determining the overall execution time of the target)
- parallelization opportunities - what can be run with
make -j
This one feature alone made the entire project valuable to me. Now I can see what a target does instead of guessing and trying to figure it out by looking at the makefile.
Another useful thing - this feature catches circular dependencies, which can somehow accidentally end up in a makefile.
Variable Inspector
The next problem - variables. Make's variable system is powerful but absolutely opaque. What does $(LDFLAGS) expand to? Which targets use $(VERSION)? No idea unless you grep the entire file.
But with lazymake you can press v and get a full-featured browser showing all variables, their definitions and usage. This turns 20-minute debugging sessions into 20-second searches.
Safety
I often saw my colleagues, as well as myself, accidentally run destructive commands because we didn't understand what the target does.
So I thought I needed a pattern matching system that would detect dangerous things:
rm -rf / # CRITICAL - requires confirmation
DROP DATABASE # CRITICAL - requires confirmation
git push --force # WARNING - shows alert
terraform destroy # WARNING - context-aware
What's especially useful is that it considers context. For example, rm -rf in a target named clean-test-cache is generally acceptable. But the same command in a target nuke-everything clearly needs confirmation.
Has this saved me from bad consequences? Yes, several times.
Syntax 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
First, parsing makefiles. They're not a programming language, not configuration files. 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.
Another difficulty was real-time output. Bubble Tea helped me get streaming output of commands in TUI.
Another difficulty is cross-platform support. I found it quite difficult to make my 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 my program for your needs).
I want to develop my project further, but for this I want 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. Always happy to hear feedback (or complaints, they're useful too)




Top comments (0)