summaryrefslogtreecommitdiff
path: root/src/cmd/go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go')
-rw-r--r--src/cmd/go/bootstrap.go17
-rw-r--r--src/cmd/go/build.go272
-rw-r--r--src/cmd/go/clean.go2
-rw-r--r--src/cmd/go/discovery.go63
-rw-r--r--src/cmd/go/doc.go101
-rw-r--r--src/cmd/go/env.go89
-rw-r--r--src/cmd/go/get.go132
-rw-r--r--src/cmd/go/help.go48
-rw-r--r--src/cmd/go/http.go52
-rw-r--r--src/cmd/go/list.go37
-rw-r--r--src/cmd/go/main.go57
-rw-r--r--src/cmd/go/match_test.go36
-rw-r--r--src/cmd/go/pkg.go93
-rw-r--r--src/cmd/go/run.go9
-rwxr-xr-xsrc/cmd/go/test.bash71
-rw-r--r--src/cmd/go/test.go44
-rw-r--r--src/cmd/go/testflag.go47
-rw-r--r--src/cmd/go/vcs.go185
18 files changed, 1095 insertions, 260 deletions
diff --git a/src/cmd/go/bootstrap.go b/src/cmd/go/bootstrap.go
index bc9a3dbbc..32941404c 100644
--- a/src/cmd/go/bootstrap.go
+++ b/src/cmd/go/bootstrap.go
@@ -10,8 +10,21 @@
package main
-import "errors"
+import (
+ "errors"
+ "io"
+)
+
+var errHTTP = errors.New("no http in bootstrap go command")
func httpGET(url string) ([]byte, error) {
- return nil, errors.New("no http in bootstrap go command")
+ return nil, errHTTP
+}
+
+func httpsOrHTTP(importPath string) (string, io.ReadCloser, error) {
+ return "", nil, errHTTP
+}
+
+func parseMetaGoImports(r io.Reader) (imports []metaImport) {
+ panic("unreachable")
}
diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go
index c330bd5de..16177c127 100644
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -12,6 +12,7 @@ import (
"go/build"
"io"
"io/ioutil"
+ "log"
"os"
"os/exec"
"path"
@@ -20,6 +21,7 @@ import (
"runtime"
"strings"
"sync"
+ "time"
)
var cmdBuild = &Command{
@@ -58,6 +60,8 @@ The build flags are shared by the build, install, run, and test commands:
-x
print the commands.
+ -compiler name
+ name of compiler to use, as in runtime.Compiler (gccgo or gc)
-gccgoflags 'arg list'
arguments to pass on each gccgo compiler/linker invocation
-gcflags 'arg list'
@@ -97,9 +101,42 @@ var buildLdflags []string // -ldflags flag
var buildGccgoflags []string // -gccgoflags flag
var buildContext = build.Default
+var buildToolchain toolchain = noToolchain{}
+
+// buildCompiler implements flag.Var.
+// It implements Set by updating both
+// buildToolchain and buildContext.Compiler.
+type buildCompiler struct{}
+
+func (c buildCompiler) Set(value string) error {
+ switch value {
+ case "gc":
+ buildToolchain = gcToolchain{}
+ case "gccgo":
+ buildToolchain = gccgcToolchain{}
+ default:
+ return fmt.Errorf("unknown compiler %q", value)
+ }
+ buildContext.Compiler = value
+ return nil
+}
+
+func (c buildCompiler) String() string {
+ return buildContext.Compiler
+}
+
+func init() {
+ switch build.Default.Compiler {
+ case "gc":
+ buildToolchain = gcToolchain{}
+ case "gccgo":
+ buildToolchain = gccgcToolchain{}
+ }
+}
// addBuildFlags adds the flags common to the build and install commands.
func addBuildFlags(cmd *Command) {
+ // NOTE: If you add flags here, also add them to testflag.go.
cmd.Flag.BoolVar(&buildA, "a", false, "")
cmd.Flag.BoolVar(&buildN, "n", false, "")
cmd.Flag.IntVar(&buildP, "p", buildP, "")
@@ -110,6 +147,7 @@ func addBuildFlags(cmd *Command) {
cmd.Flag.Var((*stringsFlag)(&buildLdflags), "ldflags", "")
cmd.Flag.Var((*stringsFlag)(&buildGccgoflags), "gccgoflags", "")
cmd.Flag.Var((*stringsFlag)(&buildContext.BuildTags), "tags", "")
+ cmd.Flag.Var(buildCompiler{}, "compiler", "")
}
type stringsFlag []string
@@ -131,9 +169,7 @@ func runBuild(cmd *Command, args []string) {
if len(pkgs) == 1 && pkgs[0].Name == "main" && *buildO == "" {
_, *buildO = path.Split(pkgs[0].ImportPath)
- if goos == "windows" {
- *buildO += ".exe"
- }
+ *buildO += exeSuffix
}
if *buildO != "" {
@@ -348,6 +384,7 @@ func goFilesPackage(gofiles []string) *Package {
bp, err := ctxt.ImportDir(dir, 0)
pkg := new(Package)
+ pkg.local = true
pkg.load(&stk, bp, err)
pkg.localPrefix = dirToImportPath(dir)
pkg.ImportPath = "command-line-arguments"
@@ -355,7 +392,7 @@ func goFilesPackage(gofiles []string) *Package {
if *buildO == "" {
if pkg.Name == "main" {
_, elem := filepath.Split(gofiles[0])
- *buildO = elem[:len(elem)-len(".go")]
+ *buildO = elem[:len(elem)-len(".go")] + exeSuffix
} else {
*buildO = pkg.Name + ".a"
}
@@ -412,7 +449,7 @@ func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action
return a
}
// gccgo standard library is "fake" too.
- if _, ok := buildToolchain.(gccgoToolchain); ok {
+ if _, ok := buildToolchain.(gccgcToolchain); ok {
// the target name is needed for cgo.
a.target = p.target
return a
@@ -568,7 +605,12 @@ func (b *builder) do(root *action) {
}
// build is the action for building a single package or command.
-func (b *builder) build(a *action) error {
+func (b *builder) build(a *action) (err error) {
+ defer func() {
+ if err != nil && err != errPrintedOutput {
+ err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
+ }
+ }()
if buildN {
// In -n mode, print a banner between packages.
// The banner is five lines so that when changes to
@@ -620,7 +662,7 @@ func (b *builder) build(a *action) error {
}
cgoExe := tool("cgo")
- if a.cgo != nil {
+ if a.cgo != nil && a.cgo.target != "" {
cgoExe = a.cgo.target
}
outGo, outObj, err := b.cgo(a.p, cgoExe, obj, gccfiles)
@@ -692,6 +734,11 @@ func (b *builder) build(a *action) error {
// http://golang.org/issue/2601
objects = append(objects, cgoObjects...)
+ // Add system object files.
+ for _, syso := range a.p.SysoFiles {
+ objects = append(objects, filepath.Join(a.p.Dir, syso))
+ }
+
// Pack into archive in obj directory
if err := buildToolchain.pack(b, a.p, obj, a.objpkg, objects); err != nil {
return err
@@ -712,7 +759,12 @@ func (b *builder) build(a *action) error {
}
// install is the action for installing a single package or executable.
-func (b *builder) install(a *action) error {
+func (b *builder) install(a *action) (err error) {
+ defer func() {
+ if err != nil && err != errPrintedOutput {
+ err = fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
+ }
+ }()
a1 := a.deps[0]
perm := os.FileMode(0666)
if a1.link {
@@ -767,7 +819,7 @@ func (b *builder) includeArgs(flag string, all []*action) []string {
for _, a1 := range all {
if dir := a1.pkgdir; dir == a1.p.build.PkgRoot && !incMap[dir] {
incMap[dir] = true
- if _, ok := buildToolchain.(gccgoToolchain); ok {
+ if _, ok := buildToolchain.(gccgcToolchain); ok {
dir = filepath.Join(dir, "gccgo")
} else {
dir = filepath.Join(dir, goos+"_"+goarch)
@@ -833,7 +885,7 @@ func (b *builder) copyFile(a *action, dst, src string, perm os.FileMode) error {
df.Close()
if err != nil {
os.Remove(dst)
- return err
+ return fmt.Errorf("copying %s to %s: %v", src, dst, err)
}
return nil
}
@@ -888,8 +940,6 @@ func (b *builder) fmtcmd(dir string, format string, args ...interface{}) string
if b.work != "" {
cmd = strings.Replace(cmd, b.work, "$WORK", -1)
}
- cmd = strings.Replace(cmd, gobin, "$GOBIN", -1)
- cmd = strings.Replace(cmd, goroot, "$GOROOT", -1)
return cmd
}
@@ -998,14 +1048,66 @@ func (b *builder) runOut(dir string, desc string, cmdargs ...interface{}) ([]byt
}
}
- var buf bytes.Buffer
- cmd := exec.Command(cmdline[0], cmdline[1:]...)
- cmd.Stdout = &buf
- cmd.Stderr = &buf
- cmd.Dir = dir
- // TODO: cmd.Env
- err := cmd.Run()
- return buf.Bytes(), err
+ nbusy := 0
+ for {
+ var buf bytes.Buffer
+ cmd := exec.Command(cmdline[0], cmdline[1:]...)
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.Dir = dir
+ // TODO: cmd.Env
+ err := cmd.Run()
+
+ // cmd.Run will fail on Unix if some other process has the binary
+ // we want to run open for writing. This can happen here because
+ // we build and install the cgo command and then run it.
+ // If another command was kicked off while we were writing the
+ // cgo binary, the child process for that command may be holding
+ // a reference to the fd, keeping us from running exec.
+ //
+ // But, you might reasonably wonder, how can this happen?
+ // The cgo fd, like all our fds, is close-on-exec, so that we need
+ // not worry about other processes inheriting the fd accidentally.
+ // The answer is that running a command is fork and exec.
+ // A child forked while the cgo fd is open inherits that fd.
+ // Until the child has called exec, it holds the fd open and the
+ // kernel will not let us run cgo. Even if the child were to close
+ // the fd explicitly, it would still be open from the time of the fork
+ // until the time of the explicit close, and the race would remain.
+ //
+ // On Unix systems, this results in ETXTBSY, which formats
+ // as "text file busy". Rather than hard-code specific error cases,
+ // we just look for that string. If this happens, sleep a little
+ // and try again. We let this happen three times, with increasing
+ // sleep lengths: 100+200+400 ms = 0.7 seconds.
+ //
+ // An alternate solution might be to split the cmd.Run into
+ // separate cmd.Start and cmd.Wait, and then use an RWLock
+ // to make sure that copyFile only executes when no cmd.Start
+ // call is in progress. However, cmd.Start (really syscall.forkExec)
+ // only guarantees that when it returns, the exec is committed to
+ // happen and succeed. It uses a close-on-exec file descriptor
+ // itself to determine this, so we know that when cmd.Start returns,
+ // at least one close-on-exec file descriptor has been closed.
+ // However, we cannot be sure that all of them have been closed,
+ // so the program might still encounter ETXTBSY even with such
+ // an RWLock. The race window would be smaller, perhaps, but not
+ // guaranteed to be gone.
+ //
+ // Sleeping when we observe the race seems to be the most reliable
+ // option we have.
+ //
+ // http://golang.org/issue/3001
+ //
+ if err != nil && nbusy < 3 && strings.Contains(err.Error(), "text file busy") {
+ time.Sleep(100 * time.Millisecond << uint(nbusy))
+ nbusy++
+ continue
+ }
+
+ return buf.Bytes(), err
+ }
+ panic("unreachable")
}
// mkdir makes the named directory.
@@ -1072,32 +1174,60 @@ type toolchain interface {
linker() string
}
-type goToolchain struct{}
-type gccgoToolchain struct{}
+type noToolchain struct{}
+
+func noCompiler() error {
+ log.Fatalf("unknown compiler %q", buildContext.Compiler)
+ return nil
+}
-var buildToolchain toolchain
+func (noToolchain) compiler() string {
+ noCompiler()
+ return ""
+}
-func init() {
- // TODO(rsc): Decide how to trigger gccgo. Issue 3157.
- if os.Getenv("GC") == "gccgo" {
- buildContext.Gccgo = true
- buildToolchain = gccgoToolchain{}
- } else {
- buildToolchain = goToolchain{}
- }
+func (noToolchain) linker() string {
+ noCompiler()
+ return ""
+}
+
+func (noToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
+ return "", noCompiler()
+}
+
+func (noToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
+ return noCompiler()
+}
+
+func (noToolchain) pkgpath(basedir string, p *Package) string {
+ noCompiler()
+ return ""
+}
+
+func (noToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error {
+ return noCompiler()
+}
+
+func (noToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
+ return noCompiler()
+}
+
+func (noToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
+ return noCompiler()
}
// The Go toolchain.
+type gcToolchain struct{}
-func (goToolchain) compiler() string {
+func (gcToolchain) compiler() string {
return tool(archChar + "g")
}
-func (goToolchain) linker() string {
+func (gcToolchain) linker() string {
return tool(archChar + "l")
}
-func (goToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
+func (gcToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
out := "_go_." + archChar
ofile = obj + out
gcargs := []string{"-p", p.ImportPath}
@@ -1114,17 +1244,17 @@ func (goToolchain) gc(b *builder, p *Package, obj string, importArgs []string, g
return ofile, b.run(p.Dir, p.ImportPath, args)
}
-func (goToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
+func (gcToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
sfile = mkAbs(p.Dir, sfile)
return b.run(p.Dir, p.ImportPath, tool(archChar+"a"), "-I", obj, "-o", ofile, "-DGOOS_"+goos, "-DGOARCH_"+goarch, sfile)
}
-func (goToolchain) pkgpath(basedir string, p *Package) string {
+func (gcToolchain) pkgpath(basedir string, p *Package) string {
end := filepath.FromSlash(p.ImportPath + ".a")
return filepath.Join(basedir, end)
}
-func (goToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error {
+func (gcToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error {
var absOfiles []string
for _, f := range ofiles {
absOfiles = append(absOfiles, mkAbs(objDir, f))
@@ -1132,12 +1262,12 @@ func (goToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s
return b.run(p.Dir, p.ImportPath, tool("pack"), "grc", mkAbs(objDir, afile), absOfiles)
}
-func (goToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
+func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
importArgs := b.includeArgs("-L", allactions)
- return b.run(p.Dir, p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg)
+ return b.run(".", p.ImportPath, tool(archChar+"l"), "-o", out, importArgs, buildLdflags, mainpkg)
}
-func (goToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
+func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch))
cfile = mkAbs(p.Dir, cfile)
return b.run(p.Dir, p.ImportPath, tool(archChar+"c"), "-FVw",
@@ -1146,27 +1276,24 @@ func (goToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error
}
// The Gccgo toolchain.
+type gccgcToolchain struct{}
var gccgoBin, _ = exec.LookPath("gccgo")
-func (gccgoToolchain) compiler() string {
+func (gccgcToolchain) compiler() string {
return gccgoBin
}
-func (gccgoToolchain) linker() string {
+func (gccgcToolchain) linker() string {
return gccgoBin
}
-func (gccgoToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
+func (gccgcToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) {
out := p.Name + ".o"
ofile = obj + out
gcargs := []string{"-g"}
- if p.Name != "main" {
- if p.fake {
- gcargs = append(gcargs, "-fgo-prefix=fake_"+p.ImportPath)
- } else {
- gcargs = append(gcargs, "-fgo-prefix=go_"+p.ImportPath)
- }
+ if prefix := gccgoPrefix(p); prefix != "" {
+ gcargs = append(gcargs, "-fgo-prefix="+gccgoPrefix(p))
}
args := stringList("gccgo", importArgs, "-c", gcargs, "-o", ofile, buildGccgoflags)
for _, f := range gofiles {
@@ -1175,19 +1302,19 @@ func (gccgoToolchain) gc(b *builder, p *Package, obj string, importArgs []string
return ofile, b.run(p.Dir, p.ImportPath, args)
}
-func (gccgoToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
+func (gccgcToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
sfile = mkAbs(p.Dir, sfile)
return b.run(p.Dir, p.ImportPath, "gccgo", "-I", obj, "-o", ofile, "-DGOOS_"+goos, "-DGOARCH_"+goarch, sfile)
}
-func (gccgoToolchain) pkgpath(basedir string, p *Package) string {
+func (gccgcToolchain) pkgpath(basedir string, p *Package) string {
end := filepath.FromSlash(p.ImportPath + ".a")
afile := filepath.Join(basedir, end)
// add "lib" to the final element
return filepath.Join(filepath.Dir(afile), "lib"+filepath.Base(afile))
}
-func (gccgoToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error {
+func (gccgcToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error {
var absOfiles []string
for _, f := range ofiles {
absOfiles = append(absOfiles, mkAbs(objDir, f))
@@ -1195,7 +1322,7 @@ func (gccgoToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles
return b.run(p.Dir, p.ImportPath, "ar", "cru", mkAbs(objDir, afile), absOfiles)
}
-func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
+func (tools gccgcToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
// gccgo needs explicit linking with all package dependencies,
// and all LDFLAGS from cgo dependencies.
afiles := make(map[*Package]string)
@@ -1215,10 +1342,10 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
ldflags = append(ldflags, afile)
}
ldflags = append(ldflags, cgoldflags...)
- return b.run(p.Dir, p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(", ldflags, "-Wl,-)")
+ return b.run(".", p.ImportPath, "gccgo", "-o", out, buildGccgoflags, ofiles, "-Wl,-(", ldflags, "-Wl,-)")
}
-func (gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
+func (gccgcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", goos, goarch))
cfile = mkAbs(p.Dir, cfile)
return b.run(p.Dir, p.ImportPath, "gcc", "-Wall", "-g",
@@ -1226,6 +1353,16 @@ func (gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) er
"-DGOOS_"+goos, "-DGOARCH_"+goarch, "-c", cfile)
}
+func gccgoPrefix(p *Package) string {
+ switch {
+ case p.build.IsCommand() && !p.forceLibrary:
+ return ""
+ case p.fake:
+ return "fake_" + p.ImportPath
+ }
+ return "go_" + p.ImportPath
+}
+
// gcc runs the gcc C compiler to create an object from a single C file.
func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error {
cfile = mkAbs(p.Dir, cfile)
@@ -1239,6 +1376,9 @@ func (b *builder) gccld(p *Package, out string, flags []string, obj []string) er
// gccCmd returns a gcc command line prefix
func (b *builder) gccCmd(objdir string) []string {
+ // NOTE: env.go's mkEnv knows that the first three
+ // strings returned are "gcc", "-I", objdir (and cuts them off).
+
// TODO: HOST_CC?
a := []string{"gcc", "-I", objdir, "-g", "-O2"}
@@ -1263,6 +1403,14 @@ func (b *builder) gccCmd(objdir string) []string {
a = append(a, "-pthread")
}
}
+
+ // On OS X, some of the compilers behave as if -fno-common
+ // is always set, and the Mach-O linker in 6l/8l assumes this.
+ // See http://golang.org/issue/3253.
+ if goos == "darwin" {
+ a = append(a, "-fno-common")
+ }
+
return a
}
@@ -1318,11 +1466,17 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
cgoflags := []string{}
// TODO: make cgo not depend on $GOARCH?
+ objExt := archChar
+
if p.Standard && p.ImportPath == "runtime/cgo" {
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
}
- if _, ok := buildToolchain.(gccgoToolchain); ok {
+ if _, ok := buildToolchain.(gccgcToolchain); ok {
cgoflags = append(cgoflags, "-gccgo")
+ if prefix := gccgoPrefix(p); prefix != "" {
+ cgoflags = append(cgoflags, "-gccgoprefix="+gccgoPrefix(p))
+ }
+ objExt = "o"
}
if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, cgoflags, "--", cgoCFLAGS, p.CgoFiles); err != nil {
return nil, nil, err
@@ -1330,7 +1484,7 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
outGo = append(outGo, gofiles...)
// cc _cgo_defun.c
- defunObj := obj + "_cgo_defun." + archChar
+ defunObj := obj + "_cgo_defun." + objExt
if err := buildToolchain.cc(b, p, obj, defunObj, defunC); err != nil {
return nil, nil, err
}
@@ -1361,7 +1515,7 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
return nil, nil, err
}
- if _, ok := buildToolchain.(gccgoToolchain); ok {
+ if _, ok := buildToolchain.(gccgcToolchain); ok {
// we don't use dynimport when using gccgo.
return outGo, outObj, nil
}
@@ -1373,7 +1527,7 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
}
// cc _cgo_import.ARCH
- importObj := obj + "_cgo_import." + archChar
+ importObj := obj + "_cgo_import." + objExt
if err := buildToolchain.cc(b, p, obj, importObj, importC); err != nil {
return nil, nil, err
}
diff --git a/src/cmd/go/clean.go b/src/cmd/go/clean.go
index 809e0f0e4..773951826 100644
--- a/src/cmd/go/clean.go
+++ b/src/cmd/go/clean.go
@@ -110,7 +110,7 @@ func clean(p *Package) {
}
dirs, err := ioutil.ReadDir(p.Dir)
if err != nil {
- errorf("%v", err)
+ errorf("go clean %s: %v", p.Dir, err)
return
}
diff --git a/src/cmd/go/discovery.go b/src/cmd/go/discovery.go
new file mode 100644
index 000000000..d9f930867
--- /dev/null
+++ b/src/cmd/go/discovery.go
@@ -0,0 +1,63 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !cmd_go_bootstrap
+
+// This code is compiled into the real 'go' binary, but it is not
+// compiled into the binary that is built during all.bash, so as
+// to avoid needing to build net (and thus use cgo) during the
+// bootstrap process.
+
+package main
+
+import (
+ "encoding/xml"
+ "io"
+ "strings"
+)
+
+// parseMetaGoImports returns meta imports from the HTML in r.
+// Parsing ends at the end of the <head> section or the beginning of the <body>.
+func parseMetaGoImports(r io.Reader) (imports []metaImport) {
+ d := xml.NewDecoder(r)
+ d.Strict = false
+ for {
+ t, err := d.Token()
+ if err != nil {
+ return
+ }
+ if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
+ return
+ }
+ if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
+ return
+ }
+ e, ok := t.(xml.StartElement)
+ if !ok || !strings.EqualFold(e.Name.Local, "meta") {
+ continue
+ }
+ if attrValue(e.Attr, "name") != "go-import" {
+ continue
+ }
+ if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
+ imports = append(imports, metaImport{
+ Prefix: f[0],
+ VCS: f[1],
+ RepoRoot: f[2],
+ })
+ }
+ }
+ return
+}
+
+// attrValue returns the attribute value for the case-insensitive key
+// `name', or the empty string if nothing is found.
+func attrValue(attrs []xml.Attr, name string) string {
+ for _, a := range attrs {
+ if strings.EqualFold(a.Name.Local, name) {
+ return a.Value
+ }
+ }
+ return ""
+}
diff --git a/src/cmd/go/doc.go b/src/cmd/go/doc.go
index 8df57ff38..775f305d2 100644
--- a/src/cmd/go/doc.go
+++ b/src/cmd/go/doc.go
@@ -14,6 +14,7 @@ The commands are:
build compile packages and dependencies
clean remove object files
doc run godoc on package sources
+ env print Go environment information
fix run go tool fix on packages
fmt run gofmt on package sources
get download and install packages and dependencies
@@ -76,6 +77,8 @@ The build flags are shared by the build, install, run, and test commands:
-x
print the commands.
+ -compiler name
+ name of compiler to use, as in runtime.Compiler (gccgo or gc)
-gccgoflags 'arg list'
arguments to pass on each gccgo compiler/linker invocation
-gcflags 'arg list'
@@ -153,6 +156,20 @@ To run godoc with specific options, run godoc itself.
See also: go fix, go fmt, go vet.
+Print Go environment information
+
+Usage:
+
+ go env [var ...]
+
+Env prints Go environment information.
+
+By default env prints information as a shell script
+(on Windows, a batch file). If one or more variable
+names is given as arguments, env prints the value of
+each named variable on its own line.
+
+
Run go tool fix on packages
Usage:
@@ -196,7 +213,7 @@ Get downloads and installs the packages named by the import paths,
along with their dependencies.
The -a, -n, -v, -x, and -p flags have the same meaning as in 'go build'
-and 'go install'. See 'go help install'.
+and 'go install'. See 'go help build'.
The -d flag instructs get to stop after downloading the packages; that is,
it instructs get not to install the packages.
@@ -253,21 +270,28 @@ is equivalent to -f '{{.ImportPath}}'. The struct
being passed to the template is:
type Package struct {
+ Dir string // directory containing package sources
+ ImportPath string // import path of package in dir
Name string // package name
Doc string // package documentation string
- ImportPath string // import path of package in dir
- Dir string // directory containing package sources
- Version string // version of installed package (TODO)
+ Target string // install path
+ Goroot bool // is this package in the Go root?
+ Standard bool // is this package part of the standard Go library?
Stale bool // would 'go install' do anything for this package?
+ Root string // Go root or Go path dir containing this package
// Source files
- GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, and XTestGoFiles)
- TestGoFiles []string // _test.go source files internal to the package they are testing
- XTestGoFiles []string // _test.go source files external to the package they are testing
- CFiles []string // .c source files
- HFiles []string // .h source files
- SFiles []string // .s source files
- CgoFiles []string // .go sources files that import "C"
+ GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
+ CgoFiles []string // .go sources files that import "C"
+ CFiles []string // .c source files
+ HFiles []string // .h source files
+ SFiles []string // .s source files
+ SysoFiles []string // .syso object files to add to archive
+
+ // Cgo directives
+ CgoCFLAGS []string // cgo: flags for C compiler
+ CgoLDFLAGS []string // cgo: flags for linker
+ CgoPkgConfig []string // cgo: pkg-config names
// Dependency information
Imports []string // import paths used by this package
@@ -275,8 +299,13 @@ being passed to the template is:
// Error information
Incomplete bool // this package or a dependency has an error
- Error *PackageError // error loading package
+ Error *PackageError // error loading package
DepsErrors []*PackageError // errors loading dependencies
+
+ TestGoFiles []string // _test.go files in package
+ TestImports []string // imports from TestGoFiles
+ XTestGoFiles []string // _test.go files outside package
+ XTestImports []string // imports from XTestGoFiles
}
The -json flag causes the package data to be printed in JSON format
@@ -479,9 +508,8 @@ An import path is a pattern if it includes one or more "..." wildcards,
each of which can match any string, including the empty string and
strings containing slashes. Such a pattern expands to all package
directories found in the GOPATH trees with names matching the
-patterns. For example, encoding/... expands to all packages
-in subdirectories of the encoding tree, while net... expands to
-net and all its subdirectories.
+patterns. As a special case, x/... matches x as well as x's subdirectories.
+For example, net/... expands to net and packages in its subdirectories.
An import path can also name a package to be downloaded from
a remote repository. Run 'go help remote' for details.
@@ -535,7 +563,12 @@ A few common code hosting sites have special syntax:
import "launchpad.net/~user/project/branch"
import "launchpad.net/~user/project/branch/sub/directory"
-For code hosted on other servers, an import path of the form
+For code hosted on other servers, import paths may either be qualified
+with the version control type, or the go tool can dynamically fetch
+the import path over https/http and discover where the code resides
+from a <meta> tag in the HTML.
+
+To declare the code location, an import path of the form
repository.vcs/path
@@ -564,6 +597,42 @@ When a version control system supports multiple protocols,
each is tried in turn when downloading. For example, a Git
download tries git://, then https://, then http://.
+If the import path is not a known code hosting site and also lacks a
+version control qualifier, the go tool attempts to fetch the import
+over https/http and looks for a <meta> tag in the document's HTML
+<head>.
+
+The meta tag has the form:
+
+ <meta name="go-import" content="import-prefix vcs repo-root">
+
+The import-prefix is the import path correponding to the repository
+root. It must be a prefix or an exact match of the package being
+fetched with "go get". If it's not an exact match, another http
+request is made at the prefix to verify the <meta> tags match.
+
+The vcs is one of "git", "hg", "svn", etc,
+
+The repo-root is the root of the version control system
+containing a scheme and not containing a .vcs qualifier.
+
+For example,
+
+ import "example.org/pkg/foo"
+
+will result in the following request(s):
+
+ https://example.org/pkg/foo?go-get=1 (preferred)
+ http://example.org/pkg/foo?go-get=1 (fallback)
+
+If that page contains the meta tag
+
+ <meta name="go-import" content="example.org git https://code.org/r/p/exproj">
+
+the go tool will verify that https://example.org/?go-get=1 contains the
+same meta tag and then git clone https://code.org/r/p/exproj into
+GOPATH/src/example.org.
+
New downloaded packages are written to the first directory
listed in the GOPATH environment variable (see 'go help gopath').
diff --git a/src/cmd/go/env.go b/src/cmd/go/env.go
new file mode 100644
index 000000000..d5b034809
--- /dev/null
+++ b/src/cmd/go/env.go
@@ -0,0 +1,89 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+)
+
+var cmdEnv = &Command{
+ Run: runEnv,
+ UsageLine: "env [var ...]",
+ Short: "print Go environment information",
+ Long: `
+Env prints Go environment information.
+
+By default env prints information as a shell script
+(on Windows, a batch file). If one or more variable
+names is given as arguments, env prints the value of
+each named variable on its own line.
+ `,
+}
+
+type envVar struct {
+ name, value string
+}
+
+func mkEnv() []envVar {
+ var b builder
+ b.init()
+
+ env := []envVar{
+ {"GOROOT", goroot},
+ {"GOBIN", gobin},
+ {"GOARCH", goarch},
+ {"GOCHAR", archChar},
+ {"GOOS", goos},
+ {"GOEXE", exeSuffix},
+ {"GOHOSTARCH", runtime.GOARCH},
+ {"GOHOSTOS", runtime.GOOS},
+ {"GOTOOLDIR", toolDir},
+ {"GOGCCFLAGS", strings.Join(b.gccCmd(".")[3:], " ")},
+ }
+
+ if buildContext.CgoEnabled {
+ env = append(env, envVar{"CGO_ENABLED", "1"})
+ } else {
+ env = append(env, envVar{"CGO_ENABLED", "0"})
+ }
+
+ return env
+}
+
+func findEnv(env []envVar, name string) string {
+ for _, e := range env {
+ if e.name == name {
+ return e.value
+ }
+ }
+ return ""
+}
+
+func runEnv(cmd *Command, args []string) {
+ env := mkEnv()
+ if len(args) > 0 {
+ for _, name := range args {
+ fmt.Printf("%s\n", findEnv(env, name))
+ }
+ return
+ }
+
+ switch runtime.GOOS {
+ default:
+ for _, e := range env {
+ fmt.Printf("%s=\"%s\"\n", e.name, e.value)
+ }
+ case "plan9":
+ for _, e := range env {
+ fmt.Printf("%s='%s'\n", e.name, strings.Replace(e.value, "'", "''", -1))
+ }
+ case "windows":
+ for _, e := range env {
+ fmt.Printf("set %s=%s\n", e.name, e.value)
+ }
+ }
+}
diff --git a/src/cmd/go/get.go b/src/cmd/go/get.go
index 0ad22adb0..abaf5ffa0 100644
--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -8,6 +8,7 @@ package main
import (
"fmt"
+ "go/build"
"os"
"path/filepath"
"runtime"
@@ -23,7 +24,7 @@ Get downloads and installs the packages named by the import paths,
along with their dependencies.
The -a, -n, -v, -x, and -p flags have the same meaning as in 'go build'
-and 'go install'. See 'go help install'.
+and 'go install'. See 'go help build'.
The -d flag instructs get to stop after downloading the packages; that is,
it instructs get not to install the packages.
@@ -57,19 +58,13 @@ func init() {
func runGet(cmd *Command, args []string) {
// Phase 1. Download/update.
- args = importPaths(args)
var stk importStack
- for _, arg := range args {
+ for _, arg := range downloadPaths(args) {
download(arg, &stk)
}
exitIfErrors()
- if *getD {
- // download only
- return
- }
-
- // Phase 2. Install.
+ // Phase 2. Rescan packages and reevaluate args list.
// Code we downloaded and all code that depends on it
// needs to be evicted from the package cache so that
@@ -80,9 +75,48 @@ func runGet(cmd *Command, args []string) {
delete(packageCache, name)
}
+ args = importPaths(args)
+
+ // Phase 3. Install.
+ if *getD {
+ // Download only.
+ // Check delayed until now so that importPaths
+ // has a chance to print errors.
+ return
+ }
+
runInstall(cmd, args)
}
+// downloadPath prepares the list of paths to pass to download.
+// It expands ... patterns that can be expanded. If there is no match
+// for a particular pattern, downloadPaths leaves it in the result list,
+// in the hope that we can figure out the repository from the
+// initial ...-free prefix.
+func downloadPaths(args []string) []string {
+ args = importPathsNoDotExpansion(args)
+ var out []string
+ for _, a := range args {
+ if strings.Contains(a, "...") {
+ var expand []string
+ // Use matchPackagesInFS to avoid printing
+ // warnings. They will be printed by the
+ // eventual call to importPaths instead.
+ if build.IsLocalImport(a) {
+ expand = matchPackagesInFS(a)
+ } else {
+ expand = matchPackages(a)
+ }
+ if len(expand) > 0 {
+ out = append(out, expand...)
+ continue
+ }
+ }
+ out = append(out, a)
+ }
+ return out
+}
+
// downloadCache records the import paths we have already
// considered during the download, to avoid duplicate work when
// there is more than one dependency sequence leading to
@@ -112,38 +146,73 @@ func download(arg string, stk *importStack) {
}
downloadCache[arg] = true
+ pkgs := []*Package{p}
+ wildcardOkay := len(*stk) == 0
+
// Download if the package is missing, or update if we're using -u.
if p.Dir == "" || *getU {
// The actual download.
stk.push(p.ImportPath)
- defer stk.pop()
- if err := downloadPackage(p); err != nil {
+ err := downloadPackage(p)
+ if err != nil {
errorf("%s", &PackageError{ImportStack: stk.copy(), Err: err.Error()})
+ stk.pop()
return
}
- // Reread the package information from the updated files.
- p = reloadPackage(arg, stk)
- if p.Error != nil {
- errorf("%s", p.Error)
- return
+ args := []string{arg}
+ // If the argument has a wildcard in it, re-evaluate the wildcard.
+ // We delay this until after reloadPackage so that the old entry
+ // for p has been replaced in the package cache.
+ if wildcardOkay && strings.Contains(arg, "...") {
+ if build.IsLocalImport(arg) {
+ args = matchPackagesInFS(arg)
+ } else {
+ args = matchPackages(arg)
+ }
}
- }
- if *getFix {
- run(stringList(tool("fix"), relPaths(p.gofiles)))
+ // Clear all relevant package cache entries before
+ // doing any new loads.
+ for _, arg := range args {
+ p := packageCache[arg]
+ if p != nil {
+ delete(packageCache, p.Dir)
+ delete(packageCache, p.ImportPath)
+ }
+ }
- // The imports might have changed, so reload again.
- p = reloadPackage(arg, stk)
- if p.Error != nil {
- errorf("%s", p.Error)
- return
+ pkgs = pkgs[:0]
+ for _, arg := range args {
+ stk.push(arg)
+ p := loadPackage(arg, stk)
+ stk.pop()
+ if p.Error != nil {
+ errorf("%s", p.Error)
+ continue
+ }
+ pkgs = append(pkgs, p)
}
}
- // Process dependencies, now that we know what they are.
- for _, dep := range p.deps {
- download(dep.ImportPath, stk)
+ // Process package, which might now be multiple packages
+ // due to wildcard expansion.
+ for _, p := range pkgs {
+ if *getFix {
+ run(stringList(tool("fix"), relPaths(p.gofiles)))
+
+ // The imports might have changed, so reload again.
+ p = reloadPackage(arg, stk)
+ if p.Error != nil {
+ errorf("%s", p.Error)
+ return
+ }
+ }
+
+ // Process dependencies, now that we know what they are.
+ for _, dep := range p.deps {
+ download(dep.ImportPath, stk)
+ }
}
}
@@ -162,10 +231,11 @@ func downloadPackage(p *Package) error {
} else {
// Analyze the import path to determine the version control system,
// repository, and the import path for the root of the repository.
- vcs, repo, rootPath, err = vcsForImportPath(p.ImportPath)
- }
- if err != nil {
- return err
+ rr, err := repoRootForImportPath(p.ImportPath)
+ if err != nil {
+ return err
+ }
+ vcs, repo, rootPath = rr.vcs, rr.repo, rr.root
}
if p.build.SrcRoot == "" {
diff --git a/src/cmd/go/help.go b/src/cmd/go/help.go
index 60654a272..26640d833 100644
--- a/src/cmd/go/help.go
+++ b/src/cmd/go/help.go
@@ -36,9 +36,8 @@ An import path is a pattern if it includes one or more "..." wildcards,
each of which can match any string, including the empty string and
strings containing slashes. Such a pattern expands to all package
directories found in the GOPATH trees with names matching the
-patterns. For example, encoding/... expands to all packages
-in subdirectories of the encoding tree, while net... expands to
-net and all its subdirectories.
+patterns. As a special case, x/... matches x as well as x's subdirectories.
+For example, net/... expands to net and packages in its subdirectories.
An import path can also name a package to be downloaded from
a remote repository. Run 'go help remote' for details.
@@ -96,7 +95,12 @@ A few common code hosting sites have special syntax:
import "launchpad.net/~user/project/branch"
import "launchpad.net/~user/project/branch/sub/directory"
-For code hosted on other servers, an import path of the form
+For code hosted on other servers, import paths may either be qualified
+with the version control type, or the go tool can dynamically fetch
+the import path over https/http and discover where the code resides
+from a <meta> tag in the HTML.
+
+To declare the code location, an import path of the form
repository.vcs/path
@@ -125,6 +129,42 @@ When a version control system supports multiple protocols,
each is tried in turn when downloading. For example, a Git
download tries git://, then https://, then http://.
+If the import path is not a known code hosting site and also lacks a
+version control qualifier, the go tool attempts to fetch the import
+over https/http and looks for a <meta> tag in the document's HTML
+<head>.
+
+The meta tag has the form:
+
+ <meta name="go-import" content="import-prefix vcs repo-root">
+
+The import-prefix is the import path correponding to the repository
+root. It must be a prefix or an exact match of the package being
+fetched with "go get". If it's not an exact match, another http
+request is made at the prefix to verify the <meta> tags match.
+
+The vcs is one of "git", "hg", "svn", etc,
+
+The repo-root is the root of the version control system
+containing a scheme and not containing a .vcs qualifier.
+
+For example,
+
+ import "example.org/pkg/foo"
+
+will result in the following request(s):
+
+ https://example.org/pkg/foo?go-get=1 (preferred)
+ http://example.org/pkg/foo?go-get=1 (fallback)
+
+If that page contains the meta tag
+
+ <meta name="go-import" content="example.org git https://code.org/r/p/exproj">
+
+the go tool will verify that https://example.org/?go-get=1 contains the
+same meta tag and then git clone https://code.org/r/p/exproj into
+GOPATH/src/example.org.
+
New downloaded packages are written to the first directory
listed in the GOPATH environment variable (see 'go help gopath').
diff --git a/src/cmd/go/http.go b/src/cmd/go/http.go
index 8d9b2a165..6de9a3e1e 100644
--- a/src/cmd/go/http.go
+++ b/src/cmd/go/http.go
@@ -13,8 +13,11 @@ package main
import (
"fmt"
+ "io"
"io/ioutil"
+ "log"
"net/http"
+ "net/url"
)
// httpGET returns the data from an HTTP GET request for the given URL.
@@ -33,3 +36,52 @@ func httpGET(url string) ([]byte, error) {
}
return b, nil
}
+
+// httpClient is the default HTTP client, but a variable so it can be
+// changed by tests, without modifying http.DefaultClient.
+var httpClient = http.DefaultClient
+
+// httpsOrHTTP returns the body of either the importPath's
+// https resource or, if unavailable, the http resource.
+func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) {
+ fetch := func(scheme string) (urlStr string, res *http.Response, err error) {
+ u, err := url.Parse(scheme + "://" + importPath)
+ if err != nil {
+ return "", nil, err
+ }
+ u.RawQuery = "go-get=1"
+ urlStr = u.String()
+ if buildV {
+ log.Printf("Fetching %s", urlStr)
+ }
+ res, err = httpClient.Get(urlStr)
+ return
+ }
+ closeBody := func(res *http.Response) {
+ if res != nil {
+ res.Body.Close()
+ }
+ }
+ urlStr, res, err := fetch("https")
+ if err != nil || res.StatusCode != 200 {
+ if buildV {
+ if err != nil {
+ log.Printf("https fetch failed.")
+ } else {
+ log.Printf("ignoring https fetch with status code %d", res.StatusCode)
+ }
+ }
+ closeBody(res)
+ urlStr, res, err = fetch("http")
+ }
+ if err != nil {
+ closeBody(res)
+ return "", nil, err
+ }
+ // Note: accepting a non-200 OK here, so people can serve a
+ // meta import in their http 404 page.
+ if buildV {
+ log.Printf("Parsing meta tags from %s (status code %d)", urlStr, res.StatusCode)
+ }
+ return urlStr, res.Body, nil
+}
diff --git a/src/cmd/go/list.go b/src/cmd/go/list.go
index fa3f5d330..edb59aa79 100644
--- a/src/cmd/go/list.go
+++ b/src/cmd/go/list.go
@@ -30,30 +30,42 @@ is equivalent to -f '{{.ImportPath}}'. The struct
being passed to the template is:
type Package struct {
+ Dir string // directory containing package sources
+ ImportPath string // import path of package in dir
Name string // package name
Doc string // package documentation string
- ImportPath string // import path of package in dir
- Dir string // directory containing package sources
- Version string // version of installed package (TODO)
+ Target string // install path
+ Goroot bool // is this package in the Go root?
+ Standard bool // is this package part of the standard Go library?
Stale bool // would 'go install' do anything for this package?
+ Root string // Go root or Go path dir containing this package
// Source files
- GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, and XTestGoFiles)
- TestGoFiles []string // _test.go source files internal to the package they are testing
- XTestGoFiles []string // _test.go source files external to the package they are testing
- CFiles []string // .c source files
- HFiles []string // .h source files
- SFiles []string // .s source files
- CgoFiles []string // .go sources files that import "C"
+ GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
+ CgoFiles []string // .go sources files that import "C"
+ CFiles []string // .c source files
+ HFiles []string // .h source files
+ SFiles []string // .s source files
+ SysoFiles []string // .syso object files to add to archive
+
+ // Cgo directives
+ CgoCFLAGS []string // cgo: flags for C compiler
+ CgoLDFLAGS []string // cgo: flags for linker
+ CgoPkgConfig []string // cgo: pkg-config names
// Dependency information
Imports []string // import paths used by this package
Deps []string // all (recursively) imported dependencies
-
+
// Error information
Incomplete bool // this package or a dependency has an error
- Error *PackageError // error loading package
+ Error *PackageError // error loading package
DepsErrors []*PackageError // errors loading dependencies
+
+ TestGoFiles []string // _test.go files in package
+ TestImports []string // imports from TestGoFiles
+ XTestGoFiles []string // _test.go files outside package
+ XTestImports []string // imports from XTestGoFiles
}
The -json flag causes the package data to be printed in JSON format
@@ -75,6 +87,7 @@ For more about specifying packages, see 'go help packages'.
func init() {
cmdList.Run = runList // break init cycle
+ cmdList.Flag.Var(buildCompiler{}, "compiler", "")
}
var listE = cmdList.Flag.Bool("e", false, "")
diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go
index 3a0f7a089..2f8209c86 100644
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -76,6 +76,7 @@ var commands = []*Command{
cmdBuild,
cmdClean,
cmdDoc,
+ cmdEnv,
cmdFix,
cmdFmt,
cmdGet,
@@ -246,8 +247,9 @@ func help(args []string) {
os.Exit(2) // failed at 'go help cmd'
}
-// importPaths returns the import paths to use for the given command line.
-func importPaths(args []string) []string {
+// importPathsNoDotExpansion returns the import paths to use for the given
+// command line, but it does no ... expansion.
+func importPathsNoDotExpansion(args []string) []string {
if len(args) == 0 {
return []string{"."}
}
@@ -269,13 +271,26 @@ func importPaths(args []string) []string {
} else {
a = path.Clean(a)
}
-
- if build.IsLocalImport(a) && strings.Contains(a, "...") {
- out = append(out, allPackagesInFS(a)...)
+ if a == "all" || a == "std" {
+ out = append(out, allPackages(a)...)
continue
}
- if a == "all" || a == "std" || strings.Contains(a, "...") {
- out = append(out, allPackages(a)...)
+ out = append(out, a)
+ }
+ return out
+}
+
+// importPaths returns the import paths to use for the given command line.
+func importPaths(args []string) []string {
+ args = importPathsNoDotExpansion(args)
+ var out []string
+ for _, a := range args {
+ if strings.Contains(a, "...") {
+ if build.IsLocalImport(a) {
+ out = append(out, allPackagesInFS(a)...)
+ } else {
+ out = append(out, allPackages(a)...)
+ }
continue
}
out = append(out, a)
@@ -344,6 +359,10 @@ func runOut(dir string, cmdargs ...interface{}) []byte {
func matchPattern(pattern string) func(name string) bool {
re := regexp.QuoteMeta(pattern)
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
+ // Special case: foo/... matches foo too.
+ if strings.HasSuffix(re, `/.*`) {
+ re = re[:len(re)-len(`/.*`)] + `(/.*)?`
+ }
reg := regexp.MustCompile(`^` + re + `$`)
return func(name string) bool {
return reg.MatchString(name)
@@ -355,6 +374,14 @@ func matchPattern(pattern string) func(name string) bool {
// The pattern is either "all" (all packages), "std" (standard packages)
// or a path including "...".
func allPackages(pattern string) []string {
+ pkgs := matchPackages(pattern)
+ if len(pkgs) == 0 {
+ fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
+ }
+ return pkgs
+}
+
+func matchPackages(pattern string) []string {
match := func(string) bool { return true }
if pattern != "all" && pattern != "std" {
match = matchPattern(pattern)
@@ -431,10 +458,6 @@ func allPackages(pattern string) []string {
return nil
})
}
-
- if len(pkgs) == 0 {
- fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
- }
return pkgs
}
@@ -442,6 +465,14 @@ func allPackages(pattern string) []string {
// beginning ./ or ../, meaning it should scan the tree rooted
// at the given directory. There are ... in the pattern too.
func allPackagesInFS(pattern string) []string {
+ pkgs := matchPackagesInFS(pattern)
+ if len(pkgs) == 0 {
+ fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
+ }
+ return pkgs
+}
+
+func matchPackagesInFS(pattern string) []string {
// Find directory to begin the scan.
// Could be smarter but this one optimization
// is enough for now, since ... is usually at the
@@ -481,10 +512,6 @@ func allPackagesInFS(pattern string) []string {
pkgs = append(pkgs, name)
return nil
})
-
- if len(pkgs) == 0 {
- fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
- }
return pkgs
}
diff --git a/src/cmd/go/match_test.go b/src/cmd/go/match_test.go
new file mode 100644
index 000000000..f058f235a
--- /dev/null
+++ b/src/cmd/go/match_test.go
@@ -0,0 +1,36 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "testing"
+
+var matchTests = []struct {
+ pattern string
+ path string
+ match bool
+}{
+ {"...", "foo", true},
+ {"net", "net", true},
+ {"net", "net/http", false},
+ {"net/http", "net", false},
+ {"net/http", "net/http", true},
+ {"net...", "netchan", true},
+ {"net...", "net", true},
+ {"net...", "net/http", true},
+ {"net...", "not/http", false},
+ {"net/...", "netchan", false},
+ {"net/...", "net", true},
+ {"net/...", "net/http", true},
+ {"net/...", "not/http", false},
+}
+
+func TestMatchPattern(t *testing.T) {
+ for _, tt := range matchTests {
+ match := matchPattern(tt.pattern)(tt.path)
+ if match != tt.match {
+ t.Errorf("matchPattern(%q)(%q) = %v, want %v", tt.pattern, tt.path, match, tt.match)
+ }
+ }
+}
diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go
index 3763000c6..44dbd6798 100644
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -17,6 +17,7 @@ import (
"sort"
"strings"
"time"
+ "unicode"
)
// A Package describes a single package found in a directory.
@@ -24,25 +25,23 @@ type Package struct {
// Note: These fields are part of the go command's public API.
// See list.go. It is okay to add fields, but not to change or
// remove existing ones. Keep in sync with list.go
- Dir string `json:",omitempty"` // directory containing package sources
- ImportPath string `json:",omitempty"` // import path of package in dir
- Name string `json:",omitempty"` // package name
- Doc string `json:",omitempty"` // package documentation string
- Target string `json:",omitempty"` // install path
- Goroot bool `json:",omitempty"` // is this package found in the Go root?
- Standard bool `json:",omitempty"` // is this package part of the standard Go library?
- Stale bool `json:",omitempty"` // would 'go install' do anything for this package?
- Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies?
- Error *PackageError `json:",omitempty"` // error loading this package (not dependencies)
-
- Root string `json:",omitempty"` // root dir of tree this package belongs to
+ Dir string `json:",omitempty"` // directory containing package sources
+ ImportPath string `json:",omitempty"` // import path of package in dir
+ Name string `json:",omitempty"` // package name
+ Doc string `json:",omitempty"` // package documentation string
+ Target string `json:",omitempty"` // install path
+ Goroot bool `json:",omitempty"` // is this package found in the Go root?
+ Standard bool `json:",omitempty"` // is this package part of the standard Go library?
+ Stale bool `json:",omitempty"` // would 'go install' do anything for this package?
+ Root string `json:",omitempty"` // Go root or Go path dir containing this package
// Source files
- GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles XTestGoFiles)
- CgoFiles []string `json:",omitempty"` // .go sources files that import "C"
- CFiles []string `json:",omitempty"` // .c source files
- HFiles []string `json:",omitempty"` // .h source files
- SFiles []string `json:",omitempty"` // .s source files
+ GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
+ CgoFiles []string `json:",omitempty"` // .go sources files that import "C"
+ CFiles []string `json:",omitempty"` // .c source files
+ HFiles []string `json:",omitempty"` // .h source files
+ SFiles []string `json:",omitempty"` // .s source files
+ SysoFiles []string `json:",omitempty"` // .syso system object files added to package
// Cgo directives
CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler
@@ -50,8 +49,12 @@ type Package struct {
CgoPkgConfig []string `json:",omitempty"` // cgo: pkg-config names
// Dependency information
- Imports []string `json:",omitempty"` // import paths used by this package
- Deps []string `json:",omitempty"` // all (recursively) imported dependencies
+ Imports []string `json:",omitempty"` // import paths used by this package
+ Deps []string `json:",omitempty"` // all (recursively) imported dependencies
+
+ // Error information
+ Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies?
+ Error *PackageError `json:",omitempty"` // error loading this package (not dependencies)
DepsErrors []*PackageError `json:",omitempty"` // errors loading dependencies
// Test information
@@ -61,16 +64,17 @@ type Package struct {
XTestImports []string `json:",omitempty"` // imports from XTestGoFiles
// Unexported fields are not part of the public API.
- build *build.Package
- pkgdir string // overrides build.PkgDir
- imports []*Package
- deps []*Package
- gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths
- target string // installed file for this package (may be executable)
- fake bool // synthesized package
- forceBuild bool // this package must be rebuilt
- local bool // imported via local path (./ or ../)
- localPrefix string // interpret ./ and ../ imports relative to this prefix
+ build *build.Package
+ pkgdir string // overrides build.PkgDir
+ imports []*Package
+ deps []*Package
+ gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths
+ target string // installed file for this package (may be executable)
+ fake bool // synthesized package
+ forceBuild bool // this package must be rebuilt
+ forceLibrary bool // this package is a library (even if named "main")
+ local bool // imported via local path (./ or ../)
+ localPrefix string // interpret ./ and ../ imports relative to this prefix
}
func (p *Package) copyBuild(pp *build.Package) {
@@ -89,6 +93,7 @@ func (p *Package) copyBuild(pp *build.Package) {
p.CFiles = pp.CFiles
p.HFiles = pp.HFiles
p.SFiles = pp.SFiles
+ p.SysoFiles = pp.SysoFiles
p.CgoCFLAGS = pp.CgoCFLAGS
p.CgoLDFLAGS = pp.CgoLDFLAGS
p.CgoPkgConfig = pp.CgoPkgConfig
@@ -171,7 +176,16 @@ func reloadPackage(arg string, stk *importStack) *Package {
// a special case, so that all the code to deal with ordinary imports works
// automatically.
func dirToImportPath(dir string) string {
- return pathpkg.Join("_", strings.Replace(filepath.ToSlash(dir), ":", "_", -1))
+ return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
+}
+
+func makeImportValid(r rune) rune {
+ // Should match Go spec, compilers, and ../../pkg/go/parser/parser.go:/isValidImport.
+ const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
+ if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
+ return '_'
+ }
+ return r
}
// loadImport scans the directory named by path, which must be an import path,
@@ -276,9 +290,8 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
p.copyBuild(bp)
// The localPrefix is the path we interpret ./ imports relative to.
- // Now that we've fixed the import path, it's just the import path.
// Synthesized main packages sometimes override this.
- p.localPrefix = p.ImportPath
+ p.localPrefix = dirToImportPath(p.Dir)
if err != nil {
p.Incomplete = true
@@ -340,6 +353,16 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
}
p1 := loadImport(path, p.Dir, stk, p.build.ImportPos[path])
if p1.local {
+ if !p.local && p.Error == nil {
+ p.Error = &PackageError{
+ ImportStack: stk.copy(),
+ Err: fmt.Sprintf("local import %q in non-local package", path),
+ }
+ pos := p.build.ImportPos[path]
+ if len(pos) > 0 {
+ p.Error.Pos = pos[0].String()
+ }
+ }
path = p1.ImportPath
importPaths[i] = path
}
@@ -371,7 +394,7 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
}
// unsafe is a fake package.
- if p.Standard && p.ImportPath == "unsafe" {
+ if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") {
p.target = ""
}
@@ -416,7 +439,7 @@ func computeStale(pkgs ...*Package) {
// isStale reports whether package p needs to be rebuilt.
func isStale(p *Package, topRoot map[string]bool) bool {
- if p.Standard && p.ImportPath == "unsafe" {
+ if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") {
// fake, builtin package
return false
}
@@ -486,7 +509,7 @@ func isStale(p *Package, topRoot map[string]bool) bool {
return false
}
- srcs := stringList(p.GoFiles, p.CFiles, p.HFiles, p.SFiles, p.CgoFiles)
+ srcs := stringList(p.GoFiles, p.CFiles, p.HFiles, p.SFiles, p.CgoFiles, p.SysoFiles)
for _, src := range srcs {
if olderThan(filepath.Join(p.Dir, src)) {
return true
diff --git a/src/cmd/go/run.go b/src/cmd/go/run.go
index 2976d5c8d..94cd59296 100644
--- a/src/cmd/go/run.go
+++ b/src/cmd/go/run.go
@@ -26,9 +26,7 @@ See also: go build.
func init() {
cmdRun.Run = runRun // break init loop
- cmdRun.Flag.BoolVar(&buildA, "a", false, "")
- cmdRun.Flag.BoolVar(&buildN, "n", false, "")
- cmdRun.Flag.BoolVar(&buildX, "x", false, "")
+ addBuildFlags(cmdRun)
}
func printStderr(args ...interface{}) (int, error) {
@@ -44,12 +42,15 @@ func runRun(cmd *Command, args []string) {
i++
}
files, cmdArgs := args[:i], args[i:]
+ if len(files) == 0 {
+ fatalf("go run: no go files listed")
+ }
p := goFilesPackage(files)
if p.Error != nil {
fatalf("%s", p.Error)
}
if p.Name != "main" {
- fatalf("cannot run non-main package")
+ fatalf("go run: cannot run non-main package")
}
p.target = "" // must build - not up to date
a1 := b.action(modeBuild, modeBuild, p)
diff --git a/src/cmd/go/test.bash b/src/cmd/go/test.bash
index daca144ee..541535101 100755
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -22,37 +22,50 @@ do
done
# Test local (./) imports.
-./testgo build -o hello testdata/local/easy.go
-./hello >hello.out
-if ! grep -q '^easysub\.Hello' hello.out; then
- echo "testdata/local/easy.go did not generate expected output"
- cat hello.out
- ok=false
-fi
-
-./testgo build -o hello testdata/local/easysub/main.go
-./hello >hello.out
-if ! grep -q '^easysub\.Hello' hello.out; then
- echo "testdata/local/easysub/main.go did not generate expected output"
- cat hello.out
- ok=false
-fi
-
-./testgo build -o hello testdata/local/hard.go
-./hello >hello.out
-if ! grep -q '^sub\.Hello' hello.out || ! grep -q '^subsub\.Hello' hello.out ; then
- echo "testdata/local/hard.go did not generate expected output"
- cat hello.out
- ok=false
-fi
+testlocal() {
+ local="$1"
+ ./testgo build -o hello "testdata/$local/easy.go"
+ ./hello >hello.out
+ if ! grep -q '^easysub\.Hello' hello.out; then
+ echo "testdata/$local/easy.go did not generate expected output"
+ cat hello.out
+ ok=false
+ fi
+
+ ./testgo build -o hello "testdata/$local/easysub/main.go"
+ ./hello >hello.out
+ if ! grep -q '^easysub\.Hello' hello.out; then
+ echo "testdata/$local/easysub/main.go did not generate expected output"
+ cat hello.out
+ ok=false
+ fi
+
+ ./testgo build -o hello "testdata/$local/hard.go"
+ ./hello >hello.out
+ if ! grep -q '^sub\.Hello' hello.out || ! grep -q '^subsub\.Hello' hello.out ; then
+ echo "testdata/$local/hard.go did not generate expected output"
+ cat hello.out
+ ok=false
+ fi
+
+ rm -f err.out hello.out hello
+
+ # Test that go install x.go fails.
+ if ./testgo install "testdata/$local/easy.go" >/dev/null 2>&1; then
+ echo "go install testdata/$local/easy.go succeeded"
+ ok=false
+ fi
+}
-rm -f err.out hello.out hello
+# Test local imports
+testlocal local
-# Test that go install x.go fails.
-if ./testgo install testdata/local/easy.go >/dev/null 2>&1; then
- echo "go install testdata/local/easy.go succeeded"
- ok=false
-fi
+# Test local imports again, with bad characters in the directory name.
+bad='#$%:, &()*;<=>?\^{}'
+rm -rf "testdata/$bad"
+cp -R testdata/local "testdata/$bad"
+testlocal "$bad"
+rm -rf "testdata/$bad"
# Test tests with relative imports.
if ! ./testgo test ./testdata/testimport; then
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index 6ca49d10f..870ab190f 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -192,8 +192,6 @@ See the documentation of the testing package for more information.
var (
testC bool // -c flag
testI bool // -i flag
- testP int // -p flag
- testX bool // -x flag
testV bool // -v flag
testFiles []string // -file flag(s) TODO: not respected
testTimeout string // -timeout flag
@@ -241,11 +239,6 @@ func runTest(cmd *Command, args []string) {
testStreamOutput = len(pkgArgs) == 0 || testBench ||
(len(pkgs) <= 1 && testShowPass)
- buildX = testX
- if testP > 0 {
- buildP = testP
- }
-
var b builder
b.init()
@@ -265,6 +258,9 @@ func runTest(cmd *Command, args []string) {
for _, path := range p.TestImports {
deps[path] = true
}
+ for _, path := range p.XTestImports {
+ deps[path] = true
+ }
}
// translate C to runtime/cgo
@@ -450,6 +446,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
ptest.imports = append(append([]*Package{}, p.imports...), imports...)
ptest.pkgdir = testDir
ptest.fake = true
+ ptest.forceLibrary = true
ptest.Stale = true
ptest.build = new(build.Package)
*ptest.build = *p.build
@@ -461,12 +458,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
m[k] = append(m[k], v...)
}
ptest.build.ImportPos = m
- computeStale(ptest)
- a := b.action(modeBuild, modeBuild, ptest)
- a.objdir = testDir + string(filepath.Separator)
- a.objpkg = ptestObj
- a.target = ptestObj
- a.link = false
} else {
ptest = p
}
@@ -477,6 +468,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
Name: p.Name + "_test",
ImportPath: p.ImportPath + "_test",
localPrefix: p.localPrefix,
+ Root: p.Root,
Dir: p.Dir,
GoFiles: p.XTestGoFiles,
Imports: p.XTestImports,
@@ -488,11 +480,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
fake: true,
Stale: true,
}
- computeStale(pxtest)
- a := b.action(modeBuild, modeBuild, pxtest)
- a.objdir = testDir + string(filepath.Separator)
- a.objpkg = buildToolchain.pkgpath(testDir, pxtest)
- a.target = a.objpkg
}
// Action for building pkg.test.
@@ -501,8 +488,9 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
Dir: testDir,
GoFiles: []string{"_testmain.go"},
ImportPath: "testmain",
+ Root: p.Root,
imports: []*Package{ptest},
- build: &build.Package{},
+ build: &build.Package{Name: "main"},
fake: true,
Stale: true,
}
@@ -523,6 +511,21 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
pmain.imports = append(pmain.imports, ptesting, pregexp)
computeStale(pmain)
+ if ptest != p {
+ a := b.action(modeBuild, modeBuild, ptest)
+ a.objdir = testDir + string(filepath.Separator)
+ a.objpkg = ptestObj
+ a.target = ptestObj
+ a.link = false
+ }
+
+ if pxtest != nil {
+ a := b.action(modeBuild, modeBuild, pxtest)
+ a.objdir = testDir + string(filepath.Separator)
+ a.objpkg = buildToolchain.pkgpath(testDir, pxtest)
+ a.target = a.objpkg
+ }
+
a := b.action(modeBuild, modeBuild, pmain)
a.objdir = testDir + string(filepath.Separator)
a.objpkg = filepath.Join(testDir, "main.a")
@@ -639,6 +642,9 @@ func (b *builder) runTest(a *action) error {
// cleanTest is the action for cleaning up after a test.
func (b *builder) cleanTest(a *action) error {
+ if buildWork {
+ return nil
+ }
run := a.deps[0]
testDir := filepath.Join(b.work, filepath.FromSlash(run.p.ImportPath+"/_test"))
os.RemoveAll(testDir)
diff --git a/src/cmd/go/testflag.go b/src/cmd/go/testflag.go
index 7c9b7f16d..ecf5bf456 100644
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -47,7 +47,7 @@ func testUsage() {
// testFlagSpec defines a flag we know about.
type testFlagSpec struct {
name string
- isBool bool
+ boolVar *bool
passToTest bool // pass to Test
multiOK bool // OK to have multiple instances
present bool // flag has been seen
@@ -56,11 +56,21 @@ type testFlagSpec struct {
// testFlagDefn is the set of flags we process.
var testFlagDefn = []*testFlagSpec{
// local.
- {name: "c", isBool: true},
+ {name: "c", boolVar: &testC},
{name: "file", multiOK: true},
- {name: "i", isBool: true},
+ {name: "i", boolVar: &testI},
+
+ // build flags.
+ {name: "a", boolVar: &buildA},
+ {name: "n", boolVar: &buildN},
{name: "p"},
- {name: "x", isBool: true},
+ {name: "x", boolVar: &buildX},
+ {name: "work", boolVar: &buildWork},
+ {name: "gcflags"},
+ {name: "ldflags"},
+ {name: "gccgoflags"},
+ {name: "tags"},
+ {name: "compiler"},
// passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v.
{name: "bench", passToTest: true},
@@ -71,9 +81,9 @@ var testFlagDefn = []*testFlagSpec{
{name: "memprofilerate", passToTest: true},
{name: "parallel", passToTest: true},
{name: "run", passToTest: true},
- {name: "short", isBool: true, passToTest: true},
+ {name: "short", boolVar: new(bool), passToTest: true},
{name: "timeout", passToTest: true},
- {name: "v", isBool: true, passToTest: true},
+ {name: "v", boolVar: &testV, passToTest: true},
}
// testFlags processes the command line, grabbing -x and -c, rewriting known flags
@@ -118,16 +128,21 @@ func testFlags(args []string) (packageNames, passToTest []string) {
continue
}
switch f.name {
- case "c":
- setBoolFlag(&testC, value)
- case "i":
- setBoolFlag(&testI, value)
+ // bool flags.
+ case "a", "c", "i", "n", "x", "v", "work":
+ setBoolFlag(f.boolVar, value)
case "p":
- setIntFlag(&testP, value)
- case "x":
- setBoolFlag(&testX, value)
- case "v":
- setBoolFlag(&testV, value)
+ setIntFlag(&buildP, value)
+ case "gcflags":
+ buildGcflags = strings.Fields(value)
+ case "ldflags":
+ buildLdflags = strings.Fields(value)
+ case "gccgoflags":
+ buildGccgoflags = strings.Fields(value)
+ case "tags":
+ buildContext.BuildTags = strings.Fields(value)
+ case "compiler":
+ buildCompiler{}.Set(value)
case "file":
testFiles = append(testFiles, value)
case "bench":
@@ -172,7 +187,7 @@ func testFlag(args []string, i int) (f *testFlagSpec, value string, extra bool)
for _, f = range testFlagDefn {
if name == f.name {
// Booleans are special because they have modes -x, -x=true, -x=false.
- if f.isBool {
+ if f.boolVar != nil {
if equals < 0 { // otherwise, it's been set and will be verified in setBoolFlag
value = "true"
} else {
diff --git a/src/cmd/go/vcs.go b/src/cmd/go/vcs.go
index cf3410242..3634b606c 100644
--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -7,7 +7,9 @@ package main
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
+ "log"
"os"
"os/exec"
"path/filepath"
@@ -102,7 +104,7 @@ var vcsGit = &vcsCmd{
tagSyncCmd: "checkout {tag}",
tagSyncDefault: "checkout origin/master",
- scheme: []string{"git", "https", "http"},
+ scheme: []string{"git", "https", "http", "git+ssh"},
pingCmd: "ls-remote {scheme}://{repo}",
}
@@ -121,7 +123,7 @@ var vcsBzr = &vcsCmd{
tagSyncCmd: "update -r {tag}",
tagSyncDefault: "update -r revno:-1",
- scheme: []string{"https", "http", "bzr"},
+ scheme: []string{"https", "http", "bzr", "bzr+ssh"},
pingCmd: "info {scheme}://{repo}",
}
@@ -136,7 +138,7 @@ var vcsSvn = &vcsCmd{
// There is no tag command in subversion.
// The branch information is all in the path names.
- scheme: []string{"https", "http", "svn"},
+ scheme: []string{"https", "http", "svn", "svn+ssh"},
pingCmd: "info {scheme}://{repo}",
}
@@ -302,12 +304,58 @@ func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) {
return nil, "", fmt.Errorf("directory %q is not using a known version control system", dir)
}
-// vcsForImportPath analyzes importPath to determine the
+// repoRoot represents a version control system, a repo, and a root of
+// where to put it on disk.
+type repoRoot struct {
+ vcs *vcsCmd
+
+ // repo is the repository URL, including scheme
+ repo string
+
+ // root is the import path corresponding to the root of the
+ // repository
+ root string
+}
+
+// repoRootForImportPath analyzes importPath to determine the
// version control system, and code repository to use.
-// On return, repo is the repository URL and root is the
-// import path corresponding to the root of the repository
-// (thus root is a prefix of importPath).
-func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err error) {
+func repoRootForImportPath(importPath string) (*repoRoot, error) {
+ rr, err := repoRootForImportPathStatic(importPath, "")
+ if err == errUnknownSite {
+ rr, err = repoRootForImportDynamic(importPath)
+
+ // repoRootForImportDynamic returns error detail
+ // that is irrelevant if the user didn't intend to use a
+ // dynamic import in the first place.
+ // Squelch it.
+ if err != nil {
+ if buildV {
+ log.Printf("import %q: %v", importPath, err)
+ }
+ err = fmt.Errorf("unrecognized import path %q", importPath)
+ }
+ }
+
+ if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.root, "...") {
+ // Do not allow wildcards in the repo root.
+ rr = nil
+ err = fmt.Errorf("cannot expand ... in %q", importPath)
+ }
+ return rr, err
+}
+
+var errUnknownSite = errors.New("dynamic lookup required to find mapping")
+
+// repoRootForImportPathStatic attempts to map importPath to a
+// repoRoot using the commonly-used VCS hosting sites in vcsPaths
+// (github.com/user/dir), or from a fully-qualified importPath already
+// containing its VCS type (foo.com/repo.git/dir)
+//
+// If scheme is non-empty, that scheme is forced.
+func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
+ if strings.Contains(importPath, "://") {
+ return nil, fmt.Errorf("invalid import path %q", importPath)
+ }
for _, srv := range vcsPaths {
if !strings.HasPrefix(importPath, srv.prefix) {
continue
@@ -315,7 +363,7 @@ func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err er
m := srv.regexp.FindStringSubmatch(importPath)
if m == nil {
if srv.prefix != "" {
- return nil, "", "", fmt.Errorf("invalid %s import path %q", srv.prefix, importPath)
+ return nil, fmt.Errorf("invalid %s import path %q", srv.prefix, importPath)
}
continue
}
@@ -338,24 +386,127 @@ func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err er
}
if srv.check != nil {
if err := srv.check(match); err != nil {
- return nil, "", "", err
+ return nil, err
}
}
vcs := vcsByCmd(match["vcs"])
if vcs == nil {
- return nil, "", "", fmt.Errorf("unknown version control system %q", match["vcs"])
+ return nil, fmt.Errorf("unknown version control system %q", match["vcs"])
}
if srv.ping {
- for _, scheme := range vcs.scheme {
- if vcs.ping(scheme, match["repo"]) == nil {
- match["repo"] = scheme + "://" + match["repo"]
- break
+ if scheme != "" {
+ match["repo"] = scheme + "://" + match["repo"]
+ } else {
+ for _, scheme := range vcs.scheme {
+ if vcs.ping(scheme, match["repo"]) == nil {
+ match["repo"] = scheme + "://" + match["repo"]
+ break
+ }
}
}
}
- return vcs, match["repo"], match["root"], nil
+ rr := &repoRoot{
+ vcs: vcs,
+ repo: match["repo"],
+ root: match["root"],
+ }
+ return rr, nil
+ }
+ return nil, errUnknownSite
+}
+
+// repoRootForImportDynamic finds a *repoRoot for a custom domain that's not
+// statically known by repoRootForImportPathStatic.
+//
+// This handles "vanity import paths" like "name.tld/pkg/foo".
+func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
+ slash := strings.Index(importPath, "/")
+ if slash < 0 {
+ return nil, fmt.Errorf("missing / in import %q", importPath)
+ }
+ urlStr, body, err := httpsOrHTTP(importPath)
+ if err != nil {
+ return nil, fmt.Errorf("http/https fetch for import %q: %v", importPath, err)
+ }
+ defer body.Close()
+ metaImport, err := matchGoImport(parseMetaGoImports(body), importPath)
+ if err != nil {
+ if err != errNoMatch {
+ return nil, fmt.Errorf("parse %s: %v", urlStr, err)
+ }
+ return nil, fmt.Errorf("parse %s: no go-import meta tags", urlStr)
+ }
+ if buildV {
+ log.Printf("get %q: found meta tag %#v at %s", importPath, metaImport, urlStr)
+ }
+ // If the import was "uni.edu/bob/project", which said the
+ // prefix was "uni.edu" and the RepoRoot was "evilroot.com",
+ // make sure we don't trust Bob and check out evilroot.com to
+ // "uni.edu" yet (possibly overwriting/preempting another
+ // non-evil student). Instead, first verify the root and see
+ // if it matches Bob's claim.
+ if metaImport.Prefix != importPath {
+ if buildV {
+ log.Printf("get %q: verifying non-authoritative meta tag", importPath)
+ }
+ urlStr0 := urlStr
+ urlStr, body, err = httpsOrHTTP(metaImport.Prefix)
+ if err != nil {
+ return nil, fmt.Errorf("fetch %s: %v", urlStr, err)
+ }
+ imports := parseMetaGoImports(body)
+ if len(imports) == 0 {
+ return nil, fmt.Errorf("fetch %s: no go-import meta tag", urlStr)
+ }
+ metaImport2, err := matchGoImport(imports, importPath)
+ if err != nil || metaImport != metaImport2 {
+ return nil, fmt.Errorf("%s and %s disagree about go-import for %s", urlStr0, urlStr, metaImport.Prefix)
+ }
+ }
+
+ if !strings.Contains(metaImport.RepoRoot, "://") {
+ return nil, fmt.Errorf("%s: invalid repo root %q; no scheme", urlStr, metaImport.RepoRoot)
+ }
+ rr := &repoRoot{
+ vcs: vcsByCmd(metaImport.VCS),
+ repo: metaImport.RepoRoot,
+ root: metaImport.Prefix,
+ }
+ if rr.vcs == nil {
+ return nil, fmt.Errorf("%s: unknown vcs %q", urlStr, metaImport.VCS)
+ }
+ return rr, nil
+}
+
+// metaImport represents the parsed <meta name="go-import"
+// content="prefix vcs reporoot" /> tags from HTML files.
+type metaImport struct {
+ Prefix, VCS, RepoRoot string
+}
+
+// errNoMatch is returned from matchGoImport when there's no applicable match.
+var errNoMatch = errors.New("no import match")
+
+// matchGoImport returns the metaImport from imports matching importPath.
+// An error is returned if there are multiple matches.
+// errNoMatch is returned if none match.
+func matchGoImport(imports []metaImport, importPath string) (_ metaImport, err error) {
+ match := -1
+ for i, im := range imports {
+ if !strings.HasPrefix(importPath, im.Prefix) {
+ continue
+ }
+ if match != -1 {
+ err = fmt.Errorf("multiple meta tags match import path %q", importPath)
+ return
+ }
+ match = i
+ }
+ if match == -1 {
+ err = errNoMatch
+ return
}
- return nil, "", "", fmt.Errorf("unrecognized import path %q", importPath)
+ return imports[match], nil
}
// expand rewrites s to replace {k} with match[k] for each key k in match.