Skip to content

Instantly share code, notes, and snippets.

@theicfire
Last active August 29, 2015 14:08
Show Gist options
  • Save theicfire/92091f7fdf89bbb20599 to your computer and use it in GitHub Desktop.
Save theicfire/92091f7fdf89bbb20599 to your computer and use it in GitHub Desktop.
# Makefile tutorial, through examples
# This is not a makefile! I just called it as such to have vim coloring work well.
########
# This makefile will always run. The default target is some_binary, because it is first.
########
some_binary:
echo "nothing"
########
# This file will make "some_binary" the first time, and the second time notice it's already made, resulting in "make: `some_binary' is up to date."
########
some_binary:
touch some_binary
########
# Alternative syntax: same line
########
some_binary: ; touch some_binary
########
# \ gives us multilines
########
some_binary:
touch \
some_binary
########
# Will call other.txt target if it is newer than the "some_binary" file, or it doesn't exist. It will call the other.txt rule first.
########
some_binary: other.txt
touch some_binary
other.txt:
touch other.txt
########
# This will always make both targets, because some_binary depends on other.txt, which is never created.
########
some_binary: other.txt
touch some_binary
other.txt:
echo "nothing"
########
# "clean" is not a special word. If there's a file called clean that is made, then "make clean" won't have to do anything. Similarly, if the clean file is older than the some_binary file, the clean rule will not be called.
########
some_binary: clean
touch some_binary
clean:
touch clean
actual_clean:
rm some_binary
rm clean
# Adding PHONY to a target will prevent make from confusing the phony target with a file name. In this example, if clean is created, make clean will still be run.
# PHONY is great to use, but I'll skip it in the rest of the examples for simplicity.
some_binary:
touch some_binary
touch clean
.PHONY: clean
clean:
rm some_binary
rm clean
########
# 2.4
# Variables can only be strings. Here's an example:
########
files = file1 file2
some_binary: $(files)
echo "Look at this variable: " $(files)
touch some_binary
file1:
touch file1
file2:
touch file2
clean:
rm file1 file2 some_binary
#-------------------
# Here's a blah.c file that some examples below require
#
##include<stdio.h>
##include <string.h>
#int main()
#{
#printf("hello there\n");
#return 0;
#}
#-------------------
########
# 2.5
# Example requires: blah.c
# If we have a target that is a ".c" file, there is an *implicit command* that will be "cc -c file.c -o file.o".
########
# Implicit command of: "cc -c blah.c -o blah.o"
# Note: 1) Do not put a comment inside of the blah.o rule; the implicit rule will not run!
# 2) If there is no blah.c file, the implicit rule will not run and will not complain.
blah.o:
clean:
rm blah.o
########
# 4.1
# Print literal '$'
########
some_binary:
echo $$
########
# 4.2
# We can use wildcards in the target, prerequisits, or commands.
# Valid wildcards are *, ?, [...]
########
some_binary: *.c
# create the binary
*.c:
touch f1.c
touch f2.c
clean:
rm *.c
########
# 4.2.3
# We CANNOT use wildcards in other places, like variable declarations or function arguments
# Use the wildcard function instead.
########
wrong = *.o # Wrong
objects := $(wildcard *.c) # Right
some_binary:
touch f1.c
touch f2.c
echo $(wrong)
echo $(objects)
clean:
rm *.c
########
# 4.3.2
# Use vpath to specify where some set of prerequisites exist. The format is "vpath <pattern> <directories, space/colon seperated>
# <pattern> can have a "%", which matches any zero or more characters.
# You can also do this globallyish with the variable VPATH
########
vpath %.h ../headers ../other-directory
some_binary: ../headers blah.h
touch some_binary
../headers:
mkdir ../headers
blah.h:
touch ../headers/blah.h
clean:
rm -rf ../headers
rm some_binary
########
# 4.4
# Making multiple targets? Make a phony 'all'!
# Note here PHONY is *after* all, because the target is seen as 'all' instead of PHONY,
# giving better error dumps.
########
all: one two three
PHONY: all
one:
touch one
two:
touch two
three:
touch three
clean:
rm one two three
########
# 4.8
# Multiple Targets: the rule will be run for each target
# $@ is a *automatic variable* that contains the target name.
########
all: f1.o f2.o
f1.o f2.o:
echo $@
# Equivalent to:
# f1.o
# echo $@
# f2.o
# echo $@
clean:
rm *.c
########
# 4.8
# Multiple Targets: We can use the wildcard % in targets, that captures zero or more of any character
# Note
# 1) We do not use *.o, because that is just the string *.o, which might be useful in the commands,
# but is only one target and does not expand.
# 2) PHONY is needed because otherwise make will create an automatic rule of "cc all.o f1.o f2.o -o all
# TODO why was this not a problem when I didn't use the % wildcard?
########
all: f1.o f2.o
.PHONY: all
%.o:
echo $@
clean:
rm *.c
########
# 4.10
# Static Pattern Rules: each .o file has a prereq of the corresponding .c name
# Run "make init" first to make the .c files
########
objects = foo.o bar.o
all: $(objects)
# targets ...: target-pattern: prereq-patterns ...
$(objects): %.o: %.c
echo "make file" $@ "with prereqs" $<
init:
touch foo.c
touch bar.c
clean:
rm foo.c bar.c
########
# 4.10
# filter can be used in Static pattern rules to match the correct files
# Run "make init" first to make the necessary files
########
files = foo.elc bar.o lose.o
src_files = foo.el bar.c lose.c
all: $(files)
$(filter %.o,$(files)): %.o: %.c
echo "target: " $@ "prereq: " $<
$(filter %.elc,$(files)): %.elc: %.el
echo "target: " $@ "prereq: " $<
init:
touch $(src_files)
clean:
rm $(src_files)
########
# 4.11
# Double-Colon Rules are rarely used, but allow the same target to run commands from multiple targets.
# If these were single colons, an warning would be printed and only the second set of commands would run.
########
all: blah
blah::
echo "hello"
blah::
echo "hello again"
clean:
rm $(src_files)
########
# 4.12
# Example requires: blah.c
# Generating prereqs automatically
# This makes one small makefile per source file
# Notes:
# 1) $$ is the current process id in bash. $$$$ is just $$, with escaping. We use it to make a temporary file, that doesn't interfere with others if there is some parallel builds going on.
# 2) cc -MM outputs a makefile line. This is the magic that generates prereqs automatically, by looking at the code itself
# 3) The purpose of the sed command is to translate (for example):
# main.o : main.c defs.h
# into:
# main.o main.d : main.c defs.h
# 4) Running `make clean` will rerun the rm -f ... rule because the include line wants to include an up to date version of the file. There is such a target that updates it, so it runs that rule before including the file.
########
all: blah.d
clean:
rm blah.d
%.d: %.c
rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
sources = blah.c
include $(sources:.c=.d)
########
# 5.1
# Add an @ before a command to stop it from being printed
# You can also run make with -s to add an @ before each line
########
all:
@echo "This make line will not be printed"
echo "But this will"
########
# 5.2
# Each command is run in a new shell (or at least the affect is as such)
########
all:
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd`
# This cd command affects the next because they are on the same line
cd ..;echo `pwd`
# Same as above
cd ..; \
echo `pwd`
########
# 5.2
# Note only: the default shell is /bin/sh. You can change this by changing the variable SHELL
########
########
# 5.4
# Make stops running a rule (and will propogate back to prerequisites) if a command returns a nonzero exit status.
# DELETE_ON_ERROR will delete the target of a rule if the rule fails in this manner. This will happen for all targets, not just the one it is before like PHONY. It's a good idea to always use this, even though make does not for historical reasons.
# Add "-k" when running make to continue running even in the face of errors. Helpful if you want to see all the errors of Make at once.
########
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false
########
# 5.4
# Add a "-" before a command to suppress the error
# Add "-i" to make to have this happen for every command.
########
one:
false
touch one
########
# 5.5
# Note only: If you ctrl+c make, it will delete the newer targets it just made.
########
########
# 5.6
# Recursively call a makefile. Use the special $(MAKE) instead of "make"
# because it will pass the make flags for you and won't itself be affected by them.
########
new_contents = "\
hello:\\n\
\\ttouch inside_file"
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)
clean:
rm -rf subdir
########
# 5.6
# The export directive takes a variable and makes it accessible to sub-make commands.
# In this example, "cooly" is exported such that the makefile in subdir can use it.
#
# Recursively call a makefile. Use the special $(MAKE) instead of "make"
# because it will pass the make flags for you and won't itself be affected by them.
#
# Note: export has the same syntax as sh, but it they aren't related (although similar in function)
########
new_contents = "\
hello:\\n\
\\techo \$$(cooly)"
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END mAKEFILE CONTENTS---"
cd subdir && $(MAKE)
# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly
clean:
rm -rf subdir
########
# 5.6 (related, although not stated in docs)
# You need to export variables to have them run in the shell as well.
########
one=this will only work locally
export two=we can run subcommands with this
.PHONY: all
all:
@echo $(one)
@echo $$one
@echo $(two)
@echo $$two
########
# 5.6
# EXPORT_ALL_VARIABLES does what you might expect
########
.EXPORT_ALL_VARIABLES:
new_contents = "\
hello:\\n\
\\techo \$$(cooly)"
cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END mAKEFILE CONTENTS---"
cd subdir && $(MAKE)
clean:
rm -rf subdir
########
# 5.7
# You can make a list of commands like so:
########
define sweet
echo "hello"
echo "target:" $@
echo "prereqs:" $<
endef
.PHONY: all
all: one
$(sweet)
# Append @ here to append @ to all the commands in sweet: @$(sweet)
one:
touch one
clean:
rm -f one
########
# 6.1
# Reference variables using ${} or $()
########
x = dude
.PHONY: all
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x
########
# 6.2
# Two flavors of variables:
# recursive - only looks for the variables when the command is *used*, not when it's *defined*.
# simply expanded - like normal imperative programming -- only those defined so far get expanded
########
# This will print "later" at the end
one = one ${later_variable}
# This will not
two := two ${later_variable}
later_variable = later
.PHONY: all
all:
echo $(one)
echo $(two)
########
# 6.2
# Simply expanded allows you to append to a variable. Recursive definitions will give an infinite loop error.
########
one = hello
one := ${one} there
.PHONY: all
all:
echo $(one)
########
# 6.2
# ?= only sets variables if they have not yet been set
########
one = hello
one ?= will not be set
two ?= will be set
.PHONY: all
all:
echo $(one)
echo $(two)
########
# 6.2
# Spaces at the end of a line are not stripped, ones at the start are
# To make a variable with a single space, have a variable guard
########
with_spaces = hello # end of line
after = $(with_spaces)there
nullstring =
space = $(nullstring) # end of line
.PHONY: all
all:
echo "$(after)"
echo start"$(space)"end
########
# 6.3
# You can text replace at the end of each space seperated word using $(var:a=b)
# Note: don't put spaces in between anything; it will be seen as a search or replacement term
# Note: This is shorthand for using the "patsubst" expansion function
########
foo := a.o b.o c.o
bar := $(foo:.o=.c)
.PHONY: all
all:
echo $(bar)
########
# 6.3
# You can use % as well to grab some text!
########
foo := a.o b.o c.o
bar := $(foo:%.o=%)
.PHONY: all
all:
echo $(bar)
########
# 6.5
# An undefined variable is actually an empty string :o
########
.PHONY: all
all:
echo $(nowhere)
########
# 6.6
# Use += to append
########
foo := start
foo += more
.PHONY: all
all:
echo $(foo)
########
# 6.7
# You can override variables that come from the command line by using "override".
# Here we ran make with "make some_option=hi"
########
override some_option += additional
.PHONY: all
all:
echo $(some_option)
########
# 6.8
# "define" is actually just a multiline variable defintion. It has nothing with being a function.
# Note here that it's a bit different than having a semi-colon between commands, because each is run
# in a seperate shell, as expected.
########
one = export blah="I was set!"; echo $$blah
define two
export blah=set
echo $$blah
endef
.PHONY: all
all:
@echo "This prints I was set:"
@$(one)
@echo "This does not:"
@$(two)
########
# 6.10
# Variables can be assigned for specific targets
########
all: one = cool
.PHONY: all
all:
echo one is defined: $(one)
.PHONY: other
other:
echo one is nothing: $(one)
########
# 6.11
# You can assign variables for specific target *patterns*
########
%.c: one = cool
blah.c:
echo one is defined: $(one)
.PHONY: other
other:
echo one is nothing: $(one)
########
# 7.1
# Conditional/If statements
########
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
########
# 7.2
# Check if variable is empty
########
nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
ifeq ($(strip $(foo)),)
echo "foo is empty"
endif
ifeq ($(foo),)
echo "foo doesn't even have spaces?"
endif
########
# 7.2
# ifdef does not expand variable references; it just sees if something is defined at all
########
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifdef bar
echo "but bar is not"
endif
########
# 7.3
# Search for a MAKEFLAG
########
bar =
foo = $(bar)
all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
echo "i was passed to MAKEFLAGS"
endif
########
# 8.1 Call functions with $(fn, arguments) or $(fn, arguments)
########
bar := $(subst not, totally, "I am not superman")
bar2 := $(subst not, totally, "I am not superman")
.PHONY: all
all:
@echo $(bar)
@echo $(bar2)
########
# 8.1 If you want to replace spaces or commas, use variables
########
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
.PHONY: all
all:
@echo $(bar)
########
# 8.1 Do NOT include spaces in the arguments after the first. That will be seen as part of the string.
########
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))
.PHONY: all
all:
# Output is ", a , b , c". Notice the spaces introduced
@echo $(bar)
# 8.2, 8.3, 8.9 TODO do something about the fns
# TODO 8.7 origin fn? Better in documentation?
########
# 8.4 foreach takes:
# $(foreach var,list,text) and sets var to each word in list, and outputs outputs that into a "list" of words in text. By list I mean a space seperated sentence of words.
# This appends an exclamation after each word
########
foo := who are you
bar := $(foreach wrd,$(foo),$(wrd)!)
.PHONY: all
all:
@echo $(bar)
########
# 8.5 If: (in a function instead of normal.. call this the functional style)
# Checks if the first argument is nonempty. If so runs the second argument, otherwise runs the third.
########
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
.PHONY: all
all:
@echo $(foo)
@echo $(bar)
########
# 8.6 Call: $(call variable,param,param)
# Sets each of the params as $(1), $(2), etc.
# $(0) is set as the variable name
########
sweet_new_fn = Variable Name: $(0)$ First: $(1) Second: $(2) Empty Variable: $(3)
.PHONY: all
all:
@echo $(call sweet_new_fn, go, tigers)
########
# 8.8 shell - This calls the shell, but it removes newlines!
########
.PHONY: all
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment