386 comments

[ 4.4 ms ] story [ 338 ms ] thread
I used to hate makefiles, but really I just hated the way C/C++ make it a manual task to decide what to compile, as opposed to something like Python modules.

Now I love Make, for non-C work.

busybox make is quite restricted, which is why I write for it.
I adore Make. I've written one (or more) for every single task or project I've touched in the last 20 years.

No smarts. It's just a collection of snippets with a few variables. "make run", "make test", "make lint", that kind of thing.

"make recent" = lint then run the most recently modified script.

You could do the same thing with Bash or other shells, but then you get stuck into Developer Land. Things are so much more complicated, without giving extra value. Make is just a DSL saying "files like this, are made into files like that, by running this command or two". That's it.

This is incredibly powerful!

Somehow every make file I’ve encountered in the wild is a lot more than “that’s it”
That just rosy tinted glasses most of the historical users are wearing. It takes time and nerve to admit that you have decades of experience with a footgun that isn’t even trivial to use beyond tutorial/builtin use cases.
> Make is just a DSL saying "files like this, are made into files like that, by running this command or two".

Nicely put.

Decades ago i wrote a testing framework in java where you could specify your tests and their dependent classes using make-like syntax. So you could have a set of test classes which define the "baseline suite", then another layer of test classes which is dependent on the above and only run if the above is successful and so on.

I really do not understand why folks today make everything so complicated. My advise has always been, stick to standard Unix tools and their way of doing things (tested and proven over time) unless you run into something which could absolutely not be done that way. Time is finite/limited and i prefer to spend it on System/Program Design/Modeling/Structure/Patterns etc. which are what is central to problem-solving; everything else is ancillary.

Likewise! I haven't been using them in the past, but at my current position almost every repository has a Makefile.

Running `make test` and knowing it will work, regardless of the stack, language, repo is a huge lifesaver.

> Make is just a DSL saying "files like this, are made into files like that, by running this command or two". That's it.

The problem with make isn’t make - it’s that what makes calling usually doesn’t do that anymore. On my last project we had a makefile that had 4 main commands - build test frontend deploy. Build and test called through to maven, frontend called npm, and deploy called docker + aws.

All of those tools do their own internal state tracking, caching, incrementalness and don’t report what they’ve done, so it’s not possible to write a molecule that says “only deploy if build has been updated” because maven/cargo/dotnet/npm/go don’t expose that information.

Same things can be said about shell scripts in a bin/ folder.
Makefiles are terrible tech. The problem is that they're slightly less bad than most other build system we've come up with, which makes them "useful" in a masochistic way.

Build systems tend to commit one or more of the following sins:

* Too basic: Once you try to build anything beyond a toy, it quickly becomes chaos.

* Too complicated: The upfront required knowledge, bureaucracy, synchronization and boilerplate is ridiculous. The build system itself takes an order of magnitude more data and memory than the build target.

* No standard library (or a substandard one that does things poorly or not at all): You must define everything yourself, leading to 10000 different incompatible implementations of the same build patterns. So now no one can just dive in and know what they're doing.

* Too constricting: The interface wasn't built as a simple layer upon an expert layer. So now as soon as your needs evolve, you have to migrate away.

* Too much magic: The hallmark of a poorly designed system. It doesn't have to be turtles all the way down, but it should be relatively close with few exceptions.

* Cryptic or inconsistent syntax.

> Once you try to build anything beyond a toy, it quickly becomes chaos.

Of course the chaos is not caused by, "very hypotheticaly" let's say, a compiler or maybe a language without modules.

How would you estimate that ? 20%, 40%, or 70%, true ?

Not OP, but its not just that C/C++ lacks modules. I think that is missing the real issue. Any complicated program probably needs a custom developed tool to build it. As a simple example, imagine a program that uses a database - you want to keep the sources as SQL and generate classes from them. Thats a custom build step.

Its just that in some languages and build systems (Node, Maven), we have abstracted this away by calling them plugins and they probably come from the same group that made the library you need.

No such pluginsystem exists, as far as I am aware, for makefiles.

Good luck writing Makefiles for Fortran, OCaml or (whenever they will really, actually work) C++ modules.

There aren't many widely used build systems that can handle such dynamic dependencies without some special "magic" for these, the only one that I know of (with a significant number of users, so not Shake) is Buck 2 (Bazel and all C++ build systems use "special magic", you can't write in user rules).

> Good luck writing Makefiles for OCaml

So what's the problem exactly? https://mmottl.github.io/ocaml-makefile/

Oh look, it even builds a project faster than Dune: https://discuss.ocaml.org/t/dune-build-vs-makefile/11394

> So what's the problem exactly?

They all have the samé problem: that you don't know the name (or even the number) of modules (module files) being generated without reading the source. And as a bonus every compiler uses a sligthly different naming scheme for the generated module file (this is of course no problem for OCaml ;).

As an example (using Fortran). File `test.f90`:

   module first

   contains

    subroutine hello ()
    end subroutine hello

  end module first

  module second

  contains

    subroutine world ()
    end subroutine world

  end module second

`gfortran -c test.f90` yields the following files (2 of them are modules):

  -rw-r--r--    1 roland  staff    221 Sep 21 19:07 first.mod
  -rw-r--r--    1 roland  staff    225 Sep 21 19:07 second.mod
  -rw-r--r--    1 roland  staff    185 Sep 21 19:07 test.f90
  -rw-r--r--    1 roland  staff    672 Sep 21 19:08 test.o
> Good luck writing Makefiles for Fortran, OCaml or (whenever they will really, actually work) C++ modules.

I've successfully written Makefiles for Fortran and they worked with ifort/ifx and gfort. In my experiments I've also made GNU Cobol, GNU Modula-2 and Vishap Oberon fit within the Makefile paradigm without much trouble. You have failed to provide reasons as to why those languages in particular (or more likely any language that's not of C heritage) can't be used with Makefiles. For instance, you can definitely couple OCaml with Makefiles, just use ocamlopt and treat .cmx files as object files, generated beforehand by ocamlopt -c (like you'd do with GCC). I am not familiar with C++ modules and as such I didn't experiment with them.

> I've successfully written Makefiles for Fortran and they worked with ifort/ifx and gfort.

Did the samé (I'm not sure if gfortran did exist at all at the time, I guess it had been g95), plus they worked with Absoft, PGI and Pathscale too (yes, that has been some time ago). And it was a great PITA. Not the least because at the time no Fortran compiler did generate the dependency description, so you either had to parse the Fortran sources by yourself or use makedepf90, which didn't work with all sources.

> You have failed to provide reasons as to why those languages in particular [...] can't be used with Makefiles.

I have obviously badly worded that. I didn't mean it is impossible, just that is a great PITA.

> I am not familiar with C++ modules and as such I didn't experiment with them.

They have the same problem, you don't know the name of the module that is going to be produced.

I've written Makefile for FORTRAN. Dealing with modules added about 6 extra lines. That was one of the more complex rules. Does that count as "special magic"?
If you've got your rule working for arbitrary named (i.e. not the name of the file) modules and submodules and an arbitrary number of modules and submodules generated by a single source file which uses FPP (whatever program that actually is ;) or CPP as preprocessor, then yes. And with "working" I mean adding each module file as a single target which is able to trigger a rebuild of the module. You should be able to get that to work easier with GNU Make 4.3 and later, as that now supports grouped targets - which I have learned elsewhere in this forum. Now the only problém is getting your module dependencies without first compiling all files to generate the modules, as `gfortran -M` (and any other compiler that generates dependency information) AFAIK still doesn't "know" which file produces which module without actually generating the module files.
There are projects that generate files, depend on multiple languages, etc. If you push the job of a build tool to the compiler infrastructure, then why even have a “build tool” in the first place? Make is simply anemic for anything remotely complex, and there are countless better tools that actually solve the problem.
Yeah my biggest problem with make is that the compiler has to generate the header file dependencies. This means starting a C or C++ project with make from scratch is a hard problem and there is no default solution or default file for this other than to just use CMake.
My 2c: Makefiles are excellent tech, just that a lot of people haven't learned to use it properly and use it as it was intended. I'm sure I'll get pushback, that's ok.

- Too basic: At least half of the software I use just uses plain makefiles and maybe a configure script. No autotools. I optionally run ./configure, and then make and make install, and it just works. I definitely wouldn't consider my setup to be a toy by any stretch of the imagination. It's built out of smaller programs that do one thing and one thing well.

- Too complicated: I don't know, I think make and how it works is really easy to understand to me at least. I guess everyone's had different experiences. Not necessarily your case, but I think usually it's because they had bad experiences that they probably blamed make for, when they were trying to build some complex project that either had a bad build setup itself (not make's fault), or without the requisite knowledge.

- No standard library: It's supposed to be tooling agnostic, which is what makes it universally applicable for a very wide range of tools, languages, and use cases. It's viewed as a feature, not a bug.

- Too constricting: I'm not sure what you mean here, it's designed to do one thing and one thing well. The simple layer is the dependency tracking.

- Too much magic: Cryptic or inconsistent syntax: See 'Too complicated'

I agree. Also a lot of the replacements are focused on one language rather than being a generic "do stuff" tool like Make.

The fact that Make can't even do subdirectories sanely is kind of ridiculous.

Does anyone know of anything better than Make? There's Ninja but it's not designed to be written by hand.

> Does anyone know of anything better than Make?

Xmake https://xmake.io/ for C and C++ (I haven't use that for anything serious yet) and Buck 2 https://buck2.build/ if you need a really complex build system. Both of these do caching of build artifacts and can do distributed builds (with less and more complex setup).

Yeah I've been following Buck2. Definitely interesting.

Xmake looks interesting too (even though I hate Lua). I wonder why it isn't more popular - I don't think I've seen a single project use it.

I think just[1] is a good generic "do stuff" tool

[1] https://github.com/casey/just

It's not a build system though. I mean a generic build system like Make, but without some of the terrible design decisions.
Have you taken a look at using Nix as a build system? One thing I don't like about most build systems is the lack of a dependency check, C is most guilty of being the troublemaker here. But anyways, with Nix you can lock in dependencies and handle arbitrary feature flags and platforms as well.

Though it's possible this goes beyond your "just do stuff"

The worst build systems are the ones centered on a particular programming language. Since there's N>>1 programming languages that's N>>1 build systems -- this does not scale, as the cognitive load is prohibitive.

The only general-purpose build system that spans all these languages is `make` or systems that target `make` (e.g., CMake). And this sucks because `make` sucks. And `make` sucks because:

  - it's really difficult to use right
    (think recursive vs. non-recursive make)
  - so many incompatible variations:
     - Unix/POSIX make
     - BSD make
     - GNU make
     - `nmake` (Windows)
  - it's rather ugly
But `make` used right is quite good. We're really lucky to have `make` for the lowest common denominator.
Nix is a general-purpose build system that spans all these languages.
I've never seen anyone use Nix to actually build software; it's a glorified launcher for shell scripts in a sandbox, and typically is used to start the actual build system, such as make/cargo/go build/npm/etc, with known inputs.
Gradle and Bazel are absolutely general purpose build tools that are very widely used in industry.

As a smaller contender, my personal favorite is the Mill build tool (written in Scala), that is basically what a build tool should be, it’s as close to a theoretical perfect as possible. I really advise reading the blog post by its author Li Haoyi: https://www.lihaoyi.com/post/SoWhatsSoSpecialAboutTheMillSca...

Yeah right, I'll install a whole fucking JRE just to build some project. No thanks.
There is not even a JRE anymore - at least read some stuff from this decade before you criticize something so shallowly.
Manure by any other name smells just as bad.
(comment deleted)
One or more, OK that leaves of course lots of room. I would estimate:

(too basic) Makefiles are not. (too complicated) They can be, depends on what you make them to be. (standard library) Well, there is one, there are some builtin functions you can use in the makefile. (too constricting) Haven't noticed that, so I would say no. (too much magic) Hmmm I don't see it. It is very clear what is a target and a dependency and so on. Not so magical. (syntax) Yeah definitely could be better. Even a plain JSON file would be better here.

Yep, terrible:

I will show how Make hits every one of your complaints:

(sarcasm on)

in file hello.c:

  #include <stdio.h>
  int main(int ac, char **av) { printf("hello\n"); return 0; }
How to compile and run this? We need a build system! Download and install GNU Make.

When that step is complete:

Type in

make hello

and its done. Now, run via ./hello

See, Too much magic (didn't even have a makefile or Makefile), no standard library, Too constricting, cryptic, too basic. And, because you had to install Make, too complicated. Hits every one of your objections.

(sarcasm off)

For me, make has two fatal flaws: 1) the lack of a builtin scripting language 2) poor recursion support

The problem with the lack of a scripting language is that I either have to do horrible shell contortions to do simple things like using a temporary file in a recipe or write a standalone script that doesn't live in the Makefile which is needless indirection. This is exacerbated by Make interpreting newlines in the recipe as a separate shell invocation, which I consider a poor design choice. It also requires needless forking for many small tasks which could be done more efficiently in process.

The lack of proper recursion means that I either have to use recursive make, which is largely considered an anti-pattern, or I have to use a flat directory structure.

What Make does have going for it is ubiquity and good performance for small projects. It is the tool most projects should probably start with and only switch to something more advanced when its scalability issues become a genuine problem.

You can use .ONESHELL: to switch to the shell behavior you are wanting.

https://www.gnu.org/software/make/manual/html_node/One-Shell...

That's for GNU make, not POSIX make [1]. For some people, they either won't, or can't, use GNU make.

[1] https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...

Who even implements an alternative implementation to GNU make? FWIW, no one "can't" use GNU make... even Apple uses GNU make (hell: they even ship GNU make, lol).
BSD Make exists. Also, are you saying that no one is developing software under a contract where they aren't allowed to install software on the target machine? I'm also unsure whether GNU Make works on embedded systems.
Technically all of these make targets look for files by the names of the targets. Each one should really be defined as .PHONY.

That said, I used to write makefiles like this all the time, but have since switched to just and justfiles in recent years which make this the default behavior, and is generally simpler to use. Things like parameters are simpler.

https://github.com/casey/just

yeah just is really cool but it's not really commonly installed so that's kind of annoying.

i feel like we're due for some kind of newfangled coreutils distribution that packages up all the most common and useful newfangled utilities (just, ripgrep, and friends) and gets them everywhere you'd want them.

I like `asdf` a lot for this, but I actually don't use it for either of those examples (though it does have plugins for them). Ripgrep is in most package repos by now and all my dev machines have a Rust toolchain installed so I can build and install `just` from source with a quick command.
I think parent meant to have it pre installed in most distros, not just easily installable
Sure, really though I don't understand why installing a single binary which is available from several easy to use package managers somehow becomes an insurmountable barrier for people when `just` is involved. "If it's not already on my system I can't use it" seems like an absurd limitation to place on your projects.
oh i have no problem at all installing stuff in my own environments, i'm all about having cool new tooling -- it just starts to get a little rude to ask others to do so in order to use something you're distributing (and therefore absent coreutils-ii-electric-boogaloo installed everywhere, i'm much more likely to reach for make, unfortunately).
Meanwhile gradle people are like: just run these included gradlew or gradlew.bat files, they'll download the actual gradle from somewhere online, pollute some folders in your home dir, and then execute the build stuff.

I notice just has some pre-built binaries that could be used for the same thing. I find it a little beyond rude what gradle normalized, but hey, it "works", and it removes the source of friction that's present any time you violate the principle of least surprise with your choice of build tool.

I have more than once considered writing a Makefile shim that would check for just, install if needed and proxy all commands to it...
The reason why Gradle needs this junk in the first place is that they aggressively change and deprecate APIs. Tried to build a 6 year old project today and of course nothing works. Gradle wrapper proved pretty useful here. Make, on the other hand, has maintained almost perfect compatibility since it's inception.
I don't understand why Gradle doesn't just provide the wrapper for download. They do provide the checksums [0], so it's not like the wrapper is customized for each repo or anything, but to download it you have to download the full distribution, extract the archive to extract the archive to extract the archive and run Gradle to run Gradle.

The properties file specifying the version and checksum is great, but we shouldn't need millions of identical copies of the binary itself checked into every repo.

[0] https://services.gradle.org/distributions/

Maybe it's different kinds of projects then. For most of what I work with distribution would have nothing to do with the build system in the repo, only people who would ever have to deal with it are other contributors that likely have some environment setup to do regardless.
Please talk to security. My machine is locked down so tight I need a director (or higher) override to get anything not in the default distribution or "blessed" by security installed, and I can't even be the one to install it. May you never have to work at The Enterprise. It sucks!
That sucks for sure, I did work a giant enterprise for a few years and it was plenty painful but not that bad at least. Well maybe it was that bad, because we didn't use make either, everything had to go through Jenkins and nobody bothered with anything for local development beyond an occasional `build.sh` somewhere in the project. Simply push your code when you think it's done and wait 30 minutes to get the next set of linter errors.
At that point wouldn't you "just" download the source and compile locally? Since you presumably could compile stuff. Add a 'bin' folder in your home directory to your PATH and enjoy.
So how do you build any project, which have countless dependencies that all have to be installed?
Like moreutils? yamu? Yet another moreutils?
But I want please, ag and friends! The "problem" with this kind of package is that everybody wants something else. And the chances that they get a part of the default MacOS or Windows install (or even part of the XCode command line tools or Plattform SDK (or whatever that is called now)) is quite small.
I kinda like these make-ish systems, but they all have one problem: Make is already on any Linux and Mac, and is pretty easy to get on Windows as well. (It’s a real pity they don’t include it in the Git Bash!) Just using the lowest common denominator is a big argument for Make IMO.
You have to handle dependencies either way to build a project - what’s one more tiny executable?

This criticism might make sense for some non-vim editor because you might have to ssh into a remote location where you can’t install stuff. But if you should be able to build a project and thus install its required dependencies, then you might as well add one additional word to the install command.

On Windows if you don't use WSL, Cygwin gets you 95% of the way there. I've been using it for decades to develop CLI tools and backbends in Python and a few other languages. You learn the quirks in about 1 month, add some tooling like apt-cyg and map C: to /c and you're off to the races.
Yeah, I liked Cygwin too when I was on Windows myself!
I thought `make` was not in the base install for Ubuntu, Debian or MacOS?
A big mistake Make has is mixing phony and file targets in the same namespace. They should be distinguishable by name, e.g. phony targets start with a : or something.

Too late of course.

In those cases, what author really needs is just[1] not make.

[1] https://just.systems/man/en/

just is just another dependency. make is available everywhere
I rather have one justfile than have one make and another nmake to support Windows
Make is available in a lot of places, but not everywhere. It has to explicitly be installed in containers and in some distributions.
Agree with the sentiment here but I've been rewriting lots of things to use Justfiles instead

https://github.com/casey/just

Avoids lots of weird makefileisims

It's true, although GPT has given Makefiles a second life by helping write them, delaying their demise.
(comment deleted)
It's an interesting phenomenon. ChatGPT and other LLMs have really opened up previously "archaic" tooling like Make and Bash. I've "written" more Bash in the last year than my entire career previously, because LLMs are such good copilots for that.
Oh god bash is archaic now?

I was not prepared to feel this old today.

Agreed but my favorite thing is to take a Makefile and throw it into ChatGPT and have it give me a justfile and seeing it remove all the weird makefile patterns.
Same. Bonus that the same file works on Windows too
The author is not even using the mtime-based dependency tracking. Also the targets are supposed to be PHONY but not marked as such. The author could have replaced it with a shell script that read $1 and matched on it to determine what to do.
Here’s a one-line horror story for you (from a real project I’m working on):

  .PHONY: $(MAKECMDGOALS)
> The author could have replaced it with a shell script that read $1

Sure, but `./build.sh dev` is a bit less obvious than `make dev`.

Another reason to use Make even if you don’t have any non-phony steps is that you can add those later if needed. (I agree that the author should mark {dev,build,deploy} as phony though.)

I got an even better one for you: `./dev.sh`. The author is doing it wrong, and giving Make a bad name.
You’ll also want ./build.sh and ./deploy.sh then. If each is 1-2 commands, I’d argue it’s a waste to use separate files here.

> giving Make a bad name

How so?

That's fine, the separate files are a benefit here. The only annoyance is clogging up the root project folder -- though some people don't seem to care about that. If they got too numerous (the 5 in OP's "more advanced" project would probably not be too numerous), I'd consider putting the scripts in their own folder. I might even call it 'make' just to mess with people. Then my commands are just make/build and make/deploy and so on (.sh unnecessary). But really, in OP's case, I just have no need for some of their simple wrappers. "npm run dev" is two characters longer than "make dev", pointless.
> I’d argue it’s a waste to use separate files here

Fine. Write a `make.sh` that parses the arguments; that would be better.

> How so?

Well, read the comments here. Do you sense that Make is a beloved tool? Most of the complaints are about some details about syntax, and those complaints are completely valid. If you use Make for its intended purpose, then it's still easily well-worth using, despite that. But if you use it as a glorified script, then all you see is the warts, without any upsides. And you then tell all your friends that "Make sux!" Which is a huge shame because Make is awesome.

(comment deleted)
First of all, misusing a tool doesn’t “give it a bad name”, and second of all who cares? A tool isn’t a human being. Make’s feelings aren’t going to be hurt by this article.

The author just shared something they think is cool. That takes guts to show the world, and our critiques should respect that.

Why is this a horror story? Under certain assumptions of how the author intends to use this, this sounds like a sensible way to define a dynamic list of phony targets to me, without having to specify them by hand.

There are many reasonable scenarios why you might want to do this: determining at the point of calling make which targets to force or deactivate for safety, projects with nested or external makefiles not directly under your control, reuse of MAKECMDGOALS throughout the makefile (including propagation to submakefiles), ...

Consider:

    .PHONY: $(MAKECMDGOALS)

    qux:

    foo: qux

    bar: foo
Now make bar and make foo bar will disagree on whether foo is phony, which may or may not be what one wants depending on both what foo and qux do, and how bar depends on foo and qux side effects.

It also very much depends on what the intent is, notably such a "autophony" make foo is very different from make -B foo.

While you're technically correct, what I gathered from their experience is the consistency of usage, between not only their own projects but third-party projects too.

They could make technical improvements to their own Makefiles, sure. But it's more about being able to enter a project and have a consistent experience in "getting started".

> But it's more about being able to enter a project and have a consistent experience in "getting started".

I'd say putting the Makefile content in `package.json` would be more consistent, especially as they are already using Gulp as the build system.

You can't put comments in package.json, JSON should never have been used for something maintained by humans.
We are not arguing whether not declaring phony targets is worse than using comments in `package.json`?

But anyway, comments in a Makefile or `package.json` are not documentation anyway, that's what the `README` or `INSTALL` (or whatever) is there for (in projects like the one the Makefile is written for).

"The author could have replaced it with a shell script that read $1 and matched on it to determine what to do."

Or just with a simple command runner like just.

https://just.systems/

Or just with a simple command which is guaranteed to be on most Linux systems already - make.

Maybe his Makefiles aren't complex, nor they seem to follow all the best practices invented by code gurus in sandals, but it works and, what's important, it works for him.

> but it works

That it what a lot of SW developers forget: your code might be the best in the world, but , if someone is not able to build it, it is useless.

I remember countless times me and forum fellas debugging makefiles written under developers’ assumptions about systems. That is also what lots of developers forget or simply aren’t aware of.

Make isn’t a silver bullet for builds. It isn’t even a bullet. Most software gets built from scratch and make’s deps graph makes little to zero sense in this mode. Make is a quirky poor dev tool footgun, jack of all trades master of none.

Make leaves the actual hard problem behind — setting up the environment correctly. A README file is much more useful.
There was a time when people would have said the same about make. The shell is the simple command that is guaranteed to be on all Unix systems from the get go. Make is the new kid on the block.

If you just want to run commands in the order written down, don't need the topological sorting feature of make and value ubiquity then a shell script is the answer.

If you are not stuck in the past and you truly live by the UNIX philosophy of doing one thing and doing it well, a command runner is the answer.

The command runner avoids the ton of foot guns both shell scripts (no matter which flavor) and make files have. just also brings a couple of features out of the box that would be very tedious and error prone that replicate in make and shell scripts.

Right but writing dependency management (of targets, not package management) in shell seems like a nightmare compared to just leveraging make. Why complicate things? It's dead simple to debug, the interface is dead simple, what's the downside?
In my PS script solution, I just added a clean option+command.

I rewrote my makefile in PS and don't miss anything from make and have no regrets, as it is simpler now.

Right, but the original point which started the thread is that "The author is not even using the mtime-based dependency tracking", in which case a plain shell script is very much a viable alternative to make.

I don't particularly mind this use of make, but as an article on make it fails to exemplify what I think is its main purpose.

I don't think that really matters. Sometimes even basic shell scripts are better modeled with a makefile.
I believe you to be correct. I think it's important that one uses the right tool for the job, regardless of whether or not it's widely adopted or supported.
>Make is the new kid on the block.

Make is from 1976. I don't think you can legitimately refer to it as that.

You could around 1976. Who's ever going to need make.
The first UNIX was announced outside of Bell Labs in 1973. In 1976, pretty much every tool was “the new kid on the block”.
Phew, I was so worried. So for 48 years out of Unix' 53 years of existence (90% of that time), make hasn't been the new kid on the block. Oh, let alone the fact that we're talking about stuff from 48 years ago, when their "screen" was a paper printout of the output.
I have no idea what that comment is supposed to mean.
That your comment was hyper-pedantic.
Who develops just? Will it be around in 5 years? Will it be ad supported? Will the developer sell my data? Etc.

I don't have any of those concerns with GNU Make.

Software with small scopes can be finished. It doesn’t sound too complicated to just push a new bug fix each year, by anyone. If anything, make is probably a significantly more complex codebase due to all the hacks it accumulated over the years, as a result of a dumb model it started with.
> There was a time when people would have said the same about make. The shell is the simple command that is guaranteed to be on all Unix systems from the get go.

That would've been a pretty short window of time since make first came out (according to wikipedia) in 1976.

> There was a time when people would have said the same about make. The shell is the simple command that is guaranteed to be on all Unix systems from the get go. Make is the new kid on the block.

I seem to recall it being praised very highly at the time as a great tool that saved many billable expensive CPU minutes and made a developer's job so much easier.

> it works and, what's important, it works for him.

Until it doesn't. And then you really have to learn about PHONY targets, why and when there must be a tab and not spaces - good luck with an editor that doesn't treat Makefiles special and is configured to convert tabs to spaces.

But those are things that he’ll learn about as he keeps using make. And why does it matter that some editors don’t know about makefiles? The one he is using handles them just fine so what’s the problem?
> And why does it matter that some editors don’t know about makefiles?

Because it isn't fun checking if the whitespace at the beginning of the line is a tab or spaces. And as said, you must know when to use tabs and/or spaces in rules.

For doing such a simple thing as calling some commands, Make has way too many subtle footguns which _will_ bite somebody, someday. The problem (that's not a problém at all, that's a reason to celebrate!) is that most JS devs and users aren't used to Make, compared to e.g. C programmers. To rephrase: as someone writing C, you have to use something like a Makefile, as anything else (like scripts) gets unreadable and -usable quite fast. But if you can get away with a less complex solution, you should really use that instead of Make.

> Because it isn't fun checking if the whitespace at the beginning of the line is a tab or spaces. And as said, you must know when to use tabs and/or spaces in rules.

that's why https://editorconfig.org/ exists, so that neither you nor your teammates have to think about these things

> neither you nor your teammates have to think about these things

You're better off using a Makefile linter. But you must know about the problem before being able to solve it. And error messages like

   Makefile:2: *** missing separator.  Stop.
aren't the most helpful.
You'll find the issue within 2 minutes of googling the error message.
And as said, you must know when to use tabs and/or spaces in rules

Is that Stockholm syndrome? Or an appeal to history/authority in action? What makes people believe that this is even remotely reasonable.

inb kids these days, I started in the '90s and wrote my share of makefiles. Tolerating make only made sense until 2010-ish, then both hw and sw advances rendered it useless.

Edit: just realized my reply to a wrong person, but let it stay here

I genuinely don’t understand why that matters. The fact that there exists bad editors that don’t support my workflow shouldn’t prevent me from using the tools that I like and am comfortable with. I use editors that don’t screw up makefiles so what’s the problem? If I take your argument to the absolutely absurd logical extreme, I shouldn’t use lower case letters because some character encodings don’t support them.
Because forcing devs to use tools for one specific format makes a bunch of unhappy devs. Especially if its just forcing them to use make.
Or just use a simple command which ALL Unix system have: sh.

If you're using make a glorified task runner, why don't you just create a scripts/ directory with shell scripts to do whatever you want. This is simpler, cleaner and works everywhere.

Make doesn't really add anything. I get the feeling that using make this way is an aesthetic preference that has somehow developed with time into a "this is the one true Unix way" cargo cult.

Why not just use make? I am constantly confused by people reinventing the wheel with new syntax and little benefit
When step one of using a tool is to disable the tools primary benefit (everything is phone) you’re reaching for the wrong tool. Like it or lump it, make deploy is much neater than docker build -t foo . && docker tag foo $item && docker login && docker push && helm apply

I wish there was a flag for make which set it to be a command runner by default for the current makefile.

Make's clear benefit is laying out a process as a series of dependencies.
Why not just use shell scripts? Why additional complexity?
Shell scripts are additional complexity? I'm not sure what you mean. Adding structure reduces complexity.
No I mean Makefiles, how author uses them, are additional complexity over shell scripts.

The whole difference of Makefiles, their original idea was to not re-compile a target _file_ if it's already there. So if you need .PHONY -- you're using Makefiles wrongly.

Honestly, it is because make is not written in Rust ;-)

This is a sect and global trend to reinvent and re-implement the wheel with the Rust :D

Please help me understand why this thing exists. Like, no snark, I like using the proper tool for a job -- when would I look at the project and think "this is something that is better done with 'just' tool". Instead of readme.txt and a folder with scripts
I can count on one hand the number of times a simple script or make worked out of the box. Sure, part of the reason is dependencies, but then I might as well use a build tool that is actually doing what a build tool should. Makefiles/bash scripts are hacks, and I don’t get this strange Stockholm syndrome UNIX-people have for them.
It’s gotten some syntactic sugar recently that’s made it pretty nice. Specifically, I’m thinking of its OS-specific sections: you can do something like

    [macos]
    # do a thing
And without any other checks, that section will only ever run on Mac. Yes, of course you can replicate this in Make, but it isn’t nearly that easy.
The strengths of make, in this context where it's been coaxed into serving as a task runner for small projects, are:

1) It's already installed practically everywhere

2) It reduces your cognitive load for all sorts of tasks down to just remembering one verb which you can reuse across multiple projects, even if the implementation ends up differing a bit

3) In conjunction with the similarly ubiquitous SSH and git, you have everything you need to apply the basic principles of DevOps automation and IaC

There's something special about waking up one day with an idea, and being able to create a fresh git repository where the first commit is the Makefile you've had in your back pocket for years that scripts everything from environment setup to deployment to test automation to code reviews.

There's zero effort beyond just copying your single file "cookbook" into that new repo.

> It's already installed practically everywhere

This always comes up, and is a sad chicken/egg problem.

We can all somehow agree that Make mostly sucks, but OS maintainers aren’t interested in providing a default alternative, due to choice overload or something.

This entire article and discussion is about why people like make.
No, a significant amount of the comments here are about how Make sucks.
CMake runs make, ninja runs make.
> It's already installed practically everywhere

Well, except Windows. But nobody uses that right?

nmake comes with msvc
Yeah, “just install msvc”. It’s easier to install msys2 when you’re that desperate. At least the tool name will be “make” out of box.
1. MSVC is not installed by default on Windows.

2. nmake is with POSIX make, let alone GNU make. It doesn't even support .PHONY target, which is what you need to replace Just with make.

3. Installing Just with WinGet is simpler, faster and takes probably only 1% of the space of installing Visual Studio for nmake.

> 1. MSVC is not installed by default on Windows.

Neither is Make or GCC on Unix.

Nothing is installed out of the box on windows, but anyone with a functioning development environment for a large number of programming languages will have installed wsl, msys or git bash along the way and have make installed as part of it.
I don't think git bash comes with a make.
I always find it a questionable choice, when someone, who wants to be a professional software engineer, uses Windows. If it is a choice at all. Of course they could also be working at some job, where there are silly ideas like everyone having to use Windows or so.

If it is a choice, it sort of shows an "I do not care" attitude to software development, or being seriously uninformed about proprietary software. Usually those are the types, for whom software engineering is merely a 9 to 5 job, and not a craft they take pride in. An activity they do not really care about at other times. Which is OK to do, not a crime. If I were hiring though, I would rather look for passionate software engineers/devs, who know a lot of stuff from tinkering and exploration. Ultimately using Windows means you are not truly in control of your productive system and are at the whim of MS. It is a risk no self-respecting software engineer should take.

To clarify, that is not to say, that there cannot be craftsmanship people using Windows. It is just way less likely. More likely they are "enterprise" software people. Even the choice to explore and use a GNU/Linux distribution betrays some kind of mentality of exploration. Wanting to know what is out there. Learning a new thing. Adapting it to ones needs. This kind of learning mindset in the long term is what sets engineers apart from others.

So I would claim, that not many good software engineers use Windows to be productive. If they have to, they will likely install some VM or some means of making things work as if they were on a GNU/Linux system. WSL or whatever, to circumvent the limitations and annoyances of a Windows system.

This is a silly take, have you heard about game developers?

If there's craftmanship anywhere, its in game development, and they surely don't want to spend all their time working on a platform without proper tooling that their end users overwhelmingly do not use.

The choice of OS has nothing to do with craftmanship or "exploration". I "explored" linux many times and am not using it currently.

In fact, I'm happy to argue that most developers that care so much about the choice of OS that they are uninterested in using another one (and do not work in OS development) are probably somehow stuck in their ways and uninterested in exploration themselves.

Taken even further, currently the only important OS is the browser, and nobody cares who launches it.

I hope you are not somehow in charge of hiring.

Game developers are such an exception ... And I left room for exceptions in my explanation.

But of course, if you want all the IP to leak via MS spyware phoning home, sure, let your devs work on Windows machines.

I think you're generalizing from your position inside some bubble, I am not sure which. Equivalently I could imagine game developers generalizing that linux people are terminal fetishists and have no interest in getting stuff done, who would rather customize (a.k.a fight) their OS for days on end than provide end user value, and through their idealistic, puristic and dogmatic approach to FOSS they feel safe but are meanwhile vulnerable to exploits of bad actors through supply chain attacks.

I think neither take is true, nor does it hold much value to claim it, unless your aim is to divide developers into arbitrary adverse tribes.

[flagged]
> You think you're superior because you use something niche.

Ah? Interesting! Tell me more about what I think!

> Some people just don't want to have to worry about laptops melting in their bags...

That is funny, because this is what happens with Windows installed, due to nonsensical system default settings. I've almost had that happen with Windows 7 once, due to an idiotic default setting regarding when the system is to wake up from hibernation, so that it turned back on while in my bag, inside an inner protection bag, nicely accumulating the heat.

Recently there was a whole long thread of people describing such problems with Windows machines. Here on HN.

Perhaps you are the one, who is not properly informed? Or are all those experiences with Windows machines somehow null and void, invalid?

why do you need a "command runner"? Have you heard of bash functions? Or... make? The thing is too simple to justify installing another tool, however nifty it is.
`just` is great and I use it all the time.

* All commands in one place, view them all with `just --list`

* Stupid-simple format

* Small standalone binary

* Configurable (with arguments, environment variables etc) but not _too_ configurable

When I see a git repo with a Makefile, I'm filled with dread. When I see a repo with a Justfile, I get warm fuzzies.

Some people say it just doesn't do enough to justify existing. These people are just wrong.

To me, just is make without features I don't need. There is not a lot of benefit for me, but there is a lot of benefit for other people who need to learn the repo and have no knowledge of either make or just.

Another benefit is that justfile just cannot get too complex and tangled. Simplicity at its finest.

We all were beginners at one time or another. And if you want to learn a tool, it helps to actually use it, even if your greenhorn usage is less than perfect. You can make incremental improvements as you learn, like we all do.
The author also seems not to have discovered 'make configure' and the horrors of the automake/autoconf toolset and the m4 macro language.
Sure but these are completely orthogonal to make. Might as well complain about gcc.

If anything, it's an argument for making better use of make's own features for configuration in the first place.

what are those horrors about?
Have you tried writing (or even just reading) a configure.ac script?
sure, and there's lots of documentation around it, that's why I asked for examples of the horrors.
That's the beauty of make and shell, it's follows the UNIX principle of being simple and doing one thing and one thing well. People want it to do many other things, like be a scripting language, a dependency tracker, etc, so they're willing to pull in bloatware. New isn't necessarily better. Autoconf and automake isn't make.
Is this satire? Being a scripting language and tracking dependencies are primary features of shells and Make, respectively.
Not satire, sorry I didn't clarify. They want make to have a builtin scripting language rather than using shell scripts, and a dependency tracking system that more complex and less tooling agnostic rather than leveraging the appropriate tool (like `npm ci`).
None of them are simple, they are chock full of hacks upon hacks, “fixing” their own idiocies, and by extension, none of them are doing their one thing well. Especially bash scripts, they should be left behind..
Can you be more specific what you view as hacks or idiocies? Besides the criticism of .PHONY targets, which I don't think is a hack nor particularly ugly. When I mean shell I'm referring to a family of shell languages that are used run commands, change directories, etc. Fish is a shell, for example. Babashka can be considered to be a shell. It doesn't even need to be those, someone using make could use python or javascript for the scripting part if it works better than a shell language.
Yes, the UNIX principle of being simple and doing one thing and one thing well.

Make does dependency tracking relatively well (for 1976). But if you just want to run some commands, your shell already does that just as well, without any of the caveats that apply to make.

There isn’t even a need for a shell script. The author is already invoking three separate tools, each of which has a mechanism for invoking custom commands.
What if he wants to have a uniform environment across projects and some aren't JavaScript?
Dear God! There is something that isn't javascript? Do you know you can it use on the server and the browser!??!
Then he could just use Gulp, but I have no skin in this absurd game.
Gulp? That JS tool that was last cool in 2018? After it which it was replaced with Grunt, which stopped being cool in 2020? And that was replaced with Webpack, ESBuild, Rome, Bun...

Why would anyone voluntarily subject themselves to that kind of insanity? :-))

Better to just use the hacksaw that is Make than all these Rube Goldberg contraptions :-)

I don’t personally care about the JS ecosystem. But OP is already using Gulp. He’s then calling Gulp from npm run. He’s then calling npm run from make. Adding make into the mix is solving nothing here. If you’re saying he should use make properly I agree!
Makefiles are ancient at this point, and work OK for what they were intended for.

The problem is when people try to expand them to everything, and they end up some arcane file full of junk nobody understands. What is a phony target anyways? And even if you understand the concept, realize it makes no sense to passerby.

> What is a phony target anyways? And even if you understand the concept, realize it makes no sense to passerby.

This is an entry level bar to the profession, one level below that will be an insult to anyone who calls themselves an engineer.

What? You sound like someone who's complaining about all the "pointless" semi-colons in C++ code.

If you can't be bothered to learn the basics of the language, your criticism isn't going to be worth much.

> You sound like someone who's complaining about all the "pointless" semi-colons in C++ code

Not sure how the two relate at all. One is a statement terminator, the other a rather complex system.

> If you can't be bothered to learn the basics of the language, your criticism isn't going to be worth much.

That's the entire point! Remember my complaint is not against Make as a build tool, but Make as a do everything(including deploy) system. There are countless options in the space. With most, you can generally open a config file or even bash script and figure out what's going on. Not so with Make, without learning as you call it. It's nonobvious. And I say that as someone who -does- understand it.

I love makefiles as well and do something similar to OP: for every repo I contribute to, especially if it's not one of mine, I'll create an uncommitted makefile to track all the shell commands that add up to my workflow within that codebase.

The ability to create dependencies between targets is the cherry on top, but the main value is just the ability to create a cheat sheet of shell snippets accessible via `make <target>` from the root of the repo.

Such a makefile is always (1) version controlled as a secret github gist (though, as personal rule, i never hardcode secrets into it), (2) committed & pushed on `make` (3) git ignored via `.git/info/exclude`. This has worked quite well for me.

One downside with this approach is that the best syntax for passing parameters down to the target from the shell is to use environment variables, which is a little awkward. `NAME=value make target` is less pleasant than `make target --name=value` would have been.

Take out the dashes and that's a supported syntax for overriding variables:

  NAME := foo
  test:
      echo "name is $(NAME)"


  $ make test
  echo name is foo
  name is foo
  $ make test NAME=bar
  echo name is bar
  name is bar
Makefile enthusiasts may enjoy this audio episode on Makefile:

https://podcasters.spotify.com/pod/show/podgenai/episodes/Ma...

I don't hate the idea of AI generated podcasts (quite the opposite), but I can't shake that I can't trust what they're saying. High quality podcasts have fact checking and a reputation to uphold. On this, they just slap a disclaimer on that it might contain inaccuracies.

Will give it a listen tho!

It is actually only the good AI generated material that will even have that kind of disclaimer. The ones that want you believe in their lies will not even have any disclaimer.

Having said this, after having listened to 100+ episodes of this podcast, I have yet to spot a single lie, although they may still exist with a very small probability. The reason why any fact checking step has not been added to this podcast is because it hasn't proven to be too necessary in the first place.

I have a different project "newssurvey" which uses external data and has citations for claims, although I still need to add an extra citation verification step for it. Perhaps in time I will.

The author is confused about what Make is for, and frankly this kind of thing is why Make gets a bad rap. Make is for traversing a graph to determine what should be built, and how to parallelize the steps. Here he doesn't have a graph or any dependencies defined at all. He does have a weird scripting language though, which more or less nobody likes.
I instictively know that makefiles would make a lot of things easier, but I've never found the right tutorial that would help me understand them. I'm not a 'C' programmer, and so much seems weighted to the idea that you're generating object files and linking and producing a.out.

Any good tutorials or resources for learning that show a broader applicability for makefiles?

unironically, the info manual. It's great.

the philosophical "c" centredness is true, but doesn't get in the way of using other languages. (there's things like indirect rules for compiling .c files auromatically for instance, but even this can be turned off)

> and so much seems weighted to the idea that you're generating object files and linking and producing a.out.

Well that's kind of wrong (it's used for that but that's an extremely limited viewpoint). Here's a short introduction to get started:

  foo: bar
      baz
This means "whenever bar has been updated, create or recreate foo by running baz". You run it with "make foo", and make will run "baz" by default in "sh".

Here's an example in a totally different context:

  .PHONY: build
  build: node_modules

  node_modules: package.json yarn.lock
      yarn install
      touch node_modules
With this, when you run "make build", it'll only do "yarn install" if node_modules's last-modified timestamp is older than both package.json and yarn.lock. The touch is there to mark it updated for the next time you run "make build", so it knows it doesn't have to do anything. Normally you wouldn't have to do that but make assumes the commands given will update the file, and "yarn install" won't necessarily update the directory's last-modified time.

This example isn't terribly useful because "yarn install" is fast and doesn't do anything itself when it's up-to-date, but it should give ideas about how flexible make actually is.

One of the big criticisms of how people use "make", and why people recommend things like "just" instead, is they don't bother to use that functionality (or any of the piles of stuff built on top of it like pattern matching) and would have just done:

  build:
      yarn install
...which appears to be how OP uses it.
There is much misunderstanding about Makefiles, what they are, and what they are not. Make is not a "programming language build system" as some would imply, but rather a recipe builder. With Make you provide the file you want built, the shell commands to build it, and any files the build depends on.

Here's a simple example to get you started. Create a file named "Makefile" with the following text and an empty file alongside it named foo.txt.

    bar.txt: foo.txt
        cp foo.txt bar.txt
When you run the "make" command in your shell it will check if the "bar.txt" file exists. If it does not exist OR if "foo.txt" has a newer timestamp, then it will rebuild "bar.txt" by executing the tab indented shell commands underneath. In this case, the only shell command used is the 'cp' command. In the linked article the author invokes npm, bundler, and netlify.

When people use Make to compile their C code they are simply invoking the C compiler just like they would any other shell command. You might have seen something in a Makefile that looks like this:

    foobar.o: foobar.c
        gcc -c foobar.c
This is just saying: "The output file 'foobar.o' depends on the input file 'foobar.c' and to build 'foobar.o' run the shell command 'gcc -c foobar.c'" which is conceptually the same as my previous example were we built "bar.txt". Since explicitly listing every .o and .c file in a Makefile is tedious, many authors opt for wildcards.

I hope this helped!

> When you run the "make" command in your shell it will check if the "bar.txt" file exists.

I got to this point and ran into an error: Makefile:2: *** missing separator. Stop.

Ok I'm just giving you a hard time, and you mention right after the existence of "tab indented" so whatever. Still it's one of the things I detest on an aesthetic level about make, even if my editor has special syntax support for makefiles to handle this archaic requirement of actual tabs without me ever having to worry about it in practice.

> Still it's one of the things I detest on an aesthetic level about make

There's a little-known variable called .RECIPEPREFIX that lets you switch from tabs to anything else. Probably a bad idea to use it in anything shared with anyone else.

I have a suspicion that a lot of people who rant about makefiles using tabs also praise python for the brilliance of using whitespace for scoping. Or who love yaml for its indented block structure.

Nah, who am I kidding ... no one loves yaml, it's just better than most of the alternatives.

I quite like python's whitespace requirements, but notably it doesn't care what your stance is on tabs vs spaces, or how many spaces, so long as you're consistent in a block. Never liked yaml, though, I don't think it's better than any of the alternatives.
For what I use YAML for, there are no alternatives for it except obscure formats like HCL.
(comment deleted)
make as a task runner is not too bad, but there are better alternatives today like just (as others have commented).

make as a build system is ok until you hit the warts.

- make/Makefiles aren't standardized, which is why automake exists. So now you're not writing Makefiles, but templates and generating the actual makefile. This doesn't matter if you own the whole toolchain, but most people don't, so this is what some folks do to guarantee their Makefiles are portable.

- make cannot do any kind of dependency resolution, it assumes that whatever you need is right there. That leads to configure scripts, which like makefiles, are not standard, so you use autoconf/autoreconf to generate the configure script that runs before you can even run a target with make.

- make (and adjacent tools like automake/autoconf/autorefconf) use mtime to determine if inputs are out of date. You can get into situations where building anything is impossible because inputs are out of date and running autoconf/autoreconf/automake/configure leaves them permanently out of date. (fwiw, many build systems can get away with using mtime if they can do proper dependency tracking)

All in all the fundamental design flaw with make is that it's built with the unix philosophy in mind: do one thing well, which is "rebuild targets if their inputs are out of date." However this is an extremely limited tool and modern build systems have to do a lot of work on top to make it useful as more than a basic task runner.

> make cannot do any kind of dependency resolution, it assumes that whatever you need is right there. That leads to configure scripts, which like makefiles, are not standard

The ancient convention there is "make configure", which sets up whatever "make [build]" needs.

> make cannot do any kind of dependency resolution

    dependency:
        ...
    
    target: dependency
        ...
Tracking build targets is not dependency management except with handwaving
the only thing I really miss in make is the ability to resolve mtime as something other than mtime. So I resort to using touchfiles which are gross but still work better than a lot of other things (I'm looking at you, docker build caching).
> make cannot do any kind of dependency resolution

Ignorant's question: isn't dependency resolution the core of make? What are you referring to here?

Please tell me, how exactly does make resolve header file dependencies of a .c or .cpp file?
Do you really expect an answer from a self-defined "ignorant"? Or is this a rhetoric question and you are hiding an answer inside it? If so I don't get it. Wouldn't it better to explain it in plain words?
It normally works in conjunction with GCC’s “-MMD -MP” arguments which provide .d files which then get included back into the Makefile with something like “-include $(OBJS:%.o=%.d)”.

It doesn’t directly interpret any source file though, if that’s what you mean.

I'm referring to package management. Modern build systems all have some way of doing package management directly or interfacing with package managers instead of just shelling out to them, which you would have to do with make.
For me, make is definitely in the “stop worrying and learn to love it” bin. It will outlive all of us, so accept it and move on.
Why use Make if they already use Gulp? Why not put that in `package.json`'s `script` stanza? And never ever use Make as a script runner without declaring the targets `.PHONY`, there will be a day when somebody has a directory (or file) named `build` or `dev` in their project root.
Because my Rust, Python, bash, Perl, etc. projects aren't impressed with the `package.json` file I wave at them. Especially before I've installed npm or any other JS runtime on my system (let alone Gulp).

As the article said, it's generally installed everywhere as soon as you install any dev-related stuff. So is bash, but it's a little clunkier for very basic usage. (`if [[ $1 = build ]]; then...`)

I do this a fair amount as well. It's really just a way of documenting the configuration and idiosyncratic commands in one place, which happens to be executable. I will happily create (uncommitted) Makefiles with hardcoded paths and keys and things, since otherwise that information would go in my ~/NOTES file and there's too much in there already. My default target tends to echo out things that I told myself I needed to remember when coming back to the project.

As soon as I notice I'm reaching for anything more than `.PHONY` targets and dead-simple filename dependencies, I stop and do the real build work in something else (callable via a make target, of course!) I know how to do complicated stuff with make, which means that I know I will do it wrong. Repeatedly. Or possibly eventually do it right, but then have to maintain the resulting fire-breathing hairball.

(But to those complaining about not marking all the non-file targets `.PHONY`: lighten up. If the correctness matters so much that you're going to be messed up by a file named `all` or `build` or whatever, you've probably already gone too far down the rabbit hole and should switch to something else.)

> If the correctness matters so much that you're going to be messed up by a file named `all` or `build`...

That's not the problém. _We_ know what "dev is already up to dáte" means, but chances are people who don't know about `.PHONY` don't.

> Even on my MacBook, I don't remember installing it explicitly.

It's there, but last I checked, it only supports building with the Apple-supplied toolchains (Xcode). If you want to use anything else, do yourself a favor and install gmake(1) from MacPorts.

For C/C++ projects that are simple enough, I can reuse the same Makefile with only minor changes, and it works on both Linux and macOS, with support for sanitizers and valgrind(1). CLion eats it up like it were candy, although the gathering of sources is automated with find(1), and even respects my `--sysroot` setting.

CMake has good cross-platform capabilities, but GNU make gives me more control over the actual compiler and linker invocations, and its CLI is less horrible than that of CMake.

The version of make which ships with Xcode is old (as Apple has stopped updating GPL software in general, and hopefully new builds of GNU make are GPL3 anyway), but it is otherwise not in any way somehow tied to Xcode's toolchain: it is a normal copy of GNU make 3.81 which runs commands the same as any other copy of make (as you provide them, and using the path).
Yep, could have been just bugs that I misinterpreted. Anyway, the stuff I want to do works with current gmake, but not with `/usr/bin/make` on macOS.
I know this is not your main point, and you touched briefly on it but, cmake is absolutely terrible.

It is supposed to make the building process more straight forward and painless but what it brings to the table is

* Weird bugs: In one version of cmake would return the python version that I had to be 0.16 (????) and the only fix was to update the cmake version

* Messy Structure: Under the guise of giving freedom to the person designing their build with no real enforcement of any rules, in any way, people can write their own absolute craptastic version of cmake scripts which really really is going to make you lose your mind (mind you some people leverage it but it is the exception not the rule)

* Opaque process: Due to the fact that the structure can be messy you are never really quite sure of what it is doing and which compile flags are effectively fed to the compiler as well as sometimes totally ignoring the library you explicitly ask it to use and trying to find another one...

OK maybe this is a bit superlative, but unfortunately for me, reflects well my experience with using cmake and feels like an extra item between me and getting things working painlessly... But I might have been doing it wrong.

I understand this perspective if considering CMake in the past (i.e. pre 3.0 or so), but in my experience CMake today is much nicer (albeit definitely still not without its flaws). Specially for the points you mention:

* “Weird bugs”: I suspect CMake wasn’t finding the version of Python you wanted because your find_package command was just finding a different version first. This is much easier to debug today by using —-debug-find-pkg=Python, which will print all the places it searches Python for and what it finds. You can then modify your find_package invocation as appropriate to find the Python you really want to use.

* Messy structure: yes, unfortunately I’ve also seen my fair share of nightmare-inducing CMake files.

* Opaque process: here I actually quite disagree. In my experience I’ve found it super easy to modify things like compiler flags (just use target_compile_options, or add_compile_options for directory wide options). And what made a big difference was using Ninja as the generator (I also use Ninja on Windows), which makes it super easy to view the final compiler commands that will actually be invoked. CMake is essentially a compiler that emits Ninja on the backend, and several times it’s been invaluable to confirm in the generated Ninja code what is actually being invoked.

CMake is definitely not perfect, but it’s much better than what it used to be! It’s ultimately a perfect match for C++ (both extremely powerful, configurable, hamstrung with decades of backwards compatibility, terrible ergonomics, etc.).