Skip to content

Commit

Permalink
Make UI more like the go tool: add build, -c and -o - fixes #30
Browse files Browse the repository at this point in the history
  • Loading branch information
FiloSottile committed May 19, 2015
1 parent e990850 commit 30769a0
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 56 deletions.
210 changes: 162 additions & 48 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ var (
outputFlags flag.FlagSet
w = outputFlags.Bool("w", false, "write result to (source) file instead of stdout")

runTestFlags flag.FlagSet
instrument = runTestFlags.String("instrument", "", "extra packages to enable for debugging")
work = runTestFlags.Bool("godebugwork", false, "print the name of the temporary work directory and do not delete it when exiting")
runFlags flag.FlagSet
instrument = runFlags.String("instrument", "", "extra packages to enable for debugging")
work = runFlags.Bool("godebugwork", false, "print the name of the temporary work directory and do not delete it when exiting")
tags = runFlags.String("tags", "", "go build tags")

buildFlags = runFlags
o = buildFlags.String("o", "", "output binary name")

testFlags = buildFlags
c = testFlags.Bool("c", false, "compile the test binary but do not run it")
)

func init() {
Expand All @@ -48,6 +55,7 @@ Usage:
The commands are:
build compile a debug-ready Go program
run compile, run, and debug a Go program
test compile, run, and debug Go package tests
output generate debug source code, but do not build or run it
Expand All @@ -57,15 +65,7 @@ Use "godebug help [command]" for more information about a command.
exit(0)
}

func runUsage() {
log.Print(
`usage: godebug run [-godebugwork] [-instrument pkgs...] gofiles... [--] [arguments...]
Run is a wrapper around 'go run'. It generates debugging code for
the named Go source files and runs 'go run' on the result.
Optionally, a '--' argument ends the list of gofiles.
const commonArgsUsage = `
By default, godebug generates debugging code only for the named
Go source files, and not their dependencies. This means that in
the debugging session you will not be able to step into function
Expand All @@ -75,30 +75,54 @@ must not be relative.
If -godebugwork is set, godebug will print the name of the
temporary work directory and not delete it when exiting.
`)
-tags works like in 'go help build'.
`

func runUsage() {
log.Print(
`usage: godebug run [-godebugwork] [-instrument pkgs...] [-tags 'tag list'] gofiles... [--] [arguments...]
Run emulates 'go run' behavior. It generates debugging code for the
named *.go files and then compiles and executes the result.
Like 'go run' it takes a list of go files which are treated as a
single main package. The rest of the arguments is passed to the
binary. Optionally, a '--' argument ends the list of gofiles.
` + commonArgsUsage)
}

func buildUsage() {
log.Print(
`usage: godebug build [-godebugwork] [-instrument pkgs...] [-tags 'tag list'] [-o output] [package]
Build is a wrapper around 'go build'. It generates debugging code for
the named target and builds the result.
Like 'go build' it takes a single main package. If arguments are a list
of *.go files they are treated as a single package. Relative packages are
not supported - which means you can't leave the package name out, too.
The output file naming if -o is not passed works like 'go build' (see
'go help build') with the addition of the suffix '.debug'.
` + commonArgsUsage)
}

func testUsage() {
log.Print(
`usage: godebug test [-godebugwork] [-instrument pkgs...] [packages] [flags for test binary]
`usage: godebug test [-godebugwork] [-instrument pkgs...] [-tags 'tag list'] [-c] [-o output] [packages] [flags for test binary]
Test is a wrapper around 'go test'. It generates debugging code for
the tests in the named packages and runs 'go test' on the result.
As with 'go test', by default godebug test needs no arguments.
By default, godebug generates debugging code only for the named
packages, and not their dependencies. This means that in the
debugging session you will not be able to step into function
calls from imported packages. To instrument other packages,
pass the -instrument flag. Packages are comma-separated and
must not be relative.
If -godebugwork is set, godebug will print the name of the
temporary work directory and not delete it when exiting.
Flags parsing, -c and -o work like for 'go test' - see 'go help test'.
The default binary name for -c has the suffix '.test.debug'.
See also: 'go help testflag'.
`)
See also: 'go help testflag'. Note that you have to use the 'test.'
prefix like '-test.v'.
` + commonArgsUsage)
}

func outputUsage() {
Expand Down Expand Up @@ -148,6 +172,8 @@ func main() {
doOutput(os.Args[2:])
case "run":
doRun(os.Args[2:])
case "build":
doBuild(os.Args[2:])
case "test":
doTest(os.Args[2:])
default:
Expand All @@ -164,16 +190,52 @@ func doHelp(args []string) {
outputUsage()
case "run":
runUsage()
case "build":
buildUsage()
case "test":
testUsage()
default:
log.Printf("Unknown help topic `%s`. Run 'godebug help'.\n", args[0])
}
}

func doBuild(args []string) {
exitIfErr(buildFlags.Parse(args))
goArgs, isPkg := parseBuildArguments(buildFlags.Args())

conf := newLoader()
if isPkg {
conf.Import(goArgs[0])
} else {
exitIfErr(conf.CreateFromFilenames("main", goArgs...))
}

tmpDir := generateSourceFiles(&conf, "build")
tmpFile := filepath.Join(tmpDir, "godebug.-i.a.out")

// Run 'go build -i' once without changing the GOPATH.
// This will recompile and install any out-of-date packages.
// When we modify the GOPATH in the next invocation of the go tool, it will
// not check if any of the uninstrumented dependencies are out-of-date.
shellGo("", []string{"build", "-o", tmpFile, "-tags", *tags, "-i"}, goArgs)

if isPkg {
goArgs = mapPkgsToTmpDir(goArgs)
} else {
goArgs = mapToTmpDir(tmpDir, goArgs)
}

bin := filepath.Base(strings.TrimSuffix(goArgs[0], ".go")) + ".debug"
if *o != "" {
bin = *o
}

shellGo(tmpDir, []string{"build", "-o", bin, "-tags", *tags}, goArgs)
}

func doRun(args []string) {
// Parse arguments.
exitIfErr(runTestFlags.Parse(args))
exitIfErr(runFlags.Parse(args))

// Separate the .go files from the arguments to the binary we're building.
gofiles, rest := getGoFiles()
Expand All @@ -182,22 +244,24 @@ func doRun(args []string) {
}

// Build a loader.Config from the .go files.
var conf loader.Config
conf := newLoader()
exitIfErr(conf.CreateFromFilenames("main", gofiles...))

tmpDir := generateSourceFiles(&conf, "run")

// Run 'go build -i' once without changing the GOPATH.
// This will recompile and install any out-of-date packages.
// When we modify the GOPATH in the next invocation of the go tool,
// it will not check if any of the uninstrumented dependencies are out-of-date.
shellGo("", []string{"build", "-o", os.DevNull, "-i"}, gofiles)
// When we modify the GOPATH in the next invocation of the go tool, it will
// not check if any of the uninstrumented dependencies are out-of-date.
shellGo("", []string{"build", "-o", os.DevNull, "-tags", *tags, "-i"},
gofiles)

// Run 'go build', then run the binary.
// We do this rather than invoking 'go run' directly so we can implement the '--' argument,
// which 'go run' does not have.
// We do this rather than invoking 'go run' directly so we can implement
// the '--' argument, which 'go run' does not have.
bin := filepath.Join(tmpDir, "godebug.a.out")
shellGo(tmpDir, []string{"build", "-o", bin}, mapToTmpDir(tmpDir, gofiles))
shellGo(tmpDir, []string{"build", "-tags", *tags, "-o", bin},
mapToTmpDir(tmpDir, gofiles))
shell("", bin, rest...)
}

Expand All @@ -211,8 +275,8 @@ func doTest(args []string) {
}

// Build a loader.Config from the provided packages.
var conf loader.Config
for _, pkg := range packages {
conf := newLoader()
for _, pkg := range gotool.ImportPaths(packages) {
exitIfErr(conf.ImportWithTests(pkg))
}

Expand All @@ -222,16 +286,28 @@ func doTest(args []string) {
// This will recompile and install any out-of-date packages.
// When we modify the GOPATH in the next invocation of the go tool,
// it will not check if any of the uninstrumented dependencies are out-of-date.
shellGo("", []string{"test", "-i"}, packages)
shellGo("", []string{"test", "-tags", *tags, "-i"}, packages)

// The target binary goes to -o if specified, otherwise to the default name
// if -c is specified, otherwise to the temporary directory.
bin := filepath.Join(tmpDir, "godebug-test-bin.test")
if *c {
bin = filepath.Base(mapPkgsToTmpDir(packages)[0]) + ".test.debug"
}
if *o != "" {
bin = abs(*o)
}

// First compile the test with -c and then run the binary directly.
// This resolves some issues that came up with running 'go test' directly:
// (1) 'go test' changes the working directory to that of the source files of the test.
// (2) 'go test' does not forward stdin to the test binary.
bin := filepath.Join(tmpDir, "godebug-test-bin.test")
goArgs := []string{"test", "-c", "-o", bin}

goArgs := []string{"test", "-tags", *tags, "-c", "-o", bin}
shellGo(tmpDir, goArgs, mapPkgsToTmpDir(packages))
shell("", bin, testFlags...)
if !*c {
shell("", bin, testFlags...)
}
}

func generateSourceFiles(conf *loader.Config, subcommand string) (tmpDirPath string) {
Expand Down Expand Up @@ -314,6 +390,14 @@ func generateSourceFiles(conf *loader.Config, subcommand string) (tmpDirPath str
return tmpDir
}

func newLoader() loader.Config {
var conf loader.Config
b := build.Default
b.BuildTags = append(b.BuildTags, strings.Split(*tags, " ")...)
conf.Build = &b
return conf
}

func checkForUnusedBreakpoints(subcommand string, prog *loader.Program, stdLib map[string]bool) {
initialPkgs := make(map[*loader.PackageInfo]bool)
for _, pkg := range prog.InitialPackages() {
Expand Down Expand Up @@ -357,13 +441,13 @@ func markAlmostAllPackages(prog *loader.Program, stdLib map[string]bool) {
}

func getGoFiles() (gofiles, rest []string) {
for i, arg := range runTestFlags.Args() {
for i, arg := range runFlags.Args() {
if arg == "--" {
rest = runTestFlags.Args()[i+1:]
rest = runFlags.Args()[i+1:]
break
}
if !strings.HasSuffix(arg, ".go") {
rest = runTestFlags.Args()[i:]
rest = runFlags.Args()[i:]
break
}
gofiles = append(gofiles, arg)
Expand Down Expand Up @@ -415,6 +499,14 @@ func getwd() string {
return cwd
}

func abs(s string) string {
res, err := filepath.Abs(s)
if err != nil {
logFatal("failed to make output path absolute")
}
return res
}

func mapPkgsToTmpDir(pkgs []string) []string {
result := make([]string, len(pkgs))
cwd := getwd()
Expand Down Expand Up @@ -485,8 +577,27 @@ func doOutput(args []string) {
})
}

func parseTestArguments(args []string) (packages, testFlags []string) {
// format: [-godebugwork] [-instrument pkgs...] [packages] [testFlags]
func parseBuildArguments(args []string) ([]string, bool) {
if len(args) == 0 {
return []string{"."}, true
}
if len(args) == 1 && !strings.HasSuffix(args[0], ".go") {
return args, true
}
for _, a := range args {
if !strings.HasSuffix(a, ".go") {
logFatal("you can only build a set of files or a single package")
}
}
return args, false
}

func isFlag(arg, name string) bool {
return arg == name || strings.HasPrefix(arg, name+"=")
}

func parseTestArguments(args []string) (packages, otherFlags []string) {
// format: [-godebugwork] [-instrument pkgs...] [-o=...] [-c] [packages] [testFlags]

// Find first unrecognized flag.
sep := len(args)
Expand All @@ -495,15 +606,18 @@ func parseTestArguments(args []string) (packages, testFlags []string) {
arg = arg[1:]
}
if strings.HasPrefix(arg, "-") &&
!strings.HasPrefix(arg, "-instrument") &&
!strings.HasPrefix(arg, "-godebugwork") {
!isFlag(arg, "-instrument") &&
!isFlag(arg, "-godebugwork") &&
!isFlag(arg, "-tags") &&
!isFlag(arg, "-o") &&
!isFlag(arg, "-c") {
sep = i
break
}
}

exitIfErr(runTestFlags.Parse(args[:sep]))
return runTestFlags.Args(), args[sep:]
exitIfErr(testFlags.Parse(args[:sep]))
return testFlags.Args(), args[sep:]
}

var (
Expand Down
Loading

0 comments on commit 30769a0

Please sign in to comment.