多目录Makefile(库及分层目录)

本文代码虽简单,但涉及比较复杂的各种调用关系,欲研究者需有耐心及清醒头脑。切切!

背景交待:
1、正在移植U-Boot,并对其源代码进行了一些分析,感觉它的Makefile十分强劲;
2、以前写的Makefile模板不合适多层目录;
3、研究一下多个库之间相互调用的问题。

平台及测试环境介绍:
1、fc9系统,i386平台,gcc版本4.3.2,使用Secure CRT连接linux系统,数据均由该软件复制而得;
2、工程目录名称为lib-test,其下有3个代码目录及一个头文件目录,分别是foo(foo.c及common.c文件)、bar(bar.c及common.c文件)、bt(backtrace.c文件)和configs(foo.h、bar.h及backtrace.h文件);其中bt目录为backtrace代码,它是作者的工程库的一部分。
目录路径、文件分布如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
lib-test
|-->main.c
|-->example.c
|-->Makefile
|              |-->foo.c
|-->foo-->|-->common.c
|              |-->Makefile
|
|               |-->bar.c
|-->bar-->|-->common.c
|               |-->Makefile
|
|              |-->backtrace.c
|-->bt--->|
|              |-->Makefile
|
|                   |-->foo.h
|-->configs->|-->bar.h
|                   |-->backtrace.h

Makefile组织
1、各个子目录单独使用Makefile,主要生成相关的库(这里特指静态库);
2、顶层Makefile负责将本目录下源代码文件编译成目标文件(如果有的话),并依次进入各种子目录编译生成相关库文件,最后进行链接,生成可执行文件。该Makefile关键语句如下:

1
2
3
for dir in $(SUBDIRS); \
do $(MAKE) -C $$dir all || exit 1; \
done

意思是进入指定子目录,并执行子目录的Makefile文件(子目录只负责生成库文件)。
库相互调用测试(详见代码):
1、foo.c文件中hello_foo函数调用bar.c文件中的bar函数以及同一目录下common.c文件的common_bar函数;
2、bar.c文件中hello_bar函数调用foo.c文件中的foo函数以及同一目录下common.c文件的common_foo函数。
代码示例:
1、foo目录下:
1)、foo.h

1
2
3
4
5
#ifndef FOO_H_
#define FOO_H_
void foo(int i);
int hello_foo(void);
#endif

2)、foo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
void foo(int i)
{
        printf("hell from %s() in file: %s, num:%d/n", __func__, __FILE__, i);
}
int hello_foo(void)
{
        printf("in func: %s()/n", __func__);
        bar(100);
        common_foo();
        printf("=========================================/n");
        return 0;
}

3)、common.c

1
2
3
4
5
6
#include <stdio.h>
int common_foo(void)
{
printf("this is a common function in foo/common.c/n");
return 0;
}

2、bar目录
1)、bar.h

1
2
3
4
5
#ifndef BAR_H_
#define BAR_H_
void bar(int i);
int hello_bar(void);
#endif

2)、bar.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
void bar(int i)
{
        printf("hell from %s() in file: %s, num:%d/n", __func__, __FILE__, i);
}
int hello_bar(void)
{
        printf("in func: %s()/n", __func__);
        foo(200);
        common_bar();
        printf("=========================================/n");
        return 0;
}

3)、common.c

1
2
3
4
5
6
#include <stdio.h>
int common_bar(void)
{
printf("this is a common function in bar/common.c/n");
return 0;
}

3、example.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
void foo1(int a)
{
        int i;
        i = a;
}
int fun(int a, int b, int c)
{
        char buf[14];
        int sum;
        sum = a + b + c;
        foo1(a);
        return sum;
}

4、main.c

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <backtrace.h>
#include <foo.h>
#include <bar.h>
int main(void)
{
        printf("hello from %s()/n/n", __func__);
        hello_foo();
        hello_bar();
       
        print_trace(11);
}

题外话
这里的3个自定义头文件使用了尖括号(<>),如果按教科书的说法是不正确的,因为尖括号是从标准库的路径搜索头文件的。然而作者经常看到一些代码中标准库的头文件是使用双引号来包含的(如#include “stdio.h”),这里反其道而行之。因为在Makefile中使用-I选项指定了额外的头文件路径,使用

1
[latelee@latelee lib-test-latelee.org]$ gcc -g -c main.c example.c --verbose -Iconfigs

命令来编译时显示了gcc搜索头文件路径的过程,如下:

1
2
3
4
5
6
7
#include "..." search starts here: 
#include <...> search starts here:
 configs
 /usr/local/include
 /usr/lib/gcc/i386-redhat-linux/4.3.2/include
 /usr/include
End of search list.

当然,实际开发中不提倡这种方法,然而在U-Boot中却是十分常见的,只是有些人不明白其中的道理罢了。
题外话 结束

库目录的Makefile(这里显示的是foo目录下的Makefile,其它者修改库名称及代码文件名称即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# A simple Makefile for lib(libxxx.a)  
# By Late Lee(http://www.latelee.org) 
AR = ar
ARFLAGS = cr
LIB = libfoo.a
RM = -rm -rf
OBJS := foo.o common.o

all: $(LIB)
$(LIB): $(OBJS)
        $(AR) $(ARFLAGS) $@ $(OBJS)
clean:
        $(RM) $(OBJS) $(LIB) *.bak *~
.PHONY: all clean

顶层目录的Makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#################################################################  
# A simple Makefile 
# By Late Lee(http://www.latelee.org) 

#  
# bugs: 
#      1. 需要显式指定库位置、名称; 
#      2. make 及 make clean处理得不好(对于库,要么删除再编译,要么无操作); 
################################################################# 

CC=gcc
CFLAGS = -Wall
DEBUG = y

ifeq ($(DEBUG), y)
CFLAGS += -g
else
CFLAGS += -O2
endif

SUBDIRS := foo bar bt

LIBS := bt/libbt.a foo/libfoo.a bar/libbar.a

LDFLAGS = $(LIBS)

RM = -rm -rf

__OBJS = main.o
__OBJS += example.o

__SRCS = $(subst .o,.c,$(__OBJS))

target = a.out

MAKE = make

#all: clean $(target) 
all: $(target)

$(__OBJS): $(__SRCS)
        $(CC) $(CFLAGS) -c $^ -I ./configs/

$(target): $(__OBJS)
        for dir in $(SUBDIRS); /
        do $(MAKE) -C $$dir all || exit 1; /
        done
        $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

clean:
        @for dir in $(SUBDIRS); do make -C $$dir clean|| exit 1; done
        $(RM) $(__OBJS) $(target) *.bak *~

.PHONY: all clean

执行make的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[latelee@latelee lib-test]$ make 
gcc -Wall -g -c main.c example.c -I ./configs/
example.c: In function ‘fun’:
example.c:10: warning: unused variable ‘buf’
for dir in foo bar bt  ; /
        do make -C $dir all || exit 1  ; /
        done
make[1]: Entering directory `/home/latelee/linux-c/lib-test/foo'
cc    -c -o foo.o foo.c
cc    -c -o common.o common.c
ar cr libfoo.a foo.o common.o
make[1]: Leaving directory `/home/latelee/linux-c/lib-test/foo'
make[1]: Entering directory `/home/latelee/linux-c/lib-test/bar'
cc    -c -o bar.o bar.c
cc    -c -o common.o common.c
ar cr libbar.a bar.o common.o
make[1]: Leaving directory `/home/latelee/linux-c/lib-test/bar'
make[1]: Entering directory `/home/latelee/linux-c/lib-test/bt'
cc    -c -o backtrace.o backtrace.c
ar cr libbt.a backtrace.o
make[1]: Leaving directory `/home/latelee/linux-c/lib-test/bt'
gcc -Wall -g main.o example.o -o a.out bt/libbt.a foo/libfoo.a bar/libbar.a

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[latelee@latelee lib-test]$ ./a.out 
hello from main()
in func: hello_foo()
hell from bar() in file: bar.c, num:100
this is a common function in foo/common.c
=========================================
in func: hello_bar()
hell from foo() in file: foo.c, num:200
this is a common function in bar/common.c
=========================================
Obtained 4 stack frames.
./a.out [0x8048733]
./a.out [0x80486cf]
/lib/libc.so.6(__libc_start_main+0xe5) [0x7ea6d5]
./a.out [0x8048601]

欢迎指正文中错误之处。其它Makefile高级应用,欢迎一起讨论,共同进步。
本文中出现的代码及Makefile下载(.tar.bz2格式,md5和为1f3102f74ef4db1cb240a4e42474fa7a):
多目录Makefile工程.tar.bz2