From 4bab4409b95dfcfb49752f0851044b364452d9c4 Mon Sep 17 00:00:00 2001 From: "notzippy@gmail.com" Date: Wed, 13 May 2020 22:26:05 -0700 Subject: [PATCH] Updated Revel command Added a check to see if harness had already started, saves a recompile on load Added check to source info for local import renames Removed the go/build check for path and just check existence of the path Formatting updates --- harness/app.go | 8 ++--- harness/harness.go | 28 +++++++++++++-- parser2/source_info_processor.go | 59 +++++++++++++++++++++++++++----- parser2/source_processor.go | 5 +++ revel/run.go | 7 +--- utils/file.go | 16 ++++----- watcher/watcher.go | 31 ++++++----------- 7 files changed, 104 insertions(+), 50 deletions(-) diff --git a/harness/app.go b/harness/app.go index 5538bd3..60b8613 100644 --- a/harness/app.go +++ b/harness/app.go @@ -106,7 +106,7 @@ func (cmd AppCmd) Kill() { if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) { // Windows appears to send the kill to all threads, shutting down the // server before this can, this check will ensure the process is still running - if _, err := os.FindProcess(int(cmd.Process.Pid));err!=nil { + if _, err := os.FindProcess(int(cmd.Process.Pid)); err != nil { // Server has already exited utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid) return @@ -143,9 +143,9 @@ func (cmd AppCmd) Kill() { } if err != nil { - utils.Logger.Error( - "Revel app failed to kill process.", - "processid", cmd.Process.Pid,"error",err, + utils.Logger.Info( + "Revel app already exited.", + "processid", cmd.Process.Pid, "error", err, "killerror", cmd.Process.Kill()) return } diff --git a/harness/harness.go b/harness/harness.go index fd6397c..cefc5d6 100644 --- a/harness/harness.go +++ b/harness/harness.go @@ -16,6 +16,7 @@ package harness import ( "crypto/tls" "fmt" + "time" "go/build" "io" "net" @@ -56,6 +57,8 @@ type Harness struct { paths *model.RevelContainer // The Revel container config *model.CommandConfig // The configuration runMode string // The runmode the harness is running in + isError bool // True if harness is in error state + ranOnce bool // True app compiled once } func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) { @@ -202,6 +205,21 @@ 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.SourceError) { + t := time.Now(); + fmt.Println("Changed detected, recompiling") + err = h.refresh() + if err!=nil && !h.ranOnce && h.useProxy { + addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort) + + fmt.Printf("\nError compiling code, to view error details see proxy running on http://%s\n\n",addr) + } + + h.ranOnce = true + fmt.Printf("\nTime to recompile %s\n",time.Now().Sub(t).String()) + return +} + +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 @@ -281,7 +299,8 @@ func (h *Harness) Run() { paths = append(paths, h.paths.CodePaths...) h.watcher = watcher.NewWatcher(h.paths, false) h.watcher.Listen(h, paths...) - h.watcher.Notify() + go h.Refresh() + // h.watcher.Notify() if h.useProxy { go func() { @@ -292,6 +311,7 @@ func (h *Harness) Run() { addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort) utils.Logger.Infof("Proxy server is listening on %s", addr) + var err error if h.paths.HTTPSsl { err = http.ListenAndServeTLS( @@ -308,13 +328,15 @@ func (h *Harness) Run() { }() } - // Kill the app on signal. + + // Make a new channel to listen for the interrupt event ch := make(chan os.Signal) signal.Notify(ch, os.Interrupt, os.Kill) - <-ch + // Kill the app and exit if h.app != nil { h.app.Kill() } + <-ch os.Exit(1) } diff --git a/parser2/source_info_processor.go b/parser2/source_info_processor.go index 2862ef7..f6d98de 100644 --- a/parser2/source_info_processor.go +++ b/parser2/source_info_processor.go @@ -8,6 +8,7 @@ import ( "go/token" "strings" "path/filepath" + "github.com/revel/cmd/logger" ) type ( @@ -31,14 +32,20 @@ func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *m strings.Contains(p.PkgPath, "/tests/") methodMap = map[string][]*model.MethodSpec{} ) + localImportMap := map[string]string{} + log := s.sourceProcessor.log.New("package", p.PkgPath) 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) + if !s.addImport(decl, p, localImportMap, log) { + continue + } + // log.Info("*** checking", p.Fset.Position(decl.Pos()).Filename) spec, found := s.getStructTypeDecl(decl, p.Fset) if found { if isController || isTest { - controllerSpec := s.getControllerSpec(spec, p) + controllerSpec := s.getControllerSpec(spec, p, localImportMap) sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec) } } else { @@ -56,7 +63,7 @@ func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *m // 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) + log.Info("Added method map to ", "receiver", receiver, "method", m.Name) } } // Check for validation @@ -271,7 +278,7 @@ func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packa } return } -func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package) (controllerSpec *model.TypeInfo) { +func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) { structType := spec.Type.(*ast.StructType) // At this point we know it's a type declaration for a struct. @@ -282,7 +289,7 @@ func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages. ImportPath: p.PkgPath, PackageName: p.Name, } - log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename,"position", p.Fset.Position(spec.Pos()).Line) + log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename, "position", p.Fset.Position(spec.Pos()).Line) for _, field := range structType.Fields.List { // If field.Names is set, it's not an embedded type. if field.Names != nil { @@ -330,9 +337,12 @@ func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages. importPath = p.PkgPath } else { var ok bool - if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok { - log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", ) - continue + if importPath, ok = localImportMap[pkgName]; !ok { + log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", ) + if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok { + log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", ) + continue + } } } @@ -377,4 +387,37 @@ func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string { prefix += "." } return prefix + funcDecl.Name.Name +} +func (s *SourceInfoProcessor) addImport(decl ast.Decl, p *packages.Package, localImportMap map[string]string, log logger.MultiLogger) (shouldContinue bool) { + shouldContinue = true + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + return + } + + if genDecl.Tok == token.IMPORT { + shouldContinue = false + 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:] + } + } + localImportMap[pkgAlias] = fullPath + } + + } + return } \ No newline at end of file diff --git a/parser2/source_processor.go b/parser2/source_processor.go index 9ea6366..0f4c663 100644 --- a/parser2/source_processor.go +++ b/parser2/source_processor.go @@ -43,6 +43,7 @@ func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor { s.sourceInfoProcessor = NewSourceInfoProcessor(s) return s } + func (s *SourceProcessor) parse() (compileError error) { if compileError = s.addPackages(); compileError != nil { return @@ -71,11 +72,15 @@ func (s *SourceProcessor) parse() (compileError error) { return } + +// Using the packages.Load function load all the packages and type specifications (forces compile). +// this sets the SourceProcessor.packageList []*packages.Package 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/...") } + s.log.Info("Reading packages", "packageList", allPackages) //allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."} config := &packages.Config{ diff --git a/revel/run.go b/revel/run.go index c996ae2..93f22e6 100644 --- a/revel/run.go +++ b/revel/run.go @@ -11,9 +11,7 @@ import ( "github.com/revel/cmd/harness" "github.com/revel/cmd/model" "github.com/revel/cmd/utils" - "go/build" "os" - "path/filepath" ) var cmdRun = &Command{ @@ -116,10 +114,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 { - return true - } - return filepath.IsAbs(pathToCheck) + return utils.DirExists(pathToCheck) } // Called to run the app diff --git a/utils/file.go b/utils/file.go index ab915bd..9a7ff02 100644 --- a/utils/file.go +++ b/utils/file.go @@ -322,8 +322,8 @@ 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(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) + 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 @@ -352,25 +352,25 @@ func findSrcPaths(appPath string, packagesList []string) (sourcePathsmap map[str Dir:appPath, } sourcePathsmap = map[string]string{} - Logger.Infof("Environment path %s root %s config env %s", os.Getenv("GOPATH"), os.Getenv("GOROOT"),config.Env) + 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.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 + 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 + log.Error("Error ", "count", len(pck.Errors), "App Import Path", pck.ID, "filesystem path", pck.PkgPath, "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 { + if len(pck.GoFiles) > 0 { sourcePathsmap[packageName] = filepath.Dir(pck.GoFiles[0]) found = true } diff --git a/watcher/watcher.go b/watcher/watcher.go index b055e7e..be7ab0a 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -33,7 +33,7 @@ type DiscerningListener interface { // Watcher allows listeners to register to be notified of changes under a given // directory. type Watcher struct { - // Parallel arrays of watcher/listener pairs. + // Parallel arrays of watcher/listener pairs. watchers []*fsnotify.Watcher listeners []Listener forceRefresh bool @@ -42,8 +42,8 @@ type Watcher struct { lastError int notifyMutex sync.Mutex paths *model.RevelContainer - refreshTimer *time.Timer // The timer to countdown the next refresh - timerMutex *sync.Mutex // A mutex to prevent concurrent updates + refreshTimer *time.Timer // The timer to countdown the next refresh + timerMutex *sync.Mutex // A mutex to prevent concurrent updates refreshChannel chan *utils.SourceError refreshChannelCount int refreshTimerMS time.Duration // The number of milliseconds between refreshing builds @@ -52,10 +52,10 @@ type Watcher struct { // Creates a new watched based on the container func NewWatcher(paths *model.RevelContainer, eagerRefresh bool) *Watcher { return &Watcher{ - forceRefresh: true, + forceRefresh: false, lastError: -1, paths: paths, - refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 10)), + refreshTimerMS: time.Duration(paths.Config.IntDefault("watch.rebuild.delay", 1000)), eagerRefresh: eagerRefresh || paths.DevMode && paths.Config.BoolDefault("watch", true) && @@ -85,7 +85,7 @@ func (w *Watcher) Listen(listener Listener, roots ...string) { for _, p := range roots { // is the directory / file a symlink? f, err := os.Lstat(p) - if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink { + if err == nil && f.Mode() & os.ModeSymlink == os.ModeSymlink { var realPath string realPath, err = filepath.EvalSymlinks(p) if err != nil { @@ -200,12 +200,13 @@ func (w *Watcher) Notify() *utils.SourceError { case <-watcher.Errors: continue default: - // No events left to pull + // No events left to pull } break } - utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError) + utils.Logger.Info("Watcher:Notify refresh state", "Current Index", i, " last error index", w.lastError, + "force", w.forceRefresh, "refresh", refresh, "lastError", w.lastError == i) if w.forceRefresh || refresh || w.lastError == i { var err *utils.SourceError if w.serial { @@ -285,22 +286,10 @@ func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool { } if dl, ok := listener.(DiscerningListener); ok { - if !dl.WatchFile(ev.Name) || ev.Op&fsnotify.Chmod == fsnotify.Chmod { + if !dl.WatchFile(ev.Name) || ev.Op & fsnotify.Chmod == fsnotify.Chmod { return false } } return true } -/* -var WatchFilter = func(c *Controller, fc []Filter) { - if MainWatcher != nil { - err := MainWatcher.Notify() - if err != nil { - c.Result = c.RenderError(err) - return - } - } - fc[0](c, fc[1:]) -} -*/