ucore lab1 实验报告 01 -- 库函数
- 0. demo 说明
- 1. function.mk
- 1.1 .SECONDEXPANSION
- 1.2 listf -- 列出所有文件
- 1.3 toobj -- 替换后缀为 .o
- 1.4 todep -- 替换后缀为 .d
- 1.5 totarget -- 添加 bin/ 前缀
- 1.6 packetname -- 添加 __objs\_ 前缀
- 1.7 cc_template -- 源文件按编译模板展开
- 1.8 do_cc_compile -- 列表中文件分别按编译模板展开
- 1.9 do_add_files_to_packet
- 1.10 do_add_objs_to_packet
- 1.11 do_create_target
- 1.12 do_finish_all
- 1.13 封装函数
0. demo 说明
为方便说明 Makefile 中各个函数的作用,构造了 demo,各文件如下:
.
├── Makefile # 每节例子
├── src
│ ├── common.c
│ ├── common.h
│ ├── init.c
│ ├── init.h
│ ├── main.c
│ └── README
└── tools└── function.mk # ucore 源码目录下待解析的 Makefile
各源码文件内容如下:
// common.c
#include <stdio.h>
#include "common.h"void prinfo(const char *s)
{if (!s) return ;printf(INFO "%s\n", s);
}// commmon.h
#ifndef _COMMON_H
#define _COMMON_H#define INFO "[INFO] "
void prinfo(const char *s);#endif// init.c
#include "init.h"
#include "common.h"static void mm_init(void)
{prinfo("memory init...");
}static void rest_init(void)
{prinfo("rest init...");
}int start_kernel(void)
{prinfo("start kernel...");mm_init();rest_init();
}//init.h
#ifndef _INIT_H
#define _INIT_Hint start_kernel(void);
static void mm_init(void);
static void rest_init(void);#endif// main.c
#include "init.h"void main(void)
{start_kernel();
}
1. function.mk
1.1 .SECONDEXPANSION
# 二次扩展
.SECONDEXPANSION:
用法如下:
ONEVAR = onefile
TWOVAR = twofile
myfile: $(ONEVAR) $$(TWOVAR)
# 第一次扩展
# myfile: onefile $(TWOVAR)
# 第二次扩展
# myfile: onefile twofile
1.2 listf – 列出所有文件
SLASH := /
# list all files in some directories: (#directories, #types)
listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\$(wildcard $(addsuffix $(SLASH)*,$(1))))
列出指定目录下所有指定后缀的文件。参数 1 指定目录,参数 2 指定后缀,如果参数 2 为空,则列出参数 1 指定目录,用法如下:
# 列出 src 目录下所有 .c .h 后缀的文件
SLASH := /include tools/function.mkall:echo $(call listf,src,c h)# 或者省略后缀类型,则列出所有文件
or:echo $(call listf,src)# make -s 结果如下:
result1:src/init.h src/common.h src/init.c src/common.c src/main.c
# make or -s 结果如下:
result2:src/init.h src/common.h src/init.c src/common.c src/README src/main.c
1.3 toobj – 替换后缀为 .o
OBJDIR := obj
SLASH := /
# get .o obj files: (#files[, packet])
toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\$(addsuffix .o,$(basename $(1))))
后缀替换成 .o
,参数 1 为文件列表,由空格隔开,用法如下:
SLASH := /
OBJDIR := objinclude tools/function.mkall:echo $(call toobj,$(notdir $(call listf,src,c)),temp)
or:echo $(call toobj,$(notdir $(call listf,src,c)))# make -s 结果如下:
result1:obj/temp/init.o obj/temp/common.o obj/temp/main.o
# make or -s 结果如下:
result2:obj/init.o obj/common.o obj/main.o
1.4 todep – 替换后缀为 .d
# get .d dependency files: (#files[, packet])
todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2)))
后缀替换成 .d
,用法与 toobj 相同,如下:
SLASH := /
OBJDIR := objinclude tools/function.mkall:echo $(call todep,$(notdir $(call listf,src,c)),temp)
or:echo $(call todep,$(notdir $(call listf,src,c)))# make -s 结果如下:
result1:obj/temp/init.d obj/temp/common.d obj/temp/main.d
# make or -s 结果如下:
result2:obj/init.d obj/common.d obj/main.d
1.5 totarget – 添加 bin/ 前缀
BINDIR := bin
SLASH := /
totarget = $(addprefix $(BINDIR)$(SLASH),$(1))
添加 bin/ 前缀,用法如下:
SLASH := /
OBJDIR := obj
BINDIR := bininclude tools/function.mkall:echo $(call totarget,image)# make -s 输出结果如下:
result:bin/image
1.6 packetname – 添加 __objs_ 前缀
OBJPREFIX := __objs_
# change $(name) to $(OBJPREFIX)$(name): (#names)
packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX))
添加 _objs 前缀,用法如下:
SLASH := /
OBJDIR := obj
BINDIR := bin
OBJPREFIX := __objs_include tools/function.mkall:echo $(call packetname,image)
or:echo $(call packetname)# make -s 结果如下:
result1:__objs_image
# make or -s 结果如下:
result2:__objs_
1.7 cc_template – 源文件按编译模板展开
# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir])
define cc_template
$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@)@$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@
$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@)@echo + cc $$<$(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@
ALLOBJS += $$(call toobj,$(1),$(4))
endef
指定编译模板,参数 1 指定源文件,参数 2 指定编译器,参数 3 指定编译选项,参数4 与 toobj、todep 第二个参数作用相同,用法如下:
.SECONDEXPANSION:
$(eval $(call cc_template,main.c,gcc,-g,temp))
展开后,效果如下:
obj/temp/main.d: main.c | obj/temp/@gcc -I./ -g -MM main.c -MT "obj/temp/main.o obj/temp/main.d"> obj/temp/main.d
obj/temp/main.o: main.c | obj/temp/$(V)gcc -I./ -g -c main.c -o obj/temp/main.o
ALLOBJS += obj/temp/main.o
说明:
-I
gcc 参数:首先在指定目录中寻找头文件
-M
生成依赖信息,包含标准头文件
-MM
生成依赖信息,忽略由标准头文件 #include <head.h>
造成的依赖
-MT
指定依赖目标
# gcc -MM main.c init.c common.c 的结果为
main.o: main.c init.h
init.o: init.c init.h common.h
common.o: common.c common.h# gcc -MM main.c init.c common.c -MT "main init" 的结果为
main init: main.c init.h
main init: init.c init.h common.h
main init: common.c common.h
|
表示 order-only
依赖目标,第一次执行 make 时寻找并生成该依赖,生成后即使该依赖目标文件时间变化,也不更新目标。
$<
第一个依赖目标
$@
第一个依赖目标
完整示例如下:
SLASH := /
OBJDIR := obj
BINDIR := bin
OBJPREFIX := __objs_.SECONDEXPANSION:include tools/function.mkSRC := src/main.c
TMP := tmp
OBJ := $(call toobj,$(SRC),$(TMP))
DEP := $(call todep,$(SRC),$(TMP))
ORDER := $(OBJDIR)$(SLASH)$(TMP)$(SLASH)$(dir $(SRC))all: $(OBJ) $(DEP)$(eval $(call cc_template,$(SRC),gcc,-g,$(TMP)))$(ORDER):mkdir -p $@.PHONY: clean
clean:rm -rf obj bin
1.8 do_cc_compile – 列表中文件分别按编译模板展开
# compile file: (#files, cc[, flags, dir])
define do_cc_compile
$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4))))
endef
对参数 1 列出的每个文件分贝按编译模板展开,用法如下:
SLASH := /
OBJDIR := obj
BINDIR := bin
OBJPREFIX := __objs_.SECONDEXPANSION:include tools/function.mkSRC := $(call listf,src,c)
TMP := tmp
OBJ := $(call toobj,$(SRC),$(TMP))
DEP := $(call todep,$(SRC),$(TMP))
ORDER := $(OBJDIR)$(SLASH)$(TMP)$(SLASH)src/all: $(DEP) $(OBJ)
$(eval $(call do_cc_compile,$(SRC),gcc,-g,$(TMP)))$(ORDER):mkdir -p $@.PHONY: clean
clean:rm -rf obj bin
1.9 do_add_files_to_packet
# add files to packet: (#files, cc[, flags, packet, dir])
define do_add_files_to_packet
__temp_packet__ := $(call packetname,$(4))
ifeq ($$(origin $$(__temp_packet__)),undefined)
$$(__temp_packet__) :=
endif
__temp_objs__ := $(call toobj,$(1),$(5))
$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5))))
$$(__temp_packet__) += $$(__temp_objs__)
endef
用法如下:
SLASH := /
OBJDIR := obj
BINDIR := bin
OBJPREFIX := __objs_.SECONDEXPANSION:include tools/function.mkSRC := $(call listf,src,c)
TMP := tmp
OBJ := $(call toobj,$(SRC),$(TMP))
DEP := $(call todep,$(SRC),$(TMP))
ORDER := $(OBJDIR)$(SLASH)$(TMP)$(SLASH)src/all: $(DEP) $(OBJ)echo " ALLOBJS: $(ALLOBJS)"echo "__temp_packet__: $(__temp_packet__)"echo " __temp_objs__: $(__temp_objs__)"echo " __objs_HELLO: $(__objs_HELLO)"$(eval $(call do_add_files_to_packet,$(SRC),gcc,-g,HELLO,$(TMP)))$(ORDER):mkdir -p $@.PHONY: clean
clean:rm -rf obj bin
上例中,调用 do_add_files_to_packet
部分效果如下:
__temp_packet__ := __objs_HELLO
# 如果 __objs_HELLO 未定义,则定义
ifeq ($(origin __objs_HELLO),undefined)
__objs_HELLO :=
endif
__temp_objs__ := obj/tmp/src/init.o obj/tmp/src/common.o obj/tmp/src/main.o
# foreach 语句和 do__cc_compile 效果相同
__objs_HELLO += obj/tmp/src/init.o obj/tmp/src/common.o obj/tmp/src/main.o
1.10 do_add_objs_to_packet
# add objs to packet: (#objs, packet)
define do_add_objs_to_packet
__temp_packet__ := $(call packetname,$(2))
ifeq ($$(origin $$(__temp_packet__)),undefined)
$$(__temp_packet__) :=
endif
$$(__temp_packet__) += $(1)
endef
用法如下:
$(eval $(call do_add_objs_to_packet,main.o init.o common.o,HELLO))
效果与 1.9 节类似,只是没有展开为编译模板部分,如下所示:
__temp_packet__ := __objs_HELLO
# 如果 __objs_HELLO 未定义,则定义
ifeq ($(origin __objs_HELLO),undefined)
__objs_HELLO :=
endif
__objs_HELLO += main.o init.o common.o
1.11 do_create_target
# add packets and objs to target (target, #packes, #objs[, cc, flags])
define do_create_target
__temp_target__ = $(call totarget,$(1))
__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3)
TARGETS += $$(__temp_target__)
ifneq ($(4),)
$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@)$(V)$(4) $(5) $$^ -o $$@
else
$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@)
endif
endef
用法如下:
$(eval $(call do_create_target,main,,obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o,gcc,-g))
效果如下:
__temp_target__ = bin/main
__temp_objs__ = obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o # 这里没有使用参数 2
TARGETS += bin/main
# 如果参数4 (指定编译器) 不为空,则
bin/main: obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o | bin/gcc -g $^ -o $@
# $^ 表示所有依赖文件
# $@ 表示目标文件
# 如果参数4 为空,则
bin/main: obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o | bin/
1.12 do_finish_all
# finish all
define do_finish_all
ALLDEPS = $$(ALLOBJS:.o=.d)
$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)):@$(MKDIR) $$@
endef
定义 ALLDEPS 变量为所有目标文件后缀替换成 .d,并创建第一个目标所需的目录
1.13 封装函数
下面的函数是对前面介绍的各种定义的简单封装。
# 对 do_cc_compile 的封装
# @files 编译源文件
# @cc 指定编译器
# @flags 编译参数,如 -g
# @dir 中间目录
# Example: $(eval $(call cc_compile,main.c init.c common.c,gcc,-g,temp))
# compile file: (#files, cc[, flags, dir])
cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4)))# 对 do_add_files_to_packet 的封装
# @files 编译源文件
# @cc 指定编译器
# @flags 编译参数
# @packet packet指定的项目前添加 OBJPREFIX 指定的字符前缀,包含多项时,每项通过空格分割
# @dir 中间目录
# Example: $(eval $(call do_add_files_to_packet,main.c init.c,gcc,-g,main init,temp))
# add files to packet: (#files, cc[, flags, packet, dir])
add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5)))# 对 do_add_objs_to_packet 的封装
# @objs 目标文件
# @packet 同 add_files 中的 packet
# Example: $(eval $(call do_add_objs_to_packet,main.o init.o,main init))
# add objs to packet: (#objs, packet)
add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2)))# 对 do_create_target 的封装
# @target 最终目标
# @packet 同 add_files 中的 packet
# @obj 目标文件
# @cc 编译器
# @flags 编译选项
# Example: $(eval $(call do_create_target,main,PACKET,main.o init.o common.o,gcc,-g))
# add packets and objs to target (target, #packet, #objs, cc, [, flags])
create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5)))# 对参数 1 指定的项目中的每一项添加 OBJPREFIX 指定的字符前缀,然后构成新的变量,输出新变量的值
read_packet = $(foreach p,$(call packetname,$(1)),$($(p)))
# 下例的输出为 HELLO WORLD
OBJPREFIX := __objs_
__objs_main := HELLO
__objs_init := WORLD
target:echo "$(call read_packet,main init)"# 参数 1 指定目标,参数 2 指定依赖
add_dependency = $(eval $(1): $(2))# 对 do_finish_all 的封装,定义 ALLDEPS 变量为所有目标文件后缀替换成 .d,并创建第一个目标所需的目录
finish_all = $(eval $(call do_finish_all))