参考:《linux程序设计(第四版)》
本文的编写从简单到复杂,一步一步完成Makefile文件的编写和完善。首先看一下我们的程序有哪些文件:
文件内的程序也很简单,就是输出该文件已经被调用,代码如下:
void funcPrintf(void)
{
printf("------ func.c ------\n");
}
如果我们想要编译一个文件,首先想到的是在shell 下执行gcc命令,那么我们先通过几个shell脚本来实现文件的编译。
#! /bin/bash
set -ev
gcc -o myApp main.c func.c func.h
echo "bulid successful!"
下面是执行结果,编译生成了myApp,程序包含了main.c 和 func.c ,运行程序后打印在了终端上。
实际上我们是执行了gcc -o myApp main.c func.c func.h命令完成了程序的编译,那么我们看一下如何用Makefile文件来完成这个操作。
myApp: main.c func.o
gcc -o myApp main.c func.o
func.o: func.h func.c
gcc -c func.h func.c
我们分解了一步,提出了一个func.o 进行单独编译。其实完全可以写成下面的样子。
myApp: main.c func.h func.c
gcc -o myApp main.c func.h func.c
然后我们在Makefile文件所在目录执行make命令即可完成编译。
很显然,从目前的情况看Makefile让我们程序编译变得更加复杂,因为我们不得不去遵守Makefile的一些规则。
1. myApp 最终生成的目标文件
2. :后面的这些内容是编译生成目标文件所依赖的文件
3. gcc -o myApp main.c func.h func.c 是编译生产目标文件需要执行的指令(指令需要以TAB开头)
上面就是Makefile的基本规则,可以理解为按照一定的规则将编译的shell 脚本写进一个名叫Makefile的文件,然后执行make指令后系统就会根据Makefile文件的规则执行编译shell指令。
既然上面的Makefile文件有两个,其中一个将func.o文件单独列出来了,这里就补充一下:我们用命令编译的时候可以分成两步:
gcc -c func.h func.c
gcc -o myApp main.c func.o
先生成连接文件func.o然后再连接func.o文件生成myApp,只是在Makefile文件编写时需要先告诉make指令编译生成myApp需要依赖main.c和func.o,然后make就会去寻找func.o文件。接着make发现生成func.o又依赖func.c和func.h文件,需要执行的指令是gcc -c func.h func.c 。总结一下:make会从最终目标出发,依次找到每个依赖,每个依赖的依赖....
编译过程中会生成各种文件,因此需要clean一下,添加一下clean指令。完善一下我们的Makefile文件,增加clean操作,删除连接文件、预编译文件和最终生成的目标文件。
myApp: main.c func.o
gcc -o myApp main.c func.o
func.o: func.h func.c
gcc -c func.h func.c
clean:
-rm func.o *.h.gch myApp
执行 make clean
插播一个知识点,不做过多解释,链接库:
老规矩先上shell脚本:
#! /bin/bash
set -ev
gcc lib.c lib.h -fPIC -shared -o lib.so
# 编译生成链接库指令
gcc -o myApp main.c func.c func.h lib.so
cp lib.so /usr/lib
echo "bulid successful!"
对比Makefile脚本:
myApp: main.c func.o lib.so
gcc -o myApp main.c func.o lib.so
func.o: func.h func.c
gcc -c func.h func.c
lib.so: lib.c lib.h
gcc lib.c lib.h -fPIC -shared -o lib.so
clean:
-rm func.o *.h.gch myApp *.so
通过上面的操作我们可以完成Makefile文件的编写,也能完成所有的工作,但是看上去不是那么的灵活。接下来需要讲一下变量,先上Makefile文件:
all: myApp
CC: gcc
INCLUDE = .
CFLAGS = -g -Wall -ansi
#CFLAGS = -O -Wall –ansi
myApp: main.c func.o lib.so
$(CC) -o myApp main.c func.o lib.so
func.o: func.h func.c
$(CC) -I$(INCLUDE) -c func.h func.c
lib.so: lib.c lib.h
$(CC) -I$(INCLUDE) lib.c lib.h -fPIC -shared -o lib.so
clean:
-rm func.o *.h.gch myApp *.so
install:
cp *.so /usr/lib ;\
echo "make install";\
./myApp;\
echo "myApp running ...";
例子很简单,用了两个变量 CC: gcc 指定了编译器, CFLAGS = -g -Wall -ansi 指定了编译等级,变量引用使用方法$(变量名),上面的Makefile文件的执行结果如下:
Makefile本身也规定了几个常用的变量:
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
all: myApp
CC: gcc
INCLUDE = .
CFLAGS = -g -Wall -ansi
#CFLAGS = -O -Wall –ansi
myApp: main.c func.o lib.so
$(CC) $(CFLAGS) -o $@ $^
func.o: func.h func.c
$(CC) -I$(INCLUDE) $(CFLAGS) -c $^
lib.so: lib.c lib.h
$(CC) -I$(INCLUDE) $(CFLAGS) $^ -fPIC -shared -o $@
clean:
-rm func.o *.h.gch myApp *.so
install:
cp *.so /usr/lib ;\
echo "make install";\
./myApp;\
echo "myApp running ...";
这个Makefile文件除了增加了一个install 操作之外,其他的动作和上面的Makefile文件规定的一致,只是用变量做了替代。解释一下install,可以理解为一些列shell操作来完成程序的安装,其实并不一定是安装操作,比如上面的install操作只是将库文件拷贝进入/usr/lib目录并且执行了该文件。
我们还有两个文件在file文件夹内,现在我们也将这两个文件的编译加入进去。当然这里是一种简单的方法,后面介绍如何多文件夹递归编译。
all: myApp
CC: gcc
INCLUDE = .
INSTDIR = /usr/lib
CFLAGS = -g -Wall -ansi
#CFLAGS = -O -Wall –ansi
myApp: main.c func.o lib.so file.o
$(CC) $(CFLAGS) -o $@ $^
func.o: func.h func.c
$(CC) -I$(INCLUDE) $(CFLAGS) -c $^
lib.so: lib.c lib.h
$(CC) -I$(INCLUDE) $(CFLAGS) $^ -fPIC -shared -o $@
file.o: file/file.c file/file.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c $^
clean:
-rm *.o *.h.gch myApp *.so
install:
cp *.so $(INSTDIR) ;\
echo "make install";\
./myApp;\
echo "myApp running ...";
最后经过了修改和完善我们完成了最终的Makefile文件:
INCLUDE = .
INSTDIR = /usr/lib
CFLAGS = -g -Wall -ansi
#CFLAGS = -O -Wall –ansi
TARGET = myApp
all: $(TARGET)
CC: gcc
$(TARGET): main.c func.o lib.so file.o
$(CC) $(CFLAGS) -o $@ $^
func.o: func.h func.c
$(CC) -I$(INCLUDE) $(CFLAGS) -c $^
lib.so: lib.c lib.h
$(CC) -I$(INCLUDE) $(CFLAGS) $^ -fPIC -shared -o $@
file.o: file/file.c file/file.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c $^
.PHONY: clean
clean:
-rm *.o *.h.gch $(TARGET) *.so
install:
cp *.so $(INSTDIR) ;\
echo "make install";\
./$(TARGET);\
echo "$(TARGET) running ...";