github.com/golang/dep@v0.5.4/cmd/dep/main.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:generate ./mkdoc.sh
     6  
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"os"
    16  	"path/filepath"
    17  	"runtime"
    18  	"runtime/pprof"
    19  	"strings"
    20  	"text/tabwriter"
    21  	"time"
    22  
    23  	"github.com/golang/dep"
    24  	"github.com/golang/dep/internal/fs"
    25  )
    26  
    27  var (
    28  	successExitCode = 0
    29  	errorExitCode   = 1
    30  )
    31  
    32  type command interface {
    33  	Name() string           // "foobar"
    34  	Args() string           // "<baz> [quux...]"
    35  	ShortHelp() string      // "Foo the first bar"
    36  	LongHelp() string       // "Foo the first bar meeting the following conditions..."
    37  	Register(*flag.FlagSet) // command-specific flags
    38  	Hidden() bool           // indicates whether the command should be hidden from help output
    39  	Run(*dep.Ctx, []string) error
    40  }
    41  
    42  // Helper type so that commands can fail without generating any additional
    43  // ouptut.
    44  type silentfail struct{}
    45  
    46  func (silentfail) Error() string {
    47  	return ""
    48  }
    49  
    50  func main() {
    51  	p := &profile{}
    52  
    53  	// Redefining Usage() customizes the output of `dep -h`
    54  	flag.CommandLine.Usage = func() {
    55  		fprintUsage(os.Stderr)
    56  	}
    57  
    58  	flag.StringVar(&p.cpuProfile, "cpuprofile", "", "Writes a CPU profile to the specified file before exiting.")
    59  	flag.StringVar(&p.memProfile, "memprofile", "", "Writes a memory profile to the specified file before exiting.")
    60  	flag.IntVar(&p.memProfileRate, "memprofilerate", 0, "Enable more precise memory profiles by setting runtime.MemProfileRate.")
    61  	flag.StringVar(&p.mutexProfile, "mutexprofile", "", "Writes a mutex profile to the specified file before exiting.")
    62  	flag.IntVar(&p.mutexProfileFraction, "mutexprofilefraction", 0, "Enable more precise mutex profiles by runtime.SetMutexProfileFraction.")
    63  	flag.Parse()
    64  
    65  	wd, err := os.Getwd()
    66  	if err != nil {
    67  		fmt.Fprintln(os.Stderr, "failed to get working directory", err)
    68  		os.Exit(1)
    69  	}
    70  
    71  	args := append([]string{os.Args[0]}, flag.Args()...)
    72  	c := &Config{
    73  		Args:       args,
    74  		Stdout:     os.Stdout,
    75  		Stderr:     os.Stderr,
    76  		WorkingDir: wd,
    77  		Env:        os.Environ(),
    78  	}
    79  
    80  	if err := p.start(); err != nil {
    81  		fmt.Fprintf(os.Stderr, "failed to profile: %v\n", err)
    82  		os.Exit(1)
    83  	}
    84  	exit := c.Run()
    85  	if err := p.finish(); err != nil {
    86  		fmt.Fprintf(os.Stderr, "failed to finish the profile: %v\n", err)
    87  		os.Exit(1)
    88  	}
    89  	os.Exit(exit)
    90  }
    91  
    92  // A Config specifies a full configuration for a dep execution.
    93  type Config struct {
    94  	WorkingDir     string    // Where to execute
    95  	Args           []string  // Command-line arguments, starting with the program name.
    96  	Env            []string  // Environment variables
    97  	Stdout, Stderr io.Writer // Log output
    98  }
    99  
   100  // Run executes a configuration and returns an exit code.
   101  func (c *Config) Run() int {
   102  	commands := commandList()
   103  
   104  	cmdName, printCommandHelp, exit := parseArgs(c.Args)
   105  	if exit {
   106  		fprintUsage(c.Stderr)
   107  		return errorExitCode
   108  	}
   109  
   110  	// 'dep help documentation' generates doc.go.
   111  	if printCommandHelp && cmdName == "documentation" {
   112  		fmt.Println("// Copyright 2017 The Go Authors. All rights reserved.")
   113  		fmt.Println("// Use of this source code is governed by a BSD-style")
   114  		fmt.Println("// license that can be found in the LICENSE file.")
   115  		fmt.Println()
   116  		fmt.Println("// DO NOT EDIT THIS FILE. GENERATED BY mkdoc.sh.")
   117  		fmt.Println("// Edit the documentation in other files and rerun mkdoc.sh to generate this one.")
   118  		fmt.Println()
   119  
   120  		var cw io.Writer = &commentWriter{W: c.Stdout}
   121  		fprintUsage(cw)
   122  		for _, cmd := range commands {
   123  			if !cmd.Hidden() {
   124  				fmt.Fprintln(cw)
   125  				short := cmd.ShortHelp()
   126  				fmt.Fprintln(cw, short)
   127  				fmt.Fprintln(cw)
   128  				fmt.Fprintln(cw, "Usage:")
   129  				fmt.Fprintln(cw)
   130  				fmt.Fprintln(cw, "", cmd.Name(), cmd.Args())
   131  				if long := cmd.LongHelp(); long != short {
   132  					fmt.Fprintln(cw, long)
   133  				}
   134  			}
   135  		}
   136  
   137  		fmt.Println("//")
   138  		fmt.Println("package main")
   139  		return successExitCode
   140  	}
   141  
   142  	outLogger := log.New(c.Stdout, "", 0)
   143  	errLogger := log.New(c.Stderr, "", 0)
   144  
   145  	for _, cmd := range commands {
   146  		if cmd.Name() == cmdName {
   147  			// Build flag set with global flags in there.
   148  			flags := flag.NewFlagSet(cmdName, flag.ContinueOnError)
   149  			flags.SetOutput(c.Stderr)
   150  
   151  			var verbose bool
   152  			// No verbose for verify
   153  			if cmdName != "check" {
   154  				flags.BoolVar(&verbose, "v", false, "enable verbose logging")
   155  			}
   156  
   157  			// Register the subcommand flags in there, too.
   158  			cmd.Register(flags)
   159  
   160  			// Override the usage text to something nicer.
   161  			resetUsage(errLogger, flags, cmdName, cmd.Args(), cmd.LongHelp())
   162  
   163  			if printCommandHelp {
   164  				flags.Usage()
   165  				return errorExitCode
   166  			}
   167  
   168  			// Parse the flags the user gave us.
   169  			// flag package automatically prints usage and error message in err != nil
   170  			// or if '-h' flag provided
   171  			if err := flags.Parse(c.Args[2:]); err != nil {
   172  				return errorExitCode
   173  			}
   174  
   175  			// Cachedir is loaded from env if present. `$GOPATH/pkg/dep` is used as the
   176  			// default cache location.
   177  			cachedir := getEnv(c.Env, "DEPCACHEDIR")
   178  			if cachedir != "" {
   179  				if err := fs.EnsureDir(cachedir, 0777); err != nil {
   180  					errLogger.Printf(
   181  						"dep: $DEPCACHEDIR set to an invalid or inaccessible path: %q\n", cachedir,
   182  					)
   183  					errLogger.Printf("dep: failed to ensure cache directory: %v\n", err)
   184  					return errorExitCode
   185  				}
   186  			}
   187  
   188  			var cacheAge time.Duration
   189  			if env := getEnv(c.Env, "DEPCACHEAGE"); env != "" {
   190  				var err error
   191  				cacheAge, err = time.ParseDuration(env)
   192  				if err != nil {
   193  					errLogger.Printf("dep: failed to parse $DEPCACHEAGE duration %q: %v\n", env, err)
   194  					return errorExitCode
   195  				}
   196  			}
   197  
   198  			// Set up dep context.
   199  			ctx := &dep.Ctx{
   200  				Out:            outLogger,
   201  				Err:            errLogger,
   202  				Verbose:        verbose,
   203  				DisableLocking: getEnv(c.Env, "DEPNOLOCK") != "",
   204  				Cachedir:       cachedir,
   205  				CacheAge:       cacheAge,
   206  			}
   207  
   208  			GOPATHS := filepath.SplitList(getEnv(c.Env, "GOPATH"))
   209  			ctx.SetPaths(c.WorkingDir, GOPATHS...)
   210  
   211  			// Run the command with the post-flag-processing args.
   212  			if err := cmd.Run(ctx, flags.Args()); err != nil {
   213  				if _, ok := err.(silentfail); !ok {
   214  					errLogger.Printf("%v\n", err)
   215  				}
   216  				return errorExitCode
   217  			}
   218  
   219  			// Easy peasy livin' breezy.
   220  			return successExitCode
   221  		}
   222  	}
   223  
   224  	errLogger.Printf("dep: %s: no such command\n", cmdName)
   225  	fprintUsage(c.Stderr)
   226  	return errorExitCode
   227  }
   228  
   229  // Build the list of available commands.
   230  //
   231  // Note that these commands are mutable, but parts of this file
   232  // use them for their immutable characteristics (help strings, etc).
   233  func commandList() []command {
   234  	return []command{
   235  		&initCommand{},
   236  		&statusCommand{},
   237  		&ensureCommand{},
   238  		&pruneCommand{},
   239  		&versionCommand{},
   240  		&checkCommand{},
   241  	}
   242  }
   243  
   244  var examples = [...][2]string{
   245  	{
   246  		"dep init",
   247  		"set up a new project",
   248  	},
   249  	{
   250  		"dep ensure",
   251  		"install the project's dependencies",
   252  	},
   253  	{
   254  		"dep ensure -update",
   255  		"update the locked versions of all dependencies",
   256  	},
   257  	{
   258  		"dep ensure -add github.com/pkg/errors",
   259  		"add a dependency to the project",
   260  	},
   261  }
   262  
   263  func fprintUsage(w io.Writer) {
   264  	fmt.Fprintln(w, "Dep is a tool for managing dependencies for Go projects")
   265  	fmt.Fprintln(w)
   266  	fmt.Fprintln(w, "Usage: \"dep [command]\"")
   267  	fmt.Fprintln(w)
   268  	fmt.Fprintln(w, "Commands:")
   269  	fmt.Fprintln(w)
   270  	tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
   271  
   272  	commands := commandList()
   273  	for _, cmd := range commands {
   274  		if !cmd.Hidden() {
   275  			fmt.Fprintf(tw, "\t%s\t%s\n", cmd.Name(), cmd.ShortHelp())
   276  		}
   277  	}
   278  	tw.Flush()
   279  	fmt.Fprintln(w)
   280  	fmt.Fprintln(w, "Examples:")
   281  	for _, example := range examples {
   282  		fmt.Fprintf(tw, "\t%s\t%s\n", example[0], example[1])
   283  	}
   284  	tw.Flush()
   285  	fmt.Fprintln(w)
   286  	fmt.Fprintln(w, "Use \"dep help [command]\" for more information about a command.")
   287  }
   288  
   289  func resetUsage(logger *log.Logger, fs *flag.FlagSet, name, args, longHelp string) {
   290  	var (
   291  		hasFlags   bool
   292  		flagBlock  bytes.Buffer
   293  		flagWriter = tabwriter.NewWriter(&flagBlock, 0, 4, 2, ' ', 0)
   294  	)
   295  	fs.VisitAll(func(f *flag.Flag) {
   296  		hasFlags = true
   297  		// Default-empty string vars should read "(default: <none>)"
   298  		// rather than the comparatively ugly "(default: )".
   299  		defValue := f.DefValue
   300  		if defValue == "" {
   301  			defValue = "<none>"
   302  		}
   303  		fmt.Fprintf(flagWriter, "\t-%s\t%s (default: %s)\n", f.Name, f.Usage, defValue)
   304  	})
   305  	flagWriter.Flush()
   306  	fs.Usage = func() {
   307  		logger.Printf("Usage: dep %s %s\n", name, args)
   308  		logger.Println()
   309  		logger.Println(strings.TrimSpace(longHelp))
   310  		logger.Println()
   311  		if hasFlags {
   312  			logger.Println("Flags:")
   313  			logger.Println()
   314  			logger.Println(flagBlock.String())
   315  		}
   316  	}
   317  }
   318  
   319  // parseArgs determines the name of the dep command and whether the user asked for
   320  // help to be printed.
   321  func parseArgs(args []string) (cmdName string, printCmdUsage bool, exit bool) {
   322  	isHelpArg := func() bool {
   323  		return strings.Contains(strings.ToLower(args[1]), "help") || strings.ToLower(args[1]) == "-h"
   324  	}
   325  
   326  	switch len(args) {
   327  	case 0, 1:
   328  		exit = true
   329  	case 2:
   330  		if isHelpArg() {
   331  			exit = true
   332  		} else {
   333  			cmdName = args[1]
   334  		}
   335  	default:
   336  		if isHelpArg() {
   337  			cmdName = args[2]
   338  			printCmdUsage = true
   339  		} else {
   340  			cmdName = args[1]
   341  		}
   342  	}
   343  	return cmdName, printCmdUsage, exit
   344  }
   345  
   346  // getEnv returns the last instance of an environment variable.
   347  func getEnv(env []string, key string) string {
   348  	for i := len(env) - 1; i >= 0; i-- {
   349  		v := env[i]
   350  		kv := strings.SplitN(v, "=", 2)
   351  		if kv[0] == key {
   352  			if len(kv) > 1 {
   353  				return kv[1]
   354  			}
   355  			return ""
   356  		}
   357  	}
   358  	return ""
   359  }
   360  
   361  // commentWriter writes a Go comment to the underlying io.Writer,
   362  // using line comment form (//).
   363  //
   364  // Copied from cmd/go/internal/help/help.go.
   365  type commentWriter struct {
   366  	W            io.Writer
   367  	wroteSlashes bool // Wrote "//" at the beginning of the current line.
   368  }
   369  
   370  func (c *commentWriter) Write(p []byte) (int, error) {
   371  	var n int
   372  	for i, b := range p {
   373  		if !c.wroteSlashes {
   374  			s := "//"
   375  			if b != '\n' {
   376  				s = "// "
   377  			}
   378  			if _, err := io.WriteString(c.W, s); err != nil {
   379  				return n, err
   380  			}
   381  			c.wroteSlashes = true
   382  		}
   383  		n0, err := c.W.Write(p[i : i+1])
   384  		n += n0
   385  		if err != nil {
   386  			return n, err
   387  		}
   388  		if b == '\n' {
   389  			c.wroteSlashes = false
   390  		}
   391  	}
   392  	return len(p), nil
   393  }
   394  
   395  type profile struct {
   396  	cpuProfile string
   397  
   398  	memProfile     string
   399  	memProfileRate int
   400  
   401  	mutexProfile         string
   402  	mutexProfileFraction int
   403  
   404  	// TODO(jbd): Add block profile and -trace.
   405  
   406  	f *os.File // file to write the profiling output to
   407  }
   408  
   409  func (p *profile) start() error {
   410  	switch {
   411  	case p.cpuProfile != "":
   412  		if err := p.createOutput(p.cpuProfile); err != nil {
   413  			return err
   414  		}
   415  		return pprof.StartCPUProfile(p.f)
   416  	case p.memProfile != "":
   417  		if p.memProfileRate > 0 {
   418  			runtime.MemProfileRate = p.memProfileRate
   419  		}
   420  		return p.createOutput(p.memProfile)
   421  	case p.mutexProfile != "":
   422  		if p.mutexProfileFraction > 0 {
   423  			runtime.SetMutexProfileFraction(p.mutexProfileFraction)
   424  		}
   425  		return p.createOutput(p.mutexProfile)
   426  	}
   427  	return nil
   428  }
   429  
   430  func (p *profile) finish() error {
   431  	if p.f == nil {
   432  		return nil
   433  	}
   434  	switch {
   435  	case p.cpuProfile != "":
   436  		pprof.StopCPUProfile()
   437  	case p.memProfile != "":
   438  		if err := pprof.WriteHeapProfile(p.f); err != nil {
   439  			return err
   440  		}
   441  	case p.mutexProfile != "":
   442  		if err := pprof.Lookup("mutex").WriteTo(p.f, 2); err != nil {
   443  			return err
   444  		}
   445  	}
   446  	return p.f.Close()
   447  }
   448  
   449  func (p *profile) createOutput(name string) error {
   450  	f, err := os.Create(name)
   451  	if err != nil {
   452  		return err
   453  	}
   454  	p.f = f
   455  	return nil
   456  }