From da77c8086d453864201fc7280c086efee4d6eb48 Mon Sep 17 00:00:00 2001 From: storyicon Date: Wed, 21 Jul 2021 20:25:38 +0800 Subject: [PATCH] feat(*): dryRun/debug mode/mode plugins/googleapis Signed-off-by: storyicon --- README.md | 37 ++++++- README_CN.md | 31 ++++-- cmd/powerproto/powerproto.yaml | 33 +++++++ cmd/powerproto/subcommands/build/command.go | 15 ++- cmd/powerproto/subcommands/init/command.go | 7 +- cmd/powerproto/subcommands/init/plugins.go | 21 ++++ cmd/powerproto/subcommands/tidy/command.go | 20 +++- pkg/bootstraps/build.go | 79 +++++++++++++-- pkg/bootstraps/tidy.go | 6 +- pkg/component/actionmanager/actions/copy.go | 11 ++- pkg/component/actionmanager/actions/move.go | 12 ++- pkg/component/actionmanager/actions/remove.go | 11 ++- .../actionmanager/actions/replace.go | 12 ++- pkg/component/compilermanager/compiler.go | 54 +++++++++-- pkg/component/pluginmanager/git.go | 91 ++++++++++++++++++ pkg/component/pluginmanager/googleapis.go | 81 ++++++++++++++++ pkg/component/pluginmanager/manager.go | 96 +++++++++++++++++-- pkg/component/pluginmanager/paths.go | 6 +- .../pluginmanager/pluginmanager_test.go | 18 ++++ pkg/component/pluginmanager/protoc.go | 30 ------ pkg/configs/configs.go | 1 + pkg/consts/consts.go | 17 +++- pkg/consts/context.go | 65 +++++++++++++ pkg/util/command/command.go | 43 ++------- pkg/util/progressbar/progressbar.go | 50 +++++++++- pkg/util/util.go | 9 +- 26 files changed, 730 insertions(+), 126 deletions(-) create mode 100644 cmd/powerproto/powerproto.yaml create mode 100644 pkg/component/pluginmanager/git.go create mode 100644 pkg/component/pluginmanager/googleapis.go create mode 100644 pkg/consts/context.go diff --git a/README.md b/README.md index fba39b4..075ed04 100644 --- a/README.md +++ b/README.md @@ -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. 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. +8. support `google apis` one-click installation and version control。 ## Installation and Dependencies @@ -110,6 +111,9 @@ Tidy the config consists of two main operations: 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 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. + +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 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. # support mixed environment variables in path, such as $GOPATH 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 plugins: # the name, path, and version number of the plugin. @@ -244,13 +267,19 @@ options: - --grpc-gateway_out=. - --go-grpc_out=paths=source_relative:. # 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: + # 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 + # Special variables. Will be replaced with the local path to the public proto file that comes with protoc by default - $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. # its working directory is the directory where the config file is located. # postActions is cross-platform compatible. @@ -281,6 +310,7 @@ scopes: - ./apis1 protoc: v3.17.3 protocWorkDir: "" +googleapis: 75e9812478607db997376ccea247dd6928f70f45 plugins: 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 @@ -302,6 +332,7 @@ scopes: - ./apis2 protoc: v3.17.3 protocWorkDir: "" +googleapis: 75e9812478607db997376ccea247dd6928f70f45 plugins: 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 diff --git a/README_CN.md b/README_CN.md index f9f8892..3362322 100644 --- a/README_CN.md +++ b/README_CN.md @@ -22,6 +22,7 @@ PowerProto主要用于解决下面三个问题: - [一、初始化配置](#一初始化配置) - [二、整理配置](#二整理配置) - [三、编译Proto文件](#三编译proto文件) + - [四、查看环境变量](#四查看环境变量) - [示例](#示例) - [配置文件](#配置文件) - [解释](#解释) @@ -43,6 +44,7 @@ PowerProto主要用于解决下面三个问题: 5. 支持批量、递归编译proto文件,提高效率。 6. 跨平台支持PostAction,可以在编译完成之后执行一些常规操作(比如替换掉所有生成文件中的"omitempty")。 7. 支持PostShell,在编译完成之后执行特定的shell脚本。 +8. 支持 `google api` 的一键安装与版本控制。 ## 安装与依赖 @@ -87,8 +89,6 @@ powerproto build -h powerproto init ``` - - ### 二、整理配置 可以通过下面的命令整理配置: @@ -110,7 +110,7 @@ powerproto tidy [the path of proto file] 1. 通过查询,将版本中的latest替换为真实的最新版本号。 2. 安装配置文件中定义的所有依赖。 - +支持通过 `-d` 参数来进入到`debug模式`,查看更详细的日志。 ### 三、编译Proto文件 @@ -135,7 +135,16 @@ powerproto build -r . 注意:`protoc`执行的工作目录默认是`proto文件`匹配到的配置文件所在的目录,它相当于你在配置文件所在目录执行protoc命令。你可以通过配置文件中的 `protocWorkDir` 来进行修改。 +支持通过 `-d` 参数来进入到`debug模式`,查看更详细的日志。 +支持通过 `-y` 参数来进入到`dryRun模式`,只打印命令而不真正执行,这对于调试非常有用。 +### 四、查看环境变量 + +如果你的命令一直卡在某个状态,大概率是出现网络问题了。 +你可以通过下面的命令来查看环境变量是否配置成功: +``` +powerproto env +``` ## 示例 @@ -229,6 +238,9 @@ protoc: 3.17.3 # 选填,执行protoc命令的工作目录,默认是配置文件所在目录 # 支持路径中混用环境变量,比如$GOPATH protocWorkDir: "" +# 选填,如果需要使用 googleapis,你应该在这里填写googleapis的commit id +# 可以填 latest,会自动转换成最新的版本 +googleapis: 75e9812478607db997376ccea247dd6928f70f45 # 必填,代表scope匹配的目录中的proto文件,在编译时需要用到哪些插件 plugins: # 插件的名字、路径以及版本号。 @@ -245,13 +257,18 @@ options: - --grpc-gateway_out=. - --go-grpc_out=paths=source_relative:. # 必填,定义了构建时 protoc 的引用路径,会被转换为 --proto_path (-I) 参数。 -# 支持混用环境变量,比如 $GOPATH/include。 -# $POWERPROTO_INCLUDE是一个特殊变量,指 $POWERPRTO/include,里面包含了protoc默认提供的公共proto 文件。 -# "." 是指配置文件所在文件夹。 importPaths: + # 特殊变量。代表当前配置文件所在文件夹 - . + # 环境变量。可以使用环境变量 + # 也支持 $GOPATH/include 这样的混合写法 - $GOPATH - $POWERPROTO_INCLUDE + # 特殊变量。引用待编译的proto文件所在的目录 + # 比如将要编译 /a/b/data.proto,那么 /a/b 目录将会被自动引用 + - $SOURCE_RELATIVE + # 特殊变量。引用googleapis字段所指定的版本的google apis + - $POWERPROTO_GOOGLEAPIS # 选填,构建完成之后执行的操作,工作目录是配置文件所在目录 # postActions是跨平台兼容的 # 注意,必须在 powerproto build 时附加 -p 参数,才会执行配置文件中的postActions @@ -281,6 +298,7 @@ scopes: - ./apis1 protoc: v3.17.3 protocWorkDir: "" +googleapis: 75e9812478607db997376ccea247dd6928f70f45 plugins: 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 @@ -302,6 +320,7 @@ scopes: - ./apis2 protoc: v3.17.3 protocWorkDir: "" +googleapis: 75e9812478607db997376ccea247dd6928f70f45 plugins: 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 diff --git a/cmd/powerproto/powerproto.yaml b/cmd/powerproto/powerproto.yaml new file mode 100644 index 0000000..ab98df4 --- /dev/null +++ b/cmd/powerproto/powerproto.yaml @@ -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: "" diff --git a/cmd/powerproto/subcommands/build/command.go b/cmd/powerproto/subcommands/build/command.go index 05035a2..eed06b1 100644 --- a/cmd/powerproto/subcommands/build/command.go +++ b/cmd/powerproto/subcommands/build/command.go @@ -22,8 +22,8 @@ import ( "github.com/spf13/cobra" "github.com/storyicon/powerproto/pkg/bootstraps" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" - "github.com/storyicon/powerproto/pkg/util/command" "github.com/storyicon/powerproto/pkg/util/logger" ) @@ -48,8 +48,8 @@ compile proto files and execute the post actions/shells: // powerproto build xxxxx.proto func CommandBuild(log logger.Logger) *cobra.Command { var recursive bool - // todo: this feature is still under development var dryRun bool + var debugMode bool var postScriptEnabled bool cmd := &cobra.Command{ Use: "build [dir|proto file]", @@ -59,10 +59,15 @@ func CommandBuild(log logger.Logger) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() if dryRun { - ctx = command.WithDryRun(ctx) + ctx = consts.WithDryRun(ctx) + log.LogWarn(nil, "running in dryRun mode") } 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]) @@ -113,5 +118,7 @@ func CommandBuild(log logger.Logger) *cobra.Command { flags := cmd.PersistentFlags() 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(&debugMode, "debug", "d", debugMode, "debug mode") + flags.BoolVarP(&dryRun, "dryRun", "y", dryRun, "dryRun mode") return cmd } diff --git a/cmd/powerproto/subcommands/init/command.go b/cmd/powerproto/subcommands/init/command.go index b145760..b7d620e 100644 --- a/cmd/powerproto/subcommands/init/command.go +++ b/cmd/powerproto/subcommands/init/command.go @@ -50,7 +50,8 @@ func GetDefaultConfig() *configs.Config { Scopes: []string{ "./", }, - Protoc: "latest", + Protoc: "latest", + GoogleAPIs: "75e9812478607db997376ccea247dd6928f70f45", Plugins: map[string]string{ "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", @@ -64,7 +65,9 @@ func GetDefaultConfig() *configs.Config { ImportPaths: []string{ ".", "$GOPATH", - "$POWERPROTO_INCLUDE", + consts.KeyPowerProtoInclude, + consts.KeyPowerProtoGoogleAPIs, + consts.KeySourceRelative, }, } } diff --git a/cmd/powerproto/subcommands/init/plugins.go b/cmd/powerproto/subcommands/init/plugins.go index af610a5..e5c9d99 100644 --- a/cmd/powerproto/subcommands/init/plugins.go +++ b/cmd/powerproto/subcommands/init/plugins.go @@ -56,6 +56,27 @@ func GetWellKnownPlugins() []*Plugin { "--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", Pkg: "istio.io/tools/cmd/protoc-gen-deepcopy@latest", diff --git a/cmd/powerproto/subcommands/tidy/command.go b/cmd/powerproto/subcommands/tidy/command.go index b746e89..7127194 100644 --- a/cmd/powerproto/subcommands/tidy/command.go +++ b/cmd/powerproto/subcommands/tidy/command.go @@ -24,6 +24,7 @@ import ( "github.com/storyicon/powerproto/pkg/bootstraps" "github.com/storyicon/powerproto/pkg/component/pluginmanager" "github.com/storyicon/powerproto/pkg/configs" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/progressbar" @@ -32,7 +33,7 @@ import ( func tidy(ctx context.Context, pluginManager pluginmanager.PluginManager, configFilePath string) error { - progress := progressbar.GetProgressBar(1) + progress := progressbar.GetProgressBar(ctx, 1) progress.SetPrefix("tidy config") err := bootstraps.StepTidyConfigFile(ctx, pluginManager, progress, configFilePath) if err != nil { @@ -47,6 +48,9 @@ func tidy(ctx context.Context, if err := bootstraps.StepInstallProtoc(ctx, pluginManager, configItems); err != nil { return err } + if err := bootstraps.StepInstallGoogleAPIs(ctx, pluginManager, configItems); err != nil { + return err + } if err := bootstraps.StepInstallPlugins(ctx, pluginManager, configItems); err != nil { 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 // You can also explicitly specify the configuration file to clean up func CommandTidy(log logger.Logger) *cobra.Command { - return &cobra.Command{ + var debugMode bool + cmd := &cobra.Command{ 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", 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 if len(args) != 0 { for _, arg := range args { @@ -90,7 +101,7 @@ func CommandTidy(log logger.Logger) *cobra.Command { continue } 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{}{ "path": path, "err": err, @@ -105,4 +116,7 @@ func CommandTidy(log logger.Logger) *cobra.Command { log.LogInfo(nil, "\r\nsucceeded, you are ready to go :)") }, } + flags := cmd.PersistentFlags() + flags.BoolVarP(&debugMode, "debug", "d", debugMode, "debug mode") + return cmd } diff --git a/pkg/bootstraps/build.go b/pkg/bootstraps/build.go index ad8dc11..d45c031 100644 --- a/pkg/bootstraps/build.go +++ b/pkg/bootstraps/build.go @@ -25,8 +25,8 @@ import ( "github.com/storyicon/powerproto/pkg/component/configmanager" "github.com/storyicon/powerproto/pkg/component/pluginmanager" "github.com/storyicon/powerproto/pkg/configs" + "github.com/storyicon/powerproto/pkg/consts" "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/logger" "github.com/storyicon/powerproto/pkg/util/progressbar" @@ -38,7 +38,7 @@ func StepLookUpConfigs( targets []string, configManager configmanager.ConfigManager, ) ([]configs.ConfigItem, error) { - progress := progressbar.GetProgressBar(len(targets)) + progress := progressbar.GetProgressBar(ctx, len(targets)) progress.SetPrefix("Lookup configs of proto files") var configItems []configs.ConfigItem deduplicate := map[string]struct{}{} @@ -67,6 +67,57 @@ func StepLookUpConfigs( 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 func StepInstallProtoc(ctx context.Context, pluginManager pluginmanager.PluginManager, @@ -74,9 +125,12 @@ func StepInstallProtoc(ctx context.Context, deduplicate := map[string]struct{}{} for _, config := range configItems { version := config.Config().Protoc + if version == "" { + return errors.Errorf("protoc version is required: %s", config.Path()) + } deduplicate[version] = struct{}{} } - progress := progressbar.GetProgressBar(len(deduplicate)) + progress := progressbar.GetProgressBar(ctx, len(deduplicate)) progress.SetPrefix("Install protoc") versionsMap := map[string]struct{}{} @@ -123,7 +177,7 @@ func StepInstallPlugins(ctx context.Context, deduplicate[pkg] = struct{}{} } } - progress := progressbar.GetProgressBar(len(deduplicate)) + progress := progressbar.GetProgressBar(ctx, len(deduplicate)) progress.SetPrefix("Install plugins") pluginsMap := map[string]struct{}{} for pkg := range deduplicate { @@ -173,7 +227,7 @@ func StepCompile(ctx context.Context, compilerManager compilermanager.CompilerManager, targets []string, ) error { - progress := progressbar.GetProgressBar(len(targets)) + progress := progressbar.GetProgressBar(ctx, len(targets)) progress.SetPrefix("Compile Proto Files") c := concurrent.NewErrGroup(ctx, 10) for _, target := range targets { @@ -203,7 +257,7 @@ func StepCompile(ctx context.Context, func StepPostAction(ctx context.Context, actionsManager actionmanager.ActionManager, configItems []configs.ConfigItem) error { - progress := progressbar.GetProgressBar(len(configItems)) + progress := progressbar.GetProgressBar(ctx, len(configItems)) progress.SetPrefix("PostAction") for _, cfg := range configItems { progress.SetSuffix(cfg.Path()) @@ -220,7 +274,7 @@ func StepPostAction(ctx context.Context, func StepPostShell(ctx context.Context, actionsManager actionmanager.ActionManager, configItems []configs.ConfigItem) error { - progress := progressbar.GetProgressBar(len(configItems)) + progress := progressbar.GetProgressBar(ctx, len(configItems)) progress.SetPrefix("PostShell") for _, cfg := range configItems { progress.SetSuffix(cfg.Path()) @@ -237,6 +291,9 @@ func StepPostShell(ctx context.Context, func Compile(ctx context.Context, targets []string) error { log := logger.NewDefault("compile") log.SetLogLevel(logger.LevelError) + if consts.IsDebugMode(ctx) { + log.SetLogLevel(logger.LevelDebug) + } configManager, err := configmanager.NewConfigManager(log) if err != nil { @@ -259,9 +316,13 @@ func Compile(ctx context.Context, targets []string) error { if err != nil { return err } + if err := StepInstallProtoc(ctx, pluginManager, configItems); err != nil { return err } + if err := StepInstallGoogleAPIs(ctx, pluginManager, configItems); err != nil { + return err + } if err := StepInstallPlugins(ctx, pluginManager, configItems); err != nil { return err } @@ -269,7 +330,7 @@ func Compile(ctx context.Context, targets []string) error { return err } - if !command.IsDisableAction(ctx) { + if !consts.IsDisableAction(ctx) { if err := StepPostAction(ctx, actionManager, configItems); err != nil { return err } @@ -277,7 +338,7 @@ func Compile(ctx context.Context, targets []string) error { return err } } 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 :)") diff --git a/pkg/bootstraps/tidy.go b/pkg/bootstraps/tidy.go index e435100..c3631b6 100644 --- a/pkg/bootstraps/tidy.go +++ b/pkg/bootstraps/tidy.go @@ -22,6 +22,7 @@ import ( "github.com/storyicon/powerproto/pkg/component/configmanager" "github.com/storyicon/powerproto/pkg/component/pluginmanager" "github.com/storyicon/powerproto/pkg/configs" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" "github.com/storyicon/powerproto/pkg/util/logger" "github.com/storyicon/powerproto/pkg/util/progressbar" @@ -31,6 +32,9 @@ import ( func StepTidyConfig(ctx context.Context, targets []string) error { log := logger.NewDefault("tidy") log.SetLogLevel(logger.LevelError) + if consts.IsDebugMode(ctx) { + log.SetLogLevel(logger.LevelDebug) + } configManager, err := configmanager.NewConfigManager(log) if err != nil { @@ -53,7 +57,7 @@ func StepTidyConfig(ctx context.Context, targets []string) error { return nil } - progress := progressbar.GetProgressBar(len(configPaths)) + progress := progressbar.GetProgressBar(ctx, len(configPaths)) progress.SetPrefix("tidy configs") for path := range configPaths { progress.SetSuffix(path) diff --git a/pkg/component/actionmanager/actions/copy.go b/pkg/component/actionmanager/actions/copy.go index 9df8b97..152db5d 100644 --- a/pkg/component/actionmanager/actions/copy.go +++ b/pkg/component/actionmanager/actions/copy.go @@ -20,8 +20,8 @@ import ( "github.com/pkg/errors" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" - "github.com/storyicon/powerproto/pkg/util/command" "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) } - if command.IsDryRun(ctx) { + if consts.IsDryRun(ctx) { log.LogInfo(map[string]interface{}{ "action": "copy", "from": absSource, "to": absDestination, - }, "DryRun") + }, consts.TextDryRun) return nil } + log.LogDebug(map[string]interface{}{ + "action": "copy", + "from": absSource, + "to": absDestination, + }, consts.TextExecuteAction) if err := util.CopyDirectory(absSource, absDestination); err != nil { return err } diff --git a/pkg/component/actionmanager/actions/move.go b/pkg/component/actionmanager/actions/move.go index c666752..06fae63 100644 --- a/pkg/component/actionmanager/actions/move.go +++ b/pkg/component/actionmanager/actions/move.go @@ -21,8 +21,8 @@ import ( "github.com/pkg/errors" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" - "github.com/storyicon/powerproto/pkg/util/command" "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) } - if command.IsDryRun(ctx) { + if consts.IsDryRun(ctx) { log.LogInfo(map[string]interface{}{ "action": "move", "from": absSource, "to": absDestination, - }, "DryRun") + }, consts.TextDryRun) return nil } + log.LogDebug(map[string]interface{}{ + "action": "move", + "from": absSource, + "to": absDestination, + }, consts.TextExecuteAction) + if err := util.CopyDirectory(absSource, absDestination); err != nil { return err } diff --git a/pkg/component/actionmanager/actions/remove.go b/pkg/component/actionmanager/actions/remove.go index fde4b20..7f7886d 100644 --- a/pkg/component/actionmanager/actions/remove.go +++ b/pkg/component/actionmanager/actions/remove.go @@ -21,7 +21,7 @@ import ( "github.com/pkg/errors" - "github.com/storyicon/powerproto/pkg/util/command" + "github.com/storyicon/powerproto/pkg/consts" "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) - if command.IsDryRun(ctx) { + if consts.IsDryRun(ctx) { log.LogInfo(map[string]interface{}{ "action": "remove", "target": path, - }, "DryRun") + }, consts.TextDryRun) return nil } + log.LogDebug(map[string]interface{}{ + "action": "remove", + "target": path, + }, consts.TextExecuteAction) + if err := os.RemoveAll(path); err != nil { return err } diff --git a/pkg/component/actionmanager/actions/replace.go b/pkg/component/actionmanager/actions/replace.go index 904863b..0e4d821 100644 --- a/pkg/component/actionmanager/actions/replace.go +++ b/pkg/component/actionmanager/actions/replace.go @@ -23,8 +23,8 @@ import ( "github.com/pkg/errors" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" - "github.com/storyicon/powerproto/pkg/util/command" "github.com/storyicon/powerproto/pkg/util/logger" ) @@ -59,15 +59,21 @@ func ActionReplace(ctx context.Context, log logger.Logger, args []string, option panic(err) } if matched { - if command.IsDryRun(ctx) { + if consts.IsDryRun(ctx) { log.LogInfo(map[string]interface{}{ "action": "replace", "file": path, "from": from, "to": to, - }, "DryRun") + }, consts.TextDryRun) return nil } + log.LogDebug(map[string]interface{}{ + "action": "replace", + "file": path, + "from": from, + "to": to, + }, consts.TextExecuteAction) data, err := ioutil.ReadFile(path) if err != nil { return err diff --git a/pkg/component/compilermanager/compiler.go b/pkg/component/compilermanager/compiler.go index 86e45d6..6e119fd 100644 --- a/pkg/component/compilermanager/compiler.go +++ b/pkg/component/compilermanager/compiler.go @@ -45,9 +45,10 @@ type BasicCompiler struct { config configs.ConfigItem pluginManager pluginmanager.PluginManager - protocPath string - arguments []string - dir string + protocPath string + arguments []string + googleapisPath string + dir string } // NewCompiler is used to create a compiler @@ -72,6 +73,9 @@ func NewBasicCompiler( config: config, pluginManager: pluginManager, } + if err := basic.calcGoogleAPIs(ctx); err != nil { + return nil, err + } if err := basic.calcProto(ctx); err != nil { return nil, err } @@ -84,11 +88,17 @@ func NewBasicCompiler( return basic, nil } - // Compile is used to compile proto file func (b *BasicCompiler) Compile(ctx context.Context, protoFilePath string) error { arguments := make([]string, len(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) _, err := command.Execute(ctx, b.Logger, b.dir, b.protocPath, arguments, nil) @@ -118,6 +128,30 @@ func (b *BasicCompiler) calcDir(ctx context.Context) error { 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 { cfg := b.config protocVersion := cfg.Config().Protoc @@ -143,9 +177,17 @@ func (b *BasicCompiler) calcArguments(ctx context.Context) error { dir := filepath.Dir(cfg.Path()) // build import paths +Loop: for _, path := range cfg.Config().ImportPaths { - if path == consts.KeyPowerProtoInclude { + switch path { + case consts.KeyPowerProtoInclude: path = b.pluginManager.IncludePath(ctx) + case consts.KeyPowerProtoGoogleAPIs: + if b.googleapisPath != "" { + path = b.googleapisPath + } + case consts.KeySourceRelative: + continue Loop } path = util.RenderPathWithEnv(path) if !filepath.IsAbs(path) { @@ -176,4 +218,4 @@ func (b *BasicCompiler) calcArguments(ctx context.Context) error { } b.arguments = arguments return nil -} \ No newline at end of file +} diff --git a/pkg/component/pluginmanager/git.go b/pkg/component/pluginmanager/git.go new file mode 100644 index 0000000..514b53b --- /dev/null +++ b/pkg/component/pluginmanager/git.go @@ -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 +} diff --git a/pkg/component/pluginmanager/googleapis.go b/pkg/component/pluginmanager/googleapis.go new file mode 100644 index 0000000..8320893 --- /dev/null +++ b/pkg/component/pluginmanager/googleapis.go @@ -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 +} diff --git a/pkg/component/pluginmanager/manager.go b/pkg/component/pluginmanager/manager.go index 037d95d..0cda803 100644 --- a/pkg/component/pluginmanager/manager.go +++ b/pkg/component/pluginmanager/manager.go @@ -41,7 +41,17 @@ type PluginManager interface { // InstallPlugin is used to install plugin 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) // ListProtocVersions is used to list protoc version 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) } +// 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 func (b *BasicPluginManager) GetProtocLatestVersion(ctx context.Context) (string, error) { versions, err := b.ListProtocVersions(ctx) @@ -163,14 +249,6 @@ func (b *BasicPluginManager) ListProtocVersions(ctx context.Context) ([]string, 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 func (b *BasicPluginManager) InstallProtoc(ctx context.Context, version string) (string, error) { ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout) diff --git a/pkg/component/pluginmanager/paths.go b/pkg/component/pluginmanager/paths.go index 744587a..73ff2cf 100644 --- a/pkg/component/pluginmanager/paths.go +++ b/pkg/component/pluginmanager/paths.go @@ -23,7 +23,6 @@ import ( "github.com/storyicon/powerproto/pkg/util" ) - // PathForInclude is used to get the local directory of include files func PathForInclude(storageDir string) string { return filepath.Join(storageDir, "include") @@ -47,6 +46,11 @@ func GetPluginPath(path string, version string) (string, error) { 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 func PathForPluginDir(storageDir string, path string, version string) (string, error) { pluginPath, err := GetPluginPath(path, version) diff --git a/pkg/component/pluginmanager/pluginmanager_test.go b/pkg/component/pluginmanager/pluginmanager_test.go index ea35ed0..6d4df3f 100644 --- a/pkg/component/pluginmanager/pluginmanager_test.go +++ b/pkg/component/pluginmanager/pluginmanager_test.go @@ -70,4 +70,22 @@ var _ = Describe("Pluginmanager", func() { Expect(exists).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()) + }) }) diff --git a/pkg/component/pluginmanager/protoc.go b/pkg/component/pluginmanager/protoc.go index fa5daee..bc2da3f 100644 --- a/pkg/component/pluginmanager/protoc.go +++ b/pkg/component/pluginmanager/protoc.go @@ -29,8 +29,6 @@ import ( "github.com/pkg/errors" "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 @@ -107,34 +105,6 @@ func IsProtocInstalled(ctx context.Context, storageDir string, version string) ( 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) { goos := strings.ToLower(runtime.GOOS) arch := strings.ToLower(runtime.GOARCH) diff --git a/pkg/configs/configs.go b/pkg/configs/configs.go index 52b9e71..1fcdb30 100644 --- a/pkg/configs/configs.go +++ b/pkg/configs/configs.go @@ -29,6 +29,7 @@ import ( // Config defines the config model type Config struct { Scopes []string `json:"scopes" yaml:"scopes"` + GoogleAPIs string `json:"googleapis" yaml:"googleapis"` Protoc string `json:"protoc" yaml:"protoc"` ProtocWorkDir string `json:"protocWorkDir" yaml:"protocWorkDir"` Plugins map[string]string `json:"plugins" yaml:"plugins"` diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go index 2dc5c2b..539bdcf 100644 --- a/pkg/consts/consts.go +++ b/pkg/consts/consts.go @@ -18,20 +18,35 @@ import ( "os" "path/filepath" + "github.com/fatih/color" + "github.com/storyicon/powerproto/pkg/util/logger" ) - // defines a set of const value const ( // ConfigFileName defines the config file name ConfigFileName = "powerproto.yaml" // The default include can be referenced by this key in import paths 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 EnvHomeDir = "POWERPROTO_HOME" // ProtobufRepository defines the protobuf repository 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 diff --git a/pkg/consts/context.go b/pkg/consts/context.go new file mode 100644 index 0000000..d71b64f --- /dev/null +++ b/pkg/consts/context.go @@ -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 +} diff --git a/pkg/util/command/command.go b/pkg/util/command/command.go index 642706a..41378e5 100644 --- a/pkg/util/command/command.go +++ b/pkg/util/command/command.go @@ -21,44 +21,11 @@ import ( "os" "os/exec" + "github.com/storyicon/powerproto/pkg/consts" "github.com/storyicon/powerproto/pkg/util" "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 func Execute(ctx context.Context, log logger.Logger, @@ -67,17 +34,21 @@ func Execute(ctx context.Context, cmd.Env = append(os.Environ(), env...) cmd.Dir = dir - if IsDryRun(ctx) && !IsIgnoreDryRun(ctx) { + if consts.IsDryRun(ctx) && !consts.IsIgnoreDryRun(ctx) { log.LogInfo(map[string]interface{}{ "command": cmd.String(), "dir": cmd.Dir, - }, "DryRun") + }, consts.TextDryRun) return nil, nil } var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr + log.LogDebug(map[string]interface{}{ + "command": cmd.String(), + "dir": cmd.Dir, + }, consts.TextExecuteCommand) if err := cmd.Run(); err != nil { return nil, &ErrCommandExec{ Err: err, diff --git a/pkg/util/progressbar/progressbar.go b/pkg/util/progressbar/progressbar.go index 6516d12..7210326 100644 --- a/pkg/util/progressbar/progressbar.go +++ b/pkg/util/progressbar/progressbar.go @@ -15,11 +15,15 @@ package progressbar import ( + "context" "fmt" "github.com/fatih/color" "github.com/vbauerster/mpb/v7" "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 @@ -58,10 +62,12 @@ func newEmbedProgressBar(container *mpb.Progress, bar *mpb.Bar) *progressBar { } } +// Incr is used to increase progress func (s *progressBar) Incr() { s.bar.Increment() } +// Wait is used to wait for the rendering of the progress bar to complete func (s *progressBar) 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 -func GetProgressBar(count int) ProgressBar { +func GetProgressBar(ctx context.Context, count int) ProgressBar { + if consts.IsDebugMode(ctx) { + return newFakeProgressbar(count) + } + var progressBar *progressBar container := mpb.New() bar := container.Add(int64(count), diff --git a/pkg/util/util.go b/pkg/util/util.go index b47c5ab..03b1c0f 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -26,8 +26,13 @@ import ( // ContainsEmpty is used to check whether items contains empty string 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 { - if item == "" { + if item == target { return true } } @@ -103,4 +108,4 @@ func GetBinaryFileName(name string) string { return name } return name -} \ No newline at end of file +}