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 in SOURCES 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 flag IS_AM572x is 1. If 1, then we have detected that we are currently on a BeagleBone AI and will execute the rest of the commands under this ifeq.
  • 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;
}

References

  1. GNU Make Manual
  2. Text-Functions
  3. tutorialspoint:, makefile_macros,makefile_dependencies
  4. stackoverflow1
  5. Automatic Variables
  6. Open Source Manual Book for Make PDF