diff options
Diffstat (limited to 'src/cmd/go')
-rw-r--r-- | src/cmd/go/bootstrap.go | 17 | ||||
-rw-r--r-- | src/cmd/go/build.go | 272 | ||||
-rw-r--r-- | src/cmd/go/clean.go | 2 | ||||
-rw-r--r-- | src/cmd/go/discovery.go | 63 | ||||
-rw-r--r-- | src/cmd/go/doc.go | 101 | ||||
-rw-r--r-- | src/cmd/go/env.go | 89 | ||||
-rw-r--r-- | src/cmd/go/get.go | 132 | ||||
-rw-r--r-- | src/cmd/go/help.go | 48 | ||||
-rw-r--r-- | src/cmd/go/http.go | 52 | ||||
-rw-r--r-- | src/cmd/go/list.go | 37 | ||||
-rw-r--r-- | src/cmd/go/main.go | 57 | ||||
-rw-r--r-- | src/cmd/go/match_test.go | 36 | ||||
-rw-r--r-- | src/cmd/go/pkg.go | 93 | ||||
-rw-r--r-- | src/cmd/go/run.go | 9 | ||||
-rwxr-xr-x | src/cmd/go/test.bash | 71 | ||||
-rw-r--r-- | src/cmd/go/test.go | 44 | ||||
-rw-r--r-- | src/cmd/go/testflag.go | 47 | ||||
-rw-r--r-- | src/cmd/go/vcs.go | 185 |
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. |