If you want to share code for several binaries you would normally organize this
code as a shared or static library. This is usually how I do it, but recently I
came across a situation where this actually became a clumsy way of doing it.
The real life scenario is the following. We have a binary that make a lot of
syntactical and type checks for a configuration file format. If the
configuration file is ok the binary starts to generate system configuration
files. We will label this binary as tool. Additional to the tool binary we want
a binary that only do the checks and is not able to generate files and that
binary is labeled tool-simple. The code base is a huge directory tree with many
Makefiles, like this:
|-Makefile
|-pkg/
|-src/
| |-proj1/
| | |-Makefile
| | |-main.c
| |
| |-proj2/
| | |-Makefile
. . .
. . .
. . .
In this case proj1 contains the source code for the binary called tool. However,
proj1 needs to be built in two binary different flavors from the exact same
source files but with different define arguments to gcc. This could have easily
been done by running make twice (traverse the build tree twice) in the normal
case. However, because the binaries will be packaged in the same tgz file we can
only traverse the build tree once if we want to make a sane build environment.
The solution is to create a structure for project q that look like this:
|-src/
| |-proj1/
| | |-Makefile
| | |-tool/
| | | |-Makefile
| | | |-cmd.c
| | | |-cmd.h
| | | |-common.inc
| | | |-main.c
| | |
| | |-tool-simple/
| | | |-Makefile
Lets consentrate on the files in the project1 dirextory tree.
proj1/Makefile
1
2
3
4
5
6
7
8
9 | all:
make -C tool all
make -C tool-simple all
clean:
make -C tool clean
make -C tool-simple clean
#eof
|
There is nothing special about this Makefile and that is the nice thing about it.
Lets keep things simple and clean where we can and all the special cases
isolated.
proj1/tool/cmd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | #include "cmd.h"
#include <stdio.h>
void tool_func() {
printf("Do tool stuff\n");
}
void simple_func() {
printf("Do simple stuff\n");
}
void common_end(){
#ifdef SIMPLE
printf("Simple end\n");
#else
printf("Tool end\n");
#endif
}
//eof
|
proj1/tool/cmd.h
1
2
3
4
5
6
7
8
9
10
11 | #ifndef CMD_H
#define CMD_H
void tool_func();
void simple_func();
void common_end();
#endif
//eof
|
proj1/tool/main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include "cmd.h"
#include <stdio.h>
int main(int argc, char** argv) {
simple_func();
#ifndef SIMPLE
printf("Tool working ...\n");
tool_func();
#endif
common_end();
return 0;
}
//eof
|
The only thing interesting in these 3 C files (main.c, cmd.c and cmd.h) are the
ifdefs and ifndefs that are used to include and exclude code depending on the
build. From the real scenario the shared code for the binaries are rather 80%
than the few percent that are used in these files.
proj1/tool/common.inc
1
2
3
4
5
6 | CC=gcc
OBJ = main.o \
cmd.o
#eof
|
The file common.inc mainly contains the list of all object files that should be
produced. In this case we have also included the common definition of CC.
proj1/tool/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | include common.inc
TARGET=tool
all: $(TARGET)
$(TARGET): $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) -Wall -c -o $@ $<
clean:
rm -f $(TARGET) $(OBJ)
#eof
|
The Makefile in the tool directory is a very normal Makefile with the include of
common.inc as the only deviation.
proj1/tool-simple/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | include ../tool/common.inc
vpath %.h ../tool
vpath %.c ../tool
TARGET=tool-simple
all: $(TARGET)
$(TARGET): $(OBJ)
$(CC) -o $@ $^
%.o: %.c
$(CC) -Wall -c -o $@ $< -DSIMPLE
clean:
rm -f $(TARGET) $(OBJ)
#eof
|
The interesting stuff is found in the Makefile contained in the tool-simple
directory In this Makefile we also include the common.inc file from the tools
directory. The tools-simple directory does not contain any C files that can be
compiled to the object files declared in common.inc though. We will compile the
.c and .h files from the tools directory in to the tools-simple directory
with the help of the vpath
directive. Note the lower case of the
directive vpath
because vpath
and VPATH
are two different directives. VPATH
specifies a list of directories
that make should search. Similar to the VPATH
directive, but more
selective, is the vpath
directive , which allows you to specify a
search path for a particular class of file names: those that match a particular
pattern. Also note that we define SIMPLE in this Makefile with the argument
-DSIMPLE when we build our object files for tool-simple By defining SIMPLE we
will not compile some code as you can see from the C files above.
Go and have fun with some Makefiles!