feat(*): dryRun/debug mode/mode plugins/googleapis

Signed-off-by: storyicon <yuanchao@bilibili.com>
This commit is contained in:
storyicon 2021-07-21 20:25:38 +08:00
parent 9aac714c32
commit da77c8086d
No known key found for this signature in database
GPG key ID: 245915D985F966CF
26 changed files with 730 additions and 126 deletions

View file

@ -43,6 +43,7 @@ PowerProto is used to solve the following three main problems:
5. support batch and recursive compilation of proto files to improve efficiency. 5. support batch and recursive compilation of proto files to improve efficiency.
6. cross-platform support PostAction, you can perform some routine operations (such as replacing "omitempty" in all generated files) after the compilation. 6. cross-platform support PostAction, you can perform some routine operations (such as replacing "omitempty" in all generated files) after the compilation.
7. support PostShell, execute specific shell scripts after the compilation. 7. support PostShell, execute specific shell scripts after the compilation.
8. support `google apis` one-click installation and version control。
## Installation and Dependencies ## Installation and Dependencies
@ -110,6 +111,9 @@ Tidy the config consists of two main operations:
2. install all dependencies defined in the config file. 2. install all dependencies defined in the config file.
Supports entering `debug mode` by appending the `-d` argument to see more detailed logs.
### III. Compiling Proto files ### III. Compiling Proto files
The Proto file can be compiled with the following command. The Proto file can be compiled with the following command.
@ -133,6 +137,22 @@ The execution logic is that for each proto file, the `powerproto.yaml` config fi
Note: The default `working directory` of `PowerProto` is the directory where the `proto file` matches to the config file, it is equivalent to the directory where you execute the `protoc` command. You can change it via `protocWorkDir` in the config file. Note: The default `working directory` of `PowerProto` is the directory where the `proto file` matches to the config file, it is equivalent to the directory where you execute the `protoc` command. You can change it via `protocWorkDir` in the config file.
Supports entering `debug mode` by appending the `-d` argument to see more detailed logs.
Supports entering `dryRun mode` by appending the `-y` argument, in this mode the commands are not actually executed, but just printed out, which is very useful for debugging.
### IV. View environment variables
If your command keeps getting stuck in a certain state, there is a high probability that there is a network problem.
You can check if the environment variables are configured successfully with the following command:
```
powerproto env
```
## Examples ## Examples
For example, you have the following file structure in the `/mnt/data/hello` directory: For example, you have the following file structure in the `/mnt/data/hello` directory:
@ -227,6 +247,9 @@ protoc: 3.17.3
# the default is the directory where the config file is located. # the default is the directory where the config file is located.
# support mixed environment variables in path, such as $GOPATH # support mixed environment variables in path, such as $GOPATH
protocWorkDir: "" protocWorkDir: ""
# optional. If you need to use googleapis, you should fill in the commit id of googleapis here.
# You can fill in the latest, it will be automatically converted to the latest version.
googleapis: 75e9812478607db997376ccea247dd6928f70f45
# required. it is used to describe which plug-ins are required for compilation # required. it is used to describe which plug-ins are required for compilation
plugins: plugins:
# the name, path, and version number of the plugin. # the name, path, and version number of the plugin.
@ -244,13 +267,19 @@ options:
- --grpc-gateway_out=. - --grpc-gateway_out=.
- --go-grpc_out=paths=source_relative:. - --go-grpc_out=paths=source_relative:.
# required. defines the path of the proto dependency, which will be converted to the --proto_path (-I) parameter. # required. defines the path of the proto dependency, which will be converted to the --proto_path (-I) parameter.
# support mixed environment variables, such as $GOPATH/include.
# $POWERPROTO_INCLUDE is a special variable that refers to $POWERPROTO_HOME/include, which contains the public proto files provided by protoc by default.
# "." is the folder where the config file is located.
importPaths: importPaths:
# Special variables. Will be replaced with the folder where the current configuration file is located.
- . - .
# Environment variables. Environment variables can be used in importPaths.
# Support mixed writing like $GOPATH/include
- $GOPATH - $GOPATH
# Special variables. Will be replaced with the local path to the public proto file that comes with protoc by default
- $POWERPROTO_INCLUDE - $POWERPROTO_INCLUDE
# Special variables. Reference to the directory where the proto file to be compiled is located
# For example, if /a/b/data.proto is to be compiled, then the /a/b directory will be automatically referenced
- $SOURCE_RELATIVE
# Special variables. Will be replaced with the local path to the version of google apis specified by the googleapis field
- $POWERPROTO_GOOGLEAPIS
# optional. The operation is executed after compilation. # optional. The operation is executed after compilation.
# its working directory is the directory where the config file is located. # its working directory is the directory where the config file is located.
# postActions is cross-platform compatible. # postActions is cross-platform compatible.
@ -281,6 +310,7 @@ scopes:
- ./apis1 - ./apis1
protoc: v3.17.3 protoc: v3.17.3
protocWorkDir: "" protocWorkDir: ""
googleapis: 75e9812478607db997376ccea247dd6928f70f45
plugins: plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0 protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0 protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
@ -302,6 +332,7 @@ scopes:
- ./apis2 - ./apis2
protoc: v3.17.3 protoc: v3.17.3
protocWorkDir: "" protocWorkDir: ""
googleapis: 75e9812478607db997376ccea247dd6928f70f45
plugins: plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.0 protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0 protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0

View file

@ -22,6 +22,7 @@ PowerProto主要用于解决下面三个问题
- [一、初始化配置](#一初始化配置) - [一、初始化配置](#一初始化配置)
- [二、整理配置](#二整理配置) - [二、整理配置](#二整理配置)
- [三、编译Proto文件](#三编译proto文件) - [三、编译Proto文件](#三编译proto文件)
- [四、查看环境变量](#四查看环境变量)
- [示例](#示例) - [示例](#示例)
- [配置文件](#配置文件) - [配置文件](#配置文件)
- [解释](#解释) - [解释](#解释)
@ -43,6 +44,7 @@ PowerProto主要用于解决下面三个问题
5. 支持批量、递归编译proto文件提高效率。 5. 支持批量、递归编译proto文件提高效率。
6. 跨平台支持PostAction可以在编译完成之后执行一些常规操作比如替换掉所有生成文件中的"omitempty")。 6. 跨平台支持PostAction可以在编译完成之后执行一些常规操作比如替换掉所有生成文件中的"omitempty")。
7. 支持PostShell在编译完成之后执行特定的shell脚本。 7. 支持PostShell在编译完成之后执行特定的shell脚本。
8. 支持 `google api` 的一键安装与版本控制。
## 安装与依赖 ## 安装与依赖
@ -87,8 +89,6 @@ powerproto build -h
powerproto init powerproto init
``` ```
### 二、整理配置 ### 二、整理配置
可以通过下面的命令整理配置: 可以通过下面的命令整理配置:
@ -110,7 +110,7 @@ powerproto tidy [the path of proto file]
1. 通过查询将版本中的latest替换为真实的最新版本号。 1. 通过查询将版本中的latest替换为真实的最新版本号。
2. 安装配置文件中定义的所有依赖。 2. 安装配置文件中定义的所有依赖。
支持通过 `-d` 参数来进入到`debug模式`,查看更详细的日志。
### 三、编译Proto文件 ### 三、编译Proto文件
@ -135,7 +135,16 @@ powerproto build -r .
注意:`protoc`执行的工作目录默认是`proto文件`匹配到的配置文件所在的目录它相当于你在配置文件所在目录执行protoc命令。你可以通过配置文件中的 `protocWorkDir` 来进行修改。 注意:`protoc`执行的工作目录默认是`proto文件`匹配到的配置文件所在的目录它相当于你在配置文件所在目录执行protoc命令。你可以通过配置文件中的 `protocWorkDir` 来进行修改。
支持通过 `-d` 参数来进入到`debug模式`,查看更详细的日志。
支持通过 `-y` 参数来进入到`dryRun模式`,只打印命令而不真正执行,这对于调试非常有用。
### 四、查看环境变量
如果你的命令一直卡在某个状态,大概率是出现网络问题了。
你可以通过下面的命令来查看环境变量是否配置成功:
```
powerproto env
```
## 示例 ## 示例
@ -229,6 +238,9 @@ protoc: 3.17.3
# 选填执行protoc命令的工作目录默认是配置文件所在目录 # 选填执行protoc命令的工作目录默认是配置文件所在目录
# 支持路径中混用环境变量,比如$GOPATH # 支持路径中混用环境变量,比如$GOPATH
protocWorkDir: "" protocWorkDir: ""
# 选填,如果需要使用 googleapis你应该在这里填写googleapis的commit id
# 可以填 latest会自动转换成最新的版本
googleapis: 75e9812478607db997376ccea247dd6928f70f45
# 必填代表scope匹配的目录中的proto文件在编译时需要用到哪些插件 # 必填代表scope匹配的目录中的proto文件在编译时需要用到哪些插件
plugins: plugins:
# 插件的名字、路径以及版本号。 # 插件的名字、路径以及版本号。
@ -245,13 +257,18 @@ options:
- --grpc-gateway_out=. - --grpc-gateway_out=.
- --go-grpc_out=paths=source_relative:. - --go-grpc_out=paths=source_relative:.
# 必填,定义了构建时 protoc 的引用路径,会被转换为 --proto_path (-I) 参数。 # 必填,定义了构建时 protoc 的引用路径,会被转换为 --proto_path (-I) 参数。
# 支持混用环境变量,比如 $GOPATH/include。
# $POWERPROTO_INCLUDE是一个特殊变量指 $POWERPRTO/include里面包含了protoc默认提供的公共proto 文件。
# "." 是指配置文件所在文件夹。
importPaths: importPaths:
# 特殊变量。代表当前配置文件所在文件夹
- . - .
# 环境变量。可以使用环境变量
# 也支持 $GOPATH/include 这样的混合写法
- $GOPATH - $GOPATH
- $POWERPROTO_INCLUDE - $POWERPROTO_INCLUDE
# 特殊变量。引用待编译的proto文件所在的目录
# 比如将要编译 /a/b/data.proto那么 /a/b 目录将会被自动引用
- $SOURCE_RELATIVE
# 特殊变量。引用googleapis字段所指定的版本的google apis
- $POWERPROTO_GOOGLEAPIS
# 选填,构建完成之后执行的操作,工作目录是配置文件所在目录 # 选填,构建完成之后执行的操作,工作目录是配置文件所在目录
# postActions是跨平台兼容的 # postActions是跨平台兼容的
# 注意,必须在 powerproto build 时附加 -p 参数才会执行配置文件中的postActions # 注意,必须在 powerproto build 时附加 -p 参数才会执行配置文件中的postActions
@ -281,6 +298,7 @@ scopes:
- ./apis1 - ./apis1
protoc: v3.17.3 protoc: v3.17.3
protocWorkDir: "" protocWorkDir: ""
googleapis: 75e9812478607db997376ccea247dd6928f70f45
plugins: plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0 protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0 protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
@ -302,6 +320,7 @@ scopes:
- ./apis2 - ./apis2
protoc: v3.17.3 protoc: v3.17.3
protocWorkDir: "" protocWorkDir: ""
googleapis: 75e9812478607db997376ccea247dd6928f70f45
plugins: plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.0 protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0 protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0

View file

@ -0,0 +1,33 @@
scopes:
- ./
googleapis: 75e9812478607db997376ccea247dd6928f70f45
protoc: v3.17.3
protocWorkDir: ""
plugins:
protoc-gen-deepcopy: istio.io/tools/cmd/protoc-gen-deepcopy@v0.0.0-20210720210831-58ecc8fccb3e
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
protoc-gen-go-json: github.com/mitchellh/protoc-gen-go-json@v1.0.0
protoc-gen-gofast: github.com/gogo/protobuf/protoc-gen-gofast@v1.3.2
protoc-gen-gogo: github.com/gogo/protobuf/protoc-gen-gogo@v1.3.2
protoc-gen-grpc-gateway: github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.5.0
protoc-gen-openapiv2: github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.5.0
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
- --grpc-gateway_out=.
- --openapiv2_out=.
- --gogo_out=.
- --gofast_out=.
- --deepcopy_out=source_relative:.
- --go-json_out=.
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
- $POWERPROTO_GOOGLEAPIS
- $SOURCE_RELATIVE
postActions: []
postShell: ""

View file

@ -22,8 +22,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/bootstraps" "github.com/storyicon/powerproto/pkg/bootstraps"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
@ -48,8 +48,8 @@ compile proto files and execute the post actions/shells:
// powerproto build xxxxx.proto // powerproto build xxxxx.proto
func CommandBuild(log logger.Logger) *cobra.Command { func CommandBuild(log logger.Logger) *cobra.Command {
var recursive bool var recursive bool
// todo: this feature is still under development
var dryRun bool var dryRun bool
var debugMode bool
var postScriptEnabled bool var postScriptEnabled bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "build [dir|proto file]", Use: "build [dir|proto file]",
@ -59,10 +59,15 @@ func CommandBuild(log logger.Logger) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context() ctx := cmd.Context()
if dryRun { if dryRun {
ctx = command.WithDryRun(ctx) ctx = consts.WithDryRun(ctx)
log.LogWarn(nil, "running in dryRun mode")
} }
if !postScriptEnabled { if !postScriptEnabled {
ctx = command.WithDisableAction(ctx) ctx = consts.WithDisableAction(ctx)
}
if debugMode {
ctx = consts.WithDebugMode(ctx)
log.LogWarn(nil, "running in debug mode")
} }
target, err := filepath.Abs(args[0]) target, err := filepath.Abs(args[0])
@ -113,5 +118,7 @@ func CommandBuild(log logger.Logger) *cobra.Command {
flags := cmd.PersistentFlags() flags := cmd.PersistentFlags()
flags.BoolVarP(&recursive, "recursive", "r", recursive, "whether to recursively traverse all child folders") flags.BoolVarP(&recursive, "recursive", "r", recursive, "whether to recursively traverse all child folders")
flags.BoolVarP(&postScriptEnabled, "postScriptEnabled", "p", postScriptEnabled, "when this flag is attached, it will allow the execution of postActions and postShell") flags.BoolVarP(&postScriptEnabled, "postScriptEnabled", "p", postScriptEnabled, "when this flag is attached, it will allow the execution of postActions and postShell")
flags.BoolVarP(&debugMode, "debug", "d", debugMode, "debug mode")
flags.BoolVarP(&dryRun, "dryRun", "y", dryRun, "dryRun mode")
return cmd return cmd
} }

View file

@ -51,6 +51,7 @@ func GetDefaultConfig() *configs.Config {
"./", "./",
}, },
Protoc: "latest", Protoc: "latest",
GoogleAPIs: "75e9812478607db997376ccea247dd6928f70f45",
Plugins: map[string]string{ Plugins: map[string]string{
"protoc-gen-go": "google.golang.org/protobuf/cmd/protoc-gen-go@latest", "protoc-gen-go": "google.golang.org/protobuf/cmd/protoc-gen-go@latest",
"protoc-gen-go-grpc": "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest", "protoc-gen-go-grpc": "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
@ -64,7 +65,9 @@ func GetDefaultConfig() *configs.Config {
ImportPaths: []string{ ImportPaths: []string{
".", ".",
"$GOPATH", "$GOPATH",
"$POWERPROTO_INCLUDE", consts.KeyPowerProtoInclude,
consts.KeyPowerProtoGoogleAPIs,
consts.KeySourceRelative,
}, },
} }
} }

View file

@ -56,6 +56,27 @@ func GetWellKnownPlugins() []*Plugin {
"--grpc-gateway_out=.", "--grpc-gateway_out=.",
}, },
}, },
{
Name: "protoc-gen-openapiv2",
Pkg: "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest",
Options: []string{
"--openapiv2_out=.",
},
},
{
Name: "protoc-gen-gogo",
Pkg: "github.com/gogo/protobuf/protoc-gen-gogo@latest",
Options: []string{
"--gogo_out=.",
},
},
{
Name: "protoc-gen-gofast",
Pkg: "github.com/gogo/protobuf/protoc-gen-gofast@latest",
Options: []string{
"--gofast_out=.",
},
},
{ {
Name: "protoc-gen-deepcopy", Name: "protoc-gen-deepcopy",
Pkg: "istio.io/tools/cmd/protoc-gen-deepcopy@latest", Pkg: "istio.io/tools/cmd/protoc-gen-deepcopy@latest",

View file

@ -24,6 +24,7 @@ import (
"github.com/storyicon/powerproto/pkg/bootstraps" "github.com/storyicon/powerproto/pkg/bootstraps"
"github.com/storyicon/powerproto/pkg/component/pluginmanager" "github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs" "github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar" "github.com/storyicon/powerproto/pkg/util/progressbar"
@ -32,7 +33,7 @@ import (
func tidy(ctx context.Context, func tidy(ctx context.Context,
pluginManager pluginmanager.PluginManager, pluginManager pluginmanager.PluginManager,
configFilePath string) error { configFilePath string) error {
progress := progressbar.GetProgressBar(1) progress := progressbar.GetProgressBar(ctx, 1)
progress.SetPrefix("tidy config") progress.SetPrefix("tidy config")
err := bootstraps.StepTidyConfigFile(ctx, pluginManager, progress, configFilePath) err := bootstraps.StepTidyConfigFile(ctx, pluginManager, progress, configFilePath)
if err != nil { if err != nil {
@ -47,6 +48,9 @@ func tidy(ctx context.Context,
if err := bootstraps.StepInstallProtoc(ctx, pluginManager, configItems); err != nil { if err := bootstraps.StepInstallProtoc(ctx, pluginManager, configItems); err != nil {
return err return err
} }
if err := bootstraps.StepInstallGoogleAPIs(ctx, pluginManager, configItems); err != nil {
return err
}
if err := bootstraps.StepInstallPlugins(ctx, pluginManager, configItems); err != nil { if err := bootstraps.StepInstallPlugins(ctx, pluginManager, configItems); err != nil {
return err return err
} }
@ -56,10 +60,17 @@ func tidy(ctx context.Context,
// By default, clean the powerproto.yaml of the current directory and all parent directories // By default, clean the powerproto.yaml of the current directory and all parent directories
// You can also explicitly specify the configuration file to clean up // You can also explicitly specify the configuration file to clean up
func CommandTidy(log logger.Logger) *cobra.Command { func CommandTidy(log logger.Logger) *cobra.Command {
return &cobra.Command{ var debugMode bool
cmd := &cobra.Command{
Use: "tidy [config file]", Use: "tidy [config file]",
Short: "tidy the config file. It will replace the version number and install the protoc and proto plugins that declared in the config file", Short: "tidy the config file. It will replace the version number and install the protoc and proto plugins that declared in the config file",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
if debugMode {
ctx = consts.WithDebugMode(ctx)
log.LogWarn(nil, "running in debug mode")
}
var targets []string var targets []string
if len(args) != 0 { if len(args) != 0 {
for _, arg := range args { for _, arg := range args {
@ -90,7 +101,7 @@ func CommandTidy(log logger.Logger) *cobra.Command {
continue continue
} }
log.LogInfo(nil, "tidy %s", path) log.LogInfo(nil, "tidy %s", path)
if err := tidy(cmd.Context(), pluginManager, path); err != nil { if err := tidy(ctx, pluginManager, path); err != nil {
log.LogFatal(map[string]interface{}{ log.LogFatal(map[string]interface{}{
"path": path, "path": path,
"err": err, "err": err,
@ -105,4 +116,7 @@ func CommandTidy(log logger.Logger) *cobra.Command {
log.LogInfo(nil, "\r\nsucceeded, you are ready to go :)") log.LogInfo(nil, "\r\nsucceeded, you are ready to go :)")
}, },
} }
flags := cmd.PersistentFlags()
flags.BoolVarP(&debugMode, "debug", "d", debugMode, "debug mode")
return cmd
} }

View file

@ -25,8 +25,8 @@ import (
"github.com/storyicon/powerproto/pkg/component/configmanager" "github.com/storyicon/powerproto/pkg/component/configmanager"
"github.com/storyicon/powerproto/pkg/component/pluginmanager" "github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs" "github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/concurrent" "github.com/storyicon/powerproto/pkg/util/concurrent"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar" "github.com/storyicon/powerproto/pkg/util/progressbar"
@ -38,7 +38,7 @@ func StepLookUpConfigs(
targets []string, targets []string,
configManager configmanager.ConfigManager, configManager configmanager.ConfigManager,
) ([]configs.ConfigItem, error) { ) ([]configs.ConfigItem, error) {
progress := progressbar.GetProgressBar(len(targets)) progress := progressbar.GetProgressBar(ctx, len(targets))
progress.SetPrefix("Lookup configs of proto files") progress.SetPrefix("Lookup configs of proto files")
var configItems []configs.ConfigItem var configItems []configs.ConfigItem
deduplicate := map[string]struct{}{} deduplicate := map[string]struct{}{}
@ -67,6 +67,57 @@ func StepLookUpConfigs(
return configItems, nil return configItems, nil
} }
// StepInstallGoogleAPIs is used to install google apis
func StepInstallGoogleAPIs(ctx context.Context,
pluginManager pluginmanager.PluginManager,
configItems []configs.ConfigItem) error {
deduplicate := map[string]struct{}{}
for _, config := range configItems {
version := config.Config().GoogleAPIs
if version != "" {
deduplicate[version] = struct{}{}
}
}
if len(deduplicate) == 0 {
return nil
}
progress := progressbar.GetProgressBar(ctx, len(deduplicate))
progress.SetPrefix("Install googleapis")
versionsMap := map[string]struct{}{}
for version := range deduplicate {
if version == "latest" {
progress.SetSuffix("query latest version of googleapis")
latestVersion, err := pluginManager.GetGoogleAPIsLatestVersion(ctx)
if err != nil {
return errors.Wrap(err, "failed to list googleapis versions")
}
version = latestVersion
}
progress.SetSuffix("check cache of googleapis %s", version)
exists, _, err := pluginManager.IsGoogleAPIsInstalled(ctx, version)
if err != nil {
return err
}
if exists {
progress.SetSuffix("the %s version of googleapis is already cached", version)
} else {
progress.SetSuffix("install %s version of googleapis", version)
_, err = pluginManager.InstallGoogleAPIs(ctx, version)
if err != nil {
return err
}
progress.SetSuffix("the %s version of googleapis is installed", version)
}
versionsMap[version] = struct{}{}
progress.Incr()
}
progress.Wait()
fmt.Println("the following versions of googleapis will be used:", util.SetToSlice(versionsMap))
return nil
}
// StepInstallProtoc is used to install protoc // StepInstallProtoc is used to install protoc
func StepInstallProtoc(ctx context.Context, func StepInstallProtoc(ctx context.Context,
pluginManager pluginmanager.PluginManager, pluginManager pluginmanager.PluginManager,
@ -74,9 +125,12 @@ func StepInstallProtoc(ctx context.Context,
deduplicate := map[string]struct{}{} deduplicate := map[string]struct{}{}
for _, config := range configItems { for _, config := range configItems {
version := config.Config().Protoc version := config.Config().Protoc
if version == "" {
return errors.Errorf("protoc version is required: %s", config.Path())
}
deduplicate[version] = struct{}{} deduplicate[version] = struct{}{}
} }
progress := progressbar.GetProgressBar(len(deduplicate)) progress := progressbar.GetProgressBar(ctx, len(deduplicate))
progress.SetPrefix("Install protoc") progress.SetPrefix("Install protoc")
versionsMap := map[string]struct{}{} versionsMap := map[string]struct{}{}
@ -123,7 +177,7 @@ func StepInstallPlugins(ctx context.Context,
deduplicate[pkg] = struct{}{} deduplicate[pkg] = struct{}{}
} }
} }
progress := progressbar.GetProgressBar(len(deduplicate)) progress := progressbar.GetProgressBar(ctx, len(deduplicate))
progress.SetPrefix("Install plugins") progress.SetPrefix("Install plugins")
pluginsMap := map[string]struct{}{} pluginsMap := map[string]struct{}{}
for pkg := range deduplicate { for pkg := range deduplicate {
@ -173,7 +227,7 @@ func StepCompile(ctx context.Context,
compilerManager compilermanager.CompilerManager, compilerManager compilermanager.CompilerManager,
targets []string, targets []string,
) error { ) error {
progress := progressbar.GetProgressBar(len(targets)) progress := progressbar.GetProgressBar(ctx, len(targets))
progress.SetPrefix("Compile Proto Files") progress.SetPrefix("Compile Proto Files")
c := concurrent.NewErrGroup(ctx, 10) c := concurrent.NewErrGroup(ctx, 10)
for _, target := range targets { for _, target := range targets {
@ -203,7 +257,7 @@ func StepCompile(ctx context.Context,
func StepPostAction(ctx context.Context, func StepPostAction(ctx context.Context,
actionsManager actionmanager.ActionManager, actionsManager actionmanager.ActionManager,
configItems []configs.ConfigItem) error { configItems []configs.ConfigItem) error {
progress := progressbar.GetProgressBar(len(configItems)) progress := progressbar.GetProgressBar(ctx, len(configItems))
progress.SetPrefix("PostAction") progress.SetPrefix("PostAction")
for _, cfg := range configItems { for _, cfg := range configItems {
progress.SetSuffix(cfg.Path()) progress.SetSuffix(cfg.Path())
@ -220,7 +274,7 @@ func StepPostAction(ctx context.Context,
func StepPostShell(ctx context.Context, func StepPostShell(ctx context.Context,
actionsManager actionmanager.ActionManager, actionsManager actionmanager.ActionManager,
configItems []configs.ConfigItem) error { configItems []configs.ConfigItem) error {
progress := progressbar.GetProgressBar(len(configItems)) progress := progressbar.GetProgressBar(ctx, len(configItems))
progress.SetPrefix("PostShell") progress.SetPrefix("PostShell")
for _, cfg := range configItems { for _, cfg := range configItems {
progress.SetSuffix(cfg.Path()) progress.SetSuffix(cfg.Path())
@ -237,6 +291,9 @@ func StepPostShell(ctx context.Context,
func Compile(ctx context.Context, targets []string) error { func Compile(ctx context.Context, targets []string) error {
log := logger.NewDefault("compile") log := logger.NewDefault("compile")
log.SetLogLevel(logger.LevelError) log.SetLogLevel(logger.LevelError)
if consts.IsDebugMode(ctx) {
log.SetLogLevel(logger.LevelDebug)
}
configManager, err := configmanager.NewConfigManager(log) configManager, err := configmanager.NewConfigManager(log)
if err != nil { if err != nil {
@ -259,9 +316,13 @@ func Compile(ctx context.Context, targets []string) error {
if err != nil { if err != nil {
return err return err
} }
if err := StepInstallProtoc(ctx, pluginManager, configItems); err != nil { if err := StepInstallProtoc(ctx, pluginManager, configItems); err != nil {
return err return err
} }
if err := StepInstallGoogleAPIs(ctx, pluginManager, configItems); err != nil {
return err
}
if err := StepInstallPlugins(ctx, pluginManager, configItems); err != nil { if err := StepInstallPlugins(ctx, pluginManager, configItems); err != nil {
return err return err
} }
@ -269,7 +330,7 @@ func Compile(ctx context.Context, targets []string) error {
return err return err
} }
if !command.IsDisableAction(ctx) { if !consts.IsDisableAction(ctx) {
if err := StepPostAction(ctx, actionManager, configItems); err != nil { if err := StepPostAction(ctx, actionManager, configItems); err != nil {
return err return err
} }
@ -277,7 +338,7 @@ func Compile(ctx context.Context, targets []string) error {
return err return err
} }
} else { } else {
log.LogWarn(nil, "PostAction and PostShell is skipped. If you need to allow execution, please append '-a' to command flags to enable") log.LogWarn(nil, "PostAction and PostShell is skipped. If you need to allow execution, please append '-p' to command flags to enable")
} }
log.LogInfo(nil, "Good job! you are ready to go :)") log.LogInfo(nil, "Good job! you are ready to go :)")

View file

@ -22,6 +22,7 @@ import (
"github.com/storyicon/powerproto/pkg/component/configmanager" "github.com/storyicon/powerproto/pkg/component/configmanager"
"github.com/storyicon/powerproto/pkg/component/pluginmanager" "github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs" "github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar" "github.com/storyicon/powerproto/pkg/util/progressbar"
@ -31,6 +32,9 @@ import (
func StepTidyConfig(ctx context.Context, targets []string) error { func StepTidyConfig(ctx context.Context, targets []string) error {
log := logger.NewDefault("tidy") log := logger.NewDefault("tidy")
log.SetLogLevel(logger.LevelError) log.SetLogLevel(logger.LevelError)
if consts.IsDebugMode(ctx) {
log.SetLogLevel(logger.LevelDebug)
}
configManager, err := configmanager.NewConfigManager(log) configManager, err := configmanager.NewConfigManager(log)
if err != nil { if err != nil {
@ -53,7 +57,7 @@ func StepTidyConfig(ctx context.Context, targets []string) error {
return nil return nil
} }
progress := progressbar.GetProgressBar(len(configPaths)) progress := progressbar.GetProgressBar(ctx, len(configPaths))
progress.SetPrefix("tidy configs") progress.SetPrefix("tidy configs")
for path := range configPaths { for path := range configPaths {
progress.SetSuffix(path) progress.SetSuffix(path)

View file

@ -20,8 +20,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
@ -48,15 +48,20 @@ func ActionCopy(ctx context.Context, log logger.Logger, args []string, options *
return errors.Errorf("absolute destination %s is not allowed in action move", destination) return errors.Errorf("absolute destination %s is not allowed in action move", destination)
} }
if command.IsDryRun(ctx) { if consts.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{ log.LogInfo(map[string]interface{}{
"action": "copy", "action": "copy",
"from": absSource, "from": absSource,
"to": absDestination, "to": absDestination,
}, "DryRun") }, consts.TextDryRun)
return nil return nil
} }
log.LogDebug(map[string]interface{}{
"action": "copy",
"from": absSource,
"to": absDestination,
}, consts.TextExecuteAction)
if err := util.CopyDirectory(absSource, absDestination); err != nil { if err := util.CopyDirectory(absSource, absDestination); err != nil {
return err return err
} }

View file

@ -21,8 +21,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
@ -49,15 +49,21 @@ func ActionMove(ctx context.Context, log logger.Logger, args []string, options *
return errors.Errorf("absolute destination %s is not allowed in action move", destination) return errors.Errorf("absolute destination %s is not allowed in action move", destination)
} }
if command.IsDryRun(ctx) { if consts.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{ log.LogInfo(map[string]interface{}{
"action": "move", "action": "move",
"from": absSource, "from": absSource,
"to": absDestination, "to": absDestination,
}, "DryRun") }, consts.TextDryRun)
return nil return nil
} }
log.LogDebug(map[string]interface{}{
"action": "move",
"from": absSource,
"to": absDestination,
}, consts.TextExecuteAction)
if err := util.CopyDirectory(absSource, absDestination); err != nil { if err := util.CopyDirectory(absSource, absDestination); err != nil {
return err return err
} }

View file

@ -21,7 +21,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util/command" "github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
@ -35,14 +35,19 @@ func ActionRemove(ctx context.Context, log logger.Logger, args []string, options
} }
path := filepath.Join(filepath.Dir(options.ConfigFilePath), arg) path := filepath.Join(filepath.Dir(options.ConfigFilePath), arg)
if command.IsDryRun(ctx) { if consts.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{ log.LogInfo(map[string]interface{}{
"action": "remove", "action": "remove",
"target": path, "target": path,
}, "DryRun") }, consts.TextDryRun)
return nil return nil
} }
log.LogDebug(map[string]interface{}{
"action": "remove",
"target": path,
}, consts.TextExecuteAction)
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(path); err != nil {
return err return err
} }

View file

@ -23,8 +23,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
@ -59,15 +59,21 @@ func ActionReplace(ctx context.Context, log logger.Logger, args []string, option
panic(err) panic(err)
} }
if matched { if matched {
if command.IsDryRun(ctx) { if consts.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{ log.LogInfo(map[string]interface{}{
"action": "replace", "action": "replace",
"file": path, "file": path,
"from": from, "from": from,
"to": to, "to": to,
}, "DryRun") }, consts.TextDryRun)
return nil return nil
} }
log.LogDebug(map[string]interface{}{
"action": "replace",
"file": path,
"from": from,
"to": to,
}, consts.TextExecuteAction)
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return err

View file

@ -47,6 +47,7 @@ type BasicCompiler struct {
protocPath string protocPath string
arguments []string arguments []string
googleapisPath string
dir string dir string
} }
@ -72,6 +73,9 @@ func NewBasicCompiler(
config: config, config: config,
pluginManager: pluginManager, pluginManager: pluginManager,
} }
if err := basic.calcGoogleAPIs(ctx); err != nil {
return nil, err
}
if err := basic.calcProto(ctx); err != nil { if err := basic.calcProto(ctx); err != nil {
return nil, err return nil, err
} }
@ -84,11 +88,17 @@ func NewBasicCompiler(
return basic, nil return basic, nil
} }
// Compile is used to compile proto file // Compile is used to compile proto file
func (b *BasicCompiler) Compile(ctx context.Context, protoFilePath string) error { func (b *BasicCompiler) Compile(ctx context.Context, protoFilePath string) error {
arguments := make([]string, len(b.arguments)) arguments := make([]string, len(b.arguments))
copy(arguments, b.arguments) copy(arguments, b.arguments)
// contains source relative
if util.Contains(b.config.Config().ImportPaths,
consts.KeySourceRelative) {
arguments = append(arguments, "--proto_path="+filepath.Dir(protoFilePath))
}
arguments = append(arguments, protoFilePath) arguments = append(arguments, protoFilePath)
_, err := command.Execute(ctx, _, err := command.Execute(ctx,
b.Logger, b.dir, b.protocPath, arguments, nil) b.Logger, b.dir, b.protocPath, arguments, nil)
@ -118,6 +128,30 @@ func (b *BasicCompiler) calcDir(ctx context.Context) error {
return nil return nil
} }
func (b *BasicCompiler) calcGoogleAPIs(ctx context.Context) error {
cfg := b.config.Config()
commitId := cfg.GoogleAPIs
if commitId == "" {
return nil
}
if !util.Contains(cfg.ImportPaths, consts.KeyPowerProtoGoogleAPIs) {
return nil
}
if commitId == "latest" {
latestVersion, err := b.pluginManager.GetGoogleAPIsLatestVersion(ctx)
if err != nil {
return err
}
commitId = latestVersion
}
local, err := b.pluginManager.InstallGoogleAPIs(ctx, commitId)
if err != nil {
return err
}
b.googleapisPath = local
return nil
}
func (b *BasicCompiler) calcProto(ctx context.Context) error { func (b *BasicCompiler) calcProto(ctx context.Context) error {
cfg := b.config cfg := b.config
protocVersion := cfg.Config().Protoc protocVersion := cfg.Config().Protoc
@ -143,9 +177,17 @@ func (b *BasicCompiler) calcArguments(ctx context.Context) error {
dir := filepath.Dir(cfg.Path()) dir := filepath.Dir(cfg.Path())
// build import paths // build import paths
Loop:
for _, path := range cfg.Config().ImportPaths { for _, path := range cfg.Config().ImportPaths {
if path == consts.KeyPowerProtoInclude { switch path {
case consts.KeyPowerProtoInclude:
path = b.pluginManager.IncludePath(ctx) path = b.pluginManager.IncludePath(ctx)
case consts.KeyPowerProtoGoogleAPIs:
if b.googleapisPath != "" {
path = b.googleapisPath
}
case consts.KeySourceRelative:
continue Loop
} }
path = util.RenderPathWithEnv(path) path = util.RenderPathWithEnv(path)
if !filepath.IsAbs(path) { if !filepath.IsAbs(path) {

View file

@ -0,0 +1,91 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"context"
"strings"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ErrGitList defines the git list error
type ErrGitList struct {
*command.ErrCommandExec
}
// GetGitLatestCommitId is used to get the latest commit id
func GetGitLatestCommitId(ctx context.Context, log logger.Logger, repo string) (string, error) {
data, err := command.Execute(ctx, log, "", "git", []string{
"ls-remote", repo, "HEAD",
}, nil)
if err != nil {
return "", &ErrGitList{
ErrCommandExec: err,
}
}
f := strings.Fields(string(data))
if len(f) != 2 {
return "", &ErrGitList{
ErrCommandExec: err,
}
}
return f[0], nil
}
// ListGitCommitIds is used to list git commit ids
func ListGitCommitIds(ctx context.Context, log logger.Logger, repo string) ([]string, error) {
data, err := command.Execute(ctx, log, "", "git", []string{
"ls-remote", repo,
}, nil)
if err != nil {
return nil, &ErrGitList{
ErrCommandExec: err,
}
}
var commitIds []string
for _, line := range strings.Split(string(data), "\n") {
f := strings.Fields(line)
if len(f) != 2 {
continue
}
commitIds = append(commitIds, f[0])
}
return commitIds, nil
}
// ListGitTags is used to list the git tags of specified repository
func ListGitTags(ctx context.Context, log logger.Logger, repo string) ([]string, error) {
data, err := command.Execute(ctx, log, "", "git", []string{
"ls-remote", "--tags", "--refs", "--sort", "version:refname", repo,
}, nil)
if err != nil {
return nil, &ErrGitList{
ErrCommandExec: err,
}
}
var tags []string
for _, line := range strings.Split(string(data), "\n") {
f := strings.Fields(line)
if len(f) != 2 {
continue
}
if strings.HasPrefix(f[1], "refs/tags/") {
tags = append(tags, strings.TrimPrefix(f[1], "refs/tags/"))
}
}
return tags, nil
}

View file

@ -0,0 +1,81 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"github.com/mholt/archiver"
)
// GoogleAPIRelease defines the release of google api
type GoogleAPIRelease struct {
workspace string
commit string
}
// GetDir is used to get the dir of google api
func (p *GoogleAPIRelease) GetDir() string {
return filepath.Join(p.workspace, "googleapis-"+p.commit)
}
// Clear is used to clear the workspace
func (p *GoogleAPIRelease) Clear() error {
return os.RemoveAll(p.workspace)
}
// GetGoogleAPIRelease is used to get the release of google api
func GetGoogleAPIRelease(ctx context.Context, commitId string) (*GoogleAPIRelease, error) {
filename := fmt.Sprintf("%s.zip", commitId)
uri := fmt.Sprintf("https://github.com/googleapis/googleapis/archive/%s", filename)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, &ErrHTTPDownload{
Url: uri,
Err: err,
}
}
workspace, err := os.MkdirTemp("", "")
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, &ErrHTTPDownload{
Url: uri,
Err: err,
}
}
zipFilePath := filepath.Join(workspace, filename)
if err := downloadFile(resp, zipFilePath); err != nil {
return nil, &ErrHTTPDownload{
Url: uri,
Err: err,
Code: resp.StatusCode,
}
}
zip := archiver.NewZip()
if err := zip.Unarchive(zipFilePath, workspace); err != nil {
return nil, err
}
return &GoogleAPIRelease{
commit: commitId,
workspace: workspace,
}, nil
}

View file

@ -41,7 +41,17 @@ type PluginManager interface {
// InstallPlugin is used to install plugin // InstallPlugin is used to install plugin
InstallPlugin(ctx context.Context, path string, version string) (local string, err error) InstallPlugin(ctx context.Context, path string, version string) (local string, err error)
// GetProtocLatestVersion is used to geet the latest version of protoc // GetGoogleAPIsLatestVersion is used to get the latest version of google apis
GetGoogleAPIsLatestVersion(ctx context.Context) (string, error)
// InstallGoogleAPIs is used to install google apis
InstallGoogleAPIs(ctx context.Context, commitId string) (local string, err error)
// ListGoogleAPIsVersions is used to list protoc version
ListGoogleAPIsVersions(ctx context.Context) ([]string, error)
// IsGoogleAPIsInstalled is used to check whether the protoc is installed
IsGoogleAPIsInstalled(ctx context.Context, commitId string) (bool, string, error)
// GoogleAPIsPath returns the googleapis path
GoogleAPIsPath(ctx context.Context, commitId string) string
// GetProtocLatestVersion is used to get the latest version of protoc
GetProtocLatestVersion(ctx context.Context) (string, error) GetProtocLatestVersion(ctx context.Context) (string, error)
// ListProtocVersions is used to list protoc version // ListProtocVersions is used to list protoc version
ListProtocVersions(ctx context.Context) ([]string, error) ListProtocVersions(ctx context.Context) ([]string, error)
@ -130,6 +140,82 @@ func (b *BasicPluginManager) InstallPlugin(ctx context.Context, path string, ver
return InstallPluginUsingGo(ctx, b.Logger, b.storageDir, path, version) return InstallPluginUsingGo(ctx, b.Logger, b.storageDir, path, version)
} }
// GetGoogleAPIsLatestVersion is used to get the latest version of google apis
func (b *BasicPluginManager) GetGoogleAPIsLatestVersion(ctx context.Context) (string, error) {
versions, err := b.ListGoogleAPIsVersions(ctx)
if err != nil {
return "", err
}
if len(versions) == 0 {
return "", errors.New("no version list")
}
return versions[len(versions)-1], nil
}
// InstallGoogleAPIs is used to install google apis
func (b *BasicPluginManager) InstallGoogleAPIs(ctx context.Context, commitId string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
defer cancel()
local := PathForGoogleAPIs(b.storageDir, commitId)
exists, err := util.IsDirExists(local)
if err != nil {
return "", err
}
if exists {
return local, nil
}
release, err := GetGoogleAPIRelease(ctx, commitId)
if err != nil {
return "", err
}
defer release.Clear()
if err := util.CopyDirectory(release.GetDir(), local); err != nil {
return "", err
}
return local, nil
}
// ListGoogleAPIsVersions is used to list protoc version
func (b *BasicPluginManager) ListGoogleAPIsVersions(ctx context.Context) ([]string, error) {
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
defer cancel()
b.versionsLock.RLock()
versions, ok := b.versions["googleapis"]
b.versionsLock.RUnlock()
if ok {
return versions, nil
}
versions, err := ListGitCommitIds(ctx, b.Logger, consts.GoogleAPIsRepository)
if err != nil {
return nil, err
}
b.versionsLock.Lock()
b.versions["googleapis"] = versions
b.versionsLock.Unlock()
return versions, nil
}
// IsGoogleAPIsInstalled is used to check whether the protoc is installed
func (b *BasicPluginManager) IsGoogleAPIsInstalled(ctx context.Context, commitId string) (bool, string, error) {
local := PathForGoogleAPIs(b.storageDir, commitId)
exists, err := util.IsDirExists(local)
return exists, local, err
}
// GoogleAPIsPath returns the googleapis path
func (b *BasicPluginManager) GoogleAPIsPath(ctx context.Context, commitId string) string {
return PathForGoogleAPIs(b.storageDir, commitId)
}
// IsProtocInstalled is used to check whether the protoc is installed
func (b *BasicPluginManager) IsProtocInstalled(ctx context.Context, version string) (bool, string, error) {
if strings.HasPrefix(version, "v") {
version = strings.TrimPrefix(version, "v")
}
return IsProtocInstalled(ctx, b.storageDir, version)
}
// GetProtocLatestVersion is used to geet the latest version of protoc // GetProtocLatestVersion is used to geet the latest version of protoc
func (b *BasicPluginManager) GetProtocLatestVersion(ctx context.Context) (string, error) { func (b *BasicPluginManager) GetProtocLatestVersion(ctx context.Context) (string, error) {
versions, err := b.ListProtocVersions(ctx) versions, err := b.ListProtocVersions(ctx)
@ -163,14 +249,6 @@ func (b *BasicPluginManager) ListProtocVersions(ctx context.Context) ([]string,
return versions, nil return versions, nil
} }
// IsProtocInstalled is used to check whether the protoc is installed
func (b *BasicPluginManager) IsProtocInstalled(ctx context.Context, version string) (bool, string, error) {
if strings.HasPrefix(version, "v") {
version = strings.TrimPrefix(version, "v")
}
return IsProtocInstalled(ctx, b.storageDir, version)
}
// InstallProtoc is used to install protoc of specified version // InstallProtoc is used to install protoc of specified version
func (b *BasicPluginManager) InstallProtoc(ctx context.Context, version string) (string, error) { func (b *BasicPluginManager) InstallProtoc(ctx context.Context, version string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout) ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)

View file

@ -23,7 +23,6 @@ import (
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
) )
// PathForInclude is used to get the local directory of include files // PathForInclude is used to get the local directory of include files
func PathForInclude(storageDir string) string { func PathForInclude(storageDir string) string {
return filepath.Join(storageDir, "include") return filepath.Join(storageDir, "include")
@ -47,6 +46,11 @@ func GetPluginPath(path string, version string) (string, error) {
return filepath.Join(enc + "@" + encVer), nil return filepath.Join(enc + "@" + encVer), nil
} }
// PathForGoogleAPIs is used to get the google apis path
func PathForGoogleAPIs(storageDir string, commitId string) string {
return filepath.Join(storageDir, "googleapis", commitId)
}
// PathForPluginDir is used to get the local directory where the specified version plug-in should be stored // PathForPluginDir is used to get the local directory where the specified version plug-in should be stored
func PathForPluginDir(storageDir string, path string, version string) (string, error) { func PathForPluginDir(storageDir string, path string, version string) (string, error) {
pluginPath, err := GetPluginPath(path, version) pluginPath, err := GetPluginPath(path, version)

View file

@ -70,4 +70,22 @@ var _ = Describe("Pluginmanager", func() {
Expect(exists).To(BeTrue()) Expect(exists).To(BeTrue())
Expect(len(local) != 0).To(BeTrue()) Expect(len(local) != 0).To(BeTrue())
}) })
It("should able to install googleapis", func() {
versions, err := manager.ListGoogleAPIsVersions(context.TODO())
Expect(err).To(BeNil())
Expect(len(versions) > 0).To(BeTrue())
latestVersion, err := manager.GetGoogleAPIsLatestVersion(context.TODO())
Expect(err).To(BeNil())
Expect(latestVersion).To(Equal(versions[len(versions)-1]))
local, err := manager.InstallGoogleAPIs(context.TODO(), latestVersion)
Expect(err).To(BeNil())
Expect(len(local) > 0).To(BeTrue())
exists, local, err := manager.IsGoogleAPIsInstalled(context.TODO(), latestVersion)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
Expect(len(local) != 0).To(BeTrue())
})
}) })

View file

@ -29,8 +29,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
) )
// ProtocRelease defines the release of protoc // ProtocRelease defines the release of protoc
@ -107,34 +105,6 @@ func IsProtocInstalled(ctx context.Context, storageDir string, version string) (
return exists, local, nil return exists, local, nil
} }
// ErrGitList defines the git list error
type ErrGitList struct {
*command.ErrCommandExec
}
// ListGitTags is used to list the git tags of specified repository
func ListGitTags(ctx context.Context, log logger.Logger, repo string) ([]string, error) {
data, err := command.Execute(ctx, log, "", "git", []string{
"ls-remote", "--tags", "--refs", "--sort", "version:refname", repo,
}, nil)
if err != nil {
return nil, &ErrGitList{
ErrCommandExec: err,
}
}
var tags []string
for _, line := range strings.Split(string(data), "\n") {
f := strings.Fields(line)
if len(f) != 2 {
continue
}
if strings.HasPrefix(f[1], "refs/tags/") {
tags = append(tags, strings.TrimPrefix(f[1], "refs/tags/"))
}
}
return tags, nil
}
func inferProtocReleaseSuffix() (string, error) { func inferProtocReleaseSuffix() (string, error) {
goos := strings.ToLower(runtime.GOOS) goos := strings.ToLower(runtime.GOOS)
arch := strings.ToLower(runtime.GOARCH) arch := strings.ToLower(runtime.GOARCH)

View file

@ -29,6 +29,7 @@ import (
// Config defines the config model // Config defines the config model
type Config struct { type Config struct {
Scopes []string `json:"scopes" yaml:"scopes"` Scopes []string `json:"scopes" yaml:"scopes"`
GoogleAPIs string `json:"googleapis" yaml:"googleapis"`
Protoc string `json:"protoc" yaml:"protoc"` Protoc string `json:"protoc" yaml:"protoc"`
ProtocWorkDir string `json:"protocWorkDir" yaml:"protocWorkDir"` ProtocWorkDir string `json:"protocWorkDir" yaml:"protocWorkDir"`
Plugins map[string]string `json:"plugins" yaml:"plugins"` Plugins map[string]string `json:"plugins" yaml:"plugins"`

View file

@ -18,20 +18,35 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/fatih/color"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
// defines a set of const value // defines a set of const value
const ( const (
// ConfigFileName defines the config file name // ConfigFileName defines the config file name
ConfigFileName = "powerproto.yaml" ConfigFileName = "powerproto.yaml"
// The default include can be referenced by this key in import paths // The default include can be referenced by this key in import paths
KeyPowerProtoInclude = "$POWERPROTO_INCLUDE" KeyPowerProtoInclude = "$POWERPROTO_INCLUDE"
// The googleapis can be referenced by this key in import paths
KeyPowerProtoGoogleAPIs = "$POWERPROTO_GOOGLEAPIS"
// KeySourceRelative can be specified in import paths to refer to
// the folder where the current proto file is located
KeySourceRelative = "$SOURCE_RELATIVE"
// Defines the program directory of PowerProto, including various binary and include files // Defines the program directory of PowerProto, including various binary and include files
EnvHomeDir = "POWERPROTO_HOME" EnvHomeDir = "POWERPROTO_HOME"
// ProtobufRepository defines the protobuf repository // ProtobufRepository defines the protobuf repository
ProtobufRepository = "https://github.com/protocolbuffers/protobuf" ProtobufRepository = "https://github.com/protocolbuffers/protobuf"
// GoogleAPIsRepository defines the google apis repository
GoogleAPIsRepository = "https://github.com/googleapis/googleapis"
)
// defines a set of text style
var (
TextExecuteAction = color.HiGreenString("EXECUTE ACTION")
TextExecuteCommand = color.HiGreenString("EXECUTE COMMAND")
TextDryRun = color.HiGreenString("DRY RUN")
) )
var homeDir string var homeDir string

65
pkg/consts/context.go Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package consts
import (
"context"
)
type debugMode struct{}
type dryRun struct{}
type ignoreDryRun struct{}
type disableAction struct{}
// WithDebugMode is used to set debug mode
func WithDebugMode(ctx context.Context) context.Context {
return context.WithValue(ctx, debugMode{}, "true")
}
// WithIgnoreDryRun is used to inject ignore dryRun flag into context
func WithIgnoreDryRun(ctx context.Context) context.Context {
return context.WithValue(ctx, ignoreDryRun{}, "true")
}
// WithDisableAction is used to disable post action/shell
func WithDisableAction(ctx context.Context) context.Context {
return context.WithValue(ctx, disableAction{}, "true")
}
// WithDryRun is used to inject dryRun flag into context
func WithDryRun(ctx context.Context) context.Context {
ctx = WithDebugMode(ctx)
return context.WithValue(ctx, dryRun{}, "true")
}
// IsDebugMode is used to decide whether to disable PostAction and PostShell
func IsDebugMode(ctx context.Context) bool {
return ctx.Value(debugMode{}) != nil
}
// IsDisableAction is used to decide whether to disable PostAction and PostShell
func IsDisableAction(ctx context.Context) bool {
return ctx.Value(disableAction{}) != nil
}
// IsIgnoreDryRun is used to determine whether it is currently in ignore DryRun mode
func IsIgnoreDryRun(ctx context.Context) bool {
return ctx.Value(ignoreDryRun{}) != nil
}
// IsDryRun is used to determine whether it is currently in DryRun mode
func IsDryRun(ctx context.Context) bool {
return ctx.Value(dryRun{}) != nil
}

View file

@ -21,44 +21,11 @@ import (
"os" "os"
"os/exec" "os/exec"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/logger"
) )
type dryRun struct{}
type ignoreDryRun struct{}
type disableAction struct{}
// WithIgnoreDryRun is used to inject ignore dryRun flag into context
func WithIgnoreDryRun(ctx context.Context) context.Context {
return context.WithValue(ctx, ignoreDryRun{}, "true")
}
// WithDisableAction is used to disable post action/shell
func WithDisableAction(ctx context.Context) context.Context {
return context.WithValue(ctx, disableAction{}, "true")
}
// WithDryRun is used to inject dryRun flag into context
func WithDryRun(ctx context.Context) context.Context {
return context.WithValue(ctx, dryRun{}, "true")
}
// IsDisableAction is used to decide whether to disable PostAction and PostShell
func IsDisableAction(ctx context.Context) bool {
return ctx.Value(disableAction{}) != nil
}
// IsIgnoreDryRun is used to determine whether it is currently in ignore DryRun mode
func IsIgnoreDryRun(ctx context.Context) bool {
return ctx.Value(ignoreDryRun{}) != nil
}
// IsDryRun is used to determine whether it is currently in DryRun mode
func IsDryRun(ctx context.Context) bool {
return ctx.Value(dryRun{}) != nil
}
// Execute is used to execute commands, return stdout and execute errors // Execute is used to execute commands, return stdout and execute errors
func Execute(ctx context.Context, func Execute(ctx context.Context,
log logger.Logger, log logger.Logger,
@ -67,17 +34,21 @@ func Execute(ctx context.Context,
cmd.Env = append(os.Environ(), env...) cmd.Env = append(os.Environ(), env...)
cmd.Dir = dir cmd.Dir = dir
if IsDryRun(ctx) && !IsIgnoreDryRun(ctx) { if consts.IsDryRun(ctx) && !consts.IsIgnoreDryRun(ctx) {
log.LogInfo(map[string]interface{}{ log.LogInfo(map[string]interface{}{
"command": cmd.String(), "command": cmd.String(),
"dir": cmd.Dir, "dir": cmd.Dir,
}, "DryRun") }, consts.TextDryRun)
return nil, nil return nil, nil
} }
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
log.LogDebug(map[string]interface{}{
"command": cmd.String(),
"dir": cmd.Dir,
}, consts.TextExecuteCommand)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return nil, &ErrCommandExec{ return nil, &ErrCommandExec{
Err: err, Err: err,

View file

@ -15,11 +15,15 @@
package progressbar package progressbar
import ( import (
"context"
"fmt" "fmt"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/vbauerster/mpb/v7" "github.com/vbauerster/mpb/v7"
"github.com/vbauerster/mpb/v7/decor" "github.com/vbauerster/mpb/v7/decor"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util/logger"
) )
// ProgressBar implements a customizable progress bar // ProgressBar implements a customizable progress bar
@ -58,10 +62,12 @@ func newEmbedProgressBar(container *mpb.Progress, bar *mpb.Bar) *progressBar {
} }
} }
// Incr is used to increase progress
func (s *progressBar) Incr() { func (s *progressBar) Incr() {
s.bar.Increment() s.bar.Increment()
} }
// Wait is used to wait for the rendering of the progress bar to complete
func (s *progressBar) Wait() { func (s *progressBar) Wait() {
s.container.Wait() s.container.Wait()
} }
@ -79,8 +85,50 @@ func getSpinner() []string {
} }
} }
type fakeProgressbar struct {
prefix string
suffix string
total int
current int
logger.Logger
}
func (f *fakeProgressbar) Incr() {
if f.current < f.total {
f.current++
}
}
// Wait is used to wait for the rendering of the progress bar to complete
func (f *fakeProgressbar) Wait() {}
// SetSuffix is used to set the prefix of progress bar
func (f *fakeProgressbar) SetPrefix(format string, args ...interface{}) {
f.prefix = fmt.Sprintf(format, args...)
}
// SetSuffix is used to set the suffix of progress bar
func (f *fakeProgressbar) SetSuffix(format string, args ...interface{}) {
f.suffix = fmt.Sprintf(format, args...)
f.LogInfo(map[string]interface{}{
"progress": fmt.Sprintf("%3.f", float64(f.current)/float64(f.total)*100),
"stage": f.prefix,
}, f.suffix)
}
func newFakeProgressbar(total int) ProgressBar {
return &fakeProgressbar{
total: total,
Logger: logger.NewDefault("progress"),
}
}
// GetProgressBar is used to get progress bar // GetProgressBar is used to get progress bar
func GetProgressBar(count int) ProgressBar { func GetProgressBar(ctx context.Context, count int) ProgressBar {
if consts.IsDebugMode(ctx) {
return newFakeProgressbar(count)
}
var progressBar *progressBar var progressBar *progressBar
container := mpb.New() container := mpb.New()
bar := container.Add(int64(count), bar := container.Add(int64(count),

View file

@ -26,8 +26,13 @@ import (
// ContainsEmpty is used to check whether items contains empty string // ContainsEmpty is used to check whether items contains empty string
func ContainsEmpty(items ...string) bool { func ContainsEmpty(items ...string) bool {
return Contains(items, "")
}
// Contains is used to check whether the target is in items
func Contains(items []string, target string) bool {
for _, item := range items { for _, item := range items {
if item == "" { if item == target {
return true return true
} }
} }