Simple C programs involving just one source code file can be compiled easily without the need for a makefile. For example, if you have a file called hello.c you can compile and create an executable program in one step as follows:
gcc hello.c -o hello
The compiler (gcc) reads the source file, compiles it, links in any required library functions and generates an output executable program (called hello) in one step.
If you have a number of source files, you can simply compile and link them into an executable program called myprog as follows:
gcc file1.c file2.c file3.c -o myprog
This approach has some can be a little inefficient for more complex programs. If your program consists of lots of source files only one of which has changed since your last build it doesn’t really make sense to recompile all files. If you need to pass lots of command line arguments to the compiler (or linker) it can become a little tedious to retype them each time you want to build. A shell script (or batch file) could solve some of these issues however the GNU tool suite provides a utility that has been developed specifically for the purpose of managing the building of complex programs: make.
A sample Makefile for ARM Cortex development is shown below. It is used to build a program consisting of two source files : init.c and main.c.
# Specify the compiler to use CC=arm-none-eabi-gcc # Specify the assembler to use AS=arm-none-eabi-as # Specify the linker to use LD=arm-none-eabi-ld # Specify the location of the gcc libraries LIBSPEC=-L /usr/local/gcc-arm-none-eabi/lib/gcc/arm-none-eabi/4.9.3/armv6-m # Specify C-compiler flags CCFLAGS=-mthumb -mcpu=cortex-m0 -g # List the object files involved in this project OBJS= init.o \ main.o # The default 'target' (output) is main.elf and it depends on the object files being there. # These object files are linked together to create main.elf main.elf : $(OBJS) $(LD) $(OBJS) $(LIBSPEC) -T linker_script.ld -lgcc --cref -Map main.map -nostartfiles -o main.elf # The object file main.o depends on main.c. main.c is compiled to make main.o main.o: main.c $(CC) -c $(CCFLAGS) main.c -o main.o init.o: init.c $(CC) -c $(CCFLAGS) init.c -o init.o # if someone types in 'make clean' then remove all object files and executables # associated wit this project clean: rm $(OBJS) rm main.elf
Comments begin with a ‘#’ symbol. The file creates and uses the following variables:
CC -> The name of the C compiler to use
AS -> The name of the assembler to use
LD -> The name of the Linker to use
LIBSPEC -> The location of the gcc libraries
CCFLAGS -> Command line arguments for the C compiler
OBJS -> The list of object files.
This Makefile defines four targets: main.elf, main.o, init.o and clean.
The first of these is main.elf and is therefore the ‘default’ target. What this means is that if you type make on its own in a command shell (in the same directory as the file), it will attempt to execute the commands that are associated with the main.elf target. You can also specify which target you want built by specifying it on the command line. For example, to build init.o simply type:
To make the ‘clean’ target (which removes object files and executables) type:
Lets examine the main.elf target more closely:
main.elf : $(OBJS) $(LD) $(OBJS) $(LIBSPEC) -T linker_script.ld --cref -Map main.map -nostartfiles -o main.elf
The first line states that main.elf depends upon the set of object files listed in the variable OBJS. If these files have a timestamp that is later than main.elf then the command that follows is executed. If the timestamps are earlier than main.elf then it would appear that nothing has changed (or your clock is wrong) so main.elf is not rebuilt needlessly.
Should something have changed then the second line is executed. In this case, the linker program is invoked with the following arguments:
$(OBJS) -> The list of object files to link together
$(LIBSPEC) -> The location of the gcc libraries
-T linker_script.ld -> The name of the linker script file
–cref -Map main.map -> Generate map file with a cross reference listing (useful for seeing where all your memory has gone :o)
-nostartfiles -> Do not use the standard system startup files when linking. This allows you full control of the inialization process. In this example, init.c contains the startup code.
-o main.elf -> Name the output file main.elf
The make utility does more than just check to see if the object files have changed recently. It will also check to see if the C source files have changed and if so, will recompile them into object files. Looking at one of the object file targets in detail:
init.o: init.c $(CC) -c $(CCFLAGS) init.c -o init.o
If init.c has recently changed, init.o will be recompiled using line 2. Lets substitute all of the variables in this line and see what’s going on:
arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m0 -g init.c -o init.o
The compiler in question is arm-none-eabi-gcc. This outputs ARM code for a target system with no operating system (hence ‘none’) and generates function calls using the ARM Embedded Application Binary Interface calling convention (see see here).
The compiler options are as follows:
-c -> Only compile, don’t link. Linking will be carried out when building the main.elf target
-mthumb -> Generate ARM Thumb code rather than ARM ‘native’ machine code. The Cortex processors only understand Thumb code while other variants can operate with different instruction sets.
-mcpu=cortex-m0 -> Generate ARM Thumb (machine code) appropriate for the Cortex M0 processor
-g -> include debugging information
-o init.o -> name the output object file init.o
If you need to add files to a project you simply create a new object file target for the source file and add the object file to the $(OBJS) list.