Live data from Hacker News

An opinionated approach to GNU Make

tech.davis-hansson.com

21–30 of 195 posts

Re: An opinionated approach to GNU Make

#21
> Make leans heavily on the shell, and in the shell spaces matter. Hence, the default behavior in Make of using tabs forces you to mix tabs and spaces, and that leads to readability issues.

I have written a great many makefiles, simple and complex. I can’t recall a single time I’ve needed to mix tabs and spaces in one (though I have had to mix them multiple times in both YAML and HTML).

(As for anything like accidental mixing, for my part I have a sanely-configured text editor and so don’t need to worry about anything silly like tabs being turned into spaces. Tabs are superior to spaces anyway. ⸺But I do use spaces for Rust and Python where that is customary, I’m not completely antisocial.)

> .ONESHELL ensures each Make recipe is ran as one single shell session, rather than one new shell per line. This both - in my opinion - is more intuitive, and it lets you do things like loops, variable assignments and so on in bash.

.ONESHELL also means that your makefile will behave differently from how anyone that’s familiar with makefiles will expect it to. But I guess this does explain why you went enabling strict mode, since you’ve basically turned off the near-equivalent default functionality from Make.

Note also that you can do loops and such already—you just need to use line continuations (put backslashes at the end of each line, which Make will consume).

Yeah, the default behaviour is idiosyncratic and will lead to surprises in the unwary (though they’ll normally observe it immediately, when the cd is ineffective on the next line, or when the if/for causes a syntax error), but I think Make has generally become niche enough that I’d prefer to pander to people that know Make than normal people. :-)

> .DELETE_ON_ERROR

Two-edged sword: it also means you can’t inspect what went wrong by looking at the file. You’re also making the very dubious assumption that merely deleting this one file will fix everything. A few times when I’ve known something to be fallible but want to be able to inspect what it created, I’ve put in something like a `… || { touch --date=@0 $@; exit 1; }` suffix so it still fails, but first zeroes its mtime so that subsequent runs will see that it’s out of date, though the file still exists.

I’m not saying it’s wrong or a bad idea, just that it’s worth considering the implications fully rather than blindly applying it.

Re: An opinionated approach to GNU Make

#22
Wow. This is atrocious.

> You really just need the .RECIPEPREFIX = >

Now I can't copy & paste a block anymore (into the shell, to run it), and all my editor indentation settings are broken.

> SHELL := bash

And the Makefile is now non-portable.

> .SHELLFLAGS := -eu -o pipefail -c

If this matters, it's likely you're wedging too complicated things into one recipe. But less bad than the other suggestions.

> .ONESHELL

Funnily enough using this is the primary reason the previous item becomes important. The subtly changed behavior also turns multi-line recipes into a giant footgun if you end up with a non-GNU make.

(skipping a few that are not as bad)

> out/image-id: $(shell find src -type f)

Might be OK in a single rule. Otherwise, it's calling find more... and more...

> Sentinel files

Actual good practice to end it on.

Re: An opinionated approach to GNU Make

#23
post #22

Wow. This is atrocious. > You really just need the .RECIPEPREFIX = > Now I can't copy & paste a block anymore (into the shell, to run it), and all my editor indentation settings are broken. > SHELL := bash And the Makefile is now non-portable. > .SHELLFLAGS := -eu -o pipefail -c If this matters, it's likely you're wedging too complicated things into one recipe. But less bad than the other suggestions. > .ONESHELL Fun…

> And the Makefile is now non-portable.

The article calls out GNU Make, so almost everything else in there is also non-portable.

Re: An opinionated approach to GNU Make

#24
post #6

> .RECIPEPREFIX = > That seems like a terrible idea. You change the basic syntax of the entire Makefile, forcing anybody reading it to get used to your custom indentation, where almost every line starts with an unnecessary >.

Also, the justification seems to be > And you will never again pull your hair out because some editor swapped a tab for four spaces and made Make do insane things. Which... I guess that would be annoying, but maybe fix your horribly broken editor rather than mutilating the Makefile?

Are you going to fix everyone else’s editor too? Software is a multiplayer game and eliminating a whole class of error that hinges on someone not noticing the difference between invisible characters in a diff is a huge win.

Re: An opinionated approach to GNU Make

#25
This article has some questionable advice imo.

    SHELL := bash
Bash is a much slower shell than Dash, which is why Debian and friends don't use it as /bin/sh. .ONESHELL mitigates the speed problem, but you could also just use the default shell and leave ONESHELL turned off.

    Use bash strict mode
    ....
    .SHELLFLAGS := -eu -o pipefail -c
I wish people would stop cargo-culting the so-called "strict mode".

The -e flag is only useful because the author likes .ONESHELL mode. If you leave ONESHELL turned off, then you don't need it.

The -u flag is useful sometimes, depending on coding style. I use it on complex scripts. Individual Makefile recipes maybe don't want that much complexity though. Also the -u flag makes the shell's variable-handling behavior inconsistent with Make's.

The pipefail option is Bash-specific, and only works because the author likes to set SHELL to Bash in their Makefiles. It's also not a good default in my opinion. There are times when it's useful, and other times when it's the opposite of what you want. Just depends on the pipeline that you're writing.

Re: An opinionated approach to GNU Make

#26
post #25

This article has some questionable advice imo. SHELL := bash Bash is a much slower shell than Dash, which is why Debian and friends don't use it as /bin/sh. .ONESHELL mitigates the speed problem, but you could also just use the default shell and leave ONESHELL turned off. Use bash strict mode .... .SHELLFLAGS := -eu -o pipefail -c I wish people would stop cargo-culting the so-called "strict mode". The -e flag is only…

The fastest shell is to not use shell special characters. For example, if you say `foo bar >/dev/null` then Make needs to launch your program as `sh -c 'foo bar >/dev/null`. But if you say just `foo bar` then Make can pass that directly to execve(), bypassing the shell entirely. Sometimes I actually do this:

    SHELL := /bin/false
Just to make sure my Makefile doesn't use shell syntax. If you want a `.STRICT` mode, then try Landlock Make.

Re: An opinionated approach to GNU Make

#27
post #5

This seems like a lot of work to not just consider ninja, meson, CMake, etc. I fully understand that the simplicity and portability of Make is alluring, but if you are actually using it to build C software it is a catastrophically poor choice and you can spend a ton of time and effort trying to come close to what you can get out of the box on a modern build system. If the tradeoff was that Make was easier to use and…

Out of the ones you list CMake is the only one I've authored and maintained and I still end up having to understand and troubleshoot Makefiles. Depending on the size of the project and the needs I try and use the fewest number of abstractions since I end up jumping down to troubleshoot anyway.

For toy stuff I'll just compile on the commandline (maybe write a bash script). I'll write a Makefile if I need to start wrangling too many things. CMake usually comes in if I need to go cross-platform or incorporate another build system or dependency that needs it. I think most places probably need CMake, but quite a few don't. If Make, as is, works then it makes sense to come up with opinions and standards that streamline authoring and maintenance.

Re: An opinionated approach to GNU Make

#28
A little trick to rebuild targets based on the build options: use .VARIABLES and pipe to sha256sum to create an option-dependent suffix for all built files:

https://bnikolic.co.uk/blog/sh/make/unix/2021/07/08/makefile

In this way a meaningful change to the makefile triggers rebuild automatically like it should

Re: An opinionated approach to GNU Make

#29

I hate developers like this. This is the attitude of every weirdo developer I've had to work with who thought they were brilliant instead of just using the stupid tool as it was intended.

That's a strange take on the post. The post only uses built-in functionality. Options exist for a reason, and defaults are _really_ difficult to change in software used by millions of people.

The effect is that the Makefile now has a dependency on bash and GNU make. What happens to the *BSD or non-GNU linux users?

Re: An opinionated approach to GNU Make

#30
I think the `.RECIPEPREFIX = >` bit triggered a lot of people here in the comments, and I agree. That would make drafting newlines a huge pain in any editor. Just enable "Show Whitespace" in your editor if you want this.

That said, I'm more concerned about the guidance to not use .PHONY and instead do this:

    # Tests - re-ran if any file under src has been changed since tmp/.tests-passed.sentinel was last touched
    tmp/.tests-passed.sentinel: $(shell find src -type f)
    > mkdir -p $(@D)
    > node run test
    > touch $@ 
The author is right, that does use make in a more more idiomatic way by relying on a real file, but I see 2 major problems:

* That's a lot of logic for something that should just be super simple.

* When I say `make test` I want it to run the tests. I don't care if they've passed before and the files haven't changed.

Really though, make just isn't a great tool for build scripts. The syntax is horrific and it's hard to scale it into something readable.

If I started a new project I'd probably consider Just: https://github.com/casey/just (though I haven't had a chance to use it myself yet).

Post reply on HN