DEV Community

bbssamuray
bbssamuray

Posted on

Make Basics Cheat Sheet

Even though its creation dates back to late 70s, make is still a widely used and useful tool.
While the syntax of makefiles may seem weird at first, I hope this entry will make it easier to understand the core principles of them.

Basic Behaviour

Makefiles mostly consist of rules and their recipes.
A simple rule looks like this:

foo.txt:bar.txt
    @echo "Hello World!"
    cat bar.txt >> foo.txt
Enter fullscreen mode Exit fullscreen mode

In a rule, left side of the colon specifies the target file created by the rule, right side specifies the dependencies needed. (Generally called prerequisites)
Everything after the first line is its recipe, all of the actual commands required in order to create the target.
Important note:
Recipes must be indented with a tab in order for a makefile to function, but dev.to replaces my tabs in the editor with 4 spaces.

If you;

  • Put the example above in a file called Makefile
  • Create a file named bar.txt
  • Run make

You would notice make has created a file called foo.txt by following that file's recipe.

If you try to run make once again, it would output this:

make: 'foo.txt' is up to date.
Enter fullscreen mode Exit fullscreen mode

This brings us to our next point:
Make only runs rules if the dependencies have changed.
It compares the target's and depedencies' last edit dates, if the dependencies have a newer last edit date that would mean rules needs to run.
This helps with big projects, as you don't have to compile the whole project when making small changes.

Complicating Things

Now that we got the basic structure out of the way we can delve into a bit more useful example and add to it as we go.

mainexe:main.o
    gcc -o mainexe main.o

main.o:main.c
    gcc -c main.c
Enter fullscreen mode Exit fullscreen mode

Unless stated otherwise, make starts with the first target in the file. That would be mainexe:main.o in our case.
Since that rule's dependency main.o doesn't exist yet, make finds main.o's rule, which is main.o:main.c.
It first creates main.o and then creates mainexe using their corresponding recipes.

Macros

You can define variables that keep string information in your makefiles.

CC=gcc
CFLAGS = -Wall -O2
DATESTRING = Today is: ` date "+%Y.%m.%d" `
#This is a comment

mainexe:main.o
    @echo $(DATESTRING)
    $(CC) -o mainexe main.o

main.o:main.c
    $(CC) $(CFLAGS) -c main.c
Enter fullscreen mode Exit fullscreen mode

This way user can substitute their own compiler and flags easily.
Also, you might have noticed there can be shell commands in macros too, everything between two back sticks get treated as shell commands.

@ symbol before commands tells make to not print the command itself.
So in our case, it prints:

Today is: 2022.09.12
Enter fullscreen mode Exit fullscreen mode

instead of:

echo Today is: ` date "+%Y.%m.%d" `
Today is: 2022.09.12
Enter fullscreen mode Exit fullscreen mode

Pattern Rules

If you have more than one .c files, you don't have to write a compile rule for all of them one by one.

CC=gcc
CFLAGS = -Wall -O2
DATESTRING = Today is: ` date "+%Y.%m.%d" `
#This is a comment

mainexe:main.o functions.o
    @echo $(DATESTRING)
    $(CC) -o mainexe main.o functions.o

%.o : %.c
    $(CC) $(CFLAGS) -c $<

Enter fullscreen mode Exit fullscreen mode

This last rule is called a "Pattern Rule". It is like a general rule for files that have a .o extension.
$< is an internal macro that refers to the first dependency
Even though it is not used here:

  • $@ refers to the target file.
  • $? refers to the dependencies that require attention.
  • ?^ refers to all of the dependencies.

Search Directory

Notice how we need to edit the makefile every time we add a new .c file, that can be taken care of easily with automation too.

CC=gcc
CFLAGS = -Wall -O2
DATESTRING = Today is: ` date "+%Y.%m.%d" `
C_FILES := $(wildcard *.c)
OBJS := $(patsubst %.c, %.o, $(C_FILES))
#OBJS := $(C_FILES:.c=.o)
#These two lines of code do the same thing

mainexe:$(OBJS)
    @echo $(DATESTRING)
    $(CC) -o mainexe $(OBJS)

%.o : %.c
    $(CC) $(CFLAGS) -c $<

Enter fullscreen mode Exit fullscreen mode
  • We get all of the file names with the .c extension in the current directory and store it in C_FILES variable
  • We replace .c with .o and store the names in OBJS variable
  • We use OBJS variable as the dependency of mainexe rule

Another thing to note is introduction of :=.
When you initialize DATESTRING with = that is called a "Lazy evaluation". It is kept as is and doesn't get expanded until it is needed.
With := they are expanded instantly, you are probably used to this from other languages.
?= sets variables only if they aren't set yet.

Phony

Phonies are functions that don't refer to files.

CC=gcc
CFLAGS = -Wall -O2
DATESTRING = Today is: ` date "+%Y.%m.%d" `
C_FILES := $(wildcard *.c)
OBJS := $(patsubst %.c, %.o, $(C_FILES))

.PHONY:all
all:mainexe

mainexe:$(OBJS)
    @echo $(DATESTRING)
    $(CC) -o mainexe $(OBJS)

%.o : %.c
    $(CC) $(CFLAGS) -c $<

.PHONY:clean
clean:
    -rm -f $(OBJS)

Enter fullscreen mode Exit fullscreen mode

Here, all and clean rules don't refer to a file in the system. We can tell that to make by declaring them as "Phony" rules.
Clean function can be called with make clean, which will delete all of the objects created while building.
- sign before the rm command suppresses errors.

Important Caveats

  • Every command runs in their own shell. If you want to print the contents of the parent directory, you need to do:
printParent:
    cd .. && \
    ls
    #Correct
Enter fullscreen mode Exit fullscreen mode

instead of:

printParent:
    cd ..
    ls
    #Incorrect
Enter fullscreen mode Exit fullscreen mode
  • First rules in makefiles are usually called all. It is nothing more than tradition, I thought it was worth mentioning to avoid confusion.

Conclusion

I hope this guide helped you learn the basics of make. This barely even scratches the surface of what you can do with it.
To learn more, you can check out this much more comprehensive tutorial:
https://makefiletutorial.com/

Top comments (0)