398 lines
11 KiB
Go
398 lines
11 KiB
Go
/*
|
||
* Copyright 2022 CloudWeGo Authors
|
||
*
|
||
* 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 config
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||
"github.com/urfave/cli/v2"
|
||
)
|
||
|
||
type Argument struct {
|
||
// Mode meta.Mode // operating mode(0-compiler, 1-plugin)
|
||
CmdType string // command type
|
||
Verbose bool // print verbose log
|
||
Cwd string // execution path
|
||
OutDir string // output path
|
||
HandlerDir string // handler path
|
||
ModelDir string // model path
|
||
RouterDir string // router path
|
||
ClientDir string // client path
|
||
BaseDomain string // request domain
|
||
ForceClientDir string // client dir (not use namespace as a subpath)
|
||
|
||
IdlType string // idl type
|
||
IdlPaths []string // master idl path
|
||
RawOptPkg []string // user-specified package import path
|
||
OptPkgMap map[string]string
|
||
Includes []string
|
||
PkgPrefix string
|
||
|
||
Gopath string // $GOPATH
|
||
Gosrc string // $GOPATH/src
|
||
Gomod string
|
||
Gopkg string // $GOPATH/src/{{gopkg}}
|
||
ServiceName string // service name
|
||
Use string
|
||
NeedGoMod bool
|
||
|
||
JSONEnumStr bool
|
||
UnsetOmitempty bool
|
||
ProtobufCamelJSONTag bool
|
||
ProtocOptions []string // options to pass through to protoc
|
||
ThriftOptions []string // options to pass through to thriftgo for go flag
|
||
ProtobufPlugins []string
|
||
ThriftPlugins []string
|
||
SnakeName bool
|
||
RmTags []string
|
||
Excludes []string
|
||
NoRecurse bool
|
||
HandlerByMethod bool
|
||
ForceNew bool
|
||
SnakeStyleMiddleware bool
|
||
EnableExtends bool
|
||
|
||
CustomizeLayout string
|
||
CustomizeLayoutData string
|
||
CustomizePackage string
|
||
ModelBackend string
|
||
}
|
||
|
||
func NewArgument() *Argument {
|
||
return &Argument{
|
||
OptPkgMap: make(map[string]string),
|
||
Includes: make([]string, 0, 4),
|
||
Excludes: make([]string, 0, 4),
|
||
ProtocOptions: make([]string, 0, 4),
|
||
ThriftOptions: make([]string, 0, 4),
|
||
}
|
||
}
|
||
|
||
// Parse initializes a new argument based on its own information
|
||
func (arg *Argument) Parse(c *cli.Context, cmd string) (*Argument, error) {
|
||
// v2 cli cannot put the StringSlice flag to struct, so we need to parse it here
|
||
arg.parseStringSlice(c)
|
||
args := arg.Fork()
|
||
args.CmdType = cmd
|
||
|
||
err := args.checkPath()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
err = args.checkIDL()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
err = args.checkPackage()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return args, nil
|
||
}
|
||
|
||
func (arg *Argument) parseStringSlice(c *cli.Context) {
|
||
arg.IdlPaths = c.StringSlice("idl")
|
||
arg.Includes = c.StringSlice("proto_path")
|
||
arg.Excludes = c.StringSlice("exclude_file")
|
||
arg.RawOptPkg = c.StringSlice("option_package")
|
||
arg.ThriftOptions = c.StringSlice("thriftgo")
|
||
arg.ProtocOptions = c.StringSlice("protoc")
|
||
arg.ThriftPlugins = c.StringSlice("thrift-plugins")
|
||
arg.ProtobufPlugins = c.StringSlice("protoc-plugins")
|
||
arg.RmTags = c.StringSlice("rm_tag")
|
||
}
|
||
|
||
func (arg *Argument) UpdateByManifest(m *meta.Manifest) {
|
||
if arg.HandlerDir == "" && m.HandlerDir != "" {
|
||
logs.Infof("use \"handler_dir\" in \".hz\" as the handler generated dir\n")
|
||
arg.HandlerDir = m.HandlerDir
|
||
}
|
||
if arg.ModelDir == "" && m.ModelDir != "" {
|
||
logs.Infof("use \"model_dir\" in \".hz\" as the model generated dir\n")
|
||
arg.ModelDir = m.ModelDir
|
||
}
|
||
if len(m.RouterDir) != 0 {
|
||
logs.Infof("use \"router_dir\" in \".hz\" as the router generated dir\n")
|
||
arg.RouterDir = m.RouterDir
|
||
}
|
||
}
|
||
|
||
// checkPath sets the project path and verifies that the model、handler、router and client path is compliant
|
||
func (arg *Argument) checkPath() error {
|
||
dir, err := os.Getwd()
|
||
if err != nil {
|
||
return fmt.Errorf("get current path failed: %s", err)
|
||
}
|
||
arg.Cwd = dir
|
||
if arg.OutDir == "" {
|
||
arg.OutDir = dir
|
||
}
|
||
if !filepath.IsAbs(arg.OutDir) {
|
||
ap := filepath.Join(arg.Cwd, arg.OutDir)
|
||
arg.OutDir = ap
|
||
}
|
||
if arg.ModelDir != "" && filepath.IsAbs(arg.ModelDir) {
|
||
return fmt.Errorf("model path %s must be relative to out_dir", arg.ModelDir)
|
||
}
|
||
if arg.HandlerDir != "" && filepath.IsAbs(arg.HandlerDir) {
|
||
return fmt.Errorf("handler path %s must be relative to out_dir", arg.HandlerDir)
|
||
}
|
||
if arg.RouterDir != "" && filepath.IsAbs(arg.RouterDir) {
|
||
return fmt.Errorf("router path %s must be relative to out_dir", arg.RouterDir)
|
||
}
|
||
if arg.ClientDir != "" && filepath.IsAbs(arg.ClientDir) {
|
||
return fmt.Errorf("router path %s must be relative to out_dir", arg.ClientDir)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// checkIDL check if the idl path exists, set and check the idl type
|
||
func (arg *Argument) checkIDL() error {
|
||
for i, path := range arg.IdlPaths {
|
||
abPath, err := filepath.Abs(path)
|
||
if err != nil {
|
||
return fmt.Errorf("idl path %s is not absolute", path)
|
||
}
|
||
ext := filepath.Ext(abPath)
|
||
if ext == "" || ext[0] != '.' {
|
||
return fmt.Errorf("idl path %s is not a valid file", path)
|
||
}
|
||
ext = ext[1:]
|
||
switch ext {
|
||
case meta.IdlThrift:
|
||
arg.IdlType = meta.IdlThrift
|
||
case meta.IdlProto:
|
||
arg.IdlType = meta.IdlProto
|
||
default:
|
||
return fmt.Errorf("IDL type %s is not supported", ext)
|
||
}
|
||
arg.IdlPaths[i] = abPath
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (arg *Argument) IsUpdate() bool {
|
||
return arg.CmdType == meta.CmdUpdate
|
||
}
|
||
|
||
func (arg *Argument) IsNew() bool {
|
||
return arg.CmdType == meta.CmdNew
|
||
}
|
||
|
||
// checkPackage check and set the gopath、 module and package name
|
||
func (arg *Argument) checkPackage() error {
|
||
gopath, err := util.GetGOPATH()
|
||
if err != nil {
|
||
return fmt.Errorf("get gopath failed: %s", err)
|
||
}
|
||
if gopath == "" {
|
||
return fmt.Errorf("GOPATH is not set")
|
||
}
|
||
|
||
arg.Gopath = gopath
|
||
arg.Gosrc = filepath.Join(gopath, "src")
|
||
|
||
// Generate the project under gopath, use the relative path as the package name
|
||
if strings.HasPrefix(arg.Cwd, arg.Gosrc) {
|
||
if gopkg, err := filepath.Rel(arg.Gosrc, arg.Cwd); err != nil {
|
||
return fmt.Errorf("get relative path to GOPATH/src failed: %s", err)
|
||
} else {
|
||
arg.Gopkg = gopkg
|
||
}
|
||
}
|
||
if len(arg.Gomod) == 0 { // not specified "go module"
|
||
// search go.mod recursively
|
||
module, path, ok := util.SearchGoMod(arg.Cwd, true)
|
||
if ok { // find go.mod in upper level, use it as project module, don't generate go.mod
|
||
rel, err := filepath.Rel(path, arg.Cwd)
|
||
if err != nil {
|
||
return fmt.Errorf("can not get relative path, err :%v", err)
|
||
}
|
||
arg.Gomod = filepath.Join(module, rel)
|
||
logs.Debugf("find module '%s' from '%s/go.mod', so use it as module name", module, path)
|
||
}
|
||
if len(arg.Gomod) == 0 { // don't find go.mod in upper level, use relative path as module name, generate go.mod
|
||
logs.Debugf("use gopath's relative path '%s' as the module name", arg.Gopkg)
|
||
// gopkg will be "" under non-gopath
|
||
arg.Gomod = arg.Gopkg
|
||
arg.NeedGoMod = true
|
||
}
|
||
arg.Gomod = util.PathToImport(arg.Gomod, "")
|
||
} else { // specified "go module"
|
||
// search go.mod in current path
|
||
module, path, ok := util.SearchGoMod(arg.Cwd, false)
|
||
if ok { // go.mod exists in current path, check module name, don't generate go.mod
|
||
if module != arg.Gomod {
|
||
return fmt.Errorf("module name given by the '-module/mod' option ('%s') is not consist with the name defined in go.mod ('%s' from %s), try to remove '-module/mod' option in your command\n", arg.Gomod, module, path)
|
||
}
|
||
} else { // go.mod don't exist in current path, generate go.mod
|
||
arg.NeedGoMod = true
|
||
}
|
||
}
|
||
|
||
if len(arg.Gomod) == 0 {
|
||
return fmt.Errorf("can not get go module, please specify a module name with the '-module/mod' flag")
|
||
}
|
||
|
||
if len(arg.RawOptPkg) > 0 {
|
||
arg.OptPkgMap = make(map[string]string, len(arg.RawOptPkg))
|
||
for _, op := range arg.RawOptPkg {
|
||
ps := strings.SplitN(op, "=", 2)
|
||
if len(ps) != 2 {
|
||
return fmt.Errorf("invalid option package: %s", op)
|
||
}
|
||
arg.OptPkgMap[ps[0]] = ps[1]
|
||
}
|
||
arg.RawOptPkg = nil
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (arg *Argument) Pack() ([]string, error) {
|
||
data, err := util.PackArgs(arg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("pack argument failed: %s", err)
|
||
}
|
||
return data, nil
|
||
}
|
||
|
||
func (arg *Argument) Unpack(data []string) error {
|
||
err := util.UnpackArgs(data, arg)
|
||
if err != nil {
|
||
return fmt.Errorf("unpack argument failed: %s", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Fork can copy its own parameters to a new argument
|
||
func (arg *Argument) Fork() *Argument {
|
||
args := NewArgument()
|
||
*args = *arg
|
||
util.CopyString2StringMap(arg.OptPkgMap, args.OptPkgMap)
|
||
util.CopyStringSlice(&arg.Includes, &args.Includes)
|
||
util.CopyStringSlice(&arg.Excludes, &args.Excludes)
|
||
util.CopyStringSlice(&arg.ProtocOptions, &args.ProtocOptions)
|
||
util.CopyStringSlice(&arg.ThriftOptions, &args.ThriftOptions)
|
||
return args
|
||
}
|
||
|
||
func (arg *Argument) GetGoPackage() (string, error) {
|
||
if arg.Gomod != "" {
|
||
return arg.Gomod, nil
|
||
} else if arg.Gopkg != "" {
|
||
return arg.Gopkg, nil
|
||
}
|
||
return "", fmt.Errorf("project package name is not set")
|
||
}
|
||
|
||
func IdlTypeToCompiler(idlType string) (string, error) {
|
||
switch idlType {
|
||
case meta.IdlProto:
|
||
return meta.TpCompilerProto, nil
|
||
case meta.IdlThrift:
|
||
return meta.TpCompilerThrift, nil
|
||
default:
|
||
return "", fmt.Errorf("IDL type %s is not supported", idlType)
|
||
}
|
||
}
|
||
|
||
func (arg *Argument) ModelPackagePrefix() (string, error) {
|
||
ret := arg.Gomod
|
||
if arg.ModelDir == "" {
|
||
path, err := util.RelativePath(meta.ModelDir)
|
||
if !strings.HasPrefix(path, "/") {
|
||
path = "/" + path
|
||
}
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
ret += path
|
||
} else {
|
||
path, err := util.RelativePath(arg.ModelDir)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
ret += "/" + path
|
||
}
|
||
return strings.ReplaceAll(ret, string(filepath.Separator), "/"), nil
|
||
}
|
||
|
||
func (arg *Argument) ModelOutDir() string {
|
||
ret := arg.OutDir
|
||
if arg.ModelDir == "" {
|
||
ret = filepath.Join(ret, meta.ModelDir)
|
||
} else {
|
||
ret = filepath.Join(ret, arg.ModelDir)
|
||
}
|
||
return ret
|
||
}
|
||
|
||
func (arg *Argument) GetHandlerDir() (string, error) {
|
||
if arg.HandlerDir == "" {
|
||
return util.RelativePath(meta.HandlerDir)
|
||
}
|
||
return util.RelativePath(arg.HandlerDir)
|
||
}
|
||
|
||
func (arg *Argument) GetModelDir() (string, error) {
|
||
if arg.ModelDir == "" {
|
||
return util.RelativePath(meta.ModelDir)
|
||
}
|
||
return util.RelativePath(arg.ModelDir)
|
||
}
|
||
|
||
func (arg *Argument) GetRouterDir() (string, error) {
|
||
if arg.RouterDir == "" {
|
||
return util.RelativePath(meta.RouterDir)
|
||
}
|
||
return util.RelativePath(arg.RouterDir)
|
||
}
|
||
|
||
func (arg *Argument) GetClientDir() (string, error) {
|
||
if arg.ClientDir == "" {
|
||
return "", nil
|
||
}
|
||
return util.RelativePath(arg.ClientDir)
|
||
}
|
||
|
||
func (arg *Argument) InitManifest(m *meta.Manifest) {
|
||
m.Version = meta.Version
|
||
m.HandlerDir = arg.HandlerDir
|
||
m.ModelDir = arg.ModelDir
|
||
m.RouterDir = arg.RouterDir
|
||
}
|
||
|
||
func (arg *Argument) UpdateManifest(m *meta.Manifest) {
|
||
m.Version = meta.Version
|
||
if arg.HandlerDir != m.HandlerDir {
|
||
m.HandlerDir = arg.HandlerDir
|
||
}
|
||
if arg.HandlerDir != m.ModelDir {
|
||
m.ModelDir = arg.ModelDir
|
||
}
|
||
// "router_dir" must not be defined by "update" command
|
||
}
|