diff --git a/harness/build.go b/harness/build.go index 31241e75..13b2fd97 100755 --- a/harness/build.go +++ b/harness/build.go @@ -80,6 +80,7 @@ func Build(buildFlags ...string) (app *App, compileError *revel.Error) { versionLinkerFlags := fmt.Sprintf("-X %s/app.APP_VERSION \"%s\"", revel.ImportPath, appVersion) flags := []string{ "build", + "-i", "-ldflags", versionLinkerFlags, "-tags", buildTags, "-o", binName} @@ -173,12 +174,16 @@ func cleanDir(dir string) { tmpPath := path.Join(revel.AppPath, dir) f, err := os.Open(tmpPath) if err != nil { - revel.ERROR.Println("Failed to clean dir:", err) + if !os.IsNotExist(err) { + revel.ERROR.Println("Failed to clean dir:", err) + } } else { defer f.Close() infos, err := f.Readdir(0) if err != nil { - revel.ERROR.Println("Failed to clean dir:", err) + if !os.IsNotExist(err) { + revel.ERROR.Println("Failed to clean dir:", err) + } } else { for _, info := range infos { path := path.Join(tmpPath, info.Name()) @@ -198,7 +203,6 @@ func cleanDir(dir string) { } } - // genSource renders the given template to produce source code, which it writes // to the given directory and file. func genSource(dir, filename, templateSource string, args map[string]interface{}) { diff --git a/harness/harness.go b/harness/harness.go index df9f7403..a6ae1cc0 100644 --- a/harness/harness.go +++ b/harness/harness.go @@ -13,7 +13,6 @@ package harness import ( "crypto/tls" "fmt" - "github.com/revel/revel" "go/build" "io" "net" @@ -22,10 +21,11 @@ import ( "net/url" "os" "os/signal" - "path" "path/filepath" "strings" "sync/atomic" + + "github.com/revel/revel" ) var ( @@ -52,7 +52,7 @@ func renderError(w http.ResponseWriter, r *http.Request, err error) { // ServeHTTP handles all requests. // It checks for changes to app, rebuilds if necessary, and forwards the request. -func (hp *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Don't rebuild the app for favicon requests. if lastRequestHadError > 0 && r.URL.Path == "/favicon.ico" { return @@ -71,18 +71,19 @@ func (hp *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Reverse proxy the request. // (Need special code for websockets, courtesy of bradfitz) if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") { - proxyWebsocket(w, r, hp.serverHost) + proxyWebsocket(w, r, h.serverHost) } else { - hp.proxy.ServeHTTP(w, r) + h.proxy.ServeHTTP(w, r) } } -// Return a reverse proxy that forwards requests to the given port. +// NewHarness method returns a reverse proxy that forwards requests +// to the given port. func NewHarness() *Harness { // Get a template loader to render errors. // Prefer the app's views/errors directory, and fall back to the stock error pages. revel.MainTemplateLoader = revel.NewTemplateLoader( - []string{path.Join(revel.RevelPath, "templates")}) + []string{filepath.Join(revel.RevelPath, "templates")}) revel.MainTemplateLoader.Refresh() addr := revel.HttpAddr @@ -101,12 +102,12 @@ func NewHarness() *Harness { port = getFreePort() } - serverUrl, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port)) + serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port)) harness := &Harness{ port: port, - serverHost: serverUrl.String()[len(scheme+"://"):], - proxy: httputil.NewSingleHostReverseProxy(serverUrl), + serverHost: serverURL.String()[len(scheme+"://"):], + proxy: httputil.NewSingleHostReverseProxy(serverURL), } if revel.HttpSsl { @@ -117,7 +118,7 @@ func NewHarness() *Harness { return harness } -// Rebuild the Revel application and run it on the given port. +// Refresh method rebuilds the Revel application and run it on the given port. func (h *Harness) Refresh() (err *revel.Error) { if h.app != nil { h.app.Kill() @@ -140,10 +141,14 @@ func (h *Harness) Refresh() (err *revel.Error) { return } +// WatchDir method returns false to file matches with doNotWatch +// otheriwse true func (h *Harness) WatchDir(info os.FileInfo) bool { return !revel.ContainsString(doNotWatch, info.Name()) } +// WatchFile method returns true given filename HasSuffix of ".go" +// otheriwse false func (h *Harness) WatchFile(filename string) bool { return strings.HasSuffix(filename, ".go") } @@ -204,7 +209,18 @@ func getFreePort() (port int) { // proxyWebsocket copies data between websocket client and server until one side // closes the connection. (ReverseProxy doesn't work with websocket requests.) func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) { - d, err := net.Dial("tcp", host) + var ( + d net.Conn + err error + ) + if revel.HttpSsl { + // since this proxy isn't used in production, + // it's OK to set InsecureSkipVerify to true + // no need to add another configuration option. + d, err = tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true}) + } else { + d, err = net.Dial("tcp", host) + } if err != nil { http.Error(w, "Error contacting backend server.", 500) revel.ERROR.Printf("Error dialing websocket backend %s: %v", host, err) diff --git a/harness/reflect.go b/harness/reflect.go index f4ff4e6a..4c7df99e 100644 --- a/harness/reflect.go +++ b/harness/reflect.go @@ -96,7 +96,7 @@ func ProcessSource(roots []string) (*SourceInfo, *revel.Error) { } // Start walking the directory tree. - filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + _ = revel.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { log.Println("Error scanning app source:", err) return nil @@ -433,7 +433,8 @@ func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPat var importPath string typeExpr := NewTypeExpr(pkgName, field.Type) if !typeExpr.Valid { - return // We didn't understand one of the args. Ignore this action. (Already logged) + log.Printf("Didn't understand argument '%s' of action %s. Ignoring.\n", name, getFuncName(funcDecl)) + return // We didn't understand one of the args. Ignore this action. } if typeExpr.PkgName != "" { var ok bool diff --git a/revel/build.go b/revel/build.go index 45e7d072..315ec707 100644 --- a/revel/build.go +++ b/revel/build.go @@ -7,17 +7,22 @@ import ( "path/filepath" "strings" - "github.com/revel/revel" "github.com/revel/cmd/harness" + "github.com/revel/revel" ) var cmdBuild = &Command{ - UsageLine: "build [import path] [target path]", + UsageLine: "build [import path] [target path] [run mode]", Short: "build a Revel application (e.g. for deployment)", Long: ` Build the Revel web application named by the given import path. This allows it to be deployed and run on a machine that lacks a Go installation. +The run mode is used to select which set of app.conf configuration should +apply and may be used to determine logic in the application itself. + +Run mode defaults to "dev". + WARNING: The target path will be completely deleted, if it already exists! For example: @@ -31,14 +36,18 @@ func init() { } func buildApp(args []string) { - if len(args) != 2 { + if len(args) < 2 { fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long) return } - appImportPath, destPath := args[0], args[1] + appImportPath, destPath, mode := args[0], args[1], "dev" + if len(args) >= 3 { + mode = args[2] + } + if !revel.Initialized { - revel.Init("", appImportPath, "") + revel.Init(mode, appImportPath, "") } // First, verify that it is either already empty or looks like a previous @@ -96,6 +105,7 @@ func buildApp(args []string) { tmplData, runShPath := map[string]interface{}{ "BinName": filepath.Base(app.BinaryPath), "ImportPath": appImportPath, + "Mode": mode, }, path.Join(destPath, "run.sh") mustRenderTemplate( diff --git a/revel/clean.go b/revel/clean.go index 1af29bf3..06b26f8d 100644 --- a/revel/clean.go +++ b/revel/clean.go @@ -17,7 +17,7 @@ For example: revel clean github.com/revel/samples/chat -It removes the app/tmp directory. +It removes the app/tmp and app/routes directory. `, } @@ -37,12 +37,17 @@ func cleanApp(args []string) { return } - // Remove the app/tmp directory. - tmpDir := path.Join(appPkg.Dir, "app", "tmp") - fmt.Println("Removing:", tmpDir) - err = os.RemoveAll(tmpDir) - if err != nil { - fmt.Fprintln(os.Stderr, "Abort:", err) - return + purgeDirs := []string{ + path.Join(appPkg.Dir, "app", "tmp"), + path.Join(appPkg.Dir, "app", "routes"), + } + + for _, dir := range purgeDirs { + fmt.Println("Removing:", dir) + err = os.RemoveAll(dir) + if err != nil { + fmt.Fprintln(os.Stderr, "Abort:", err) + return + } } } diff --git a/revel/new.go b/revel/new.go index 7252226a..c8748986 100644 --- a/revel/new.go +++ b/revel/new.go @@ -4,10 +4,12 @@ import ( "bytes" "fmt" "go/build" + "log" "math/rand" "os" "os/exec" "path/filepath" + "strings" "github.com/revel/revel" ) @@ -60,6 +62,8 @@ func newApp(args []string) { errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n") } + revel.ERROR.SetFlags(log.LstdFlags) + // checking and setting go paths initGoPaths() @@ -96,9 +100,6 @@ func initGoPaths() { "Please refer to http://golang.org/doc/code.html to configure your Go environment.") } - // set go src path - srcRoot = filepath.Join(filepath.SplitList(gopath)[0], "src") - // check for go executable var err error gocmd, err = exec.LookPath("go") @@ -106,12 +107,38 @@ func initGoPaths() { errorf("Go executable not found in PATH.") } + // revel/revel#1004 choose go path relative to current working directory + workingDir, _ := os.Getwd() + goPathList := filepath.SplitList(gopath) + for _, path := range goPathList { + if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) { + srcRoot = path + break + } + + path, _ = filepath.EvalSymlinks(path) + if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) { + srcRoot = path + break + } + } + + if len(srcRoot) == 0 { + revel.ERROR.Fatalln("Abort: could not create a Revel application outside of GOPATH.") + } + + // set go src path + srcRoot = filepath.Join(srcRoot, "src") } func setApplicationPath(args []string) { var err error importPath = args[0] - if filepath.IsAbs(importPath) { + + // revel/revel#1014 validate relative path, we cannot use built-in functions + // since Go import path is valid relative path too. + // so check basic part of the path, which is "." + if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") { errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.", importPath) } diff --git a/revel/package.go b/revel/package.go index fe1e4c30..41ea785c 100644 --- a/revel/package.go +++ b/revel/package.go @@ -2,19 +2,25 @@ package main import ( "fmt" - "github.com/revel/revel" "io/ioutil" "os" "path/filepath" + + "github.com/revel/revel" ) var cmdPackage = &Command{ - UsageLine: "package [import path]", + UsageLine: "package [import path] [run mode]", Short: "package a Revel application (e.g. for deployment)", Long: ` Package the Revel web application named by the given import path. This allows it to be deployed and run on a machine that lacks a Go installation. +The run mode is used to select which set of app.conf configuration should +apply and may be used to determine logic in the application itself. + +Run mode defaults to "dev". + For example: revel package github.com/revel/samples/chat @@ -31,8 +37,14 @@ func packageApp(args []string) { return } + // Determine the run mode. + mode := "dev" + if len(args) >= 2 { + mode = args[1] + } + appImportPath := args[0] - revel.Init("", appImportPath, "") + revel.Init(mode, appImportPath, "") // Remove the archive if it already exists. destFile := filepath.Base(revel.BasePath) + ".tar.gz" @@ -42,7 +54,7 @@ func packageApp(args []string) { tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath)) panicOnError(err, "Failed to get temp dir") - buildApp([]string{args[0], tmpDir}) + buildApp([]string{args[0], tmpDir, mode}) // Create the zip file. archiveName := mustTarGzDir(destFile, tmpDir) diff --git a/revel/package_run.bat.template b/revel/package_run.bat.template index e73399ba..f101d802 100644 --- a/revel/package_run.bat.template +++ b/revel/package_run.bat.template @@ -1,2 +1,2 @@ @echo off -{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode prod +{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode {{.Mode}} diff --git a/revel/package_run.sh.template b/revel/package_run.sh.template index ac0e12ad..69bcaa61 100644 --- a/revel/package_run.sh.template +++ b/revel/package_run.sh.template @@ -1,3 +1,3 @@ #!/bin/sh SCRIPTPATH=$(cd "$(dirname "$0")"; pwd) -"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode prod +"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}} diff --git a/revel/rev.go b/revel/rev.go index f793d8fd..6c234b80 100644 --- a/revel/rev.go +++ b/revel/rev.go @@ -4,7 +4,6 @@ package main import ( "flag" "fmt" - "github.com/agtorre/gocolorize" "io" "math/rand" "os" @@ -12,9 +11,11 @@ import ( "strings" "text/template" "time" + + "github.com/agtorre/gocolorize" ) -// Cribbed from the genius organization of the "go" command. +// Command structure cribbed from the genius organization of the "go" command. type Command struct { Run func(args []string) UsageLine, Short, Long string @@ -36,6 +37,7 @@ var commands = []*Command{ cmdPackage, cmdClean, cmdTest, + cmdVersion, } func main() { diff --git a/revel/run.go b/revel/run.go index 891415b8..7b477dfd 100644 --- a/revel/run.go +++ b/revel/run.go @@ -1,8 +1,8 @@ package main import ( - "github.com/revel/revel" "github.com/revel/cmd/harness" + "github.com/revel/revel" "strconv" ) @@ -65,7 +65,7 @@ func runApp(args []string) { } // Else, just build and run the app. - revel.TRACE.Println("Running in live build mode.") + revel.TRACE.Println("Running in live build mode.") app, err := harness.Build() if err != nil { errorf("Failed to build app: %s", err) diff --git a/revel/test.go b/revel/test.go index 3128cd6d..68f92d38 100644 --- a/revel/test.go +++ b/revel/test.go @@ -3,9 +3,6 @@ package main import ( "encoding/json" "fmt" - "github.com/revel/revel" - "github.com/revel/cmd/harness" - "github.com/revel/modules/testrunner/app/controllers" "io" "io/ioutil" "net/http" @@ -13,6 +10,10 @@ import ( "path" "strings" "time" + + "github.com/revel/cmd/harness" + "github.com/revel/modules/testrunner/app/controllers" + "github.com/revel/revel" ) var cmdTest = &Command{ @@ -117,13 +118,19 @@ You can add it to a run mode configuration with the following line: ) for i := 0; ; i++ { if resp, err = http.Get(baseUrl + "/@tests.list"); err == nil { - break + if resp.StatusCode == http.StatusOK { + break + } } if i < 3 { time.Sleep(3 * time.Second) continue } - errorf("Failed to request test list: %s", err) + if err != nil { + errorf("Failed to request test list: %s", err) + } else { + errorf("Failed to request test list: non-200 response") + } } defer resp.Body.Close() json.NewDecoder(resp.Body).Decode(&testSuites) diff --git a/revel/util.go b/revel/util.go index 42405653..a3328f9e 100644 --- a/revel/util.go +++ b/revel/util.go @@ -66,22 +66,10 @@ func mustChmod(filename string, mode os.FileMode) { // Additionally, the trailing ".template" is stripped from the file name. // Also, dot files and dot directories are skipped. func mustCopyDir(destDir, srcDir string, data map[string]interface{}) error { - var fullSrcDir string - // Handle symlinked directories. - f, err := os.Lstat(srcDir) - if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink { - fullSrcDir, err = os.Readlink(srcDir) - if err != nil { - panic(err) - } - } else { - fullSrcDir = srcDir - } - - return filepath.Walk(fullSrcDir, func(srcPath string, info os.FileInfo, err error) error { + return revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error { // Get the relative path from the source base, and the corresponding path in // the dest directory. - relSrcPath := strings.TrimLeft(srcPath[len(fullSrcDir):], string(os.PathSeparator)) + relSrcPath := strings.TrimLeft(srcPath[len(srcDir):], string(os.PathSeparator)) destPath := path.Join(destDir, relSrcPath) // Skip dot files and dot directories. @@ -124,7 +112,7 @@ func mustTarGzDir(destFilename, srcDir string) string { tarWriter := tar.NewWriter(gzipWriter) defer tarWriter.Close() - filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error { + revel.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error { if info.IsDir() { return nil } diff --git a/revel/version.go b/revel/version.go new file mode 100644 index 00000000..7343b1c6 --- /dev/null +++ b/revel/version.go @@ -0,0 +1,34 @@ +// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. +// Revel Framework source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "runtime" + + "github.com/revel/revel" +) + +var cmdVersion = &Command{ + UsageLine: "version", + Short: "displays the Revel Framework and Go version", + Long: ` +Displays the Revel Framework and Go version. + +For example: + + revel version +`, +} + +func init() { + cmdVersion.Run = versionApp +} + +func versionApp(args []string) { + fmt.Printf("Version(s):") + fmt.Printf("\n Revel v%v (%v)", revel.Version, revel.BuildDate) + fmt.Printf("\n %s %s/%s\n\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) +}