github.com/mlmmr/revel-cmd@v0.21.2-0.20191112133115-68d8795776dd/model/command_config.go (about)

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