parse build args
This commit is contained in:
parent
4f39735095
commit
922340d8c4
27
cmd/build.go
27
cmd/build.go
@ -1,31 +1,24 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/qiniu/goc/v2/pkg/flag"
|
||||
|
||||
"github.com/qiniu/goc/v2/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var buildCmd = &cobra.Command{
|
||||
Use: "build",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.StartWait("doing something")
|
||||
time.Sleep(time.Second * 3)
|
||||
log.Infof("building")
|
||||
time.Sleep(time.Second * 3)
|
||||
log.Infof("making temp dir")
|
||||
time.Sleep(time.Second * 3)
|
||||
log.StopWait()
|
||||
log.Donef("done")
|
||||
log.Infof("hello")
|
||||
log.Errorf("hello")
|
||||
log.Warnf("hello")
|
||||
log.Debugf("hello")
|
||||
log.Fatalf("fail to excute: %v, %v", "ee", "ddd")
|
||||
},
|
||||
Run: build,
|
||||
|
||||
DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(buildCmd)
|
||||
}
|
||||
|
||||
func build(cmd *cobra.Command, args []string) {
|
||||
remainedArgs := flag.BuildCmdArgsParse(cmd, args)
|
||||
where, buildName := flag.GetPackagesDir(remainedArgs)
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"github.com/qiniu/goc/v2/pkg/config"
|
||||
"github.com/qiniu/goc/v2/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@ -40,8 +39,6 @@ Find more information at:
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().BoolVar(&config.GocConfig.Debug, "debug", false, "run goc in debug mode")
|
||||
|
||||
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
|
||||
}
|
||||
|
||||
// Execute the goc tool
|
||||
|
16
doc/how_to_parse_args_and_flags_in_goc.md
Normal file
16
doc/how_to_parse_args_and_flags_in_goc.md
Normal file
@ -0,0 +1,16 @@
|
||||
# goc 中的参数处理设计
|
||||
|
||||
## 背景
|
||||
|
||||
goc build/install/run 有不少人反馈使用起来和 go build/install/run 相比,还是有不少的差异。这种差异导致在日常开发、CI/CD 中替换不便,有些带引号的参数会被改写的面目全非。
|
||||
|
||||
## 原则
|
||||
|
||||
goc build/install/run 会尽可能的模仿 go 原生的方式去处理参数。
|
||||
|
||||
## 主要问题
|
||||
|
||||
1. goc 使用 cobra 库来组织各个子命令。cobra 对 flag 处理采用的是 posix 风格(两个个短横线),和 go 的 flag 处理差异很大(一个短横线)。
|
||||
2. go 命令中 args 和 flags 有着严格先后顺序。而 cobra 库对 flags 和 args 的位置没有要求。
|
||||
3. 参数中 `[packages]` 有多种组合情况,会影响到插桩的起始位置。
|
||||
4. goc 还有自己参数,且需要和**非** goc build/install/run 的子命令保持一致(两个短横线)。
|
2
doc/return_error_or_fatal.md
Normal file
2
doc/return_error_or_fatal.md
Normal file
@ -0,0 +1,2 @@
|
||||
# 返回 error 还是原地 fatal
|
||||
|
1
go.mod
1
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/ugorji/go v1.1.4 // indirect
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||
|
@ -1,7 +1,50 @@
|
||||
package config
|
||||
|
||||
type gocConfig struct {
|
||||
Debug bool
|
||||
Debug bool
|
||||
CurPkgDir string
|
||||
CurModProjectDir string
|
||||
TmpModProjectDir string
|
||||
TmpPkgDir string
|
||||
BinaryName string
|
||||
}
|
||||
|
||||
var GocConfig gocConfig
|
||||
|
||||
type goConfig struct {
|
||||
BuildA bool
|
||||
BuildBuildmode string // -buildmode flag
|
||||
BuildMod string // -mod flag
|
||||
BuildModReason string // reason -mod flag is set, if set by default
|
||||
BuildI bool // -i flag
|
||||
BuildLinkshared bool // -linkshared flag
|
||||
BuildMSan bool // -msan flag
|
||||
BuildN bool // -n flag
|
||||
BuildO string // -o flag
|
||||
BuildP int // -p flag
|
||||
BuildPkgdir string // -pkgdir flag
|
||||
BuildRace bool // -race flag
|
||||
BuildToolexec string // -toolexec flag
|
||||
BuildToolchainName string
|
||||
BuildToolchainCompiler func() string
|
||||
BuildToolchainLinker func() string
|
||||
BuildTrimpath bool // -trimpath flag
|
||||
BuildV bool // -v flag
|
||||
BuildWork bool // -work flag
|
||||
BuildX bool // -x flag
|
||||
// from buildcontext
|
||||
Installsuffix string // -installSuffix
|
||||
BuildTags string // -tags
|
||||
// from load
|
||||
BuildAsmflags string
|
||||
BuildCompiler string
|
||||
BuildGcflags string
|
||||
BuildGccgoflags string
|
||||
BuildLdflags string
|
||||
|
||||
// mod related
|
||||
ModCacheRW bool
|
||||
ModFile string
|
||||
}
|
||||
|
||||
var GoConfig goConfig
|
||||
|
82
pkg/flag/build_flags.go
Normal file
82
pkg/flag/build_flags.go
Normal file
@ -0,0 +1,82 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/qiniu/goc/v2/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var buildUsage string = `Usage:
|
||||
goc build [-o output] [build flags] [packages] [goc flags]
|
||||
|
||||
The [goc flags] can be placed in anywhere in the command line.
|
||||
However, other flags' order are same with the go official command.
|
||||
`
|
||||
|
||||
// BuildCmdArgsParse parse both go flags and goc flags, it returns all non-flag arguments.
|
||||
// It will log fatal if error
|
||||
func BuildCmdArgsParse(cmd *cobra.Command, args []string) []string {
|
||||
// 首先解析 cobra 定义的 flag
|
||||
allFlagSets := cmd.Flags()
|
||||
// 因为 args 里面含有 go 的 flag,所以需要忽略解析 go flag 的错误
|
||||
allFlagSets.Init("GOC", pflag.ContinueOnError)
|
||||
allFlagSets.Parse(args)
|
||||
|
||||
// 重写 help
|
||||
helpFlag := allFlagSets.Lookup("help")
|
||||
|
||||
if helpFlag.Changed {
|
||||
printHelp(buildUsage, cmd)
|
||||
}
|
||||
// 删除 help flag
|
||||
args = findAndDelHelpFlag(args)
|
||||
|
||||
// 必须手动调用
|
||||
// 由于关闭了 cobra 的 flag parse,root PersistentPreRun 调用时,log.NewLogger 并没有拿到 debug 值
|
||||
log.NewLogger()
|
||||
|
||||
// 删除 cobra 定义的 flag
|
||||
allFlagSets.Visit(func(f *pflag.Flag) {
|
||||
args = findAndDelGocFlag(args, f.Name)
|
||||
})
|
||||
|
||||
// 然后解析 go 的 flag
|
||||
goFlagSets := flag.NewFlagSet("GO", flag.ContinueOnError)
|
||||
addBuildFlags(goFlagSets)
|
||||
addOutputFlags(goFlagSets)
|
||||
err := goFlagSets.Parse(args)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
return goFlagSets.Args()
|
||||
}
|
||||
|
||||
func findAndDelGocFlag(a []string, x string) []string {
|
||||
new := make([]string, 0, len(a))
|
||||
x = "--" + x
|
||||
for _, v := range a {
|
||||
if v == x {
|
||||
continue
|
||||
} else {
|
||||
new = append(new, v)
|
||||
}
|
||||
}
|
||||
|
||||
return new
|
||||
}
|
||||
|
||||
func findAndDelHelpFlag(a []string) []string {
|
||||
new := make([]string, 0, len(a))
|
||||
for _, v := range a {
|
||||
if v == "--help" || v == "-h" {
|
||||
continue
|
||||
} else {
|
||||
new = append(new, v)
|
||||
}
|
||||
}
|
||||
|
||||
return new
|
||||
}
|
40
pkg/flag/flags.go
Normal file
40
pkg/flag/flags.go
Normal file
@ -0,0 +1,40 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/qiniu/goc/v2/pkg/config"
|
||||
)
|
||||
|
||||
func addBuildFlags(cmdSet *flag.FlagSet) {
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildA, "a", false, "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildN, "n", false, "")
|
||||
cmdSet.IntVar(&config.GoConfig.BuildP, "p", 4, "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildV, "v", false, "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildX, "x", false, "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildBuildmode, "buildmode", "default", "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildMod, "mod", "", "")
|
||||
cmdSet.StringVar(&config.GoConfig.Installsuffix, "installsuffix", "", "")
|
||||
|
||||
// 类型和 go 原生的不一样,这里纯粹是为了 parse 并传递给 go
|
||||
cmdSet.StringVar(&config.GoConfig.BuildAsmflags, "asmflags", "", "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildCompiler, "compiler", "", "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildGcflags, "gcflags", "", "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildGccgoflags, "gccgoflags", "", "")
|
||||
// mod related
|
||||
cmdSet.BoolVar(&config.GoConfig.ModCacheRW, "modcacherw", false, "")
|
||||
cmdSet.StringVar(&config.GoConfig.ModFile, "modfile", "", "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildLdflags, "ldflags", "", "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildLinkshared, "linkshared", false, "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildPkgdir, "pkgdir", "", "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildRace, "race", false, "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildMSan, "msan", false, "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildTags, "tags", "", "")
|
||||
cmdSet.StringVar(&config.GoConfig.BuildToolexec, "toolexec", "", "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildTrimpath, "trimpath", false, "")
|
||||
cmdSet.BoolVar(&config.GoConfig.BuildWork, "work", false, "")
|
||||
}
|
||||
|
||||
func addOutputFlags(cmdSet *flag.FlagSet) {
|
||||
cmdSet.StringVar(&config.GoConfig.BuildO, "o", "", "")
|
||||
}
|
20
pkg/flag/help.go
Normal file
20
pkg/flag/help.go
Normal file
@ -0,0 +1,20 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func printHelp(usage string, cmd *cobra.Command) {
|
||||
fmt.Println(usage)
|
||||
|
||||
flags := cmd.LocalFlags()
|
||||
globalFlags := cmd.Parent().PersistentFlags()
|
||||
|
||||
fmt.Println("Flags:")
|
||||
fmt.Println(flags.FlagUsages())
|
||||
|
||||
fmt.Println("Global Flags:")
|
||||
fmt.Println(globalFlags.FlagUsages())
|
||||
}
|
181
pkg/flag/packages.go
Normal file
181
pkg/flag/packages.go
Normal file
@ -0,0 +1,181 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered
|
||||
//
|
||||
// Return 1: [packages] 所在的目录位置,供后续插桩使用。
|
||||
//
|
||||
// Return 2: 如果参数是 *.go,第一个 .go 文件的文件名。go build 中,二进制名字既可能是目录名也可能是文件名,和参数类型有关。
|
||||
//
|
||||
// 如果 [packages] 非法(即不符合 go 原生的定义),则返回对应错误
|
||||
// 这里只考虑 go mod 的方式
|
||||
func GetPackagesDir(patterns []string) (string, string) {
|
||||
for _, p := range patterns {
|
||||
// patterns 只支持两种格式
|
||||
// 1. 要么是直接指向某些 .go 文件的相对/绝对路径
|
||||
if strings.HasSuffix(p, ".go") {
|
||||
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
|
||||
// check if valid
|
||||
if err := goFilesPackage(patterns); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
// 获取绝对路径
|
||||
absp, err := filepath.Abs(p)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return filepath.Dir(absp), filepath.Base(absp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 要么是 import path
|
||||
coverWd, err := getDirFromImportPaths(patterns)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
return coverWd, ""
|
||||
}
|
||||
|
||||
// goFilesPackage 对一组 go 文件解析,判断是否合法
|
||||
// go 本身还判断语法上是否是同一个 package,goc 这里不做解析
|
||||
// 1. 都是 *.go 文件?
|
||||
// 2. *.go 文件都在同一个目录?
|
||||
// 3. *.go 文件存在?
|
||||
func goFilesPackage(gofiles []string) error {
|
||||
// 1. 必须都是 *.go 结尾
|
||||
for _, f := range gofiles {
|
||||
if !strings.HasSuffix(f, ".go") {
|
||||
return fmt.Errorf("named files must be .go files: %s", f)
|
||||
}
|
||||
}
|
||||
|
||||
var dir string
|
||||
for _, file := range gofiles {
|
||||
// 3. 文件都存在?
|
||||
fi, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2.1 有可能以 *.go 结尾的目录
|
||||
if fi.IsDir() {
|
||||
return fmt.Errorf("%s is a directory, should be a Go file", file)
|
||||
}
|
||||
|
||||
// 2.2 所有 *.go 必须在同一个目录内
|
||||
dir1, _ := filepath.Split(file)
|
||||
if dir1 == "" {
|
||||
dir1 = "./"
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
dir = dir1
|
||||
} else if dir != dir1 {
|
||||
return fmt.Errorf("named files must all be in one directory: have %s and %s", dir, dir1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDirFromImportPaths return the import path's real directory
|
||||
//
|
||||
// 该函数接收到的只有 dir 或 import path,file 在上一步已被排除
|
||||
// 只考虑 go modules 的情况
|
||||
func getDirFromImportPaths(patterns []string) (string, error) {
|
||||
// no import path, pattern = current wd
|
||||
if len(patterns) == 0 {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fail to parse import path: %w", err)
|
||||
}
|
||||
return wd, nil
|
||||
}
|
||||
|
||||
// 为了简化插桩的逻辑,goc 对 import path 要求必须都在同一个目录
|
||||
// 所以干脆只允许一个 pattern 得了 -_-
|
||||
// 对于 goc build/run 来说本身就是只能在一个目录内
|
||||
// 对于 goc install 来讲,这个行为就和 go install 不同,不过多 import path 较少见 >_<,先忽略
|
||||
if len(patterns) > 1 {
|
||||
return "", fmt.Errorf("goc only support one import path now")
|
||||
}
|
||||
|
||||
pattern := patterns[0]
|
||||
switch {
|
||||
// case isLocalImport(pattern) || filepath.IsAbs(pattern):
|
||||
// dir1, err := filepath.Abs(pattern)
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
|
||||
// }
|
||||
// if _, err := os.Stat(dir1); err != nil {
|
||||
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
|
||||
// }
|
||||
// return dir1, nil
|
||||
|
||||
case strings.Contains(pattern, "..."):
|
||||
i := strings.Index(pattern, "...")
|
||||
dir, _ := filepath.Split(pattern[:i])
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
|
||||
}
|
||||
return dir, nil
|
||||
|
||||
case strings.IndexByte(pattern, '@') > 0:
|
||||
return "", fmt.Errorf("import path with @ version query is not supported in goc")
|
||||
|
||||
case isMetaPackage(pattern):
|
||||
return "", fmt.Errorf("`std`, `cmd`, `all` import path is not supported by goc")
|
||||
|
||||
default: // 到这一步认为 pattern 是相对路径或者绝对路径
|
||||
dir1, err := filepath.Abs(pattern)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
|
||||
}
|
||||
if _, err := os.Stat(dir1); err != nil {
|
||||
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
|
||||
}
|
||||
|
||||
return dir1, nil
|
||||
}
|
||||
}
|
||||
|
||||
// isLocalImport reports whether the import path is
|
||||
// a local import path, like ".", "..", "./foo", or "../foo"
|
||||
func isLocalImport(path string) bool {
|
||||
return path == "." || path == ".." ||
|
||||
strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
|
||||
}
|
||||
|
||||
// isMetaPackage checks if the name is a reserved package name
|
||||
func isMetaPackage(name string) bool {
|
||||
return name == "std" || name == "cmd" || name == "all"
|
||||
}
|
||||
|
||||
// find direct path of current project which contains go.mod
|
||||
func findModuleRoot(dir string) string {
|
||||
dir = filepath.Clean(dir)
|
||||
|
||||
// look for enclosing go.mod
|
||||
for {
|
||||
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
|
||||
return dir
|
||||
}
|
||||
d := filepath.Dir(dir)
|
||||
if d == dir {
|
||||
break
|
||||
}
|
||||
dir = d
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
@ -8,6 +8,8 @@ type ciLogger struct {
|
||||
|
||||
func newCiLogger() *ciLogger {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
// fix: increases the number of caller from always reporting the wrapper code as caller
|
||||
logger = logger.WithOptions(zap.AddCallerSkip(2))
|
||||
zap.ReplaceGlobals(logger)
|
||||
return &ciLogger{
|
||||
logger: logger,
|
||||
|
Loading…
Reference in New Issue
Block a user