
Make for hipsters - kiyanwang
https://mattandre.ws/2016/05/make-for-hipsters/
======
shoo
This introduction neglects to mention that Makefile rules are primarily rules
to derive files (usually from other files). Make will not re-run a rule when
the target (file) generated by the rule is already up to date.

From this perspective, a well structured Makefile can be thought of as a
pipeline/graph of functions, that derive values (files) from previously known
values (files).

Expressing a build process in this kind of way makes it possible to support
incremental / distributed build in a fairly natural / obvious way.

It is less obvious how to support incremental / distributed builds for the
more general kind of imperative build script that does a bunch of side effects
(e.g. the style of build script you'd get using something like ant).

~~~
nneonneo
For anyone who's curious: you can use a ".PHONY" target to force targets to be
rebuilt (i.e. that they don't correspond to real files). All dependencies of
the .PHONY pseudo-target will be rebuilt unconditionally when encountered.
Typically, "all", "clean", "install", etc. will all be phony targets as the
goal isn't to literally make a file called "all".

The syntax is simple:

    
    
        .PHONY: all clean install
    

marks "all", "clean" and "install" as phony. Even if the file "all" happens to
exist, "make all" will still trigger the build.

~~~
Bromskloss
> All dependencies of the .PHONY pseudo-target will be rebuilt unconditionally
> when encountered.

To be clear, they will rebuilt only if they are not already up to date
relative to their own dependencies, right?

~~~
rahkiin
yes.

Phony is only about making sure that 'all' is seen as target, not as file or
folder

~~~
delinka
This is only an issue if you do indeed have files or folders named the same as
targets, right? For example, if you do have an "all" file, you want make to
ignore the file when considering the 'all' target, you'd apply .PHONY to the
'all' target.

~~~
dilap
right.

------
0xmohit
I prefer a help rule in the Makefile, like so:

    
    
       help:
            @grep -P '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
    

Assuming that the trimmed Makefile read:

    
    
       build: ## Build the binary
    
       clean: ## Clean the binary and intermediate files
    
       distclean: clean ## Clean dependencies, intermediate files and binary
    
       deps: ## Install dependencies
    
       all: deps build ## Get dependencies and build binary
    
       release: distclean all ## distclean + all
    
    

Now issuing _make help_ would display:

    
    
       all                  Get dependencies and build binary
       build                Build the binary
       clean                Clean the binary and intermediate files
       deps                 Install dependencies
       distclean            Clean dependencies, intermediate files and binary
       release              distclean + all

~~~
urda
First off I wanted to say this is amazing and I'm going to roll it into my
current and future Makefiles.

Second, if you're on OS X the `-P` flag is going to have problems [1]. Works
fine if you just replace `grep -P` with something like `ack`!

[1]
[http://stackoverflow.com/q/16658333/169153](http://stackoverflow.com/q/16658333/169153)

------
capote
Do so many people who go on this site not know how to make makefiles? Also why
is it for hipsters? Or is the article for hipsters? Why does the author call
his own published notes factually inaccurate? Why is it hacky to use a
makefile? Is this all a big ruse?

~~~
s_kilk
Unfortunately, many developers have a mortal fear of anything from before the
year 2000. They've grown up with this tribal wisdom of unix and it's various
tools being a beardy wilderness of byzantine hacks and toxic waste dumps.

Meanwhile they'll merrily pile up a thousand node modules into a towering
pillar of bad, just to run the equivalent of a shell one-liner, and call it a
job well done.

~~~
WayneBro
Please show me the makefile for your _Javascript project_ that will run
_perfectly_ on Linux, Windows and Mac with little to no setup.

I doubt it exists. Until then, I'll stick with my Node.js build tools, _thank
you very much_.

Honestly, if anyone has an example of a makefile that can bundle my scripts,
insert bundle links into my HTML which is compiled from jade and nunjucks
templates, minify the JS, CSS and HTML and optimize my images...I'd love to
see that.

~~~
nwmcsween
Sure github.com/nwmcsween/asm.css/Makefile

~~~
WayneBro
"that will run perfectly on Linux, Windows and Mac with little to no setup"?
No.

I see no reason at all to use a Makefile in that project other than it being
your personal preference. It's completely non-standard for web projects and it
will only serve to confuse and annoy the next developer who wants to work on
your code.

Node.js, NPM, Gulp and Grunt are all orders of magnitude easier to get running
on a wide variety of operating systems than your typical C/C++ build tools.
Furthermore, you don't have to learn some cryptic syntax to use them. It's all
Javascript and JSON.

~~~
nwmcsween
Because gulp, grunt are not real build systems, they are glorified scripts
that do whatever, there is no tracking of dependencies, nothing. Make isn't a
cryptic syntax it's a quite simple language that took me about a day to
understand fully.

~~~
WayneBro
Incorrect. Gulp and grunt can track dependencies the same way you do anything
else with it...just add the right plugin.

Saying that gulp/grunt aren't real build systems because of one feature that
you actually missed anyway is like saying that Javascript isn't a real
programming language because it doesn't compile to assembly.

Practically nobody uses Make for JS outside of a few misguided folks. There
are good reasons for that. One of them is that Make's craptic language is
useless outside of that one single task. Another one is that Make sucks at
cross-platform. It forces you to use OS tools that don't exist on every OS.
Meanwhile, Node.js tools work everywhere.

------
nneonneo
The single biggest piece of advice I can give for Make is to make sure your
text editor uses real tabs in Makefiles. My personal preference for everything
is spaces (mostly a habit learned from Python's PEP 8), but Make doesn't
honour spaces for indentation - only tabs.

Despite being an obvious usability hole, this has never been fixed.
Exacerbating the issue is the poor error message given upon encountering a
space-indented line in Make:

    
    
        Makefile:2: *** missing separator.  Stop.
    

Make is ubiquitous and fairly easy to use, but it's warts like this that
remind you that it is fundamentally a build system from the _far_ past.

~~~
jomar
That's an error message from GNU Make. I'm not sure what version of GNU Make
you're using, because GNU Make has emitted the following since 1998:

    
    
      Makefile:2: *** missing separator (did you mean TAB instead of 8 spaces?).  Stop.
    

See 2c64fb221a265f9e7fc93374906b1e7540377561:

    
    
      1998-09-04  Paul D. Smith  <psmith@gnu.org>
      
      * read.c (read_makefile): If we hit the "missing separator" error,
      check for the common case of 8 spaces instead of a TAB and give an
      extra comment to help people out.
    

I guess you may have indented it in the modern way with 4 spaces of "tab".
Either way, there's also help to be had from your other tools, e.g., vim
highlights space-indented make recipes in glaring red error bars.

~~~
groovy2shoes
You can also add a line like this to your .vimrc:

    
    
      au FileType make setlocal ts=4 sts=4 sw=4 noet
    

and have vim automatically use "hard" tabs when it detects that you're editing
a Makefile. Of course, you can s/4/2/g or whatever your preferred tab width
is.

------
zx2c4
In case anyone's curious, I've been using Makefiles for web development for a
long time. The src files of a webpage are un-minified javascript, less/sass
files, and thumbnails. The dst files are the minified/built/thumbnailed
output. This suits Make particularly well.

[https://gist.github.com/zx2c4/11de49b2780c787b3ed5e1ee394857...](https://gist.github.com/zx2c4/11de49b2780c787b3ed5e1ee39485747)

That's the Makefile I use on
[https://www.edgesecurity.com/](https://www.edgesecurity.com/) if you're
interested. It also has in it some very simple ssh/rsync deployment mechanism.
Since the deployment relies on the built files, I can simply run:

    
    
        $ make deploy
    

And the entire webpage is built and deployed in one step.

Every webpage I manage has a variant of this, with all sorts of rules for
building and managing things. It turns out to be much easier than grunt or ant
or random bash scripts or vagrant or whatever else kids use these days.

------
profeta
This tutorial is awful.

You should read the manual, which is shorter than that page!

make is NOT:

    
    
        alias:
            commands
    

this will bite you when you least expect. Make is:

    
    
        targetfile: inputfiles
            commands to get targetfile to exist
    

and you can use things like

    
    
        %.css_min: %.css
            sass stuff
    
        .PHONY: release
        # above is needed otherwise you won't build if there is a release/ dir
        release: release-min.css
    
        release-min.css: (list *.css_min files, there are helpers for it)
            cat *.css_min > release-min.css
    
    

anyway. you get the idea. work with files and why you need those files.

~~~
mercurysmessage
Yea, you can just do what he is doing using shell scripts anyway. I don't see
why you'd use make for it.

------
teddyh
Meanwhile, there’s a perfectly good introduction in the GNU Make manual:

[https://www.gnu.org/software/make/manual/make.html#Introduct...](https://www.gnu.org/software/make/manual/make.html#Introduction)

Also available in print: [https://shop.fsf.org/books-docs/gnu-make-
version-381](https://shop.fsf.org/books-docs/gnu-make-version-381)

~~~
groovy2shoes
I learned how to use make by reading OpenBSD's man page for it[1]. It's
written extremely well, and is concise yet comprehensive. Of course, it's
specific to BSD make, but most of the concepts are portable to GNU make with
some caveats. FreeBSD and NetBSD, iirc, both have similarly high-quality
manuals for their respective makes, and I've come to favor BSD make in
general, using bmake[2] on Linux and Cygwin for my own projects (bmake being a
portable, packaged-up version of NetBSD's make).

[1]: [http://man.openbsd.org/OpenBSD-
current/man1/make.1](http://man.openbsd.org/OpenBSD-current/man1/make.1)

[2]:
[http://www.crufty.net/help/sjg/bmake.htm](http://www.crufty.net/help/sjg/bmake.htm)

------
jsz0
I like to use makefiles for managing docker containers / images. For my needs
it's a more practical and flexible solution than other docker orchestration /
management tools I've tried. A few things I've learned that might be useful:

* (cd /path/to/something && make goal)

* You can use .DEFAULT_GOAL := goal_name to set the default target run by 'make' with no target name specified.

* Adding .SILENT will suppress make entering/leaving directory messages. Probably wise to be careful with one until you're 100% sure you're not ending up in an unintended directory doing nasty things.

* You can use $(COLOR) = `tput setaf x' and $(RESET) = `tput sgr0` to add colors to your make output. For example @echo "$(RED)ERROR!$(RESET)"

* Use if ! -f conditionals to start goals that rely on external scripts/programs. Otherwise make will run until it hits the error which will leave you with a partially completed goal.
    
    
      goal:
    
        @if [ ! -f ./scripts/blah.sh ]; then echo "ERROR: ./scripts/blah.sh not found"; exit 1; fi
    
        @echo "without the if ! -f this command would run"
    
        @echo "and so would this one"
    
        @./scripts/blah.sh $(IMPORTANT_STUFFS)
    
        @echo "but not this one"

~~~
mdaniel
> You can use .DEFAULT_GOAL := goal_name to set the default target run by
> 'make' with no target name specified.

I had always, perhaps erroneously, thought that the very first goal in the
file was the default one.

~~~
jsz0
IIRC .DEFAULT_GOAL is a relatively new feature. If it's not set goals will be
processed in the normal order.

------
ThePhysicist
I routinely use a Makefile for building my frontend resources, which works
quite well:

[https://github.com/adewes/gitboard/blob/master/Makefile](https://github.com/adewes/gitboard/blob/master/Makefile)

Using the inotify tools it's even possible to automatically trigger a rebuild
whenever a file changes.

For me, no need to use Gulp anymore, as Makefiles are easier to reason about,
more composable and make use of existing infrastructure instead of reinventing
the wheel over and over again.

------
heja2009
> You need to create a makefile to tell make what to do.

Nope. You can say "make hello" and have make automatically use its default
rules - which can be configured - to e.g. compile hello.c into a hello
executable. I use this frequently.

~~~
nneonneo
Make's default implicit rule set is very useful for C, C++, Fortran, Pascal,
and basically nothing else.

Out of curiousity: how do you configure the implicit ruleset? I wasn't able to
find documentation on that (the closest I found was [https://ftp.gnu.org/old-
gnu/Manuals/make-3.79.1/html_chapter...](https://ftp.gnu.org/old-
gnu/Manuals/make-3.79.1/html_chapter/make_10.html#SEC95), which explicitly
documents the Make implicit rules but says nothing about changing/augmenting
them).

~~~
orodley
Many implicit rules make use of variables, which you can change as you see
fit. For example, creating an object file from a C file will do something like
"$(CC) $(CFLAGS) $< -o $@". You can change CC to change the compiler used, and
add stuff to CFLAGS to add compiler options, all without touching the rule
itself.

Other than that there's not much configuration, but the predefined rules are
usually quite simple, so it's pretty easy to just write your own implicit
rules if the predefined ones don't suit your needs.

------
treve
A better title would have been 'Make 101'. Its an absolute beginners tutorial.

~~~
joelg
A _more precise_ title would have been 'Make 101'. I also wouldn't have read
an article by that title, although (not knowing any make) I'm glad that I did
read this one.

------
VLM
Something unmentioned so far is when you start getting "advanced" the make
scene is much like the shell scene and its fairly easy to write something that
only runs on GNU make or only on BSD make and there is plenty of opportunity
for argument about that being a bug or feature etc.

So there's that. Make being such a good idea and a popular standard, there's
no shortage of people implementing not quite compatible make-like-systems.

Another fun tip is make your makefile executable and try the first line like

#!/usr/bin/env gmake

or whatever seems appropriate. Or perhaps GNU make is installed as "make" on
your OS or you use an "alternatives" link farm or whatever. Some people will
flip out if you put arguments on that command line, others will pat you on the
back and high-five. So there's that "fun".

------
leni536
One really neat thing about make: If you include other makefiles then before
including them make checks if there are prerequisites of these files and
rebuilds them as needed. One can use this to automatically generate header
dependencies of translation units for C/C++ projects. Some of the relevant
lines in one of my Makefiles:

    
    
         include ${DEPENDS}
         
         ${DEPDIR}/%.d: ${SRCDIR}/%.${SRCEXT}
         	@mkdir -p ${shell dirname $@} ; \
         	$(CC) -MM ${INCLUDE} -mmcu='${AVRTARGET}' $< >> $@
     

If any file in the "include" line needs updating then it is generated by gcc
and included in the same make run. I didn't find a good documentation about
this feature and I discovered this with make's pretty verbose -d option.
Previously I did something like this using multiple makefiles and recursively
calling make which is generally frowned upon (for valid reasons).

~~~
efaref
I think these days you're better off using -MMD to generate header
dependencies at the same time as compiling, rather than doing it in a separate
rule.

    
    
        %.o: %.c
                $(CC) $(CFLAGS) -MMD -c $<
        -include $(OBJECTS:.o=.d)

~~~
leni536
Is this always correct? The %.d files don't depend explicitly on the %.c files
so they aren't rebuilt every time at the beginning of the make run, so old
dependencies are included after a change in header dependencies. I'm still
trying to construct an example where such a makefile breaks though.

~~~
efaref
The only case it becomes problematic is if you delete a header file, as make
won't know how to remake it and won't recompile the C file (removing the
dependency) until it has done so. There are two options to resolve this:

Also specify -MP on the GCC command line, which causes GCC to emit an empty
rule for all the header files, like this:

    
    
        foo.o: foo.c foo.h bar.h
        foo.h:
        bar.h:
    

Alternatively, you can write a generic empty header creation rule:

    
    
        %.h:
    

Which has the same effect.

~~~
JdeBP
Neither make nor any of its alternatives or improvements get header files
completely right. This is because compilers simply _do not emit_ all of the
information. A build tool not only has to know about the header files found,
to monitor them for changes, but also has to know about the places where
header files were _not_ found, to monitor for files suddenly appearing there.
I wrote a C++ preprocessor that would spit out both sets of information, for
use with redo, but I've never published it. Without this information things
can end up not being re-built correctly in a number of cases that one can
encounter in practice.

~~~
leni536
> A build tool not only has to know about the header files found, to monitor
> them for changes, but also has to know about the places where header files
> were not found, to monitor for files suddenly appearing there.

I wonder how my makefile fails here.

------
toomim
I don't see what this has to do with hipsters. The fonts are not very cool.

~~~
oneeyedpigeon
Abuse of a top-level domain which occurs at the end of the author's name -
that gets about a 7/10 on my hipsterometer.

~~~
macca321
I own forhipsters.com, what does that score?

~~~
imtringued
5/10 but only because it's not forhipste.rs

------
peterwwillis
Here's a more in-depth and easy to read guide for beginners:
[http://www.computerhope.com/unix/umake.htm](http://www.computerhope.com/unix/umake.htm)

If you want to see some interesting/convoluted Makefiles, get a very complex,
large piece of software (something from Gnome, possibly) and use autoconf &
automake to generate the Makefiles, then pour through them to see how they
tick.

~~~
nneonneo
For the target audience (total Make beginners) poring through autogenerated
makefiles might not be such good advice. It's functionally equivalent to
telling people to learn Bash by studying a `configure` script, or learning
JavaScript by studying EmScripten output.

If you're decent with Make and want to learn advanced tricks, then by all
means go right ahead, but 95% of Make users will never need to delve into such
details.

------
sleepychu
I feel like the author just structured their build dependencies wrong. I used
to work on a project that had a _16,000_ line Makefile, where almost the
entire file was actively in use. So you can abuse any build system.

------
jbergens
Another way is using npm-install-changed for the "npm install" problem and fix
everything else with other npm scripts like npm-run-all. Use make if you like
it but if everything else is npm you can solve most problems using "npm run"
and npm scripts.

[https://github.com/oNaiPs/npm-install-changed](https://github.com/oNaiPs/npm-
install-changed)

[http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-
tool...](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/)

[edit: formatting]

~~~
majewsky
Supplying even a trivial Makefile is advantageous if your target audience
knows it. They'll see a Makefile and just do `make && make check && make
install` without needing to browse your README.

For example, Debian's packaging process is heavily optimized towards Makefiles
that supply the standard targets, and you can reduce a lot of friction for
packagers if you supply a wellbehaving Makefile with your program. (Even if
developers would usually `go build` or whatever instead of `make`.)

~~~
jbergens
yes it is good to adapt to your target audience. If that is linux users a
makefile might be the best. For a lot of other users like javascript web
developers npm might be better. For end users on windows an msi or exe
installation file is probably best. Makefiles are unusual on windows which
makes them a somewhat bad solution even for developers on windows machines. If
you want to create system that works on multiple platforms there are probably
better ways. At least make sure to not use some *nix-only syntax in the
makefile or calling grep or awk.

~~~
JdeBP
Makefiles are only considered unusual on Windows if one ignores decades of
.MAK files. (-:

------
miiiiiike
I use make to manage JS and container builds. Couldn't imagine using anything
else right now.

------
bluejekyll
Answer me this, does anyone have a good cross-platform method of writing Make
files? I guess with Windows now having bash, I can actually just target bash
scripts?

~~~
hawski
Take a look at makefiles from various suckless projects. For example:
[http://git.suckless.org/dwm/tree/Makefile](http://git.suckless.org/dwm/tree/Makefile)

Their makefiles work with GNU Make and BSD Make, don't know about nmake (AFAIR
Windows).

------
ashitlerferad
Pop make quiz: what is the difference between these two Makefiles?

\----------------------------------------------------

foo: bar

bar:

\----------------------------------------------------

.PHONY: foo

foo:

~~~
jhjjhj
if bar happens to already exist no rules will execute for the first.

------
alpyne
Making Make Make Sense from @izs is great:

[https://www.youtube.com/watch?v=dsqBSgdQz_8](https://www.youtube.com/watch?v=dsqBSgdQz_8)

------
zeveb
Make is old and crufty, but it's still the best at what it does.

What I'd really love right now is a program which can look at a Go source tree
and write make rules such that I don't have to run go build or go install
unless dependencies have actually changed. It's really annoying to be pushing
new images when nothing has actually changed.

~~~
koloron
> but it's still the best at what it does.

If "what it does" means "build complex software projects" I think your claim
is rather debatable.

GHC is currently switching away from make after having already overhauled its
make-based build system two times:

[http://research.microsoft.com/en-
us/um/people/simonpj/papers...](http://research.microsoft.com/en-
us/um/people/simonpj/papers/ghc-shake/ghc-shake.pdf)

------
nwmcsween
The problem with $buildtool is that 99.99% of the time the people implemting
it had no idea how make worked and invented something easier to use in the
particular domain (grunt, gulp, webpack and many more). Make creates a DAG of
files and resolves targets, the examples given in this article also fail to
understand how make works.

------
adrianratnapala
The depressing thing is that after decades of trying, there still does doesn't
seem to be anything better than Make.

And Make is _terrible_.

At least if want have to deal with more than one directory.

------
nkuttler
Make is a great tool for automating simple things, but if you use a
programming language you should probably use native automation tools for
common tasks. If you have any non-trivial logic or want to enable code-reuse
make doesn't seem like the right choice either.

~~~
jacquesm
I hate this. Every programming language that bolts on its own build
environment. Make is language agnostic and that's a huge benefit, it means you
have to learn about just _one_ build tool and its peculiarities and that will
allow you to build many different projects in different languages.

All these languages with their half-baked build tools that won't accept that
they may have to play nice with other languages and _their_ build tools are
not helping.

Of course it is great when you write a language to also throw in a build tool,
but in the end if the build tool re-implements 30% or so of make in a broken
way I don't see the point.

Make does have its limitations, but most ordinary projects get nowhere close
to reaching those.

~~~
lomnakkus
> Of course it is great when you write a language to also throw in a build
> tool, but in the end if the build tool re-implements 30% or so of make in a
> broken way I don't see the point.

Well, other build tools sometimes also do a lot _more_ than make ever did.
(And in some areas, perhaps less.)

Example: Cabal/cabal-install (for Haskell) can automatically fetch all your
project's dependencies and automatically compile them for you.

> Make does have its limitations, but most ordinary projects get nowhere close
> to reaching those.

I suppose it depends on what you mean by "ordinary", but IME this Makefiles by
virtue of being purely file-based is useless for most of _my_ projects. There
are a many langauges (e.g. Java/Scala, Haskell) where it just doesn't fit for
normal development. As an example: Tracking intra-file dependencies for proper
recompilation is _really_ difficult to for Scala code without reimplementing a
huge chunk of a Scala compiler. (I'd venture to suggest that you'd be foolish
to even try doing this in Make.)

~~~
majewsky
> Tracking intra-file dependencies for proper recompilation is really
> difficult to for Scala code without reimplementing a huge chunk of a Scala
> compiler.

tup ([http://gittup.org/tup](http://gittup.org/tup)) has a really nice way of
handling interdependencies. They set up some filesystem magic to figure out
which files were read while compiling a certain target, and record these files
as dependencies for the target.

Another instance of a hard problem ("reimplementing [...] a Scala compiler")
that turns into a very easy problem once you look at it from the right angle.

~~~
gavinpc
I love tup so much. It's a "functional reactive" build tool that practically
nobody's heard of, so I'd argue that it's even more "hipster" than make
(whatever that means).

When I decided I needed a single build system to replace a hodgepodge of
stuff, I looked at make because that was apparently the go-to thing.

It took me about 2 minutes to decide that I wanted no part of make. I'd
assumed it was basically what Tup is. I'm lucky that I learned about Tup from
a thread on this site, because it's made me so happy (especially once the
initial pain was over... and yeah, you have to change).

So whenever I see these posts, I make sure that someone is mentioning Tup as a
clean alternative.

edit: to be explicit, Tup is obsessed with speed and correctness, and handles
incremental builds optimally (see Mike Shal's whitepaper on this). The "price"
is that you can't _modify_ files during a build. A file is either "regular"
(not touched by build) or "generated" (produced wholly from one build rule).
You have to describe the complete DAG up front. Because of all this, Tup will
actually _remove_ build targets when they're not needed, which I believe no
other build system does. I could never go back.

~~~
majewsky
The only practical problem with tup is that it's not make, which creates
friction for packagers. And since I'm doing some packaging work myself, I'm
heavily optimizing for packager happiness.

------
gkya
make is not used in this post. He wrote shell scripts with a single tab
indentation.

------
GFK_of_xmaspast
I guess make could be for hipsters in the same way that vinyl is for hipsters,
i.e. obsolete technology that should have gone away by the end of the 80s.

