makefile的作用是用来编译管理项目代码,节省编译项目所用的时间,并且一次编写终身受益。
并且由于目标比依赖生成的要晚,更新依赖之后会检测目标或者是依赖的生成的时间,所以即使当 .c 程序被修改之后再次使用 make 命令来运行 makefile 文件的时候,(运行makefile文件使用的是 make 命令)所显示的结果却可以是更改程序的结果(makefile的工作机制)。
创建 makefile 的基本要素有三个,分别是目标,依赖和命令。其中目标指的是 makefile 中要生成的目标文件。
依赖指的是用来生成目标文件的文件(即,目标文件是由依赖来生成的)。命令指的是由依赖生成目标文件的命令(即,通过这个命令可由依赖来得到目标文件,确切的说从依赖到目标是由 gcc 命令来生成的)
在 makefile 中主要三个内容,这三个内容可以描述为一个规则,两个函数,三个自动变量。
一个规则
这个规则叫做模式规则,模式规则指:
在makefile中的目标和依赖中,在目标中可以包含一个或者多个%,同样在依赖中也可以包含%,并且在依赖条件中的%的取值取决于目标中的%,换句话说,依赖和目标中的%所代表的值必须是相等的。
两个函数
src=$(wildcard *.c)函数
它的功能是找到目标文件下的.c文件并将它们全都赋给src (即,src中存放的是.c文件,并且这个函数中的src是个任意命名的变量,但是$(wildcard .c)是这个实现找出.c文件的makefile函数),要注意的是在这个函数中wildcard和.c之间是没有逗号将它们两个隔开的,它们两个之间只有空格)
obj=$(patsubst %.c , %.o , $(src))函数
它的功能是把后缀中的.c 文件替换为 .o文件并赋给obj,把第三个参数的集合当中取出来 里面所有包含第一个参数的部分 替换成第二个参数的部分 (即,obj中存放的是.o的目标文件),在这里obj是个任意命名的变量,但是$(patsubst %.c , %.o , $(src))是实现这个将.c 文件转换为.o文件的makefile函数。要注意的是在这个函数中patsubst 和%.c 之间是没有逗号将它们两个隔开的,它们两个之间只有空格,%.c , %.o , $(src)之间使用逗号隔开的。
三个自动变量
在makefile中有三个自动变量,分别为
$@(在命令中表示规则中的目标)
$^(目标:依赖格式中的所有的依赖)
$< (目标:依赖中的第一个依赖,并且该依赖可以依次向后读取)
在makefile中除了这一个规则,两个函数,三个规则外,还有一个clean和在makefile中变量的定义和对变量的取值也是一块重要的内容。
这个clean在格式上只有目标没有依赖,在clean的命令中可以编写上对目标文件和最终文件进行删除的命令。(在makefile中规定,当只有目标文件,而没有依赖的时候,则直接执行这个目标下所对应的命令)当编写完这个makefile文件后,当要去执行这里的clean的时候,所执行的命令是 make clean。
而命令make clear -n(-n表示模拟执行以下makefile中的clear命令,只为了把makefile中的这个命令显示一下,并没有真正的执行)。
在clean目标中需要配合使用伪目标声明,这个伪目标声明是指当当前路径下含有以clean命令的文件或者是目录的时候,则在使用命令make clean的时候,就会导致结果无法执行,并会提示“clean是最新的”。
解决这种问题,只需要在makefile中加入一个伪目标声明即可,格式为 .PHONY.clean
解决方法:
在makefile中除了自带的三个自动变量外还可以手动的去自定义变量。对makefile中所定义的变量进行取址的方式为:$(变量名) ,对这里的变量所赋的值可以是.c文件和.o文件
makefile的具体制作
(假设有三个用来实现加减乘功能的函数分别为在 add.c mul.c sub.c 中,以及一个 main 函数)
1.创建 makefile
首先创建的makefile文件的文件名可以是makefile,也可以是Makefile ,创建该文件的命令是vi makefile或者是vi Makefile
vi Makefile
2.编写依赖规则
目标:依赖
(一个Tab的距离)命令
# app 为最后需要编译成的目标,后面为依赖
app:main.c add.c mul.c sub.c
# 使用 gcc 编译命令,编译成 app
gcc main.c add.c mul.c sub.c -o app
第二行 gcc 则是实现从依赖到目标的命令
或者是
# 依赖 .o 文件生成可执行的文件app
app:main.o add.o mul.o sub.o
gcc main.o add.o mul.o sub.o -o app
main.o:main.c
gcc -c main.c -o main.o
# 依赖 .c 文件生成 .o 文件
# 但是本身只有 .c 文件,所以还需要命令来生成 .o 文件
add.o:add.c
gcc -c add.c -o add.o
mul.o:mul.c
gcc -c mul.c -o mul.o
sub.o:sub.c
gcc -c sub.c -o sub.o
# clean 目标用于删除 .o 文件和可执行文件的
clean:
rm -rf main.o add.o sub.o mul.o
在这里要注意,由于makefile文件是从上往下读,并且把文件的第一行作为终极目标。所以要把最终生成可执行文件的目标和依赖放在makefile文件的第一行。
使用自定义变量来进行编译
# 此处设置自定义变量 src
src=main.o add.o mul.o sub.o
# 使用 $(src) 去除自定义变量中的值
app:$(src)
gcc main.o add.o mul.o sub.o -o app
main.o:main.c
gcc -c main.c -o main.o
add.o:add.c
gcc -c add.c -o add.o
mul.o:mul.c
gcc -c mul.c -o mul.o
sub.o:sub.c
gcc -c sub.c -o sub.o
clean:
rm -rf main.o add.o sub.o mul.o
使用 makefile 三个自定义变量进行编译
obj = add.o sub.o mul.o main.o
target=app
$(target):$(obj)
gcc $(obj) -o $(target)
# 当查找顶层的 main.o 时没找到,则执行下面的语句,将 %.o:%.c 替换为 main.o:main.c ,如此类推
%.o:%.c
# $@ (在命令中表示规则中的目标)
# $^ (目标:依赖格式中的所有的依赖)
# $< (目标:依赖中的第一个依赖,并且该依赖可以依次向后读取)
# 上面三个命令只能在规则中使用
gcc -c $< -o $@
使用makefile 中维护的变量进行编译
obj = add.o sub.o mul.o main.o
target=app
# makefile 中维护的变量, 默认为 cc
CC = gcc
# 预编译时的选项,如 头文件 -I
CPPFLAGS =
# 编译的时候使用的参数 -Wall -g -c
CFLAGS =
# 链接库使用的选项 -L -l
LDFLAGS =
$(target):$(obj)
$(CC) $(obj) -o $(target)
%.o:%.c
$(CC) -c $< -o $@
使用makefile提供的函数进行编译
# 取出制定目录下的所有 .c 文件
src = $(wildcard ./*.c)
# 在$(patsubst %.c,%.o,$(dir) )中,patsubst把$(dir)中的变量符合后缀是.c的全部替换成
.o
obj = $(patsubst ./%.c, ./%.o, $(src))
target=app
ALL:$(target)
CC = gcc
CPPFLAGS =
CFLAGS =
LDFLAGS =
$(obj):%.o:%.c
$(CC) -c $< -o $@
$(target):$(obj)
$(CC) $(obj) -o $(target)
clean:
# 若 rm 改为 -rm 则即使clean出错也会继续向下执行
-rm -rf $(target) $(obj)
# 声明 clean 为伪目标,避免目录中存在 clean 文件
.PHONY: clean ALL
使用 makefile 编译不同目录中的文件
假设现在将 .c 文件放置与 src 目录中,.h 文件放置于 include 目录中,并将要将编译的 .o 文件放置于 obj 中,目录图如下:
.
├── include
│ └── head.h
├── obj
└── src
├── add.c
├── main.c
├── mul.c
└── sub.c
此时的 makefile 应该这样编写:
src = $(wildcard ./src/*.c)
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))
inc_path = ./include/
CC = gcc
target=app
CPPFLAGS =
CFLAGS = -I
LDFLAGS =
ALL:$(target)
$(obj):./obj/%.o:./src/%.c
$(CC) -c $< -o $@ $(CFLAGS) $(inc_path)
$(target):$(obj)
$(CC) $(obj) -o $(target)
clean:
-rm -rf $(target) $(obj)
.PHONY: clean ALL
扩展知识
Makefile 选项说明 CFLAGS 、LDFLAGS 、LIBS
-
CFLAGS 表示用于C编译器的选项
CXXFLAGS 表示用于C++编译器的选项
这两个变量实际上涵盖了编译和汇编的两个步骤 -
CFLAGS:指定头文件(.h)的路径,如:CFLAGS=-I/usr/include -I/path/include 。
相同地,安装一个包时会在安装路径下建立一个include文件夹,当安装过程中出现故障时,试着把曾经安装的包的include文件夹增加到该变量中来。
- LDFLAGS:gcc 等编译器会用到的一些优化參数,也能够在里面指定库文件的位置。
使用方法:LDFLAGS=-L/usr/lib -L/path/to/your/lib。每安装一个包都差点儿一定的会在安装文件夹里建立一个lib文件夹。假设明明安装了某个包,而安装还有一个包时,它愣是说找不到,能够抒那个包的lib路径增加的LDFALGS中试一下。
- LIBS:告诉链接器要链接哪些库文件。如LIBS = -lpthread -liconv
简单地说,LDFLAGS是告诉链接器从哪里寻找库文件,而LIBS是告诉链接器要链接哪些库文件。
有时候LDFLAGS指定-L尽管能让链接器找到库进行链接。可是运行时链接器却找不到这个库。假设要让软件运行时库文件的路径也得到扩展,那么我们须要增加这两个库给”-Wl,R”:
LDFLAGS = -L/var/xxx/lib -L/opt/mysql/lib -Wl,R/var/xxx/lib -Wl,R/opt/mysql/lib
假设在运行./configure曾经环境变量设置export LDFLAGS=”-L/var/xxx/lib -L/opt/mysql/lib -Wl,R/var/xxx/lib -Wl,R/opt/mysql/lib” ,注意环境变量设置等号两边不能够有空格,并且要加上引號(shell的使用方法)。那么运行configure以后。Makefile将会设置这个选项。链接时会有这个參数,编译出来的可运行程序的库文件搜索路径就得到扩展了。
linux 中使用动态库
前面介绍了如何使用 Makefile 编译,但是编译完成后引用动态库也是一件麻烦事,例如程序集成了 x264 的动态库,但是调用的时候却找不到动态库,这就是一件麻烦事,有以下三种解决方法。
如 x264 动态库位于 /home/username/x264build/lib 目录下
- 在用户环境变量中添加 LD__LIBRARY_PATH (不常用)
在用户环境变量中添加动态库位置可以永久生效(只要该动态库还在此位置),如 ubuntu 中用户环境变量在 /home/username/.profile 中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/username/x264build/lib
配置完后,要重启终端
- 将动态库路径添加到临时路径中 (测试使用)
此方法直接在终端中输入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/username/x264build/lib
此时会将动态库临时导入环境变量,缺点是关闭此终端则无法使用。
- 将动态库路径写到配置文件中
配置文件路径为 /etc/ld.so.conf ,将路径添加进去,然后执行更新配置命令,方法如下
sudo vi /etc/ld.so.conf
然后将路径写入:
# 原本就有的路径
include /etc/ld.so.conf.d/*.conf
# 自己添加的路径
/home/username/x264build/lib
最后执行更新配置文件命令
sudo ldconfig -v