Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

GNU Make学习笔记

3
Apr
2011

GNU Make学习笔记

By Alex
/ in C,C++,Linux
0 Comments
make
概述

对于任何编译器可以通过Shell调用的编程语言,均可以通过GNU Make(以下称make)构建。make可以用于任何这样的任务:如果某些文件所依赖的文件发生了变化,则自动更新这些文件。

需要编写makefile文件来使用make,该文件中包含了文件之间的依赖关系、提供更新每个文件的命令,在makefile所在目录中运行make命令,即可完成文件更新,make会根据文件名GNUmakefile、makefile、Makefile依次寻找,可以使用-f或--file指定特殊的Makefile文件名。

make命令参数
参数   说明
 -b  -m 忽略和其它版本make的兼容性
-B --always-make 认为所有的目标都需要更新(重编译)
-C <dir>
--directory=<dir>
指定读取makefile的目录
--debug[=<options>]

输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:

a 也就是all,输出所有的调试信息。(会非常的多)
b 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
v 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i 也就是implicit,输出所有的隐含规则。
j 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
m 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息

-d相当于--debug=a

输出的东西很多,难以阅读,不如-n

-e
--environment-overrides
指明环境变量的值覆盖makefile中定义的变量的值
-f=<file>
--file=<file>
--makefile=<file>
指定需要执行的makefile
-h
--help
显示帮助信息
-i
--ignore-errors
在执行时忽略所有的错误
-I <dir>
--include-dir=<dir>
指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录
-j [<jobsnum>]
--jobs[=<jobsnum>]
指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少
-k
--keep-going
出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了
-l <load>
--load-average[=<load]
--max-load[=<load>]
指定make运行命令的负载
-n
--just-print
--dry-run
--recon

仅输出执行过程中的命令序列,但并不执行

可用于调试Makefile的执行过程

-o <file>
--old-file=<file>
--assume-old=<file>
不重新生成的指定的<file>,即使这个目标的依赖文件新于它
-p
--print-data-base
输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息
-q
--question
不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生
-r
--no-builtin-rules
禁止make使用任何隐含规则
-R
--no-builtin-variabes
禁止make使用任何内置系统变量
-s
--silent
--quiet
在命令运行时不输出命令的输出
-S
--no-keep-going
--stop
取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效
-t
--touch
相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行
-v
--version
输出make程序的版本、版权等关于make的信息
-w
--print-directory
输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用
--no-print-directory 禁止“-w”选项
-W <file>
--what-if=<file>
--new-file=<file>
--assume-file=<file>
假定目标<file>需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>的修改时间为当前时间
--warn-undefined-variables 只要make发现有未定义的变量,那么就输出警告信息
命令示例
Shell
1
2
#  列出所有目标
make -qp | awk -F':' '/^[a-zA-Z0-9][^$# \/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}' | sort
Makefile介绍
规则

简单的Makefile文件由一系列类似下面的“规则”组成:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
target ... : prerequisites ... # 如果prerequisites中某个文件比target更新或者target不存在,则会执行相应的命令
    recipe # 必须以TAB开头
    ...
# 或者
target ... : prerequisites ; recipe
 
# 目标(target):可以是某个程序生成的文件的名称,例如object文件、可执行文件;亦可是某个需要执行的操作的标签(伪目标),例如clean
# 前提(prerequisites)是作为target输入的文件,通常一个target依赖于多个文件。前提不是必须的
# 命令(recipe)表示需要make执行的操作,一个recipe可以包含多个命令,这些命令可以处于在同一行,或者各自一行
# 每个recipe前面需要有一个tab字符。如果任何prerequisites改变,则recipe包含的规则被执行以生成target文件
# 一个具体的例子:
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o  insert.o search.o files.o utils.o
main.o : main.c defs.h
    cc -c main.c
clean :
    rm edit main.o kbd.o command.o display.o \   # 反斜杠可以构成逻辑行(logical line)
    insert.o search.o files.o utils.o
# 更好的写法:
.PHONY : clean    # .PHONY表示伪目标
clean :
    -rm edit $(objects)  # 小减号表示无视命令错误

需要注意:

  1. 每个recipe前面必须是TAB
  2. 每个recipe在一个Subshell中执行,也就是说,你不能:
    Shell
    1
    2
    3
    clean:
        cd /tmp
        rm -rf *

    这样是无法删除/tmp/*的,因为第二个recipe的当前目录并不是/tmp,你需要这样:

    Shell
    1
    2
    clean:
        cd /tmp; rm -rf*

    如果命令太长,可以结尾 \并换行  

执行Makefile的过程

默认的make从Makefile的第一个target开始执行(不包括.开头的target),这称为“default goal”,伪目标可以作为default goal,只要其位于第一个。

  1. 读入所有的Makefile
  2. 读入被include的其它Makefile
  3. 初始化文件中的变量
  4. 推导隐晦规则,并分析所有规则
  5. 为所有的目标文件创建依赖关系链
  6. 根据依赖关系,决定哪些目标要重新生成
  7. 执行生成命令
Shell
1
2
3
4
5
all : prog1 prog2 prog3  # 伪目标作为default goal
.PHONY : all
prog1 : prog1.o utils.o
    cc -o prog1 prog1.o utils.o
...
使用变量

变量可以定义一个文本,并在之后多次替换

Shell
1
2
3
4
5
6
# 定义变量
objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
 
edit : $(objects)
    cc -o edit $(objects) # 使用变量
编写Makefile
Makefile包含的内容
 内容 说明 
明确规则(explicit rule) 明确描述如何重新构建一个或多个文件(即目标),列出其所依赖的其他文件,可能给出如何创建或更新target的recipe
隐含规则(implicit rule)

隐含规则根据文件名来判断如何重新构建它,描述了一个文件如何依赖一个名称类似的文件、如何更新文件

例如,对于任何一个whatever.o,会自动判定whatever.c是其依赖

变量定义(variable definition) 包含可以后续替换的预定义文本
指令(directive)

指示make在读取Makefile时执行特殊的操作:

  1. 读取另外一个Makefile
  2. 根据变量来确定是否使用或者忽略Makefile的一部分
  3. 定义多行变量
注释

# 开头的行为注释

包含其它Makefile文件

include指令使make暂停当前文件的读入,并读入其它一个或多个Makefile文件:

Shell
1
2
3
include filenames...
include foo *.mk $(bar)   # 包含目录下所有的mk文件,以及bar变量展开后对应当的文件
sinclude # 或者-include,即使找不到也不会导致报告错误

如果包含的文件不在当前目录,则按以下顺序寻找:

  1. 通过‘-I’ 或者‘--include-dir’ 命令选项的目录
  2. /usr/local/include
  3. /usr/gnu/include
  4. /usr/local/include
  5.  /usr/include

如果文件最终找不到,会生成一个警告信息

通配符

可以使用以下类型的Unix风格通配符和特殊符号来引用某些文件:

通配符  说明 
* 通配任意长度的字符串,例如 *.c 匹配任何C源文件
? 通配单个字符 
[...]  
~ 表示当前用户的HOME目录

注意,上述通配符不能自动在变量中展开(为文件集合),你必须使用wildcard函数:

Shell
1
objects := $(wildcard *.o) 
指定文件搜索路径

如果源文件分散在多个目录中,可以使用以下方式指定这些目录:

Shell
1
2
3
4
VPATH = src:../headers  # 路径使用冒号分隔
# 或者使用vpath关键字,指定某种模式的文件在哪里寻找
vpath  
vpath %.h ../headers  # %表示匹配0-N个字符
空目标

如果希望一个目标不做任何事情,可以:

Shell
1
2
3
null:
    # 空目标
    @:
伪目标

伪目标不是文件,并不需要生成伪目标对应的文件。使用.PHONY : target的形式声明一个伪目标

多目标

Makefile中可以包含多个目标,如果多个目标依赖同一个文件,并且其recipe类似,则可以将其合并:

Shell
1
2
3
4
5
6
7
8
bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@ # $@是一个自动化变量,表示当前规则中所有target的集合
 
# 等价于
bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput
依赖

依赖(prerequisites)是指作为某个目标前提条件的目标,依赖必须先于目标构建。

依赖有两类:

  1. 普通依赖:依赖需要先行构建,如果任何一个依赖过期,则目标被认为是过期的
  2. order-only依赖:不会触发目标的重新构建
1
target : normal-prerequisites | order-only-prerequisites

同一个目标的普通依赖,先于order-only依赖构建。

静态模式

可以使用静态模式简化多目标规则的定义:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
....
    
# target 定义了一系列的target,可以使用通配符
# target-parrtern 可选,定义target的模式,例如%.o表示目标都是.o文件
# prereq-parrterns 定义依赖的模式,例如%.c表示依赖与目标同名,且扩展名为c
 
# 例子:
objects = foo.o bar.o
all: $(objects)
 
$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
# $<、$@都是自动化变量,前者表示依赖的集合,后者表示目标的集合,上述规则展开后等价于:
foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o
# 如果%.o文件有数百个,这样的静态模式将会非常方便
回显与错误处理
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 命令的回显
# 在命令前添加@,可以禁止回显
@echo Compiling module...
# make -n | --just-print 可以只回显,而不执行命令,便于调试
# make -s | --slient可以全面禁止回显
 
# 命令执行
# 如果下一条命令依赖于上一条的结果,则应当放置在同一行,并分号隔之
cd /home/alex/CPP; PWD
 
# 命令出错
# 如果命令的返回值为非0,默认make会终止执行
# 在命令前面添加-号,可以阻止该行为
clean:
    -rm -f *.o
# make -i | --ignore-errors则可以忽视所有错误
# make -k | --keep-going可以在出错时终止当前规则,继续执行下一规则
嵌套执行Make

大型工程中,可能把不同模块的源代码存放在不同目录中,并且在对应的目录中分别编写Makefile,这样有利于Makefile的维护。可以在父目录中编写一个总控Makefile,它负责分别调用各子Makefile:

Shell
1
2
3
4
5
6
MAKE = make -k
subsystem:
    cd subdir && $(MAKE)
# 或者:
subsystem:
    $(MAKE) -C subdir

除非指定-e参数,否则总控Makefile不会覆盖子Makefile中定义的变量。如果需要把某个变量传递到子Makefile中,可以:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
export <variable ...>
 
export variable = value
# 等价于
variable = value
export variable
 
# 传递所有变量:
export
 
# 如果要禁止某个变量传递,则:
unexport <variable ...>

SHELL、MAKEFLAGS这两个变量,无论export与否,都会自动传递到子Makefile。MAKEFLAGS是make的参数(-C、-f、-h、-o、-W不会传递到子Makefile),可以通过下面的语句禁止传递:

Shell
1
2
subsystem:
    cd subdir && $(MAKE) MAKEFLAGS=

在嵌套执行时,可以使用-w|--print-directory来显示正在进入的子目录,Make会打印Entering directory、Leaving directory等信息,使用-C时该标记自动打开。

定义命令包

如果Makefile中经常出现相同的命令序列,可以将其定义为一个变量:

Shell
1
2
3
4
5
6
7
8
# 以define开始,endef结束
define run-yacc   # run-yacc是该命令包的名字,不能与已定义的变量重名
yacc $(firstword $^)
mv y.tab.c $@
endef
 
foo.c : foo.y
    $(run-yacc)  # 和使用普通变量一样
使用变量

Makefile中的变量类似于C/C++的宏,代表一个字符串,在执行时按原样展开并替换变量的引用,变量名大小写敏感,不得包含:、# 、=、空白符

变量基础知识
  1. 变量声明时必须赋予初值
  2. 使用前,需要加前缀$,最好使用$()或者${}这样的语法
  3. $字符使用$$转义得到
变量赋值
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#  = 赋值,仅仅当引用变量时才延迟的扩展
#  变量可以使用后面的变量来定义,延迟扩展
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
    echo $(foo) # 打印Huh?
 
#  := 赋值,立即计算出值
# 另外一种赋值方式,只能使用前面定义的变量给新变量赋值
x := foo
y := $(x) bar
x := later
 
dir := /foo/bar   # 同行井号终止变量赋值,前面的空格属于变量值的一部分
 
# 如果FOO没有被定义,则赋值为bar,否则什么都不做
FOO ?= bar
 
# 追加变量值
variable := value
variable += more  # value more
变量高级用法
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 变量值替换:把var中所有以a结尾的部分替换为b
# 所谓结尾,是指空白或者结束符出现的地方
$(var:a=b)
# 示例
foo := a.o b.o c.o
bar := $(foo:.o=.c)# 所有.o换成.c
 
# 另外一种变量值替换,在静态模式一节讲述过
# 依赖于foo中每个子串具有相同的pattern
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
 
 
# 把变量值作为另外一个变量的名字:
x = y
y = z
a := $($(x))# a的值为z
Override指示符

如果变量是通过make命令行参数设置的,那么Makefile对其赋值会被忽略,使用override指示符可以改变此行为:

Shell
1
2
3
override <variable> = <value>
override <variable> := <value>
override <variable> += <more text>
多行变量

与命令包类似,可以通过define定义多行变量:

Shell
1
2
3
4
define two-lines
echo foo
echo $(bar)
endef
环境变量

如果Makefile中的变量、make命令行传入变量,与系统环境变量重名,那么系统环境变量被覆盖

make -e使系统环境变量覆盖Makefile中的变量

目标变量

可以定义局部作用域的变量:

Shell
1
2
3
4
5
6
7
8
9
10
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
# variable-assignment可以是各种赋值表达式,例如=、:=、+=、?=
 
# 局部变量自动带入目标引发的所有规则中
prog : CFLAGS = -g # 局部变量
prog : prog.o foo.o bar.o# 规则
    $(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
    $(CC) $(CFLAGS) prog.c # CFLAGS的值依旧是-g,只要该规则是prog目标引发的
模式变量

可以定义一个模式,把变量定义应用到符合此模式的所有目标上。make中的模式至少包含一个%号。

Shell
1
2
3
4
5
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
 
# 把变量CFLAGS应用到所有.o文件
%.o : CFLAGS = -O
自动化变量

在模式规则中,目标、依赖目标都是一批文件,为了只书写一个命令就完成所有目标的生成,需要使用自动化变量。

自动化变量会把匹配模式的文件自动的、逐个取出,注意自动化变量只应当出现在规则的命令部分。

变量  说明
$@

目标的文件名。在模式规则中,对应匹配的单个文件

$%

目标成员名。对于目标 foo.a(bar.o), $@为 foo.a, $%则为 bar.o

如果目标不是archive成员则$%为空

$<

第一个依赖的名字。在模式规则中,对应匹配的单个文件

$? 所有比目标新的依赖的集合,以空格分隔
$^ 所有的依赖的集合,以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标
$+ 同上,但是不去除重复依赖目标
$* 表示目标模式%及其之前的部分,例如对于模式a.%.b、目标dir/a.foo.b,$*为dir/a.foo
$(@D) $@的目录部分,不以/结尾,如果$@种不包含目录,则值为.(当前目录)
$(@F)

$@的文件部分。类似的还有:

$(*D)、$(*F)
$(%D)、$(%F)
$(<D)、$(<F)
$(^D)、$(^F)
$(+D)、$(+F)
$(?D)、$(?F)

特殊变量
变量 说明
MAKE 当前使用的make解释器的路径
MAKECMDGOALS 本次构建的目标列表
MAKEFILE_LIST make需要处理的makefile列表,当前makefile总是位于列表的最后
MAKE_VERSION make的版本
CURDIR make的工作目录
.VARIABLES 所有变量的集合,包括预定义变量、自定义变量
使用条件判断

使用条件判断可以让make在运行时走不同的分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 语法
<conditional-directive> # 条件指令,这一行上多余的空格是允许的,只要不是TAB开头
<text-if-true>
endif
 
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
 
# 条件指令:ifeq、ifneq、ifdef、ifndef,写法:
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
 
ifeq ($(strip $(foo)),) # 如果strip函数的返回值为空
<text-if-empty>
endif
 
ifdef var1 # 如果var1的值非空
endif
 
# 示例
foo: $(objects)
ifeq ($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(objects) $(normal_libs)
endif
 
ifdef HTTP_PROXY
_DOCKER_RUN_EXTRA_ARGS += --env HTTP_PROXY=${HTTP_PROXY}
endif
 
ifeq ($(ROOT_PACKAGE),)
    $(error the variable ROOT_PACKAGE must be set prior to including golang.mk)
endif

需要注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式来选择语句,所有最好不要把自动化变量放入条件表达式中,因为自动化变量在运行时才有。

使用函数

函数的返回值可以作为变量使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 函数调用语法
$(<function> <arguments>)
# 或是
${<function> <arguments>}
 
# 示例
comma:= ,  # 一个逗号
empty:=    # 空
space:= $(empty) $(empty) # 利用空定义了一个空格
foo:= a b c  # 值为"a b c"
# 调用subst函数,参数分别为:被替换子串、替换子串、字符串
bar:= $(subst $(space),$(comma),$(foo))
 
# 这样相当于定义一个函数
createTmp=$(shell mktemp -d)
# 每次执行$(createTmp)都会重新mktemp
字符串处理函数
函数名 说明
 subst

$(subst <from>,<to>,<text>)

名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。 

示例:

Shell
1
2
# 把street中的ee换成EE
$(subst ee,EE,feet on the street)
patsubst

$(patsubst <pattern>,<replacement>,<text>)

名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。 这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。

1
2
APPS = minimal bootstrap
$(patsubst %,$(OUTPUT)/%.o,$(APPS)) # minimal.o bootstrap.o
strip

$(strip <string>)

名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。

findstring

$(findstring <find>,<in>)

名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。

filter

$(filter <pattern...>,<text>)

名称:过滤函数——filter。
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
返回:返回符合模式<pattern>的字串。

filter-out

$(filter-out <pattern...>,<text>)

名称:反过滤函数——filter-out。
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。
返回:返回不符合模式<pattern>的字串。

sort

$(sort <list>)

名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
备注:sort函数会去掉<list>中相同的单词。

word

$(word <n>,<text>)

名称:取单词函数——word。
功能:取字符串<text>中第<n>(索引从1开始)个单词
返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返
回空字符串。

wordlist

$(wordlist <s>,<e>,<text>)

名称:取单词串函数——wordlist。
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。

words

$(words <text>)

名称:单词个数统计函数——words。
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words, foo bar baz)返回值是“3”。
备注:如果我们要取<text>中最后的一个单词,可以这样:$(word $(words <text>),<text>)。

firstword

$(firstword <text>)

名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstword foo bar)返回值是“foo”。

lastword 最后一个单词
文件名操作函数
函数名 说明
dir

$(dir <names...>)

名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。

notdir

$(notdir <names...>)

名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“ /”)之后的部分。
返回:返回文件名序列<names>的非目录部分。

suffix

$(suffix <names...>)

名称:取后缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。

basename

$(basename <names...>)

名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。

addsuffix

$(addsuffix <suffix>,<names...>)

名称:加后缀函数——addsuffix。
功能:把后缀<suffix>加到<names>中的每个单词后面。
返回:返回加过后缀的文件名序列。

addprefix

$(addprefix <prefix>,<names...>)

名称:加前缀函数——addprefix。
功能:把前缀<prefix>加到<names>中的每个单词后面。
返回:返回加过前缀的文件名序列。

join

$(join <list1>,<list2>)

名称:连接函数——join。
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
返回:返回连接过后的字符串。

foreach 函数

该函数很特殊,用来产生循环结构。

Shell
1
2
3
4
5
6
7
8
9
10
11
# 语法
$(foreach <var>,<list>,<text>)
# var 局部变量名
# list 遍历单词列表
# text针对每个单词需要执行的表达式
# 返回值:空格分隔的表达式计算结果的列表
 
# 举例
names := a b c d
files := $(foreach n,$(names),$(n).o)
# files=a.o b.o c.o d.o”
if 函数

与GNU Make的条件判断很类似

Shell
1
2
3
4
5
6
# 语法
$(if <condition>,<then-part>)
# 或者
$(if <condition>,<then-part>,<else-part>)
 
# 返回值:依据condition真假返回then-part、else-part,或者空字符串
call函数

创建一个表达式,并传入参数调用之

Shell
1
2
3
4
5
6
7
# 语法
$(call <expression>,<parm1>,<parm2>,<parm3>...)
 
# 执行call函数时,实参会代替expression中类似$(1),$(2),$(3)的形参:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
# foo为 b a
origin函数

该函数说明变量的来源:

1
2
3
4
5
6
7
# 语法
$(origin <variable>)
 
# 示例
ifeq ($(origin GOBIN), undefined)
    GOBIN := $(GOPATH)/bin
endif
返回值 说明
undefined 如果<variable>从来没有定义过,origin函数返回这个值
default 如果<variable>是一个默认的定义,比如“CC”这个变量
file 如果<variable>这个变量被定义在Makefile中
command line 如果<variable>这个变量是被命令行定义的
override 如果<variable>是被override指示符重新定义的
automatic 如果<variable>是一个命令运行中的自动化变量
shell函数

shell函数的参数应该就是操作系统Shell的命令,类似于反引号。

Shell
1
2
3
# 示例
contents := $(shell cat foo)
files := $(shell echo *.c)
控制make的函数

一些函数可以控制make的运行。可检测一些运行Makefile时的运行时信息,并且根据这些信息来决定是让make继续执行,还是停止

 函数 说明 
error

$(error <text ...>)

产生一个致命的错误,<text ...>是错误信息

warning

$(warning <text ...>)

这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息

eval

动态定义一个仅仅对本次运行有效的目标

Shell
1
2
3
4
5
6
7
8
# 获取 make target ARGS
ARGS:=$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
# 将所有ARGS定义为空目标
$(eval $(ARGS):;@:)
 
 
# 现在你可以传递参数给make target了
# make target arg0 arg1 
make命令的运行
退出码
 退出码 说明 
0 表示成功执行
1  如果make运行时出现任何错误,其返回1
2  如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2
指定目标

指定目标 一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这是make的默认行为。

只需要在make命令后添加目标的名字,即可执行指定的目标。

环境变量MAKECMDGOALS存放make的最终目标:

Shell
1
2
3
4
5
sources = foo.c bar.c
# 如果最终目标不是clean,则包含foo.d、bar.d两个makefile
ifneq ( $(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif
目标
伪目标命名约定
 伪目标  说明
all 这个伪目标是所有目标的目标,其功能一般是编译所有的目标
clean 删除所有被make创建的文件
install 安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目录
print 列出改变过的源文件
tar 是把源程序打包成一个tar文件
dist 创建一个压缩文件,一般是把tar文件压成zip文件或是gz文件
TAGS 更新所有的目标,以备完整地重编译使用
check/test 用来测试makefile的流程
特殊目标
目标 说明
.PHONY 其所有依赖被看作是伪目标
.DEFAULT 其规则用于构建那些没有声明具体规则的目标
.PRECIOUS 其所有依赖在make过程中被特殊处理:当命令在执行过程中被中断时,make不会删除它们。而且如果目标的依赖文件是 中间过程文件,同样这些文件不会被删除。依赖可以是模式,例如%.o
.INTERMEDIATE 其依赖被作为中间过程文件看到
.SECONDARY

其依赖被作为中间过程文件对待。但这些文件不会被自动删除

没有任何依赖的.SECONDARY表示:所有文件作为中间过程文件,且不自动擅长

.DELETE_ON_ERROR 如果规则执行错误,则删除已经被修改的目标文件
隐含规则

本章讲述的一些在Makefile中的“隐含的”规则。所谓隐含规则,就是一种惯例,例如即使Makefile中没有书写把*.c编译为.o这一规则,make命令也会自动推倒出此规则并执行。

隐含规则会使用一些系统变量,通过改变这些变量,我们可以改变隐含规则的运行时行为,例如修改CFLAGS可以控制编译器参数。

我们可以通过“Pattern规则”、“suffix规则”来定义自己的“隐含规则”。

使用隐含规则

只要不显式编写针对某个目标的规则,那么隐含规则即可针对此目标生效,例如:

Shell
1
2
3
4
5
6
# 并没有编写目标foo.o、bar.o的规则,因此make会使用隐含规则推导其依赖目标、生成命令
# 自动寻找的隐含规则为:
# 把*.o的依赖设置为*.c
# 使用命令 cc –c $(CFLAGS) *.c来生成*.o文件
foo : foo.o bar.o
    cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

 make会到隐含规则库中寻找可用的规则,如果找不到,则会报错。另外,在隐含规则库中,规则是有顺序的,前面的规则优先被识别和使用。

隐含规则一览
 隐含规则  说明
编译C程序的隐含规则 *.o目标的依赖目标会自动推导为*.c,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)
编译C++程序的隐含规则 *.o目标的依赖目标会自动推导为*.c或者*.C,并且其生成命令是$(CXX) –c $(CPPFLAGS) $(CFLAGS)
编译Pascal程序的隐含规则 *.o目标的依赖目标会自动推导为*.p,并且其生成命令是$(PC) –c $ (PFLAGS)
汇编和汇编预处理的隐含规则

*.o目标的依赖目标会自动推导为*.s,默认使用编译器as,并且其生成命令是$(AS) $(ASFLAGS)

*.s目标的依赖目标会自动推导为*.S,默认使用C预编译器cpp,并且其生成命令是:$(AS) $(ASFLAGS)

链接Object文件的隐含规则 如果*目标的依赖目标设置为*.o,则通过运行C的编译器来运行链接程序(一般为ld)生成,ld”)命令是:$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
隐含规则使用的变量

隐含规则都使用一些预先设置的变量,可以在Makefile中指定、通过命令行传入、通过环境变量设置。

变量  说明 
AR 函数库(*.a或者Windows下的*.lib)打包程序。默认命令是ar
AS 汇编语言编译程序。默认命令是as
CC C语言编译程序。默认命令是cc
CXX C++语言编译程序。默认命令是g++
CPP C程序的预处理器(输出是标准输出设备)。默认命令是$(CC) –E
RM 删除文件命令。默认命令是rm -f
ARFLAGS 函数库打包程序AR命令的参数。默认值是rv
ASFLAGS 汇编语言编译器参数
CFLAGS C语言编译器参数
CXXFLAGS C++语言编译器参数
CPPFLAGS C预处理器参数
LDFLAGS 链接器参数,例如ld
定义Pattern规则

可以使用模式规则来自定义一个隐含规则。模式规则与一般规则类似,只是,规则的目标必须包含%字符,%统配一个或者多个字符(目标文件名),依赖目标中也可以使用%,其值取决于目标中%的值。

注意:变量和函数的展开发生在读取Makefile时,而模式规则中%的值取决于运行时。

模式规则示例:

Shell
1
2
3
4
5
6
# 把所有.c文件编译为.o文件
%.o : %.c
 
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
# 自动化变量$<表示所有依赖目标的逐个值
# 自动化变量$@表示所有目标的逐个值
remake
简介

remake是对make的Fork和扩展,特性:

  1. 剖析,使用 --profile选项可以查看什么目标耗时严重,输出格式Callgrind Profile
  2. 列出目标: --tasks(打印目标之前一行的注释) --targets
  3. 使用 -c / --search-parent自动从上级目录查找target
  4. 改善执行跟踪, -x /  --trace=normal,可以显示任何将要执行的命令
  5. 调试能力:查看目标属性、查看当前目标Stack、设置断点、设置/展开GNU变量、生成展开GNU变量后的Shell脚本以便在外面运行
安装

到https://sourceforge.net/projects/bashdb/files/remake/下载源代码,然后安装:

Shell
1
2
3
./configure
make
sudo make install
使用remake调试功能

如果执行Makefile过程中出现问题,通常可以基于remake的Call stack检查、扩展错误信息,这两个特性,完成诊断。更进一步,可以使用--trace/-x选项。

如果这些手段都无法解决问题,考虑使用Debugger,从而:

  1. 检查变量状态、目标如何被定义、目标状态
  2. 在特定位置停止执行,例如在某个目标,或者发生错误之时
  3. 修改状态
进入调试模式

使用 remake -X 或 remake --debugger发起调用,从一开始就进入调试状态。

或者,可以在目标中启用调试:

1
2
3
4
5
6
7
8
9
10
11
foo: bar
 
debug:
      $(debugger "debug target break")
 
bar:
      $(debugger "first bar command")
      @echo hi
 
baz: debug
      @echo hello again

最后,使用选项 --post-mortem 或  -!,可以在出错时进入调试模式。 

调试输出简介

使用-X命令以调试模式启动,你会看到类似下面的输出:

Shell
1
2
3
4
5
6
7
8
9
10
11
# remake -X
Reading makefiles...
Updating makefiles...
# -> 提示尚未完成目标的前置条件检查
#  当前位置信息
-> (/tmp/libcdio-paranoia/Makefile:428)
# 目标    依赖         依赖
Makefile: Makefile.in config.status
# 提示符 <0>中的0表示命令历史序号,每次发生递归的remake调用,都会在内部增加一层<>
remake<0> step
remake<<0>>
单步跟踪

要进行单步跟踪,可以在提示符下输入 step命令。 Makefile的解释/执行会向前进一步,这可能导致Step in:

Shell
1
2
3
4
5
6
7
8
9
10
11
# 这会导致Step In,因为Makefile依赖于Makefile.in
remake<1> step
-> (/tmp/libcdio-paranoia/Makefile:415)
# 需要注意,在Debugger中,变量已经展开
Makefile.in: Makefile.am m4/ld-version-script.m4 ...
# 源码中的415行:
# (srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
remake<2> step
-> (/src/external-vcs/github/rocky/libcdio-paranoia/Makefile:443)
aclocal.m4: m4/ld-version-script.m4 ...
remake<3>
打印Backtrace

要查看当前目标依赖的Trace,可以输入 backtrace命令: 

Shell
1
2
3
4
5
remake<3> backtrace
=>#0  aclocal.m4 at /tmp/libcdio-paranoia/Makefile:443
  #1  Makefile.in at /tmp/libcdio-paranoia/Makefile:415
  #2  Makefile at /tmp/libcdio-paranoia/Makefile:428
remake<4>
结束调试会话

要结束调试会话,输入命令: quit

设置并运行至断点

单步跟踪非常啰嗦,我们可以通过命令 continue,直接跳转到自己感兴趣的目标:

Shell
1
2
3
4
5
6
7
# 在目标dist上设置断点
remake<0> continue dist
Breakpoint 1 on target `dist', mask 0x0f: file Makefile, line 703.
Updating goal targets...
-> (/src/external-vcs/github/rocky/libcdio-paranoia/Makefile:703)
dist:
remake<1>

这时,在输入step命令,可以跟踪目标dist的执行过程:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
remake<1> step
File 'dist' does not exist.
Must remake target 'dist'.
Makefile:704: target 'dist' does not exist
# 这里是要执行的命令
##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
remake  dist-bzip2 dist-gzip am__post_remove_distdir='@:'
##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
# 这里的++表示正在Stepping Shell命令
++ (/src/external-vcs/github/rocky/libcdio-paranoia/Makefile:703)
dist
remake<2>
查看目标源码

要查看Makefile中,当前正在执行的目标的源码,可以使用 list命令: 

Shell
1
2
3
4
5
6
remake<2> list
/tmp/libcdio-paranoia/Makefile:705
dist:
#  recipe to execute (from 'Makefile', line 706):
    $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
    $(am__post_remove_distdir)
读写变量

使用 print命令,可以查询任何GNU Make变量的定义,需要注意,依赖的变量不会展开:

Shell
1
2
3
4
5
6
7
remake<2> print MAKE
# origin default表示内置变量定义
(origin default) MAKE = $(MAKE_COMMAND)
 
 
remake<3> print DATA
Makefile:168 (origin: makefile) DATA := libcdio_paranoia.pc libcdio_cdda.pc

使用 expand命令,可以展开任何变量的值:

Shell
1
2
remake<4> expand MAKE
(origin default) MAKE := remake

除了查询,你也可以设置变量的值,使用 set、 setq或 setqx命令:

Shell
1
2
3
4
5
6
# set命令,在定义NAME的值之前,将$(MAKE_COMMAND)展开
remake<6> set MAKE $(MAKE_COMMAND)
Variable MAKE now has value 'remake'
# setq则不展开
remake<7>  setq MAKE $(MAKE_COMMAND)
Variable MAKE now has value '$(MAKE_COMMAND)'
调试Shell命令

考虑下面的示例Makefile:

test2.mk
Shell
1
2
3
4
5
6
PACKAGE=make
 
all: $(PACKAGE).txt
 
$(PACKAGE).txt: ../doc/remake.texi
    makeinfo --no-headers $< > $@

启动调试后:

Shell
1
2
3
4
5
6
7
8
9
# remake -X -f test2.mk
...
Reading makefiles...
updating makefiles....
Updating goal targets....
  /tmp/remake/src/test2.mk:3        File `all' does not exist.
 
-> (/tmp/test2.mk:5)
make.txt: ../doc/remake.texi

暂停在目标make.txt,我们要想看该目标的信息,可以使用 target命令。但是该命令返回的信息太多了,要仅仅查看它关联的自动变量,可以:

Shell
1
2
3
4
5
6
7
8
9
10
11
# 这三个命令效果一样:target make.txt variables, target @ variables, info locals
remake<1> target @ variables
@ := all
% :=
 
* :=
+ := make.txt
| :=
< := all
^ := make.txt
? :=

为target命令指定选项,可以仅仅列出它的Shell命令:

Shell
1
2
3
4
5
remake<2> target @ commands
 
make.txt:
#  commands to execute (from `test2.mk', line 6):
    makeinfo --no-headers $< > $@

还可以将命令完全展开:

Shell
1
2
3
4
5
6
7
8
remake<5> target @ expand
 
#  commands to execute (from `test2.mk', line 6):
    makeinfo --no-headers $< > $@
 
# 完全展开
#  commands to execute (from `test2.mk', line 6):
    -makeinfo --no-headers ../doc/remake.texi > make.txt

使用 write命令,可以将当前target的命令写出为Shell脚本: 

Shell
1
2
3
(/tmp/remake/src/test2.mk:6): make.txt
remake<6> write
File "/tmp/make.txt.sh" written.

使用 shell命令,可以在Debugger中执行shell命令,例如查看生成的脚本的内容: 

Shell
1
2
3
4
5
remake<7> shell cat -n /tmp/make.txt.sh
#!/bin/sh
# cd /tmp/remake/src/
#/tmp/remake/src/test2.mk:5
makeinfo --no-headers ../doc/remake.texi > make.txt

如果使用 step命令,remake会一句一句的执行命令:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
remake<8> step
 
  Must remake target `make.txt'.
Invoking recipe from test2.mk:6 to update target `make.txt'.
##>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
makeinfo --no-headers ../doc/remake.texi > make.txt
##<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 
# ++ 表示我们即将运行代码
++ (/tmp/test2.mk:5)
 
 
make.txt
remake<9> step
  Successfully remade target file `make.txt'.
# <- 表示执行完毕,即将退出目标
<- (/tmp/test2.mk:5)
make.txt
remake<10>

使用 finish命令,可以执行到当目标的结尾处。 

事件图标
图标 事件
-> 在检查一个目标的前置条件之前,暂停了执行
.. 在检查一个目标的前置条件之后,暂停了执行
<- 在执行了目标的命令列表之后,暂停了执行
rd 即将读取一个Makefile
!! 在post-mortem(出错后调试)模式下,发生了错误
- - 运行一个Makefile目标的step
++ 运行一个POSIX命令的step
:o 在Makefile中,通过$(debugger)函数进入调试模式
|| 完成
调试命令列表
命令 说明
break

设置断点: break {target | line-number} [ all | run | prereq | end ]*

查看断点: break

示例:

Shell
1
2
3
4
break               # 列出所有断点
break 10            # 在Debugger停止在的Makefile的第10行设置断点
break tests         # 在tests目标上设置断点
break tests prereq  # 在test的依赖检查完成之前设置断点 
delete 删除断点,需要指定断点序号: delete [ bpnumber [bpnumber…] ]
print 打印变量定义(不展开): print [variable]
expand 展开变量: expand string
write 输出目标的命令到文件: write [target [[filename|**here*]]]
target

查看目标的信息: [target-name] [info1 [info2…]]

target-name可以是目标名,或者 @表示当前目标; <表示第一个依赖

infoX可以是:

commands 为了更新目标需要执行的命令列表
depends 目标的依赖列表
nonorder 无需按序执行的依赖
order 必须按序执行的依赖
expand 类似于commands,但是展开所有Makefile变量
state 目标状态
time 目标关联的文件的修改时间,是否updated。phony目标提示File does not exist
variables 自动变量,例如@或<

edit 在当前目标的位置编辑Makefile
list

列出目标的依赖: list [ target ]

列出目标或所在行号的命令: list line-number | -

load 读入并eval Makfile: load file-glob
info

获取被调试程序的各种信息:

info break  断点列表
info files  读入文件列表
info frame 当前目标的栈帧
info line 当前行信息
info program  Makefile的信息
info rules 隐含或统配规则信息
info target  目标名字
info targets 所有目标
info tasks  目标及其依赖信息
info variables 变量信息

cd 设置当前目录
pwd 打印当前目录
shell 执行Shell命令
continue

Run to xxx,继续执行,直到下一个断点或停止点

格式: continue [ target [all | run | prereq | end ]* ]

如果 target 不为空,则先在target的相关位置设置断点,然后Run to那个地方

finish

Step out,执行直到当前目标结束的那个点

格式: finish [ count ]。count表示Step out几层,默认0

next

Step over,单步/多步跟踪

格式: next [ count ]

step 类似于next,但是粒度更细
skip 跳过当前目标尚未结束的命令
run 重启执行过程: run [ args ]
quit 优雅退出
backtrace 打印调用栈帧,每个帧都是一个目标
frame 重新定位到栈帧中的某个帧: frame [ number ]
setq 设置变量值,不进行Makefile变量展开: setq basename value
setqx 设置变量值,进行Makefile变量展开: setqx variable value

 

← Spring知识集锦
2011年清明节 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Linux内核学习笔记(一)
  • Native编程知识集锦
  • Visual Studio知识集锦
  • AutoTools学习笔记
  • Bazel学习笔记

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2