Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Practical Makefiles, by example (mutantstargoat.com)
118 points by jeffreyrogers on April 1, 2015 | hide | past | favorite | 28 comments


To anyone implementing the automatic dependency generation mentioned in this article, you can actually make things even simpler by combining the compiling and dependency steps.

This works because if you add a new dependency to a file, that information will only be needed for the next build - the current file will already be considered out of date seeing as it was edited to add the #include.

  gcc -MD -MP foo.c -o foo.o
Will compile foo.c into foo.o, and also generate foo.d (-MD). Foo.d will contains make style dependencies, and also a phony target for every dependency (-MP). This allows you to delete dependant files, as make considers the target of a rule that has no perquisites or commands to be up to date if said target does not exist.


I second this, using CFLAGS=-MMD or -MD makes writing a Makefile a lot more simpler. This also gets rid of the need to add a rule for building dependency files and you can rely on the built-in rules (see `make -p`) to build object files.

A good Makefile should have any rules for building object files if you're using a language like C or C++, which Make has built-in rules for. If using another language, adding a few generic rules should be enough.

Here's a Makefile template I've been using for some time. It may look complicated initially but only the first 70 or so lines are the actual beef. The rest of the Makefile is helpful rules for tooling (tags, cscope, coverage, profile) but that doesn't work too well at the moment. It also supports out-of-source-tree builds (using vpath to locate source files, object files and other outputs go under $PWD, vpath is does not work for object files).

https://github.com/rikusalminen/makefile-for-c


I can't say I share your love for make's built in rules. I find using them often just makes the build system harder to understand and debug. I usually disable all of the built in ones using:

  # Disable built in suffix rules
  .SUFFIXES:

  # Disable builtin pattern rules
  MAKEFLAGS+=-r


> I can't say I share your love for make's built in rules.

Yes they can be a bit limiting, but even if you do not want to use built-in rules it's still a good idea to use generic rules using wildcards.

This is what lots of "Makefile tutorials" get wrong, they start by writing rules to build individual object files and targets.

Even if you want to write your own build rules, you should not need more than a few good rules for building and linking your object files.


Completely agree with you here - there should never be the need to hardcode file names in a makefile or repeat a rule multiple times - just use pattern rules.


One other dependency often forgotten is on the Makefile itself. This way if the Makefile changes, everything gets rebuilt:

    DEPS = Makefile
    %.o: %.c $(DEPS)
        $(CC) $(CCFLAGS) -o $@ -c $<


This may or may not be what you want, depending on the use case. If you change an important variable in the Makefile (e.g. CFLAGS), you might want to recompile everything. However, if you're just adding a new source file to the Makefile, you definitely do not want to rebuild everything.

I prefer to run `make clean` manually when necessary rather than adding a rule to rebuild everything "just in case". But I can imagine that this might be useful in some cases.


I like to put CFLAGS/CXXFLAGS et al in separate files that just define the corresponding macro. These get included in the makefile which has dependencies added to rules that reference such a macro on the file that defines the macro. E.g. the .o rule for C++ has a dependency on the CXXFLAGS which defines CXXFLAGS. Change CXXFLAGS and things rebuild, mess with LDFLAGS and you re-link etc... Handy if you need it but you can end up with lots of little files.


I disagree with this. I don't find CMake any less easy to use than make in the simple case, but it is far better than Autotools in the complex case. Just learn CMake and be done with it. As a bonus, it saves you some typing making your program cross-platform, although that is a pain no matter what tool you're using.


CMake works really well when you are building a normal user space application with only popular libraries (or ones that use CMake themselves) as dependencies that CMake can find and configure. Some CMake generators are better than others (e.g. ninja-build is really nice with CMake). Creating MSVC Project files was badly broken the last time I tried and even when it worked, the resulting project files are awkward for MSVC IDE users (I prefer nmake makefiles if compiling on Windows with MSVC).

CMake falls apart when you try to do something more complicated, e.g. building a bootable kernel image which requires special handling for compiling and/or linking. I have done a bare metal project using CMake and GNU Make and the latter was a lot easier.

To name a few other platforms where using GNU Make works better: Android NDK (default Android.mk build system is GNU Make, there are CMake-based hacks for Android but they were atrocious) and doing micro controller work like Arduino (when writing C, not using the Arduino language).

To give an example where CMake works a lot better than GNU Make is cross compiling, e.g. building Windows binaries on a Linux host. All it takes is a few lines of "toolchain specs" and apt-getting the mingw toolchain. But even this falls apart when you need to build both, "host" and "target" binaries like you often need with hardware projects.

CMake is by no means perfect and GNU Make is still a very useful tool to know.


Is anyone aware of the reasons for choosing the particular symbols that Make uses i.e. '@' (targets), '^' (list of dependencies) and '<' (first in the list of dependencies)?

I have admittedly limited experience with make and I always seem to forget what symbol stood for what. They don't seem to be particularly intuitive mnemonics to me.

For example, one could argue that '<' for target and '>' for dependencies would be a tad clearer.


I always think of '^' and '<' as being arrows pointing at the relevant dependencies, i.e "that list of things up ^ there" or "that thing on the far left <". It can also help to think of '<' as being like a stdin redirect, so a simple rule might look like "myprog < $<".

I'm not sure what the deal is with '@' though.


I have always thought of '@' as an archery target.


Very appreciated. I'm trying to make small hobby projects with a simple C and Makefiles, to get a taste of a stripped-down, basic environment, and after I got accustomed with basic Makefile usage (which is taught in any of thousands tutorials on the matter), I found myself lacking coherent and easy to read material that would get me to a more advanced level. Tutorials and stack overflow questions either cover really basic stuff or suggest things that seem like awful practice in the first glance, and GNU Make manual is written in way that suggests you sit down and read it for a few hours instead of just giving you a quick and sane way to solve a particular problem.


See also this post by Mike Bostock (d3.js developer), ‘Why Use Make’, with practical non-C examples:

http://bost.ocks.org/mike/make/


With all the news lately about Bazel, kind of refreshing to talk about this war-horse.

Consider: 1. Paul Graham recommends to "do things that don't scale". 2. Google designed Blaze when gmake started to scale badly.

Chances are, gmake plus some shell scripts and language-dependent build flows is all you really need. Worry about replacing it with something more scalable when you actually have that problem :).


An elegant weapon from a more civilized time, indeed.

Frack Windows, and frack XML :-(


Getting into JavaScript there's grunt. I know it's not really comparable to make as it doesn't check dependencies but ... it really seems like it would be cool for a "new" build system that took plugins the way grunt does. Maybe they'd wouldn't fit most people's use cases enough?


This is nice introduction for someone just beginning gcc like compilers. For example I would use this back then when I started to use avr-gcc. It doesn't have to be cross-platform or need any other super complex fancy things. It just needs few extra rules and some basic stuffs. Make is great choice for this kind of things.


Object files should not be placed in the same directories as source files.


It's interesting that under the section "Handling cross-platform differences" the author calls 'uname -s'. It's not cross-platform if you ignore Windows (and requiring to install cygwin doesn't count), or cross-compiling scenarios. Also makefiles don't help all that much if you want to work in the platform's 'native' IDEs (like Visual Studio or Xcode). These 3 points (cross-platform, cross-compiling and IDE support) are exactly the points that meta-build-systems like cmake (or scons, or premake) fix. Remember that cmake isn't a replacement for make, for command line compilation it usually makes sense to let cmake generate makefiles (except on Windows).


Nice, but the page's color scheme starts hurting my eyes after about 15 seconds. Dark / colored text on a light background would be much more reader-friendly.


Practical Makefiles: nice oxymoron.


Great idea. But awful theme. And no longer really relevant.

CMake is now all the rage. Like krapht said, just learn CMake and be done with it.


CMake generates Makefiles (and other build system files on other systems). If something doesn't work, you can either fiddle with it without understanding what you're doing, or you can look at the Makefile and see what went wrong...

I prefer to just use plain Makefiles, they're easier to fix and "make correct" than anything else. For me, I guess. make is weird, but more minimal than cmake. (cmake is HUGE. autotools are a lot weirder than make. considering the alternatives, I actually like make...)


ugh. CMake is atrocious. It covers maybe .1% of the cases that autotools handles while having some of the worst documentation known to man. Not to mention yet another language to learn. It's not more "modern" than autotools and it's in no way shape or form "newer" than autotools, seeing that automake/autoconf have been in active development since inception.


It does let you avoid shell, which makes cross platform stuff less fraught.

This should not be interpreted as an endorsement of CMakes miserable, quarter-assed BASIC-in-almost-s-exprs language.


Upvoted, and re: documentation, I will say that I enjoyed the print book[0] when I was reading it years ago. Your comment re: quarter-assed BASIC... is about what I was thinking when I wrote https://news.ycombinator.com/item?id=9283016 -- a big missed opportunity.

[0] http://www.amazon.com/Mastering-CMake-Ken-Martin/dp/19309342...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: