Signed-off-by: storyicon <yuanchao@bilibili.com>
This commit is contained in:
storyicon 2021-07-21 00:24:43 +08:00
commit 9aac714c32
No known key found for this signature in database
GPG key ID: 245915D985F966CF
47 changed files with 5480 additions and 0 deletions

60
cmd/powerproto/main.go Normal file
View file

@ -0,0 +1,60 @@
// 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 main
import (
"fmt"
"github.com/spf13/cobra"
cmdbuild "github.com/storyicon/powerproto/cmd/powerproto/subcommands/build"
cmdenv "github.com/storyicon/powerproto/cmd/powerproto/subcommands/env"
cmdinit "github.com/storyicon/powerproto/cmd/powerproto/subcommands/init"
cmdtidy "github.com/storyicon/powerproto/cmd/powerproto/subcommands/tidy"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// Version is set via build flag -ldflags -X main.Version
var (
Version string
Branch string
Revision string
BuildDate string
)
var log = logger.NewDefault("command")
// GetRootCommand is used to get root command
func GetRootCommand() *cobra.Command {
return &cobra.Command{
Use: "[powerproto]",
Version: fmt.Sprintf("%s, branch: %s, revision: %s, buildDate: %s", Version, Branch, Revision, BuildDate),
Short: "powerproto is used to build proto files and version control of protoc and related plug-ins",
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}
}
func main() {
cmdRoot := GetRootCommand()
cmdRoot.AddCommand(
cmdbuild.CommandBuild(log),
cmdinit.CommandInit(log),
cmdtidy.CommandTidy(log),
cmdenv.CommandEnv(log),
)
cmdRoot.Execute()
}

View file

@ -0,0 +1,117 @@
// 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 build
import (
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/bootstraps"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
const description = `
Examples:
compile specific proto file
powerproto build [proto file]
compile the proto file in the folder, excluding sub folders:
powerproto build [dir]
compile all proto files in the folder recursively, including sub folders:
powerproto build -r [dir]
compile proto files and execute the post actions/shells:
powerproto build -r -a [dir]
`
// compile proto files
// powerproto build -r .
// powerproto build .
// 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 postScriptEnabled bool
cmd := &cobra.Command{
Use: "build [dir|proto file]",
Short: "compile proto files",
Long: strings.TrimSpace(description),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
if dryRun {
ctx = command.WithDryRun(ctx)
}
if !postScriptEnabled {
ctx = command.WithDisableAction(ctx)
}
target, err := filepath.Abs(args[0])
if err != nil {
log.LogFatal(nil, "failed to abs target path: %s", err)
}
fileInfo, err := os.Stat(target)
if err != nil {
log.LogFatal(map[string]interface{}{
"target": target,
}, "failed to stat target: %s", err)
}
var targets []string
if fileInfo.IsDir() {
log.LogInfo(nil, "search proto files...")
if recursive {
targets, err = util.GetFilesWithExtRecursively(target, ".proto")
if err != nil {
log.LogFatal(nil, "failed to walk directory: %s", err)
}
} else {
targets, err = util.GetFilesWithExt(target, ".proto")
if err != nil {
log.LogFatal(nil, "failed to walk directory: %s", err)
}
}
} else {
targets = append(targets, target)
}
if len(targets) == 0 {
log.LogWarn(nil, "no file to compile")
return
}
if err := bootstraps.StepTidyConfig(ctx, targets); err != nil {
log.LogFatal(nil, "failed to tidy config: %+v", err)
return
}
if err := bootstraps.Compile(ctx, targets); err != nil {
log.LogFatal(nil, "failed to compile: %+v", err)
}
log.LogInfo(nil, "succeed! you are ready to go :)")
},
}
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")
return cmd
}

View file

@ -0,0 +1,57 @@
// 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 env
import (
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// Print environment variables related to program operation
func CommandEnv(log logger.Logger) *cobra.Command {
return &cobra.Command{
Use: "env",
Short: "list the environments and binary files",
Run: func(cmd *cobra.Command, args []string) {
log.LogInfo(nil, "[ENVIRONMENT]")
for _, key := range []string{
consts.EnvHomeDir,
"HTTP_PROXY",
"HTTPS_PROXY",
"GOPROXY",
} {
log.LogInfo(nil, "%s=%s", key, os.Getenv(key))
}
log.LogInfo(nil, "[BIN]")
for _, key := range []string {
"go",
"git",
} {
path, err := exec.LookPath(key)
if err != nil {
log.LogError(nil, "failed to find: %s", key)
continue
}
log.LogInfo(nil, "%s=%s", key, path)
}
},
}
}

View file

@ -0,0 +1,114 @@
// 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 build
import (
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"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"
)
// UserPreference defines the model of user preference
type UserPreference struct {
Plugins []string `survey:"plugins"`
}
// GetUserPreference is used to get user preference
func GetUserPreference() (*UserPreference, error) {
var preference UserPreference
err := survey.Ask([]*survey.Question{
{
Name: "plugins",
Prompt: &survey.MultiSelect{
Message: "select plugins to use. Later, you can also manually add in the configuration file",
Options: GetWellKnownPluginsOptionValues(),
},
},
}, &preference)
return &preference, err
}
// GetDefaultConfig is used to get default config
func GetDefaultConfig() *configs.Config {
return &configs.Config{
Scopes: []string{
"./",
},
Protoc: "latest",
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",
},
Options: []string{
"--go_out=.",
"--go_opt=paths=source_relative",
"--go-grpc_out=.",
"--go-grpc_opt=paths=source_relative",
},
ImportPaths: []string{
".",
"$GOPATH",
"$POWERPROTO_INCLUDE",
},
}
}
// CommandInit is used to initialize the configuration
// file in the current directory
func CommandInit(log logger.Logger) *cobra.Command {
return &cobra.Command{
Use: "init",
Short: "init a config file in current directory",
Run: func(cmd *cobra.Command, args []string) {
exists, err := util.IsFileExists(consts.ConfigFileName)
if err != nil {
log.LogError(nil, err.Error())
return
}
if exists {
log.LogInfo(nil, "config file already exists in this directory")
return
}
preference, err := GetUserPreference()
if err != nil {
log.LogError(nil, err.Error())
return
}
config := GetDefaultConfig()
if len(preference.Plugins) != 0 {
var compileOptions []string
plugins := map[string]string{}
for _, val := range preference.Plugins {
plugin, ok := GetPluginFromOptionsValue(val)
if ok {
plugins[plugin.Name] = plugin.Pkg
compileOptions = append(compileOptions, plugin.Options...)
}
}
config.Plugins = plugins
config.Options = compileOptions
}
if err := configs.SaveConfigs(consts.ConfigFileName, config); err != nil {
log.LogFatal(nil, "failed to save config: %s", err)
}
log.LogInfo(nil, "succeed! You can use `powerproto tidy` to tidy this config file")
},
}
}

View file

@ -0,0 +1,95 @@
// 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 build
import (
"fmt"
)
// Plugin defines the plugin options
type Plugin struct {
Name string
Pkg string
Options []string
}
// String implements the standard string interface
func (options *Plugin) String() string {
return fmt.Sprintf("%s: %s", options.Name, options.Pkg)
}
// GetWellKnownPlugins is used to get well known plugins
func GetWellKnownPlugins() []*Plugin {
return []*Plugin{
{
Name: "protoc-gen-go",
Pkg: "google.golang.org/protobuf/cmd/protoc-gen-go@latest",
Options: []string{
"--go_out=.",
"--go_opt=paths=source_relative",
},
},
{
Name: "protoc-gen-go-grpc",
Pkg: "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
Options: []string{
"--go-grpc_out=.",
"--go-grpc_opt=paths=source_relative",
},
},
{
Name: "protoc-gen-grpc-gateway",
Pkg: "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest",
Options: []string{
"--grpc-gateway_out=.",
},
},
{
Name: "protoc-gen-deepcopy",
Pkg: "istio.io/tools/cmd/protoc-gen-deepcopy@latest",
Options: []string{
"--deepcopy_out=source_relative:.",
},
},
{
Name: "protoc-gen-go-json",
Pkg: "github.com/mitchellh/protoc-gen-go-json@latest",
Options: []string{
"--go-json_out=.",
},
},
}
}
// GetPluginFromOptionsValue is used to get plugin by option value
func GetPluginFromOptionsValue(val string) (*Plugin, bool) {
plugins := GetWellKnownPlugins()
for _, plugin := range plugins {
if plugin.String() == val {
return plugin, true
}
}
return nil, false
}
// GetWellKnownPluginsOptionValues is used to get option values of well known plugins
func GetWellKnownPluginsOptionValues() []string {
plugins := GetWellKnownPlugins()
packages := make([]string, 0, len(plugins))
for _, plugin := range plugins {
packages = append(packages, plugin.String())
}
return packages
}

View file

@ -0,0 +1,108 @@
// 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 tidy
import (
"context"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/bootstraps"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar"
)
func tidy(ctx context.Context,
pluginManager pluginmanager.PluginManager,
configFilePath string) error {
progress := progressbar.GetProgressBar(1)
progress.SetPrefix("tidy config")
err := bootstraps.StepTidyConfigFile(ctx, pluginManager, progress, configFilePath)
if err != nil {
return err
}
progress.Incr()
progress.Wait()
configItems, err := configs.LoadConfigItems(configFilePath)
if err != nil {
return err
}
if err := bootstraps.StepInstallProtoc(ctx, pluginManager, configItems); err != nil {
return err
}
if err := bootstraps.StepInstallPlugins(ctx, pluginManager, configItems); err != nil {
return err
}
return nil
}
// 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{
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) {
var targets []string
if len(args) != 0 {
for _, arg := range args {
dir, _ := filepath.Abs(arg)
targets = append(targets, dir)
}
} else {
dir, err := os.Getwd()
if err != nil {
log.LogFatal(nil, "failed to get current dir: %s", err)
}
targets = configs.ListConfigPaths(dir)
}
pluginManager, err := pluginmanager.NewPluginManager(pluginmanager.NewConfig(), log)
if err != nil {
log.LogFatal(nil, "failed to create plugin manager: %s", err)
}
configMap := map[string]struct{}{}
for _, path := range targets {
exists, err := util.IsFileExists(path)
if err != nil {
log.LogFatal(map[string]interface{}{
"path": path,
"err": err,
}, "failed to tidy config")
}
if !exists {
continue
}
log.LogInfo(nil, "tidy %s", path)
if err := tidy(cmd.Context(), pluginManager, path); err != nil {
log.LogFatal(map[string]interface{}{
"path": path,
"err": err,
}, "failed to tidy config")
}
configMap[path] = struct{}{}
}
log.LogInfo(nil, "these following config files were tidied:")
for path := range configMap {
log.LogInfo(nil, " %s", path)
}
log.LogInfo(nil, "\r\nsucceeded, you are ready to go :)")
},
}
}