/
command_config.go
353 lines (319 loc) · 13.5 KB
/
command_config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
package model
import (
"fmt"
"github.com/jd-ly/cmd"
"github.com/jd-ly/cmd/utils"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/jd-ly/cmd/model/command"
)
// The constants
const (
NEW COMMAND = iota + 1
RUN
BUILD
PACKAGE
CLEAN
TEST
VERSION
)
type (
// The Revel command type
COMMAND int
// The Command config for the line input
CommandConfig struct {
Index COMMAND // The index
Verbose []bool `short:"v" long:"debug" description:"If set the logger is set to verbose"` // True if debug is active
FrameworkVersion *Version // The framework version
CommandVersion *Version // The command version
HistoricMode bool `long:"historic-run-mode" description:"If set the runmode is passed a string not json"` // True if debug is active
ImportPath string // The import path (relative to a GOPATH)
GoPath string // The GoPath
GoCmd string // The full path to the go executable
//SrcRoot string // The source root
AppPath string // The application path (absolute)
AppName string // The application name
HistoricBuildMode bool `long:"historic-build-mode" description:"If set the code is scanned using the original parsers, not the go.1.11+"` // True if debug is active
Vendored bool // True if the application is vendored
PackageResolver func(pkgName string) error // a package resolver for the config
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"`
GoModFlags []string `long:"gomod-flags" description:"These flags will execute go mod commands for each flag, this happens during the build process"`
New command.New `command:"new"`
Build command.Build `command:"build"`
Run command.Run `command:"run"`
Package command.Package `command:"package"`
Clean command.Clean `command:"clean"`
Test command.Test `command:"test"`
Version command.Version `command:"version"`
}
)
// Updates the import path depending on the command
func (c *CommandConfig) UpdateImportPath() error {
var importPath string
required := true
switch c.Index {
case NEW:
importPath = c.New.ImportPath
case RUN:
importPath = c.Run.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case BUILD:
importPath = c.Build.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case PACKAGE:
importPath = c.Package.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case CLEAN:
importPath = c.Clean.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case TEST:
importPath = c.Test.ImportPath
c.Vendored = utils.Exists(filepath.Join(importPath, "go.mod"))
case VERSION:
importPath = c.Version.ImportPath
required = false
}
if len(importPath) == 0 || filepath.IsAbs(importPath) || importPath[0] == '.' {
utils.Logger.Info("Import path is absolute or not specified", "path", importPath)
// Try to determine the import path from the GO paths and the command line
currentPath, err := os.Getwd()
if len(importPath) > 0 {
if importPath[0] == '.' {
// For a relative path
importPath = filepath.Join(currentPath, importPath)
}
// For an absolute path
currentPath, _ = filepath.Abs(importPath)
}
if err == nil {
for _, path := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) {
utils.Logger.Infof("Checking import path %s with %s", currentPath, path)
if strings.HasPrefix(currentPath, path) && len(currentPath) > len(path) + 1 {
importPath = currentPath[len(path) + 1:]
// Remove the source from the path if it is there
if len(importPath) > 4 && (strings.ToLower(importPath[0:4]) == "src/" || strings.ToLower(importPath[0:4]) == "src\\") {
importPath = importPath[4:]
} else if importPath == "src" {
if c.Index != VERSION {
return fmt.Errorf("Invlaid import path, working dir is in GOPATH root")
}
importPath = ""
}
utils.Logger.Info("Updated import path", "path", importPath)
}
}
}
}
c.ImportPath = importPath
// We need the source root determined at this point to check the setversions
c.initAppFolder()
utils.Logger.Info("Returned import path", "path", importPath)
if required && c.Index != NEW {
if err := c.SetVersions(); err != nil {
utils.Logger.Panic("Failed to fetch revel versions", "error", err)
}
if err := c.FrameworkVersion.CompatibleFramework(c); err != nil {
utils.Logger.Fatal("Compatibility Error", "message", err,
"Revel framework version", c.FrameworkVersion.String(), "Revel tool version", c.CommandVersion.String())
}
utils.Logger.Info("Revel versions", "revel-tool", c.CommandVersion.String(), "Revel Framework", c.FrameworkVersion.String())
}
if !required {
return nil
}
if len(importPath) == 0 {
return fmt.Errorf("Unable to determine import path from : %s", importPath)
}
return nil
}
func (c *CommandConfig) initAppFolder() (err error) {
utils.Logger.Info("initAppFolder", "vendored", c.Vendored, "build-gopath", build.Default.GOPATH, "gopath-env", os.Getenv("GOPATH"))
// check for go executable
c.GoCmd, err = exec.LookPath("go")
if err != nil {
utils.Logger.Fatal("Go executable not found in PATH.")
}
// First try to determine where the application is located - this should be the import value
appFolder := c.ImportPath
wd, err := os.Getwd()
if len(appFolder) == 0 {
// We will assume the working directory is the appFolder
appFolder = wd
} else if strings.LastIndex(wd, appFolder) == len(wd) - len(appFolder) {
// Check for existence of an /app folder
if utils.Exists(filepath.Join(wd, "app")) {
appFolder = wd
} else {
appFolder = filepath.Join(wd, appFolder)
}
} else if strings.Contains(appFolder, ".") {
appFolder = filepath.Join(wd, filepath.Base(c.ImportPath))
} else if !filepath.IsAbs(appFolder) {
appFolder = filepath.Join(wd, appFolder)
}
utils.Logger.Info("Determined app folder to be", "appfolder", appFolder, "working", wd, "importPath", c.ImportPath)
// Use app folder to read the go.mod if it exists and extract the package information
goModFile := filepath.Join(appFolder, "go.mod")
utils.Logger.Info("Checking gomod, extracting from file", "path", goModFile,"exists", utils.Exists(goModFile))
if utils.Exists(goModFile) {
c.Vendored = true
utils.Logger.Info("Found go mod, extracting from file", "path", goModFile)
file, err := ioutil.ReadFile(goModFile)
if err != nil {
return err
}
for _, line := range strings.Split(string(file), "\n") {
if strings.Index(line, "module ") == 0 {
c.ImportPath = strings.TrimSpace(strings.Split(line, "module")[1])
c.AppPath = appFolder
//c.SrcRoot = appFolder
utils.Logger.Info("Set application path and package based on go mod", "path", c.AppPath)
return nil
}
}
// c.SrcRoot = appFolder
c.AppPath = appFolder
} else if c.Index != NEW || (c.Index == NEW && c.New.NotVendored) {
workingDir, _ := os.Getwd()
goPathList := filepath.SplitList(c.GoPath)
bestpath := ""
for _, path := range goPathList {
if c.Index == NEW {
// If the GOPATH is part of the working dir this is the most likely target
if strings.HasPrefix(workingDir, path) {
bestpath = path
}
} else {
if utils.Exists(filepath.Join(path, "src", c.ImportPath)) {
bestpath = path
break
}
}
}
utils.Logger.Info("Source root", "cwd", workingDir, "gopath", c.GoPath, "c.ImportPath", c.ImportPath, "bestpath", bestpath)
if len(bestpath) > 0 {
c.AppPath = filepath.Join(bestpath, "src", c.ImportPath)
}
// Recalculate the appFolder because we are using a GOPATH
} else {
// This is new and not vendored, so the app path is the appFolder
c.AppPath = appFolder
}
utils.Logger.Info("Set application path", "path", c.AppPath, "vendored",c.Vendored, "importpath",c.ImportPath)
return nil
}
// Used to initialize the package resolver
func (c *CommandConfig) InitPackageResolver() {
c.initGoPaths()
utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath)
// This should get called when needed
c.PackageResolver = func(pkgName string) error {
utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored)
var getCmd *exec.Cmd
print("Downloading related packages ...")
if c.Vendored {
getCmd = exec.Command(c.GoCmd, "mod", "tidy")
} else {
utils.Logger.Info("No vendor folder detected, not using dependency manager to import package", "package", pkgName)
getCmd = exec.Command(c.GoCmd, "get", "-u", pkgName)
}
utils.CmdInit(getCmd, !c.Vendored, c.AppPath)
utils.Logger.Info("Go get command ", "exec", getCmd.Path, "dir", getCmd.Dir, "args", getCmd.Args, "env", getCmd.Env, "package", pkgName)
output, err := getCmd.CombinedOutput()
if err != nil {
utils.Logger.Error("Failed to import package", "error", err, "gopath", build.Default.GOPATH, "GO-ROOT", build.Default.GOROOT, "output", string(output))
}
println(" completed.")
return nil
}
}
// lookup and set Go related variables
func (c *CommandConfig) initGoPaths() {
utils.Logger.Info("InitGoPaths", "vendored", c.Vendored)
// check for go executable
var err error
c.GoCmd, err = exec.LookPath("go")
if err != nil {
utils.Logger.Fatal("Go executable not found in PATH.")
}
if c.Vendored {
return
}
// lookup go path
c.GoPath = build.Default.GOPATH
if c.GoPath == "" {
utils.Logger.Fatal("Abort: GOPATH environment variable is not set. " +
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
}
return
//todo determine if the rest needs to happen
// revel/revel#1004 choose go path relative to current working directory
// What we want to do is to add the import to the end of the
// gopath, and discover which import exists - If none exist this is an error except in the case
// where we are dealing with new which is a special case where we will attempt to target the working directory first
/*
// If source root is empty and this isn't a version then skip it
if len(c.SrcRoot) == 0 {
if c.Index == NEW {
c.SrcRoot = c.New.ImportPath
} else {
if c.Index != VERSION {
utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.")
}
return
}
}
// set go src path
c.SrcRoot = filepath.Join(c.SrcRoot, "src")
c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath))
utils.Logger.Info("Set application path", "path", c.AppPath)
*/
}
// Sets the versions on the command config
func (c *CommandConfig) SetVersions() (err error) {
c.CommandVersion, _ = ParseVersion(cmd.Version)
pathMap, err := utils.FindSrcPaths(c.AppPath, []string{RevelImportPath}, c.PackageResolver)
if err == nil {
utils.Logger.Info("Fullpath to revel", "dir", pathMap[RevelImportPath])
fset := token.NewFileSet() // positions are relative to fset
versionData, err := ioutil.ReadFile(filepath.Join(pathMap[RevelImportPath], "version.go"))
if err != nil {
utils.Logger.Error("Failed to find Revel version:", "error", err, "path", pathMap[RevelImportPath])
}
// Parse src but stop after processing the imports.
f, err := parser.ParseFile(fset, "", versionData, parser.ParseComments)
if err != nil {
return utils.NewBuildError("Failed to parse Revel version error:", "error", err)
}
// Print the imports from the file's AST.
for _, s := range f.Decls {
genDecl, ok := s.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok != token.CONST {
continue
}
for _, a := range genDecl.Specs {
spec := a.(*ast.ValueSpec)
r := spec.Values[0].(*ast.BasicLit)
if spec.Names[0].Name == "Version" {
c.FrameworkVersion, err = ParseVersion(strings.Replace(r.Value, `"`, ``, -1))
if err != nil {
utils.Logger.Errorf("Failed to parse version")
} else {
utils.Logger.Info("Parsed revel version", "version", c.FrameworkVersion.String())
}
}
}
}
}
return
}