Live data from Hacker News

Makefiles – Best Practices

danyspin97.org

51–60 of 127 posts

Re: Makefiles – Best Practices

#51
post #40

GNU Make is an awesome task-runner. I use it all the time, for all sorts of things. It's my shell-script replacement, and I write one-off Makefiles frequently. It's got a nice macro-processor, some helpful string manipulation tools, and expresses task dependencies perfectly. The manual is also very well written. But if you're writing a software package, consider using CMake, Meson, Autotools, or something similar. Un…

I haven't found a reason to not just use Make yet when starting from green fields, including some very big projects. In fact, I have often been annoyed at some projects picking something like CMake for no reason beyond it's "more advanced," but which ends up just being an extra dependency I have to fetch and install.

If I were to pick one of the above for building large projects on linux variants, which one would you pick?

Re: Makefiles – Best Practices

#53
post #40

GNU Make is an awesome task-runner. I use it all the time, for all sorts of things. It's my shell-script replacement, and I write one-off Makefiles frequently. It's got a nice macro-processor, some helpful string manipulation tools, and expresses task dependencies perfectly. The manual is also very well written. But if you're writing a software package, consider using CMake, Meson, Autotools, or something similar. Un…

I haven't found a reason to not just use Make yet when starting from green fields, including some very big projects. In fact, I have often been annoyed at some projects picking something like CMake for no reason beyond it's "more advanced," but which ends up just being an extra dependency I have to fetch and install. If I were to pick one of the above for building large projects on linux variants, which one would you…

For me, I mostly work in embedded Linux - that's Linux for routers, custom hardware, sometimes a Docker image, etc. That means lots of packaging work. Sometimes it means build machines that can't even run the compiled binary. I compile libraries for platforms that the original author would never have imagined.

In that space, there are lots and lots of details that need to be "just so". Lots of specifics about the cflags and linker flags. Requirements on where 'make install' puts files. Sometimes restrictions where an output binary wants to search for config files (spoiler: use sysconfdir).

Out of all the libraries/programs that I've had to package, autotoolized projects are the easiest to handle by far, followed by CMake projects.

Hand-written Makefiles are usually painful to a packager in one way or another. The most common ones being hard-coded CC/CXX/CFLAGS/CXXFLAGS, non-standard target names, and non-standard/incomplete usage of directory variables.

I know that Autotools is crusty in a lot of ways, and that the learning curve is steep. It's a nasty mix of Perl and m4, and runs lots of checks that don't matter at all. It's also what I use for all of my own libraries and programs, because the result is worth the pain (for me).

So for any program that I expect more than two humans to ever compile, I'd recommend Autotools if you're a perfectionist and CMake if you're not. Within Autotools, I'd recommend Automake and Autoconf if the language is C/C++, and just Autoconf/Make otherwise. (But be sure to follow the Makefile conventions: https://www.gnu.org/software/make/manual/html_node/Makefile-...).

I wouldn't recommend a hand-rolled Makefile unless you're the only one using it, the build is really weird for some reason, or unless it's a wrapper around some other build tool.

Re: Makefiles – Best Practices

#55
post #25

Earlier quoted context omitted.

FWIW I also read the GNU Make manual, and based some code for automatic deps off a profoundly ugly example it had. Then later people on HN showed me a better/simpler way to do it. https://news.ycombinator.com/item?id=15060149 https://www.gnu.org/software/make/manual/html_node/Automatic... After reading the manual and writing 3 substantial Makefiles from scratch, I still think Make is ugly and, by modern standards, no…

GNU Make makes it easy to write a makefile that works well for a clean serial build, but has bugs once you add -j or when your repository is in some intermediate state. So true. I wrote about this and solutions here: https://www.cmcrossroads.com/article/pitfalls-and-benefits-g...

Thanks, I bought your GNU Make book a couple years ago and read pretty much the whole thing! Along with reading the GNU Make manual a few years before that, I made an attempt to "give Make a fair shake".

I liked it, but it's honestly weird to me that a book published in 2015 can improve on the state of the understanding of a tool with 40 years of heritage :) I also get this "groundhog day" effect after about 10 years of seeing new GNU make tutorials / best practices on Hacker News every couple months. Everybody who learns Make has to go through this same thing.

-----

I read that you reimplemented GNU Make for Electric Cloud and I was impressed by that :) My project Oil [1] is a similar sort of project. It can run thousands of lines of unmodified shell/bash scripts found "in the wild". I got big distro scripts working last year, and I got thousands of lines of interactive completion scripts working recently, which I need to blog about.

For awhile I thought it would be nice to replace GNU make too, although (1) that's a lot of effort, and following Oil's strategy isn't possible since Makefiles can't be statically parsed and (2) I think it's happening anyway.

Major build systems are now split up into high-level and low-level parts, i.e. autoconf generating Makefiles, CMake generating Makefiles/ninja files. Android used to be 250K lines of pure GNU make (including GMSL), but now it's a high level Blueprint DSL generating Ninja too.

There aren't many pure Make projects anymore, at least in open source code. The exception I can think of are embedded ones like buildroot.

I like how Ninja focuses on build execution only, punting logic to a higher level (CMake, gyp, Blueprint). So my pet theory is that you can replace Make with a DSL that generates

1) A ninja file for fast, parallel, incremental developer builds

2) A shell script for portable builds for distro packagers/end users. This is just a clean serial build, so it can be a shell script rather than a Makefile.

I plan to test this theory by throwing out the modest 700 lines of Make I've written from scratch and replacing it with a Ninja/shell generator :)

I've debugged and read enough Make to be able to identify and fix most problems. But it still feels like like whack-a-mole to me. You can fix one problem and introduce another, since there's no real way to test for correctness (and efficiency). Problems can be reintroduced by seemingly innocuous changes.

[1] http://www.oilshell.org/blog/2018/01/28.html

Re: Makefiles – Best Practices

#56

What about using Python (with system calls) to build the project? Much more understandable than Makefiles.

Despite the downvotes, it is reasonable to write the project build script/workflow in an easy-to-read, cross-platform language. Makefiles often devolve into shell scripts or use of autotools in very slow and not-cross-platform ways. This becomes more spaghetti as you build dependencies between/across projects.

Lately with projects that do several things on build, I include a build.go and ask users to just `go run build.go [command]`. I get many features built in, it's quite maintainable and cross platform, etc. Even if it calls out to other makefiles or build scripts, at least you can centralize it and do different things per OS or whatever.

Re: Makefiles – Best Practices

#57

Earlier quoted context omitted.

https://www.cmcrossroads.com/article/tips-and-tricks-automat...

Thanks John, your book is the most-referred to on my office shelf ;) Edit: https://nostarch.com/gnumake (via https://blog.jgc.org/2015/04/the-gnu-make-book-probably-more... )

+1 for a great book, I bought that book too!

I use the patterns in @jgrahamc's article "Escaping: A Walk on the Wild Side" pretty often. Especially the \n definition, which works great to put on the end of a $(foreach) loop inside of a recipe.

@jgrahamc: Thanks for all you've written on Make over the years.

Re: Makefiles – Best Practices

#58
post #25

Earlier quoted context omitted.

FWIW I also read the GNU Make manual, and based some code for automatic deps off a profoundly ugly example it had. Then later people on HN showed me a better/simpler way to do it. https://news.ycombinator.com/item?id=15060149 https://www.gnu.org/software/make/manual/html_node/Automatic... After reading the manual and writing 3 substantial Makefiles from scratch, I still think Make is ugly and, by modern standards, no…

> In my mind, the biggest problem is that it offers you virtually no help in writing correct parallel and incremental builds. That’s interesting, because in my mind, parallel and incremental builds are the main features of Make, the features that it is best at, and all other features are secondary. It sounds like your problem is with the correctness part. Make gives you no tools to enforce that your build rules are a…

Yeah I think we're in agreement, except that Ninja is not a replacement for Make.

Make is both a high-level and a low-level build tool (i.e. logic/graph description vs. execution)

Ninja is only a low-level build tool -- it focuses on execution only, punting logic to a higher level, which I like.

See my comments here:

https://news.ycombinator.com/item?id=19057836

I used Bazel/Blaze for many years, and even contributed to it a long time ago, and I agree it has many nice properties (although I'm more interested in building open source projects with diverse dependencies, which it isn't great for AFAIK.)

Another potential advantage of a build generator (described in that comment) is having one mode to enforce correctness of the build description, and another mode to be fast. In other words, you could generate a Ninja file with a sandbox like Bazel uses. You could use a tree of symlinks or run in a chroot with a setuid helper.

Re: Makefiles – Best Practices

#59
post #31

Earlier quoted context omitted.

Your book is awesome, thanks for writing it! I converted our whole build at $DAYJOB from recursive to non-recursive, and your book helped immensely. It was a lot of work, but in the end it was worth it.

That's very kind. Weirdly, I have never done a conference presentation about GNU make. If I did what would people want to hear about?

I think the main point nowadays is how to write Makefiles for highly parallel builds. It's surely the thing that gave me the most headaches. In the end such a talk will probably mostly be about how to avoid calling Make recursively. Another talk I'd like to hear, which would be more meta: does it still make sense to bother with Make at all, aside from maintaining legacy systems? Are there features or paradigms which still make it worthwhile, compared to other popular build systems?

Re: Makefiles – Best Practices

#60
post #18

Does the "?=" operator behave like ":=" or like "=" ?

Both $ cat Makefile $(info FOO $(flavor FOO)) FOO ?= foo $(info FOO $(flavor FOO)) BAR := $(info BAR $(flavor BAR)) BAR ?= bar $(info BAR $(flavor BAR)) BAZ = $(info BAZ $(flavor BAZ)) BAZ ?= baz $(info BAZ $(flavor BAZ)) $ make FOO undefined FOO recursive BAR simple BAR simple BAZ recursive BAZ recursive If undefined then becomes recursive, otherwise the flavour is preserved.

Make variable expansions are always recursive.

For what you're describing I think the proper term as used by the GNU Manual is deferred. In other languages the phrase lazy evaluation is common.

The one thing I've always had trouble remembering with GNU Make is precedence. Variables defined as command-line arguments (make FOO=bar) override assignments, but environment variables (FOO=bar make) only override ?= assignments. != is both immediate (the shell expansion) and deferred (the result of the shell expansion).

I feel like there's some inconsistency when variables are inherited across recursive invocations, but in a simple test with make 3.81 (macOS) I couldn't find any. Maybe my suspicion is just a byproduct of my weird inability to remember the precedence rules.

I normally try to stick to portable make these days, anyhow. Between the ancient version of GNU Make on macOS, OpenBSD Make, and NetBSD Make, you're mostly stuck with POSIX syntax. If you add Solaris' default make, and especially if you add AIX' make, you can't rely on any extensions at all.

For me sticking with portable make is easier than installing and maintaining GNU Make on every flavor of operating system I test on, and definitely easier than installing autotools or installing and maintaining the most recent version of CMake.

Post reply on HN