# Using Variables in GNU Make (Make series pt.2)

*This is the second article in our* [*Makefile Basics*](https://born2code.hashnode.dev/series/makefile-basics) *series. Check out our* [*first article*](https://born2code.hashnode.dev/a-quick-introduction-to-gnu-make-make-series-pt1) *if you haven't already, as it covers all the groundwork needed so far. All set? Let's go!*

So far we have set up a very basic Makefile which we are using to compile our toy project. As a reminder, this is the project structure we are using:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681055603792/a2aa8122-b890-420d-8103-d168ebdad011.png align="center")

Which we are compiling with the following makefile:

```makefile
all: main
	echo "If a rule is present in all, this will always run 😱"

main: src/a.o src/b.o
	gcc src/a.o src/b.o -Iinclude -o main

src/a.o: src/a.c
	gcc -c src/a.c -o src/a.o -Iinclude

src/b.o: src/b.c
	gcc -c src/b.c -o src/b.o -Iinclude
```

Here are some issues with our current makefile:

1. **Too specific:** if we want to add new files to our project, such as `x.c`, we have to manually create rules for them.
    
2. **No build directory:** currently, we are compiling all our *object* files next to our *source* files. This makes our project *bloated* and *hard to navigate* after each compilation.
    
3. **No c dependency tracking:** ok, this one is a bit more tricky, but Make doesn't know how c works. If we modify our header files `a.h` and `b.h`, Make will not automatically recompile the associated `a.c` and `b.c` files. This is because Make works on a per-file basis and cannot *automatically* detect dependencies across multiple files. Instead, we have to *manually* tell it how to handle dependency tracking in c.
    

In this post, **we will be focusing on the first issue** and making our Makefile more generalized.

---

## Variables in GNU Make

If you want to make your code more generalized in a language such as c, you are probably thinking about using *variables*. The same is true in Make.

> **warning:** in Make, all variables are **strings**. Handling integers and floats is a more complex topic which we will be dealing with in later articles. Don't worry though, except for very advanced Makefiles you won't be needing this functionality.

```makefile
SOME_VAR = value
SOME_LST = value1 value2 value3

all:
    echo $(SOME_VAR)
    echo $(SOME_LST)
```

Let's break this down!

```makefile
SOME_VAR = value
SOME_LST = value1 value2 value3
```

In Make, any variable is comprised of a *series of strings*. To declare a variable, write down its name, followed by an `'='`, and a series of space-separated values.

> By convention, all Make variables should be **UPPER CASE** and [**SNAKE\_CASE**](https://www.freecodecamp.org/news/snake-case-vs-camel-case-vs-pascal-case-vs-kebab-case-whats-the-difference/#snake-case)**.**

```makefile
all:
    echo $(SOME_VAR)
    echo $(SOME_LST)
```

To *access* a variable in Make, use the `$()` syntax, with your variable name between the `(...)`.

* **Single-value** variables will be displayed normally
    
* **Lists** will have their values separated by a `' '`
    

Let's try and use this new-found knowledge to update our Makefile!

Remember to keep your Makefile as general as possible with **variables**. Keep in mind that *any part of your build process might change*, including directory names, binary names, compilation options, and source files.

```makefile
# header directory
DIR_INC = ./include

# c compiler and compilation options
C_COMPILER = gcc
C_ARGS = -Wall -Wextra -Werror -O3 -I$(DIR_INC)

# the name of the binary to genarate
BINARY = ./main

# this is the same thing as 'all: ./main'
all: $(BINARY)

# this is the same thing as './main: src/a.o src/b.o'
$(BINARY): src/a.o src/b.o
	$(C_COMPILER) $(C_ARGS) src/a.o src/b.o -o $(BINARY)

src/a.o: src/a.c
	$(C_COMPILER) $(C_ARGS) -c src/a.c -o src/a.o

src/b.o: src/b.c
	$(C_COMPILER) $(C_ARGS) -c src/b.c -o src/b.o
```

Wohooo! We now have much more control over **how** we compile our source files, and we can change these options at any time without having to modify a hundred different lines of code!

But how can we achieve similar flexibility regarding **which** files are compiled?

```makefile
FILES_SRC = ./src/a.c ./src/b.c
FILES_OBJ = ./src/a.o ./src/b.o
```

Even if we define our files as a list, **how can Make compile them automatically** regardless of their name? Is there some way to **automate** this?

---

## Wildcards

*For information on the* `wildcard` *function in GNU Make, see our upcoming article on Make functions.*

You might remember wildcards from using the terminal in Linux. If you don't, what are you waiting for, start using Linux! In Make, wildcards are represented by the `'%'` symbol.

> **wildcards** represent a ***pattern*** in a string. `'./src/%.c'` represents **any file** that starts with `./src/` and ends with `.c.`

We can use **wildcards** in GNU Make to construct more general rules that target any files with a specific **pattern** in their name.

```makefile
# This is a rule which will target any object file in ./src.
# For that file to be compiled, a file with the same pattern but
# ending in '.c' must exist in ./src.
./src/%.o: ./src/%.c

# './src/a.o' will use this rule. 
# For it to be compiled, './src/a.c' must exist
```

Ok, that's all well and good, but if we have rules that apply to multiple files, **how can we retrieve the name of each file** to compile them?

```makefile
# how can we access the name of the object file being built
# and the source file it depends on?
./src/%.o: ./src/%.c
    gcc -c ??? -o ???
```

---

## Rule Variables

Not everything in Make is intuitive. In fact, a lot of features in Make are very *un*intuitive\*.\* **Rule variables** are such a feature.

> **Rule variables** allow us to reference the name and the dependencies in a rule without knowing them in advance.

* **$@:** the task in a rule
    
* **$^:** *every* prerequisite in a rule
    
* **$&lt;:** the *first* prerequisite in a rule
    

```makefile
task: rule1 rule2 rule3
    echo $@ # prints 'task'
    echo $^ # prints 'rule1 rule2 rule3'
    echo $< # prints 'rule1'
```

I know this makes no sense but try and keep this in your head. A lot of Make is just like this: strange syntax you just *have* to memorize.

Coming back to **wildcards**, we can use **rule variables** to access the name of the files we want to compile

```makefile
./src/%.o: ./src/%.c
    gcc -c $^ -o $@ # same as 'gcc -c a.c -o a.o'
```

Here is our updated Makefile

```makefile
# ================================================================== #
#                              VARIABLES                             #
# ================================================================== #

# source and header directory
DIR_SRC = ./src
DIR_INC = ./include

# source and object files
FILES_SRC = $(DIR_SRC)/a.c $(DIR_SRC)/b.c
FILES_OBJ = $(DIR_SRC)/a.o $(DIR_SRC)/b.o

# c compiler and compilation options
C_COMPILER = gcc
C_ARGS = -Wall -Wextra -Werror -O3 -I$(DIR_INC)

# the name of the binary to genarate
BINARY = ./main

# ================================================================== #
#                                RULES                               #
# ================================================================== #

# this is the same thing as 'all: ./main'
all: $(BINARY)

# this is the same thing as './main: src/a.o src/b.o'
#
# Note that we are now using $(FILES_SRC) instead of './src/a.c' and
# './src/b.c' to compile our project, so any new files we add will
# automatically be included in the compilation 
$(BINARY): $(FILES_OBJ)
	$(C_COMPILER) $(C_ARGS) $(FILES_SRC) -o $(BINARY)

# this rule will be applied to './src/a.c' AND './src/b.c'
$(DIR_SRC)/%.o: $(DIR_SCR)/%.c
    $(C_COMPILES) $(C_ARGS) -c $^ -o $@
```

This is quite a leap in complexity already, so take your time to re-read the code.

We are trying to generalize our Makefile by encapsulating any information we might want to modify later into **variables**. This is especially useful for source files, as we will be adding new files as our project progresses.

To make this process easier, we have created a general rule using **wildcards** and **rule variables** that compiles all our source files into object files. Now, all we need to do is modify `FILES_SRC` and `FILES_OBJ` each time we add new files to our project!

---

## Conclusion

I hope you are beginning to see how Make can be a powerful tool when compiling large projects by automatically generating commands for you as well as tracking changes.

Currently though, we still have to specify our source and object files. In the next article, we will see how we can use **Make Functions** to automate that process.
