bazel

Description

Bazel 是一个开源的构建、测试工具,类似于 Make、Maven和Gradle。

Conception

Workspace

workspace 是一个目录,包含需要构建的源文件;该目录应该包含一个名为 WORKSPACE / WORKSPACE.bzl 的文本文件(同时则WORKSPACE.bzl 优先,可能为空或者包含外部依赖)。

Repositories

代码组织在 repositories 内,包含 WORKSPACE 文件的文件加被称为主 repositories,亦称为 @ 。其他的外部 repositories 在 WORKSPACE 文件使用 workspace 规则中定义。

Packages

一个 repository 中,组织代码的主要单元是 package。一个 package 是一个相关文件及其之间相互依赖关系的集合。一个 package 定义为包含 BUILD 或者 BUILD.bazel 的文件夹,该文件位于 resposity 的顶级目录下。一个 package 包含目录下所有的文件及子文件夹下的文件,但不包括包含 BUILD 文件的文件夹下的文件。

1
2
3
4
5
src/my/app/BUILD
src/my/app/app.cc
src/my/app/data/input.txt
src/my/app/tests/BUILD
src/my/app/tests/test.cc

上面目录包含两个 package,my/appmy/app/testsmy/app/data 不是 package,只是 package my/app 下的一个文件夹。

Targets

package 是一个容器。package 内的元素称为 targets。大多数 targets 是两个主要类型(files、rules)中的一个。此外,还有另外一种很少用的类型的 target,package groups

文件又被分为源文件(source files)和生成文件(generated files),生成文件是根据特定规则由源文件生成。

第二种 target 是规则。一个规则指定一系列输入和输出文件之间的关系,包括必要的步骤来驱动从源文件到生成文件的转换。一个规则的输出总是生成文件(generated files)。规则的输入,可以是源文件,也可以是生成的文件。所以,一条规则的输出可能是另一个规则的输出,从而构造较长的规则链。

Package groups 是一系列的 packages,主要目的是用来限制特定规则的访问。package group 使用 package_group 方法定义,包含两个参数:包含的package列表和名称。

Labels

每个 targets 只属于一个package。target 的名称被称作它的 label,典型的 label 示例如下。

1
@myrepo//my/app/main:app_binary

指向相同 repository 的 label,repository 名称可以省略。所以,@myrepo 内部,label 通常如下。

1
//my/app/main:app_binary

每个 label 包含两部分,package 名称(my/app/main) 和一个 target 名称( app_binray)。每个 label 唯一标示一个 target。label 有时以其他样式展示,当省略冒号时,target 名称被认为和package name 的最后一个组件相同,比如,以下两个 label 含义相同。

1
2
//my/app
//my/app:app

label 以 // 开始,package 则从不以此开头,所以 my/app 是包含 target //my/app 的 package。

在 BUILD 文件内部,label 的 package name 部分可以省略,冒号也可以作为可选项省略。所以,在 package my/app 下的 BUILD 文件(比如,//my/app:BUILD)内,下面的 labels 含义是相同的。

1
2
3
4
//my/app:app
//my/app
:app
app

相似的,在 BUILD 文件内,属于 package 的文件,可以使用相对于 package 文件夹的相对路径来表示。

1
2
generate.cc
testdata/input.txt

但是,在其他 package 内或命令行,这些文件 target 必须使用完整的label来引用,比如 //my/app:generate.cc

@// 开头的 label 指向 main reposity,即使在外部库中依然可以使用。因此在外部reposity 中使用时, @//a/b/c//a/b/c 不同,前面的形式返回 main repository,后面的形式只在外部 reposity 中查找。

label 的词法规范

Target name

target-name 是 package 内 target 的 name。BUILD 文件中 rule 的 name 是其定义中 name 属性的值;文件的 name 是相对于包含 BUILD 文件夹路径的相对路径。不用使用 .. 在其他 package 中引用文件,使用 //packagename:filename 代替。文件的相对路径不可以有前置和后置的 //foofoo/ 是禁止的)和连续的 /foo//bar 也是禁止的),.../ 也是禁止的。

在文件名中使用 / 很常见,所以不建议在 rule 名称中使用 /

Package name

Package name 是包含 BUILD 文件的文件夹相对于顶层目录的相对路径,比如 my/app。package name 不应该包含 // ,或以 / 结尾。

Rules

rule 指定了输入和输出之间的关系,以及构建和输出的步骤。rules 可以是一个或多个不同类型的 classes(生成编译的可执行文件、测试程序及其它支持的输出类型)。

Rule 的 name 在以定义的 name 属性指定,虽然名称可以随意定义,但对于*_binary*_test 规则来说,rule 名称决定输出文件名称。

1
2
3
4
5
6
7
8
cc_binary(
name = "my_app",
srcs = ["my_app.cc"],
deps = [
"//absl/base",
"//absl/strings",
],
)

BUILD files

BUILD 文件使用命令式语言 starlark 处理。值得注意的是,starlark 程序不能随意执行 I/O 操作,这使得 BUILD 文件的解释过程是幂等的,只依赖一系列已知的输入,这保证了构建过程的可复制性。

通常,顺序很重要,变量必须在使用前定义。但是,大多数时候 BUILD 文件仅有一系列规则组成,他们的相对顺序不重要。

为了鼓励代码和数据分离,BUILD 文件不允许包含函数定义、for语句、if语句,他们应该定义在 bzl 文件中。此外,*args**kwargs 也不被允许,应该准确的列出所有的参数。

BUILD 文件应该仅适用 ASCII 字符编写。

加载扩展

bazel 扩展文件以 .bzl 结尾,使用 load 命令从扩展中加载一个符号。

1
load("//foo/bar:file.bzl", "some_library")

上面命令会加载文件 foo/var/file.bzl 并添加 some_library 到环境变量,该命令可以加载 rules、functions、常量。可以通过添加额外的参数,来加载多个标识。参数必须是字符串常量,不能使用变量。load 命令必须在文件的顶层,比如,不能放在 function 里面。可以重命名标识。

1
2
load("//foo/bar:file.bzl", some_library_alias = "some_library")
load(":my_rules.bzl", "some_rule", nice_alias = "some_other_rule")

.bzl 文件中以 _ 开头的标识,不能被导出,也即不能再其他文件中被load。

Types of build rule

主要的构建规则成簇出现,根据语言进行分组。

  • *_binary
  • *_test
  • *_library

Dependencies

基于关系的依赖在 targets 之间组成 DAG 图。

依赖类型

srcs 依赖

被规则直接使用的文件。

deps 依赖

规则指向独立编译的模块,提供头文件、标识、库、数据等。

data 依赖

构造系统只在隔离的文件夹运行测试,只有在配置的 data 列表中配置的文件可用。因此,如果一个可执行文件、库、测试程序的执行需要一些文件,需要在data字段中指定,比如。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# I need a config file from a directory named env:
java_binary(
name = "setenv",
...
data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
name = "regtest",
srcs = ["regtest.sh"],
data = [
"//data:file1.txt",
"//data:file2.txt",
...
],
)

这些文件,可以使用相对路径 /path/to/data/file 访问。测试程序中,可以通过拼接测试源目录和相对workspace的路径进行拼接来访问,比如 ${TEST_SRCDIR}/workspace/path/to/data/file

protobuf - java

maven

打包插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>

IDEA

引用飘红

把 proto 所在文件夹标记为 source root。

对比

StringValue & string

类似的还有 int32 & Int32Value 等。

StringValuestring 相比:

  • StringValue 可以为 null,在部分语言中,string 类型数据不能为 null,那么对于 string 类型,无法区分 "" 是代表 null 还是空字符串
  • gRPC 调用过程的的参数和返回值不能为基本类型,也就是不能为 string、int32 这种

理财 - 股票

A 股

首先说下A股,即人民币普通股票,由中国境内公司注册的公司发行(B股:中国证券交易所的外资股,H股:中国境内注册,香港上市的股票),T+1割接,10%涨跌幅,一手为固定的100股,正常交易时间为交易日的 9:30~11:30、13:00-15:00。

A股股票又区分板块,不同板块的开户条件不同。

板块 股票代码开头 开户条件 备注
主板 60(上交所)、000(深交所) 成熟企业
中小板 002 成长性强企业/商业龙头
创业板 / 二板 300 20交易日日均资产>=10w;首次证券交易满2年 成长性高的创新企业
科创板 688 20交易日日均资产>=50w;首次证券交易满2年 高科技企业,20%涨跌幅
新三板 400 / 430 / 830 10交易日日均资产>=100w;首次证券交易满2年 股份转让

go - strange

三目元算符

go 没有三目运算符,可以通过定义方法,实现类似的功能。这里要注意参数拷贝传递的问题,避免造成性能损失和逻辑错误。首先,不使用指针。

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
package main

import "reflect"

type Person struct {
Age int
Name string
}

func IF(condition bool, tv, fv interface{}) interface{} {
if condition {
return tv
} else {
return fv
}
}

func main() {
a := 1
b := 2
c := IF(a > b, a, b)
println(reflect.TypeOf(c).Name())
println(c.(int))

pa := Person{Age: 18, Name: "wii"}
pb := Person{Age: 16, Name: "bvz"}
pc := IF(pa.Age > pb.Age, pa, pb)
println(&pa, &pb, &pc)

pd := IF(pa.Age > pb.Age, &pa, &pb).(*Person)
println(&pa, &pb, pd)
}

输出。

1
2
3
4
int
2
0xc00006ef30 0xc00006ef18 0xc00006eef8
0xc00006ef30 0xc00006ef18 0xc00006ef30

从输出可以看出,pc := IF(pa.Age > pb.Age, pa, pb) 的返回值的内存地址,既不是 pa,也不是 pb。

interface{}

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

type Dt struct {
content string
}

func Ifc(ifc interface{}) {
println("Ifc, &ifc:", &ifc)
}

func IfcPtr(ifc interface{}) {
println("IfcPtr, ifc:", ifc.(*Dt))
}

func main() {
dt := Dt{"wii"}
println("main, &dt:", &dt)
Ifc(dt)
IfcPtr(&dt)
}

输出

1
2
3
main, &dt: 0xc000044758
Ifc, &ifc: 0xc000044748
IfcPtr, ifc: 0xc000044758

可以看出,interface{} 类型参数传递时,并不会把变量隐式转换为指针。

结构 & 成员函数

go 与其他语言定义结构(类)的方式不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义结构
type Data struct {
name string // 定义成员变量
}

// 定义成员函数
func (p *Data) Ptr() {
println("ptr: ", p.name)
}

func (p Data) Cp() {
println("cp: ", p.name)
}

// 使用
dt := Data{name: "wii"} // OR dt := Data{"wii"}
dt.Ptr()

接口

go 与其他语言定义接口的方式也不同。

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
44
45
46
47
48
49
50
package main

import "fmt"

// Bird 定义接口
type Bird interface {
Name() string
Fly() bool
Chirp() string
}

// Duck 定义接口实现
type Duck struct {
nm string
CanFly bool
Sound string
}

// NewDuck 创建 Duck; go 中的类型,没有构造函数的概念,这里定义一个函数,用户创建对象并初始化
func NewDuck() *Duck {
return &Duck{CanFly: false, Sound: "quack", nm: "Duck"}
}

// Name 定义接口函数实现
func (duck *Duck) Name() string {
return duck.nm
}

func (duck *Duck) Fly() bool {
return duck.CanFly
}

func (duck *Duck) Chirp() string {
return duck.Sound
}

// IF go 中没有三目运算符, 这个函数实现简单的三目运算
func IF(condition bool, tv interface{}, fv interface{}) interface{} {
if condition {
return tv
} else {
return fv
}
}

func main() {
var bird Bird = NewDuck()
fmt.Printf("%v\n", IF(bird.Fly(), bird.Name() + " is flying!", bird.Name() + " cannot fly!"))
println(bird.Name() + " is chirping:", bird.Chirp(), bird.Chirp(), "...")
}

可以看出,在接口是实现上,只要一个类定义了接口的所有方法,那么就可以把该对象的实例赋值给接口类型变量。

拷贝传递 & 指针传递

和 C++ 类似,go 也有拷贝传递和指针传递。首先来看一个示例。

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
package main

type Data struct {
name string
}

func (p *Data) Ptr() {
println("ptr, p: ", p)
println("ptr: ", p.name)
}

func (p Data) Cp() {
println("cp, &p: ", &p)
println("cp: ", p.name)
}

func Ptr(p *Data) {
println("ptr, p: ", p)
println("ptr: ", p.name)
}

func Cp(p Data) {
println("cp, &p: ", &p)
println("cp: ", p.name)
}

func main() {
da := Data{name: "wii"}
println("main, &da: ", &da)

da.Ptr()
(&da).Ptr()
da.Cp()
(&da).Cp()

println("-----")

Ptr(&da)
// Ptr(da) // ERROR: Cannot use 'da' (type Data) as the type *Data
Cp(da)
// Cp(&da) // ERROR: Cannot use '&da' (type *Data) as the type Data
}

输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
main, &da:  0xc000044768
ptr, p: 0xc000044768
ptr: wii
ptr, p: 0xc000044768
ptr: wii
cp, &p: 0xc000044748
cp: wii
cp, &p: 0xc000044738
cp: wii
-----
ptr, p: 0xc000044768
ptr: wii
cp, &p: 0xc000044758
cp: wii

关注下面两个点

  • 参数类型(指针和非指针)
  • 成员函数和普通函数

首先,定义 Data 变量 da,内存地址为 0xc000044768。紧接着是四次调用,2(调用对象指针、非指针) * 2(参数指针、非指针)的组合;从这四次调用可以可以发现;对于结构的成员函数,调用对象的形式和参数形式无需匹配,实际效果以参数形式为准。如果成员函数参数为指针,则得到的是调用对象的指针形式(无论调用对象是否是指针形式);同样的,如果成员函数参数为非指针形式,则得到的是调用对象的非指针形式(无论调用对象是否是非指针形式);且,两种方式都可以编译通过。

其次,对于普通函数调用,严格区分参数是否为指针形式。比如,Cp(&da)Ptr(da) 两种调用方式都无法通过编译。

最后,从变量的内存地址可以看出,拷贝传递会创建新的内存区域保存传递的参数。

接下来,再来看一个实例。

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
// file: st.go; mod: "evaluate-go/pkg/st"
package st

type Cart struct {
Price float64
Count int
Promo float64
}

func (r Cart) TotalPrice() float64 {
return r.Price * float64(r.Count) * r.Promo
}

func (r *Cart) TotalPricePtr() float64 {
return r.Price * float64(r.Count) * r.Promo
}

func (r Cart) Promotion(p float64) float64 {
println("Promotion, &r: ", &r)
r.Promo *= p
return r.TotalPrice()
}

func (r *Cart) PromotionPtr(p float64) float64 {
println("PromotionPtr, &r: ", r)
r.Promo *= p
return r.TotalPricePtr()
}
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
// file: st.go; mod: "evaluate-go/cmd/st"
package main

import (
"evaluate-go/pkg/st"
"fmt"
)

func main() {
ra := st.Cart{Price: 4, Count: 5, Promo: 1}
fmt.Printf("%v\n", ra.TotalPrice())
fmt.Printf("%v\n", ra.TotalPricePtr())
fmt.Printf("%v\n", (&ra).TotalPricePtr())

println("-----")
fmt.Printf("%v\n", ra.Promotion(0.9))
fmt.Printf("%v\n", ra.TotalPrice())
fmt.Printf("%v\n", ra.TotalPricePtr())

println("-----")
rb := st.Cart{Price: 4, Count: 5, Promo: 1}
fmt.Printf("%v\n", rb.PromotionPtr(0.9))
fmt.Printf("%v\n", rb.TotalPrice())
fmt.Printf("%v\n", rb.TotalPricePtr())
}

输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
main, &ra:  0xc000092eb8
20
20
20
-----
Promotion, &r: 0xc000092ed0
18
20
20
-----
PromotionPtr, &r: 0xc000092ea0
18
18
18

这里需要注意的是,ra.Promotion(0.9) 这个调用,由于是拷贝传递,修改的 Promo 值,是临时变量的,而不是ra 的;这里可能会感到比较奇怪的是,调用了ra.Promotion(0.9) 来设置促销信息(Promo),但是计算总价时,设置的促销信息并未生效(ra.TotalPrice() 返回值仍是 20);调用 rb.PromotionPtr(0.9) 设置促销信息之后,则符合预期。

整形 * 浮点

整形和浮点进行运算,需要显式转换类型。

1
2
3
4
5
i := 10
f := 1.2
// println(i * f) ERROR: Invalid operation: i * f (mismatched types int and float64)
println(float64(i) * f)
println(i * int(f))

node - npm

install pkg

1
2
$ npm install <pkg-name> # OR npm install <pkg-name>
$ npm install <pkg-name>@<version> -s

Upgrade pkg

1
2
3
# 检查过时的包
$ npm outdated
$ npm update --save <pkg-name> # OR --save-dev

使用工具更新所有依赖

1
2
3
$ npm i -g npm-check-updates # 安装依赖
$ ncu -u # 升级
$ npm install # 安装升级后的依赖

spring boot - log4j

Dependency

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- exclude logback-classic -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- add slf4j-log4j12 dependency -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>

Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# root logger setup; [level], appenderName...
log4j.rootLogger=INFO, CL, FILE

# appender; consul
log4j.appender.CL=org.apache.log4j.ConsoleAppender
log4j.appender.CL.layout=org.apache.log4j.PatternLayout
log4j.appender.CL.layout.ConversionPattern=%-5p %c %x - %m%n

# appender; file
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.File=/tmp/eubalaena.log
log4j.appender.FILE.MaxFileSize=1MB
log4j.appender.FILE.MaxBackupIndex=15
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%-5p %c %x - %m%n

log4j.logger.pub.wii=DEBUG, FILE
log4j.logger.org.springframework=INFO, FILE
log4j.logger.org.mybatis=INFO, FILE
org.mybatis.spring.mapper.ClassPathMapperScanner=OFF

java - 正则

match

1
2
3
4
5
6
7
8
9
10
11
12
13
// string.match: yes OR no
private static final String REGEX = "^@encrypt\\{(.*)}$";
"@encrypt{file:mysql.username}".match(REGEX); // return true

// Pattern
String REGEX = "^@encrypt\\{(.*)}$";
Pattern PT = Pattern.compile(REGEX);
Matcher m = PT.matcher(value);

m.find(); // find matched subsequence one by one; yes OR no
m.match(); // match entire string; yes OR no

// group

vs code server

安装

1
curl -fsSL https://code-server.dev/install.sh | sh

更多参考这里

配置

详细文档参考这里。默认配置文件路径是 ~/.config/code-server/config.yaml,也可以通过 --bind-addr 参数指定。

配置文件示例。

1
2
3
4
bind-addr: 0.0.0.0:9025
auth: password
password: 67da2351225608a4384150e8
cert: false # 如果使用 https, cert 改为 true

配置 https 自认证

1
2
3
4
5
# 命令行启动
code-server --cert # 默认证书文件在 ~/.local/share/code-server

# 基于配置启动,修改配置文件
cert: true # 设置为 true 会自动创建 self-signed 证书

运行

1
code-server --config /path/to/config --bind-addr ip:port

服务管理

1
2
3
4
5
6
# 配置自启动
sudo systemctl enable code-server@{user}
# 手动启动
sudo systemctl start code-server@{user}
# 手动停止
sudo systemctl stop code-server@{user}