Project organisation and Makefiles (11)
3 min read
It's good to know the C language, but it's much better if you also have a great organisation for your project. It permits to easily retrieve your file, to follow the usual methods known by the other programmers that might see your work, and Makefiles will give you more power on the compilation.
Let's transform the structure of this project :
hello_world/ main.c foo.c foo.h
To this :
hello_world/ src/ main.c foo.c include/ foo.h
We separate the code files into the src/ folder (means sources), and the header files into the include/ folder.
Include compiler flag
Let's compile our project :
gcc src/main.c src/foo.c -o program. But, we get this error :
src/main.c:3:10: fatal error: foo.h: No such file or directory 3 | #include "foo.h" | ^~~~~~~ compilation terminated. src/foo.c:1:10: fatal error: foo.h: No such file or directory 1 | #include "foo.h" | ^~~~~~~ compilation terminated.
That's because the "foo.h" file is not located in the src/ folder.
We could include "include/foo.h", but this is too annoying. So, we will use a new compiler flag to easily locate the header files !
gcc src/main.c src/foo.c -I include/ -o program
Then, it works. The "foo.h" has been located in the include/ folder.
The compilation part is annoying, we have to add every new C file as a
A program called
make permits to easily compile all the files of our projects.
We have to create a file named
Makefile at the project's root :
hello_world/ src/ main.c foo.c include/ foo.h Makefile
make file looks like a bash script but with some specificities :
CC = gcc # Specifies the C Compiler we are going to use CC_FLAGS = -I include/ SRC = $(wildcard src/*.c) # Creates a variable `SRC` with all the files in the "src/" folder OBJ = $(SRC:.c=.o) # Creates a variable `OBJ` with all the C files of `SRC` with the ".o" extension instead of ".c" BIN = program build : $(BIN) $(BIN) : $(OBJ) $(CC) $^ -o $@ $(CC_FLAGS) src/%.o : src/%.c $(CC) $< -o $@ $(CC_FLAGS) -c
make build in your command shell.
gcc src/foo.c -c -o src/foo.o -I include/ gcc src/main.c -c -o src/main.o -I include/ gcc src/foo.o src/main.o -o program -I include/
All the C files were compiled into object files, then these object files were compiled into one binary file.
I can't understand this make script... looks so weird !
In a Makefile, we can create variables, declare targets and run some system commands.
<ID> = <value>
Make targets and system commands
<target> : <?prerequisites> <?system commands>
A target can require other targets to work. The "build" target needs the "$(BIN)" target done, and the
$(BIN)target needs the
build : $(BIN) $(BIN) : $(OBJ) ...
System commands are working exactly the same as BASH commands. There are some variables we can use in them.
In target's code, we can use special variables.
$@is the target's identifier
$<is the first prerequisite
$^is all the prerequisites
This script :
program : main.c foo.c gcc $^ -o $@
program : main.c foo.c gcc main.c foo.c -o program
All files in a row !
We use this :
SRC = $(wildcard src/*.c) OBJ = $(SRC:.c=.o) src/%.o : src/%.c $(CC) $< -o $@ $(CC_FLAGS) -c
The same target compiles each C files into an object file of the same name. It uses the same system command.
To make this target happening, we have to require
$(OBJ) as a prerequisite of another target, like there :
$(BIN) : $(OBJ)
Clean the generated files
clean : rm -rf src/*.o $(BIN)
Generated files should never be published with the project source code because they are not the same on every platforms.