makefile网上的教程动辄好几十页,看着怕,我来个简陋版的,看完①,②,③,④再动手不迟。
写一句废话,你手边可能有现成的样例makefile看着很烦,好像很厉害,其实里面的好多都是用不上的或者可简略的,装B而已,莫怕!
目标是:你需要将main.c 1.c 2.c 1.h 2.h 这些文件用gcc编译器生成名为dest的可执行文件(具体代码内容随便,可参照我附录的打包程序,环境ubuntu10.04,gcc编译器,环境类似即可)。
首先需要明白,makefile就是帮你自动编译,手动编译不就是编译(-c)和链接(-o)这两个主要过程嘛,自动的同样也是这两个过程。
编译过程:主要涉及到.h文件和.c文件,编译生成.o文件。
链接过程:主要涉及到库文件和.o文件一起,生成目标程序。
①编译过程:这部分主旨是向make工具说明.h和.c文件的关联关系然后生成.o文件。
main.o : main.c 1.h 2.h 描述目标文件和源文件,通过‘:’分开。(把.c文件里面包含的.h文件都列出来,系统.h文件就不必了)
gcc -o main.o -c main.c 描述目标文件的生成方式,其实就是编译main.o的命令,该行代码是从行首敲tab键后开始的。
#以下每个.c文件生成对应的.o文件,和上面格式一样(这里我指定了.o文件的命名,你不指定也行,随你)
1.o : 1.c 1.h
gcc -o 1.o -c 1.c
2.o : 2.c 2.h
gcc -o 2.o -c 2.c
你已经会makefile编译部分了,以上编译的目的就是得到中间文件main.o、1.o、2.o
② 链接过程:和编译类似,说明目标文件和源文件的关联关系,并说明链接命令。
dest : main.o 1.o2.o 描述目标文件和源文件,通过‘:’分开。
gcc main.o 1.o 2.o -lpthread –o dest 该行代码是从行首敲tab键后开始的。实际的程序中除了链接.o文件经#常会有库文件,也别忘了,该行具体就是链接命令,格式自己查吧。
好了,你已经会makefile链接了,以上目前就是获得目标可执行文件dest。
③:收尾
靠这①和②,你就能写makefile了。不过有时你编译前需要删除原来编译过程中的.o和可执行程序,于是你需要在makefile中添加:
clean:
-rm *.o dest
④:结束
OK,完整的makefile已经好了,你一天的任务已经完成。好了,今天可以歇息了。简陋但是可用完整的makefile就算完成了,面对不是非常大文件量的程序,你只要能手动理清文件关系(无非就是涉及到.c、.h文件的增删改),使用上面的方式就可以完成makefile了,就是看上去不够‘厉害’、“专业”。半小时你就能够写makefile了,当然,这些就像是只完成了函数的功能一样。可能你手里有些复杂的makefile,但从中分析到最后,就是这两个步骤,你的前人和网上提供makefile,考虑的实在是太多,太前卫了搞的特别NB,但其实主要都是编译、链接这两部分。
你以为结束了?no,too,naive。
实际小工程上面的可以应对可,但是看上去可能不合群吧,且大工程一般也只是改的份。
以改的角度去使用makefile,做了如下记录:
详见资源附件
如果新建,单个 文件夹工程下,我一般使用如下模板(注意下面的代码拷贝后一定要注意有些该是制表符的地方别自动变成了空格):
#最终要的目标命名
TARGET = datamgt
#所有头文件路径
INCPATH = -I./ -I./include/
#指明了所有待编译的文件,供OBJS提取并获得所有所需的.o文件
SOURCE = $(wildcard *.c)
#获取所有的.o文件,都是包含路径的
OBJS = $(patsubst %.c,%.o,$(SOURCE))
#编译时加入的宏定义,等同于在函数中#define EXAMPLE_NUM (600)
DEFINES = -DEXAMPLE_NUM_ONE=600 -DEXAMPLE_NUM_TWO=900
#指明编译时的动态库,范例为两个系统动态库
LIBS = -L -lm -lpthread
#指定运行时库路径,一般如pthread等为系统动态库,无需指明,但是三方库或自己的动态库就要指明路径了
LFLAGS = #-Wl -rpath=/usr/lib/
#编译 警告,优化等级。。。
CFLAGS = -O2 -Wall
########## compiler 交叉编译,编译平台
ARCH = arm
CROSS_COMPILE = #arm-none-linux-gnueabi- #arm-linux-
CC = $(CROSS_COMPILE)gcc
STRIP = $(CROSS_COMPILE)strip
LINK = $(CROSS_COMPILE)gcc #g++
########## first target 伪目标
first: all
all: $(TARGET)
########## 隐含规则 && 头文件自动依赖
#去包含所有OBJS中的.o文件对应的*.d文件,.d文件是所有.o文件的依赖说明
#其实首次运行时,不须要.d文件,也不存在.d文件
#但由于再次编译时,如果依赖关系中的.h文件包含的.h文件啥的发生深层连带变动,会无法触发重新编译,
#所以这里每次编译后需要一个.d文件去记录.o文件所有的依赖文件,这样就不会出现.h文件深层变动后无法触发编译的情况
-include $(OBJS:.o=.d)
#模式规则
#$@:目标文件,$<:第一个依赖文件,$^:所有依赖文件
#$*:表示目标模式中“%”及其之前的部分
#%.o:%.c这个依赖关系会自动加上上面的-include的同一个.o的依赖关系,
#但是%.o:%.c中的%.o模式的来源是哪些呢?不清楚。但实际使用下来,子目录也自动去找了。
#单独的" < "、“ > ”是重定向符
#-MM是gcc的功能,能够列出.c文件的头文件依赖
#sed为文件替换编辑操作,作用主要是把.d文件自己也标识为依赖.c文件的依赖文件
#%.o:%.c
$(OBJS):%.o:%.c
$(CC) $(DEFINES) $(CFLAGS) $(INCPATH) -c $< -o $@
$(CC) -MM $(INCPATH) $< > $*.temp
sed -e 's,$*.o[:],$*.o $*.d:,g' < $*.temp > $*.d
rm $*.temp
########## link 最终编译,$(LINK)=》$(CC)貌似也没啥不妥的
$(TARGET): $(OBJS)
$(CC) $(LFLAGS) -o $@ $^ $(LIBS)
#$(LINK) $(LFLAGS) -o $@ $^ $(LIBS)
#删除所有.d .o TARGET文件,
clean:
@rm -rf $(patsubst %.c,%.o,$(SOURCE)) $(patsubst %.c,%.d,$(SOURCE)) $(TARGET)
#@rm -rf $(patsubst %.c,%.temp,$(SOURCE))
test:
@echo $(SOURCE)
@echo $(OBJS)