Make
This is a short blog to cover how to use the Makefile in your C/C++ projects! I will also cover how you can use the outputs of your shell commands in the Makefile itself.
The main purpose of this particular blog will be to write a Makefile that can:
- Detect which BeagleBone device it is running on.
- Conditional Compile a C++ program depending upon which BeagleBoard it is.
- Demonstrate
ifeq
usage in Make. - Study the usage of a few Text-Functions in Make.
What is a Makefile
You need a file called a "makefile" to tell ‘make' what to do. Most often, the makefile tells ‘make' how to compile and link a program.
Syntax of Make
A simple Makefile consists of "rules" with the following shape:
TARGET ... : PREREQUISITES ...
RECIPE
-
A "target" is usually the name of a file that is generated by a program; examples of targets are executable or object files. A target can also be the name of an action to carry out, such as ‘clean'.
-
A "prerequisite" is a file that is used as input to create the target. A target often depends on several files.
-
A "recipe" is an action that
make
carries out. A recipe may have more than one command, either on the same line or each on its own line.
Note: you need to put a tab character at the beginning of every recipe line!
You will also see in many places symbols like these: $<
, $^
or $@
.
Let's see what each one means.
Eg. consider the following snippet of code.
all: library.cpp main.cpp
In this case:
- $@ evaluates to all
- $< evaluates to library.cpp
- $^ evaluates to library.cpp main.cpp
Read more on these automatic variables here
Now, Let's have a look at our Makefile:
CC = gcc
DEFAULT_COMMON_FLAGS = -Werror -Wall -std=c++11 -g # Flags: 1 for AM572x || 0 for AM33xx
EXECUTABLE = bin/runfile
SRC = board_detect.cpp
HEADER = board_detect.h
Here, We have defined the compiler as gcc by assigning it to the variable CC.
-
DEFAULT_COMMON_FLAGS
variable tells us what parameters we are going to pass to gcc. - The
EXECUTABLE
variable as the name suggests, contains the location/name of the compiled binary. -
SRC
is the name of source file.
IS_AM572x :=$(shell grep -q AI model && echo 1 || echo 0)
Here above, we have compared the contents of model
with the string AI
and if we detect AI then variable IS_AM572x
is set to 1
.
The keyword here is shell
that helps us write bash syntax in this Makefile.
all: $(SRC) $(HEADER)
echo $(cur_dir)
ifeq ($(IS_AM572x),1)
@echo AM572x chip detected
$(CC) $(DEFAULT_COMMON_FLAGS) $(SRC) -DIS_AM572x -o $(EXECUTABLE)
else ifeq ($(IS_AM572x),0)
@echo AM33xx chip detected
$(CC) $(DEFAULT_COMMON_FLAGS) $(SRC) -o $(EXECUTABLE)
else
@echo Not Recognized HW
endif
Now, if we write make all
in the command console, this part of the code is what essentially is executed.
-
all: $(SOURCES)
: this means that all which is the target has the value which is stored inSOURCES
variable as it's prerequisite. -
ifeq ($(IS_AM572x),1)
: Mind that there is no tab before this line. If you do accidentally put in a tab that will produce errors. What this line does is the it checks whether the flagIS_AM572x
is1
. If1
, then we have detected that we are currently on a BeagleBone AI and will execute the rest of the commands under thisifeq
. - The
else ifeq
is almost like the else-if equivalent and this performs a check if it's a BeagleBone Black. - Finally, the
else
points to any other situations where the hardware is either not detected or is a device that is not currently supported by our code.
run: $(EXECUTABLE) all
./$< # This will give error if you have not compiled
debug: all $(EXECUTABLE)
gdb $^ # This will execute all: first then run the debugger
In the above code snippet, I have demonstrated the use of automatic variables in Make.
clean: $(EXECUTABLE)
rm $(EXECUTABLE)
Last, but not least, when we type in make clean
we clean the working directory off of any old binary files generated
If you want to test this on your BeagleBone, a slight change will be required in the model
path where you will have to write
/proc/device-tree/model
Extra Tips
To pass a string from the Makefile to the cpp program, use g++ hello.cpp -DdhrFlag=\"DHRUVA\" -o argtest
where the program hello.cpp
is:
#include <iostream>
#include <string.h>
using namespace std;
int main() {
#ifdef dhrFlag
string dhr = dhrFlag;
cout << dhr;
#endif
cout << "\nHello\n";
return 0;
}