commands.go 20.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package cmds

import (
	"errors"
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"syscall"

D
Derek Parker 已提交
15
	"github.com/derekparker/delve/pkg/config"
16
	"github.com/derekparker/delve/pkg/goversion"
17
	"github.com/derekparker/delve/pkg/logflags"
D
Derek Parker 已提交
18 19
	"github.com/derekparker/delve/pkg/terminal"
	"github.com/derekparker/delve/pkg/version"
20 21
	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/api"
22
	"github.com/derekparker/delve/service/rpc2"
A
aarzilli 已提交
23
	"github.com/derekparker/delve/service/rpccommon"
24 25 26 27 28
	"github.com/spf13/cobra"
)

var (
	// Log is whether to log debug statements.
29 30 31
	Log bool
	// LogOutput is a comma separated list of components that should produce debug output.
	LogOutput string
32 33
	// Headless is whether to run without terminal.
	Headless bool
A
aarzilli 已提交
34 35
	// APIVersion is the requested API version while running headless
	APIVersion int
36 37 38 39 40 41 42 43
	// AcceptMulti allows multiple clients to connect to the same server
	AcceptMulti bool
	// Addr is the debugging server listen address.
	Addr string
	// InitFile is the path to initialization file.
	InitFile string
	// BuildFlags is the flags passed during compiler invocation.
	BuildFlags string
E
Evgeny L 已提交
44 45
	// WorkingDir is the working directory for running the program.
	WorkingDir string
46

47 48 49
	// Backend selection
	Backend string

50 51 52 53
	// RootCommand is the root of the command tree.
	RootCommand *cobra.Command

	traceAttachPid  int
54 55
	traceExecFile   string
	traceTestBinary bool
56 57 58 59 60 61 62 63 64 65 66
	traceStackDepth int

	conf *config.Config
)

const dlvCommandLongDesc = `Delve is a source level debugger for Go programs.

Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.

The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.
67 68 69 70

Pass flags to the program you are debugging using ` + "`--`" + `, for example:

` + "`dlv exec ./hello -- server --config conf/config.toml`"
71 72

// New returns an initialized command tree.
73
func New(docCall bool) *cobra.Command {
74 75 76 77
	// Config setup and load.
	conf = config.LoadConfig()
	buildFlagsDefault := ""
	if runtime.GOOS == "windows" {
78 79 80 81 82
		ver, _ := goversion.Installed()
		if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
			// Work-around for https://github.com/golang/go/issues/13154
			buildFlagsDefault = "-ldflags='-linkmode internal'"
		}
83 84 85 86 87 88 89 90 91 92
	}

	// Main dlv root command.
	RootCommand = &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
		Long:  dlvCommandLongDesc,
	}

	RootCommand.PersistentFlags().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
93 94
	RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
	RootCommand.PersistentFlags().StringVarP(&LogOutput, "log-output", "", "", `Comma separated list of components that should produce debug output, possible values:
95 96 97
	debugger	Log debugger commands
	gdbwire		Log connection to gdbserial backend
	lldbout		Copy output from debugserver/lldb to standard output
98
	debuglineerr	Log recoverable errors reading .debug_line
99
	rpc		Log all RPC messages
100
	fncall		Log function call protocol
101
Defaults to "debugger" when logging is enabled with --log.`)
102
	RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
103
	RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.")
A
aarzilli 已提交
104
	RootCommand.PersistentFlags().IntVar(&APIVersion, "api-version", 1, "Selects API version when headless.")
105 106
	RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.")
	RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.")
E
Evgeny L 已提交
107
	RootCommand.PersistentFlags().StringVar(&WorkingDir, "wd", ".", "Working directory for running the program.")
108 109 110
	RootCommand.PersistentFlags().StringVar(&Backend, "backend", "default", `Backend selection:
	default		Uses lldb on macOS, native everywhere else.
	native		Native backend.
111 112 113
	lldb		Uses lldb-server or debugserver.
	rr		Uses mozilla rr (https://github.com/mozilla/rr).
`)
114

115 116
	// 'attach' subcommand.
	attachCommand := &cobra.Command{
117
		Use:   "attach pid [executable]",
118
		Short: "Attach to running process and begin debugging.",
119 120 121 122 123 124
		Long: `Attach to an already running process and begin debugging it.

This command will cause Delve to take control of an already running process, and
begin a new debug session.  When exiting the debug session you will have the
option to let the process continue or kill it.
`,
125 126 127 128 129
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return errors.New("you must provide a PID")
			}
			return nil
130
		},
131
		Run: attachCmd,
132
	}
133
	RootCommand.AddCommand(attachCommand)
134

135 136 137 138
	// 'connect' subcommand.
	connectCommand := &cobra.Command{
		Use:   "connect addr",
		Short: "Connect to a headless debug server.",
139
		Long:  "Connect to a running headless debug server.",
140 141 142 143 144
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return errors.New("you must provide an address as the first argument")
			}
			return nil
145
		},
146
		Run: connectCmd,
147
	}
148
	RootCommand.AddCommand(connectCommand)
149 150 151 152

	// 'debug' subcommand.
	debugCommand := &cobra.Command{
		Use:   "debug [package]",
153
		Short: "Compile and begin debugging main package in current directory, or the package specified.",
154 155 156 157 158 159
		Long: `Compiles your program with optimizations disabled, starts and attaches to it.

By default, with no arguments, Delve will compile the 'main' package in the
current directory, and begin to debug it. Alternatively you can specify a
package name and Delve will compile that package instead, and begin a new debug
session.`,
160 161
		Run: debugCmd,
	}
162
	debugCommand.Flags().String("output", "debug", "Output path for the binary.")
163 164 165 166
	RootCommand.AddCommand(debugCommand)

	// 'exec' subcommand.
	execCommand := &cobra.Command{
167
		Use:   "exec <path/to/binary>",
168 169 170 171 172 173 174
		Short: "Execute a precompiled binary, and begin a debug session.",
		Long: `Execute a precompiled binary and begin a debug session.

This command will cause Delve to exec the binary and immediately attach to it to
begin a new debug session. Please note that if the binary was not compiled with
optimizations disabled, it may be difficult to properly debug it. Please
consider compiling debugging binaries with -gcflags="-N -l".`,
175 176 177 178 179 180 181
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return errors.New("you must provide a path to a binary")
			}
			return nil
		},
		Run: func(cmd *cobra.Command, args []string) {
182
			os.Exit(execute(0, args, conf, "", executingExistingFile))
183 184 185 186
		},
	}
	RootCommand.AddCommand(execCommand)

187 188 189 190 191 192 193 194
	// Deprecated 'run' subcommand.
	runCommand := &cobra.Command{
		Use:   "run",
		Short: "Deprecated command. Use 'debug' instead.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("This command is deprecated, please use 'debug' instead.")
			os.Exit(0)
		},
195
	}
196
	RootCommand.AddCommand(runCommand)
197 198 199 200 201

	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test [package]",
		Short: "Compile test binary and begin debugging program.",
202 203 204 205 206 207 208
		Long: `Compiles a test binary with optimizations disabled and begins a new debug session.

The test command allows you to begin a new debug session in the context of your
unit tests. By default Delve will debug the tests in the current directory.
Alternatively you can specify a package name, and Delve will debug the tests in
that package instead.`,
		Run: testCmd,
209
	}
210
	testCommand.Flags().String("output", "debug.test", "Output path for the binary.")
211 212
	RootCommand.AddCommand(testCommand)

213 214 215 216
	// 'trace' subcommand.
	traceCommand := &cobra.Command{
		Use:   "trace [package] regexp",
		Short: "Compile and begin tracing program.",
217 218 219 220 221 222 223
		Long: `Trace program execution.

The trace sub command will set a tracepoint on every function matching the
provided regular expression and output information when tracepoint is hit.  This
is useful if you do not want to begin an entire debug session, but merely want
to know what functions your process is executing.`,
		Run: traceCmd,
224
	}
225
	traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
226 227
	traceCommand.Flags().StringVarP(&traceExecFile, "exec", "e", "", "Binary file to exec and trace.")
	traceCommand.Flags().BoolVarP(&traceTestBinary, "test", "t", false, "Trace a test binary.")
228
	traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
229
	traceCommand.Flags().String("output", "debug", "Output path for the binary.")
230
	RootCommand.AddCommand(traceCommand)
231

232 233 234 235
	coreCommand := &cobra.Command{
		Use:   "core <executable> <core>",
		Short: "Examine a core dump.",
		Long: `Examine a core dump.
236

237 238 239 240 241 242 243 244 245 246 247 248 249
The core command will open the specified core file and the associated
executable and let you examine the state of the process when the
core dump was taken.`,
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 2 {
				return errors.New("you must provide a core file and an executable")
			}
			return nil
		},
		Run: coreCmd,
	}
	RootCommand.AddCommand(coreCommand)

250 251 252 253 254 255
	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("Delve Debugger\n%s\n", version.DelveVersion)
256 257
		},
	}
258
	RootCommand.AddCommand(versionCommand)
259

260
	if path, _ := exec.LookPath("rr"); path != "" || docCall {
261 262 263 264
		replayCommand := &cobra.Command{
			Use:   "replay [trace directory]",
			Short: "Replays a rr trace.",
			Long: `Replays a rr trace.
265

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed:
https://github.com/mozilla/rr
			`,
			PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
				if len(args) == 0 {
					return errors.New("you must provide a path to a binary")
				}
				return nil
			},
			Run: func(cmd *cobra.Command, args []string) {
				Backend = "rr"
				os.Exit(execute(0, []string{}, conf, args[0], executingOther))
			},
		}
		RootCommand.AddCommand(replayCommand)
	}

283 284
	RootCommand.DisableAutoGenTag = true

285 286 287
	return RootCommand
}

288 289 290 291 292 293 294 295
// Remove the file at path and issue a warning to stderr if this fails.
func remove(path string) {
	err := os.Remove(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not remove %v: %v\n", path, err)
	}
}

296 297
func debugCmd(cmd *cobra.Command, args []string) {
	status := func() int {
298 299 300 301 302 303
		debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}

304
		dlvArgs, targetArgs := splitArgs(cmd, args)
305
		err = gobuild(debugname, dlvArgs)
306 307 308 309
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}
310 311
		defer remove(debugname)
		processArgs := append([]string{debugname}, targetArgs...)
312
		return execute(0, processArgs, conf, "", executingGeneratedFile)
313 314 315 316 317 318
	}()
	os.Exit(status)
}

func traceCmd(cmd *cobra.Command, args []string) {
	status := func() int {
319 320
		err := logflags.Setup(Log, LogOutput)
		if err != nil {
321 322 323
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}
324

325 326 327 328 329 330 331
		if Headless {
			fmt.Fprintf(os.Stderr, "Warning: headless mode not supported with trace\n")
		}
		if AcceptMulti {
			fmt.Fprintf(os.Stderr, "Warning: accept multiclient mode not supported with trace")
		}

332 333 334 335 336 337
		var regexp string
		var processArgs []string

		dlvArgs, targetArgs := splitArgs(cmd, args)

		if traceAttachPid == 0 {
338
			var dlvArgsLen = len(dlvArgs)
339

340
			if dlvArgsLen == 1 {
341
				regexp = args[0]
342
				dlvArgs = dlvArgs[0:0]
343
			} else if dlvArgsLen >= 2 {
344 345 346 347
				if traceExecFile != "" {
					fmt.Fprintln(os.Stderr, "Cannot specify package when using exec.")
					return 1
				}
348 349
				regexp = dlvArgs[dlvArgsLen-1]
				dlvArgs = dlvArgs[:dlvArgsLen-1]
350
			}
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

			debugname := traceExecFile
			if traceExecFile == "" {
				debugname, err = filepath.Abs(cmd.Flag("output").Value.String())
				if err != nil {
					fmt.Fprintf(os.Stderr, "%v\n", err)
					return 1
				}
				if traceTestBinary {
					if err := gotestbuild(debugname, dlvArgs); err != nil {
						fmt.Fprintf(os.Stderr, "%v\n", err)
						return 1
					}
				} else {
					if err := gobuild(debugname, dlvArgs); err != nil {
						fmt.Fprintf(os.Stderr, "%v\n", err)
						return 1
					}
				}
				defer remove(debugname)
371 372
			}

373
			processArgs = append([]string{debugname}, targetArgs...)
374
		}
375 376 377

		// Make a local in-memory connection that client and server use to communicate
		listener, clientConn := service.ListenerPipe()
378 379 380
		defer listener.Close()

		// Create and start a debug server
A
aarzilli 已提交
381
		server := rpccommon.NewServer(&service.Config{
382 383 384
			Listener:    listener,
			ProcessArgs: processArgs,
			AttachPid:   traceAttachPid,
A
aarzilli 已提交
385
			APIVersion:  2,
E
Evgeny L 已提交
386
			WorkingDir:  WorkingDir,
387
			Backend:     Backend,
D
Derek Parker 已提交
388
		})
389 390 391 392
		if err := server.Run(); err != nil {
			fmt.Fprintln(os.Stderr, err)
			return 1
		}
393
		client := rpc2.NewClientFromConn(clientConn)
394 395 396 397 398 399
		funcs, err := client.ListFunctions(regexp)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return 1
		}
		for i := range funcs {
D
Derek Parker 已提交
400 401 402 403 404 405 406
			_, err = client.CreateBreakpoint(&api.Breakpoint{
				FunctionName: funcs[i],
				Tracepoint:   true,
				Line:         -1,
				Stacktrace:   traceStackDepth,
				LoadArgs:     &terminal.ShortLoadConfig,
			})
407 408 409 410
			if err != nil {
				fmt.Fprintln(os.Stderr, err)
				return 1
			}
D
Derek Parker 已提交
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
			addrs, err := client.FunctionReturnLocations(funcs[i])
			if err != nil {
				fmt.Fprintln(os.Stderr, err)
				return 1
			}
			for i := range addrs {
				_, err = client.CreateBreakpoint(&api.Breakpoint{
					Addr:        addrs[i],
					TraceReturn: true,
					Line:        -1,
					LoadArgs:    &terminal.ShortLoadConfig,
				})
				if err != nil {
					fmt.Fprintln(os.Stderr, err)
					return 1
				}
			}
428 429 430 431
		}
		cmds := terminal.DebugCommands(client)
		t := terminal.New(client, nil)
		defer t.Close()
432
		err = cmds.Call("continue", t)
433 434 435 436 437 438 439 440 441 442 443
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return 1
		}
		return 0
	}()
	os.Exit(status)
}

func testCmd(cmd *cobra.Command, args []string) {
	status := func() int {
444 445 446 447 448 449
		debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			return 1
		}

450
		dlvArgs, targetArgs := splitArgs(cmd, args)
451
		err = gotestbuild(debugname, dlvArgs)
452 453 454
		if err != nil {
			return 1
		}
455 456
		defer remove(debugname)
		processArgs := append([]string{debugname}, targetArgs...)
457

458
		return execute(0, processArgs, conf, "", executingGeneratedTest)
459 460 461 462 463 464 465 466 467 468
	}()
	os.Exit(status)
}

func attachCmd(cmd *cobra.Command, args []string) {
	pid, err := strconv.Atoi(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0])
		os.Exit(1)
	}
469
	os.Exit(execute(pid, args[1:], conf, "", executingOther))
470 471 472 473
}

func coreCmd(cmd *cobra.Command, args []string) {
	os.Exit(execute(0, []string{args[0]}, conf, args[1], executingOther))
474 475 476 477 478
}

func connectCmd(cmd *cobra.Command, args []string) {
	addr := args[0]
	if addr == "" {
479
		fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n")
480 481
		os.Exit(1)
	}
482
	os.Exit(connect(addr, nil, conf, executingOther))
483 484 485 486 487 488 489 490 491
}

func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
	if cmd.ArgsLenAtDash() >= 0 {
		return args[:cmd.ArgsLenAtDash()], args[cmd.ArgsLenAtDash():]
	}
	return args, []string{}
}

492
func connect(addr string, clientConn net.Conn, conf *config.Config, kind executeKind) int {
493
	// Create and start a terminal - attach to running instance
494 495 496 497 498 499
	var client *rpc2.RPCClient
	if clientConn != nil {
		client = rpc2.NewClientFromConn(clientConn)
	} else {
		client = rpc2.NewClient(addr)
	}
500 501 502 503 504 505 506
	if client.Recorded() && (kind == executingGeneratedFile || kind == executingGeneratedTest) {
		// When using the rr backend remove the trace directory if we built the
		// executable
		if tracedir, err := client.TraceDirectory(); err == nil {
			defer SafeRemoveAll(tracedir)
		}
	}
507
	term := terminal.New(client, conf)
508
	term.InitFile = InitFile
509 510 511 512 513 514 515
	status, err := term.Run()
	if err != nil {
		fmt.Println(err)
	}
	return status
}

516 517 518 519 520
type executeKind int

const (
	executingExistingFile = executeKind(iota)
	executingGeneratedFile
521
	executingGeneratedTest
522 523 524
	executingOther
)

525
func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind executeKind) int {
526 527 528 529
	if err := logflags.Setup(Log, LogOutput); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return 1
	}
530

531
	if Headless && (InitFile != "") {
532
		fmt.Fprint(os.Stderr, "Warning: init file ignored\n")
533 534
	}

535 536 537 538 539 540 541
	if !Headless && AcceptMulti {
		fmt.Fprint(os.Stderr, "Warning accept-multi: ignored\n")
		// AcceptMulti won't work in normal (non-headless) mode because we always
		// call server.Stop after the terminal client exits.
		AcceptMulti = false
	}

542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
	var listener net.Listener
	var clientConn net.Conn
	var err error

	// Make a TCP listener
	if Headless {
		listener, err = net.Listen("tcp", Addr)
	} else {
		listener, clientConn = service.ListenerPipe()
	}
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
		return 1
	}
	defer listener.Close()

558
	var server service.Server
559

560 561
	disconnectChan := make(chan struct{})

562
	// Create and start a debugger server
A
aarzilli 已提交
563 564 565
	switch APIVersion {
	case 1, 2:
		server = rpccommon.NewServer(&service.Config{
566 567 568 569 570 571 572 573 574 575
			Listener:             listener,
			ProcessArgs:          processArgs,
			AttachPid:            attachPid,
			AcceptMulti:          AcceptMulti,
			APIVersion:           APIVersion,
			WorkingDir:           WorkingDir,
			Backend:              Backend,
			CoreFile:             coreFile,
			Foreground:           Headless,
			DebugInfoDirectories: conf.DebugInfoDirectories,
576 577

			DisconnectChan: disconnectChan,
D
Derek Parker 已提交
578
		})
579
	default:
D
Derek Parker 已提交
580
		fmt.Printf("Unknown API version: %d\n", APIVersion)
581 582 583
		return 1
	}

584
	if err := server.Run(); err != nil {
585
		if err == api.ErrNotExecutable {
586 587 588 589 590 591 592 593 594 595 596
			switch kind {
			case executingGeneratedFile:
				fmt.Fprintln(os.Stderr, "Can not debug non-main package")
				return 1
			case executingExistingFile:
				fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0])
				return 1
			default:
				// fallthrough
			}
		}
597 598 599 600 601 602
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	var status int
	if Headless {
603
		ch := make(chan os.Signal, 1)
604
		signal.Notify(ch, syscall.SIGINT)
605 606 607 608
		select {
		case <-ch:
		case <-disconnectChan:
		}
609
		err = server.Stop()
610 611
		if err != nil {
			fmt.Println(err)
612
		}
613

614
		return status
615 616
	}

617
	return connect(listener.Addr().String(), clientConn, conf, kind)
618 619
}

A
aarzilli 已提交
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
func optflags(args []string) []string {
	// after go1.9 building with -gcflags='-N -l' and -a simultaneously works.
	// after go1.10 specifying -a is unnecessary because of the new caching strategy, but we should pass -gcflags=all=-N -l to have it applied to all packages
	// see https://github.com/golang/go/commit/5993251c015dfa1e905bdf44bdb41572387edf90

	ver, _ := goversion.Installed()
	switch {
	case ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}):
		args = append(args, "-gcflags", "all=-N -l")
	case ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}):
		args = append(args, "-gcflags", "-N -l", "-a")
	default:
		args = append(args, "-gcflags", "-N -l")
	}
	return args
}

637
func gobuild(debugname string, pkgs []string) error {
A
aarzilli 已提交
638 639
	args := []string{"-o", debugname}
	args = optflags(args)
640
	if BuildFlags != "" {
A
aarzilli 已提交
641
		args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
642
	}
643
	args = append(args, pkgs...)
644 645 646
	return gocommand("build", args...)
}

647
func gotestbuild(debugname string, pkgs []string) error {
648
	args := []string{"-c", "-o", debugname}
A
aarzilli 已提交
649
	args = optflags(args)
650
	if BuildFlags != "" {
A
aarzilli 已提交
651
		args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
652
	}
653
	args = append(args, pkgs...)
654 655 656 657 658 659 660 661 662 663
	return gocommand("test", args...)
}

func gocommand(command string, args ...string) error {
	allargs := []string{command}
	allargs = append(allargs, args...)
	goBuild := exec.Command("go", allargs...)
	goBuild.Stderr = os.Stderr
	return goBuild.Run()
}
664

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
// SafeRemoveAll removes dir and its contents but only as long as dir does
// not contain directories.
func SafeRemoveAll(dir string) {
	dh, err := os.Open(dir)
	if err != nil {
		return
	}
	defer dh.Close()
	fis, err := dh.Readdir(-1)
	if err != nil {
		return
	}
	for _, fi := range fis {
		if fi.IsDir() {
			return
		}
	}
	for _, fi := range fis {
		if err := os.Remove(filepath.Join(dir, fi.Name())); err != nil {
			return
		}
	}
	os.Remove(dir)
}