bazel - usage

查看所有 Targets

1
$ bazel query //... --output label_kind

修改输出路径

1
2
3
# 仍会在项目创建 bazel-* 软链, 一般无需修改
$ bazel --output_base=output build //... # 修改输出路径
$ bazel --output_user_root=output build //... # 修改输出 & 安装路径

使用 github 依赖

在 WORKSPACE 中指定

1
2
3
4
5
http_archive(
name = "com_google_googleapis",
strip_prefix = "googleapis-8b976f7c6187f7f68956207b9a154bc278e11d7e",
urls = ["https://github.com/googleapis/googleapis/archive/8b976f7c6187f7f68956207b9a154bc278e11d7e.tar.gz"],
)

这里的 8b976f7c6187f7f68956207b9a154bc278e11d7e 为 commit id,下载 url 为 https://github.com/{user}/{repo}/archive/{commit}.tar.gz

BUILD 文件中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
proto_library(
name = "person_proto",
srcs = ["person.proto"],
deps = [
"@com_google_googleapis//google/api:annotations_proto", # 这里引用
"@com_google_googleapis//google/api:client_proto",
"@com_google_googleapis//google/api:field_behavior_proto",
"@com_google_googleapis//google/api:resource_proto",
"@com_google_protobuf//:empty_proto",
"@com_google_protobuf//:field_mask_proto",
"@com_google_protobuf//:any_proto",
],
)

proto 中引用

1
2
3
4
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";

部署 jar 包

WORKSPACE 中添加依赖

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
RULES_JVM_EXTERNAL_TAG = "4.0"
RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169"

http_archive(
name = "rules_jvm_external",
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
sha256 = RULES_JVM_EXTERNAL_SHA,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)

load("@rules_jvm_external//:defs.bzl", "maven_install")

maven_install(
artifacts = [
"junit:junit:4.12",
"androidx.test.espresso:espresso-core:3.1.1",
"org.hamcrest:hamcrest-library:1.3",
],
repositories = [
# Private repositories are supported through HTTP Basic auth
# "http://test:Test1234@localhost:8081/artifactory/maven-repo-demo",
"https://maven.google.com",
"https://repo1.maven.org/maven2",
],
)

load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
rules_jvm_external_setup()

BUILD 文件添加 exporter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java_gapic_library(
name = "java_cook_gapic",
srcs = [":cook_proto"],
gapic_yaml = "cook_gapic.yaml",
grpc_service_config = "cook_gapic_service_config.json",
deps = [
":java_cook_proto",
],
)

# 定义 exporter
java_export(
name = "java_cook_gapic_export",
maven_coordinates = "pub.wii.cook:cook-v1:0.0.1",
# pom_template = "pom.tmpl", # You can omit this
# srcs = glob(["*.java"]), # 如果是 java 项目, 这里部署的是 gapic 生成的 jar 包, 先注释掉
runtime_deps = [
":java_cook_gapic"
],
)

执行 exporter

1
2
3
4
5
# 部署到本地
$ bazel run --define "maven_repo=file://$HOME/.m2/repository" //cook/v1:java_cook_gapic_export.publish

# 部署到 maven 仓库, 注意这里要用 https 协议, 不支持 http
$ bazel run --define "maven_repo=https://127.0.0.1:8081/artifactory/maven-repo-demo" --define "maven_user=test" --define "maven_password=Test1234" //cook/v1:java_cook_gapic_export.publish

gapis

Introduction

打包

使用 gRPC + protocol buffers 时,会面临三种打包方式,proto librarygrpc librarygapic library 。不同的打包方式需要使用不同的工具/插件,以如下定义为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

proto library

首先是打包 proto library,需要下载 protoc 程序(在 protobuf 的 release 页面下载)。

1
$ protoc --java_out=jv greeter.proto

这个命令只会生成数据结构,会忽略 service 定义。

gRPC library

然后,是打包 gRPC library,需要下载对应的 generator,工具下载 / 本地编译步骤参考这里,另外 gRPC 相关的库及代码在这里,命令参考如下。

1
$ protoc --plugin=protoc-gen-grpc-java=/Users/wii/Downloads/protoc-gen-grpc-java-1.38.0-osx-x86_64.exe --grpc-java_out=jv --java_out=jv greeter.proto

gRPC library 和 proto library 的区别是,会生成用于服务调用的 client 和 server 端代码。

gapic library

最后,是打包 gapic library,同样需要下载对应的 generator,以 Java 为例,工具参考gapic-generator-javagapic-generator。顺便,artman 作为一个可选的生成工具,已经停止维护。要打包 java 的 gapic library ,首先需要编译插件,从gapic-generator 下载 release 包,运行命令 ./gradlew fatjar 进行编译。

首先,生成 description 文件。

1
$ protoc --include_imports --include_source_info -o greeter.description greeter.proto

接着,生成 proto message classes。

1
2
$ mkdir java-proto-lib-out
$ protoc --java_out=java-proto-lib-out greeter.proto

然后,生成 gRPC stub 。

1
2
$ mkdir java-grpc-lib-out
$ protoc --plugin=protoc-gen-grpc=/Users/wii/Downloads/protoc-gen-grpc-java-1.38.0-osx-x86_64.exe --grpc_out=java-grpc-lib-out greeter.proto

进一步,新建服务描述文件。

1
2
3
4
5
type: google.api.Service
config_version: 3
name: greeter.gapic.cook.pub.wii

title: Example Library API

接着,生成客户端配置文件。

1
2
3
4
5
6
java -XX:-OmitStackTraceInFastThrow -cp /Users/wii/Downloads/gapic-generator-2.11.1/build/libs/gapic-generator-2.11.1-fatjar.jar \
com.google.api.codegen.GeneratorMain GAPIC_CONFIG \
--descriptor_set=greeter.proto.pb \
--service_yaml=greeter-service.yaml \
-o=/Users/wii/Data/tmp/grpc/greeter-gapic-config.yaml
# 输出文件使用绝对路径

然后,创建 package 的 metadata 配置文件。

1
2
3
4
5
6
7
artifact_type: GAPIC
proto_deps:
- google-common-protos
api_name: greeter
api_version: v1
organization_name: solo-kingdom
proto_path: wii/cook/gapic/greeter/v1

最后,生成代码。

java

1
2
3
4
5
6
7
8
java -cp /Users/wii/Downloads/gapic-generator-2.11.1/build/libs/gapic-generator-2.11.1-fatjar.jar \
com.google.api.codegen.GeneratorMain LEGACY_GAPIC_AND_PACKAGE \
--descriptor_set=greeter.proto.pb \
--service_yaml=greeter-service.yaml \
--gapic_yaml=greeter-gapic-config.yaml \
--package_yaml2=greeter-meta-config.yaml \
--language=java \
--o=java-code-gen

python

1
2
3
4
5
6
7
8
java -cp /Users/wii/Downloads/gapic-generator-2.11.1/build/libs/gapic-generator-2.11.1-fatjar.jar \
com.google.api.codegen.GeneratorMain LEGACY_GAPIC_AND_PACKAGE \
--descriptor_set=greeter.proto.pb \
--service_yaml=greeter-service.yaml \
--gapic_yaml=greeter-gapic-config.yaml \
--language=python \
--package_yaml2=greeter-meta-config.yaml \
--o=python-code-gen

顺便,这里提供了一个示例。

最后,在使用 gapic 时,可能需要引用一些 google 的 proto 文件,比如 google/api/annotation.proto ,这些文件定义在这里

Bazel

rules_proto

获取 sha256

1
2
3
4
$ COMMIT_ID=734b8d41d39a903c70132828616f26cb2c7f908c
$ wget https://github.com/stackb/rules_proto/archive/${COMMIT_ID}.tar.gz
$ shasum -a 256 ${COMMIT_ID}.tar.gz
c89348b73f4bc59c0add4074cc0c620a5a2a08338eb4ef207d57eaa8453b82e8

Artman

1
2
# install
$ pip3 install googleapis-artman

配置文件

生成 gapic 库时,指定的配置文件。

1
2
3
4
5
6
7
8
9
10
java_gapic_library(
name = "java_cook_gapic",
srcs = [":cook_proto"],
gapic_yaml = "cook_gapic.yaml", # 这里指定的文件
grpc_service_config = "cook_gapic_service_config.json",
deps = [
":java_cook_proto",
":java_cook_grpc"
],
)

配置参考 auth.proto 内定义的 Authentication 结构。

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
// `Authentication` defines the authentication configuration for API methods
// provided by an API service.
//
// Example:
//
// name: calendar.googleapis.com
// authentication:
// providers:
// - id: google_calendar_auth
// jwks_uri: https://www.googleapis.com/oauth2/v1/certs
// issuer: https://securetoken.google.com
// rules:
// - selector: "*"
// requirements:
// provider_id: google_calendar_auth
// - selector: google.calendar.Delegate
// oauth:
// canonical_scopes: https://www.googleapis.com/auth/calendar.read
message Authentication {
// A list of authentication rules that apply to individual API methods.
//
// **NOTE:** All service configuration rules follow "last one wins" order.
repeated AuthenticationRule rules = 3;

// Defines a set of authentication providers that a service supports.
repeated AuthProvider providers = 4;
}

可以看到,有两个字段,rules & providers。对于 rules,数据结构为 AuthenticationRule。

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
// Authentication rules for the service.
//
// By default, if a method has any authentication requirements, every request
// must include a valid credential matching one of the requirements.
// It's an error to include more than one kind of credential in a single
// request.
//
// If a method doesn't have any auth requirements, request credentials will be
// ignored.
message AuthenticationRule {
// Selects the methods to which this rule applies.
//
// Refer to [selector][google.api.DocumentationRule.selector] for syntax details.
string selector = 1;

// The requirements for OAuth credentials.
OAuthRequirements oauth = 2;

// If true, the service accepts API keys without any other credential.
// This flag only applies to HTTP and gRPC requests.
bool allow_without_credential = 5;

// Requirements for additional authentication providers.
repeated AuthRequirement requirements = 7;
}

示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type: google.api.Service
config_version: 3
name: analyticsadmin.googleapis.com
title: Google Analytics Admin API

# 横线开头定义数组
apis:
- name: google.analytics.admin.v1alpha.AnalyticsAdminService

authentication:
rules:
- selector: 'google.analytics.admin.v1alpha.AnalyticsAdminService.*'
oauth:
canonical_scopes: |-
https://www.googleapis.com/auth/analytics.edit
- selector: '*'
allow_without_credential: true # 无需认证

Reference

gRPC

Introduction

gRPC 是谷歌开源的一款 RPC 框架,protocal buffers 作为它的 IDL(Interface Definition Language,接口定义语言),也可以作为其底层数据交换格式。

Overview

在 gRPC,一个客户端应用可以直接调用不同机器上服务端的方法。在一些 RPC 系统中,gRPC 是基于以下思路,定义一个服务接口,指定可以远程调用的方法,机器参数和返回类型;在服务端,实现这个接口,并且运行一个 gRPC 服务,来处理客户端的请求;在客户端,保存一个存根(stub,在一些语言中简称为客户端)提供和服务端相同的方法。

Concept Diagram

Protocol Buffers

默认情况下,gRPC 使用 Protocol Buffers (Protobuffer,PB,谷歌开源的且成熟的序列化结构化数据的机制/工具)。使用 PB ,首先需要在 proto 文件中定义一个需要序列化的结构。

1
2
3
4
5
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}

如果需要定义一个 Service。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// file: greeter.proto

syntax = "proto3";
// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

如下命令。

1
$ protoc --java_out=jv greeter.proto

并不会生成用于 RPC 的 client 和 server,需要使用如下命令。

1
$ protoc --java_out=jv --grpc-java_out=jv greeter.proto

在使用之前,需要安装工具 protoc-gen-grpc-java,工具下载 / 本地编译步骤参考这里。至于下载,可以移步 这里,选择一个版本并点击链接,在介绍表格部分有 Files 字段,点击 View all ,现在指定平台的可执行文件即可,然后运行如下命令。

1
$ protoc --plugin=protoc-gen-grpc-java=/Users/wii/Downloads/protoc-gen-grpc-java-1.38.0-osx-x86_64.exe --grpc-java_out=jv --java_out=jv greeter.proto 

注意 指定 plugin 时,使用决定路径。

查看生成结果。

1
2
$ ls jv
GreeterGrpc.java GreeterOuterClass.java

Core Concepts

服务定义

gRPC 默认使用 Protocol Buffers 作为服务定义语言,如果需要也可以使用其他可选项。

1
2
3
4
5
6
7
8
9
10
11
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
string greeting = 1;
}

message HelloResponse {
string reply = 1;
}

gRPC 允许定义多种类型的服务方法。

  • 一元 RPCs(Unary RPCs),客户端发送一次请求,得到一次响应,和通常的方法调用类似

    1
    rpc SayHello(HelloRequest) returns (HelloResponse);
  • 服务端流 RPCs(Server streaming RPCs),客户端向服务端发送一次请求,得到一个流来读取一系列的消息。客户端从返回的流中读取数据,直到没有更多信息。gRPC 保证一次独立调用中消息的顺序

    1
    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  • 客户端流 RPCs(Client streaming RPCs ),客户端使用流,向服务端发送一系列的消息。客户端结束写入后,等待服务端读取并返回一个响应。同样,gRPC 保证一次独立调用中消息的顺序

    1
    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • 双向流 RPCs(Bidirectional streaming RPCs),客户端和服务端通过读写流发送消息序列,两个流操作独立,所以客户端和服务端可以以任意顺序读写。比如,服务端可以等待接受到所有消息后返回响应消息序列;也可以每接受一个消息,返回一个响应消息。

    1
    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

使用 API

首先,在 .proto 文件中定义服务(service),gRPC 提供 PB(protocol buffer)编译插件,来生成客户端和服务端代码。gRPC 用户通常在客户端调用这些API,在服务端实现这些 API。

  • 在服务端,实现服务接口定义的方法,并且运行一个 gRPC 服务来处理客户端的请求。gRPC 基础设施解码进来的请求,执行服务方法,并编码服务响应
  • 在客户端,有一个本地对象,称作 stub(对于一些语言,术语为 client),其实现和服务端相同的方法。client 可以在本地对象上调用这些方法,使用合适的 pb 消息类型包装调用的参数,gRPC接着传送请求给服务端,并且返回服务端的 pb 格式消息响应。

同步 vs 异步

gRPC 在大部分语言中,具备异步和同步风格的接口。

RPC 声明周期

Unary RPC

  • 当客户端调用 stub 的方法时,服务端会被告知这次调用的 metadata,包括方法的名称,请求的超时时间等
  • 服务端可以马上返回自己的初始 metadata(在响应前必须传送),也可以等待客户端的请求消息,具体的行为由应用程序指定
  • 一但服务器得到了完整的客户端请求消息,会做对应的工作并构造一个响应消息。接着返回响应,及详细的状态(status)和可选的尾部 metadata。
  • 如果返回状态是 OK,客户端会得到这个响应,这就完成了客户端的调用。

Server streaming RPC

server streaming RPC 和 unary RPC 类似,不同的是服务端在响应中返回一个消息流(a stream of message)。在所有的消息发送完后,详细的状态码和可选的尾部 metadata 会被传送给客户端。

Client streaming RPC

服务端返回单个消息的实际,通常是接受到所有消息后,但并非必须如此。

Bidirectional streaming RPC

双向流RPC,服务端接受到的客户端的 metadata、请求方法、deadline。

Deadlines / Timeouts

gRPC 允许客户端指定超时时间。服务端可以知道一个指定 RPC 调用是否超时,或者剩余处理时间。

终止 RPC

可能存在服务端成功返回,但是客户端请求超时的情况。也有可能,服务端在客户端还在发送请求时来结束本次RPC。

取消 RPC

服务端和客户端都可以在任意时间取消一次 RPC 请求。

Metadata

Metadata 包含一次特定 RPC 请求的信息,数据格式是 k-v 对的形式,key为string,value可以是string,也有可能是二进制数据。

Channels

一个 gRPC 通道提供一个和指定host和port的 gRPC 服务器的连接,当创建一个客户端 stub 时会用到。客户端可以指定通道参数,来修改 gPRC 的默认行为,比如控制消息压缩开关。一个 channel 包含状态,包括 connectedidle

gRPC 怎么处理关闭 channel 的流程,语言间有所不同。

Appendix

thrift VS protocol buffer

  • pb 生成 service 的 client 和 server 代码,需要额外的插件

负载均衡

关于 gRPC 的负载均衡,这里 列举了主要的几种方式,胖客户端、Lookaside LB、Proxy。

参考

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 # 安装升级后的依赖