github.com/mlmmr/revel-cmd@v0.21.2-0.20191112133115-68d8795776dd/model/command_config.go (about) 1 package model 2 3 import ( 4 "fmt" 5 "github.com/mlmmr/revel-cmd" 6 "github.com/mlmmr/revel-cmd/logger" 7 "github.com/mlmmr/revel-cmd/utils" 8 "go/ast" 9 "go/build" 10 "go/parser" 11 "go/token" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strings" 17 ) 18 19 // The constants 20 const ( 21 NEW COMMAND = iota + 1 22 RUN 23 BUILD 24 PACKAGE 25 CLEAN 26 TEST 27 VERSION 28 ) 29 30 type ( 31 // The Revel command type 32 COMMAND int 33 34 // The Command config for the line input 35 CommandConfig struct { 36 Index COMMAND // The index 37 Verbose []bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active 38 FrameworkVersion *Version // The framework version 39 CommandVersion *Version // The command version 40 HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active 41 ImportPath string // The import path (relative to a GOPATH) 42 GoPath string // The GoPath 43 GoCmd string // The full path to the go executable 44 SrcRoot string // The source root 45 AppPath string // The application path (absolute) 46 AppName string // The application name 47 Vendored bool // True if the application is vendored 48 PackageResolver func(pkgName string) error // a packge resolver for the config 49 BuildFlags []string `short:"X" long:"build-flags" description:"These flags will be used when building the application. May be specified multiple times, only applicable for Build, Run, Package, Test commands"` 50 // The new command 51 New struct { 52 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 53 SkeletonPath string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"` 54 Vendored bool `short:"V" long:"vendor" description:"True if project should contain a vendor folder to be initialized. Creates the vendor folder and the 'Gopkg.toml' file in the root"` 55 Run bool `short:"r" long:"run" description:"True if you want to run the application right away"` 56 } `command:"new"` 57 // The build command 58 Build struct { 59 TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"false"` 60 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 61 Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 62 CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"` 63 } `command:"build"` 64 // The run command 65 Run struct { 66 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 67 Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 68 Port int `short:"p" long:"port" default:"-1" description:"The port to listen" ` 69 NoProxy bool `short:"n" long:"no-proxy" description:"True if proxy server should not be started. This will only update the main and routes files on change"` 70 } `command:"run"` 71 // The package command 72 Package struct { 73 TargetPath string `short:"t" long:"target-path" description:"Full path and filename of target package to deploy" required:"false"` 74 Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 75 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 76 CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"` 77 } `command:"package"` 78 // The clean command 79 Clean struct { 80 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 81 } `command:"clean"` 82 // The test command 83 Test struct { 84 Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` 85 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 86 Function string `short:"f" long:"suite-function" description:"The suite.function"` 87 } `command:"test"` 88 // The version command 89 Version struct { 90 ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` 91 Update bool `short:"u" long:"update" description:"Update the framework and modules" required:"false"` 92 } `command:"version"` 93 } 94 ) 95 96 // Updates the import path depending on the command 97 func (c *CommandConfig) UpdateImportPath() error { 98 var importPath string 99 required := true 100 switch c.Index { 101 case NEW: 102 importPath = c.New.ImportPath 103 case RUN: 104 importPath = c.Run.ImportPath 105 case BUILD: 106 importPath = c.Build.ImportPath 107 case PACKAGE: 108 importPath = c.Package.ImportPath 109 case CLEAN: 110 importPath = c.Clean.ImportPath 111 case TEST: 112 importPath = c.Test.ImportPath 113 case VERSION: 114 importPath = c.Version.ImportPath 115 required = false 116 } 117 118 if len(importPath) == 0 || filepath.IsAbs(importPath) || importPath[0] == '.' { 119 utils.Logger.Info("Import path is absolute or not specified", "path", importPath) 120 // Try to determine the import path from the GO paths and the command line 121 currentPath, err := os.Getwd() 122 if len(importPath) > 0 { 123 if importPath[0] == '.' { 124 // For a relative path 125 importPath = filepath.Join(currentPath, importPath) 126 } 127 // For an absolute path 128 currentPath, _ = filepath.Abs(importPath) 129 } 130 131 if err == nil { 132 for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) { 133 utils.Logger.Infof("Checking import path %s with %s", currentPath, path) 134 if strings.HasPrefix(currentPath, path) && len(currentPath) > len(path)+1 { 135 importPath = currentPath[len(path)+1:] 136 // Remove the source from the path if it is there 137 if len(importPath) > 4 && strings.ToLower(importPath[0:4]) == "src/" { 138 importPath = importPath[4:] 139 } else if importPath == "src" { 140 if c.Index != VERSION { 141 return fmt.Errorf("Invlaid import path, working dir is in GOPATH root") 142 } 143 importPath = "" 144 } 145 utils.Logger.Info("Updated import path", "path", importPath) 146 } 147 } 148 } 149 } 150 151 c.ImportPath = importPath 152 utils.Logger.Info("Returned import path", "path", importPath, "buildpath", build.Default.GOPATH) 153 if required && c.Index != NEW { 154 if err := c.SetVersions(); err != nil { 155 utils.Logger.Panic("Failed to fetch revel versions", "error", err) 156 } 157 if err:=c.FrameworkVersion.CompatibleFramework(c);err!=nil { 158 utils.Logger.Fatal("Compatibility Error", "message", err, 159 "Revel framework version", c.FrameworkVersion.String(), "Revel tool version", c.CommandVersion.String()) 160 } 161 utils.Logger.Info("Revel versions", "revel-tool", c.CommandVersion.String(), "Revel Framework", c.FrameworkVersion.String()) 162 } 163 if !required { 164 return nil 165 } 166 if len(importPath) == 0 { 167 return fmt.Errorf("Unable to determine import path from : %s", importPath) 168 } 169 return nil 170 } 171 172 // Used to initialize the package resolver 173 func (c *CommandConfig) InitPackageResolver() { 174 c.Vendored = utils.DirExists(filepath.Join(c.AppPath, "vendor")) 175 if c.Index == NEW && c.New.Vendored { 176 c.Vendored = true 177 } 178 179 utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath) 180 181 var ( 182 depPath string 183 err error 184 ) 185 186 if c.Vendored { 187 utils.Logger.Info("Vendor folder detected, scanning for deps in path") 188 depPath, err = exec.LookPath("dep") 189 if err != nil { 190 // Do not halt build unless a new package needs to be imported 191 utils.Logger.Fatal("Build: `dep` executable not found in PATH, but vendor folder detected." + 192 "Packages can only be added automatically to the vendor folder using the `dep` tool. " + 193 "You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`") 194 } 195 } 196 197 // This should get called when needed 198 c.PackageResolver = func(pkgName string) error { 199 //useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor")) 200 201 var getCmd *exec.Cmd 202 utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored) 203 if c.Vendored { 204 utils.Logger.Info("Using dependency manager to import package", "package", pkgName) 205 206 if depPath == "" { 207 utils.Logger.Error("Build: Vendor folder found, but the `dep` tool was not found, " + 208 "if you use a different vendoring (package management) tool please add the following packages by hand, " + 209 "or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " + 210 "For more information and usage of the tool please see http://github.com/golang/dep") 211 utils.Logger.Error("Missing package", "package", pkgName) 212 return fmt.Errorf("Missing package %s", pkgName) 213 } 214 // Check to see if the package exists locally 215 _, err := build.Import(pkgName, c.AppPath, build.FindOnly) 216 if err != nil { 217 getCmd = exec.Command(depPath, "ensure", "-add", pkgName) 218 } else { 219 getCmd = exec.Command(depPath, "ensure", "-update", pkgName) 220 } 221 222 223 } else { 224 utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName) 225 getCmd = exec.Command(c.GoCmd, "get", "-u", pkgName) 226 } 227 228 utils.CmdInit(getCmd, c.AppPath) 229 utils.Logger.Info("Go get command ", "exec", getCmd.Path, "dir", getCmd.Dir, "args", getCmd.Args, "env", getCmd.Env, "package", pkgName) 230 output, err := getCmd.CombinedOutput() 231 if err != nil { 232 fmt.Printf("Error stack %v\n", logger.NewCallStack()) 233 utils.Logger.Error("Failed to import package", "error", err, "gopath", build.Default.GOPATH, "GO-ROOT", build.Default.GOROOT, "output", string(output)) 234 } 235 return err 236 } 237 } 238 239 // lookup and set Go related variables 240 func (c *CommandConfig) InitGoPaths() { 241 utils.Logger.Info("InitGoPaths") 242 // lookup go path 243 c.GoPath = build.Default.GOPATH 244 if c.GoPath == "" { 245 utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " + 246 "Please refer to http://golang.org/doc/code.html to configure your Go environment.") 247 } 248 249 // check for go executable 250 var err error 251 c.GoCmd, err = exec.LookPath("go") 252 if err != nil { 253 utils.Logger.Fatal("Go executable not found in PATH.") 254 } 255 256 // revel/revel#1004 choose go path relative to current working directory 257 258 // What we want to do is to add the import to the end of the 259 // gopath, and discover which import exists - If none exist this is an error except in the case 260 // where we are dealing with new which is a special case where we will attempt to target the working directory first 261 workingDir, _ := os.Getwd() 262 goPathList := filepath.SplitList(c.GoPath) 263 bestpath := "" 264 for _, path := range goPathList { 265 if c.Index == NEW { 266 // If the GOPATH is part of the working dir this is the most likely target 267 if strings.HasPrefix(workingDir, path) { 268 bestpath = path 269 } 270 } else { 271 if utils.Exists(filepath.Join(path, "src", c.ImportPath)) { 272 c.SrcRoot = path 273 break 274 } 275 } 276 } 277 278 utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath, "bestpath",bestpath) 279 if len(c.SrcRoot) == 0 && len(bestpath) > 0 { 280 c.SrcRoot = bestpath 281 } 282 283 // If source root is empty and this isn't a version then skip it 284 if len(c.SrcRoot) == 0 { 285 if c.Index != VERSION { 286 utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.") 287 } 288 return 289 } 290 291 // set go src path 292 c.SrcRoot = filepath.Join(c.SrcRoot, "src") 293 294 c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath)) 295 utils.Logger.Info("Set application path", "path", c.AppPath) 296 } 297 298 // Sets the versions on the command config 299 func (c *CommandConfig) SetVersions() (err error) { 300 c.CommandVersion, _ = ParseVersion(cmd.Version) 301 _, revelPath, err := utils.FindSrcPaths(c.ImportPath, RevelImportPath, c.PackageResolver) 302 if err == nil { 303 utils.Logger.Info("Fullpath to revel", "dir", revelPath) 304 fset := token.NewFileSet() // positions are relative to fset 305 306 versionData, err := ioutil.ReadFile(filepath.Join(revelPath, RevelImportPath, "version.go")) 307 if err != nil { 308 utils.Logger.Error("Failed to find Revel version:", "error", err, "path", revelPath) 309 } 310 311 // Parse src but stop after processing the imports. 312 f, err := parser.ParseFile(fset, "", versionData, parser.ParseComments) 313 if err != nil { 314 return utils.NewBuildError("Failed to parse Revel version error:", "error", err) 315 } 316 317 // Print the imports from the file's AST. 318 for _, s := range f.Decls { 319 genDecl, ok := s.(*ast.GenDecl) 320 if !ok { 321 continue 322 } 323 if genDecl.Tok != token.CONST { 324 continue 325 } 326 for _, a := range genDecl.Specs { 327 spec := a.(*ast.ValueSpec) 328 r := spec.Values[0].(*ast.BasicLit) 329 if spec.Names[0].Name == "Version" { 330 c.FrameworkVersion, err = ParseVersion(strings.Replace(r.Value, `"`, ``, -1)) 331 if err != nil { 332 utils.Logger.Errorf("Failed to parse version") 333 } else { 334 utils.Logger.Info("Parsed revel version", "version", c.FrameworkVersion.String()) 335 } 336 } 337 } 338 } 339 } 340 return 341 }