EBC Exercise 15 make
Embedded Linux Class by Mark A. Yoder
As your programs become more complex they will spread over multiple files. make is a utility that tells which files depend on which and how to compile them all together into an executable. This exercise walks you through how to create a Makefile.
This exercise is heavily based on TI's OMAP™/DaVinci™ System Integration using Linux Workshop.
In part A of the lab, you will build your first basic Makefile – basically turning command line execution into make rules. In Part B, you will increase the usability of your makefile by adding built-in variables and user-defined variables. This will provide you with a fundamental understanding of how makefiles work.
Lab Prep – Examine the directory contents and app.c
beagle$ cd exercises beagle$ git pull beagle$ cd make beagle$ ls beagle$ gedit app.c
(See here if this doesn't work.)
Part A – Using the Command Line and Creating a Simple Makefile
In this part, we will simply use the GNU compiler (gcc) from the command line to build the “Hello World” example and run it. Then, we’ll place these commands into a basic makefile and run the makefile. In the next part, we’ll use built-in and user-defined variables.
- Build and run “Hello World” from the command line.
To compile app.c, type the following command:
beagle$ gcc –g –c app.c –o app.o
gcc = GNU C compiler (command) –g = symbolic debug (compiler option) –c = (fill in answer below) app.c = file to compile (kind of “dependency” or “prerequisite”) -o = output filename is next (compiler option) app.o = output file (the “target”)
In the above gcc command, name the target, dependency and command.
- Target = _______________
- Dependency = ________________
- Command = __________________
- On your host computer use the man command to look up gcc.
To find the parameters for any standard C functions or Linux commands, you can use the “man” (short for “manual”) command. Let’s try it on gcc:
host$ man gcc
What does the –c option (from the previous step) tell the compiler to do?
To quit the man page, type q.
- Link the object file and produce the final executable.
Next, link the object file (app.o) to create the executable app.arm:
beagle$ gcc –g app.o –o app.arm
Now run the executable:
You should see “Hello World” displayed in the command window. The extension used for the output file (.arm) indicates we are building for the arm (or Beagle).
Note: For those of you who know Linux well, you can skip this explanation. For the rest …
./ before the name of an executable tells Linux to look for the program
in the current directory.
We use this as it is the proper way to specify the path of the file to be run.
- “Clean” the existing executable (.arm) and intermediate (.o) files.
Type the following to remove the files generated by the gcc commands you executed:
beagle$ rm –rf app.arm beagle$ rm –rf app.o
This removal of files mirrors what a “clean” macro or rule might do. We’ll actually add a rule shortly to accomplish this in our makefile.
- Examine “starter” makefile.
The current makefile simply contains comments and placeholders for the code you will write. Using your favorite editor, open the makefile. For example:
beagle$ gedit makefile
- Create rules for app.arm and app.o in your makefile.
Remember, a rule is made up of a target, dependency(ies) and command(s). For example:
target : dependency CMD
Also note that the commands are tabbed over (at least one tab). Create the rule for app.o in the area of the makefile with the header comments specifying the intermediate (.o) rule (as shown below). We’ll help you with the rule for app.o, but app.arm is up to you. For app.o, type in the following rule. We will use the absolute path of gcc for now and later turn it into a variable:
# --------------------------------------------------- # ------ intermeditate object files rule (.o) ------- # --------------------------------------------------- app.o : app.c /usr/bin/gcc –g –c app.c –o app.o
app.o = target app.c = dependency /usr/bin/gcc -g … = command
- Type in the rule for .x.
Next, type in the rule for app.arm ABOVE the rule for app.o in the area specified for the (.x) rule. Make sure you use the –g compiler option in the .x rule.
- Test your makefile.
Close makefile and type the following:
After running make, list the current directory. Do you see a new app.arm executable? Run it.
Do you see “Hello World”? If so, your rules work. Next, let’s add a few more rules…
- Open makefile in a different Linux process.
Stop. Before you open makefile again, try opening it in a different Linux process by typing in the following:
beagle$ gedit makefile &
The “&” tells Linux to open the makefile in a separate process (window). When you edit a file, you can simply click Save, then click inside the terminal window and run it without having to re-open the makefile. Handy – and could save you some time.
- Create a “clean” rule in your makefile.
Whenever you run make, it will search and note the timestamps of the source files and executables and won’t run if everything is up to date. So, it is common to create a “clean” rule that removes the intermediate and executable files prior to the next build.
In the makefile (underneath the comment header for “clean all”), add the following .PHONY rule for “clean” (these are the same commands you used earlier on the command line):
.PHONY : clean clean : rm –rf ___.arm rm –rf _______
.PHONY tells make to NOT search for a file named “clean” because this is a phony target (i.e. it is not a file that needs to be searched for or created). In a large and complex makefile, this actually saves some compile time (plus, it is just good practice to use .PHONY when the target is not an actual file). The two files are the final executable and the intermediate object file.
- Create an “all” rule in your makefile.
When make runs without any rules specified (i.e. you just type “make” on the command line), it will make (by default) the first rule in the makefile. Therefore, it is common to create an “all” rule that is placed first in the makefile. Our example only has one final target (app.arm), so “all” doesn’t make as much sense now.
In the makefile (under the comment header for “make all”), add the following .PHONY rule for “all”:
.PHONY : all all : app.arm
Close makefile and let’s run it…
- Run make to create the executable app.arm.
On the command line, type in the following:
make will probably tell you that the files are “up to date” and there is nothing to do. So, you must run “clean” before you build again. Type:
beagle$ make clean
make runs the first rule in the makefile which is the “all” rule. This should successfully build the app.arm executable.
Note: make assumes the name of the make file is makefile or Makefile. make also looks for the FIRST makefile it finds. So, to be safe, you might want to capitalize Makefile because capital “M” comes before lower-case “m” alphabetically. You can also use a different name for the makefile – e.g. my_makefile.mak. In this case, you need to use the following command to “force” the use of a different make file name:
beagle$ make –f my_makefile.mak
- Run app.arm.
You should see “Hello World” again. Ok, now that we have the simple makefile done, let’s turn it up a few notches…
- Review the different ways to run make.
As a review, you can run make in several ways:
make (makes the first rule in the make file named makefile or Makefile) make <rule> (makes the rule specified with <rule>, e.g. “make clean”) make –f my_makefile (forces the use of a make file named my_makefile)
Part B – Using Built-in and User-Defined Variables
In this part, we will add some user-defined variables and built-in variables to simplify and help the makefile more readable. You will also have a chance to build a “test’ rule to help debug your makefile. This continues Part A which is here.
- Add CC (user-defined variable) to your makefile.
Right now, our arm makefile is “hard coded”. Over the next few steps, we’ll attempt to make it more generic. Variables make your code more readable and maintainable over time. With a large, complex makefile, you will only want to change variables in one spot vs. changing them everywhere in the code.
Add the following variable in the section of your makefile labeled “user-defined vars”:
CC := $(LINUXarm_GCC)
CC specifies the path and name of the compiler being used. Notice that CC is based on another variable named LINUXarm_GCC. Where does this name come from? It comes from an include file named path.mak. Open path.mak and view its contents. Notice the use of LINUXarm_GCC variable and what it is set to.
Whenever you use a variable (like CC) in a rule, you must place it inside $( ) for make to recognize it – for example, $(CC). After adding this variable, use it in the two rules (.x and .o). For example, the command for the .x rule changes from:
gcc –g app.o –o app.arm
$(CC) -g app.o –o app.arm
- Apply this same concept to the .o rule.
- Add include for path.mak.
In the “include” area of the makefile, add the following statement:
- Test your makefile: clean, make and then run the executable.
- Add CFLAGS and LINKER_FLAGS variables to your makefile.
Add the following variables in the section of your makefile labeled “user-defined vars”:
CFLAGS := -g LINKER_FLAGS := -lstdc++
CFLAGS specifies the compiler options – in this case, -g (symbolic debug). LINKER_FLAGS will tell the linker to include this standard library during build. (The example option –lstd++ specifies the linker should include the standard C++ libraries.)
Use these new variables in the .x and .o rules in your makefile.
- Test your makefile.
- Add built-in variables to your .o rule.
Make contains some built in variables for targets ($@), dependencies ($^ or $<) and wildcards (%). Modify the .o rule to use these built-in variables.
The .o rule changes from:
app.o : app.c $(CC) $(CFLAGS) –c app.c –o app.o
%.o : %.c $(CC) $(CFLAGS) –c _____ -o _____
Because we only have ONE dependency, use the $< to indicate the first dependency only. Later on, if we add more dependencies, we might have to change this built-in symbol. % is a special type of make substitution for targets and dependencies. The %.o rule will not run unless a “filename.o” is a dependency to another rule (and, in our case, app.o is a dependency to the .x rule – so it works).
- Add built-in variables to your .x rule.
The .x rule changes from:
app.arm: app.o $(CC) $(CFLAGS) app.o -o app.arm
app.arm: app.o $(CC) $(CFLAGS) $(LINKER_FLAGS) _____ -o _____
- Don’t forget to add the additional LINKER_FLAGS to the .x rule.
- Test makefile.
- Add a comment to your .x rule.
Comments can be printed to standard I/O by using the echo command. In the .x rule, add a second command line as follows:
@echo; echo $@ successfully created; echo
The @echo command tells make to echo “nothing” and don’t echo the word “echo”. So, effectively, this is a line return (just like the echo at the end of the line). Because built-in variables are valid for the entire rule, we can use the $@ to indicate the target name.
Test makefile and observe the echo commands. Did they work? As usual, you might need to run “make clean” before “make” so that make builds the executable.
- Add “test” rule to help debug your makefile.
Near the bottom of makefile, you’ll see a commented area named “basic debug for makefile”. Add the following .PHONY rule beneath the comments:
.PHONY : test test: @echo CC = $(CC)
This will echo the path and name of the compiler used. Try it. Does it work? You can also add other echo statements for CFLAGS and LINUXarm_GCC. This is a handy method to debug your makefile.
Close your makefile when finished.
Embedded Linux Class by Mark A. Yoder