Introduction
Make is a program that's used to build other programs or documents. Make is suitable for any process that has intermediate steps that may or may not be complete.
Make doesn't rebuild things that are already up to date. Further more, it knows how to build the intermediate targets. You instruct Make with a Make file.
There's more than one Make program. The most common that C/C++ developers encounter is GNU Make. I will describe GNU Make with examples first, then look at BSD Make and end with some commentary on the differences.
Getting Started with GNU Make
GNU Make has a lot of rules built into it. They're called built in rules. Lets look at an example, hello.c
1 2 3 4 5 6 7
|
#include <stdio.h>
int main()
{
printf("hello world\n:");
return 0;
}
| |
To build the program, simply type:
It runs:
So GNU Make already knows that a program can be constructed from a .c file, and it knows how to compile and link it.
Let's try another one, this time with some more files. We'll need a makefile for this one. A GNU Make file can be called GNUmakefile, Makefile, or makefile. I will use GNUmakefile in these examples as we'll be looking at BSD Make later.
calc.c
1 2 3 4 5 6 7 8
|
#include <stdio.h>
#include "engine.h"
int main()
{
printf("%d\n", calc("2+3"));
return 0;
}
| |
engine.h
1 2 3
|
#pragma once
int calc(const char* expression);
| |
engine.c
1 2 3 4 5 6
|
#include "engine.h"
int calc(const char *expression)
{
return 5;
}
| |
GNUmakefile
1 2 3 4 5
|
OBJ = calc.o engine.o
calc: $(OBJ)
$(OBJ): engine.h
| |
We define a macro OBJ, that has the list of our object files. Remember, GNU Make knows how to create an object file from a C program.
Next, we tell GNU Make that our program, calc, is made from these OBJ files. When we run make, it does:
cc -c -o calc.o calc.c
cc -c -o engine.o engine.c
cc calc.o engine.o -o calc |
Finally, we tell GNU Make that if engine.h changes, we must recompile the object files. This is called a dependency.
Now, let's introduce C++. Lets define one new file: calcpp.cc
1 2 3 4 5 6 7 8 9 10 11
|
#include <iostream>
extern "C"
{
#include "engine.h"
}
int main()
{
std::cout << calc("2+3") << std::endl;
return 0;
}
| |
This is our modified GNUmakefile
1 2 3 4 5 6
|
OBJ = calcpp.o engine.o
calc: $(OBJ)
$(LINK.cc) $^ -o $@
$(OBJ): engine.h
| |
So what's changed? We need to tell GNU Make that we're linking a C++ program, We use the GNU Make macro LINK.cc to tell it that we're linking a C++ program.
$^ means all the dependencies, calcpp.o and engine.o in this case.
$@ means the target we're making, calc in this case.
When we run make, it does:
c++ -c -o calcpp.o calcpp.cc
c++ calcpp.o engine.o -o calc |
What about compiling engine.c? Well, we compiled it earlier and make knows it doesn't need to be recompiled.
BSD Make
BSD Make predate's GNU Make and it's a little different. BSD Make doesn't have a lot of built in rules. It needs to be told quite a lot in the make file. As you've seen above, GNU Make know quite a lot.
BSD Make knows about dependencies and rules to build those dependencies, but it doesn't have all those built in default rules, so it needs to be told. Instead of being told everything, a system that uses BSD Make provides a set of include files that define how things are done on that system. For example, on FreeBSD, if you want to write a program, you include <bsd.prog.mk>. This has all the rules needed to compile, install, uninstall, archive … while develop your program.
A comparison of GNU Make and BSD Make
Remember, GNU is the Free Software Foundation, toolset. GNU means GNU is Not Unix. This is important. When the GNU project was started, it's aim was to provide freely available equivalents of Unix programs. And they improved them by adding helpful and consistent behaviour.
BSD Make just knows about dependencies, targets, rules and macros. GNU Make adds built in rules to that mix to make it easier for the developer.
It turns out that details of systems are conflicting and cannot be known in advance. BSD Make deals with system dependencies by having the system define its parameters. GNU Make's built in rules turn out to be inadequate. The Auto Tools project (AutoConf, AutoMake, LibTools) was developed to compensate for this. It works by discovering the system at configuration time, which then generates makefiles.
I prefer the BSD solution. It's clear that in this example it's better designed. I find that's generally true of BSD.
An Example
This example uses 3 C++ source files (main.cc, config.cc, dispatch.cc), and 2 header files (config.hpp, dispatch.hpp). I haven't added the dependencies, in the real world, they're auto-generated.
GNUmakefile
1 2 3 4 5 6
|
CXXFLAGS = -g
all: goodlistener
goodlistener: main.o config.o dispatch.o
$(LINK.cc) -o $@ $^
| |
BSDmakefile
1 2 3 4 5 6 7
|
PROG_CXX = goodlistener
SRCS = main.cc config.cc dispatch.cc
MAN = goodlistener.1
MAKEOBJDIR = work
CXXFLAGS=-g
.include <bsd.prog.mk>
| |