make命令是一个常用的编译命令,尤其在C/C++开发中,make命令通过makefile文件中描述源程序之间的依赖关系进行自动编译;
makefile文件是按照规定格式编写,需说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系;
首次执行make命令时,编译所有相关文件,之后再执行make命令时,以增量方式进行编译,即只对修改的源文件相关的目标文件进行编译;
注:许多tarball格式的开源软件,解压后先执行./configure,再执行make,然后执行make install进行安装;
makefile文件支持include,即把一些基本依赖规则写在一个公共文件中,其他makefile文件包含此文件;
通常公共makefile文件命名为common.mk;
一、make命令
make命令后接参数,称为目标;
1 常见目标
make all:编译所有目标
make install:安装已编译的程序
make uninstall:卸载已安装的程序
make clean:删除由make命令产生的文件,通常删除目标文件.o
make distclean:删除由./configure产生的文件
make check:测试刚编译的软件
make installcheck:检查安装的库和程序
make dist:重新打包成packname-version.tar.gz
执行make命令时,需要一个Makefile文件,以告诉make命令如何编译和链接程序;
2 参数
-B:重新建立所有目标
-d:打印调试信息
-C:切换到指定路径下寻找Makefile
-f:将指定文件看做Makefile
-j:同时运行命令的个数,即多线程执行Makefile,后接的个数可由nproc命令返回值来指定
注:nproc命令打印当前进程可用的处理数(线程数);
二、程序的编译和链接
一般在C/C++开发中,首先将源文件编译成目标文件(Windows下.obj文件,Unix下.o文件)——编译compile,再将目标文件合成执行文件——链接link;
三、make命令如何工作?
1 make在当前目录下寻找“Makefile”或“makefile”文件
2 若找到,查找文件中的第一个目标文件.o
3 若目标文件不存在,根据依赖关系查找.s文件
4 若.s文件不存在,根据依赖关系查找.i文件
5 若.i文件不存在,根据依赖关系查找.c文件,此时.c文件一定存在,于是生成一个.o文件,再去执行
四、Makefile文件格式
1 概述
Makefile文件由一系列规则rules构成,每条规则形式如下:
<target>: <prerequisites>
[Tab]<commands>
第一行冒号前为目标,冒号后为前置条件;第二行必须由一个Tab键起首,后接命令;
目标是必须的,不可省略;前置条件和命令是可选的,但两者必须至少存在一个;
每条规则明确两件事——构建目标的前置条件是什么?如何构建?
2 目标target
目标可以是文件名,指明make命令所要构建的对象;也可以是某个操作名称,称“伪目标”;
clean:
rm *.o
以上代码目标是clean,命令是rm *.o;
执行make clean命令,实现对象文件的删除;
为避免设置的伪目标名称在当前路径下有相同名称的文件,make命令发现该名称的文件已存在,便不再构建,也就不执行rm操作的情况发生,先将该名称声明为伪目标,因此make命令不会检查是否存在该名称的文件,每次执行对应的操作;
.PHONY:clean
clean:
rm *.o
若make命令没有指定目标,默认执行Makefile文件中第一个目标;
3 前置条件prerequisites
前置条件通常是一组文件名,用空格隔开;
指定目标是否重新构建的判断标准——只要有一个前置条件不存在或有更新,则该目标需重新构建;
result.txt:source.txt
cp source.txt result.txt
若当前路径下source.txt存在,make result.txt可正常执行,否则需再写一条规则,用于生成source.txt;
source.txt:
echo "This is a source file." > source.txt
source.txt没有前置条件,与其他文件文官,只要该文件不存在,每次执行make source.txt命令都会生成该文件;
若要生成多个文件,写法如下:
source:file1 file2 file3
source是伪目标,只有3个前置条件,没有对应命令;执行make source命令后一次性生成file1 file2 file3文件,比如下写法方便:
make file1
make file2
make file3
4 命令commands
命令表示如何更新目标文件,由一行或多行shell命令组成;
注:
shell命令一定是写在命令中,否则会被make忽略;
每行命令前必须有一个Tab键;
每行命令在一个独立的shell中执行,shell之间没有继承关系,因此上一行为的变量赋值,在下一行无效;
若前后两条命令有共享数据,可写在一行,用分号隔开;
var-kept:
export foo=bar;echo "foo=[$$foo]"
或,在换行符前加反斜杠\进行转义;
var-kept:
export foo=bar;\
echo "foo=[$$foo]"
或,加上.ONSHELL命令;
.ONESHELL:
var-kept:
export foo=bar;
echo "foo=[$$foo]"
五、Makefile文件语法
1 注释
每行以#开头的为注释;
2 回声echoing
正常情况下,make打印每条命令,再执行该命令,称回声;
在命令前加@,关闭回声,即只输出命令的执行结果,出错则停止执行;
注:
前缀-表示命令执行有错,忽略错误,继续执行;
不加前缀输出执行的命令和命令执行的结果,出错则停止执行;
3 通配符
通配符指定一组符合条件的文件;
Makefile通配符与bash一致;
*:任意0个或多个字符
?:任意一个字符
[...]:方括号内任意一个字符
4 模式匹配
make命令允许对文件名进行类似正则运算的匹配,主要用到%;
5 变量和赋值符
自定义变量,使用=赋值;
调用变量,将变量名放在$()中;
Makefile提供四种赋值运算符——=、:=、?=、+=;
(1)=
递归展开赋值,默认赋值方式;
var2=$(var1)
var1="TEST"
all:
echo $(var2)
输出:TEST
(2):=
直接赋值,不会递归展开,若引用的变量不存在,则为空串;
var2:=$(var1)
var1="TEST"
all:
echo $(var2)
输出:(空串)
(3)?=
若未初始化,则赋值;
var1="test"
var1?="TEST"
var2?="TEST"
all:
echo $(var1) and $(var2)
输出:test and TEST
(4)+=
将值追加到现有内容末尾;
var1="TEST"
var1+="test"
all:
echo var1
输出:TESTtest
六、内置变量
make命令提供一系列内置变量,如$(CC)指向当前使用的编译器,$(MAKE)指向当前使用的make工具;
RM:rm -f
AR:ar
CC:cc
CXX:g++
七、自动变量
自动变量的值与当前规则有关;
1 $@
表示当前目标;
2 $<
表示第一个前置条件;
3 $?
表示所有比目标更新的前置条件;
4 $^
表示所有前置条件;
5 $(@D)和$(@F)
$(@D)表示$@的目标名,$(@F)表示$@的文件名;
八、判断
Makefile文件使用bash语法,完成判断;
<条件语句>
<条件为真,执行程序段>
else
<条件为假,执行程序段>
endif
1 条件语句
(1)ifeq 比较两个参数值是否相等
ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq 'arg1' "arg2"
ifeq "arg1" 'arg2'
注:参数还可用make函数,如ifeq ($(strip $(foo)),);
(2)ifneq 比较两个参数值是否不等
ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq 'arg1' "arg2"
ifneq "arg1" 'arg2'
(3)ifdef 判断变量是否有值
ifdef var
(4)ifndef 判断变量是否无值
ifndef var
举例1:
bar=
foo=$(bar)
ifdef foo
frobozz=yes
else
frobozz=no
endif
#frobozz值为yes
举例2:
foo=
ifdef foo
frobozz=yes
else
frobozz=no
endif
#frobozz值为no
注:以上举例说明ifdef只是测试一个变量是否有值,而非把变量扩展到当前位置!
九、循环
Makefile文件使用bash语法,完成判断;
1 写法一
LIST变量是Makefile变量,引用Makefile变量需使用$()括起来;
而all目标后的命令是shell命令,其中定义的变量也是shell变量,引用shell变量需使用$$作为开头,但shell变量不需括号;
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
2 写法二
all:
for i in one two three; do \
echo $$i; \
done
十、函数
1 函数的调用语法
函数调用,类似变量使用,语法如下:
$(func args) # 推荐!
# 或
${func args}
其中,args参数之间以","隔开,func和args之间以" "隔开;
2 字符串处理函数
(1)$(subst <from>,<to>,<text>)
字符串替换,将字符串<text>中的<from>转换成<to>,返回替换后的字符串;
(2)$(patsubst <pattern>,<replacement>,<text>)
模式字符串替换,查找字符串<text>中符合模式<pattern>的单词,并替换成<replacement>;
注:<pattern>可包括通配符%,表示任意长度的字符串;
若<replacement>也包括%,则这里的%是<pattern>中%代表的字符串;
(3)$(strip <string>)
去空格,去掉<string>中开头和结尾的空白符,返回去掉空格的字符串;
(4)$(findstring <find>,<in>)
查找字符串,在字符串<in>中查找<find>字符串,若找到则返回<find>,否则返回空字符串;
(5)$(filter <pattern...>,<text>)
过滤,以<pattern>模式过滤<text>字符串中的单词,保留符合模式的单词,返回符合模式的字符串;
可有多个模式,模式之间以" "隔开;
(6)$(filter-out <pattern...>,<text>)
反向过滤,以<pattern>模式过滤<text>字符串中的单词,去掉符合模式的单词,返回不符合模式的字符串;
可有多个模式,模式之间以" "隔开;
(7)$(sort <list>)
排序,给字符串<list>中的单词以升序排序,返回排序后的字符串;
(8)$(word <n>,<text>)
取单词,获取并返回字符串<text>中第<n>个单词(从1开始),若<n>比<text>中的单词数大,则返回空字符串;
(9)$(wordlist <s>,<e>,<text>)
取单词串,获取并返回字符串<text>中第<s>个到第<e>个的单词串;
(10)$(words <text>)
单词个数统计,统计并返回字符串<text>中单词个数;
注:获取字符串中最后一个单词,使用$(word $(words <text>),<text>)
(11)$(firstword <text>)
首单词,获取并返回字符串<text>中第一个单词;
注:等价于$(word 1,<text>)
3 文件名处理函数
(1)$(dir <names...>)
取目录,从文件名序列<names>中获取并返回目录部分;
注:目录部分指最后一个反斜杠之前的部分,若无反斜杠,则返回"./";
(2)$(notdir <names...>)
取文件,从文件名序列<names>中获取并返回文件部分;
注:文件部分指最后一个反斜杠之后的部分;
(3)$(suffix <names...>)
取后缀,从文件名序列<names>中获取并返回各文件的后缀序列,若无后缀,则返回空字符串;
(4)$(basename <names>)
取前缀,从文件名序列<names>中获取并返回各文件的前缀序列,若无前缀,则返回空字符串;
(5)$(addsuffix <suffix>,<names...>)
加后缀,把<suffix>加到<names>中每个单词后面并返回;
(6)$(addprefix <prefix>,<names...>)
加前缀,把<prefix>加到<names>中每个单词前面并返回;
(7)$(join <list1>,<list2>)
连接,将<list2>中的单词对应的加到<list1>中单词之后;
(8)$(wildcard <pattern...>)
拓展通配符,用于定义变量和引用函数时通配符失效的情况,获取符合模式的字符串并返回;
4 其他函数
(1)$(foreach <var>,<list>,<text>)
把<list>中单词逐一取出放到<var>所指定的变量中,再执行<text>所包含的表达式,每次执行<text>都会返回一个字符串,执行完foreach后返回由多个字符串组成、空格隔开的字符串;
### Makefile 内容
targets := a b c d
objects := $(foreach i,$(targets),$(i).o)
all:
@echo $(targets)
@echo $(objects)
### bash 中执行 make
$ make
a b c d
a.o b.o c.o d.o
(2)$(if <condition>,<then-part>,<else-part>)
### Makefile 内容
val := a
objects := $(if $(val),$(val).o,nothing)
no-objects := $(if $(no-val),$(val).o,nothing)
all:
@echo $(objects)
@echo $(no-objects)
### bash 中执行 make
$ make
a.o
nothing
(3)$(shell <shell-command>)
执行一个shell命令,将执行结果作为函数返回;
(4)make控制函数
$(error <text>):产生一个致命错误,Makefile停止执行
$(warning <text>):输出警告信息,Makefile继续执行
(5)$(call <expression>,<para1>,<para2>,...)
<expression>表达式中的变量,如$(1)、$(2)、...等被参数<para1>、<para2>、...依次取代,<expression>的返回值就是call函数的返回值;
### Makefile 内容
log = "====debug====" $(1) "====end===="
all:
@echo $(call log,"正在 Make")
### bash 中执行 make
$ make
====debug==== 正在 Make ====end====
十一、Makefile实例
1 编译C语言项目
edit : main.o kbd.o command.o display.o
cc -o edit main.o kbd.o command.o display.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h
cc -c display.c
clean :
rm edit main.o kbd.o command.o display.o
.PHONY: edit clean
2 公共Makefile文件common.mk
#This is the common part for makefile
SOURCE := $(wildcard *.c) $(wildcard *.cc) $(wildcard *.cpp)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(patsubst %.cpp,%.o,$(SOURCE))))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
CPPFLAGS += -MD
.PHONY : everything objs clean veryclean vc rebuild ct rl
everything : $(TARGETS)
objs : $(OBJS)
clean :
@$(RM) *.o
@$(RM) *.d
veryclean: clean
@$(RM) $(TARGETS)
@$(RM) cscope.out
@$(RM) core*
vc: veryclean
ct:
@$(RM) $(TARGETS)
rl: ct everything
rebuild: veryclean everything
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM) $(patsubst %.d,%.o,$@)
endif
-include $(DEPS)
3 Makefile文件编写训练
(1)准备工作
准备三个文件file1.c、file2.c、file2.h:
file1.c:
#include <stdio.h>
#include "file2.h"
int main()
{
printf("print file1\n");
File2Print();
return 0;
}
file2.c:
#include "file2.h"
void File2Print()
{
printf("Print file2\n");
}
file2.h:
#ifndef FILE2_H_
#define FILE2_H_
#ifdef __cplusplus
extern "C" {
#endif
void File2Print();
#ifdef __cplusplus
}
#endif
#endif
(2)Makefile文件基础编程
helloworld: file1.o file2.o
gcc file1.o file2.o -o helloworld
file1.o: file1.c file2.h
gcc -c file1.c -o file1.o
file2.o: file2.c file2.h
gcc -c file2.c -o file2.o
clean:
rm -rf *.o helloworld
注:
gcc命令中,-c参数将后接文件(.c或.cc)编译成目标文件(.o),-o参数指定输出文件名(默认为.o文件);
(3)Makefile文件进阶编程一——使用变量
OBJS = file1.o file2.o
CC = gcc
CFLAGS = -Wall -O -g
helloworld : $(OBJS)
$(CC) $(OBJS) -o helloworld
file1.o : file1.c file2.h
$(CC) $(CFLAGS) -c file1.c -o file1.o
file2.o : file2.c file2.h
$(CC) $(CFLAGS) -c file2.c -o file2.o
clean:
rm -rf *.o helloworld
注:
gcc命令中,-Wall -O -g参数用于配置编译器,-Wall参数输出所有警告信息,-O参数在编译时优化,-g参数编译debug版本;
ARFLAGS:ar命令的参数
CFLAGS:C语言编译器的参数
CXXFLAGS:C++语言编译器的参数
(4)Makefile文件进阶编程二——使用函数
CC = gcc
XX = g++
CFLAGS = -Wall -O –g
TARGET = ./helloworld
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o:%.cpp
$(XX) $(CFLAGS) -c $< -o $@
SOURCES = $(wildcard *.c *.cpp)
OBJS = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES)))
$(TARGET) : $(OBJS)
$(XX) $(OBJS) -o $(TARGET)
chmod a+x $(TARGET)
clean:
rm -rf *.o helloworld
注:
$(wildcard *.c *.cpp)函数获取并返回所有以.c或.cpp结尾的文件列表;
$(patsubst %.c,%o,$(SOURCE))函数获取所有以.c结尾的字符串并替换成以.o结尾的新的文件列表;