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 }