OK, here is a short introduction to make and makefiles:
Compiling, linking and running programs is a quite repetitive task: every change in any program file will make it necessary to recompile, relink and re-run the program. Well, in fact: no. Yes, you will have to run the program again, but you do not need to compile and link all the program sources - just the relevant parts. What the relevant parts are in each case is called a "dependency": object files are depending on certain source files, libraries and binaries are depending on the objects. What you do in a makefile is to create rules, how to get a depending file from its dependencies.
Lets try an example: we have 5 source files "src1", "src2", etc. and 3 header files "h1", "h2", "h3". Together they should form a library (src2-4) and a binary (src1 and src5). Lets say our compile command is "compile" and our linkage editor is called "link". "link -b" will create binaries, "link -l" will create libraries.
The first you have to do is to consider what is depending on what: every source file goes into one object file, but maybe not every header is used in every source. Lets say "h1" is used in every source, "h2" only in src4 and "h3" only in src2 and src4. The resulting objects will go into two files (see above, a binary and a library). Here are these dependencies:
src1, h1 ---------> obj1 --------------+
src2, h1, h3 ----> obj2 --+ |
src3, h1 --------> obj3 --+--> lib1 |
src4, h1, h2, h3 -> obj4 --+ |
src5, h1 ---------> obj5 --------------+--> bin1
As you see every entry on the right side is depending on one or several entries on the left. "make" now does the following: it compares file dates on the files on the left and the right side and if a target (the right side) is older than any of its dependencies it recreates this target. For instance, if "src1" would change, "make would create "obj1" anew. Now, because "bin1" has a changed dependency, it is also recreated, but "lib1" is not, because nothing has changed in its path. Would you have changed "h1", everything would have been recreated.
We are now ready to create our makefile. A makefile consists of rules, which in turn have three parts:
- the target: the file which has to be created
- the dependencies: all the files necessary to create the target
- the action: the command(s) necessary to create the target from the dependencies.
The format is:
target: dep1 dep2 dep3 ....
command1
command2
command3
...etc.
So we write first the rules to create the objects from the sources and headers:
# Sample Makefile v1: compiler calls
obj1: src1 h1
compile src1 h1 --output obj1
obj2: src2 h1 h3
compile src3 h1 h3 --output obj2
obj3: src3 h1
compile src3 h1 --output obj3
obj4: src4 h1 h2 h3
compile src1 h1 h2 h3 --output obj4
obj5: src5 h1
compile src5 h1 --output obj5
Now all the sources get compiled, but not linked yet. Let us add these rules too:
# Sample Makefile v2: compiler calls and linkage editor
all: bin1 lib1
obj1: src1 h1
compile src1 h1 --output obj1
obj2: src2 h1 h3
compile src3 h1 h3 --output obj2
obj3: src3 h1
compile src3 h1 --output obj3
obj4: src4 h1 h2 h3
compile src1 h1 h2 h3 --output obj4
obj5: src5 h1
compile src5 h1 --output obj5
lib1: obj2 obj3 obj4
link -l obj2 obj3 obj4 --output lib1
bin1: obj1 obj5
link -b obj1 obj5 --output bin1
You will notice the rule "all", which has no commands at all. This is the default rule "make" will try to execute if no other rule is given as parameter.
You can name this file simply "Makefile" and call "make" without any parameter. "make" will look for a rule file called "Makefile" (capitalized "M") by default and execute the "all" rule and all necessary rules for this target in it if it finds one. But you can also call one of its rules, for instance "make bin1" or "make obj3". It will execute only this rule then (if that target is not up to date).
Now, ok, the program gets compiled and linked, but is that all "make" can do?
No, not at all. It is true in was invented to compile and link programs, but you can do everything which you can press into such rules. For instance you can create an installation rule. For the installation of our example program the binary should go to "/usr/local/bin/bin1.exe" and the library to "/usr/local/lib/bin1/lib1.a". Lets add such a rule:
# Sample Makefile v3: compiler calls, linkage editor and installation
all: bin1 lib1
obj1: src1 h1
compile src1 h1 --output obj1
obj2: src2 h1 h3
compile src3 h1 h3 --output obj2
obj3: src3 h1
compile src3 h1 --output obj3
obj4: src4 h1 h2 h3
compile src1 h1 h2 h3 --output obj4
obj5: src5 h1
compile src5 h1 --output obj5
lib1: obj2 obj3 obj4
link -l obj2 obj3 obj4 --output lib1
bin1: obj1 obj5
link -b obj1 obj5 --output bin1
install: bin1 lib1
mkdir -p /usr/local/bin 2>/dev/null
mkdir -p /usr/local/lib/bin1 2>/dev/null
cp bin1 /usr/local/bin/bin1.exe
cp lib1 /usr/local/lib/bin1/lib1.a
Notice that there is no file "install" and it isn't created in the process. It is perfectly valid to have rules where the target doesn't exist. As this rule ins not depending on "all" you can still compile and link your program with "make" (without parameter) and install it by "make install".
It is also not necessary for a target to have dependencies: we could also add the following rule, to clean our compile-directory of old objects and binaries or to force a complete recreation of everything in a new run:
clean:
rm -f *o
rm -f bin1
rm -f lib1
"make clean" would no remove all the compiler leftovers from previous make-runs.
To add even more flexibility to your makefiles, suppose you would want to change the compiler. Right now you use "compiler", but suppose there is a new compiler "comp_new", you would want to use. And suppose, too, that you want to use certain default compiler flags when you create your objects. You could change youe makefile in every line of course, but you can also use variables. The basic syntax for the assignment and the usage of a variable is:
# the assignment:
VARIABLE = VALUE
# the usage (this will get replaced with the content upon execution):
$(VARIABLE)
So we change the makefile a last time:
# Sample Makefile v4: compiler calls, linkage editor and installation
# added variables
# switch between these two lines to change compilers:
# COMPILE = compile
COMPILE = comp_new
# our compilers might need different switches:
# CFLAGS = -x1 -y1 -z1
CFLAGS = -X2 -Y2 -Z2
LFLAGS = -a -b -c
obj1: src1 h1
$(COMPILE) $(CFLAGS) src1 h1 --output obj1
obj2: src2 h1 h3
$(COMPILE) $(CFLAGS) src3 h1 h3 --output obj2
obj3: src3 h1
$(COMPILE) $(CFLAGS) src3 h1 --output obj3
obj4: src4 h1 h2 h3
$(COMPILE) $(CFLAGS) src1 h1 h2 h3 --output obj4
obj5: src5 h1
$(COMPILE) $(CFLAGS) src5 h1 --output obj5
lib1: obj2 obj3 obj4
link -l $(LFLAGS) obj2 obj3 obj4 --output lib1
bin1: obj1 obj5
link -b $(LFLAGS) obj1 obj5 --output bin1
install: bin1 lib1
mkdir -p /usr/local/bin 2>/dev/null
mkdir -p /usr/local/lib/bin1 2>/dev/null
cp bin1 /usr/local/bin/bin1.exe
cp lib1 /usr/local/lib/bin1/lib1.a
clean:
rm -f *o
rm -f bin1
rm -f lib1
"make" is not limited to programming at all. I have seen people using it to do their backup, process automated mails and what not. The limitation is only what you are able to imagine.
For this you will probably find some more features (which i have intentionally left out for clarity) handy: rules for processing whole classes of files, so that you don't have to write the rule for every file anew, complex variables which change their contents depending on which file has invoked the rule (like: "$<", "$@", and so on) , etc., etc..
I suggest reading the man page and probably one or the other book about "make" (yes, there have been books written about the topic) to find out more. This here is just meant as a starter and I'll keep it at that.
I hope this helps.
bakunin