makefile初识
Makefile初识
Makefile定义了整个工程的编译规则,一个工程中的头文件和源文件不计数,其按类型、功能、模块分别放在若干目录中,makefile定义了一系列的规则来指定,那些文件需要先编译,那些文件后编译。
首先了解一下Linux下的GCC(GNU Compiler Collextion):这是一个工具集,包含gcc(跟大写的不一样),g++等编译器和ar,nm等工具集。GCC编译器程序的编译有4个阶段:预编译->编译和优化->汇编->链接,下面以c代码作例子:源代码(*.c) 【预编译 -E】 预处理后的代码(*.i) 【编译和优化 -S】 汇编代码(*.s) 【汇编 -c】 目标文件(*.o) 【链接】 可执行文件。
一些命令选项:
gcc hello.c /* 生成可执行文件,名字为默认的a.out。gcc后面的不知可以是*.c文件,同样也可以是*.o或其他。*/
gcc -o hello hello.c /* 生成可执行文件,名字接在-o后面,即hello */
gcc -E hello.c /* 经过预编译的过程,默认名字格式,产生hello.i */
gcc -S hello.c /* 经过预编译与编译优化的过程,名字为默认的hello.s */
gcc -c hello.c /* 经过了预编译和编译优化及汇编的过程,产生目标文件,名字为默认的hello.o */
其他选项:
gcc -D宏名,gcc -DOS_LINUX则相当于在预编译的时候添加了#define OS_LINUX,即当出现#ifdef OS_LINUX的时候,就会满足条件。
gcc -Idir,将头文件的搜索路径扩大,包含dir目录
gcc -Ldir,将链接时实用的链接库搜索路径扩大,包含dir目录,gcc优先使用共享程序库。
gcc -static,仅使用静态程序库进行链接。
gcc -0n,n为数字,优化程序执行速度和占用空间,常用的是2,gcc -02
Makefile的规则
target...:prerequisites ...
command
...
...
target目标文件,prerequisites是要生成target所需要的文件或是目标。command是make需要执行的命令。
一下面的例子为例,贯穿整个makefile的编写过程。
目录结构:
project
main.c
include/
add.h
sub.h
src/
add_int.c
add_float.c
sub_int.c
sub_float.c
add.h
1 #ifndef _ADD_H_ 2 #define _ADD_H_ 3 4 extern void add_int(void); 5 extern void add_float(void); 6 7 #endif
sub.h
1 #ifndef _ADD_H_ 2 #define _ADD_H_ 3 4 extern void sub_int(void); 5 extern void sub_float(void); 6 7 #endif
add_int.c
1 #include <stdio.h> 2 #include "add.h" 3 4 void add_int(void) 5 { 6 printf("add_int.\n"); 7 }
add_float.c
1 #include <stdio.h> 2 #include "add.h" 3 4 void add_float(void) 5 { 6 printf("add_float.\n"); 7 }
sub_int.c
#include <stdio.h> #include "sub.h" void sub_int(void) { printf("sub_int.\n"); }
sub_float.c
#include <stdio.h> #include "sub.h" void sub_float(void) { printf("sub_float.\n"); }
main.c
#include <stdio.h> #include "add.h" #include "sub.h" int main(void) { add_int(); add_float(); sub_int(); sub_float(); return 0; }
makefile的编写
1 #bin/bash 2 3 target: add_int.o add_float.o sub_int.o sub_float.o main.o 4 gcc src/add_int.o src/add_float.o src/sub_int.o src/sub_float.o main.o -o target 5 6 add_int.o: src/add_int.c include/add.h 7 gcc -o src/add_int.o -c src/add_int.c -Iinclude 8 9 add_float.o: src/add_float.c include/add.h 10 gcc -o src/add_float.o -c src/add_float.c -Iinclude 11 12 sub_int.o: src/sub_int.c include/sub.h 13 gcc -o src/sub_int.o -c src/sub_int.c -Iinclude 14 15 sub_float.o: src/sub_float.c include/sub.h 16 gcc -o src/sub_float.o -c src/sub_float.c -Iinclude 17 18 main.o: main.c include/add.h include/sub.h 19 gcc -o main.o -c main.c -Iinclude 20 21 clean: 22 rm -f target src/add_int.o src/add_float.o src/sub_int.o src/sub_float.o
makefile 使用变量
可是使用变量对上述makefile进行修改。一般变量使用大写字母。
$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
CC = gcc
TARGET = target
OBJS = src/add_int.o src/add_float.o src/sub_int.o src/sub_float.o main.o
CFLAGS = -Iinclude -O2
预定义的变量
自动变量
模式匹配
使用自动变量来简化规则书写。
main.o:%.o:%.c
$(CC) -o $@ -c $< $(CFLAGS)
使用变量和模式匹配来重写上述makefile
#bin/bash CC = gcc OBJS = src/add_int.o src/add_float.o src/sub_int.o src/sub_float.o main.o CFLAGS = -Iinclude -O2 TARGET = target $(TARGET):$(OBJS) $(CC) $^ -o $@ $(CFLAGS) $(OBJS):%.o:%.c $(CC) -o $@ -c $< $(CFLAGS) .PHONY:clean clean: rm $(OBJS) $(TARGET)
搜索路径:
在大的系统中,存在很多目录,手动的-I的方法添加目录不方便,所有使用VPATH变量,他可以自动找到指定文件的目录并添加到文件上。
使用方法:VPATH = path1:path2......
使用冒号隔开,记得在最后加上当前目录。
#bin/bash CC = gcc OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o
VPATH = src:. CFLAGS = -Iinclude -O2 TARGET = target $(TARGET):$(OBJS) $(CC) $^ -o $@ $(CFLAGS) $(OBJS):%.o:%.c $(CC) -o $@ -c $< $(CFLAGS) .PHONY:clean clean: rm *.o $(TARGET)
自动推导规则:使用命令make编译扩展名为c的C语言文件的时候,源文件的编译规则不用明确给出。
#bin/bash CC = gcc OBJS = src/add_int.o src/add_float.o src/sub_int.o src/sub_float.o main.o CFLAGS = -Iinclude -O2 TARGET = target $(TARGET):$(OBJS) $(CC) $^ -o $@ $(CFLAGS) .PHONY:clean clean: rm $(OBJS) $(TARGET)
递归make
当有多人在多个目录进行程序开发时,并且每个人负责一个模块,二文件在相对独立的目录中,这是由同一个Makefile维护代码的编译会出现一些问题。
1、递归调用的方式:make命令有递归的作用,他可以递归调用每个子目录的Makefile。假设目标add和sub都有Makefile,则可以用一下两种方式:
add:
cd add && $(MAKE)
add:
$(MAKE) -c add
目录变更
project\
add\
add.h
add_int.c
add_float.c
sub\
sub.h
sub_int.c
sub_float.c