// 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. // 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" "github.com/revel/cmd" "github.com/revel/cmd/model" "github.com/revel/cmd/utils" "go/ast" "go/parser" "go/token" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "strings" "bytes" ) 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 } ) var cmdVersion = &Command{ UsageLine: "revel version", Short: "displays the Revel Framework and Go version", Long: ` Displays the Revel Framework and Go version. For example: revel version [] `, } func init() { v := &VersionCommand{} cmdVersion.UpdateConfig = v.UpdateConfig cmdVersion.RunWith = v.RunWith } // Update the version func (v *VersionCommand) UpdateConfig(c *model.CommandConfig, args []string) bool { if len(args) > 0 { c.Version.ImportPath = args[0] } return true } // Displays the version of go and Revel func (v *VersionCommand) RunWith(c *model.CommandConfig) (err error) { utils.Logger.Info("Requesting version information", "config", c) v.Command = c // Update the versions with the local values v.updateLocalVersions() needsUpdates := true versionInfo := "" for x := 0; x < 2 && needsUpdates; x++ { needsUpdates = false versionInfo, needsUpdates = v.doRepoCheck(x==0) } fmt.Println(versionInfo) cmd := exec.Command(c.GoCmd, "version") cmd.Stdout = os.Stdout if e := cmd.Start(); e != nil { fmt.Println("Go command error ", e) } else { cmd.Wait() } return } // Checks the Revel repos for the latest version func (v *VersionCommand) doRepoCheck(updateLibs bool) (versionInfo string, needsUpdate bool) { var ( title string localVersion *model.Version ) for _, repo := range []string{"revel", "cmd", "modules"} { versonFromRepo, err := v.versionFromRepo(repo, "", "version.go") if err != nil { utils.Logger.Info("Failed to get version from repo", "repo", repo, "error", err) } switch repo { case "revel": title, repo, localVersion = "Revel Framework", "github.com/revel/revel", v.revelVersion case "cmd": title, repo, localVersion = "Revel Cmd", "github.com/revel/cmd/revel", v.cmdVersion case "modules": title, repo, localVersion = "Revel Modules", "github.com/revel/modules", v.modulesVersion } // 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)) { needsUpdate = true if shouldUpdate { v.doUpdate(title, repo, localVersion, versonFromRepo) v.updateLocalVersions() } } } versionInfo = versionInfo + v.outputVersion(title, repo, localVersion, versonFromRepo) } return } // 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) 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) } else if repo == "github.com/revel/cmd/revel" { // One extra step required here to run the install for the command utils.Logger.Fatal("Revel command tool was updated, you must manually run the following command before continuing\ngo install github.com/revel/cmd/revel") } return } // Prints out the local and remote versions, calls update if needed func (v *VersionCommand) outputVersion(title, repo string, local, remote *model.Version) (output string) { buffer := &bytes.Buffer{} remoteVersion := "Unknown" if remote != nil { remoteVersion = remote.VersionString() } localVersion := "Unknown" if local != nil { localVersion = local.VersionString() } fmt.Fprintf(buffer, "%s\t:\t%s\t(%s remote master branch)\n", title, localVersion, remoteVersion) return buffer.String() } // Returns the version from the repository func (v *VersionCommand) versionFromRepo(repoName, branchName, fileName string) (version *model.Version, err error) { if branchName == "" { branchName = "master" } // Try to download the version of file from the repo, just use an http connection to retrieve the source // Assuming that the repo is github fullurl := "https://raw.githubusercontent.com/revel/" + repoName + "/" + branchName + "/" + fileName resp, err := http.Get(fullurl) if err != nil { return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return } utils.Logger.Info("Got version file", "from", fullurl, "content", string(body)) return v.versionFromBytes(body) } // Returns version information from a file called version on the gopath func (v *VersionCommand) compareAndUpdateVersion(remoteVersion *model.Version, localVersion *model.Version) (err error) { return } func (v *VersionCommand) versionFromFilepath(sourcePath string) (version *model.Version, err error) { utils.Logger.Info("Fullpath to revel", "dir", sourcePath) sourceStream, err := ioutil.ReadFile(filepath.Join(sourcePath, "version.go")) if err != nil { return } return v.versionFromBytes(sourceStream) } // Returns version information from a file called version on the gopath func (v *VersionCommand) versionFromBytes(sourceStream []byte) (version *model.Version, err error) { fset := token.NewFileSet() // positions are relative to fset // Parse src but stop after processing the imports. f, err := parser.ParseFile(fset, "", sourceStream, parser.ParseComments) if err != nil { err = utils.NewBuildError("Failed to parse Revel version error:", "error", err) return } version = &model.Version{} // Print the imports from the file's AST. for _, s := range f.Decls { genDecl, ok := s.(*ast.GenDecl) if !ok { continue } if genDecl.Tok != token.CONST { continue } for _, a := range genDecl.Specs { spec := a.(*ast.ValueSpec) r := spec.Values[0].(*ast.BasicLit) switch spec.Names[0].Name { case "Version": version.ParseVersion(strings.Replace(r.Value, `"`, "", -1)) case "BuildDate": version.BuildDate = r.Value case "MinimumGoVersion": version.MinGoVersion = r.Value } } } return } // Fetch the local version of revel from the file system 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) if err != nil { utils.Logger.Warn("Unable to extract version information from Revel library", "error,err") return } revelPath = revelPath + model.RevelImportPath utils.Logger.Info("Fullpath to revel", "dir", revelPath) v.revelVersion, err = v.versionFromFilepath(revelPath) 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) 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) } return }