《APUE》——Make工程管理器

一、了解Make

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

实际上,make工程管理器就是个自动编译管理器,能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。用户只需一次编写简单的编译语句即可,这大大提高了实际的工作效率。

在 Linux(unix )环境下使用GNU 的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要我们投入一些时间去完成一个或者多个称之为Makefile 文件的编写。所要完成的Makefile 文件描述了整个工程的编译、连接等规则。

二、Makefile的基本结构

默认的情况下,make命令会在当前目录下按顺序找寻文件名“GNUmakefile”、“makefile”、“Makefile”的文件,找到后就解释并执行该文件,如果找不到就提示错误并退出。一般Makeifle文件名我们会用Makefile或makefile,而不会使用GNUmakefile。makefile是make读入的唯一配置文件,在一个makefile文件中通常包含的结构如下:

(1)目标体(target):通常是目标文件或者可执行文件。
(2)依赖文件(dependency_file):创建目标体需要依赖的文件,可以是文件、目录或其他目标。
(3)命令(command):也称动作,可以是Linux的命令,且这一行必须以TAB键开头。
基本格式如下:

target:   dependency_files
	command  /*此行必须以TAB键开头*/

通用结构体:

#以'#'开头的行表示注释
#定义变量VAR,强制赋值为app
VAR=test 
#在VAR之前定义的值后面再追加app这个值,这时该变量值扩展为testapp 
VAR+=app 
#如果之前VAR没有被定义,则定义并使用testapp;否则使用之前的值。
VAR?=testapp 
# 第一条目标为总的目标,
# 依赖可以是文件(目录)或为其他目标
# 动作可以是Linux命令,动作的那一行必须以TAB键开头
target: depend1 depend2 depend3 ... #依赖文件可以多个
[TAB] command1 
[TAB] command2 
target1: #也可以没有依赖文件,执行目标时,直接运行command1和command2
[TAB] command1 
[TAB] command2 

三、Makefile的变量

1.时间戳

make会自动检查相关文件的时间戳。以下面代码进行讲解:

david:   kang.o    yul.o
	gcc   kang.o  bar.o   -o   myprog  #通过kang.o  bar.o  生成可执行文件   myprog
kang.o:  kang.c   kang.h   head.h
	gcc   -Wall   -O   -g   -c  kang.c   -o   kang.o  #kang.c   生成目标文件   kang.o
yul.o:   bar.c    head.h
	gcc    -Wall   -O   -g    -c    yul.c   -o   yul.o  #yul.c  生成目标文件yul.o

这个makefile中有3个目标体,分别是david、kang.o、yul.o,第一个目标david的依赖体是kang.o、yul.o,而kang.o、yul.o又是目标体。首先,在检查david、kang.o、yul.o3个文件的时间戳之前,会向下检查其他比如kang.o和yul.o作为目标文件的文件的时间戳。例如kang.o的依赖文件有kang.c kang.h head.h,如果三个依赖文件中有一个比目标文件kang.o的时间戳新,那么就会执行语句:gcc -Wall -O -g -c kang.c -o kang.o,检查完kang.o和yul.o的时间戳之后,再回到第一个目标文件语句,如果kang.o和yul.o两个文件中有一个以上比目标文件david的时间戳新,则运行第二条语句:gcc kang.o bar.o -o myprog。这样,make就完成了,自动检查时间戳的工作,这也是make工作的基本流程。

make允许再makefile中创建和使用变量。变量是在makefile中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。变量的值可以代替目标体、依赖文件、命令、以及makefile文件的其他部分。makefile中,定义变量的方式有两种:递归展开方式和简单方式。
递归展开方式的定义格式为:VAR=var
简单扩展方式的定义格式为:VAR:=var
make中变量的使用方式为:${变量名}
变量名不包括":" “#” "="以及结尾空格的任何字符串。

makefile中的变量可分为用户自定义变量、预定义变量、自动变量以及环境变量

2.用户自定义变量:

OBJS = kang.o  yul.o
CC  = gcc       #OBJS和CC均用户自定义变量
david : ${OBJS}
	${CC}  ${OBJS}   -o   david

3.常见的预定义变量

命 令 格 式 					含义
AR				库文件维护程序的名称,默认值为ar   创建静态库.a
AS				汇编程序的名称,默认值为as
CC				C编译器的名称,默认值为cc
CPP				C预编译器的名称,默认值为$(CC) –E
CXX				C++编译器的名称,默认值为g++
FC				FORTRAN编译器的名称,默认值为f77
RM				文件删除程序的名称,默认值为rm –f
ARFLAGS			库文件维护程序的选项,无默认值
ASFLAGS			汇编程序的选项,无默认值
CFLAGS			C编译器的选项,无默认值
CPPFLAGS		C预编译的选项,无默认值
CXXFLAGS		C++编译器的选项,无默认值
FFLAGS			FORTRAN编译器的选项,无默认值

4.自动变量

命 令 格 式                       含     义
$*                     不包含扩展名的目标文件名称
$+                     所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$<                     第一个依赖文件的名称
$?                     所有时间戳比目标文件晚的依赖文件,并以空格分开
$@                     目标文件的完整名称
$^                     所有不重复的依赖文件,以空格分开
$%                     如果目标是归档成员,则该变量表示目标的归档成员名称

5.环境变量

使用环境变量的方法比较简单,make在启动时会自动读取系统当前以及定义的环境变量,并且创建与之具有相同名称和数值的变量。但是如果用户在makefile中定了相同名称的变量,那么用户自定义变量会覆盖同名的环境变量。

四、Makefile的规则

1.隐式规则

在C编译中,make的隐式规则指出:所有的.o文件都可自动由.c文件使用命令"$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c -o file.o"来生成。
在这里插入图片描述
图表来源:《嵌入式Linux C 语音应用开发教程》

2.模式规则

模式规则类似于普通规则。只是在模式规则中,目标名中需要包含有模式字符“%”(一个),包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。例如:对于模式规则“%.o : %.c”,它表示的含义是:所有的.o文件依赖于对应的.c文件。
一个模式规则的格式为:

%.o : %.c ; 
	command...

五、make常见的命令行选项

命 令 格 式                              含     义
-C dir                       读入指定目录下的 Makefile
-f file                      读入当前目录下的 file 文件作为 Makefile  
-i                           忽略所有的命令执行错误
-I dir                       指定被包含的 Makefile 所在目录
-n                           只打印要执行的命令,但不执行这些命令
-p                           显示 make 变量数据库和隐含规则
-s                           在执行命令时不显示命令
-w                           如果 make 在执行过程中改变目录,则打印当前目录名

六、代码实战

以我当前目录下的文件为例,执行make之前的目录情况:
在这里插入图片描述

makefile文件内容如下:

#定义变量指定静态库和动态库文件名
LIBNAME  = pf
#定义变量指定库文件和头文件将存放的路径,``是命令转换符
INSTPATH = `pwd`/../lib/
#定义编译器,如果今后需要交叉编译的话,修改这里的编译器即可
CC = gcc
#编译静态库需要的命令
AR = ar
#all是这个makefile文件的第一个目标文件,dynamic_lib static_lib是它的两个依赖,另外还有两个执行动作make  clear  和make install
#注意:dynamic_lib static_lib也是目标文件,所以在执行make clear和make  install之前,需要执行完这两个目标文件
#另外make  clear和make install之前加上@的意思是在执行这个语句时,不打印命令本身,只是输出命令执行的结果。这两个make在执行的过程中,会载入两次makefile这个文件,但是只执行clear和install 这两个目标
all:dynamic_lib static_lib
	@make  	clear
	@make	install

#用来生成动态库,all目标的一个依赖
dynamic_lib:
	${CC} -shared -fPIC *.c  -o lib${LIBNAME}.so

#用来生成静态库,all目标的一个依赖
static_lib:
	${CC} -c *.c
	${AR} -rcs lib${LIBNAME}.a *.o
	
#install是一个单独的目标,将当前路径下生成的库文件和头文件移到INSTPATH路径下
install:
	cp -rf lib${LIBNAME}.* ${INSTPATH}
	cp -rf *.h ${INSTPATH}
	
#也是一个单独的目标,将INSTPATH路径下的库文件和头文件删除,可在命令行下输入make uninstall执行该目标
uninstall:
	rm -f ${INSTPATH}/lib${LIBNAME}.*
	rm -f ${INSTPATH}/*.h

#删除当前路径下的.o文件(即编译生成的object文件),可在命令行下输入make  clear 执行该目标
clear:
	rm -f *.o
	
#删除当前路径下的库文件,可在命令行下输入make  clean来执行
clean: clear
	rm -f lib${LIBNAME}.*

执行make进行编译,可以看到一条make命令就同时编译并安装库文件和头文件到目标路径下了
在这里插入图片描述
执行make clean目标将当前路径下把编译生成的文件删除,也可以使用make uninstall命令将安装路径下安装的库文件和头文件删除:
在这里插入图片描述
当然我们也可以在test路径下编写一个makefile文件用来对main.c编译、运行测试:

CFLAGS: 指定头文件(.h文件)的路径
LDFLAGS:gcc 等编译器会用到的一些优化参数,可以在里面指定库文件的位置。

APPNAME=main
LIBPATH=`pwd`/../lib/

CFLAGS+=-I${LIBPATH}
LDFLAGS+=-L${LIBPATH}
LDFLAGS+=-static

CC=gcc
all:
	${CC} ${CFLAGS}  main.c  -o  ${APPNAME}  ${LDFLAGS}    -lpf
clean:
	rm -f ${APPNAME}
run:
	export  LD_LIBRARY_PATH={LD_LIBRARY_PATH}:LIBPATH
	./${APPNAME}

main.c

#include <stdio.h>
#include "pf.h"
int main (int argc, char **argv)
{
	func();
	printf("func() execute successfully!\n");
	return 0;
} 

在这里插入图片描述
如果每次我们都要切换到src或test路径下来编译还比较麻烦,这样我们可以在项目顶层路径下编写一个makefile文件,在他里面调用并执行每一个子目录下的makefile文件。如:

all:
	@echo "start  compile library"
	make   -C  src
	@echo  "start compile  test  program"
	make  -C  test
run:
	@echo  "start  run  test  progaram"
	make run -C  test
clean:
	make  clean -C   src
	make   clean -C  test
distclean:clean
	rm -f  lib/*.a
	rm -f  lib/*.so

在该makefile文件中make命令里的-C(大写C)选项指定进入到指定路径下执行相应目标, 譬如 make -C src是指到src路径下执行里面makefile文件里的默认目标,也就是总的目标all,而make clean -C src则是指到src路径下执行里面makefile文件里的
clean目标。

make
在这里插入图片描述
make run
在这里插入图片描述
make distclean
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值