]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: work toward build script
authorRuss Cox <rsc@golang.org>
Tue, 20 Dec 2011 21:42:44 +0000 (16:42 -0500)
committerRuss Cox <rsc@golang.org>
Tue, 20 Dec 2011 21:42:44 +0000 (16:42 -0500)
The commands in the standard tree are now named
by the pseudo-import paths cmd/gofmt etc.
This avoids ambiguity between cmd/go's directory
and go/token's parent directory.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5503050

src/cmd/gc/runtime.go
src/cmd/gc/unsafe.go
src/cmd/go/build.go
src/cmd/go/list.go
src/cmd/go/main.go
src/cmd/go/pkg.go

index 2d9e423214715961c83b931fa59e3e49f50b1908..000b2328ff2b653cfba892107bfa5f46800f628a 100644 (file)
@@ -6,6 +6,8 @@
 // to update builtin.c.boot.  This is not done automatically
 // to avoid depending on having a working compiler binary.
 
+// +build ignore
+
 package PACKAGE
 
 // emitted by compiler, not referred to by go programs
index db27d7425facaf47f083c66f08890792b379a2bb..c7b48a8b062c86b5392b3dab2b4f08f4df6358e7 100644 (file)
@@ -6,6 +6,8 @@
 // to update builtin.c.boot.  This is not done automatically
 // to avoid depending on having a working compiler binary.
 
+// +build ignore
+
 package PACKAGE
 
 type Pointer uintptr // not really; filled in by compiler
index c434dc0db54b9f051f609cf0c72775a7c67574d3..f53efc8c1ff965538fa73922168d44aa363226c1 100644 (file)
@@ -6,6 +6,7 @@ package main
 
 import (
        "bytes"
+       "errors"
        "fmt"
        "go/build"
        "io"
@@ -14,8 +15,8 @@ import (
        "os/exec"
        "path/filepath"
        "regexp"
-       "runtime"
        "strings"
+       "sync"
 )
 
 // Break init cycles
@@ -108,7 +109,11 @@ type builder struct {
        goroot      string               // the $GOROOT
        goarch      string               // the $GOARCH
        goos        string               // the $GOOS
+       gobin       string               // the $GOBIN
        actionCache map[cacheKey]*action // a cache of already-constructed actions
+
+       output    sync.Mutex
+       scriptDir string // current directory in printed script
 }
 
 // An action represents a single action in the action graph.
@@ -148,9 +153,10 @@ func (b *builder) init(aflag, nflag, xflag bool) {
        b.nflag = nflag
        b.xflag = xflag
        b.actionCache = make(map[cacheKey]*action)
-       b.goroot = runtime.GOROOT()
        b.goarch = build.DefaultContext.GOARCH
        b.goos = build.DefaultContext.GOOS
+       b.goroot = build.Path[0].Path
+       b.gobin = build.Path[0].BinDir()
 
        b.arch, err = build.ArchChar(b.goarch)
        if err != nil {
@@ -312,7 +318,11 @@ func (b *builder) do(a *action) {
        }
        if a.f != nil {
                if err := a.f(b, a); err != nil {
-                       errorf("%s", err)
+                       if err == errPrintedOutput {
+                               exitStatus = 2
+                       } else {
+                               errorf("%s", err)
+                       }
                        a.failed = true
                }
        }
@@ -321,6 +331,14 @@ func (b *builder) do(a *action) {
 
 // build is the action for building a single package or command.
 func (b *builder) build(a *action) error {
+       if b.nflag {
+               // In -n mode, print a banner between packages.
+               // The banner is five lines so that when changes to
+               // different sections of the bootstrap script have to
+               // be merged, the banners give patch something
+               // to use to find its context.
+               fmt.Printf("\n#\n# %s\n#\n\n", a.p.ImportPath)
+       }
        obj := filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+"/_obj")) + string(filepath.Separator)
        if a.pkgobj == "" {
                a.pkgobj = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+".a"))
@@ -362,7 +380,7 @@ func (b *builder) build(a *action) error {
                        sfiles = nil
                }
 
-               outGo, outObj, err := b.cgo(a.p.Dir, obj, gccfiles, a.p)
+               outGo, outObj, err := b.cgo(a.p, obj, gccfiles)
                if err != nil {
                        return err
                }
@@ -405,7 +423,7 @@ func (b *builder) build(a *action) error {
                        // additional reflect type data.
                        gcargs = append(gcargs, "-+")
                }
-               if err := b.gc(a.p.Dir, obj+out, gcargs, inc, gofiles); err != nil {
+               if err := b.gc(a.p, obj+out, gcargs, inc, gofiles); err != nil {
                        return err
                }
                objects = append(objects, out)
@@ -439,7 +457,7 @@ func (b *builder) build(a *action) error {
 
        for _, file := range cfiles {
                out := file[:len(file)-len(".c")] + "." + b.arch
-               if err := b.cc(a.p.Dir, obj, obj+out, file); err != nil {
+               if err := b.cc(a.p, obj, obj+out, file); err != nil {
                        return err
                }
                objects = append(objects, out)
@@ -448,14 +466,14 @@ func (b *builder) build(a *action) error {
        // assemble .s files
        for _, file := range sfiles {
                out := file[:len(file)-len(".s")] + "." + b.arch
-               if err := b.asm(a.p.Dir, obj, obj+out, file); err != nil {
+               if err := b.asm(a.p, obj, obj+out, file); err != nil {
                        return err
                }
                objects = append(objects, out)
        }
 
        // pack into archive
-       if err := b.gopack(obj, a.pkgobj, objects); err != nil {
+       if err := b.gopack(a.p, obj, a.pkgobj, objects); err != nil {
                return err
        }
 
@@ -467,7 +485,7 @@ func (b *builder) build(a *action) error {
                        inc[i] = "-L"
                }
                a.pkgbin = obj + "a.out"
-               if err := b.ld(a.p.Dir, a.pkgbin, inc, a.pkgobj); err != nil {
+               if err := b.ld(a.p, a.pkgbin, inc, a.pkgobj); err != nil {
                        return err
                }
        }
@@ -503,7 +521,7 @@ func (b *builder) install(a *action) error {
 // copyFile is like 'cp src dst'.
 func (b *builder) copyFile(dst, src string, perm uint32) error {
        if b.nflag || b.xflag {
-               b.showcmd("cp %s %s", src, dst)
+               b.showcmd("", "cp %s %s", src, dst)
                if b.nflag {
                        return nil
                }
@@ -528,27 +546,91 @@ func (b *builder) copyFile(dst, src string, perm uint32) error {
        return nil
 }
 
-// fmtcmd is like fmt.Sprintf but replaces references to the
-// work directory (a temporary directory with a clumsy name)
-// with $WORK.
-func (b *builder) fmtcmd(format string, args ...interface{}) string {
-       s := fmt.Sprintf(format, args...)
-       s = strings.Replace(s, b.work, "$WORK", -1)
-       return s
+// fmtcmd formats a command in the manner of fmt.Sprintf but also:
+//
+//     If dir is non-empty and the script is not in dir right now,
+//     fmtcmd inserts "cd dir\n" before the command.
+//
+//     fmtcmd replaces the value of b.work with $WORK.
+//     fmtcmd replaces the value of b.goroot with $GOROOT.
+//     fmtcmd replaces the value of b.gobin with $GOBIN.
+//
+//     fmtcmd replaces the name of the current directory with dot (.)
+//     but only when it is at the beginning of a space-separated token.
+//
+func (b *builder) fmtcmd(dir string, format string, args ...interface{}) string {
+       cmd := fmt.Sprintf(format, args...)
+       if dir != "" {
+               cmd = strings.Replace(" "+cmd, " "+dir, " .", -1)[1:]
+               if b.scriptDir != dir {
+                       b.scriptDir = dir
+                       cmd = " cd " + dir + "\n" + cmd
+               }
+       }
+       cmd = strings.Replace(cmd, b.work, "$WORK", -1)
+       cmd = strings.Replace(cmd, b.gobin, "$GOBIN", -1)
+       cmd = strings.Replace(cmd, b.goroot, "$GOROOT", -1)
+       return cmd
 }
 
 // showcmd prints the given command to standard output
 // for the implementation of -n or -x.
-func (b *builder) showcmd(format string, args ...interface{}) {
-       fmt.Println(b.fmtcmd(format, args...))
+func (b *builder) showcmd(dir string, format string, args ...interface{}) {
+       b.output.Lock()
+       defer b.output.Unlock()
+       fmt.Println(b.fmtcmd(dir, format, args...))
+}
+
+// showOutput prints "# desc" followed by the given output.
+// The output is expected to contain references to 'dir', usually
+// the source directory for the package that has failed to build.
+// showOutput rewrites mentions of dir with a relative path to dir.
+// This is usually shorter and more pleasant than the absolute path.
+// For example, if fmt doesn't compile and we are in src/pkg/html,
+// the output is
+//
+//     $ go build
+//     # fmt
+//     ../fmt/print.go:1090: undefined: asdf
+//     $
+//
+// instead of
+//
+//     $ go build
+//     # fmt
+//     /usr/gopher/go/src/pkg/fmt/print.go:1090: undefined: asdf
+//     $
+//
+// showOutput also replaces references to the work directory with $WORK.
+//
+func (b *builder) showOutput(dir, desc, out string) {
+       prefix := "# " + desc
+       suffix := "\n" + out
+       pwd, _ := os.Getwd()
+       if reldir, err := filepath.Rel(pwd, dir); err == nil {
+               suffix = strings.Replace(suffix, " "+dir, " "+reldir, -1)
+               suffix = strings.Replace(suffix, "\n"+dir, "\n"+reldir, -1)
+       }
+       suffix = strings.Replace(suffix, " "+b.work, " $WORK", -1)
+
+       b.output.Lock()
+       defer b.output.Unlock()
+       fmt.Print(prefix, suffix)
 }
 
+// errPrintedOutput is a special error indicating that a command failed
+// but that it generated output as well, and that output has already
+// been printed, so there's no point showing 'exit status 1' or whatever
+// the wait status was.  The main executor, builder.do, knows not to
+// print this error.
+var errPrintedOutput = errors.New("already printed output - no need to show error")
+
 // run runs the command given by cmdline in the directory dir.
 // If the commnd fails, run prints information about the failure
 // and returns a non-nil error.
-func (b *builder) run(dir string, cmdline ...string) error {
+func (b *builder) run(dir string, desc string, cmdline ...string) error {
        if b.nflag || b.xflag {
-               b.showcmd("cd %s; %s", dir, strings.Join(cmdline, " "))
+               b.showcmd(dir, "%s", strings.Join(cmdline, " "))
                if b.nflag {
                        return nil
                }
@@ -562,8 +644,17 @@ func (b *builder) run(dir string, cmdline ...string) error {
        // TODO: cmd.Env
        err := cmd.Run()
        if buf.Len() > 0 {
-               fmt.Fprintf(os.Stderr, "# cd %s; %s\n", dir, strings.Join(cmdline, " "))
-               fmt.Fprintf(os.Stderr, "%s\n", buf.Bytes())
+               out := buf.Bytes()
+               if out[len(out)-1] != '\n' {
+                       out = append(out, '\n')
+               }
+               if desc == "" {
+                       desc = b.fmtcmd(dir, "%s", strings.Join(cmdline, " "))
+               }
+               b.showOutput(dir, desc, string(out))
+               if err != nil {
+                       err = errPrintedOutput
+               }
        }
        return err
 }
@@ -571,7 +662,7 @@ func (b *builder) run(dir string, cmdline ...string) error {
 // mkdir makes the named directory.
 func (b *builder) mkdir(dir string) error {
        if b.nflag || b.xflag {
-               b.showcmd("mkdir -p %s", dir)
+               b.showcmd("", "mkdir -p %s", dir)
                if b.nflag {
                        return nil
                }
@@ -583,50 +674,75 @@ func (b *builder) mkdir(dir string) error {
        return nil
 }
 
+// mkAbs returns an absolute path corresponding to
+// evaluating f in the directory dir.
+// We always pass absolute paths of source files so that
+// the error messages will include the full path to a file
+// in need of attention.
+func mkAbs(dir, f string) string {
+       // Leave absolute paths alone.
+       // Also, during -n mode we use the pseudo-directory $WORK
+       // instead of creating an actual work directory that won't be used.
+       // Leave paths beginning with $WORK alone too.
+       if filepath.IsAbs(f) || strings.HasPrefix(f, "$WORK") {
+               return f
+       }
+       return filepath.Join(dir, f)
+}
+
 // gc runs the Go compiler in a specific directory on a set of files
 // to generate the named output file. 
-func (b *builder) gc(dir, ofile string, gcargs, importArgs []string, gofiles []string) error {
+func (b *builder) gc(p *Package, ofile string, gcargs, importArgs []string, gofiles []string) error {
        args := []string{b.arch + "g", "-o", ofile}
        args = append(args, gcargs...)
        args = append(args, importArgs...)
-       args = append(args, gofiles...)
-       return b.run(dir, args...)
+       for _, f := range gofiles {
+               args = append(args, mkAbs(p.Dir, f))
+       }
+       return b.run(p.Dir, p.ImportPath, args...)
 }
 
 // asm runs the assembler in a specific directory on a specific file
 // to generate the named output file. 
-func (b *builder) asm(dir, obj, ofile, sfile string) error {
-       return b.run(dir, b.arch+"a", "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile)
+func (b *builder) asm(p *Package, obj, ofile, sfile string) error {
+       sfile = mkAbs(p.Dir, sfile)
+       return b.run(p.Dir, p.ImportPath, b.arch+"a", "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile)
 }
 
 // gopack runs the assembler in a specific directory to create
 // an archive from a set of object files.
 // typically it is run in the object directory.
-func (b *builder) gopack(objDir, afile string, ofiles []string) error {
-       return b.run(objDir, append([]string{"gopack", "grc", afile}, ofiles...)...)
+func (b *builder) gopack(p *Package, objDir, afile string, ofiles []string) error {
+       cmd := []string{"gopack", "grc"}
+       cmd = append(cmd, mkAbs(objDir, afile))
+       for _, f := range ofiles {
+               cmd = append(cmd, mkAbs(objDir, f))
+       }
+       return b.run(p.Dir, p.ImportPath, cmd...)
 }
 
 // ld runs the linker to create a package starting at mainpkg.
-func (b *builder) ld(dir, out string, importArgs []string, mainpkg string) error {
-       return b.run(dir, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)
+func (b *builder) ld(p *Package, out string, importArgs []string, mainpkg string) error {
+       return b.run(p.Dir, p.ImportPath, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)
 }
 
 // cc runs the gc-toolchain C compiler in a directory on a C file
 // to produce an output file.
-func (b *builder) cc(dir, objdir, ofile, cfile string) error {
-       inc := filepath.Join(runtime.GOROOT(), "pkg",
-               fmt.Sprintf("%s_%s", b.goos, b.goarch))
-       return b.run(dir, b.arch+"c", "-FVw", "-I", objdir, "-I", inc, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)
+func (b *builder) cc(p *Package, objdir, ofile, cfile string) error {
+       inc := filepath.Join(b.goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch))
+       cfile = mkAbs(p.Dir, cfile)
+       return b.run(p.Dir, p.ImportPath, b.arch+"c", "-FVw", "-I", objdir, "-I", inc, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)
 }
 
 // gcc runs the gcc C compiler to create an object from a single C file.
-func (b *builder) gcc(dir, out string, flags []string, cfile string) error {
-       return b.run(dir, b.gccCmd(dir, flags, "-o", out, "-c", cfile)...)
+func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error {
+       cfile = mkAbs(p.Dir, cfile)
+       return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir, flags, "-o", out, "-c", cfile)...)
 }
 
 // gccld runs the gcc linker to create an executable from a set of object files
-func (b *builder) gccld(dir, out string, flags []string, obj []string) error {
-       return b.run(dir, append(b.gccCmd(dir, flags, "-o", out), obj...)...)
+func (b *builder) gccld(p *Package, out string, flags []string, obj []string) error {
+       return b.run(p.Dir, p.ImportPath, append(b.gccCmd(p.Dir, flags, "-o", out), obj...)...)
 }
 
 // gccCmd returns a gcc command line ending with args
@@ -645,7 +761,7 @@ func (b *builder) gccCmd(objdir string, flags []string, args ...string) []string
 
 var cgoRe = regexp.MustCompile(`[/\\:]`)
 
-func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, outObj []string, err error) {
+func (b *builder) cgo(p *Package, obj string, gccfiles []string) (outGo, outObj []string, err error) {
        // cgo
        // TODO: CGOPKGPATH, CGO_FLAGS?
        gofiles := []string{obj + "_cgo_gotypes.go"}
@@ -664,14 +780,14 @@ func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, out
        }
        cgoArgs = append(cgoArgs, "--")
        cgoArgs = append(cgoArgs, p.CgoFiles...)
-       if err := b.run(dir, cgoArgs...); err != nil {
+       if err := b.run(p.Dir, p.ImportPath, cgoArgs...); err != nil {
                return nil, nil, err
        }
        outGo = append(outGo, gofiles...)
 
        // cc _cgo_defun.c
        defunObj := obj + "_cgo_defun." + b.arch
-       if err := b.cc(dir, obj, defunObj, defunC); err != nil {
+       if err := b.cc(p, obj, defunObj, defunC); err != nil {
                return nil, nil, err
        }
        outObj = append(outObj, defunObj)
@@ -680,7 +796,7 @@ func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, out
        var linkobj []string
        for _, cfile := range cfiles {
                ofile := obj + cfile[:len(cfile)-1] + "o"
-               if err := b.gcc(dir, ofile, p.info.CgoCFLAGS, obj+cfile); err != nil {
+               if err := b.gcc(p, ofile, p.info.CgoCFLAGS, obj+cfile); err != nil {
                        return nil, nil, err
                }
                linkobj = append(linkobj, ofile)
@@ -688,28 +804,28 @@ func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, out
                        outObj = append(outObj, ofile)
                }
        }
-       for _, file := range csfiles {
+       for _, file := range gccfiles {
                ofile := obj + cgoRe.ReplaceAllString(file[:len(file)-1], "_") + "o"
-               if err := b.gcc(dir, ofile, p.info.CgoCFLAGS, file); err != nil {
+               if err := b.gcc(p, ofile, p.info.CgoCFLAGS, file); err != nil {
                        return nil, nil, err
                }
                linkobj = append(linkobj, ofile)
                outObj = append(outObj, ofile)
        }
        dynobj := obj + "_cgo_.o"
-       if err := b.gccld(dir, dynobj, p.info.CgoLDFLAGS, linkobj); err != nil {
+       if err := b.gccld(p, dynobj, p.info.CgoLDFLAGS, linkobj); err != nil {
                return nil, nil, err
        }
 
        // cgo -dynimport
        importC := obj + "_cgo_import.c"
-       if err := b.run(dir, "cgo", "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
+       if err := b.run(p.Dir, p.ImportPath, "cgo", "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
                return nil, nil, err
        }
 
        // cc _cgo_import.ARCH
        importObj := obj + "_cgo_import." + b.arch
-       if err := b.cc(dir, obj, importObj, importC); err != nil {
+       if err := b.cc(p, obj, importObj, importC); err != nil {
                return nil, nil, err
        }
        outObj = append(outObj, importObj)
index bd75fd6cc4e21ae72dbf118704d8f0b2131b2868..df18df17a7abc61eb76dd0ffb573317d8cc54330 100644 (file)
@@ -14,7 +14,7 @@ var cmdList = &Command{
        UsageLine: "list [-f format] [-json] [importpath...]",
        Short:     "list packages",
        Long: `
-List lists the packages named by the import paths.
+List lists the packages named by the import paths, one per line.
 
 The default output shows the package name and file system location:
 
@@ -24,7 +24,7 @@ The default output shows the package name and file system location:
 
 The -f flag specifies an alternate format for the list,
 using the syntax of package template.  The default output
-is equivalent to -f '{{.Name}} {{.Dir}}'.  The struct
+is equivalent to -f '{{.ImportPath}}'.  The struct
 being passed to the template is:
 
     type Package struct {
@@ -57,7 +57,7 @@ func init() {
        cmdList.Run = runList // break init cycle
 }
 
-var listFmt = cmdList.Flag.String("f", "{{.Name}} {{.Dir}}", "")
+var listFmt = cmdList.Flag.String("f", "{{.ImportPath}}", "")
 var listJson = cmdList.Flag.Bool("json", false, "")
 var nl = []byte{'\n'}
 
index 79bcd66746fceabc0489d288e05235dffd43d2ff..6d70da824fda3116cd66509a2e3d5f93c74b9ad2 100644 (file)
@@ -237,8 +237,38 @@ func run(cmdline ...string) {
 // allPackages returns all the packages that can be found
 // under the $GOPATH directories and $GOROOT.
 func allPackages() []string {
-       have := make(map[string]bool)
+       have := map[string]bool{
+               "builtin": true, // ignore pseudo-package that exists only for documentation
+       }
        var pkgs []string
+
+       // Commands
+       goroot := build.Path[0].Path
+       cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
+       filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
+               if err != nil || !fi.IsDir() {
+                       return nil
+               }
+               name := path[len(cmd):]
+               // Commands are all in cmd/, not in subdirectories.
+               if strings.Contains(name, string(filepath.Separator)) {
+                       return filepath.SkipDir
+               }
+
+               _, err = build.ScanDir(path)
+               if err != nil {
+                       return nil
+               }
+
+               // We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
+               name = "cmd/" + name
+               if !have[name] {
+                       have[name] = true
+                       pkgs = append(pkgs, name)
+               }
+               return nil
+       })
+
        for _, t := range build.Path {
                src := t.SrcDir() + string(filepath.Separator)
                filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
@@ -256,21 +286,19 @@ func allPackages() []string {
                                return nil
                        }
                        name := path[len(src):]
-                       if have[name] {
-                               return nil
+                       if !have[name] {
+                               pkgs = append(pkgs, name)
+                               have[name] = true
                        }
-                       pkgs = append(pkgs, name)
-                       have[name] = true
 
                        // Avoid go/build test data.
+                       // TODO: Move it into a testdata directory.
                        if path == filepath.Join(build.Path[0].SrcDir(), "go/build") {
                                return filepath.SkipDir
                        }
 
                        return nil
                })
-
-               // TODO: Commands.
        }
        return pkgs
 }
index 503b098ce8644401f981727fc119e2979eaccfce..599c4e0779b19918731a9e2325e3ba8ee9bcdad4 100644 (file)
@@ -70,13 +70,15 @@ func loadPackage(arg string) (*Package, error) {
 
        // Find basic information about package path.
        t, importPath, err := build.FindTree(arg)
+       dir := ""
        // Maybe it is a standard command.
-       if err != nil && !filepath.IsAbs(arg) && !strings.HasPrefix(arg, ".") {
+       if err != nil && !filepath.IsAbs(arg) && strings.HasPrefix(arg, "cmd/") {
                goroot := build.Path[0]
-               p := filepath.Join(goroot.Path, "src/cmd", arg)
+               p := filepath.Join(goroot.Path, "src", arg)
                if st, err1 := os.Stat(p); err1 == nil && st.IsDir() {
                        t = goroot
-                       importPath = "../cmd/" + arg
+                       importPath = arg
+                       dir = p
                        err = nil
                }
        }
@@ -84,7 +86,9 @@ func loadPackage(arg string) (*Package, error) {
                return nil, err
        }
 
-       dir := filepath.Join(t.SrcDir(), filepath.FromSlash(importPath))
+       if dir == "" {
+               dir = filepath.Join(t.SrcDir(), filepath.FromSlash(importPath))
+       }
 
        // Maybe we know the package by its directory.
        if p := packageCache[dir]; p != nil {
@@ -140,6 +144,13 @@ func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string
        }
        sort.Strings(p.gofiles)
 
+       // Packages that use cgo import runtime/cgo implicitly,
+       // except runtime/cgo itself.
+       if len(info.CgoFiles) > 0 && (!p.Standard || p.ImportPath != "runtime/cgo") {
+               p.Imports = append(p.Imports, "runtime/cgo")
+               sort.Strings(p.Imports)
+       }
+
        // Record package under both import path and full directory name.
        packageCache[dir] = p
        packageCache[importPath] = p