Make Basics Cheat Sheet
Published: 2022-09-11 23:06:48

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
```
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.
```
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
```

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
```
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
```
instead of:

```
echo Today is: ` date "+%Y.%m.%d" `
Today is: 2022.09.12
```

#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 $<

```
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 $<

```
- 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)

```
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
```

instead of:
```
printParent:
	cd ..
	ls
	#Incorrect
```

- 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/