makefile教程

分類 devops, make

简介

Linux的make程序用来自动化编译大型源码,很多时候,我们在Linux下编译安装软件,只需要敲一个make就可以全自动完成,非常方便。

make能自动化完成这些工作,是因为项目提供了一个Makefile文件,它负责告诉make,应该如何编译和链接程序。

Makefile相当于Java项目的pom.xml,Node工程的package.json,Rust项目的Cargo.toml,不同之处在于,make虽然最初是针对C语言开发,但它实际上并不限定C语言,而是可以应用到任意项目,甚至不是编程语言。此外,make主要用于Unix/Linux环境的自动化开发,掌握Makefile的写法,可以更好地在Linux环境下做开发,也可以为后续开发Linux内核做好准备。

在本教程中,我们将由浅入深,一步一步学习如何编写Makefile,完全针对零基础小白,只需要提前掌握如何使用Linux命令。


在Linux环境下,当我们输入make命令时,它就在当前目录查找一个名为Makefile的文件,然后,根据这个文件定义的规则,自动化地执行任意命令,包括编译命令。

Makefile这个单词,顾名思义,就是指如何生成文件。

我们举个例子:在当前目录下,有3个文本文件:a.txtb.txtc.txt

现在,我们要合并a.txtb.txt,生成中间文件m.txt,再用中间文件m.txtc.txt合并,生成最终的目标文件x.txt,整个逻辑如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────┐ ┌─────┐ ┌─────┐
│a.txt│ │b.txt│ │c.txt│
└─────┘ └─────┘ └─────┘
│ │ │
└───┬───┘ │
│ │
▼ │
┌─────┐ │
│m.txt│ │
└─────┘ │
│ │
└─────┬─────┘


┌─────┐
│x.txt│
└─────┘

根据上述逻辑,我们来编写Makefile

规则

Makefile由若干条规则(Rule)构成,每一条规则指出一个目标文件(Target),若干依赖文件(prerequisites),以及生成目标文件的命令。

例如,要生成m.txt,依赖a.txtb.txt,规则如下:

1
2
3
# 目标文件: 依赖文件1 依赖文件2
m.txt: a.txt b.txt
cat a.txt b.txt > m.txt

一条规则的格式为目标文件: 依赖文件1 依赖文件2 ...,紧接着,以Tab开头的是命令,用来生成目标文件。上述规则使用cat命令合并了a.txtb.txt,并写入到m.txt。用什么方式生成目标文件make并不关心,因为命令完全是我们自己写的,可以是编译命令,也可以是cpmv等任何命令。

#开头的是注释,会被make命令忽略。

1
注意:Makefile的规则中,命令必须以Tab开头,不能是空格。

类似的,我们写出生成x.txt的规则如下:

1
2
x.txt: m.txt c.txt
cat m.txt c.txt > x.txt

由于make执行时,默认执行第一条规则,所以,我们把规则x.txt放到前面。完整的Makefile如下:

1
2
3
4
5
x.txt: m.txt c.txt
cat m.txt c.txt > x.txt

m.txt: a.txt b.txt
cat a.txt b.txt > m.txt

在当前目录创建a.txtb.txtc.txt,输入一些内容,执行make

1
2
3
$ make
cat a.txt b.txt > m.txt
cat m.txt c.txt > x.txt

make默认执行第一条规则,也就是创建x.txt,但是由于x.txt依赖的文件m.txt不存在(另一个依赖c.txt已存在),故需要先执行规则m.txt创建出m.txt文件,再执行规则x.txt。执行完成后,当前目录下生成了两个文件m.txtx.txt

可见,Makefile定义了一系列规则,每个规则在满足依赖文件的前提下执行命令,就能创建出一个目标文件,这就是英文Make file的意思。

把默认执行的规则放第一条,其他规则的顺序是无关紧要的,因为make执行时自动判断依赖。

此外,make会打印出执行的每一条命令,便于我们观察执行顺序以便调试。

如果我们再次运行make,输出如下:

1
2
$ make
make: `x.txt' is up to date.

make检测到x.txt已经是最新版本,无需再次执行,因为x.txt的创建时间晚于它依赖的m.txtc.txt的最后修改时间。

1
make使用文件的创建和修改时间来判断是否应该更新一个目标文件。

修改c.txt后,运行make,会触发x.txt的更新:

1
2
$ make
cat m.txt c.txt > x.txt

但并不会触发m.txt的更新,原因是m.txt的依赖a.txtb.txt并未更新,所以,make只会根据Makefile去执行那些必要的规则,并不会把所有规则都无脑执行一遍。

在编译大型程序时,全量编译往往需要几十分钟甚至几个小时。全量编译完成后,如果仅修改了几个文件,再全部重新编译完全没有必要,用Makefile实现增量编译就十分节省时间。

当然,是否能正确地实现增量更新,取决于我们的规则写得对不对,make本身并不会检查规则逻辑是否正确。

伪目标

因为m.txtx.txt都是自动生成的文件,所以,可以安全地删除。

删除时,我们也不希望手动删除,而是编写一个clean规则来删除它们:

1
2
3
clean:
rm -f m.txt
rm -f x.txt

clean规则与我们前面编写的规则有所不同,它没有依赖文件,因此,要执行clean,必须用命令make clean

1
2
3
$ make clean
rm -f m.txt
rm -f x.txt

然而,在执行clean时,我们并没有创建一个名为clean的文件,所以,因为目标文件clean不存在,每次运行make clean,都会执行这个规则的命令。

如果我们手动创建一个clean的文件,这个clean规则就不会执行了!

如果我们希望makeclean不要视为文件,可以添加一个标识:

1
2
3
4
.PHONY: clean
clean:
rm -f m.txt
rm -f x.txt

此时,clean就不被视为一个文件,而是伪目标(Phony Target)。

大型项目通常会提供cleaninstall这些约定俗成的伪目标名称,方便用户快速执行特定任务。

一般来说,并不需要用.PHONY标识clean等约定俗成的伪目标名称,除非有人故意搞破坏,手动创建名字叫clean的文件。

执行多条命令

一个规则可以有多条命令,例如:

1
2
3
4
cd:
pwd
cd ..
pwd

执行cd规则:

1
2
3
4
5
6
$ make cd
pwd
/home/ubuntu/makefile-tutorial/v1
cd ..
pwd
/home/ubuntu/makefile-tutorial/v1

观察输出,发现cd ..命令执行后,并未改变当前目录,两次输出的pwd是一样的,这是因为make针对每条命令,都会创建一个独立的Shell环境,类似cd ..这样的命令,并不会影响当前目录。

解决办法是把多条命令以;分隔,写到一行:

1
2
cd_ok:
pwd; cd ..; pwd;

再执行cd_ok目标就得到了预期结果:

1
2
3
4
$ make cd_ok
pwd; cd ..; pwd
/home/ubuntu/makefile-tutorial/v1
/home/ubuntu/makefile-tutorial

可以使用\把一行语句拆成多行,便于浏览:

1
2
3
4
cd_ok:
pwd; \
cd ..; \
pwd

另一种执行多条命令的语法是用&&,它的好处是当某条命令失败时,后续命令不会继续执行:

1
2
cd_ok:
cd .. && pwd

控制打印

默认情况下,make会打印出它执行的每一条命令。如果我们不想打印某一条命令,可以在命令前加上@,表示不打印命令(但是仍然会执行):

1
2
3
no_output:
@echo 'not display'
echo 'will display'

执行结果如下:

1
2
3
4
$ make no_output
not display
echo 'will display'
will display

注意命令echo 'not display'本身没有打印,但命令仍然会执行,并且执行的结果仍然正常打印。

控制错误

make在执行命令时,会检查每一条命令的返回值,如果返回错误(非0值),就会中断执行。

例如,不使用-f删除一个不存在的文件会报错:

1
2
3
has_error:
rm zzz.txt
echo 'ok'

执行上述目标,输出如下:

1
2
3
4
$ make has_error
rm zzz.txt
rm: zzz.txt: No such file or directory
make: *** [has_error] Error 1

由于命令rm zzz.txt报错,导致后面的命令echo 'ok'并不会执行,make打印出错误,然后退出。

有些时候,我们想忽略错误,继续执行后续命令,可以在需要忽略错误的命令前加上-

1
2
3
ignore_error:
-rm zzz.txt
echo 'ok'

执行上述目标,输出如下:

1
2
3
4
5
6
$ make ignore_error
rm zzz.txt
rm: zzz.txt: No such file or directory
make: [ignore_error] Error 1 (ignored)
echo 'ok'
ok

make检测到rm zzz.txt报错,并打印错误,但显示(ignored),然后继续执行后续命令。

对于执行可能出错,但不影响逻辑的命令,可以用-忽略。

参考源码

可以从GitHub下载源码。

小结

编写Makefile就是编写一系列规则,用来告诉make如何执行这些规则,最终生成我们期望的目标文件。

查看官方手册:

编译C程序

C程序的编译通常分两步:

  1. 将每个.c文件编译为.o文件;
  2. 将所有.o文件链接为最终的可执行文件。

我们假设如下的一个C项目,包含hello.chello.hmain.c

hello.c内容如下:

1
2
3
4
5
6
7
#include <stdio.h>

int hello()
{
printf("hello, world!\n");
return 0;
}

hello.h内容如下:

1
int hello();

main.c内容如下:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include "hello.h"

int main()
{
printf("start...\n");
hello();
printf("exit.\n");
return 0;
}

注意到main.c引用了头文件hello.h。我们很容易梳理出需要生成的文件,逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌───────┐ ┌───────┐ ┌───────┐
│hello.c│ │main.c │ │hello.h│
└───────┘ └───────┘ └───────┘
│ │ │
│ └────┬────┘
│ │
▼ ▼
┌───────┐ ┌───────┐
│hello.o│ │main.o │
└───────┘ └───────┘
│ │
└───────┬──────┘


┌─────────┐
│world.out│
└─────────┘

假定最终生成的可执行文件是world.out,中间步骤还需要生成hello.omain.o两个文件。根据上述依赖关系,我们可以很容易地写出Makefile如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 生成可执行文件:
world.out: hello.o main.o
cc -o world.out hello.o main.o

# 编译 hello.c:
hello.o: hello.c
cc -c hello.c

# 编译 main.c:
main.o: main.c hello.h
cc -c main.c

clean:
rm -f *.o world.out

执行make,输出如下:

1
2
3
4
$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o

在当前目录下可以看到hello.omain.o以及最终的可执行程序world.out。执行world.out

1
2
3
4
$ ./world.out 
start...
hello, world!
exit.

与我们预期相符。

修改hello.c,把输出改为"hello, bob!\n",再执行make,观察输出:

1
2
3
$ make
cc -c hello.c
cc -o world.out hello.o main.o

仅重新编译了hello.c,并未编译main.c。由于hello.o已更新,所以,仍然要重新生成world.out。执行world.out

1
2
3
4
$ ./world.out 
start...
hello, bob!
exit.

与我们预期相符。

修改hello.h

1
2
// int 变为 void:
void hello();

以及hello.c,再次执行make

1
2
3
4
$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o

会触发main.c的编译,因为main.c依赖hello.h

执行make clean会删除所有的.o文件,以及可执行文件world.out,再次执行make就会强制全量编译:

1
2
3
4
5
$ make clean && make
rm -f *.o world.out
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o

这个简单的Makefile使我们能自动化编译C程序,十分方便。

不过,随着越来越多的.c文件被添加进来,如何高效维护Makefile的规则?我们后面继续讲解。

参考源码

可以从GitHub下载源码。

小结

Makefile正确定义规则后,我们就能用make自动化编译C程序。



使用隐式规则

我们仍然以上一节的C项目为例,当我们添加越来越多的.c文件时,就需要编写越来越多的规则来生成.o文件。

实际上,有的同学可能发现了,即使我们把.o的规则删掉,也能正常编译:

1
2
3
4
5
6
# 只保留生成 world.out 的规则:
world.out: hello.o main.o
cc -o world.out hello.o main.o

clean:
rm -f *.o world.out

执行make,输出如下:

1
2
3
4
$ make
cc -c -o hello.o hello.c
cc -c -o main.o main.c
cc -o world.out hello.o main.o

我们没有定义hello.omain.o的规则,为什么make也能正常创建这两个文件?

因为make最初就是为了编译C程序而设计的,为了免去重复创建编译.o文件的规则,make内置了隐式规则(Implicit Rule),即遇到一个xyz.o时,如果没有找到对应的规则,就自动应用一个隐式规则:

1
2
xyz.o: xyz.c
cc -c -o xyz.o xyz.c

make针对C、C++、ASM、Fortran等程序内置了一系列隐式规则,可以参考官方手册查看。

对于C程序来说,使用隐式规则有一个潜在问题,那就是无法跟踪.h文件的修改。如果我们修改了hello.h的定义,由于隐式规则main.o: main.c并不会跟踪hello.h的修改,导致main.c不会被重新编译,这个问题我们放到后面解决。

参考源码

可以从GitHub下载源码。

小结

针对C、C++、ASM、Fortran等程序,make内置了一系列隐式规则,使用隐式规则可减少大量重复的通用编译规则。

查看官方手册:



当我们在Makefile中重复写很多文件名时,一来容易写错,二来如果要改名,要全部替换,费时费力。

编程语言使用变量(Variable)来解决反复引用的问题,类似的,在Makefile中,也可以使用变量来解决重复问题。

以上一节的Makefile为例:

1
2
3
4
5
world.out: hello.o main.o
cc -o world.out hello.o main.o

clean:
rm -f *.o world.out

编译的最终文件world.out重复出现了3次,因此,完全可以定义一个变量来替换它:

1
2
3
4
5
6
7
TARGET = world.out

$(TARGET): hello.o main.o
cc -o $(TARGET) hello.o main.o

clean:
rm -f *.o $(TARGET)

变量定义用变量名 = 值或者变量名 := 值,通常变量名全大写。引用变量用$(变量名),非常简单。

注意到hello.o main.o这个“列表”也重复了,我们也可以用变量来替换:

1
2
3
4
5
6
7
8
OBJS = hello.o main.o
TARGET = world.out

$(TARGET): $(OBJS)
cc -o $(TARGET) $(OBJS)

clean:
rm -f *.o $(TARGET)

如果有一种方式能让make自动生成hello.o main.o这个“列表”,就更好了。注意到每个.o文件是由对应的.c文件编译产生的,因此,可以让make先获取.c文件列表,再替换,得到.o文件列表:

1
2
3
4
5
6
7
8
9
10
# $(wildcard *.c) 列出当前目录下的所有 .c 文件: hello.c main.c
# 用函数 patsubst 进行模式替换得到: hello.o main.o
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
TARGET = world.out

$(TARGET): $(OBJS)
cc -o $(TARGET) $(OBJS)

clean:
rm -f *.o $(TARGET)

这样,我们每添加一个.c文件,不需要修改Makefile,变量OBJS会自动更新。

思考:为什么我们不能直接定义OBJS = $(wildcard *.o)make列出所有.o文件?

内置变量

我们还可以用变量$(CC)替换命令cc

1
2
$(TARGET): $(OBJS)
$(CC) -o $(TARGET) $(OBJS)

没有定义变量CC也可以引用它,因为它是make的内置变量(Builtin Variables),表示C编译器的名字,默认值是cc,我们也可以修改它,例如使用交叉编译时,指定编译器:

1
2
CC = riscv64-linux-gnu-gcc
...

自动变量

Makefile中,经常可以看到$@$<这样的变量,这种变量称为自动变量(Automatic Variable),它们在一个规则中自动指向某个值。

例如,$@表示目标文件,$^表示所有依赖文件,因此,我们可以这么写:

1
2
world.out: hello.o main.o
cc -o $@ $^

在没有歧义时可以写$@,也可以写$(@),有歧义时必须用括号,例如$(@D)

为了更好地调试,我们还可以把变量打印出来:

1
2
3
4
5
world.out: hello.o main.o
@echo '$$@ = $@' # 变量 $@ 表示target
@echo '$$< = $<' # 变量 $< 表示第一个依赖项
@echo '$$^ = $^' # 变量 $^ 表示所有依赖项
cc -o $@ $^

执行结果输出如下:

1
2
3
4
$@ = world.out
$< = hello.o
$^ = hello.o main.o
cc -o world.out hello.o main.o

参考源码

可以从GitHub下载源码。

小结

使用变量可以让Makefile更加容易维护。

查看官方手册:

使用模式规则

前面我们讲了使用隐式规则可以让make在必要时自动创建.o文件的规则,但make的隐式规则的命令是固定的,对于xyz.o: xyz.c,它实际上是:

1
$(CC) $(CFLAGS) -c -o $@ $<

能修改的只有变量$(CC)$(CFLAGS)。如果要执行多条命令,使用隐式规则就不行了。

这时,我们可以自定义模式规则(Pattern Rules),它允许make匹配模式规则,如果匹配上了,就自动创建一条模式规则。

我们修改上一节的Makefile如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
TARGET = world.out

$(TARGET): $(OBJS)
cc -o $(TARGET) $(OBJS)

# 模式匹配规则:当make需要目标 xyz.o 时,自动生成一条 xyz.o: xyz.c 规则:
%.o: %.c
@echo 'compiling $<...'
cc -c -o $@ $<

clean:
rm -f *.o $(TARGET)

make执行world.out: hello.o main.o时,发现没有hello.o文件,于是需要查找以hello.o为目标的规则,结果匹配到模式规则%.o: %.c,于是make自动根据模式规则为我们动态创建了如下规则:

1
2
3
hello.o: hello.c
@echo 'compiling $<...'
cc -c -o $@ $<

查找main.o也是类似的匹配过程,于是我们执行make,就可以用模式规则完成编译:

1
2
3
4
5
6
$ make
compiling hello.c...
cc -c -o hello.o hello.c
compiling main.c...
cc -c -o main.o main.c
cc -o world.out hello.o main.o

模式规则的命令完全由我们自己定义,因此,它比隐式规则更灵活。

但是,模式规则仍然没有解决修改hello.h头文件不会触发main.c重新编译的问题,这个依赖问题我们继续放到后面解决。

最后注意,模式规则是按需生成,如果我们在当前目录创建一个zzz.o文件,因为make并不会在执行过程中用到它,所以并不会自动生成zzz.o: zzz.c这个规则。

参考源码

可以从GitHub下载源码。

小结

使用模式规则可以灵活地按需动态创建规则,它比隐式规则更灵活。

查看官方手册:



前面我们讲了隐式规则和模式规则,这两种规则都可以解决自动把.c文件编译成.o文件,但都无法解决.c文件依赖.h文件的问题。

因为一个.c文件依赖哪个.h文件必须要分析文件内容才能确定,没有一个简单的文件名映射规则。

但是,要识别出.c文件的头文件依赖,可以用GCC提供的-MM参数:

1
2
$ cc -MM main.c
main.o: main.c hello.h

上述输出告诉我们,编译main.o依赖main.chello.h两个文件。

因此,我们可以利用GCC的这个功能,对每个.c文件都生成一个依赖项,通常我们把它保存到.d文件中,再用include引入到Makefile,就相当于自动化完成了每个.c文件的精准依赖。

我们改写上一节的Makefile如下:

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
# 列出所有 .c 文件:
SRCS = $(wildcard *.c)

# 根据SRCS生成 .o 文件列表:
OBJS = $(SRCS:.c=.o)

# 根据SRCS生成 .d 文件列表:
DEPS = $(SRCS:.c=.d)

TARGET = world.out

# 默认目标:
$(TARGET): $(OBJS)
$(CC) -o $@ $^

# xyz.d 的规则由 xyz.c 生成:
%.d: %.c
rm -f $@; \
$(CC) -MM $< >$@.tmp; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@; \
rm -f $@.tmp

# 模式规则:
%.o: %.c
$(CC) -c -o $@ $<

clean:
rm -rf *.o *.d $(TARGET)

# 引入所有 .d 文件:
include $(DEPS)

变量$(SRCS)通过扫描目录可以确定为hello.c main.c,因此,变量$(OBJS)赋值为hello.o main.o,变量$(DEPS)赋值为hello.d main.d

通过include $(DEPS)我们引入hello.dmain.d文件,但是这两个文件一开始并不存在,不过,make通过模式规则匹配到%.d: %.c,这就给了我们一个机会,在这个模式规则内部,用cc -MM命令外加sed.d文件创建出来。

运行make,首次输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ make
Makefile:31: hello.d: No such file or directory
Makefile:31: main.d: No such file or directory
rm -f main.d; \
cc -MM main.c >main.d.tmp; \
sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.tmp > main.d; \
rm -f main.d.tmp
rm -f hello.d; \
cc -MM hello.c >hello.d.tmp; \
sed 's,\(hello\)\.o[ :]*,\1.o hello.d : ,g' < hello.d.tmp > hello.d; \
rm -f hello.d.tmp
cc -c -o hello.o hello.c
cc -c -o main.o main.c
cc -o world.out hello.o main.o

make会提示找不到hello.dmain.d,不过随后自动创建出hello.dmain.dhello.d内容如下:

1
hello.o hello.d : hello.c

上述规则有两个目标文件,实际上相当于如下两条规则:

1
2
hello.o : hello.c
hello.d : hello.c

main.d内容如下:

1
main.o main.d : main.c hello.h

因此,main.o依赖于main.chello.h,这个依赖关系就和我们手动指定的一致。

改动hello.h,再次运行make,可以触发main.c的编译:

1
2
3
4
5
6
7
$ make
rm -f main.d; \
cc -MM main.c >main.d.tmp; \
sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.tmp > main.d; \
rm -f main.d.tmp
cc -c -o main.o main.c
cc -o world.out hello.o main.o

在实际项目中,对每个.c文件都可以生成一个对应的.d文件表示依赖关系,再通过include引入到Makefile,同时又能让make自动更新.d文件,有点蛋生鸡和鸡生蛋的关系,不过,这种机制能正常工作,除了.d文件不存在时会打印错误,有强迫症的同学肯定感觉不满意,这个问题我们后面解决。

参考源码

可以从GitHub下载源码。

小结

利用GCC生成.d文件,再用include引入Makefile,可解决一个.c文件应该如何正确触发编译的问题。

查看官方手册:

完善Makefile

上一节我们解决了自动生成依赖的问题,这一节我们对项目目录进行整理,把所有源码放入src目录,所有编译生成的文件放入build目录:

1
2
3
4
5
6
7
<project>
├── Makefile
├── build
└── src
├── hello.c
├── hello.h
└── main.c

整理Makefile,内容如下:

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
39
40
41
42
43
SRC_DIR = ./src
BUILD_DIR = ./build
TARGET = $(BUILD_DIR)/world.out

CC = cc
CFLAGS = -Wall

# ./src/*.c
SRCS = $(shell find $(SRC_DIR) -name '*.c')
# ./src/*.c => ./build/*.o
OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
# ./src/*.c => ./build/*.d
DEPS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.d,$(SRCS))

# 默认目标:
all: $(TARGET)

# build/xyz.d 的规则由 src/xyz.c 生成:
$(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
@mkdir -p $(dir $@); \
rm -f $@; \
$(CC) -MM $< >$@.tmp; \
sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.tmp > $@; \
rm -f $@.tmp

# build/xyz.o 的规则由 src/xyz.c 生成:
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c -o $@ $<

# 链接:
$(TARGET): $(OBJS)
@echo "buiding $@..."
@mkdir -p $(dir $@)
$(CC) -o $(TARGET) $(OBJS)

# 清理 build 目录:
clean:
@echo "clean..."
rm -rf $(BUILD_DIR)

# 引入所有 .d 文件:
-include $(DEPS)

这个Makefile定义了源码目录SRC_DIR、生成目录BUILD_DIR,以及其他变量,同时用-include消除了.d文件不存在的错误。执行make,输出如下:

1
2
3
4
5
$ make
cc -Wall -c -o build/hello.o src/hello.c
cc -Wall -c -o build/main.o src/main.c
buiding build/world.out...
cc -o ./build/world.out ./build/hello.o ./build/main.o

可以说基本满足编译需求,收工!

参考源码

可以从GitHub下载源码。

小结

除了基础的用法外,Makefile还支持条件判断,环境变量,嵌套执行,变量展开等各种功能,需要用到时可以查询官方手册



留言與分享

oracelDB基础

分類 database, oracleDB

OracleDB 笔记整理

1. SQL 分类

分类 说明 关键字
DML (Data Manipulation Language) 数据操作语言 SELECT, INSERT, UPDATE, DELETE, MERGE
DDL (Data Definition Language) 数据定义语言 CREATE, ALTER, DROP, RENAME, TRUNCATE, COMMENT
DCL (Data Control Language) 数据控制语言 GRANT, REVOKE
事务 (Transaction) 事务控制 COMMIT, ROLLBACK, SAVEPOINT

2. OracleDB 构成

2.1 基本构成

OracleDB 由 Oracle实例(instance)Database 构成:

  • 实例(Instance) 由:
    • SGA(System Global Area):内存区域
    • Background Process:后台进程
  • Database 由:
    • 控制文件(制御ファイル)
    • REDO 文件(REDO ログ)
    • 数据文件(データファイル)

2.2 进程视角

除了实例中的 Background Process,还包括:

  • 用户进程
  • 服务器进程
  • 监听进程

2.3 工具列表

安装和升级相关工具

工具名 用途
Oracle Universal Installer (OUI) 安装 Oracle 软件
Oracle Database Configuration Assistant (DBCA) 创建数据库
Oracle Database Upgrade Assistant (DBUA) 升级现有数据库到新版本

网络相关工具

工具名 用途
Oracle Net Manager (netmgr) 配置 Oracle 网络
Oracle Net Configuration Assistant (netca) 配置 Oracle 网络

实例和数据库管理工具

工具名 用途
Oracle Enterprise Manager (EM) 管理 Oracle DB
SQL*Plus SQL 命令行工具
SQL Developer 图形化数据库管理工具
Recovery Manager (RMAN) 数据库备份、恢复、复原
Oracle Secure Backup 备份管理
Data Pump 数据库间高速数据传输
SQL*Loader 外部文件数据批量导入

3. 安装

3.1 OUI 功能

  • 显示已安装的 Oracle 软件
  • 安装新软件
  • 删除软件
  • 查看在线帮助
  • 检查安装需求

3.2 系统要求

  • 内存:1GB
  • SWAP:1.5GB
  • 硬盘空间
    • 最小 1GB
    • 一般需要 6.1GB

3.3 创建用户和组

  • 软件所有者:Oracle 用户
  • Oracle Inventory Group:用于管理 Oracle 软件
  • DB 管理组
    • OSDBA:数据库管理员组
    • OSOPER:受限制的数据库管理员组

3.4 环境变量

变量名 说明
ORACLE_BASE Oracle 主目录
ORACLE_HOME Oracle 软件安装位置
ORACLE_SID 系统标识(实例名)
LD_LIBRARY_PATH 共享库路径(如 $ORACLE_HOME/lib

3.5 安装脚本

脚本名 用途
orainstRoot.sh 生成 inventory pointer 文件
root.sh 生成 oratab 并设置环境变量 (oraenvcoraenv),指定 dbstartdbshut 脚本

3.6 创建数据库(DBCA)

3.6.1 指定 Global DB 名

格式:database_name.domain_name

3.6.2 Enterprise Manager 选项

  • Database Express:单数据库管理
  • Cloud Control:集中管理多个数据库(需预先安装 Cloud Control)

3.6.3 存储类型

类型 说明
文件系统 使用操作系统文件
ASM (Automatic Storage Management) 文件存储在 ASM 磁盘组,需额外实例

3.6.4 数据库文件位置

  • 使用模板的文件位置
  • 所有数据库文件共享文件夹
  • Oracle Managed Files:由 Oracle 直接管理文件

3.6.5 模板

模板包含以下信息:

  • 数据库选项
  • 初始化参数
  • 存储属性(数据文件、表空间、控制文件、REDO 日志属性)
模板分类
模板类型 说明
通用事务处理(默认) 适用于 OLTP 场景
数据仓库 (Data Warehouse) 适用于复杂查询和大数据处理
自定义模板 用户自定义配置
模板形式
形式 说明
Sheet Template 包含现有数据库结构和物理文件
Non-Sheet Template 仅包含数据库特性

4. EM Express

4.1 功能

  • 提供数据库管理功能(不包括启动/停止数据库
  • 如需启动/停止数据库,需使用 Oracle Enterprise Manager Cloud Control

4.2 手动配置 EM Express

  1. 启动监听进程
  2. 初始化 DISPATCHERS 参数(设置 PROTOCOL=TCP):
    1
    dispatchers="(PROTOCOL=TCP)(SERVICE=<sid>XDB)"
  3. 设置端口(需 SYSDBA 权限):
    1
    EXEC DBMS_XDB_CONFIG.setHTTPSPORT(5500);

4.3 使用 EM Express

4.3.1 查询 EM Express 端口

1
SELECT DBMS_XDB_CONFIG.getHTTPSPort FROM DUAL;

4.3.2 权限分配

权限 说明
EM_EXPRESS_BASIC 只读模式
EM_EXPRESS_ALL 完全权限

4.4 连接数据库

4.4.1 SQL*Plus 连接方式

  1. 运行 oraenv 设置环境变量:
    1
    . oraenv
  2. 启动 SQL*Plus(/nolog 表示不登录数据库):
    1
    sqlplus /nolog
  3. 连接数据库:
    1
    CONNECT <用户名>/<密码> [AS SYSDBA | AS SYSOPER]
  4. 其他功能:
    • 执行 SQL 脚本:
      1
      @<sql文件名>
    • 执行操作系统命令:
      1
      HOST <命令>  # 例如:HOST ls

4.4.2 SQL Developer 连接方式

  1. 运行 oraenv 设置环境变量:
    1
    . oraenv
  2. 启动 SQL Developer:
    1
    2
    cd $ORACLE_HOME/sqldeveloper
    sh sqldeveloper.sh
  3. 功能:
    • 普通模式:查看、创建、编辑、删除表、视图等对象
    • DBA Navigator:连接 DBA 用户后可启动/停止数据库

5. Oracle 网络构成

5.1 Oracle Net 概述

  • 功能:提供网络服务
  • 安装方式:随 Oracle 数据库软件或客户端一同安装
  • 通信模式
    • 客户端-服务端模式:两端均需安装 Oracle Net
    • 客户端-Web 服务器-DB 服务器模式:客户端和 Web 服务器需安装 Oracle Net

5.2 通信条件

角色 要求
DB 服务端 1. 网络服务器在线
2. 已安装 Oracle DB
3. 支持 TCP/IP 协议
4. 监听进程已启动
客户端 1. 网络服务器在线
2. 已安装 Oracle 客户端
3. 支持 TCP/IP 协议

5.3 监听进程

  • 自动安装:通过 OUI 初始化 DB 时,NetCA 会默认安装监听进程

  • 作用:处理客户端连接请求(连接建立后不再参与通信)

  • 关键命令

    命令 功能
    lsnrctl start <监听进程名> 启动监听进程
    lsnrctl stop <监听进程名> 停止监听进程
    lsnrctl status <监听进程名> 查看状态
    lsnrctl services <监听进程名> 查看支持的服务
    LSNRCTL> set current_listener <名称> 切换监听进程(需交互模式)

5.4 客户端构成

  • 数据库连接示例
    1
    2
    3
    CONNECT hr@(DESCRIPTION = 
    (ADDRESS = (PROTOCOL = TCP)(HOST = proj1-sv)(PORT = 1521))
    (CONNECT_DATA = (SERVICE_NAME = sales.edifist.com)))

5.5 命名方法

类型 存储位置 说明
本地命名 客户端 tnsnames.ora 网络服务名映射存储在本地文件
LDAP 基准 LDAP 服务器 集中管理服务名映射
简易连接命名 直接使用 TCP/IP 连接字符串(如 connect scott@host名/服务名
外部命名 第三方命名服务(如 NIS) 依赖非 Oracle 服务

本地命名配置方法

1
netca  # 启动图形化配置工具

简易连接示例

1
2
sqlplus /nolog
SQL> CONNECT hr/hr@oracle_sv/orcl.edifist.com

6. 数据库服务器架构概要

6.1 核心组件

  • Oracle 实例:内存结构(SGA) + 后台进程
  • Oracle 数据库:物理文件集合

6.2 SGA (System Global Area) 构成

组件 功能
数据库缓存 存储数据块(Buffer Cache)
REDO 日志 Buffer 记录数据变更历史
共享池 缓存 SQL/PLSQL 解析结果、执行计划、数据字典
Large Pool 可选,用于共享服务器模式、并行查询、RMAN 备份等
Java Pool 支持 Java 虚拟机(JVM)
Stream Pool 支持 Oracle Stream 数据复制

6.3 关键后台进程

进程 名称 功能
SMON System Monitor 实例恢复(如崩溃后自动修复)
PMON Process Monitor 清理异常终止的用户进程资源
DBWn Database Writer 将脏缓冲区写入数据文件
CKPT Checkpoint 触发 DBWn 写入,更新控制文件(用于灾难恢复)
LGWR Log Writer 将 REDO 日志缓冲区写入磁盘
ARCn Archiver 归档 REDO 日志
MMON Manageability Monitor 执行 AWR(自动工作负载仓库)相关任务

6.4 用户与服务器进程

  • 用户进程:运行应用程序(如 SQL*Plus)
  • 服务器进程:处理用户提交的 SQL 查询

6.5 PGA (Program Global Area)

  • 特点:非共享内存,仅限单个进程访问
  • 用途:存储 SQL 执行时的排序区、会话信息等

7. 实例启停

7.1 启动方法

  • SQL*Plus(STARTUP 命令)
  • Windows 服务管理器
  • SQL Developer
  • Enterprise Manager Cloud Control

7.2 启动状态迁移

状态 描述
NOMOUNT 读取参数文件,分配 SGA,启动后台进程(控制文件未打开)
MOUNT 打开控制文件(获知数据文件/日志文件路径,但未打开文件)
OPEN 打开所有数据文件和 REDO 日志,数据库可用

7.3 权限要求

权限 能力
SYSDBA 完全控制(包括启停实例、用户授权)
SYSOPER 仅限启停实例(无权管理用户对象)

连接示例

1
2
3
4
CONNECT 用户名/密码 AS {SYSOPER | SYSDBA}
sqlplus /nolog
CONNECT sys AS sysdba
STARTUP

7.4 停止流程

  1. 关闭数据库(OPEN → CLOSED)
    • 执行 Checkpoint,写入数据文件和 REDO 日志
    • 关闭数据文件和 REDO 日志(控制文件仍打开)
  2. 卸载数据库(CLOSED → DISMOUNT)
    • 实例与数据库分离
  3. 停止实例(DISMOUNT → SHUTDOWN)
    • 终止后台进程,释放 SGA 内存

7.5 停止模式对比

行为 NORMAL TRANSACTIONAL IMMEDIATE ABORT
接受新连接 × × × ×
等待当前会话结束 × ×
等待当前事务结束 ×
执行 Checkpoint 后关闭 ×

7.6 初始化参数文件

类型 名称 特点
静态 PFILE(文本文件) 手动编辑,需重启生效
动态 SPFILE(二进制文件) 支持在线修改(ALTER SYSTEM),优先使用

留言與分享

vim教程

分類 devops, vim

Vim 编辑器使用笔记整理

1. 退出 Vim

命令行模式退出方式

命令 说明
:wq 保存并退出
:q! 强制退出,不保存
:q 退出(未修改时)
:wq! 强制保存并退出
:w <文件路径> 另存为指定文件
:saveas 文件路径 另存为指定文件
:x 保存并退出(类似:wq)

普通模式退出方式

  • 输入 Shift+zz 即可保存退出

2. 删除文本

普通模式删除命令

命令 说明
x 删除游标所在字符
X 删除游标前一个字符
Delete x
dd 删除整行
dw 删除一个单词(不适用中文)
d$D 删除至行尾
d^ 删除至行首
dG 删除到文档结尾
d1G 删除至文档开头
:%d 清空整个文档内容(删除所有行)
:1,$d :%d,删除从第一行到最后一行
ggdG 普通模式下清空文档的快捷方式(先跳转到首行,然后删除到末尾)

注意:这些命令会立即生效且不可撤销,使用前请确保已保存重要内容

数字前缀用法

  • 2dd 表示一次删除2行
  • 3dw 表示删除3个单词

3. 重复执行命令

  • 普通模式下 . (小数点)表示重复上一次命令
  • 数字前缀:10x 删除10个连续字符

4. 游标跳转

行间跳转

命令 说明
nG 跳转到第n行(需先:set nu显示行号)
gg 跳转到第一行
G 跳转到最后一行

行内跳转

命令 说明
w 到下一个单词开头
e 到当前单词结尾
b 到前一个单词开头
ge 到前一个单词结尾
0^ 到行头
$ 到行尾
f<字母> 向后搜索字母并跳转
F<字母> 向前搜索字母并跳转
t<字母> 向后搜索字母并跳转到匹配前
T<字母> 向前搜索字母并跳转到匹配后

5. 复制粘贴和剪切

复制命令(yank)

命令 说明
yy 复制整行(3yy复制3行)
y^ 复制至行首
y$ 复制至行尾
yw 复制一个单词
yG 复制至文本末
y1G 复制至文本开头

粘贴命令

命令 说明
p 粘贴至光标后
P 粘贴至光标前

6. 替换和撤销

命令 说明
r+<字母> 替换游标所在字母
R 连续替换(按Esc结束)
cc 替换整行
cw 替换一个单词
C 替换至行末
~ 反转字母大小写
u 撤销操作
U 撤销当前行所有修改
Ctrl+r 重做(redo)

7. 缩进调整

缩进命令

命令 说明
>> 整行向右缩进
<< 整行向左回退
:set shiftwidth=n 设置缩进字符数

文本位置调整

命令 说明
:ce 本行内容居中
:ri 本行文本靠右
:le 本行内容靠左

8. 查找功能

基本查找

命令 说明
/字符串 向下查找
?字符串 向上查找
n 继续查找
N 反向查找

高级查找

命令 说明
* 向后查找当前单词
# 向前查找当前单词
g* 向后查找部分匹配单词
g# 向前查找部分匹配单词

9. 多文件编辑

多文件操作

命令 说明
:n 编辑下一个文件
:N 编辑上一个文件
:e 文件名 打开新文件
:e# 回到前一个文件
:ls 列出编辑过的文档
:b 文件名/编号 切换到指定文件
:bd 文件名/编号 从列表删除文件
:f 显示当前文件名

文件恢复

1
2
vim -r 文件名
:ewcover 文件名

10. 可视模式

进入可视模式

命令 说明
v 字符选择模式
V 行选择模式
Ctrl+v 区域选择模式

可视模式操作

  • d 删除选中区域
  • y 复制选中区域

11. 视窗操作

窗口分割

命令 说明
:new 新建窗口
:sp 文件名 水平分割窗口
:vsp 文件名 垂直分割窗口
Ctrl+w s 水平分割当前窗口
Ctrl+w v 垂直分割当前窗口

窗口切换

命令 说明
Ctrl+w j/k/h/l 向下/上/左/右切换窗口
Ctrl+w q 关闭当前窗口
Ctrl+w o 只保留当前窗口

12. 其他功能

文档加密

1
vim -x 文件名

执行外部命令

1
2
:!命令
:w 文件名 # 另存为

帮助系统

1
2
3
:F1        # 打开帮助
:h 主题 # 查看特定帮助
:ver # 显示版本

功能设定

1
2
3
4
:set nu    # 显示行号
:set ai # 自动缩进
:set aw # 自动保存
:set cin # C语言风格缩进

提示:所有设置可通过修改 ~/.vimrc 文件永久保存

留言與分享

GitHub Actions 是 GitHub 的持续集成服务,于2018年10月推出

这些天,我一直在试用,觉得它非常强大,有创意,比 Travis CI 玩法更多。

本文是一个简单教程,演示如何使用 GitHub Actions 自动发布一个 React 应用到 GitHub Pages

一、GitHub Actions 是什么?

大家知道,持续集成由很多操作组成,比如抓取代码、运行测试、登录远程服务器,发布到第三方服务等等。GitHub 把这些操作就称为 actions。

很多操作在不同项目里面是类似的,完全可以共享。GitHub 注意到了这一点,想出了一个很妙的点子,允许开发者把每个操作写成独立的脚本文件,存放到代码仓库,使得其他开发者可以引用。

如果你需要某个 action,不必自己写复杂的脚本,直接引用他人写好的 action 即可,整个持续集成过程,就变成了一个 actions 的组合。这就是 GitHub Actions 最特别的地方。

GitHub 做了一个官方市场,可以搜索到他人提交的 actions。另外,还有一个 awesome actions 的仓库,也可以找到不少 action。

上面说了,每个 action 就是一个独立脚本,因此可以做成代码仓库,使用userName/repoName的语法引用 action。比如,actions/setup-node就表示github.com/actions/setup-node这个仓库,它代表一个 action,作用是安装 Node.js。事实上,GitHub 官方的 actions 都放在 github.com/actions 里面。

既然 actions 是代码仓库,当然就有版本的概念,用户可以引用某个具体版本的 action。下面都是合法的 action 引用,用的就是 Git 的指针概念,详见官方文档

1
2
3
4

actions/setup-node@74bc508 # 指向一个 commit
actions/[email protected] # 指向一个标签
actions/setup-node@master # 指向一个分支

二、基本概念

GitHub Actions 有一些自己的术语。

(1)workflow (工作流程):持续集成一次运行的过程,就是一个 workflow。

(2)job (任务):一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务。

(3)step(步骤):每个 job 由多个 step 构成,一步步完成。

(4)action (动作):每个 step 可以依次执行一个或多个命令(action)。

三、workflow 文件

GitHub Actions 的配置文件叫做 workflow 文件,存放在代码仓库的.github/workflows目录。

workflow 文件采用 YAML 格式,文件名可以任意取,但是后缀名统一为.yml,比如foo.yml。一个库可以有多个 workflow 文件。GitHub 只要发现.github/workflows目录里面有.yml文件,就会自动运行该文件。

workflow 文件的配置字段非常多,详见官方文档。下面是一些基本字段。

(1)name

name字段是 workflow 的名称。如果省略该字段,默认为当前 workflow 的文件名。

1
2

name: GitHub Actions Demo

(2)on

on字段指定触发 workflow 的条件,通常是某些事件。

1
2

on: push

上面代码指定,push事件触发 workflow。

on字段也可以是事件的数组。

1
2

on: [push, pull_request]

上面代码指定,push事件或pull_request事件都可以触发 workflow。

完整的事件列表,请查看官方文档。除了代码库事件,GitHub Actions 也支持外部事件触发,或者定时运行。

(3)on.<push|pull_request>.<tags|branches>

指定触发事件时,可以限定分支或标签。

1
2
3
4
5

on:
push:
branches:
- master

上面代码指定,只有master分支发生push事件时,才会触发 workflow。

(4)jobs.<job_id>.name

workflow 文件的主体是jobs字段,表示要执行的一项或多项任务。

jobs字段里面,需要写出每一项任务的job_id,具体名称自定义。job_id里面的name字段是任务的说明。

1
2
3
4
5
6

jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job

上面代码的jobs字段包含两项任务,job_id分别是my_first_jobmy_second_job

(5)jobs.<job_id>.needs

needs字段指定当前任务的依赖关系,即运行顺序。

1
2
3
4
5
6
7

jobs:
job1:
job2:
needs: job1
job3:
needs: [job1, job2]

上面代码中,job1必须先于job2完成,而job3等待job1job2的完成才能运行。因此,这个 workflow 的运行顺序依次为:job1job2job3

(6)jobs.<job_id>.runs-on

runs-on字段指定运行所需要的虚拟机环境。它是必填字段。目前可用的虚拟机如下。

  • ubuntu-latestubuntu-18.04ubuntu-16.04
  • windows-latestwindows-2019windows-2016
  • macOS-latestmacOS-10.14

下面代码指定虚拟机环境为ubuntu-18.04

1
2

runs-on: ubuntu-18.04

(7)jobs.<job_id>.steps

steps字段指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定以下三个字段。

  • jobs.<job_id>.steps.name:步骤名称。
  • jobs.<job_id>.steps.run:该步骤运行的命令或者 action。
  • jobs.<job_id>.steps.env:该步骤所需的环境变量。

下面是一个完整的 workflow 文件的范例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

name: Greeting from Mona
on: push

jobs:
my-job:
name: My Job
runs-on: ubuntu-latest
steps:
- name: Print a greeting
env:
MY_VAR: Hi there! My name is
FIRST_NAME: Mona
MIDDLE_NAME: The
LAST_NAME: Octocat
run: |
echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.

上面代码中,steps字段只包括一个步骤。该步骤先注入四个环境变量,然后执行一条 Bash 命令。

四、实例:React 项目发布到 GitHub Pages

下面是一个实例,通过 GitHub Actions 构建一个 React 项目,并发布到 GitHub Pages。最终代码都在这个仓库里面,发布后的参考网址为ruanyf.github.io/github-actions-demo

第一步,GitHub Actions 目前还处在测试阶段,需要到这个网址申请测试资格。申请以后,可能需要几天才能通过。据说,2019年11月就会放开。

获得资格后,仓库顶部的菜单会出现Actions一项。

第二步,这个示例需要将构建成果发到 GitHub 仓库,因此需要 GitHub 密钥。按照官方文档,生成一个密钥。然后,将这个密钥储存到当前仓库的Settings/Secrets里面。

上图是储存秘密的环境变量的地方。环境变量的名字可以随便起,这里用的是ACCESS_TOKEN。如果你不用这个名字,后面脚本里的变量名也要跟着改。

第三步,本地计算机使用create-react-app,生成一个标准的 React 应用。

1
2
3

$ npx create-react-app github-actions-demo
$ cd github-actions-demo

然后,打开package.json文件,加一个homepage字段,表示该应用发布后的根目录(参见官方文档)。

1
2

"homepage": "https://[username].github.io/github-actions-demo",

上面代码中,将[username]替换成你的 GitHub 用户名,参见范例

第四步,在这个仓库的.github/workflows目录,生成一个 workflow 文件,名字可以随便取,这个示例是ci.yml

我们选用一个别人已经写好的 action:JamesIves/github-pages-deploy-action,它提供了 workflow 的范例文件,直接拷贝过来就行了(查看源码)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

name: GitHub Actions Build and Deploy Demo
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master

- name: Build and Deploy
uses: JamesIves/github-pages-deploy-action@master
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages
FOLDER: build
BUILD_SCRIPT: npm install && npm run build

上面这个 workflow 文件的要点如下。

  1. 整个流程在master分支发生push事件时触发。
  2. 只有一个job,运行在虚拟机环境ubuntu-latest
  3. 第一步是获取源码,使用的 action 是actions/checkout
  4. 第二步是构建和部署,使用的 action 是JamesIves/github-pages-deploy-action
  5. 第二步需要四个环境变量,分别为 GitHub 密钥、发布分支、构建成果所在目录、构建脚本。其中,只有 GitHub 密钥是秘密变量,需要写在双括号里面,其他三个都可以直接写在文件里。

第五步,保存上面的文件后,将整个仓库推送到 GitHub。

GitHub 发现了 workflow 文件以后,就会自动运行。你可以在网站上实时查看运行日志,日志默认保存30天。

等到 workflow 运行结束,访问 GitHub Page,会看到构建成果已经发上网了。

以后,每次修改后推送源码,GitHub Actions 都会自动运行,将构建产物发布到网页。

五、参考链接

(完)

留言與分享

VPC

分類 后端, AWS
  1. Security Group(SG):

    1. 所有设定默认是拒绝,只可以设定许可规则,拒否ルールは指定できません。
    2. 特征是不单可以指定CIDR等IP,还可以指定SG
    3. 安全组是有状态的 — 如果您从实例发送一个请求,则无论入站安全组规则如何,都将允许该请求的响应流量流入。如果是为响应已允许的入站流量,则该响应可以出站,此时可忽略出站规则。セキュリティグループはステートフルです。
  2. Access Control List(ACL):

    1. VPC 自动带有可修改的默认网络 ACL。默认情况下,它允许所有入站和出站 IPv4 流量以及 IPv6 流量 (如果适用)。
    2. 可以创建自定义网络 ACL 并将其与子网相关联。默认情况下,每个自定义网络 ACL 都拒绝所有入站和出站流量,直至您添加规则。
    3. VPC 中的每个子网都必须与一个网络 ACL 相关联。如果您没有明确地将子网与网络 ACL 相关联,则子网将自动与默认网络 ACL 关联。
    4. 可以将网络 ACL 与多个子网关联。但是,一个子网一次只能与一个网络 ACL 关联。当您将一个网络 ACL 与一个子网关联时,将删除之前的关联。
    5. 网络 ACL 包含规则的编号列表。我们按顺序评估(从编号最小的规则开始)规则,以判断是否允许流量进入或离开任何与网络 ACL 关联的子网。您可以使用的最高规则编号为 32766。我们建议您开始先以增量方式创建规则(例如,以 10 或 100 的增量增加),这样您可以在稍后需要时插入新的规则。
    6. 网络 ACL 有单独的入站和出站规则,每项规则都或是允许或是拒绝数据流。
    7. 网络 ACL 没有任何状态,这意味着对允许入站数据流的响应会随着出站数据流规则的变化而改变(反之亦然)。(ネットワーク ACL はステートレスです。許可されているインバウンドトラフィックに対する応答は、アウトバウンドトラフィックのルールに従います(その逆の場合も同様です)。)
    8. ルール番号。ルールは、最も低い番号のルールから評価されます。ルールがトラフィックに一致すると、それと相反するより高い数値のルールの有無にかかわらず、すぐに適用されます。
    9. タイプ。トラフィックのタイプ(SSH など)。また、すべてのトラフィックまたはカスタム範囲を指定することもできます。

セキュリティグループはステートフルです。(レスポンスでも明示する必要なしで許可。)
ACLはステートレスです。(レスポンスでも明示する必要ある。)

留言與分享

  • 第 1 頁 共 1 頁
作者的圖片

Kein Chan

這是獨立全棧工程師Kein Chan的技術博客
分享一些技術教程,命令備忘(cheat-sheet)等


全棧工程師
資深技術顧問
數據科學家
Hit廣島觀光大使


Tokyo/Macau