1
0
mirror of https://github.com/golang/go synced 2024-09-30 20:28:32 -06:00

go.tools/go/loader: simplify command-line syntax.

Previously, each word could be a package import path or a
comma-separated list of *.go file names.  Now, if the
first word ends with ".go", all words are assumed to be
Go source files.  This makes it impossible to specify
two ad-hoc packages from source files, but no-one needs that.

FromArgs also takes a boolean indicating whether tests
are wanted or not.

Also: ssadump: add -test flag to set that boolean.
For the oracle it's always true.

LGTM=gri
R=gri
CC=golang-codereviews
https://golang.org/cl/61470047
This commit is contained in:
Alan Donovan 2014-02-11 16:52:16 -05:00
parent 1f29e74bfa
commit 08fadac071
5 changed files with 112 additions and 65 deletions

View File

@ -9,7 +9,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"go/build" "go/build"
"log"
"os" "os"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
@ -32,6 +31,8 @@ L build distinct packages seria[L]ly instead of in parallel.
N build [N]aive SSA form: don't replace local loads/stores with registers. N build [N]aive SSA form: don't replace local loads/stores with registers.
`) `)
var testFlag = flag.Bool("test", false, "Loads test code (*_test.go) for imported packages.")
var runFlag = flag.Bool("run", false, "Invokes the SSA interpreter on the program.") var runFlag = flag.Bool("run", false, "Invokes the SSA interpreter on the program.")
var interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter. var interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter.
@ -45,14 +46,15 @@ Usage: ssadump [<flag> ...] <args> ...
Use -help flag to display options. Use -help flag to display options.
Examples: Examples:
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package % ssadump -build=FPG hello.go # quickly dump SSA form of a single package
% ssadump -run -interp=T hello.go # interpret a program, with tracing % ssadump -run -interp=T hello.go # interpret a program, with tracing
% ssadump -run unicode -- -test.v # interpret the unicode package's tests, verbosely % ssadump -run -test unicode -- -test.v # interpret the unicode package's tests, verbosely
` + loader.FromArgsUsage + ` + loader.FromArgsUsage +
` `
When -run is specified, ssadump will find the first package that When -run is specified, ssadump will run the program.
defines a main function and run it in the interpreter. The entry point depends on the -test flag:
If none is found, the tests of each package will be run instead. if clear, it runs the first package named main.
if set, it runs the tests of each package.
` `
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
@ -70,6 +72,13 @@ func init() {
} }
func main() { func main() {
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "ssadump: %s.\n", err)
os.Exit(1)
}
}
func doMain() error {
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
@ -109,7 +118,7 @@ func main() {
case 'L': case 'L':
mode |= ssa.BuildSerially mode |= ssa.BuildSerially
default: default:
log.Fatalf("Unknown -build option: '%c'.", c) return fmt.Errorf("unknown -build option: '%c'", c)
} }
} }
@ -121,7 +130,8 @@ func main() {
case 'R': case 'R':
interpMode |= interp.DisableRecover interpMode |= interp.DisableRecover
default: default:
log.Fatalf("Unknown -interp option: '%c'.", c) fmt.Fprintf(os.Stderr, "ssadump: unknown -interp option: '%c'.", c)
os.Exit(1)
} }
} }
@ -134,16 +144,17 @@ func main() {
if *cpuprofile != "" { if *cpuprofile != "" {
f, err := os.Create(*cpuprofile) f, err := os.Create(*cpuprofile)
if err != nil { if err != nil {
log.Fatal(err) fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} }
pprof.StartCPUProfile(f) pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
// Use the initial packages from the command line. // Use the initial packages from the command line.
args, err := conf.FromArgs(args) args, err := conf.FromArgs(args, *testFlag)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
// The interpreter needs the runtime package. // The interpreter needs the runtime package.
@ -154,7 +165,7 @@ func main() {
// Load, parse and type-check the whole program. // Load, parse and type-check the whole program.
iprog, err := conf.Load() iprog, err := conf.Load()
if err != nil { if err != nil {
log.Fatal(err) return err
} }
// Create and build SSA-form program representation. // Create and build SSA-form program representation.
@ -163,29 +174,38 @@ func main() {
// Run the interpreter. // Run the interpreter.
if *runFlag { if *runFlag {
// If a package named "main" defines func main, run that.
// Otherwise run all packages' tests.
var main *ssa.Package var main *ssa.Package
pkgs := prog.AllPackages() pkgs := prog.AllPackages()
for _, pkg := range pkgs { if *testFlag {
if pkg.Object.Name() == "main" && pkg.Func("main") != nil { // If -test, run all packages' tests.
main = pkg if len(pkgs) > 0 {
break main = prog.CreateTestMainPackage(pkgs...)
}
if main == nil {
return fmt.Errorf("no tests")
}
} else {
// Otherwise, run main.main.
for _, pkg := range pkgs {
if pkg.Object.Name() == "main" {
main = pkg
if main.Func("main") == nil {
return fmt.Errorf("no func main() in main package")
}
break
}
}
if main == nil {
return fmt.Errorf("no main package")
} }
} }
if main == nil && len(pkgs) > 0 {
// TODO(adonovan): only run tests if -test flag specified.
main = prog.CreateTestMainPackage(pkgs...)
}
if main == nil {
log.Fatal("No main package and no tests")
}
if runtime.GOARCH != conf.Build.GOARCH { if runtime.GOARCH != build.Default.GOARCH {
log.Fatalf("Cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s).", return fmt.Errorf("cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s)",
conf.Build.GOARCH, runtime.GOARCH) build.Default.GOARCH, runtime.GOARCH)
} }
interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args) interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args)
} }
return nil
} }

View File

@ -14,7 +14,7 @@ import (
func loadFromArgs(args []string) (prog *loader.Program, rest []string, err error) { func loadFromArgs(args []string) (prog *loader.Program, rest []string, err error) {
conf := &loader.Config{} conf := &loader.Config{}
rest, err = conf.FromArgs(args) rest, err = conf.FromArgs(args, true)
if err == nil { if err == nil {
prog, err = conf.Load() prog, err = conf.Load()
} }
@ -39,11 +39,10 @@ func TestLoadFromArgs(t *testing.T) {
} }
// Successful load. // Successful load.
args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"} args = []string{"fmt", "errors", "--", "surplus"}
prog, rest, err := loadFromArgs(args) prog, rest, err := loadFromArgs(args)
if err != nil { if err != nil {
t.Errorf("loadFromArgs(%q) failed: %s", args, err) t.Fatalf("loadFromArgs(%q) failed: %s", args, err)
return
} }
if got, want := fmt.Sprint(rest), "[surplus]"; got != want { if got, want := fmt.Sprint(rest), "[surplus]"; got != want {
t.Errorf("loadFromArgs(%q) rest: got %s, want %s", args, got, want) t.Errorf("loadFromArgs(%q) rest: got %s, want %s", args, got, want)
@ -54,7 +53,7 @@ func TestLoadFromArgs(t *testing.T) {
pkgnames = append(pkgnames, info.Pkg.Path()) pkgnames = append(pkgnames, info.Pkg.Path())
} }
// Only the first import path (currently) contributes tests. // Only the first import path (currently) contributes tests.
if got, want := fmt.Sprint(pkgnames), "[fmt_test P]"; got != want { if got, want := fmt.Sprint(pkgnames), "[fmt_test]"; got != want {
t.Errorf("Created: got %s, want %s", got, want) t.Errorf("Created: got %s, want %s", got, want)
} }
@ -82,3 +81,25 @@ func TestLoadFromArgs(t *testing.T) {
} }
} }
} }
func TestLoadFromArgsSource(t *testing.T) {
// mixture of *.go/non-go.
args := []string{"testdata/a.go", "fmt"}
prog, _, err := loadFromArgs(args)
if err == nil {
t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
} else {
// "named files must be .go files: fmt": ok
}
// successful load
args = []string{"testdata/a.go", "testdata/b.go"}
prog, _, err = loadFromArgs(args)
if err != nil {
t.Errorf("loadFromArgs(%q) failed: %s", args, err)
return
}
if len(prog.Created) != 1 || prog.Created[0].Pkg.Path() != "P" {
t.Errorf("loadFromArgs(%q): got %v, want [P]", prog.Created)
}
}

View File

@ -23,7 +23,7 @@
// // Use the command-line arguments to specify // // Use the command-line arguments to specify
// // a set of initial packages to load from source. // // a set of initial packages to load from source.
// // See FromArgsUsage for help. // // See FromArgsUsage for help.
// rest, err := conf.FromArgs(os.Args[1:]) // rest, err := conf.FromArgs(os.Args[1:], wantTests)
// //
// // Parse the specified files and create an ad-hoc package with path "foo". // // Parse the specified files and create an ad-hoc package with path "foo".
// // All files must have the same 'package' declaration. // // All files must have the same 'package' declaration.
@ -228,15 +228,14 @@ func (conf *Config) ParseFile(filename string, src interface{}, mode parser.Mode
// FromArgs may wish to include in their -help output. // FromArgs may wish to include in their -help output.
const FromArgsUsage = ` const FromArgsUsage = `
<args> is a list of arguments denoting a set of initial packages. <args> is a list of arguments denoting a set of initial packages.
Each argument may take one of two forms: It may take one of two forms:
1. A comma-separated list of *.go source files. 1. A list of *.go source files.
All of the specified files are loaded, parsed and type-checked All of the specified files are loaded, parsed and type-checked
as a single package. as a single package. All the files must belong to the same directory.
All the files must belong to the same directory.
2. An import path. 2. A list of import paths, each denoting a package.
The package's directory is found relative to the $GOROOT and The package's directory is found relative to the $GOROOT and
$GOPATH using similar logic to 'go build', and the *.go files in $GOPATH using similar logic to 'go build', and the *.go files in
@ -248,9 +247,8 @@ Each argument may take one of two forms:
the non-*_test.go files are included in the primary package. Test the non-*_test.go files are included in the primary package. Test
files whose package declaration ends with "_test" are type-checked files whose package declaration ends with "_test" are type-checked
as another package, the 'external' test package, so that a single as another package, the 'external' test package, so that a single
import path may denote two packages. This behaviour may be import path may denote two packages. (Whether this behaviour is
disabled by prefixing the import path with "notest:", enabled is tool-specific, and may depend on additional flags.)
e.g. "notest:fmt".
Due to current limitations in the type-checker, only the first Due to current limitations in the type-checker, only the first
import path of the command line will contribute any tests. import path of the command line will contribute any tests.
@ -266,33 +264,40 @@ A '--' argument terminates the list of packages.
// set of initial packages to be specified; see FromArgsUsage message // set of initial packages to be specified; see FromArgsUsage message
// for details. // for details.
// //
func (conf *Config) FromArgs(args []string) (rest []string, err error) { func (conf *Config) FromArgs(args []string, xtest bool) (rest []string, err error) {
for len(args) > 0 { for i, arg := range args {
arg := args[0]
args = args[1:]
if arg == "--" { if arg == "--" {
rest = args[i+1:]
args = args[:i]
break // consume "--" and return the remaining args break // consume "--" and return the remaining args
} }
}
if strings.HasSuffix(arg, ".go") { if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
// Assume arg is a comma-separated list of *.go files // Assume args is a list of a *.go files
// denoting a single ad-hoc package. // denoting a single ad-hoc package.
err = conf.CreateFromFilenames("", strings.Split(arg, ",")...) for _, arg := range args {
} else { if !strings.HasSuffix(arg, ".go") {
// Assume arg is a directory name denoting a return nil, fmt.Errorf("named files must be .go files: %s", arg)
// package, perhaps plus an external test
// package unless prefixed by "notest:".
if path := strings.TrimPrefix(arg, "notest:"); path != arg {
conf.Import(path)
} else {
err = conf.ImportWithTests(path)
} }
} }
if err != nil { err = conf.CreateFromFilenames("", args...)
return nil, err } else {
// Assume args are directories each denoting a
// package and (perhaps) an external test, iff xtest.
for _, arg := range args {
if xtest {
err = conf.ImportWithTests(arg)
if err != nil {
break
}
} else {
conf.Import(arg)
}
} }
} }
return args, nil
return
} }
// CreateFromFilenames is a convenience function that parses the // CreateFromFilenames is a convenience function that parses the
@ -371,7 +376,8 @@ func (conf *Config) Import(path string) {
if conf.ImportPkgs == nil { if conf.ImportPkgs == nil {
conf.ImportPkgs = make(map[string]bool) conf.ImportPkgs = make(map[string]bool)
} }
conf.ImportPkgs[path] = false // unaugmented source package // Subtle: adds value 'false' unless value is already true.
conf.ImportPkgs[path] = conf.ImportPkgs[path] // unaugmented source package
} }
// PathEnclosingInterval returns the PackageInfo and ast.Node that // PathEnclosingInterval returns the PackageInfo and ast.Node that

View File

@ -53,7 +53,7 @@ func TestStdlib(t *testing.T) {
t0 := time.Now() t0 := time.Now()
var conf loader.Config var conf loader.Config
if _, err := conf.FromArgs(allPackages()); err != nil { if _, err := conf.FromArgs(allPackages(), true); err != nil {
t.Errorf("FromArgs failed: %v", err) t.Errorf("FromArgs failed: %v", err)
return return
} }

View File

@ -225,7 +225,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
conf := loader.Config{Build: buildContext, SourceImports: true} conf := loader.Config{Build: buildContext, SourceImports: true}
// Determine initial packages. // Determine initial packages.
args, err := conf.FromArgs(args) args, err := conf.FromArgs(args, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }