说明: 1、文中多处出现Makefile,它可以认为是一个具体的文件——即文件名就是“Makefile”,也可以认为它是抽象的“Makefile”,比如下文说到的“两个Makefile”,它们的名称肯定是不同的,但它们都是“Makefile”。——不知这样说,阁下能不能明白,我也没有好的文字表达了。 2、本文以小笑自己从网络、书籍总结的Makefile模板来讲一下有关的Makefile知识、技巧,当然,不可能很完整,不过能正常使用。
本文不打算讲述Makefile的来源、好处以及其它一些理论的知识,有关Makefile的知识可以写成一本书。网络上的《跟我一起写 Makefile》是一篇很好的文章,建议看一下。此处给出小笑的一个Makefile例子,它能应付基本的项目管理。小笑的毕业设计程序就是在这个Makefile基础上修改而来的。闲话不说,进入主题。
先来看一下具体的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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 ###################################### # my Makefile template # # Uage: # compile:"make all" or just "make" # clean:"make clean" # # ChangeLog: # 2010-4-21: # add some info # 2010-4-20: # new for DEBUG # another way to change SRCS to OBJECTS # another way to generate .o file ######################################## ### 宏定义DEBUG,我没有找到好的办法,只好出此下策。 DEBUG = y # 这些就是传说中的编译器了,比如CC是C编译器、CPP是C++编译器,其它们都是宏来着。 # 也可以定义其它一些编译器,比如交叉编译器CROSS_COMPILER=arm-linux-gcc等等。 CC = gcc CPP = g++ CROSS_COMPILER = arm-linux-gcc ###===================================== #####>>>>>!!!!! C编译的一些标志,如打开警告,调试标志等 !!!!!!<<<<<##### ### C CFLAGS = -Wall ##! 这里就是添加调试或优化标志,当然,也可以在上述宏中使用,不必这样麻烦。 ifeq ($(DEBUG), y) CFLAGS += -g else CFLAGS += -O2 endif #####>>>>>!!!!! 这是C++语言编译的一些标志,同C !!!!!!<<<<<##### ### C++ CPPFLAGS = -Wall ##! 一样的 ifeq ($(DEBUG), y) CPPFLAGS += -g else CPPFLAGS += -O2 endif ###===================================== #####>>>>>!!!!! 这些是别的一些宏,如链接库位置、名称等(我不知如何移称呼这个宏,百度吧) !!!!!!<<<<<##### ### 如-lpthread or -lncurses or -lpanel or -lmenu or -lm,等。 ### 注意:有些库不是Linux默认的,比如多线程的pthread,如果编译时不加上的话,编译是不会通过的, ### 此外,还有ncurses库、SDL库等等,要注意一下。 ### 在此处添加 LDFLAGS = LDFLAGS += # 这个是删除使用到的宏 RM = rm -rf ###===================================== ### 此处添加目标名称 #####>>>!!最好起一个有意义的名称,比如采集视频数据的,可以是capture,显示用的,可以是display,等等 !! target = ### 此处添加目标文件(即.o文件) OBJECTS = .o OBJECTS += .o ### 这是生成.o文件的另外一种方法,有点麻烦,但也可以。 #此处添加源文件 #SRCS = #SRCS += # 生成相应的.o文件 #OBJECTS = $(SRCS:.c=.o) #OBJECTS = $(SRCS:.cpp=.o) ###===================================== ### 真正的编译开始,all是一个伪目标,编译时的“make all”中的“all”就是它。 all: $(target) ############################## # 这是另外一种方法: # foo.o:foo.c foo.h(可以不使用.h文件的!) # (tab) $(CC) $(CPPFLAGS) -c $< -o $@ # # thread.o: thread.cpp thread.h # $(CC) $(CPPFLAGS) -c $< -o $@ # main.o: main.cpp thread.cpp # $(CC) $(CPPFLAGS) -c $< -o $@ # # 这种方法就是实打实的,需要什么,添加什么,我也用过,也不麻烦,不妨一试。 ############################## # 这种方法简便一些 # 上述列出了所有用到的.o文件,都是依赖条件。 # $^:所有的依赖文件,$<:第一个依赖文件,$@:目标文件(可执行的程序) $(target): $(OBJECTS) $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ # 清除,比如中间文件,目标文件。 clean: @echo "Cleaning..." $(RM) $(OBJECTS) $(target) @echo "Done" # 安装。其实这个命令除了显示一些信息外,什么事也没做。 # 因为一般程序编译安装都是:make;make install;这样做,以防万一。 # 也可试试删除这几行,执行一下make install,看看效果。 install: @echo " Note:" @echo "To install or not install,that is the question" @echo # 声明了三个伪目标 .PHONY:all clean install ### end of the Makefile
Makefile的格式如下所示:
1 2 target : prerequisites command
下面是《跟我一起写 Makefile》中的介绍:
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。 prerequisites就是要生成那个target所需要的文件。 command就是make需要执行的命令。(任意的Shell命令)
下文所讲的“目标文件”,可能是指生成的“可执行文件”,也可能是指这里的“target”,假设读者应当能区别出来。
在使用Makefile来make程序时,最常见的提示信息就是“Nothing to be done for XXX”,很多人看到这个信息,很不理解(试一下那个make install测试吧)。我认为在两种情况下会出现这个提示信息,第一种情况,这个程序已经编译过一次了,已经生成了.o和可执行文件了,所有目标均已是最新,不需要再做一次“无用功”了——make是何等讲究效率!如果你想每次make all都从编译一次,可以在Makefile的all后添加clean,这个clean必须放在第一位置才能在每次编译前都清除一些中间生成的文件(.o文件)和目标文件。如下:
不要担心clean在后面才出现,Makefile不管先后的。
另一种情况是声明了一个“伪目标”,但又make它,就会出现上面的提示信息,比如上述的Makefile模板中,如果去掉与目标install相关的几行语句,但最后却声明它是“伪目标”的话,当执行“make install”后,就会出现“Nothing to be done for install”。
当make一个不存在的目标时,会提示:
1 Make: *** No rule to make target ‘XXX’. Stop.
因为make确实找不到XXX,当然也不会去执行了。
make程序时,也不一定是“make all”,只要是一个在Makefile文件中出现的目标文件(target)即可。如果你将“all”改为“love”的话,你输入“make love”,照样能顺利通过编译,阁下不妨一试。
此外,还有一些make的技巧,比如一个程序要应用于两个平台(我写的视频采集程序要在PC上执行,也要在ARM上执行),程序是不用改多少的。最关键的就是编译器,当应用于ARM平台时,只要修改Makefile中的编译器就可以了。所以我的工程目录下有两个Makefile,比如PC平台中的为Makefile,而ARM平台的为arm-Makefile。这样,在编译PC平台的程序时,可以直接“make all”,因为make首先找到的是Makefile,就不会执行到arm-Makefile了。那么,交叉编译怎么办呢?make有一个-f选项,可以选择自定义的Makefile。不过在编译过程中,出现了一些问题,我明明在Makefile中指明了编译器为arm-linux-gcc了,但编译过程中有些文件还是被gcc所编译,造成链接的失败,我实在没有办法,只能显式指定CC选项了。这样,在交叉编译时,简单的“make all”,就变成了“make –f arm-Makefile CC=arm-linux-gcc”,由于同时要处理两个平台,不得不出此下策了。(其实也没有多么麻烦。)
我还发现gcc的-M选项的好处。此选项是在编译时指定某一个宏,在条件编译中特别有用。在PC机上,摄像头的设备文件为/dev/video,但在ARM开发板上却为/dev/video1,我也不知是怎么搞的,在开发板中,video不是video1的链接文件,我试了几次,结果还是一样。没办法,只好在程序显式使用宏定义来指定设备文件名称了。不过,为了方便,我使用了条件编译。如下:
1 2 3 4 5 #ifdef __ARM__ #define device “/dev/video1” #else #define device “/dev/video” #endif
即使用ARM来选择设备文件的名称。
这时,gcc的-D选项就派上用场了,在arm-Makefile文件中的CFLAGS中添加了-DARM,在Makefile文件中不添加,这样就很好解决了两个平台的设备文件名称问题了。当然,如果仅仅只是针对ARM平台的话,这些也用不着,但不失为一种方法。(更正:以前写成“-M”选项是错误的!正确的选项为“-D”,特此说明并致歉!)
本着“够用即可”的原则,不再详细介绍Makefile了。毕竟,小笑也不太懂。
上述的Makefile可以认为是一个Makefile的模板,可以在它的基础上修改,成为适合自己的工程的“Makefile”,再总结出一个自己的Makefile模板。
再:写完这篇文章后,我读了几次,感觉不像我写作风格,总是表达不出想要表达的意思。自己文笔水平是一个问题,二来对Makefile的确了解不太深入。望诸君见谅!