Makefile makefile
make工程管理工具:帮助我们实现项目的编译在C语言中源文件才会参与编译。当只有某些文件做了修改,那么就重新编译整个项目是不可取的做法。我们需要一种方式,能够帮助我们自动判断,哪些文件做了修改,哪些文件需要重新编译,哪么文件需要重新先编译,哪些文件需要后编译。Makefile就能够帮助我们来完成这个任务。makefile仅仅是一个文件,里面也有很多规则,来说明如何编译,编译的顺序。Makefile中制定的规则会由make这个工具来执行。
当想要制定makefile文件名时
make -f my_mkfile
Makefile中的规则有很多,一条规则一般有一下几个部分组成:
目标:依赖列表
命令列表
其中第一条规则中的“目标”,将称为终极目标。我们的终极目标就是为了生成这个目标。
其他的规则之间,没有必然的顺序关系。
每一个规则中的目标可以是一个文件也可以是一个标签
每一个规则中目标,不一定要有依赖
Makefile的是根据文件的时间信息,来判断是否需要重新编译
如果想取消某一条命令本身的打印,那么可以在这条命令的前面加上@
赋值方式(=)和(:=), 前者是延时展开赋值,后者是立即展开赋值
条件赋值?=
A = 10
D ?= $A
追加赋值+=
E += $A
特殊变量:
$@ 当前规则中的目标
$^ 当前规则中的所有依赖
$< 当前依赖中的第一个
$$ 当前执行的进程编号
$* 模式规则中的所有%匹配的部分
$? 模式规则中所有比所在规则中目标更加新的文件组成列表
01写第一个简单的Makefile
//test.c
void test();
int main()
{
test();
return 0;
}
//tst
void test()
{
printf("%s\n", __FUNCTION__);
printf("%s\n", __func__);
printf("%s\n", __FILE__);
}
//Makefile
all:
@gcc test.c tst.c //@不回显这条命令
02写第二个简单的Makefile
//test.c
void test();
int main()
{
test();
return 0;
}
//tst
void test()
{
printf("%s\n", __FUNCTION__);
printf("%s\n", __func__);
printf("%s\n", __FILE__);
}
//Makefile
TGT = a.out
SRCS = test.c tst.c
$(TGT):$(SRCS)
gcc $^ -o $@
clean:
rm -f $(TGT)
03写第三个简单的Makefile
//test.c
void test();
int main()
{
test();
return 0;
}
//tst
void test()
{
printf("%s\n", __FUNCTION__);
printf("%s\n", __func__);
printf("%s\n", __FILE__);
}
//Makefile
TGT = a.out
SRCS = test.c tst.c
OBJ = test.o tst.o
CC = gcc
$(TGT):$(OBJ)
$(CC) $^ -o $@
test.o:test.c
$(CC) $^ -c -o $@
tst.o:tst.c
$(CC) $^ -c -o $@
clean:
rm -f $(TGT) $(OBJ)
//
//Makefile
TGT = a.out
SRCS = test.c tst.c
OBJ = test.o tst.o
CC = gcc
$(TGT):$(OBJ)
$(CC) $^ -o $@
%.o:%.c
$(CC) $< -c //即使不写也能成功,由于隐含规则的存在
//使用make -p打印隐含规则
clean:
rm -f $(TGT) $(OBJ)
//
//Makefile
TGT = a.out
SRCS = test.c tst.c
OBJ = test.o tst.o
CC = gcc
$(TGT):$(SRCS:.c=.o)
$(CC) $^ -o $@
#下面的规则称为模式规则,%c会匹配.c文件前面的部分
#模式规则执行时,是一个循环的过程,每一次取出一个.c,调用下面的命令生成对应的.o
%.o:%.c
$(CC) $< -c
clean:
rm -f $(TGT) $(SRCS:.c=.o)
//
04写第四个简单的Makefile
目录结构
04
|__->main.c
|__head
| |__->head.c
|__->tst.c
|__->foo.c
|__->Makefile
//main.c
include "head.h" //这样写是错误的,没办法识别head.h路径
//
int main()
{
test();
return 0;
}
//tst
#include <stdio.h>
void test()
{
printf("%s\n", __FUNCTION__);
printf("%s\n", __func__);
printf("%s\n", __FILE__);
}
//head.h
#pragma once
void test(void);
int main(void);
void foo(void);
//foo.c
#include<stdio.h>
void foo()
{
printf("%s\n", __TIME__);
}
//Makefile
TGT = a.out
SRCS = main.c test.c foo.c
指定交叉编译环境
#CROSS_COMPILER = arm-linux-
CC = $(CROSS_COMPILER)gcc
CFLAGS=-I$(HEAD_DIR) //编译选项
CUR_DIR = $(shell pwd)
HEAD_DIR = $(CUR_DIR)/head
$(TGT):$(SRCS:.c=.o)
$(CC) $^ $(CFLAGS)-o $@
%.o:%.c
$(CC) $< -c
#下面的规则用来说明,头文件的更新,应该使得依赖于它的文件被重新生成
%.d:%c
$(CC)n -MM $< >$@
#下面的一行用来告诉make,将上面的模式规则中的命令执行结果包含进当前文件(下面三行作用一样)
#include $(SRCS:.c=.o)
#-include $(SRCS:.c=.o)
sinclude $(SRCS:.c=.o)
.PHONY:clean:
rm -f $(TGT) $(SRCS:.c=.o) $(SRCS:.c=.d)
//
#伪目标:不是真正的目标,仅仅是为了执行其所在规则下面的命令,不应该
让make来判断它是否存在,或者是否应该被生成。
#.PHONY 后面声明的是伪目标
05写第五个简单的Makefile
目录结构
04
|__main
| |__->main.c
|
|__head
| |__->head.h
|
|__tst
| |__->tst.c
| |
| |__foo
| |__->foo.c
|
|__->Makefile
//main.c
include "head.h" //这样写是错误的,没办法识别head.h路径
//
int main()
{
test();
return 0;
}
//tst
void test()
{
printf("%s\n", __FUNCTION__);
printf("%s\n", __func__);
printf("%s\n", __FILE__);
}
//head.h
#pragma once
void test(void);
int main(void);
void foo(void);
//foo.c
#include<stdio.h>
void foo()
{
printf("%s\n", __TIME__);
}
#主Makefile:作用是制定规则来说明如何在当前目录下生成终极目标文件a.out
TGT = a.out
#指定子目录(生成a.out会依赖于这些目录)
SUB_DIR = main tst
#指定子目标
SUB_TGT = built-in.o
export TOP_DIR = $(shell pwd)
#指定头文件所在目录
export HEAD_DIR = $(TOP_DIR)/head
export CROSS_COMPILER = arm-linux-
export CC = $(CROSS_COMPILER)gcc
#编译选项
export CFLAGS = -I$(HEAD_DIR)/head -Wall
#指定链接器
export LD = ld
#指定链接选项
export LDFLAGS =
#终极目标所在规则
$(TGT):$(SUB_DIR)
$(CC) $(CFLAGS) $(^:=/$(SUB_TGT)
#下面的规则用来告诉make,进入到a.out所需要依赖的那些子目录
$(SUB_TGT):
make -C $@
#-C选项后面跟目录,可以让make进入到后面指定的目录
clean:
rm -vf $(TGT)
for dir in $(SUB_DIR); do \
make -C $$dir clean; \
done
.PHONY: clean $(SUB_DIR)
接下来开始写子makefile,用来告诉make如何生成当前目录下的子目标
SRCS = tst.c
SUB_DIR = foo
#下面的规则,说明了如何生成当前目录下的子目标
(是由当前目录下的.c生成的.o和当前目录下的子目录下的子目标临时打包生成的)
$(SUB_TGT): $(SRCS:.c=.o) $(SUB_DIR)
$(LD) $(LDFLAGS) $(SRCS:.c=.o) $(SUB_DIR:=/$(SUB_TGT)) -r -o $@
%.o:%.c
$(CC) $(CFLAGS) $< -c
%.d:%c
$(CC) $(CFLAGS) $< -MM >$@
sinclude $(SRCS:.c=.d)
$(SUB_DIR):
make -C $@
clean:
rm -vf $(SUB_TGT) $(SRCS:.c=.o) $(SRCS:.c=.d)
for dir in $(SUB_DIR); do \
make -C $$dir clean; \
done
.PHONY: clean $(SUB_DIR)