diff --git a/.codebeatsettings b/.codebeatsettings index a99f39e..c5f22a4 100644 --- a/.codebeatsettings +++ b/.codebeatsettings @@ -1,13 +1,13 @@ { "GOLANG": { - "ABC":[25, 35, 50, 70], + "ABC":[33, 38, 50, 70], "ARITY":[5,6,7,8], - "BLOCK_NESTING":[7, 9, 11, 13], - "CYCLO":[20, 30, 45, 60], - "TOO_MANY_IVARS": [20, 25, 40, 45], + "BLOCK_NESTING":[9, 10, 12, 13], + "CYCLO":[30, 35, 45, 60], + "TOO_MANY_IVARS": [28, 30, 40, 45], "TOO_MANY_FUNCTIONS": [20, 30, 40, 50], "TOTAL_COMPLEXITY": [150, 250, 400, 500], "LOC": [100, 175, 250, 320], "TOTAL_LOC": [300, 400, 500, 600] } -} \ No newline at end of file +} diff --git a/.travis.yml b/.travis.yml index f191f77..530d6ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,9 @@ language: go go: - - "1.8.x" - - "1.9.x" - - "1.10.x" - - "1.11.x" + - "1.12.x" + - "1.13.x" + - "1.14.x" - "tip" os: @@ -19,49 +18,48 @@ branches: - master - develop +env: + # Setting environments variables + - GO111MODULE=on install: - # Setting environments variables - export PATH=$PATH:$HOME/gopath/bin - export REVEL_BRANCH="develop" - 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi' - 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"' - - git clone -b $REVEL_BRANCH git://github.com/revel/revel ../revel/ - - git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/ - - go get -t -v github.com/revel/cmd/revel - - go get -u github.com/golang/dep/cmd/dep - - echo $GOPATH - - echo $PATH + # Since travis already checks out go build the commandline tool (revel) + - mkdir $HOME/GOPATH_PROTECTED + - export GOPATH=$HOME/GOPATH_PROTECTED + - go build -o $HOME/gopath/bin/revel github.com/revel/cmd/revel - pwd + - env script: - go test -v github.com/revel/cmd/revel/... # Ensure the new-app flow works (plus the other commands). - - revel version - - revel new my/testapp - - revel test my/testapp - - revel clean my/testapp - - revel build my/testapp build/testapp - - revel build my/testapp build/testapp prod - - revel package my/testapp - - revel package my/testapp prod + #- revel version + #- revel new my/testapp + #- revel test my/testapp + #- revel clean my/testapp + #- revel build my/testapp build/testapp + #- revel build my/testapp build/testapp prod + #- revel package my/testapp + #- revel package my/testapp prod # Ensure the new-app flow works (plus the other commands). - - revel new -a my/testapp2 - - revel test -a my/testapp2 - - revel clean -a my/testapp2 - - revel build -a my/testapp2 -t build/testapp2 - - revel build -a my/testapp2 -t build/testapp2 -m prod - - revel package -a my/testapp2 - - revel package -a my/testapp2 -m prod + - revel new --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v + - revel test --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v + - revel clean --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v + - revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v -t build/testapp2 + - revel build --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v -t build/testapp2 -m prod + - revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v + - revel package --gomod-flags "edit -replace=github.com/revel/revel=github.com/revel/revel@develop" -a my/testapp2 -v -m prod - - revel new -v -a my/testapp3 -V - - revel test -v -a my/testapp3 - - revel clean -v -a my/testapp3 - - revel build -a my/testapp3 -t build/testapp3 - - revel build -a my/testapp3 -t build/testapp3 -m prod - - revel package -a my/testapp3 - - revel package -a my/testapp3 -m prod + # Check build works with no-vendor flag + - cd $GOPATH + - export GO111MODULE=auto + - revel new -a my/testapp2 --no-vendor + - revel test -a my/testapp2 matrix: allow_failures: diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..268f752 --- /dev/null +++ b/go.mod @@ -0,0 +1,30 @@ +module github.com/revel/cmd + +go 1.12 + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/agtorre/gocolorize v1.0.0 + github.com/fsnotify/fsnotify v1.4.7 + github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 // indirect + github.com/jessevdk/go-flags v1.4.0 + github.com/mattn/go-colorable v0.1.6 + github.com/myesui/uuid v1.0.0 // indirect + github.com/pkg/errors v0.9.1 + github.com/revel/config v0.21.0 + github.com/revel/log15 v2.11.20+incompatible + github.com/revel/modules v0.21.0 // indirect + github.com/revel/pathtree v0.0.0-20140121041023-41257a1839e9 // indirect + github.com/revel/revel v0.21.0 + github.com/stretchr/testify v1.4.0 + github.com/twinj/uuid v1.0.0 // indirect + github.com/xeonx/timeago v1.0.0-rc4 // indirect + golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 // indirect + golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect + golang.org/x/tools v0.0.0-20200219054238-753a1d49df85 + gopkg.in/fsnotify/fsnotify.v1 v1.4.7 + gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 + gopkg.in/stack.v0 v0.0.0-20141108040640-9b43fcefddd0 + gopkg.in/stretchr/testify.v1 v1.2.2 // indirect +) diff --git a/harness/app.go b/harness/app.go index 3b855b8..5538bd3 100644 --- a/harness/app.go +++ b/harness/app.go @@ -12,6 +12,7 @@ import ( "os" "os/exec" "time" + "sync" "github.com/revel/cmd/model" "github.com/revel/cmd/utils" @@ -21,15 +22,16 @@ import ( // App contains the configuration for running a Revel app. (Not for the app itself) // Its only purpose is constructing the command to execute. type App struct { - BinaryPath string // Path to the app executable - Port int // Port to pass as a command line argument. - cmd AppCmd // The last cmd returned. - Paths *model.RevelContainer + BinaryPath string // Path to the app executable + Port int // Port to pass as a command line argument. + cmd AppCmd // The last cmd returned. + PackagePathMap map[string]string // Package to directory path map + Paths *model.RevelContainer } // NewApp returns app instance with binary path in it -func NewApp(binPath string, paths *model.RevelContainer) *App { - return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort} +func NewApp(binPath string, paths *model.RevelContainer, packagePathMap map[string]string) *App { + return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort, PackagePathMap:packagePathMap} } // Cmd returns a command to run the app server using the current configuration. @@ -63,8 +65,8 @@ func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelConta func (cmd AppCmd) Start(c *model.CommandConfig) error { listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}} cmd.Stdout = listeningWriter + utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath) utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env) - utils.CmdInit(cmd.Cmd, c.AppPath) if err := cmd.Cmd.Start(); err != nil { utils.Logger.Fatal("Error running:", "error", err) } @@ -72,9 +74,9 @@ func (cmd AppCmd) Start(c *model.CommandConfig) error { select { case exitState := <-cmd.waitChan(): fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port) - err := utils.NewError("","Revel Run Error", "starting your application there was an exception. See terminal output, " + exitState,"") - // TODO pretiffy command line output - // err.MetaError = listeningWriter.getLastOutput() + err := utils.NewError("", "Revel Run Error", "starting your application there was an exception. See terminal output, " + exitState, "") + // TODO pretiffy command line output + // err.MetaError = listeningWriter.getLastOutput() return err case <-time.After(60 * time.Second): @@ -106,10 +108,30 @@ func (cmd AppCmd) Kill() { // server before this can, this check will ensure the process is still running if _, err := os.FindProcess(int(cmd.Process.Pid));err!=nil { // Server has already exited - utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid) + utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid) return } + // Wait for the shutdown channel + waitMutex := &sync.WaitGroup{} + waitMutex.Add(1) + ch := make(chan bool, 1) + go func() { + waitMutex.Done() + s, err := cmd.Process.Wait() + defer func() { + ch <- true + }() + if err != nil { + utils.Logger.Info("Wait failed for process ", "error", err) + } + if s != nil { + utils.Logger.Info("Revel App exited", "state", s.String()) + } + }() + // Wait for the channel to begin waiting + waitMutex.Wait() + // Send an interrupt signal to allow for a graceful shutdown utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid) var err error @@ -128,20 +150,6 @@ func (cmd AppCmd) Kill() { return } - // Wait for the shutdown - ch := make(chan bool, 1) - go func() { - s, err := cmd.Process.Wait() - defer func() { - ch <- true - }() - if err != nil { - utils.Logger.Info("Wait failed for process ", "error", err) - } - if s != nil { - utils.Logger.Info("Revel App exited", "state", s.String()) - } - }() // Use a timer to ensure that the process exits utils.Logger.Info("Waiting to exit") @@ -149,7 +157,7 @@ func (cmd AppCmd) Kill() { case <-ch: return case <-time.After(60 * time.Second): - // Kill the process + // Kill the process utils.Logger.Error( "Revel app failed to exit in 60 seconds - killing.", "processid", cmd.Process.Pid, @@ -198,7 +206,7 @@ func (w *startupListeningWriter) Write(p []byte) (int, error) { w.notifyReady = nil } } - if w.notifyReady!=nil { + if w.notifyReady != nil { w.buffer.Write(p) } return w.dest.Write(p) diff --git a/harness/build.go b/harness/build.go index ea6b7ef..f3a45de 100644 --- a/harness/build.go +++ b/harness/build.go @@ -19,17 +19,25 @@ import ( "time" "github.com/revel/cmd/model" - "github.com/revel/cmd/parser" + _ "github.com/revel/cmd/parser" "github.com/revel/cmd/utils" + "github.com/revel/cmd/parser2" + "github.com/revel/cmd/parser" ) var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"") type ByString []*model.TypeInfo -func (c ByString) Len() int { return len(c) } -func (c ByString) Swap(i, j int) { c[i], c[j] = c[j], c[i] } -func (c ByString) Less(i, j int) bool { return c[i].String() < c[j].String() } +func (c ByString) Len() int { + return len(c) +} +func (c ByString) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} +func (c ByString) Less(i, j int) bool { + return c[i].String() < c[j].String() +} // Build the app: // 1. Generate the the main.go file. @@ -40,7 +48,13 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err // First, clear the generated files (to avoid them messing with ProcessSource). cleanSource(paths, "tmp", "routes") - sourceInfo, err := parser.ProcessSource(paths) + var sourceInfo *model.SourceInfo + + if c.HistoricBuildMode { + sourceInfo, err = parser.ProcessSource(paths) + } else { + sourceInfo, err = parser2.ProcessSource(paths) + } if err != nil { return } @@ -88,33 +102,8 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err utils.Logger.Fatal("Go executable not found in PATH.") } - // Detect if deps tool should be used (is there a vendor folder ?) - useVendor := utils.DirExists(filepath.Join(paths.BasePath, "vendor")) - basePath := paths.BasePath - for !useVendor { - basePath = filepath.Dir(basePath) - found := false - // Check to see if we are still in the GOPATH - for _, gopath := range filepath.SplitList(build.Default.GOPATH) { - if strings.HasPrefix(basePath, gopath) { - found = true - break - } - } - if !found { - break - } else { - useVendor = utils.DirExists(filepath.Join(basePath, "vendor")) - } - } - - pkg, err := build.Default.Import(paths.ImportPath, "", build.FindOnly) - if err != nil { - return - } - - // Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name. - binName := filepath.Join(pkg.BinDir, "revel.d", paths.ImportPath, filepath.Base(paths.BasePath)) + // Binary path is a combination of target/app directory, app's import path and its name. + binName := filepath.Join("target", "app", paths.ImportPath, filepath.Base(paths.BasePath)) // Change binary path for Windows build goos := runtime.GOOS @@ -135,8 +124,21 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err return false } - for { + if len(c.GoModFlags) > 0 { + for _, gomod := range c.GoModFlags { + goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...) + utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) + output, err := goModCmd.CombinedOutput() + utils.Logger.Info("Gomod applied ", "output", string(output)) + // If the build succeeded, we're done. + if err != nil { + utils.Logger.Error("Gomod Failed continuing ", "error", err, "output", string(output)) + } + } + } + + for { appVersion := getAppVersion(paths) if appVersion == "" { appVersion = "noVersionProvided" @@ -151,7 +153,6 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err if len(c.BuildFlags) == 0 { flags = []string{ "build", - "-i", "-ldflags", versionLinkerFlags, "-tags", buildTags, "-o", binName} @@ -159,6 +160,7 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err if !contains(c.BuildFlags, "build") { flags = []string{"build"} } + flags = append(flags, c.BuildFlags...) if !contains(flags, "-ldflags") { ldflags := "-ldflags= " + versionLinkerFlags // Add in build flags @@ -175,31 +177,38 @@ func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err err } } - // This is Go main path - gopath := c.GoPath - for _, o := range paths.ModulePathMap { - gopath += string(filepath.ListSeparator) + o - } + // Add in build flags + flags = append(flags, c.BuildFlags...) // Note: It's not applicable for filepath.* usage flags = append(flags, path.Join(paths.ImportPath, "app", "tmp")) buildCmd := exec.Command(goPath, flags...) - buildCmd.Env = append(os.Environ(), - "GOPATH="+gopath, - ) - utils.CmdInit(buildCmd, c.AppPath) - utils.Logger.Info("Exec:", "args", buildCmd.Args) + if !c.Vendored { + // This is Go main path + gopath := c.GoPath + for _, o := range paths.ModulePathMap { + gopath += string(filepath.ListSeparator) + o.Path + } + + buildCmd.Env = append(os.Environ(), + "GOPATH=" + gopath, + ) + } + utils.CmdInit(buildCmd, !c.Vendored, c.AppPath) + + utils.Logger.Info("Exec:", "args", buildCmd.Args, "working dir", buildCmd.Dir) output, err := buildCmd.CombinedOutput() // If the build succeeded, we're done. if err == nil { utils.Logger.Info("Build successful continuing") - return NewApp(binName, paths), nil + return NewApp(binName, paths, sourceInfo.PackageMap), nil } // Since there was an error, capture the output in case we need to report it stOutput := string(output) + utils.Logger.Infof("Got error on build of app %s", stOutput) // See if it was an import error that we can go get. matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1) @@ -251,7 +260,7 @@ func getAppVersion(paths *model.RevelContainer) string { if (err != nil && os.IsNotExist(err)) || !info.IsDir() { return "" } - gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "--work-tree="+paths.BasePath, "describe", "--always", "--dirty") + gitCmd := exec.Command(gitPath, "--git-dir=" + gitDir, "--work-tree=" + paths.BasePath, "describe", "--always", "--dirty") utils.Logger.Info("Exec:", "args", gitCmd.Args) output, err := gitCmd.Output() @@ -381,7 +390,7 @@ func containsValue(m map[string]string, val string) bool { // Parse the output of the "go build" command. // Return a detailed Error. -func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { +func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceError { errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`). FindSubmatch(output) if errorMatch == nil { @@ -389,7 +398,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { if errorMatch == nil { utils.Logger.Error("Failed to parse build errors", "error", string(output)) - return &utils.Error{ + return &utils.SourceError{ SourceType: "Go code", Title: "Go Compilation Error", Description: "See console for build error.", @@ -416,13 +425,14 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { return newPath } + // Read the source for the offending file. var ( - relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go" - absFilename = findInPaths(relFilename) - line, _ = strconv.Atoi(string(errorMatch[2])) - description = string(errorMatch[4]) - compileError = &utils.Error{ + relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go" + absFilename = findInPaths(relFilename) + line, _ = strconv.Atoi(string(errorMatch[2])) + description = string(errorMatch[4]) + compileError = &utils.SourceError{ SourceType: "Go code", Title: "Go Compilation Error", Path: relFilename, @@ -440,7 +450,7 @@ func newCompileError(paths *model.RevelContainer, output []byte) *utils.Error { fileStr, err := utils.ReadLines(absFilename) if err != nil { compileError.MetaError = absFilename + ": " + err.Error() - utils.Logger.Info("Unable to readlines "+compileError.MetaError, "error", err) + utils.Logger.Info("Unable to readlines " + compileError.MetaError, "error", err) return compileError } diff --git a/harness/harness.go b/harness/harness.go index 546016e..fd6397c 100644 --- a/harness/harness.go +++ b/harness/harness.go @@ -34,6 +34,7 @@ import ( "html/template" "io/ioutil" "sync" + "encoding/json" ) var ( @@ -89,12 +90,12 @@ func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err erro fmt.Fprintf(iw, "An error ocurred %s", err.Error()) return } - var revelError *utils.Error + var revelError *utils.SourceError switch e := err.(type) { - case *utils.Error: + case *utils.SourceError: revelError = e case error: - revelError = &utils.Error{ + revelError = &utils.SourceError{ Title: "Server Error", Description: e.Error(), } @@ -161,6 +162,7 @@ func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode str addr := paths.HTTPAddr port := paths.Config.IntDefault("harness.port", 0) scheme := "http" + if paths.HTTPSsl { scheme = "https" } @@ -199,7 +201,7 @@ func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode str // Refresh method rebuilds the Revel application and run it on the given port. // called by the watcher -func (h *Harness) Refresh() (err *utils.Error) { +func (h *Harness) Refresh() (err *utils.SourceError) { // Allow only one thread to rebuild the process // If multiple requests to rebuild are queued only the last one is executed on // So before a build is started we wait for a second to determine if @@ -217,10 +219,10 @@ func (h *Harness) Refresh() (err *utils.Error) { h.app, newErr = Build(h.config, h.paths) if newErr != nil { utils.Logger.Error("Build detected an error", "error", newErr) - if castErr, ok := newErr.(*utils.Error); ok { + if castErr, ok := newErr.(*utils.SourceError); ok { return castErr } - err = &utils.Error{ + err = &utils.SourceError{ Title: "App failed to start up", Description: err.Error(), } @@ -229,12 +231,22 @@ func (h *Harness) Refresh() (err *utils.Error) { if h.useProxy { h.app.Port = h.port - if err2 := h.app.Cmd(h.runMode).Start(h.config); err2 != nil { + runMode := h.runMode + if !h.config.HistoricMode { + // Recalulate run mode based on the config + var paths []byte + if len(h.app.PackagePathMap)>0 { + paths, _ = json.Marshal(h.app.PackagePathMap) + } + runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.Verbose, string(paths)) + + } + if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil { utils.Logger.Error("Could not start application", "error", err2) - if err,k :=err2.(*utils.Error);k { + if err,k :=err2.(*utils.SourceError);k { return err } - return &utils.Error{ + return &utils.SourceError{ Title: "App failed to start up", Description: err2.Error(), } diff --git a/logger/revel_logger.go b/logger/revel_logger.go index b3a6f86..0097421 100644 --- a/logger/revel_logger.go +++ b/logger/revel_logger.go @@ -81,7 +81,7 @@ func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger { // Create a new logger func New(ctx ...interface{}) MultiLogger { r := &RevelLogger{Logger: log15.New(ctx...)} - r.SetStackDepth(1) + r.SetStackDepth(0) return r } @@ -99,7 +99,7 @@ func (c callHandler) Log(log *log15.Record) error { ctx := log.Ctx var ctxMap ContextMap if len(ctx) > 0 { - ctxMap = make(ContextMap, len(ctx)/2) + ctxMap = make(ContextMap, len(ctx) / 2) for i := 0; i < len(ctx); i += 2 { v := ctx[i] @@ -108,8 +108,8 @@ func (c callHandler) Log(log *log15.Record) error { key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v) } var value interface{} - if len(ctx) > i+1 { - value = ctx[i+1] + if len(ctx) > i + 1 { + value = ctx[i + 1] } else { value = "LOGGER_VALUE_MISSING" } diff --git a/model/command/build.go b/model/command/build.go new file mode 100644 index 0000000..61e3cbb --- /dev/null +++ b/model/command/build.go @@ -0,0 +1,10 @@ +package command +type ( + Build struct { + ImportCommand + TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"false"` + Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` + CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"` + } + +) diff --git a/model/command/clean.go b/model/command/clean.go new file mode 100644 index 0000000..644cca7 --- /dev/null +++ b/model/command/clean.go @@ -0,0 +1,6 @@ +package command +type ( + Clean struct { + ImportCommand + } +) diff --git a/model/command/import_command.go b/model/command/import_command.go new file mode 100644 index 0000000..8e86f29 --- /dev/null +++ b/model/command/import_command.go @@ -0,0 +1,7 @@ +package command + +type ( + ImportCommand struct { + ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` + } +) diff --git a/model/command/new.go b/model/command/new.go new file mode 100644 index 0000000..5ff685f --- /dev/null +++ b/model/command/new.go @@ -0,0 +1,14 @@ +package command + + +type ( + New struct { + ImportCommand + SkeletonPath string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"` + Package string `short:"p" long:"package" description:"The package name, this becomes the repfix to the app name, if defined vendored is set to true" required:"false"` + NotVendored bool `long:"no-vendor" description:"True if project should not be configured with a go.mod, this requires you to have the project on the GOPATH, this is only compatible with go versions v1.12 or older"` + Run bool `short:"r" long:"run" description:"True if you want to run the application right away"` + Callback func() error + } + +) \ No newline at end of file diff --git a/model/command/package.go b/model/command/package.go new file mode 100644 index 0000000..264ea12 --- /dev/null +++ b/model/command/package.go @@ -0,0 +1,9 @@ +package command +type ( + Package struct { + ImportCommand + TargetPath string `short:"t" long:"target-path" description:"Full path and filename of target package to deploy" required:"false"` + Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` + CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"` + } +) \ No newline at end of file diff --git a/model/command/run.go b/model/command/run.go new file mode 100644 index 0000000..1c745e6 --- /dev/null +++ b/model/command/run.go @@ -0,0 +1,9 @@ +package command +type ( + Run struct { + ImportCommand + Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` + Port int `short:"p" long:"port" default:"-1" description:"The port to listen" ` + 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"` + } +) \ No newline at end of file diff --git a/model/command/test_command.go b/model/command/test_command.go new file mode 100644 index 0000000..0822441 --- /dev/null +++ b/model/command/test_command.go @@ -0,0 +1,9 @@ +package command + +type ( + Test struct { + ImportCommand + Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` + Function string `short:"f" long:"suite-function" description:"The suite.function"` + } +) \ No newline at end of file diff --git a/model/command/version.go b/model/command/version.go new file mode 100644 index 0000000..45dca50 --- /dev/null +++ b/model/command/version.go @@ -0,0 +1,8 @@ +package command +type ( + Version struct { + ImportCommand + Update bool `short:"u" long:"update" description:"Update the framework and modules" required:"false"` + UpdateVersion string `long:"update-version" description:"Specify the version the revel and app will be switched to" required:"false"` + } +) diff --git a/model/command_config.go b/model/command_config.go index 93b355b..08f0b90 100644 --- a/model/command_config.go +++ b/model/command_config.go @@ -2,6 +2,8 @@ package model import ( "fmt" + "github.com/revel/cmd" + "github.com/revel/cmd/utils" "go/ast" "go/build" "go/parser" @@ -11,10 +13,7 @@ import ( "os/exec" "path/filepath" "strings" - - "github.com/revel/cmd" - "github.com/revel/cmd/logger" - "github.com/revel/cmd/utils" + "github.com/revel/cmd/model/command" ) // The constants @@ -34,63 +33,29 @@ type ( // 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 - Vendored bool // True if the application is vendored - PackageResolver func(pkgName string) error // a packge 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"` - // The new command - New struct { - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - SkeletonPath string `short:"s" long:"skeleton" description:"Path to skeleton folder (Must exist on GO PATH)" required:"false"` - 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"` - Run bool `short:"r" long:"run" description:"True if you want to run the application right away"` - } `command:"new"` - // The build command - Build struct { - TargetPath string `short:"t" long:"target-path" description:"Path to target folder. Folder will be completely deleted if it exists" required:"false"` - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` - CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"` - } `command:"build"` - // The run command - Run struct { - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` - Port int `short:"p" long:"port" default:"-1" description:"The port to listen" ` - 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"` - } `command:"run"` - // The package command - Package struct { - TargetPath string `short:"t" long:"target-path" description:"Full path and filename of target package to deploy" required:"false"` - Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - CopySource bool `short:"s" long:"include-source" description:"Copy the source code as well"` - } `command:"package"` - // The clean command - Clean struct { - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - } `command:"clean"` - // The test command - Test struct { - Mode string `short:"m" long:"run-mode" description:"The mode to run the application in"` - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - Function string `short:"f" long:"suite-function" description:"The suite.function"` - } `command:"test"` - // The version command - Version struct { - ImportPath string `short:"a" long:"application-path" description:"Path to application folder" required:"false"` - Update bool `short:"u" long:"update" description:"Update the framework and modules" required:"false"` - } `command:"version"` + 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 execut 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"` } ) @@ -103,14 +68,19 @@ func (c *CommandConfig) UpdateImportPath() error { 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 @@ -132,11 +102,10 @@ func (c *CommandConfig) UpdateImportPath() error { 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:] + 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 - isSRC := strings.ToLower(importPath[0:4]) - if len(importPath) > 4 && (isSRC == "src/" || isSRC == "src\\") { + 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 { @@ -151,7 +120,9 @@ func (c *CommandConfig) UpdateImportPath() error { } c.ImportPath = importPath - utils.Logger.Info("Returned import path", "path", importPath, "buildpath", build.Default.GOPATH) + // 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) @@ -171,82 +142,118 @@ func (c *CommandConfig) UpdateImportPath() error { 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") + if utils.Exists(goModFile) { + c.Vendored = true + 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) + return nil +} + // Used to initialize the package resolver func (c *CommandConfig) InitPackageResolver() { - c.Vendored = utils.DirExists(filepath.Join(c.AppPath, "vendor")) - if c.Index == NEW && c.New.Vendored { - c.Vendored = true - } - + c.initGoPaths() utils.Logger.Info("InitPackageResolver", "useVendor", c.Vendored, "path", c.AppPath) - var ( - depPath string - err error - ) - - if c.Vendored { - utils.Logger.Info("Vendor folder detected, scanning for deps in path") - depPath, err = exec.LookPath("dep") - if err != nil { - // Do not halt build unless a new package needs to be imported - utils.Logger.Fatal("Build: `dep` executable not found in PATH, but vendor folder detected." + - "Packages can only be added automatically to the vendor folder using the `dep` tool. " + - "You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`") - } - } - // This should get called when needed c.PackageResolver = func(pkgName string) error { - //useVendor := utils.DirExists(filepath.Join(c.AppPath, "vendor")) - - var getCmd *exec.Cmd utils.Logger.Info("Request for package ", "package", pkgName, "use vendor", c.Vendored) + var getCmd *exec.Cmd if c.Vendored { - utils.Logger.Info("Using dependency manager to import package", "package", pkgName) - - if depPath == "" { - utils.Logger.Error("Build: Vendor folder found, but the `dep` tool was not found, " + - "if you use a different vendoring (package management) tool please add the following packages by hand, " + - "or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " + - "For more information and usage of the tool please see http://github.com/golang/dep") - utils.Logger.Error("Missing package", "package", pkgName) - return fmt.Errorf("Missing package %s", pkgName) - } - // Check to see if the package exists locally - _, err := build.Import(pkgName, c.AppPath, build.FindOnly) - if err != nil { - getCmd = exec.Command(depPath, "ensure", "-add", pkgName) - } else { - getCmd = exec.Command(depPath, "ensure", "-update", pkgName) - } - + 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.AppPath) + 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 { - fmt.Printf("Error stack %v\n", logger.NewCallStack()) utils.Logger.Error("Failed to import package", "error", err, "gopath", build.Default.GOPATH, "GO-ROOT", build.Default.GOROOT, "output", string(output)) } - return err + + return nil } } // lookup and set Go related variables -func (c *CommandConfig) InitGoPaths() { - utils.Logger.Info("InitGoPaths") - // 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.") - } - +func (c *CommandConfig) initGoPaths() { + utils.Logger.Info("InitGoPaths", "vendored", c.Vendored) // check for go executable var err error c.GoCmd, err = exec.LookPath("go") @@ -254,59 +261,59 @@ func (c *CommandConfig) InitGoPaths() { 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 - 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)) { - c.SrcRoot = path - break + /* + // 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 } } - } - utils.Logger.Info("Source root", "path", c.SrcRoot, "cwd", workingDir, "gopath", c.GoPath, "bestpath", bestpath) - if len(c.SrcRoot) == 0 && len(bestpath) > 0 { - c.SrcRoot = bestpath - } + // set go src path + c.SrcRoot = filepath.Join(c.SrcRoot, "src") - // If source root is empty and this isn't a version then skip it - if len(c.SrcRoot) == 0 { - if c.Index != VERSION { - utils.Logger.Fatal("Abort: could not create a Revel application outside of GOPATH.") - } - return - } + c.AppPath = filepath.Join(c.SrcRoot, filepath.FromSlash(c.ImportPath)) + utils.Logger.Info("Set application path", "path", c.AppPath) - // 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) - _, revelPath, err := utils.FindSrcPaths(c.ImportPath, RevelImportPath, c.PackageResolver) + pathMap, err := utils.FindSrcPaths(c.AppPath, []string{RevelImportPath}, c.PackageResolver) if err == nil { - utils.Logger.Info("Fullpath to revel", "dir", revelPath) + utils.Logger.Info("Fullpath to revel", "dir", pathMap[RevelImportPath]) fset := token.NewFileSet() // positions are relative to fset - versionData, err := ioutil.ReadFile(filepath.Join(revelPath, RevelImportPath, "version.go")) + versionData, err := ioutil.ReadFile(filepath.Join(pathMap[RevelImportPath], "version.go")) if err != nil { - utils.Logger.Error("Failed to find Revel version:", "error", err, "path", revelPath) + utils.Logger.Error("Failed to find Revel version:", "error", err, "path", pathMap[RevelImportPath]) } // Parse src but stop after processing the imports. diff --git a/model/revel_container.go b/model/revel_container.go index f15a665..0647208 100644 --- a/model/revel_container.go +++ b/model/revel_container.go @@ -4,13 +4,12 @@ package model import ( "github.com/revel/cmd/utils" "github.com/revel/config" - "go/build" - "errors" "fmt" "path/filepath" "sort" "strings" + "golang.org/x/tools/go/packages" ) type ( @@ -65,7 +64,11 @@ type ( CookieSecure bool // True if cookie is secure SecretStr string // The secret string MimeConfig *config.Context // The mime configuration - ModulePathMap map[string]string // The module path map + ModulePathMap map[string]*ModuleInfo // The module path map + } + ModuleInfo struct { + ImportPath string + Path string } WrappedRevelCallback struct { @@ -96,30 +99,22 @@ var RevelModulesImportPath = "github.com/revel/modules" // This function returns a container object describing the revel application // eventually this type of function will replace the global variables. -func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp *RevelContainer, err error) { - rp = &RevelContainer{ModulePathMap: map[string]string{}} +func NewRevelPaths(mode, importPath, appSrcPath string, callback RevelCallback) (rp *RevelContainer, err error) { + rp = &RevelContainer{ModulePathMap: map[string]*ModuleInfo{}} // Ignore trailing slashes. rp.ImportPath = strings.TrimRight(importPath, "/") - rp.SourcePath = srcPath + rp.SourcePath = appSrcPath rp.RunMode = mode - // If the SourcePath is not specified, find it using build.Import. - var revelSourcePath string // may be different from the app source path - if rp.SourcePath == "" { - rp.SourcePath, revelSourcePath, err = utils.FindSrcPaths(importPath, RevelImportPath, callback.PackageResolver) - if err != nil { - return - } - } else { - // If the SourcePath was specified, assume both Revel and the app are within it. - rp.SourcePath = filepath.Clean(rp.SourcePath) - revelSourcePath = rp.SourcePath + // We always need to determine the paths for files + pathMap, err := utils.FindSrcPaths(appSrcPath, []string{importPath+"/app", RevelImportPath}, callback.PackageResolver) + if err != nil { + return } - + rp.AppPath, rp.RevelPath = pathMap[importPath], pathMap[RevelImportPath] // Setup paths for application - rp.RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath)) - rp.BasePath = filepath.Join(rp.SourcePath, filepath.FromSlash(importPath)) - rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "vendor")) + rp.BasePath = rp.SourcePath + rp.PackageInfo.Vendor = utils.Exists(filepath.Join(rp.BasePath, "go.mod")) rp.AppPath = filepath.Join(rp.BasePath, "app") // Sanity check , ensure app and conf paths exist @@ -188,6 +183,7 @@ func NewRevelPaths(mode, importPath, srcPath string, callback RevelCallback) (rp rp.SecretStr = rp.Config.StringDefault("app.secret", "") callback.FireEvent(REVEL_BEFORE_MODULES_LOADED, nil) + utils.Logger.Info("Loading modules") if err := rp.loadModules(callback); err != nil { return rp, err } @@ -248,9 +244,10 @@ func (rp *RevelContainer) loadModules(callback RevelCallback) (err error) { // Adds a module paths to the container object func (rp *RevelContainer) addModulePaths(name, importPath, modulePath string) { + utils.Logger.Info("Adding module path","name", name,"import path", importPath,"system path", modulePath) if codePath := filepath.Join(modulePath, "app"); utils.DirExists(codePath) { rp.CodePaths = append(rp.CodePaths, codePath) - rp.ModulePathMap[name] = modulePath + rp.ModulePathMap[name] = &ModuleInfo{importPath, modulePath} if viewsPath := filepath.Join(modulePath, "app", "views"); utils.DirExists(viewsPath) { rp.TemplatePaths = append(rp.TemplatePaths, viewsPath) } @@ -273,13 +270,21 @@ func (rp *RevelContainer) ResolveImportPath(importPath string) (string, error) { if rp.Packaged { return filepath.Join(rp.SourcePath, importPath), nil } + config := &packages.Config{ + Mode: packages.LoadSyntax, + Dir:rp.AppPath, + } - modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly) + pkgs, err := packages.Load(config, importPath) + if len(pkgs)==0 { + return "", errors.New("No packages found for import " + importPath +" using app path "+ rp.AppPath) + } +// modPkg, err := build.Import(importPath, rp.AppPath, build.FindOnly) if err != nil { return "", err } - if rp.PackageInfo.Vendor && !strings.HasPrefix(modPkg.Dir,rp.BasePath) { - return "", fmt.Errorf("Module %s was found outside of path %s.",importPath, modPkg.Dir) + if len(pkgs[0].GoFiles)>0 { + return filepath.Dir(pkgs[0].GoFiles[0]), nil } - return modPkg.Dir, nil + return pkgs[0].PkgPath, errors.New("No files found in import path " + importPath) } diff --git a/model/source_info.go b/model/source_info.go index f4c9a4a..836f004 100644 --- a/model/source_info.go +++ b/model/source_info.go @@ -12,14 +12,14 @@ import ( type SourceInfo struct { // StructSpecs lists type info for all structs found under the code paths. // They may be queried to determine which ones (transitively) embed certain types. - StructSpecs []*TypeInfo + StructSpecs []*TypeInfo // ValidationKeys provides a two-level lookup. The keys are: // 1. The fully-qualified function name, // e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action" // 2. Within that func's file, the line number of the (overall) expression statement. // e.g. the line returned from runtime.Caller() // The result of the lookup the name of variable being validated. - ValidationKeys map[string]map[int]string + ValidationKeys map[string]map[int]string // A list of import paths. // Revel notices files with an init() function and imports that package. InitImportPaths []string @@ -28,7 +28,9 @@ type SourceInfo struct { // app/controllers/... that embed (directly or indirectly) revel.Controller controllerSpecs []*TypeInfo // testSuites list the types that constitute the set of application tests. - testSuites []*TypeInfo + testSuites []*TypeInfo + // packageMap a map of import to system directory (if available) + PackageMap map[string]string } // TypesThatEmbed returns all types that (directly or indirectly) embed the @@ -74,7 +76,7 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered utils.Logger.Info("Debug: Skipping adding spec for unexported type", "type", filteredItem.StructName, "package", filteredItem.ImportPath) - filtered = append(filtered[:i], filtered[i+1:]...) + filtered = append(filtered[:i], filtered[i + 1:]...) exit = false break } @@ -97,8 +99,8 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered // Report non controller structures in controller folder. if !found && !strings.HasPrefix(spec.StructName, "Test") { - utils.Logger.Warn("Type found in package: "+packageFilter+ - ", but did not embed from: "+filepath.Base(targetType), + utils.Logger.Warn("Type found in package: " + packageFilter + + ", but did not embed from: " + filepath.Base(targetType), "name", spec.StructName, "importpath", spec.ImportPath, "foundstructures", unfoundNames) } } @@ -110,7 +112,7 @@ func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered // `revel.Controller` func (s *SourceInfo) ControllerSpecs() []*TypeInfo { if s.controllerSpecs == nil { - s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers") + s.controllerSpecs = s.TypesThatEmbed(RevelImportPath + ".Controller", "controllers") } return s.controllerSpecs } @@ -119,7 +121,19 @@ func (s *SourceInfo) ControllerSpecs() []*TypeInfo { // `testing.TestSuite` func (s *SourceInfo) TestSuites() []*TypeInfo { if s.testSuites == nil { - s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite") + s.testSuites = s.TypesThatEmbed(RevelImportPath + "/testing.TestSuite", "testsuite") } return s.testSuites } + +func (s *SourceInfo) Merge(srcInfo2 *SourceInfo) { + s.StructSpecs = append(s.StructSpecs, srcInfo2.StructSpecs...) + s.InitImportPaths = append(s.InitImportPaths, srcInfo2.InitImportPaths...) + for k, v := range srcInfo2.ValidationKeys { + if _, ok := s.ValidationKeys[k]; ok { + utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k) + continue + } + s.ValidationKeys[k] = v + } +} \ No newline at end of file diff --git a/model/version.go b/model/version.go index 60ee3b0..6016285 100644 --- a/model/version.go +++ b/model/version.go @@ -8,19 +8,20 @@ import ( ) type Version struct { - Prefix string - Major int - Minor int - Maintenance int - Suffix string - BuildDate string + Prefix string + Major int + Minor int + Maintenance int + Suffix string + BuildDate string MinGoVersion string } // The compatibility list var frameworkCompatibleRangeList = [][]string{ {"0.0.0", "0.20.0"}, // minimum Revel version to use with this version of the tool - {"0.19.99", "0.30.0"}, // Compatible with Framework V 0.19.99 - 0.30.0 + {"0.19.99", "0.30.0"}, // Compatible with Framework V 0.19.99 - 0.30.0 + {"1.0.0", "1.1.0"}, // Compatible with Framework V 1.0 - 1.1 } // Parses a version like v1.2.3a or 1.2 @@ -70,6 +71,7 @@ func (v *Version) CompatibleFramework(c *CommandConfig) error { if !v.Newer(start) || v.Newer(end) { continue } + // Framework is older then 0.20, turn on historic mode if i == 0 { c.HistoricMode = true @@ -109,7 +111,7 @@ func (v *Version) Newer(o *Version) bool { if v.Maintenance != o.Maintenance { return v.Maintenance > o.Maintenance } - return false + return true } // Convert the version to a string diff --git a/parser/imports.go b/parser/imports.go index 150c6ed..8f40285 100644 --- a/parser/imports.go +++ b/parser/imports.go @@ -42,13 +42,14 @@ func addImports(imports map[string]string, decl ast.Decl, srcDir string) { // 2. Exempt the standard library; their directories always match the package name. // 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly if pkgAlias == "" { + utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH) pkg, err := build.Import(fullPath, srcDir, 0) if err != nil { // We expect this to happen for apps using reverse routing (since we // have not yet generated the routes). Don't log that. if !strings.HasSuffix(fullPath, "/app/routes") { - utils.Logger.Error("Could not find import:", "path", fullPath, "srcPath", srcDir, "error", err) + utils.Logger.Warn("Could not find import:", "path", fullPath, "srcPath", srcDir, "error", err) } continue } else { diff --git a/parser/reflect.go b/parser/reflect.go index b4390f2..212e14e 100644 --- a/parser/reflect.go +++ b/parser/reflect.go @@ -85,7 +85,7 @@ func (pc *processContainer) processPath(path string, info os.FileInfo, err error if err != nil { if errList, ok := err.(scanner.ErrorList); ok { var pos = errList[0].Pos - newError := &utils.Error{ + newError := &utils.SourceError{ SourceType: ".go source", Title: "Go Compilation Error", Path: pos.Filename, diff --git a/parser2/source_info_processor.go b/parser2/source_info_processor.go new file mode 100644 index 0000000..e0e4141 --- /dev/null +++ b/parser2/source_info_processor.go @@ -0,0 +1,379 @@ +package parser2 + +import ( + "github.com/revel/cmd/utils" + "golang.org/x/tools/go/packages" + "github.com/revel/cmd/model" + "go/ast" + "go/token" + "strings" + "path/filepath" +) + +type ( + SourceInfoProcessor struct { + sourceProcessor *SourceProcessor + } +) + +func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor { + return &SourceInfoProcessor{sourceProcessor:sourceProcessor} +} + +func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) { + sourceInfo = &model.SourceInfo{ + ValidationKeys: map[string]map[int]string{}, + } + var ( + isController = strings.HasSuffix(p.PkgPath, "/controllers") || + strings.Contains(p.PkgPath, "/controllers/") + isTest = strings.HasSuffix(p.PkgPath, "/tests") || + strings.Contains(p.PkgPath, "/tests/") + methodMap = map[string][]*model.MethodSpec{} + ) + for _, tree := range p.Syntax { + for _, decl := range tree.Decls { + s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename) + //println("*** checking", p.Fset.Position(decl.Pos()).Filename) + spec, found := s.getStructTypeDecl(decl, p.Fset) + if found { + if isController || isTest { + controllerSpec := s.getControllerSpec(spec, p) + sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec) + } + } else { + // Not a type definition, this could be a method for a controller try to extract that + // Func declaration? + funcDecl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + // This could be a controller action endpoint, check and add if needed + if isController && + funcDecl.Recv != nil && // Must have a receiver + funcDecl.Name.IsExported() && // be public + funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 { + // return one result + if m, receiver := s.getControllerFunc(funcDecl, p); m != nil { + methodMap[receiver] = append(methodMap[receiver], m) + s.sourceProcessor.log.Info("Added method map to ", "receiver", receiver, "method", m.Name) + } + } + // Check for validation + if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 { + sourceInfo.ValidationKeys[p.PkgPath + "." + s.getFuncName(funcDecl)] = lineKeyMap + } + if funcDecl.Name.Name == "init" { + sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath) + } + } + } + } + + // Add the method specs to the struct specs. + for _, spec := range sourceInfo.StructSpecs { + spec.MethodSpecs = methodMap[spec.StructName] + } + + return +} +// Scan app source code for calls to X.Y(), where X is of type *Validation. +// +// Recognize these scenarios: +// - "Y" = "Validation" and is a member of the receiver. +// (The common case for inline validation) +// - "X" is passed in to the func as a parameter. +// (For structs implementing Validated) +// +// The line number to which a validation call is attributed is that of the +// surrounding ExprStmt. This is so that it matches what runtime.Callers() +// reports. +// +// The end result is that we can set the default validation key for each call to +// be the same as the local variable. +func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) (map[int]string) { + var ( + lineKeys = make(map[int]string) + + // Check the func parameters and the receiver's members for the *revel.Validation type. + validationParam = s.getValidationParameter(funcDecl) + ) + + ast.Inspect(funcDecl.Body, func(node ast.Node) bool { + // e.g. c.Validation.Required(arg) or v.Required(arg) + callExpr, ok := node.(*ast.CallExpr) + if !ok { + return true + } + + // e.g. c.Validation.Required or v.Required + funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + + switch x := funcSelector.X.(type) { + case *ast.SelectorExpr: // e.g. c.Validation + if x.Sel.Name != "Validation" { + return true + } + + case *ast.Ident: // e.g. v + if validationParam == nil || x.Obj != validationParam { + return true + } + + default: + return true + } + + if len(callExpr.Args) == 0 { + return true + } + + // Given the validation expression, extract the key. + key := callExpr.Args[0] + switch expr := key.(type) { + case *ast.BinaryExpr: + // If the argument is a binary expression, take the first expression. + // (e.g. c.Validation.Required(myName != "")) + key = expr.X + case *ast.UnaryExpr: + // If the argument is a unary expression, drill in. + // (e.g. c.Validation.Required(!myBool) + key = expr.X + case *ast.BasicLit: + // If it's a literal, skip it. + return true + } + + if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid { + lineKeys[p.Fset.Position(callExpr.End()).Line] = typeExpr.TypeName("") + } else { + s.sourceProcessor.log.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", p.PkgPath, + "line", p.Fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String()) + } + return true + }) + + return lineKeys + +} +// Check to see if there is a *revel.Validation as an argument. +func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object { + for _, field := range funcDecl.Type.Params.List { + starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation + if !ok { + continue + } + + selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation + if !ok { + continue + } + + xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev + if !ok { + continue + } + + if selExpr.Sel.Name == "Validation" && s.sourceProcessor.importMap[xIdent.Name] == model.RevelImportPath { + return field.Names[0].Obj + } + } + return nil +} +func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package) (method *model.MethodSpec, recvTypeName string) { + selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr) + if !ok { + return + } + if selExpr.Sel.Name != "Result" { + return + } + if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || s.sourceProcessor.importMap[pkgIdent.Name] != model.RevelImportPath { + return + } + method = &model.MethodSpec{ + Name: funcDecl.Name.Name, + } + + // Add a description of the arguments to the method. + for _, field := range funcDecl.Type.Params.List { + for _, name := range field.Names { + var importPath string + typeExpr := model.NewTypeExprFromAst(p.Name, field.Type) + if !typeExpr.Valid { + utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, s.getFuncName(funcDecl)) + return // We didn't understand one of the args. Ignore this action. + } + // Local object + if typeExpr.PkgName == p.Name { + importPath = p.PkgPath + } else if typeExpr.PkgName != "" { + var ok bool + if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok { + utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName("")) + } + } + method.Args = append(method.Args, &model.MethodArg{ + Name: name.Name, + TypeExpr: typeExpr, + ImportPath: importPath, + }) + } + } + + // Add a description of the calls to Render from the method. + // Inspect every node (e.g. always return true). + method.RenderCalls = []*model.MethodCall{} + ast.Inspect(funcDecl.Body, func(node ast.Node) bool { + // Is it a function call? + callExpr, ok := node.(*ast.CallExpr) + if !ok { + return true + } + + // Is it calling (*Controller).Render? + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + + // The type of the receiver is not easily available, so just store every + // call to any method called Render. + if selExpr.Sel.Name != "Render" { + return true + } + + // Add this call's args to the renderArgs. + pos := p.Fset.Position(callExpr.Lparen) + methodCall := &model.MethodCall{ + Line: pos.Line, + Names: []string{}, + } + for _, arg := range callExpr.Args { + argIdent, ok := arg.(*ast.Ident) + if !ok { + continue + } + methodCall.Names = append(methodCall.Names, argIdent.Name) + } + method.RenderCalls = append(method.RenderCalls, methodCall) + return true + }) + + var recvType = funcDecl.Recv.List[0].Type + if recvStarType, ok := recvType.(*ast.StarExpr); ok { + recvTypeName = recvStarType.X.(*ast.Ident).Name + } else { + recvTypeName = recvType.(*ast.Ident).Name + } + return +} +func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package) (controllerSpec *model.TypeInfo) { + structType := spec.Type.(*ast.StructType) + + // At this point we know it's a type declaration for a struct. + // Fill in the rest of the info by diving into the fields. + // Add it provisionally to the Controller list -- it's later filtered using field info. + controllerSpec = &model.TypeInfo{ + StructName: spec.Name.Name, + ImportPath: p.PkgPath, + PackageName: p.Name, + } + for _, field := range structType.Fields.List { + // If field.Names is set, it's not an embedded type. + if field.Names != nil { + continue + } + + // A direct "sub-type" has an ast.Field as either: + // Ident { "AppController" } + // SelectorExpr { "rev", "Controller" } + // Additionally, that can be wrapped by StarExprs. + fieldType := field.Type + pkgName, typeName := func() (string, string) { + // Drill through any StarExprs. + for { + if starExpr, ok := fieldType.(*ast.StarExpr); ok { + fieldType = starExpr.X + continue + } + break + } + + // If the embedded type is in the same package, it's an Ident. + if ident, ok := fieldType.(*ast.Ident); ok { + return "", ident.Name + } + + if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok { + if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok { + return pkgIdent.Name, selectorExpr.Sel.Name + } + } + return "", "" + }() + + // If a typename wasn't found, skip it. + if typeName == "" { + continue + } + + // Find the import path for this type. + // If it was referenced without a package name, use the current package import path. + // Else, look up the package's import path by name. + var importPath string + if pkgName == "" { + importPath = p.PkgPath + } else { + var ok bool + if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok { + s.sourceProcessor.log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap) + continue + } + } + + controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{ + ImportPath: importPath, + StructName: typeName, + }) + } + s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath) + return +} +func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + return + } + + if genDecl.Tok != token.TYPE { + return + } + + if len(genDecl.Specs) == 0 { + utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line) + return + } + + spec = genDecl.Specs[0].(*ast.TypeSpec) + _, found = spec.Type.(*ast.StructType) + + return + +} +func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string { + prefix := "" + if funcDecl.Recv != nil { + recvType := funcDecl.Recv.List[0].Type + if recvStarType, ok := recvType.(*ast.StarExpr); ok { + prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")" + } else { + prefix = recvType.(*ast.Ident).Name + } + prefix += "." + } + return prefix + funcDecl.Name.Name +} \ No newline at end of file diff --git a/parser2/source_processor.go b/parser2/source_processor.go new file mode 100644 index 0000000..d7499ee --- /dev/null +++ b/parser2/source_processor.go @@ -0,0 +1,217 @@ +package parser2 + +import ( + "go/ast" + "go/token" + "github.com/revel/cmd/model" + "golang.org/x/tools/go/packages" + "github.com/revel/cmd/utils" + "errors" + + "strings" + "github.com/revel/cmd/logger" +) + +type ( + SourceProcessor struct { + revelContainer *model.RevelContainer + log logger.MultiLogger + packageList []*packages.Package + importMap map[string]string + packageMap map[string]string + sourceInfoProcessor *SourceInfoProcessor + sourceInfo *model.SourceInfo + } +) + +func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.SourceInfo, compileError error) { + utils.Logger.Info("ProcessSource") + processor := NewSourceProcessor(revelContainer) + compileError = processor.parse() + sourceInfo = processor.sourceInfo + if compileError == nil { + processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap) + } + + if false { + compileError = errors.New("Incompleted") + utils.Logger.Panic("Not implemented") + } + return +} + +func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor { + s := &SourceProcessor{revelContainer:revelContainer, log:utils.Logger.New("parser", "SourceProcessor")} + s.sourceInfoProcessor = NewSourceInfoProcessor(s) + return s +} +func (s *SourceProcessor) parse() (compileError error) { + if compileError = s.addPackages(); compileError != nil { + return + } + if compileError = s.addImportMap(); compileError != nil { + return + } + if compileError = s.addSourceInfo(); compileError != nil { + return + } + s.sourceInfo.PackageMap = map[string]string{} + getImportFromMap := func(packagePath string) string { + for path := range s.packageMap { + if strings.Index(path, packagePath) == 0 { + fullPath := s.packageMap[path] + return fullPath[:(len(fullPath) - len(path) + len(packagePath))] + } + } + return "" + } + s.sourceInfo.PackageMap[model.RevelImportPath] = getImportFromMap(model.RevelImportPath) + s.sourceInfo.PackageMap[s.revelContainer.ImportPath] = getImportFromMap(s.revelContainer.ImportPath) + for _, module := range s.revelContainer.ModulePathMap { + s.sourceInfo.PackageMap[module.ImportPath] = getImportFromMap(module.ImportPath) + } + + return +} +func (s *SourceProcessor) addPackages() (err error) { + allPackages := []string{s.revelContainer.ImportPath + "/...", model.RevelImportPath} + for _, module := range s.revelContainer.ModulePathMap { + allPackages = append(allPackages, module.ImportPath + "/...") // +"/app/controllers/...") + } + //allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."} + + config := &packages.Config{ + // ode: packages.NeedSyntax | packages.NeedCompiledGoFiles, + Mode: + packages.NeedTypes | // For compile error + packages.NeedDeps | // To load dependent files + packages.NeedName | // Loads the full package name + packages.NeedSyntax, // To load ast tree (for end points) + //Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | + // packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | + // packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | + // packages.NeedTypesSizes, + + //Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | + // packages.NeedCompiledGoFiles | packages.NeedTypesSizes | + // packages.NeedSyntax | packages.NeedCompiledGoFiles , + //Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles | + // packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // | + // packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo, + //packages.LoadSyntax | packages.NeedDeps, + Dir:s.revelContainer.AppPath, + } + s.packageList, err = packages.Load(config, allPackages...) + s.log.Info("Loaded packages ", "len results", len(s.packageList), "error", err) + return +} +func (s *SourceProcessor) addImportMap() (err error) { + s.importMap = map[string]string{} + s.packageMap = map[string]string{} + for _, p := range s.packageList { + + if len(p.Errors) > 0 { + // Generate a compile error + for _, e := range p.Errors { + if !strings.Contains(e.Msg, "fsnotify") { + err = utils.NewCompileError("", "", e) + } + } + } + for _, tree := range p.Syntax { + for _, decl := range tree.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + + if genDecl.Tok == token.IMPORT { + for _, spec := range genDecl.Specs { + importSpec := spec.(*ast.ImportSpec) + //fmt.Printf("*** import specification %#v\n", importSpec) + var pkgAlias string + if importSpec.Name != nil { + pkgAlias = importSpec.Name.Name + if pkgAlias == "_" { + continue + } + } + quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\"" + fullPath := quotedPath[1 : len(quotedPath) - 1] // Remove the quotes + if pkgAlias == "" { + pkgAlias = fullPath + if index := strings.LastIndex(pkgAlias, "/"); index > 0 { + pkgAlias = pkgAlias[index + 1:] + } + } + s.importMap[pkgAlias] = fullPath + } + } + } + } + } + return +} + +func (s *SourceProcessor) addSourceInfo() (err error) { + for _, p := range s.packageList { + if sourceInfo := s.sourceInfoProcessor.processPackage(p); sourceInfo != nil { + if s.sourceInfo != nil { + s.sourceInfo.Merge(sourceInfo) + } else { + s.sourceInfo = sourceInfo + } + } + } + return +} + +// Add imports to the map from the source dir +//func addImports(imports map[string]string, decl ast.Decl, srcDir string) { +// genDecl, ok := decl.(*ast.GenDecl) +// if !ok { +// return +// } +// +// if genDecl.Tok != token.IMPORT { +// return +// } +// +// for _, spec := range genDecl.Specs { +// importSpec := spec.(*ast.ImportSpec) +// var pkgAlias string +// if importSpec.Name != nil { +// pkgAlias = importSpec.Name.Name +// if pkgAlias == "_" { +// continue +// } +// } +// quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\"" +// fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes +// +// // If the package was not aliased (common case), we have to import it +// // to see what the package name is. +// // TODO: Can improve performance here a lot: +// // 1. Do not import everything over and over again. Keep a cache. +// // 2. Exempt the standard library; their directories always match the package name. +// // 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly +// if pkgAlias == "" { +// +// utils.Logger.Debug("Reading from build", "path", fullPath, "srcPath", srcDir, "gopath", build.Default.GOPATH) +// pkg, err := build.Import(fullPath, srcDir, 0) +// if err != nil { +// // We expect this to happen for apps using reverse routing (since we +// // have not yet generated the routes). Don't log that. +// if !strings.HasSuffix(fullPath, "/app/routes") { +// utils.Logger.Warn("Could not find import:", "path", fullPath, "srcPath", srcDir, "error", err) +// } +// continue +// } else { +// utils.Logger.Debug("Found package in dir", "dir", pkg.Dir, "name", pkg.ImportPath) +// } +// pkgAlias = pkg.Name +// } +// +// imports[pkgAlias] = fullPath +// } +//} \ No newline at end of file diff --git a/revel/build.go b/revel/build.go index 352fc0f..1106cc5 100644 --- a/revel/build.go +++ b/revel/build.go @@ -13,7 +13,6 @@ import ( "github.com/revel/cmd/harness" "github.com/revel/cmd/model" "github.com/revel/cmd/utils" - "go/build" ) var cmdBuild = &Command{ @@ -38,6 +37,12 @@ func init() { // The update config updates the configuration command so that it can run func updateBuildConfig(c *model.CommandConfig, args []string) bool { c.Index = model.BUILD + if c.Build.TargetPath == "" { + c.Build.TargetPath = "target" + } + if len(args) == 0 && c.Build.ImportPath != "" { + return true + } // If arguments were passed in then there must be two if len(args) < 2 { fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long) @@ -54,6 +59,7 @@ func updateBuildConfig(c *model.CommandConfig, args []string) bool { // The main entry point to build application from command line func buildApp(c *model.CommandConfig) (err error) { + appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode if len(c.Build.Mode) > 0 { mode = c.Build.Mode @@ -64,7 +70,7 @@ func buildApp(c *model.CommandConfig) (err error) { c.Build.Mode = mode c.Build.ImportPath = appImportPath - revel_paths, err := model.NewRevelPaths(mode, appImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver)) + revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) if err != nil { return } @@ -89,7 +95,7 @@ func buildApp(c *model.CommandConfig) (err error) { if err != nil { return } - err = buildCopyModules(c, revel_paths, packageFolders) + err = buildCopyModules(c, revel_paths, packageFolders, app) if err != nil { return } @@ -108,7 +114,7 @@ func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model srcPath := filepath.Join(destPath, "src") destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath)) tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath)) - if err = utils.CopyFile(destBinaryPath, app.BinaryPath); err != nil { + if err = utils.CopyFile(destBinaryPath, filepath.Join(revel_paths.BasePath, app.BinaryPath)); err != nil { return } utils.MustChmod(destBinaryPath, 0755) @@ -149,14 +155,14 @@ func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model } // Based on the section copy over the build modules -func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string) (err error) { +func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) { destPath := filepath.Join(c.Build.TargetPath, "src") // Find all the modules used and copy them over. config := revel_paths.Config.Raw() - modulePaths := make(map[string]string) // import path => filesystem path // We should only copy over the section of options what the build is targeted for // We will default to prod + moduleImportList := []string{} for _, section := range config.Sections() { // If the runmode is defined we will only import modules defined for that run mode if c.Build.Mode != "" && c.Build.Mode != section { @@ -171,17 +177,14 @@ func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, if moduleImportPath == "" { continue } + moduleImportList = append(moduleImportList, moduleImportPath) - modPkg, err := build.Import(moduleImportPath, revel_paths.RevelPath, build.FindOnly) - if err != nil { - utils.Logger.Fatalf("Failed to load module %s (%s): %s", key[len("module."):], c.ImportPath, err) - } - modulePaths[moduleImportPath] = modPkg.Dir } } // Copy the the paths for each of the modules - for importPath, fsPath := range modulePaths { + for _, importPath := range moduleImportList { + fsPath := app.PackagePathMap[importPath] utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath) if c.Build.CopySource { err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil) diff --git a/revel/clean.go b/revel/clean.go index 6a210f3..ebbae09 100644 --- a/revel/clean.go +++ b/revel/clean.go @@ -8,7 +8,7 @@ import ( "fmt" "github.com/revel/cmd/model" "github.com/revel/cmd/utils" - "go/build" + "os" "path/filepath" ) @@ -37,6 +37,9 @@ func init() { // Update the clean command configuration, using old method func updateCleanConfig(c *model.CommandConfig, args []string) bool { c.Index = model.CLEAN + if len(args) == 0 && c.Clean.ImportPath != "" { + return true + } if len(args) == 0 { fmt.Fprintf(os.Stderr, cmdClean.Long) return false @@ -47,14 +50,10 @@ func updateCleanConfig(c *model.CommandConfig, args []string) bool { // Clean the source directory of generated files func cleanApp(c *model.CommandConfig) (err error) { - appPkg, err := build.Import(c.ImportPath, "", build.FindOnly) - if err != nil { - utils.Logger.Fatal("Abort: Failed to find import path:", "error", err) - } purgeDirs := []string{ - filepath.Join(appPkg.Dir, "app", "tmp"), - filepath.Join(appPkg.Dir, "app", "routes"), + filepath.Join(c.AppPath, "app", "tmp"), + filepath.Join(c.AppPath, "app", "routes"), } for _, dir := range purgeDirs { diff --git a/revel/clean_test.go b/revel/clean_test.go index 50dae6d..0b6c218 100644 --- a/revel/clean_test.go +++ b/revel/clean_test.go @@ -22,12 +22,12 @@ func TestClean(t *testing.T) { main.Commands[model.NEW].RunWith(c) c.Index = model.TEST main.Commands[model.TEST].RunWith(c) - a.True(utils.Exists(filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go")), - "Missing main from path "+filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go")) + a.True(utils.Exists(filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")), + "Missing main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")) c.Clean.ImportPath = c.ImportPath a.Nil(main.Commands[model.CLEAN].RunWith(c), "Failed to run clean-test") - a.False(utils.Exists(filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go")), - "Did not remove main from path "+filepath.Join(gopath, "src", "clean-test", "app", "tmp", "main.go")) + a.False(utils.Exists(filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")), + "Did not remove main from path "+filepath.Join(gopath, "clean-test", "app", "tmp", "main.go")) }) if !t.Failed() { if err := os.RemoveAll(gopath); err != nil { diff --git a/revel/command_test.go b/revel/command_test.go index 01e52f8..57e8306 100644 --- a/revel/command_test.go +++ b/revel/command_test.go @@ -7,7 +7,9 @@ import ( "github.com/stretchr/testify/assert" "go/build" "os" + "os/exec" "path/filepath" + "fmt" ) // Test that the event handler can be attached and it dispatches the event received @@ -43,10 +45,26 @@ func setup(suffix string, a *assert.Assertions) (string) { // Create a new app for the name func newApp(name string, command model.COMMAND, precall func(c *model.CommandConfig), a *assert.Assertions) *model.CommandConfig { - c := &model.CommandConfig{} + c := &model.CommandConfig{Vendored:true} switch command { case model.NEW: c.New.ImportPath = name + c.New.Callback=func() error { + // On callback we will invoke a specific branch of revel so that it works + + goModCmd := exec.Command("go", "mod", "tidy") + utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) + getOutput, _ := goModCmd.CombinedOutput() + fmt.Printf("Calling go mod tidy %s",string(getOutput)) + + goModCmd = exec.Command("go", "mod", "edit", "-replace=github.com/revel/revel=github.com/revel/revel@develop") + utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) + getOutput, _ = goModCmd.CombinedOutput() + fmt.Printf("Calling go mod edit %v",string(getOutput)) + + + return nil + } case model.BUILD: c.Build.ImportPath = name case model.TEST: @@ -68,7 +86,7 @@ func newApp(name string, command model.COMMAND, precall func(c *model.CommandCon if c.UpdateImportPath()!=nil { a.Fail("Unable to update import path") } - c.InitGoPaths() + c.InitPackageResolver() return c } diff --git a/revel/new.go b/revel/new.go index 84f848e..147d08a 100644 --- a/revel/new.go +++ b/revel/new.go @@ -46,14 +46,23 @@ func init() { // Called when unable to parse the command line automatically and assumes an old launch func updateNewConfig(c *model.CommandConfig, args []string) bool { c.Index = model.NEW + if len(c.New.Package) > 0 { + c.New.NotVendored = false + } + c.Vendored = !c.New.NotVendored + if len(args) == 0 { - fmt.Fprintf(os.Stderr, cmdNew.Long) - return false + if len(c.New.ImportPath) == 0 { + fmt.Fprintf(os.Stderr, cmdNew.Long) + return false + } + return true } c.New.ImportPath = args[0] if len(args) > 1 { c.New.SkeletonPath = args[1] } + return true } @@ -67,7 +76,7 @@ func newApp(c *model.CommandConfig) (err error) { } // checking and setting skeleton - if err=setSkeletonPath(c);err!=nil { + if err = setSkeletonPath(c); err != nil { return } @@ -76,72 +85,26 @@ func newApp(c *model.CommandConfig) (err error) { return utils.NewBuildError("Abort: Unable to create app path.", "path", c.AppPath) } - if c.New.Vendored { - utils.Logger.Info("Creating a new vendor app") - - vendorPath := filepath.Join(c.AppPath, "vendor") - if !utils.DirExists(vendorPath) { - - if err := os.MkdirAll(vendorPath, os.ModePerm); err != nil { - return utils.NewBuildError("Failed to create "+vendorPath, "error", err) - } - } - - // In order for dep to run there needs to be a source file in the folder - tempPath := filepath.Join(c.AppPath, "tmp") - utils.Logger.Info("Checking for temp folder for source code", "path", tempPath) - if !utils.DirExists(tempPath) { - if err := os.MkdirAll(tempPath, os.ModePerm); err != nil { - return utils.NewBuildIfError(err, "Failed to create "+vendorPath) - } - - if err = utils.GenerateTemplate(filepath.Join(tempPath, "main.go"), NEW_MAIN_FILE, nil); err != nil { - return utils.NewBuildIfError(err, "Failed to create main file "+vendorPath) - } - } - - // Create a package template file if it does not exist - packageFile := filepath.Join(c.AppPath, "Gopkg.toml") - utils.Logger.Info("Checking for Gopkg.toml", "path", packageFile) - if !utils.Exists(packageFile) { - utils.Logger.Info("Generating Gopkg.toml", "path", packageFile) - if err := utils.GenerateTemplate(packageFile, VENDOR_GOPKG, nil); err != nil { - return utils.NewBuildIfError(err, "Failed to generate template") - } - } else { - utils.Logger.Info("Package file exists in skeleto, skipping adding") - } - - getCmd := exec.Command("dep", "ensure", "-v") - utils.CmdInit(getCmd, c.AppPath) - - utils.Logger.Info("Exec:", "args", getCmd.Args, "env", getCmd.Env, "workingdir",getCmd.Dir) - getOutput, err := getCmd.CombinedOutput() - if err != nil { - return utils.NewBuildIfError(err, string(getOutput)) - } - } - // checking and setting application if err = setApplicationPath(c); err != nil { return err } - // At this point the versions can be set - c.SetVersions() + + // This kicked off the download of the revel app, not needed for vendor + if !c.Vendored { + // At this point the versions can be set + c.SetVersions() + } // copy files to new app directory - if err = copyNewAppFiles(c);err != nil { + if err = copyNewAppFiles(c); err != nil { return } - // Rerun the dep tool if vendored - if c.New.Vendored { - getCmd := exec.Command("dep", "ensure", "-v") - utils.CmdInit(getCmd, c.AppPath) - utils.Logger.Info("Exec:", "args", getCmd.Args) - getOutput, err := getCmd.CombinedOutput() - if err != nil { - utils.Logger.Fatal(string(getOutput)) + // Run the vendor tool if needed + if c.Vendored { + if err = createModVendor(c); err != nil { + return } } @@ -156,6 +119,72 @@ func newApp(c *model.CommandConfig) (err error) { return } +func createModVendor(c *model.CommandConfig) (err error) { + + utils.Logger.Info("Creating a new mod app") + goModCmd := exec.Command("go", "mod", "init", filepath.Join(c.New.Package, c.AppName)) + + utils.CmdInit(goModCmd, !c.Vendored, c.AppPath) + + utils.Logger.Info("Exec:", "args", goModCmd.Args, "env", goModCmd.Env, "workingdir", goModCmd.Dir) + getOutput, err := goModCmd.CombinedOutput() + if c.New.Callback != nil { + err = c.New.Callback() + } + if err != nil { + return utils.NewBuildIfError(err, string(getOutput)) + } + return +} + +func createDepVendor(c *model.CommandConfig) (err error) { + + utils.Logger.Info("Creating a new vendor app") + + vendorPath := filepath.Join(c.AppPath, "vendor") + if !utils.DirExists(vendorPath) { + + if err := os.MkdirAll(vendorPath, os.ModePerm); err != nil { + return utils.NewBuildError("Failed to create " + vendorPath, "error", err) + } + } + + // In order for dep to run there needs to be a source file in the folder + tempPath := filepath.Join(c.AppPath, "tmp") + utils.Logger.Info("Checking for temp folder for source code", "path", tempPath) + if !utils.DirExists(tempPath) { + if err := os.MkdirAll(tempPath, os.ModePerm); err != nil { + return utils.NewBuildIfError(err, "Failed to create " + vendorPath) + } + + if err = utils.GenerateTemplate(filepath.Join(tempPath, "main.go"), NEW_MAIN_FILE, nil); err != nil { + return utils.NewBuildIfError(err, "Failed to create main file " + vendorPath) + } + } + + // Create a package template file if it does not exist + packageFile := filepath.Join(c.AppPath, "Gopkg.toml") + utils.Logger.Info("Checking for Gopkg.toml", "path", packageFile) + if !utils.Exists(packageFile) { + utils.Logger.Info("Generating Gopkg.toml", "path", packageFile) + if err := utils.GenerateTemplate(packageFile, VENDOR_GOPKG, nil); err != nil { + return utils.NewBuildIfError(err, "Failed to generate template") + } + } else { + utils.Logger.Info("Package file exists in skeleto, skipping adding") + } + + getCmd := exec.Command("dep", "ensure", "-v") + utils.CmdInit(getCmd, !c.Vendored, c.AppPath) + + utils.Logger.Info("Exec:", "args", getCmd.Args, "env", getCmd.Env, "workingdir", getCmd.Dir) + getOutput, err := getCmd.CombinedOutput() + if err != nil { + return utils.NewBuildIfError(err, string(getOutput)) + } + return +} + // Used to generate a new secret key const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -180,13 +209,13 @@ func setApplicationPath(c *model.CommandConfig) (err error) { } // If we are running a vendored version of Revel we do not need to check for it. - if !c.New.Vendored { + if !c.Vendored { _, err = build.Import(model.RevelImportPath, "", build.FindOnly) if err != nil { //// Go get the revel project err = c.PackageResolver(model.RevelImportPath) if err != nil { - return utils.NewBuildIfError(err, "Failed to fetch revel "+model.RevelImportPath) + return utils.NewBuildIfError(err, "Failed to fetch revel " + model.RevelImportPath) } } } @@ -210,13 +239,13 @@ func setSkeletonPath(c *model.CommandConfig) (err error) { switch strings.ToLower(sp.Scheme) { // TODO Add support for ftp, sftp, scp ?? case "" : - sp.Scheme="file" + sp.Scheme = "file" fallthrough case "file" : fullpath := sp.String()[7:] if !filepath.IsAbs(fullpath) { fullpath, err = filepath.Abs(fullpath) - if err!=nil { + if err != nil { return } } @@ -251,11 +280,11 @@ func newLoadFromGit(c *model.CommandConfig, sp *url.URL) (err error) { targetPath := filepath.Join(os.TempDir(), "revel", "skeleton") os.RemoveAll(targetPath) pathpart := strings.Split(sp.Path, ":") - getCmd := exec.Command("git", "clone", sp.Scheme+"://"+sp.Host+pathpart[0], targetPath) + getCmd := exec.Command("git", "clone", sp.Scheme + "://" + sp.Host + pathpart[0], targetPath) utils.Logger.Info("Exec:", "args", getCmd.Args) getOutput, err := getCmd.CombinedOutput() if err != nil { - utils.Logger.Fatal("Abort: could not clone the Skeleton source code: ","output", string(getOutput), "path", c.New.SkeletonPath) + utils.Logger.Fatal("Abort: could not clone the Skeleton source code: ", "output", string(getOutput), "path", c.New.SkeletonPath) } outputPath := targetPath if len(pathpart) > 1 { diff --git a/revel/new_test.go b/revel/new_test.go index c1d3bca..2deac86 100644 --- a/revel/new_test.go +++ b/revel/new_test.go @@ -3,95 +3,48 @@ package main_test import ( "github.com/revel/cmd/model" "github.com/revel/cmd/revel" - "github.com/revel/cmd/utils" "github.com/stretchr/testify/assert" - "io/ioutil" "os" - "path/filepath" "testing" ) // test the commands func TestNew(t *testing.T) { a := assert.New(t) - gopath := setup("revel-test-new", a) + gopath := setup("revel-test-new", a) - t.Run("New", func(t *testing.T) { - a := assert.New(t) - c := newApp("new-test", model.NEW, nil, a) - a.Nil(main.Commands[model.NEW].RunWith(c), "New failed") - }) - t.Run("Path", func(t *testing.T) { - a := assert.New(t) - c := newApp("new/test/a", model.NEW, nil, a) - a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed") - }) - t.Run("Path-Duplicate", func(t *testing.T) { - a := assert.New(t) - c := newApp("new/test/b", model.NEW, nil, a) - a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed") - c = newApp("new/test/b", model.NEW, nil, a) - a.NotNil(main.Commands[model.NEW].RunWith(c), "Duplicate path Did Not failed") - }) - t.Run("Skeleton-Git", func(t *testing.T) { - a := assert.New(t) - c := newApp("new/test/c/1", model.NEW, nil, a) - c.New.SkeletonPath = "git://github.com/revel/skeletons:basicnsadnsak" - a.NotNil(main.Commands[model.NEW].RunWith(c), "Expected Failed to run with new") - // We need to pick a different path - c = newApp("new/test/c/2", model.NEW, nil, a) - c.New.SkeletonPath = "git://github.com/revel/skeletons:basic/bootstrap4" - a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run with new skeleton git") - }) - if !t.Failed() { - if err := os.RemoveAll(gopath); err != nil { - a.Fail("Failed to remove test path") - } - } -} - -// test the commands -func TestNewVendor(t *testing.T) { - a := assert.New(t) - gopath := setup("revel-test-new-vendor", a) - precall := func(c *model.CommandConfig) { - c.New.Vendored = true - } t.Run("New", func(t *testing.T) { a := assert.New(t) - c := newApp("onlyone/v/a", model.NEW, precall, a) - c.New.Vendored = true + c := newApp("new-test", model.NEW, nil, a) a.Nil(main.Commands[model.NEW].RunWith(c), "New failed") }) - t.Run("Test", func(t *testing.T) { + t.Run("New-NotVendoredmode", func(t *testing.T) { a := assert.New(t) - c := newApp("onlyone/v/a", model.TEST, nil, a) - a.Nil(main.Commands[model.TEST].RunWith(c), "Test failed") + c := newApp("new-notvendored", model.NEW, nil, a) + c.New.NotVendored = true + a.Nil(main.Commands[model.NEW].RunWith(c), "New failed") }) - t.Run("Build", func(t *testing.T) { + t.Run("Path", func(t *testing.T) { a := assert.New(t) - c := newApp("onlyone/v/a", model.BUILD, nil, a) - c.Index = model.BUILD - c.Build.TargetPath = filepath.Join(gopath, "src/onlyone/v/a", "target") - a.Nil(main.Commands[model.BUILD].RunWith(c), " Build failed") - a.True(utils.DirExists(c.Build.TargetPath), "Target folder not made", c.Build.TargetPath) + c := newApp("new/test/a", model.NEW, nil, a) + a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed") }) - t.Run("Package", func(t *testing.T) { + t.Run("Path-Duplicate", func(t *testing.T) { a := assert.New(t) - c := newApp("onlyone/v/a", model.PACKAGE, nil, a) - c.Package.TargetPath = filepath.Join(gopath, "src/onlyone/v/a", "target.tar.gz") - a.Nil(main.Commands[model.PACKAGE].RunWith(c), "Package Failed") - a.True(utils.Exists(c.Package.TargetPath), "Target package not made", c.Package.TargetPath) + c := newApp("new/test/b", model.NEW, nil, a) + a.Nil(main.Commands[model.NEW].RunWith(c), "New path failed") + c = newApp("new/test/b", model.NEW, nil, a) + a.NotNil(main.Commands[model.NEW].RunWith(c), "Duplicate path Did Not failed") }) - t.Run("TestVendorDir", func(t *testing.T) { - // Check to see that no additional packages were downloaded outside the vendor folder - files, err := ioutil.ReadDir(gopath) - a.Nil(err, "Failed to read gopath folder") - // bin/ onlyone/ pkg/ src/ - a.Equal(3, len(files), "Expected single file in "+gopath) - files, err = ioutil.ReadDir(filepath.Join(gopath, "src")) - a.Nil(err, "Failed to read src folder") - a.Equal(1, len(files), "Expected single file in source folder", filepath.Join(gopath, "src")) + t.Run("Skeleton-Git", func(t *testing.T) { + a := assert.New(t) + c := newApp("new/test/c/1", model.NEW, nil, a) + c.New.SkeletonPath = "git://github.com/revel/skeletons:basicnsadnsak" + a.NotNil(main.Commands[model.NEW].RunWith(c), "Expected Failed to run with new") + // We need to pick a different path + c = newApp("new/test/c/2", model.NEW, nil, a) + c.New.SkeletonPath = "git://github.com/revel/skeletons:basic/bootstrap4" + a.Nil(main.Commands[model.NEW].RunWith(c), "Failed to run with new skeleton git") }) if !t.Failed() { if err := os.RemoveAll(gopath); err != nil { @@ -99,3 +52,4 @@ func TestNewVendor(t *testing.T) { } } } + diff --git a/revel/package.go b/revel/package.go index 8967258..723e7a9 100644 --- a/revel/package.go +++ b/revel/package.go @@ -40,6 +40,9 @@ func init() { // Called when unable to parse the command line automatically and assumes an old launch func updatePackageConfig(c *model.CommandConfig, args []string) bool { c.Index = model.PACKAGE + if len(args)==0 && c.Package.ImportPath!="" { + return true + } c.Package.ImportPath = args[0] if len(args) > 1 { c.Package.Mode = args[1] @@ -58,7 +61,7 @@ func packageApp(c *model.CommandConfig) (err error) { } appImportPath := c.ImportPath - revel_paths, err := model.NewRevelPaths(mode, appImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver)) + revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) if err != nil { return } diff --git a/revel/revel.go b/revel/revel.go index dd95b43..06be182 100644 --- a/revel/revel.go +++ b/revel/revel.go @@ -71,27 +71,30 @@ func main() { wd, _ := os.Getwd() utils.InitLogger(wd, logger.LvlError) - parser := flags.NewParser(c, flags.HelpFlag|flags.PassDoubleDash) - if len(os.Args)<2 { + parser := flags.NewParser(c, flags.HelpFlag | flags.PassDoubleDash) + if len(os.Args) < 2 { parser.WriteHelp(os.Stdout) os.Exit(1) } if err := ParseArgs(c, parser, os.Args[1:]); err != nil { - fmt.Fprint(os.Stderr, err.Error() +"\n") + fmt.Fprint(os.Stderr, err.Error() + "\n") os.Exit(1) } // Switch based on the verbose flag - if len(c.Verbose)>1 { + if len(c.Verbose) > 1 { utils.InitLogger(wd, logger.LvlDebug) - } else if len(c.Verbose)>0 { + } else if len(c.Verbose) > 0 { utils.InitLogger(wd, logger.LvlInfo) } else { utils.InitLogger(wd, logger.LvlWarn) } - if err := c.UpdateImportPath();err!=nil { + // Setup package resolver + c.InitPackageResolver() + + if err := c.UpdateImportPath(); err != nil { utils.Logger.Error(err.Error()) parser.WriteHelp(os.Stdout) os.Exit(1) @@ -100,14 +103,8 @@ func main() { command := Commands[c.Index] println("Revel executing:", command.Short) - // Setting go paths - c.InitGoPaths() - - // Setup package resolver - c.InitPackageResolver() - if err := command.RunWith(c); err != nil { - utils.Logger.Error("Unable to execute","error",err) + utils.Logger.Error("Unable to execute", "error", err) os.Exit(1) } } @@ -142,13 +139,10 @@ func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err } } - if len(extraArgs) > 0 { - utils.Logger.Info("Found additional arguements, setting them") - if !Commands[c.Index].UpdateConfig(c, extraArgs) { - buffer := &bytes.Buffer{} - parser.WriteHelp(buffer) - err = fmt.Errorf("Invalid command line arguements %v\n%s", extraArgs, buffer.String()) - } + if !Commands[c.Index].UpdateConfig(c, extraArgs) { + buffer := &bytes.Buffer{} + parser.WriteHelp(buffer) + err = fmt.Errorf("Invalid command line arguements %v\n%s", extraArgs, buffer.String()) } return diff --git a/revel/run.go b/revel/run.go index c7bd5ee..c996ae2 100644 --- a/revel/run.go +++ b/revel/run.go @@ -6,7 +6,7 @@ package main import ( "strconv" - + "encoding/json" "fmt" "github.com/revel/cmd/harness" "github.com/revel/cmd/model" @@ -106,7 +106,9 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool { } case 0: // Attempt to set the import path to the current working director. - c.Run.ImportPath,_ = os.Getwd() + if c.Run.ImportPath == "" { + c.Run.ImportPath, _ = os.Getwd() + } } c.Index = model.RUN return true @@ -114,7 +116,7 @@ func updateRunConfig(c *model.CommandConfig, args []string) bool { // Returns true if this is an absolute path or a relative gopath func runIsImportPath(pathToCheck string) bool { - if _, err := build.Import(pathToCheck, "", build.FindOnly);err==nil { + if _, err := build.Import(pathToCheck, "", build.FindOnly); err == nil { return true } return filepath.IsAbs(pathToCheck) @@ -126,7 +128,7 @@ func runApp(c *model.CommandConfig) (err error) { c.Run.Mode = "dev" } - revel_path, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver)) + revel_path, err := model.NewRevelPaths(c.Run.Mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) if err != nil { return utils.NewBuildIfError(err, "Revel paths") } @@ -157,7 +159,11 @@ func runApp(c *model.CommandConfig) (err error) { utils.Logger.Errorf("Failed to build app: %s", err) } app.Port = revel_path.HTTPPort - runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose) + var paths []byte + if len(app.PackagePathMap) > 0 { + paths, _ = json.Marshal(app.PackagePathMap) + } + runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.Verbose, string(paths)) if c.HistoricMode { runMode = revel_path.RunMode } diff --git a/revel/test.go b/revel/test.go index d1ec415..a82fb6b 100644 --- a/revel/test.go +++ b/revel/test.go @@ -55,6 +55,10 @@ func init() { // Called to update the config command with from the older stype func updateTestConfig(c *model.CommandConfig, args []string) bool { c.Index = model.TEST + if len(args) == 0 && c.Test.ImportPath != "" { + return true + } + // The full test runs // revel test (run mode) (suite(.function)) if len(args) < 1 { @@ -78,7 +82,7 @@ func testApp(c *model.CommandConfig) (err error) { } // Find and parse app.conf - revel_path, err := model.NewRevelPaths(mode, c.ImportPath, "", model.NewWrappedRevelCallback(nil, c.PackageResolver)) + revel_path, err := model.NewRevelPaths(mode, c.ImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver)) if err != nil { return } @@ -95,7 +99,7 @@ func testApp(c *model.CommandConfig) (err error) { } // Direct all the output into a file in the test-results directory. - file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + file, err := os.OpenFile(filepath.Join(resultPath, "app.log"), os.O_CREATE | os.O_WRONLY | os.O_APPEND, 0666) if err != nil { return utils.NewBuildError("Failed to create test result log file: ", "error", err) } @@ -104,11 +108,16 @@ func testApp(c *model.CommandConfig) (err error) { if reverr != nil { return utils.NewBuildIfError(reverr, "Error building: ") } - runMode := fmt.Sprintf(`{"mode":"%s","testModeFlag":true, "specialUseFlag":%v}`, app.Paths.RunMode, c.Verbose) + var paths []byte + if len(app.PackagePathMap) > 0 { + paths, _ = json.Marshal(app.PackagePathMap) + } + runMode := fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, app.Paths.RunMode, c.Verbose, string(paths)) if c.HistoricMode { runMode = app.Paths.RunMode } cmd := app.Cmd(runMode) + cmd.Dir = c.AppPath cmd.Stderr = io.MultiWriter(cmd.Stderr, file) cmd.Stdout = io.MultiWriter(cmd.Stderr, file) @@ -225,7 +234,7 @@ func filterTestSuites(suites *[]tests.TestSuiteDesc, suiteArgument string) *[]te // in case it hasn't finished starting up yet. func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) { var ( - err error + err error resp *http.Response testSuites []tests.TestSuiteDesc ) @@ -258,7 +267,7 @@ func getTestsList(baseURL string) (*[]tests.TestSuiteDesc, error) { func runTestSuites(paths *model.RevelContainer, baseURL, resultPath string, testSuites *[]tests.TestSuiteDesc) (*[]tests.TestSuiteResult, bool) { // We can determine the testsuite location by finding the test module and extracting the data from it - resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"], "app", "views", "TestRunner/SuiteResult.html") + resultFilePath := filepath.Join(paths.ModulePathMap["testrunner"].Path, "app", "views", "TestRunner/SuiteResult.html") var ( overallSuccess = true diff --git a/revel/version.go b/revel/version.go index 1000693..5dc6acf 100644 --- a/revel/version.go +++ b/revel/version.go @@ -29,10 +29,10 @@ import ( type ( // The version container VersionCommand struct { - Command *model.CommandConfig // The command - revelVersion *model.Version // The Revel framework version - modulesVersion *model.Version // The Revel modules version - cmdVersion *model.Version // The tool version + Command *model.CommandConfig // The command + revelVersion *model.Version // The Revel framework version + modulesVersion *model.Version // The Revel modules version + cmdVersion *model.Version // The tool version } ) @@ -74,10 +74,10 @@ func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) { versionInfo := "" for x := 0; x < 2 && needsUpdates; x++ { needsUpdates = false - versionInfo, needsUpdates = v.doRepoCheck(x==0) + versionInfo, needsUpdates = v.doRepoCheck(x == 0) } - fmt.Println(versionInfo) + fmt.Printf("%s\n\nGo Location:%s\n\n", versionInfo, c.GoCmd) cmd := exec.Command(c.GoCmd, "version") cmd.Stdout = os.Stdout if e := cmd.Start(); e != nil { @@ -112,7 +112,7 @@ func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needs // Only do an update on the first loop, and if specified to update shouldUpdate := updateLibs && v.Command.Version.Update if v.Command.Version.Update { - if localVersion == nil || (versonFromRepo != nil && versonFromRepo.Newer(localVersion)) { + if localVersion == nil || (versonFromRepo != nil && versonFromRepo.Newer(localVersion)) { needsUpdate = true if shouldUpdate { v.doUpdate(title, repo, localVersion, versonFromRepo) @@ -127,7 +127,7 @@ func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needs // Checks for updates if needed func (v *VersionCommand) doUpdate(title, repo string, local, remote *model.Version) { - utils.Logger.Info("Updating package", "package", title, "repo",repo) + utils.Logger.Info("Updating package", "package", title, "repo", repo) fmt.Println("Attempting to update package", title) if err := v.Command.PackageResolver(repo); err != nil { utils.Logger.Error("Unable to update repo", "repo", repo, "error", err) @@ -228,34 +228,26 @@ func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.V } // Fetch the local version of revel from the file system -func (v *VersionCommand) updateLocalVersions() { +func (v *VersionCommand) updateLocalVersions() { v.cmdVersion = &model.Version{} v.cmdVersion.ParseVersion(cmd.Version) v.cmdVersion.BuildDate = cmd.BuildDate v.cmdVersion.MinGoVersion = cmd.MinimumGoVersion - var modulePath, revelPath string - _, revelPath, err := utils.FindSrcPaths(v.Command.ImportPath, model.RevelImportPath, v.Command.PackageResolver) + pathMap, err := utils.FindSrcPaths(v.Command.AppPath, []string{model.RevelImportPath, model.RevelModulesImportPath}, v.Command.PackageResolver) if err != nil { - utils.Logger.Warn("Unable to extract version information from Revel library", "error,err") + utils.Logger.Warn("Unable to extract version information from Revel library", "path", pathMap[model.RevelImportPath], "error", err) return } - revelPath = revelPath + model.RevelImportPath - utils.Logger.Info("Fullpath to revel", "dir", revelPath) - v.revelVersion, err = v.versionFromFilepath(revelPath) + utils.Logger.Info("Fullpath to revel modules", "dir", pathMap[model.RevelImportPath]) + v.revelVersion, err = v.versionFromFilepath(pathMap[model.RevelImportPath]) if err != nil { utils.Logger.Warn("Unable to extract version information from Revel", "error,err") } - _, modulePath, err = utils.FindSrcPaths(v.Command.ImportPath, model.RevelModulesImportPath, v.Command.PackageResolver) + v.modulesVersion, err = v.versionFromFilepath(pathMap[model.RevelModulesImportPath]) if err != nil { - utils.Logger.Warn("Unable to extract version information from Revel library", "error,err") - return - } - modulePath = modulePath + model.RevelModulesImportPath - v.modulesVersion, err = v.versionFromFilepath(modulePath) - if err != nil { - utils.Logger.Warn("Unable to extract version information from Revel Modules", "error", err) + utils.Logger.Warn("Unable to extract version information from Revel Modules", "path", pathMap[model.RevelModulesImportPath], "error", err) } return diff --git a/tests/testrunner.go b/tests/testrunner.go index 6117873..a2fb9bd 100644 --- a/tests/testrunner.go +++ b/tests/testrunner.go @@ -107,7 +107,7 @@ func describeSuite(testSuite interface{}) TestSuiteDesc { } // errorSummary gets an error and returns its summary in human readable format. -func errorSummary(err *utils.Error) (message string) { +func errorSummary(err *utils.SourceError) (message string) { expectedPrefix := "(expected)" actualPrefix := "(actual)" errDesc := err.Description diff --git a/utils/build_error.go b/utils/build_error.go index 6412bbb..faa8614 100644 --- a/utils/build_error.go +++ b/utils/build_error.go @@ -3,6 +3,8 @@ package utils import ( "fmt" "github.com/revel/cmd/logger" + "strconv" + "regexp" ) type ( @@ -43,3 +45,60 @@ func NewBuildIfError(err error, message string, args ...interface{}) (b error) { func (b *BuildError) Error() string { return fmt.Sprint(b.Message, b.Args) } + +// Parse the output of the "go build" command. +// Return a detailed Error. +func NewCompileError(importPath, errorLink string, error error) *SourceError { + // Get the stack from the error + + errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`). + FindSubmatch([]byte(error.Error())) + if errorMatch == nil { + errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch([]byte(error.Error())) + + if errorMatch == nil { + Logger.Error("Failed to parse build errors", "error", error) + return &SourceError{ + SourceType: "Go code", + Title: "Go Compilation Error", + Description: "See console for build error.", + } + } + + errorMatch = append(errorMatch, errorMatch[3]) + + Logger.Error("Build errors", "errors", error) + } + + + // Read the source for the offending file. + var ( + relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go" + absFilename = relFilename + line, _ = strconv.Atoi(string(errorMatch[2])) + description = string(errorMatch[4]) + compileError = &SourceError{ + SourceType: "Go code", + Title: "Go Compilation Error", + Path: relFilename, + Description: description, + Line: line, + } + ) + + // errorLink := paths.Config.StringDefault("error.link", "") + + if errorLink != "" { + compileError.SetLink(errorLink) + } + + fileStr, err := ReadLines(absFilename) + if err != nil { + compileError.MetaError = absFilename + ": " + err.Error() + Logger.Info("Unable to readlines " + compileError.MetaError, "error", err) + return compileError + } + + compileError.SourceLines = fileStr + return compileError +} \ No newline at end of file diff --git a/utils/command.go b/utils/command.go index 4a36592..e00b4e1 100644 --- a/utils/command.go +++ b/utils/command.go @@ -10,25 +10,27 @@ import ( ) // Initialize the command based on the GO environment -func CmdInit(c *exec.Cmd, basePath string) { +func CmdInit(c *exec.Cmd, addGoPath bool, basePath string) { c.Dir = basePath // Dep does not like paths that are not real, convert all paths in go to real paths realPath := &bytes.Buffer{} - for _, p := range filepath.SplitList(build.Default.GOPATH) { - rp,_ := filepath.EvalSymlinks(p) - if realPath.Len() > 0 { - realPath.WriteString(string(filepath.ListSeparator)) + if addGoPath { + for _, p := range filepath.SplitList(build.Default.GOPATH) { + rp, _ := filepath.EvalSymlinks(p) + if realPath.Len() > 0 { + realPath.WriteString(string(filepath.ListSeparator)) + } + realPath.WriteString(rp) } - realPath.WriteString(rp) + // Go 1.8 fails if we do not include the GOROOT + c.Env = []string{"GOPATH=" + realPath.String(), "GOROOT=" + os.Getenv("GOROOT")} } - // Go 1.8 fails if we do not include the GOROOT - c.Env = []string{"GOPATH=" + realPath.String(), "GOROOT="+ os.Getenv("GOROOT")} // Fetch the rest of the env variables for _, e := range os.Environ() { pair := strings.Split(e, "=") - if pair[0]=="GOPATH" || pair[0]=="GOROOT" { + if pair[0] == "GOPATH" || pair[0] == "GOROOT" { continue } - c.Env = append(c.Env,e) + c.Env = append(c.Env, e) } } \ No newline at end of file diff --git a/utils/error.go b/utils/error.go index e03e1f3..93143d8 100644 --- a/utils/error.go +++ b/utils/error.go @@ -8,7 +8,7 @@ import ( // The error is a wrapper for the type ( - Error struct { + SourceError struct { SourceType string // The type of source that failed to build. Title, Path, Description string // Description of the error, as presented to the user. Line, Column int // Where the error was encountered. @@ -24,8 +24,8 @@ type ( } ) // Return a new error object -func NewError(source, title,path,description string) *Error { - return &Error { +func NewError(source, title, path, description string) *SourceError { + return &SourceError{ SourceType:source, Title:title, Path:path, @@ -34,7 +34,7 @@ func NewError(source, title,path,description string) *Error { } // Creates a link based on the configuration setting "errors.link" -func (e *Error) SetLink(errorLink string) { +func (e *SourceError) SetLink(errorLink string) { errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1) errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1) @@ -44,7 +44,7 @@ func (e *Error) SetLink(errorLink string) { // Error method constructs a plaintext version of the error, taking // account that fields are optionally set. Returns e.g. Compilation Error // (in views/header.html:51): expected right delim in end; got "}" -func (e *Error) Error() string { +func (e *SourceError) Error() string { if e == nil { panic("opps") } @@ -69,7 +69,7 @@ func (e *Error) Error() string { // ContextSource method returns a snippet of the source around // where the error occurred. -func (e *Error) ContextSource() []SourceLine { +func (e *SourceError) ContextSource() []SourceLine { if e.SourceLines == nil { return nil } @@ -82,7 +82,7 @@ func (e *Error) ContextSource() []SourceLine { end = len(e.SourceLines) } - lines := make([]SourceLine, end-start) + lines := make([]SourceLine, end - start) for i, src := range e.SourceLines[start:end] { fileLine := start + i + 1 lines[i] = SourceLine{src, fileLine, fileLine == e.Line} diff --git a/utils/file.go b/utils/file.go index cb714ee..ab915bd 100644 --- a/utils/file.go +++ b/utils/file.go @@ -4,15 +4,15 @@ import ( "archive/tar" "bytes" "compress/gzip" - "errors" "fmt" - "go/build" + "errors" "html/template" "io" "io/ioutil" "os" "path/filepath" "strings" + "golang.org/x/tools/go/packages" ) // DirExists returns true if the given path exists and is a directory. @@ -109,7 +109,7 @@ func GenerateTemplate(filename, templateSource string, args map[string]interface func RenderTemplate(destPath, srcPath string, data interface{}) (err error) { tmpl, err := template.ParseFiles(srcPath) if err != nil { - return NewBuildIfError(err, "Failed to parse template "+srcPath) + return NewBuildIfError(err, "Failed to parse template " + srcPath) } f, err := os.Create(destPath) @@ -119,12 +119,12 @@ func RenderTemplate(destPath, srcPath string, data interface{}) (err error) { err = tmpl.Execute(f, data) if err != nil { - return NewBuildIfError(err, "Failed to Render template "+srcPath) + return NewBuildIfError(err, "Failed to Render template " + srcPath) } err = f.Close() if err != nil { - return NewBuildIfError(err, "Failed to close file stream "+destPath) + return NewBuildIfError(err, "Failed to close file stream " + destPath) } return } @@ -133,12 +133,12 @@ func RenderTemplate(destPath, srcPath string, data interface{}) (err error) { func RenderTemplateToStream(output io.Writer, srcPath []string, data interface{}) (err error) { tmpl, err := template.ParseFiles(srcPath...) if err != nil { - return NewBuildIfError(err, "Failed to parse template "+srcPath[0]) + return NewBuildIfError(err, "Failed to parse template " + srcPath[0]) } err = tmpl.Execute(output, data) if err != nil { - return NewBuildIfError(err, "Failed to render template "+srcPath[0]) + return NewBuildIfError(err, "Failed to render template " + srcPath[0]) } return } @@ -150,7 +150,7 @@ func MustChmod(filename string, mode os.FileMode) { // Called if panic func PanicOnError(err error, msg string) { - if revErr, ok := err.(*Error); (ok && revErr != nil) || (!ok && err != nil) { + if revErr, ok := err.(*SourceError); (ok && revErr != nil) || (!ok && err != nil) { Logger.Panicf("Abort: %s: %s %s", msg, revErr, err) } } @@ -181,7 +181,7 @@ func CopyDir(destDir, srcDir string, data map[string]interface{}) error { if info.IsDir() { err := os.MkdirAll(filepath.Join(destDir, relSrcPath), 0777) if !os.IsExist(err) { - return NewBuildIfError(err, "Failed to create directory", "path", destDir+"/"+relSrcPath) + return NewBuildIfError(err, "Failed to create directory", "path", destDir + "/" + relSrcPath) } return nil } @@ -189,7 +189,7 @@ func CopyDir(destDir, srcDir string, data map[string]interface{}) error { // If this file ends in ".template", render it as a template. if strings.HasSuffix(relSrcPath, ".template") { - return RenderTemplate(destPath[:len(destPath)-len(".template")], srcPath, data) + return RenderTemplate(destPath[:len(destPath) - len(".template")], srcPath, data) } // Else, just copy it over. @@ -218,7 +218,7 @@ func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error { path = filepath.Join(linkName, name) - if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink { + if err == nil && info.Mode() & os.ModeSymlink == os.ModeSymlink { var symlinkPath string symlinkPath, err = filepath.EvalSymlinks(path) if err != nil { @@ -320,49 +320,71 @@ func Empty(dirname string) bool { } // Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory -func FindSrcPaths(appImportPath, revelImportPath string, packageResolver func(pkgName string) error) (appSourcePath, revelSourcePath string, err error) { - var ( - gopaths = filepath.SplitList(build.Default.GOPATH) - goroot = build.Default.GOROOT - ) - - if len(gopaths) == 0 { - err = errors.New("GOPATH environment variable is not set. " + - "Please refer to http://golang.org/doc/code.html to configure your Go environment.") - return - } - - if ContainsString(gopaths, goroot) { - err = fmt.Errorf("GOPATH (%s) must not include your GOROOT (%s). "+ - "Please refer to http://golang.org/doc/code.html to configure your Go environment. ", - build.Default.GOPATH, goroot) - return - - } - - appPkgDir := "" - appPkgSrcDir := "" - if len(appImportPath)>0 { - Logger.Info("Seeking app package","app",appImportPath) - appPkg, err := build.Import(appImportPath, "", build.FindOnly) - if err != nil { - err = fmt.Errorf("Failed to import " + appImportPath + " with error %s", err.Error()) - return "","",err +func FindSrcPaths(appPath string, packageList []string, packageResolver func(pkgName string) error) (sourcePathsmap map[string]string, err error) { + sourcePathsmap, missingList, err := findSrcPaths(appPath, packageList) + if err != nil && packageResolver != nil || len(missingList)>0 { + Logger.Info("Failed to find package, attempting to call resolver for missing packages","missing packages",missingList) + for _, item := range missingList { + if err = packageResolver(item); err != nil { + return + } } - appPkgDir,appPkgSrcDir =appPkg.Dir, appPkg.SrcRoot + sourcePathsmap, missingList, err = findSrcPaths(appPath, packageList) } - Logger.Info("Seeking remote package","using",appImportPath, "remote",revelImportPath) - revelPkg, err := build.Default.Import(revelImportPath, appPkgDir, build.FindOnly) - if err != nil { - Logger.Info("Resolved called Seeking remote package","using",appImportPath, "remote",revelImportPath) - packageResolver(revelImportPath) - revelPkg, err = build.Import(revelImportPath, appPkgDir, build.FindOnly) - if err != nil { - err = fmt.Errorf("Failed to find Revel with error: %s", err.Error()) - return + if err != nil && len(missingList) > 0 { + for _, missing := range missingList { + Logger.Error("Unable to import this package", "package", missing) + } + } + + return +} + +var NO_APP_FOUND = errors.New("No app found") +var NO_REVEL_FOUND = errors.New("No revel found") + +// Find the full source dir for the import path, uses the build.Default.GOPATH to search for the directory +func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[string]string, missingList[] string, err error) { + // Use packages to fetch + // by not specifying env, we will use the default env + config := &packages.Config{ + Mode: packages.NeedName | packages.NeedFiles, + Dir:appPath, + } + sourcePathsmap = map[string]string{} + Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"),config.Env) + + pkgs, err := packages.Load(config, packagesList...) + Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"),config.Env) + Logger.Info("Loaded packages ", "len results", len(pkgs), "error", err, "basedir", appPath) + for _, packageName := range packagesList { + found := false + log := Logger.New("seeking", packageName) + for _, pck := range pkgs { + log.Info("Found package", "package", pck.ID) + if pck.ID == packageName { + if pck.Errors != nil && len(pck.Errors) > 0 { + log.Info("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "errors", pck.Errors) + continue + + } + //a,_ := pck.MarshalJSON() + log.Info("Found ", "count", len(pck.GoFiles), "App Import Path", pck.ID, "apppath", appPath) + if len(pck.GoFiles)>0 { + sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0]) + found = true + } + } + } + if !found { + if packageName == "github.com/revel/revel" { + err = NO_REVEL_FOUND + } else { + err = NO_APP_FOUND + } + missingList = append(missingList, packageName) } } - revelSourcePath, appSourcePath = revelPkg.Dir[:len(revelPkg.Dir)-len(revelImportPath)], appPkgSrcDir return } diff --git a/watcher/watcher.go b/watcher/watcher.go index c2d19fa..b055e7e 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -12,7 +12,7 @@ import ( "github.com/revel/cmd/model" "github.com/revel/cmd/utils" - "gopkg.in/fsnotify/fsnotify.v1" + "github.com/fsnotify/fsnotify" "time" ) @@ -20,7 +20,7 @@ import ( type Listener interface { // Refresh is invoked by the watcher on relevant filesystem events. // If the listener returns an error, it is served to the user on the current request. - Refresh() *utils.Error + Refresh() *utils.SourceError } // DiscerningListener allows the receiver to selectively watch files. @@ -44,7 +44,7 @@ type Watcher struct { paths *model.RevelContainer refreshTimer *time.Timer // The timer to countdown the next refresh timerMutex *sync.Mutex // A mutex to prevent concurrent updates - refreshChannel chan *utils.Error + refreshChannel chan *utils.SourceError refreshChannelCount int refreshTimerMS time.Duration // The number of milliseconds between refreshing builds } @@ -61,7 +61,7 @@ func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher { paths.Config.BoolDefault("watch", true) && paths.Config.StringDefault("watch.mode", "normal") == "eager", timerMutex: &sync.Mutex{}, - refreshChannel: make(chan *utils.Error, 10), + refreshChannel: make(chan *utils.SourceError, 10), refreshChannelCount: 0, } } @@ -178,7 +178,7 @@ func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher // Notify causes the watcher to forward any change events to listeners. // It returns the first (if any) error returned. -func (w *Watcher) Notify() *utils.Error { +func (w *Watcher) Notify() *utils.SourceError { if w.serial { // Serialize Notify() calls. w.notifyMutex.Lock() @@ -207,7 +207,7 @@ func (w *Watcher) Notify() *utils.Error { utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError) if w.forceRefresh || refresh || w.lastError == i { - var err *utils.Error + var err *utils.SourceError if w.serial { err = listener.Refresh() } else { @@ -229,7 +229,7 @@ func (w *Watcher) Notify() *utils.Error { // Build a queue for refresh notifications // this will not return until one of the queue completes -func (w *Watcher) notifyInProcess(listener Listener) (err *utils.Error) { +func (w *Watcher) notifyInProcess(listener Listener) (err *utils.SourceError) { shouldReturn := false // This code block ensures that either a timer is created // or that a process would be added the the h.refreshChannel