"bytes"
"debug/elf"
"debug/macho"
+ "flag"
"fmt"
"go/format"
"internal/race"
}
os.Unsetenv("GOROOT_FINAL")
+ flag.Parse()
+ if *proxyAddr != "" {
+ StartProxy()
+ select {}
+ }
+
if canRun {
args := []string{"build", "-tags", "testgo", "-o", "testgo" + exeSuffix}
if race.Enabled {
func (tg *testgoData) run(args ...string) {
tg.t.Helper()
if status := tg.doRun(args); status != nil {
- tg.t.Logf("go %v failed unexpectedly: %v", args, status)
+ wd, _ := os.Getwd()
+ tg.t.Logf("go %v failed unexpectedly in %s: %v", args, wd, status)
tg.t.FailNow()
}
}
}
}
+// If -testwork is specified, the test prints the name of the temp directory
+// and does not remove it when done, so that a programmer can
+// poke at the test file tree afterward.
+var testWork = flag.Bool("testwork", false, "")
+
// cleanup cleans up a test that runs testgo.
func (tg *testgoData) cleanup() {
tg.t.Helper()
if tg.wd != "" {
+ wd, _ := os.Getwd()
+ tg.t.Logf("ended in %s", wd)
+
if err := os.Chdir(tg.wd); err != nil {
// We are unlikely to be able to continue.
fmt.Fprintln(os.Stderr, "could not restore working directory, crashing:", err)
os.Exit(2)
}
}
+ if *testWork {
+ tg.t.Logf("TESTWORK=%s\n", tg.path("."))
+ return
+ }
for _, path := range tg.temps {
- tg.check(os.RemoveAll(path))
+ tg.check(removeAll(path))
}
if tg.tempdir != "" {
- tg.check(os.RemoveAll(tg.tempdir))
+ tg.check(removeAll(tg.tempdir))
}
}
+func removeAll(dir string) error {
+ // module cache has 0444 directories;
+ // make them writable in order to remove content.
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil // ignore errors walking in file system
+ }
+ if info.IsDir() {
+ os.Chmod(path, 0777)
+ }
+ return nil
+ })
+ return os.RemoveAll(dir)
+}
+
// failSSH puts an ssh executable in the PATH that always fails.
// This is to stub out uses of ssh by go get.
func (tg *testgoData) failSSH() {
defer tg.cleanup()
tg.tempDir("gopath")
tg.setenv("GOPATH", tg.path("gopath"))
- tg.run("get", "golang.org/x/tour/content")
- tg.run("get", "-t", "golang.org/x/tour/content")
+ tg.run("get", "golang.org/x/tour/content...")
+ tg.run("get", "-t", "golang.org/x/tour/content...")
}
func TestInstalls(t *testing.T) {
checkbar("cmd")
}
-func TestGoBuildInTestOnlyDirectoryFailsWithAGoodError(t *testing.T) {
+func TestGoBuildTestOnly(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
- tg.runFail("build", "./testdata/testonly")
- tg.grepStderr("no non-test Go files in", "go build ./testdata/testonly produced unexpected error")
+ tg.makeTempdir()
+ tg.setenv("GOPATH", tg.path("."))
+ tg.tempFile("src/testonly/t_test.go", `package testonly`)
+ tg.tempFile("src/testonly2/t.go", `package testonly2`)
+ tg.cd(tg.path("src"))
+
+ // Named explicitly, test-only packages should be reported as unbuildable/uninstallable,
+ // even if there is a wildcard also matching.
+ tg.runFail("build", "testonly", "testonly...")
+ tg.grepStderr("no non-test Go files in", "go build ./xtestonly produced unexpected error")
+ tg.runFail("install", "./testonly")
+ tg.grepStderr("no non-test Go files in", "go install ./testonly produced unexpected error")
+
+ // Named through a wildcards, the test-only packages should be silently ignored.
+ tg.run("build", "testonly...")
+ tg.run("install", "./testonly...")
}
func TestGoTestDetectsTestOnlyImportCycles(t *testing.T) {
tg.tempFile("src/@x/x.go", "package x\n")
tg.setenv("GOPATH", tg.path("."))
tg.runFail("build", "@x")
- tg.grepStderr("invalid input directory name \"@x\"", "did not reject @x directory")
+ tg.grepStderr("invalid input directory name \"@x\"|cannot use path@version syntax", "did not reject @x directory")
tg.tempFile("src/@x/y/y.go", "package y\n")
tg.setenv("GOPATH", tg.path("."))
tg.runFail("build", "@x/y")
- tg.grepStderr("invalid import path \"@x/y\"", "did not reject @x/y import path")
+ tg.grepStderr("invalid import path \"@x/y\"|cannot use path@version syntax", "did not reject @x/y import path")
tg.tempFile("src/-x/x.go", "package x\n")
tg.setenv("GOPATH", tg.path("."))
BuildA bool // -a flag
BuildBuildmode string // -buildmode flag
BuildContext = build.Default
+ BuildGetmode string // -getmode flag
BuildI bool // -i flag
BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag
Goos = BuildContext.GOOS
ExeSuffix string
Gopath = filepath.SplitList(BuildContext.GOPATH)
+
+ // ModulesEnabled specifies whether the go command is running
+ // in module-aware mode (as opposed to GOPATH mode).
+ // It is equal to modload.Enabled, but not all packages can import modload.
+ ModulesEnabled bool
)
func init() {
"encoding/json"
"fmt"
"os"
+ "path/filepath"
"runtime"
"strings"
"cmd/go/internal/cache"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
- "cmd/go/internal/vgo"
+ "cmd/go/internal/modload"
"cmd/go/internal/work"
)
// ExtraEnvVars returns environment variables that should not leak into child processes.
func ExtraEnvVars() []cfg.EnvVar {
+ gomod := ""
+ if modload.Init(); modload.ModRoot != "" {
+ gomod = filepath.Join(modload.ModRoot, "go.mod")
+ }
+ return []cfg.EnvVar{
+ {Name: "GOMOD", Value: gomod},
+ }
+}
+
+// ExtraEnvVarsCostly returns environment variables that should not leak into child processes
+// but are costly to evaluate.
+func ExtraEnvVarsCostly() []cfg.EnvVar {
var b work.Builder
b.Init()
cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
return nil
}
cmd := b.GccCmd(".", "")
+
return []cfg.EnvVar{
// Note: Update the switch in runEnv below when adding to this list.
{Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")},
{Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")},
{Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
{Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")},
- {Name: "VGOMODROOT", Value: vgo.ModRoot},
}
}
func runEnv(cmd *base.Command, args []string) {
env := cfg.CmdEnv
+ env = append(env, ExtraEnvVars()...)
- // Do we need to call ExtraEnvVars, which is a bit expensive?
+ // Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
// Only if we're listing all environment variables ("go env")
// or the variables being requested are in the extra list.
- needExtra := true
+ needCostly := true
if len(args) > 0 {
- needExtra = false
+ needCostly = false
for _, arg := range args {
switch arg {
case "CGO_CFLAGS",
"CGO_LDFLAGS",
"PKG_CONFIG",
"GOGCCFLAGS":
- needExtra = true
+ needCostly = true
}
}
}
- if needExtra {
- env = append(env, ExtraEnvVars()...)
+ if needCostly {
+ env = append(env, ExtraEnvVarsCostly()...)
}
if len(args) > 0 {
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
+ "cmd/go/internal/modload"
"cmd/go/internal/str"
- "cmd/go/internal/vgo"
"fmt"
"os"
)
func runFix(cmd *base.Command, args []string) {
printed := false
for _, pkg := range load.Packages(args) {
- if vgo.Enabled() && !pkg.Module.Top {
+ if modload.Enabled() && !pkg.Module.Main {
if !printed {
- fmt.Fprintf(os.Stderr, "vgo: not fixing packages in dependency modules\n")
+ fmt.Fprintf(os.Stderr, "go: not fixing packages in dependency modules\n")
printed = true
}
continue
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
+ "cmd/go/internal/modload"
"cmd/go/internal/str"
- "cmd/go/internal/vgo"
)
func init() {
}()
}
for _, pkg := range load.PackagesAndErrors(args) {
- if vgo.Enabled() && !pkg.Module.Top {
+ if modload.Enabled() && !pkg.Module.Main {
if !printed {
- fmt.Fprintf(os.Stderr, "vgo: not formatting packages in dependency modules\n")
+ fmt.Fprintf(os.Stderr, "go: not formatting packages in dependency modules\n")
printed = true
}
continue
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
- "cmd/go/internal/vgo"
+ "cmd/go/internal/modload"
"cmd/go/internal/work"
)
// Even if the arguments are .go files, this loop suffices.
printed := false
for _, pkg := range load.Packages(args) {
- if vgo.Enabled() && !pkg.Module.Top {
+ if modload.Enabled() && !pkg.Module.Main {
if !printed {
- fmt.Fprintf(os.Stderr, "vgo: not generating in packages in dependency modules\n")
+ fmt.Fprintf(os.Stderr, "go: not generating in packages in dependency modules\n")
printed = true
}
continue
// 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, err error) {
+func parseMetaGoImports(r io.Reader, mod ModuleMode) (imports []metaImport, err error) {
d := xml.NewDecoder(r)
d.CharsetReader = charsetReader
d.Strict = false
if err == io.EOF || len(imports) > 0 {
err = nil
}
- return
+ break
}
if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
- return
+ break
}
if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
- return
+ break
}
e, ok := t.(xml.StartElement)
if !ok || !strings.EqualFold(e.Name.Local, "meta") {
continue
}
if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
- // Ignore VCS type "mod", which is new Go modules.
- // This code is for old go get and must ignore the new mod lines.
- // Otherwise matchGoImport will complain about two
- // different metaImport lines for the same Prefix.
- if f[1] == "mod" {
- continue
- }
imports = append(imports, metaImport{
Prefix: f[0],
VCS: f[1],
})
}
}
+
+ // Extract mod entries if we are paying attention to them.
+ var list []metaImport
+ var have map[string]bool
+ if mod == PreferMod {
+ have = make(map[string]bool)
+ for _, m := range imports {
+ if m.VCS == "mod" {
+ have[m.Prefix] = true
+ list = append(list, m)
+ }
+ }
+ }
+
+ // Append non-mod entries, ignoring those superseded by a mod entry.
+ for _, m := range imports {
+ if m.VCS != "mod" && !have[m.Prefix] {
+ list = append(list, m)
+ }
+ }
+ return list, nil
}
// attrValue returns the attribute value for the case-insensitive key
"cmd/go/internal/load"
"cmd/go/internal/search"
"cmd/go/internal/str"
- "cmd/go/internal/vgo"
"cmd/go/internal/web"
"cmd/go/internal/work"
)
var CmdGet = &base.Command{
- UsageLine: "get [-d] [-f] [-fix] [-insecure] [-t] [-u] [-v] [build flags] [packages]",
+ UsageLine: "get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]",
Short: "download and install packages and dependencies",
Long: `
Get downloads the packages named by the import paths, along with their
For more about how 'go get' finds source code to
download, see 'go help importpath'.
+This text describes the behavior of get when using GOPATH
+to manage source code and dependencies.
+If instead the go command is running in module-aware mode,
+the details of get's flags and effects change, as does 'go help get'.
+See 'go help modules' and 'go help module-get'.
+
See also: go build, go install, go clean.
`,
}
-var getD = CmdGet.Flag.Bool("d", false, "")
-var getF = CmdGet.Flag.Bool("f", false, "")
-var getT = CmdGet.Flag.Bool("t", false, "")
-var getU = CmdGet.Flag.Bool("u", false, "")
-var getFix = CmdGet.Flag.Bool("fix", false, "")
-var getInsecure = CmdGet.Flag.Bool("insecure", false, "")
+var HelpGopathGet = &base.Command{
+ UsageLine: "gopath-get",
+ Short: "legacy GOPATH go get",
+ Long: `
+The 'go get' command changes behavior depending on whether the
+go command is running in module-aware mode or legacy GOPATH mode.
+This help text, accessible as 'go help gopath-get' even in module-aware mode,
+describes 'go get' as it operates in legacy GOPATH mode.
+
+Usage: ` + CmdGet.UsageLine + `
+` + CmdGet.Long,
+}
+
+var (
+ getD = CmdGet.Flag.Bool("d", false, "")
+ getF = CmdGet.Flag.Bool("f", false, "")
+ getT = CmdGet.Flag.Bool("t", false, "")
+ getU = CmdGet.Flag.Bool("u", false, "")
+ getFix = CmdGet.Flag.Bool("fix", false, "")
+
+ Insecure bool
+)
func init() {
work.AddBuildFlags(CmdGet)
CmdGet.Run = runGet // break init loop
+ CmdGet.Flag.BoolVar(&Insecure, "insecure", Insecure, "")
}
func runGet(cmd *base.Command, args []string) {
- if vgo.Enabled() {
- base.Fatalf("go get: vgo not implemented")
+ if cfg.ModulesEnabled {
+ // Should not happen: main.go should install the separate module-enabled get code.
+ base.Fatalf("go get: modules not implemented")
}
work.BuildInit()
return
}
- work.InstallPackages(args, true)
+ work.InstallPackages(args)
}
// downloadPaths prepares the list of paths to pass to download.
// in the hope that we can figure out the repository from the
// initial ...-free prefix.
func downloadPaths(args []string) []string {
+ for _, arg := range args {
+ if strings.Contains(arg, "@") {
+ base.Fatalf("go: cannot use path@version syntax in GOPATH mode")
+ }
+ }
+
args = load.ImportPathsForGoGet(args)
var out []string
for _, a := range args {
)
security := web.Secure
- if *getInsecure {
+ if Insecure {
security = web.Insecure
}
}
repo = remote
if !*getF && err == nil {
- if rr, err := repoRootForImportPath(p.ImportPath, security); err == nil {
- repo := rr.repo
+ if rr, err := RepoRootForImportPath(p.ImportPath, IgnoreMod, security); err == nil {
+ repo := rr.Repo
if rr.vcs.resolveRepo != nil {
resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo)
if err == nil {
repo = resolved
}
}
- if remote != repo && rr.isCustom {
- return fmt.Errorf("%s is a custom import path for %s, but %s is checked out from %s", rr.root, repo, dir, remote)
+ if remote != repo && rr.IsCustom {
+ return fmt.Errorf("%s is a custom import path for %s, but %s is checked out from %s", rr.Root, repo, dir, remote)
}
}
}
} else {
// Analyze the import path to determine the version control system,
// repository, and the import path for the root of the repository.
- rr, err := repoRootForImportPath(p.ImportPath, security)
+ rr, err := RepoRootForImportPath(p.ImportPath, IgnoreMod, security)
if err != nil {
return err
}
- vcs, repo, rootPath = rr.vcs, rr.repo, rr.root
+ vcs, repo, rootPath = rr.vcs, rr.Repo, rr.Root
}
- if !blindRepo && !vcs.isSecure(repo) && !*getInsecure {
+ if !blindRepo && !vcs.isSecure(repo) && !Insecure {
return fmt.Errorf("cannot download, %v uses insecure protocol", repo)
}
var parseMetaGoImportsTests = []struct {
in string
+ mod ModuleMode
out []metaImport
}{
{
`<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">`,
+ IgnoreMod,
[]metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
},
{
`<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
<meta name="go-import" content="baz/quux git http://github.com/rsc/baz/quux">`,
+ IgnoreMod,
[]metaImport{
{"foo/bar", "git", "https://github.com/rsc/foo/bar"},
{"baz/quux", "git", "http://github.com/rsc/baz/quux"},
{
`<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
<meta name="go-import" content="foo/bar mod http://github.com/rsc/baz/quux">`,
+ IgnoreMod,
[]metaImport{
{"foo/bar", "git", "https://github.com/rsc/foo/bar"},
},
{
`<meta name="go-import" content="foo/bar mod http://github.com/rsc/baz/quux">
<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">`,
+ IgnoreMod,
[]metaImport{
{"foo/bar", "git", "https://github.com/rsc/foo/bar"},
},
},
+ {
+ `<meta name="go-import" content="foo/bar mod http://github.com/rsc/baz/quux">
+ <meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">`,
+ PreferMod,
+ []metaImport{
+ {"foo/bar", "mod", "http://github.com/rsc/baz/quux"},
+ },
+ },
{
`<head>
<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
</head>`,
+ IgnoreMod,
[]metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
},
{
`<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
<body>`,
+ IgnoreMod,
[]metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
},
{
`<!doctype html><meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">`,
+ IgnoreMod,
[]metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
},
{
// XML doesn't like <div style=position:relative>.
`<!doctype html><title>Page Not Found</title><meta name=go-import content="chitin.io/chitin git https://github.com/chitin-io/chitin"><div style=position:relative>DRAFT</div>`,
+ IgnoreMod,
[]metaImport{{"chitin.io/chitin", "git", "https://github.com/chitin-io/chitin"}},
},
}
func TestParseMetaGoImports(t *testing.T) {
for i, tt := range parseMetaGoImportsTests {
- out, err := parseMetaGoImports(strings.NewReader(tt.in))
+ out, err := parseMetaGoImports(strings.NewReader(tt.in), tt.mod)
if err != nil {
t.Errorf("test#%d: %v", i, err)
continue
return nil
}
-// 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
-
- // isCustom is true for custom import paths (those defined by HTML meta tags)
- isCustom bool
+// RepoRoot describes the repository root for a tree of source code.
+type RepoRoot struct {
+ Repo string // repository URL, including scheme
+ Root string // import path corresponding to root of repo
+ IsCustom bool // defined by served <meta> tags (as opposed to hard-coded pattern)
+ VCS string // vcs type ("mod", "git", ...)
+
+ vcs *vcsCmd // internal: vcs command access
}
var httpPrefixRE = regexp.MustCompile(`^https?:`)
-// repoRootForImportPath analyzes importPath to determine the
+// ModuleMode specifies whether to prefer modules when looking up code sources.
+type ModuleMode int
+
+const (
+ IgnoreMod ModuleMode = iota
+ PreferMod
+)
+
+// RepoRootForImportPath analyzes importPath to determine the
// version control system, and code repository to use.
-func repoRootForImportPath(importPath string, security web.SecurityMode) (*repoRoot, error) {
+func RepoRootForImportPath(importPath string, mod ModuleMode, security web.SecurityMode) (*RepoRoot, error) {
rr, err := repoRootFromVCSPaths(importPath, "", security, vcsPaths)
if err == errUnknownSite {
// If there are wildcards, look up the thing before the wildcard,
if i := strings.Index(lookup, "/.../"); i >= 0 {
lookup = lookup[:i]
}
- rr, err = repoRootForImportDynamic(lookup, security)
+ rr, err = repoRootForImportDynamic(lookup, mod, security)
if err != nil {
err = fmt.Errorf("unrecognized import path %q (%v)", importPath, err)
}
}
}
- if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.root, "...") {
+ 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)
// repoRootFromVCSPaths attempts to map importPath to a repoRoot
// using the mappings defined in vcsPaths.
// If scheme is non-empty, that scheme is forced.
-func repoRootFromVCSPaths(importPath, scheme string, security web.SecurityMode, vcsPaths []*vcsPath) (*repoRoot, error) {
+func repoRootFromVCSPaths(importPath, scheme string, security web.SecurityMode, vcsPaths []*vcsPath) (*RepoRoot, error) {
// A common error is to use https://packagepath because that's what
// hg and git require. Diagnose this helpfully.
if loc := httpPrefixRE.FindStringIndex(importPath); loc != nil {
if security == web.Secure && !vcs.isSecureScheme(scheme) {
continue
}
- if vcs.ping(scheme, match["repo"]) == nil {
+ if vcs.pingCmd != "" && vcs.ping(scheme, match["repo"]) == nil {
match["repo"] = scheme + "://" + match["repo"]
- break
+ goto Found
}
}
+ // No scheme found. Fall back to the first one.
+ match["repo"] = vcs.scheme[0] + "://" + match["repo"]
+ Found:
}
}
- rr := &repoRoot{
+ rr := &RepoRoot{
+ Repo: match["repo"],
+ Root: match["root"],
+ VCS: vcs.cmd,
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
+// repoRootForImportDynamic finds a *RepoRoot for a custom domain that's not
// statically known by repoRootForImportPathStatic.
//
// This handles custom import paths like "name.tld/pkg/foo" or just "name.tld".
-func repoRootForImportDynamic(importPath string, security web.SecurityMode) (*repoRoot, error) {
+func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.SecurityMode) (*RepoRoot, error) {
slash := strings.Index(importPath, "/")
if slash < 0 {
slash = len(importPath)
return nil, fmt.Errorf(msg, err)
}
defer body.Close()
- imports, err := parseMetaGoImports(body)
+ imports, err := parseMetaGoImports(body, mod)
if err != nil {
return nil, fmt.Errorf("parsing %s: %v", importPath, err)
}
}
urlStr0 := urlStr
var imports []metaImport
- urlStr, imports, err = metaImportsForPrefix(mmi.Prefix, security)
+ urlStr, imports, err = metaImportsForPrefix(mmi.Prefix, mod, security)
if err != nil {
return nil, err
}
if err := validateRepoRoot(mmi.RepoRoot); err != nil {
return nil, fmt.Errorf("%s: invalid repo root %q: %v", urlStr, mmi.RepoRoot, err)
}
- rr := &repoRoot{
- vcs: vcsByCmd(mmi.VCS),
- repo: mmi.RepoRoot,
- root: mmi.Prefix,
- isCustom: true,
- }
- if rr.vcs == nil {
+ vcs := vcsByCmd(mmi.VCS)
+ if vcs == nil && mmi.VCS != "mod" {
return nil, fmt.Errorf("%s: unknown vcs %q", urlStr, mmi.VCS)
}
+
+ rr := &RepoRoot{
+ Repo: mmi.RepoRoot,
+ Root: mmi.Prefix,
+ IsCustom: true,
+ VCS: mmi.VCS,
+ vcs: vcs,
+ }
return rr, nil
}
// It is an error if no imports are found.
// urlStr will still be valid if err != nil.
// The returned urlStr will be of the form "https://golang.org/x/tools?go-get=1"
-func metaImportsForPrefix(importPrefix string, security web.SecurityMode) (urlStr string, imports []metaImport, err error) {
+func metaImportsForPrefix(importPrefix string, mod ModuleMode, security web.SecurityMode) (urlStr string, imports []metaImport, err error) {
setCache := func(res fetchResult) (fetchResult, error) {
fetchCacheMu.Lock()
defer fetchCacheMu.Unlock()
if err != nil {
return setCache(fetchResult{urlStr: urlStr, err: fmt.Errorf("fetch %s: %v", urlStr, err)})
}
- imports, err := parseMetaGoImports(body)
+ imports, err := parseMetaGoImports(body, mod)
if err != nil {
return setCache(fetchResult{urlStr: urlStr, err: fmt.Errorf("parsing %s: %v", urlStr, err)})
}
"cmd/go/internal/web"
)
-// Test that RepoRootForImportPath creates the correct RepoRoot for a given importPath.
+// Test that RepoRootForImportPath determines the correct RepoRoot for a given importPath.
// TODO(cmang): Add tests for SVN and BZR.
func TestRepoRootForImportPath(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
tests := []struct {
path string
- want *repoRoot
+ want *RepoRoot
}{
{
"github.com/golang/groupcache",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://github.com/golang/groupcache",
+ Repo: "https://github.com/golang/groupcache",
},
},
// Unicode letters in directories (issue 18660).
{
"github.com/user/unicode/испытание",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://github.com/user/unicode",
+ Repo: "https://github.com/user/unicode",
},
},
// IBM DevOps Services tests
{
"hub.jazz.net/git/user1/pkgname",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://hub.jazz.net/git/user1/pkgname",
+ Repo: "https://hub.jazz.net/git/user1/pkgname",
},
},
{
"hub.jazz.net/git/user1/pkgname/submodule/submodule/submodule",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://hub.jazz.net/git/user1/pkgname",
+ Repo: "https://hub.jazz.net/git/user1/pkgname",
},
},
{
},
{
"hub.jazz.net/git/user/pkg.name",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://hub.jazz.net/git/user/pkg.name",
+ Repo: "https://hub.jazz.net/git/user/pkg.name",
},
},
// User names cannot have uppercase letters
// OpenStack tests
{
"git.openstack.org/openstack/swift",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://git.openstack.org/openstack/swift",
+ Repo: "https://git.openstack.org/openstack/swift",
},
},
// Trailing .git is less preferred but included for
// be compilable on both old and new go
{
"git.openstack.org/openstack/swift.git",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://git.openstack.org/openstack/swift.git",
+ Repo: "https://git.openstack.org/openstack/swift.git",
},
},
{
"git.openstack.org/openstack/swift/go/hummingbird",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://git.openstack.org/openstack/swift",
+ Repo: "https://git.openstack.org/openstack/swift",
},
},
{
},
{
"git.apache.org/package-name.git",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://git.apache.org/package-name.git",
+ Repo: "https://git.apache.org/package-name.git",
},
},
{
"git.apache.org/package-name_2.x.git/path/to/lib",
- &repoRoot{
+ &RepoRoot{
vcs: vcsGit,
- repo: "https://git.apache.org/package-name_2.x.git",
+ Repo: "https://git.apache.org/package-name_2.x.git",
},
},
{
"chiselapp.com/user/kyle/repository/fossilgg",
- &repoRoot{
+ &RepoRoot{
vcs: vcsFossil,
- repo: "https://chiselapp.com/user/kyle/repository/fossilgg",
+ Repo: "https://chiselapp.com/user/kyle/repository/fossilgg",
},
},
{
}
for _, test := range tests {
- got, err := repoRootForImportPath(test.path, web.Secure)
+ got, err := RepoRootForImportPath(test.path, IgnoreMod, web.Secure)
want := test.want
if want == nil {
if err == nil {
- t.Errorf("repoRootForImportPath(%q): Error expected but not received", test.path)
+ t.Errorf("RepoRootForImportPath(%q): Error expected but not received", test.path)
}
continue
}
if err != nil {
- t.Errorf("repoRootForImportPath(%q): %v", test.path, err)
+ t.Errorf("RepoRootForImportPath(%q): %v", test.path, err)
continue
}
- if got.vcs.name != want.vcs.name || got.repo != want.repo {
- t.Errorf("repoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.vcs, got.repo, want.vcs, want.repo)
+ if got.vcs.name != want.vcs.name || got.Repo != want.Repo {
+ t.Errorf("RepoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.vcs, got.Repo, want.vcs, want.Repo)
}
}
}
f.Close()
}
- want := repoRoot{
+ want := RepoRoot{
vcs: vcs,
- root: path.Join("example.com", vcs.name),
+ Root: path.Join("example.com", vcs.name),
}
- var got repoRoot
- got.vcs, got.root, err = vcsFromDir(dir, tempDir)
+ var got RepoRoot
+ got.vcs, got.Root, err = vcsFromDir(dir, tempDir)
if err != nil {
t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err)
continue
}
- if got.vcs.name != want.vcs.name || got.root != want.root {
- t.Errorf("FromDir(%q, %q) = VCS(%s) Root(%s), want VCS(%s) Root(%s)", dir, tempDir, got.vcs, got.root, want.vcs, want.root)
+ if got.vcs.name != want.vcs.name || got.Root != want.Root {
+ t.Errorf("FromDir(%q, %q) = VCS(%s) Root(%s), want VCS(%s) Root(%s)", dir, tempDir, got.vcs, got.Root, want.vcs, want.Root)
}
}
}
buf := new(bytes.Buffer)
PrintUsage(buf)
usage := &base.Command{Long: buf.String()}
- tmpl(&commentWriter{W: os.Stdout}, documentationTemplate, append([]*base.Command{usage}, base.Commands...))
+ cmds := []*base.Command{usage}
+ for _, cmd := range base.Commands {
+ if cmd.UsageLine == "gopath-get" {
+ // Avoid duplication of the "get" documentation.
+ continue
+ }
+ cmds = append(cmds, cmd)
+ }
+ tmpl(&commentWriter{W: os.Stdout}, documentationTemplate, cmds)
fmt.Println("package main")
return
}
var HelpPackages = &base.Command{
UsageLine: "packages",
- Short: "package lists",
+ Short: "package lists and patterns",
Long: `
Many commands apply to a set of packages:
- "main" denotes the top-level package in a stand-alone executable.
-- "all" expands to all package directories found in all the GOPATH
+- "all" expands to all packages found in all the GOPATH
trees. For example, 'go list all' lists all the packages on the local
-system.
+system. When using modules, "all" expands to all packages in
+the main module and their dependencies, including dependencies
+needed by tests of any of those.
- "std" is like all but expands to just the packages in the standard
Go library.
"cmd/go/internal/cache"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
+ "cmd/go/internal/modload"
"cmd/go/internal/work"
)
var CmdList = &base.Command{
- UsageLine: "list [-cgo] [-deps] [-e] [-export] [-f format] [-json] [-test] [build flags] [packages]",
- Short: "list packages",
+ // Note: -f -json -m are listed explicitly because they are the most common list flags.
+ // Do not send CLs removing them because they're covered by [list flags].
+ UsageLine: "list [-f format] [-json] [-m] [list flags] [build flags] [packages]",
+ Short: "list packages or modules",
Long: `
-List lists the packages named by the import paths, one per line.
+List lists the named packages, one per line.
+The most commonly-used flags are -f and -json, which control the form
+of the output printed for each package. Other list flags, documented below,
+control more specific details.
The default output shows the package import path:
golang.org/x/net/html
The -f flag specifies an alternate format for the list, using the
-syntax of package template. The default output is equivalent to -f
-'{{.ImportPath}}'. The struct being passed to the template is:
+syntax of package template. The default output 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
- ImportComment string // path in import comment on package statement
- Name string // package name
- Doc string // package documentation string
- Target string // install path
- Shlib string // the shared library that contains this package (only set when -linkshared)
- 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?
- StaleReason string // explanation for Stale==true
- Root string // Go root or Go path dir containing this package
- ConflictDir string // this directory shadows Dir in $GOPATH
- BinaryOnly bool // binary-only package: cannot be recompiled from sources
- ForTest string // package is only for use in named test
- DepOnly bool // package is only a dependency, not explicitly listed
- Export string // file containing export data (when using -export)
+ Dir string // directory containing package sources
+ ImportPath string // import path of package in dir
+ ImportComment string // path in import comment on package statement
+ Name string // package name
+ Doc string // package documentation string
+ Target string // install path
+ Shlib string // the shared library that contains this package (only set when -linkshared)
+ 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?
+ StaleReason string // explanation for Stale==true
+ Root string // Go root or Go path dir containing this package
+ ConflictDir string // this directory shadows Dir in $GOPATH
+ BinaryOnly bool // binary-only package: cannot be recompiled from sources
+ ForTest string // package is only for use in named test
+ DepOnly bool // package is only a dependency, not explicitly listed
+ Export string // file containing export data (when using -export)
+ Module *Module // info about package's containing module, if any (can be nil)
// Source files
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
Err string // the error itself
}
+The module information is a Module struct, defined in the discussion
+of list -m below.
+
The template function "join" calls strings.Join.
The template function "context" returns the build context, defined as:
- type Context struct {
- GOARCH string // target architecture
- GOOS string // target operating system
- GOROOT string // Go root
- GOPATH string // Go path
- CgoEnabled bool // whether cgo can be used
- UseAllFiles bool // use files regardless of +build lines, file names
- Compiler string // compiler to assume when computing target paths
- BuildTags []string // build constraints to match in +build lines
- ReleaseTags []string // releases the current release is compatible with
- InstallSuffix string // suffix to use in the name of the install dir
- }
+ type Context struct {
+ GOARCH string // target architecture
+ GOOS string // target operating system
+ GOROOT string // Go root
+ GOPATH string // Go path
+ CgoEnabled bool // whether cgo can be used
+ UseAllFiles bool // use files regardless of +build lines, file names
+ Compiler string // compiler to assume when computing target paths
+ BuildTags []string // build constraints to match in +build lines
+ ReleaseTags []string // releases the current release is compatible with
+ InstallSuffix string // suffix to use in the name of the install dir
+ }
For more information about the meaning of these fields see the documentation
for the go/build package's Context type.
referring to cached copies of generated Go source files.
Although they are Go source files, the paths may not end in ".go".
+The -m flag causes list to list modules instead of packages.
+
+When listing modules, the -f flag still specifies a format template
+applied to a Go struct, but now a Module struct:
+
+ type Module struct {
+ Path string // module path
+ Version string // module version
+ Versions []string // available module versions (with -versions)
+ Replace *Module // replaced by this module
+ Time *time.Time // time version was created
+ Update *Module // available update, if any (with -u)
+ Main bool // is this the main module?
+ Indirect bool // is this module only an indirect dependency of main module?
+ Dir string // directory holding files for this module, if any
+ Error *ModuleError // error loading module
+ }
+
+ type ModuleError struct {
+ Err string // the error itself
+ }
+
+The default output is to print the module path and then
+information about the version and replacement if any.
+For example, 'go list -m all' might print:
+
+ my/main/module
+ golang.org/x/text v0.3.0 => /tmp/text
+ rsc.io/pdf v0.1.1
+
+The Module struct has a String method that formats this
+line of output, so that the default format is equivalent
+to -f '{{.String}}'.
+
+Note that when a module has been replaced, its Replace field
+describes the replacement module, and its Dir field is set to
+the replacement's source code, if present. (That is, if Replace
+is non-nil, then Dir is set to Replace.Dir, with no access to
+the replaced source code.)
+
+The -u flag adds information about available upgrades.
+When the latest version of a given module is newer than
+the current one, list -u sets the Module's Update field
+to information about the newer module.
+The Module's String method indicates an available upgrade by
+formatting the newer version in brackets after the current version.
+For example, 'go list -m -u all' might print:
+
+ my/main/module
+ golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text
+ rsc.io/pdf v0.1.1 [v0.1.2]
+
+(For tools, 'go list -m -u -json all' may be more convenient to parse.)
+
+The -versions flag causes list to set the Module's Versions field
+to a list of all known versions of that module, ordered according
+to semantic versioning, earliest to latest. The flag also changes
+the default output format to display the module path followed by the
+space-separated version list.
+
+The arguments to list -m are interpreted as a list of modules, not packages.
+The main module is the module containing the current directory.
+The active modules are the main module and its dependencies.
+With no arguments, list -m shows the main module.
+With arguments, list -m shows the modules specified by the arguments.
+Any of the active modules can be specified by its module path.
+The special pattern "all" specifies all the active modules, first the main
+module and then dependencies sorted by module path.
+A pattern containing "..." specifies the active modules whose
+module paths match the pattern.
+A query of the form path@version specifies the result of that query,
+which is not limited to active modules.
+See 'go help module' for more about module queries.
+
+The template function "module" takes a single string argument
+that must be a module path or query and returns the specified
+module as a Module struct. If an error occurs, the result will
+be a Module struct with a non-nil Error field.
+
For more about build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.
+
+For more about modules, see 'go help modules'.
`,
}
work.AddBuildFlags(CmdList)
}
-var listCgo = CmdList.Flag.Bool("cgo", false, "")
-var listDeps = CmdList.Flag.Bool("deps", false, "")
-var listE = CmdList.Flag.Bool("e", false, "")
-var listExport = CmdList.Flag.Bool("export", false, "")
-var listFmt = CmdList.Flag.String("f", "{{.ImportPath}}", "")
-var listJson = CmdList.Flag.Bool("json", false, "")
-var listTest = CmdList.Flag.Bool("test", false, "")
+var (
+ listCgo = CmdList.Flag.Bool("cgo", false, "")
+ listDeps = CmdList.Flag.Bool("deps", false, "")
+ listE = CmdList.Flag.Bool("e", false, "")
+ listExport = CmdList.Flag.Bool("export", false, "")
+ listFmt = CmdList.Flag.String("f", "", "")
+ listJson = CmdList.Flag.Bool("json", false, "")
+ listM = CmdList.Flag.Bool("m", false, "")
+ listU = CmdList.Flag.Bool("u", false, "")
+ listTest = CmdList.Flag.Bool("test", false, "")
+ listVersions = CmdList.Flag.Bool("versions", false, "")
+)
+
var nl = []byte{'\n'}
func runList(cmd *base.Command, args []string) {
out := newTrackingWriter(os.Stdout)
defer out.w.Flush()
- var do func(*load.PackagePublic)
+ if *listFmt == "" {
+ if *listM {
+ *listFmt = "{{.String}}"
+ if *listVersions {
+ *listFmt = `{{.Path}}{{range .Versions}} {{.}}{{end}}`
+ }
+ } else {
+ *listFmt = "{{.ImportPath}}"
+ }
+ }
+
+ var do func(interface{})
if *listJson {
- do = func(p *load.PackagePublic) {
- b, err := json.MarshalIndent(p, "", "\t")
+ do = func(x interface{}) {
+ b, err := json.MarshalIndent(x, "", "\t")
if err != nil {
out.Flush()
base.Fatalf("%s", err)
fm := template.FuncMap{
"join": strings.Join,
"context": context,
+ "module": modload.ModuleInfo,
}
tmpl, err := template.New("main").Funcs(fm).Parse(*listFmt)
if err != nil {
base.Fatalf("%s", err)
}
- do = func(p *load.PackagePublic) {
- if err := tmpl.Execute(out, p); err != nil {
+ do = func(x interface{}) {
+ if err := tmpl.Execute(out, x); err != nil {
out.Flush()
base.Fatalf("%s", err)
}
}
}
+ if *listM {
+ // Module mode.
+ if *listCgo {
+ base.Fatalf("go list -cgo cannot be used with -m")
+ }
+ if *listDeps {
+ // TODO(rsc): Could make this mean something with -m.
+ base.Fatalf("go list -deps cannot be used with -m")
+ }
+ if *listExport {
+ base.Fatalf("go list -export cannot be used with -m")
+ }
+ if *listTest {
+ base.Fatalf("go list -test cannot be used with -m")
+ }
+
+ if modload.Init(); !modload.Enabled() {
+ base.Fatalf("go list -m: not using modules")
+ }
+ modload.LoadBuildList()
+
+ mods := modload.ListModules(args, *listU, *listVersions)
+ if !*listE {
+ for _, m := range mods {
+ if m.Error != nil {
+ base.Errorf("go list -m %s: %v", m.Path, m.Error.Err)
+ }
+ }
+ base.ExitIfErrors()
+ }
+ for _, m := range mods {
+ do(m)
+ }
+ return
+ }
+
+ // Package mode (not -m).
+ if *listU {
+ base.Fatalf("go list -u can only be used with -m")
+ }
+ if *listVersions {
+ base.Fatalf("go list -versions can only be used with -m")
+ }
+
var pkgs []*load.Package
if *listE {
pkgs = load.PackagesAndErrors(args)
import (
"cmd/go/internal/base"
+ "cmd/go/internal/search"
"cmd/go/internal/str"
"fmt"
"strings"
return flags
}
-var cmdlineMatchers []func(*Package) bool
+var (
+ cmdlineMatchers []func(*Package) bool
+ cmdlineMatcherLiterals []func(*Package) bool
+)
// SetCmdlinePatterns records the set of patterns given on the command line,
// for use by the PerPackageFlags.
args = []string{"."}
}
cmdlineMatchers = nil // allow reset for testing
+ cmdlineMatcherLiterals = nil
for _, arg := range args {
cmdlineMatchers = append(cmdlineMatchers, MatchPackage(arg, cwd))
}
+ for _, arg := range args {
+ if !strings.Contains(arg, "...") && !search.IsMetaPackage(arg) {
+ cmdlineMatcherLiterals = append(cmdlineMatcherLiterals, MatchPackage(arg, cwd))
+ }
+ }
}
// isCmdlinePkg reports whether p is a package listed on the command line.
}
return false
}
+
+// isCmdlinePkgLiteral reports whether p is a package listed as
+// a literal package argument on the command line
+// (as opposed to being the result of expanding a wildcard).
+func isCmdlinePkgLiteral(p *Package) bool {
+ for _, m := range cmdlineMatcherLiterals {
+ if m(p) {
+ return true
+ }
+ }
+ return false
+}
"cmd/go/internal/modinfo"
"cmd/go/internal/search"
"cmd/go/internal/str"
- "cmd/go/internal/vgo"
+)
+
+var (
+ // module hooks; nil if module use is disabled
+ ModBinDir func() string // return effective bin directory
+ ModLookup func(parentPath, path string) (dir, realPath string, err error) // lookup effective meaning of import
+ ModPackageModuleInfo func(path string) *modinfo.ModulePublic // return module info for Package struct
+ ModImportPaths func(args []string) []string // expand import paths
+ ModPackageBuildInfo func(main string, deps []string) string // return module info to embed in binary
+ ModInfoProg func(info string) []byte // wrap module info in .go code for binary
+ ModImportFromFiles func([]string) // update go.mod to add modules for imports in these files
)
var IgnoreImports bool // control whether we ignore imports in packages
// 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
- ImportComment string `json:",omitempty"` // path in import comment on package statement
- Name string `json:",omitempty"` // package name
- Doc string `json:",omitempty"` // package documentation string
- Target string `json:",omitempty"` // installed target for this package (may be executable)
- Shlib string `json:",omitempty"` // the shared library that contains this package (only set when -linkshared)
- 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?
- Root string `json:",omitempty"` // Go root or Go path dir containing this package
- ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
- BinaryOnly bool `json:",omitempty"` // package cannot be recompiled
- ForTest string `json:",omitempty"` // package is only for use in named test
- DepOnly bool `json:",omitempty"` // package is only as a dependency, not explicitly listed
- Export string `json:",omitempty"` // file containing export data (set by go list -export)
+ Dir string `json:",omitempty"` // directory containing package sources
+ ImportPath string `json:",omitempty"` // import path of package in dir
+ ImportComment string `json:",omitempty"` // path in import comment on package statement
+ Name string `json:",omitempty"` // package name
+ Doc string `json:",omitempty"` // package documentation string
+ Target string `json:",omitempty"` // installed target for this package (may be executable)
+ Shlib string `json:",omitempty"` // the shared library that contains this package (only set when -linkshared)
+ 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?
+ Root string `json:",omitempty"` // Go root or Go path dir containing this package
+ ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
+ BinaryOnly bool `json:",omitempty"` // package cannot be recompiled
+ ForTest string `json:",omitempty"` // package is only for use in named test
+ DepOnly bool `json:",omitempty"` // package is only as a dependency, not explicitly listed
+ Export string `json:",omitempty"` // file containing export data (set by go list -export)
+ Module *modinfo.ModulePublic `json:",omitempty"` // info about package's module, if any
// Stale and StaleReason remain here *only* for the list command.
// They are only initialized in preparation for list execution.
TestImports []string `json:",omitempty"` // imports from TestGoFiles
XTestGoFiles []string `json:",omitempty"` // _test.go files outside package
XTestImports []string `json:",omitempty"` // imports from XTestGoFiles
-
- Module *modinfo.ModulePublic `json:",omitempty"` // info about package module
}
// AllFiles returns the names of all the files considered for the package.
type PackageInternal struct {
// Unexported fields are not part of the public API.
- Build *build.Package
- Imports []*Package // this package's direct imports
- RawImports []string // this package's original imports as they appear in the text of the program
- ForceLibrary bool // this package is a library (even if named "main")
- CmdlineFiles bool // package built from files listed on command line
- CmdlinePkg bool // package listed on command line
- Local bool // imported via local path (./ or ../)
- LocalPrefix string // interpret ./ and ../ imports relative to this prefix
- ExeName string // desired name for temporary executable
- CoverMode string // preprocess Go source files with the coverage tool in this mode
- CoverVars map[string]*CoverVar // variables created by coverage analysis
- OmitDebug bool // tell linker not to write debug information
- GobinSubdir bool // install target would be subdir of GOBIN
- BuildInfo string // add this info to package main
- TestmainGo *[]byte // content for _testmain.go
+ Build *build.Package
+ Imports []*Package // this package's direct imports
+ RawImports []string // this package's original imports as they appear in the text of the program
+ ForceLibrary bool // this package is a library (even if named "main")
+ CmdlineFiles bool // package built from files listed on command line
+ CmdlinePkg bool // package listed on command line
+ CmdlinePkgLiteral bool // package listed as literal on command line (not via wildcard)
+ Local bool // imported via local path (./ or ../)
+ LocalPrefix string // interpret ./ and ../ imports relative to this prefix
+ ExeName string // desired name for temporary executable
+ CoverMode string // preprocess Go source files with the coverage tool in this mode
+ CoverVars map[string]*CoverVar // variables created by coverage analysis
+ OmitDebug bool // tell linker not to write debug information
+ GobinSubdir bool // install target would be subdir of GOBIN
+ BuildInfo string // add this info to package main
+ TestmainGo *[]byte // content for _testmain.go
Asmflags []string // -asmflags for this package
Gcflags []string // -gcflags for this package
stk.Push(path)
defer stk.Pop()
+ if strings.HasPrefix(path, "mod/") {
+ // Paths beginning with "mod/" might accidentally
+ // look in the module cache directory tree in $GOPATH/src/mod/.
+ // This prefix is owned by the Go core for possible use in the
+ // standard library (since it does not begin with a domain name),
+ // so it's OK to disallow entirely.
+ return &Package{
+ PackagePublic: PackagePublic{
+ ImportPath: path,
+ Error: &PackageError{
+ ImportStack: stk.Copy(),
+ Err: fmt.Sprintf("disallowed import path %q", path),
+ },
+ },
+ }
+ }
+
+ if strings.Contains(path, "@") {
+ var text string
+ if cfg.ModulesEnabled {
+ text = "can only use path@version syntax with 'go get'"
+ } else {
+ text = "cannot use path@version syntax in GOPATH mode"
+ }
+ return &Package{
+ PackagePublic: PackagePublic{
+ ImportPath: path,
+ Error: &PackageError{
+ ImportStack: stk.Copy(),
+ Err: text,
+ },
+ },
+ }
+ }
+
// Determine canonical identifier for this package.
// For a local import the identifier is the pseudo-import path
// we create from the full directory to the package.
importPath := path
origPath := path
isLocal := build.IsLocalImport(path)
- var vgoDir string
- var vgoErr error
+ var modDir string
+ var modErr error
if isLocal {
importPath = dirToImportPath(filepath.Join(srcDir, path))
- } else if vgo.Enabled() {
+ } else if cfg.ModulesEnabled {
parentPath := ""
if parent != nil {
parentPath = parent.ImportPath
}
var p string
- vgoDir, p, vgoErr = vgo.Lookup(parentPath, path)
- if vgoErr == nil {
+ modDir, p, modErr = ModLookup(parentPath, path)
+ if modErr == nil {
importPath = p
}
} else if mode&ResolveImport != 0 {
// in order to return partial information.
var bp *build.Package
var err error
- if vgoDir != "" {
- bp, err = cfg.BuildContext.ImportDir(vgoDir, 0)
- } else if vgoErr != nil {
+ if modDir != "" {
+ bp, err = cfg.BuildContext.ImportDir(modDir, 0)
+ } else if modErr != nil {
bp = new(build.Package)
- err = fmt.Errorf("unknown import path %q: %v", importPath, vgoErr)
+ err = fmt.Errorf("unknown import path %q: %v", importPath, modErr)
+ } else if cfg.ModulesEnabled && path != "unsafe" {
+ bp = new(build.Package)
+ err = fmt.Errorf("unknown import path %q: internal error: module loader did not resolve import", importPath)
} else {
buildMode := build.ImportComment
if mode&ResolveImport == 0 || path != origPath {
bp.ImportPath = importPath
if cfg.GOBIN != "" {
bp.BinDir = cfg.GOBIN
- } else if vgo.Enabled() {
- bp.BinDir = vgo.BinDir()
+ } else if cfg.ModulesEnabled {
+ bp.BinDir = ModBinDir()
}
- if vgoDir == "" && err == nil && !isLocal && bp.ImportComment != "" && bp.ImportComment != path &&
+ if modDir == "" && err == nil && !isLocal && bp.ImportComment != "" && bp.ImportComment != path &&
!strings.Contains(path, "/vendor/") && !strings.HasPrefix(path, "vendor/") {
err = fmt.Errorf("code in directory %s expects import %q", bp.Dir, bp.ImportComment)
}
p = setErrorPos(p, importPos)
}
- if vgoDir == "" && origPath != cleanImport(origPath) {
+ if modDir == "" && origPath != cleanImport(origPath) {
p.Error = &PackageError{
ImportStack: stk.Copy(),
Err: fmt.Sprintf("non-canonical import path: %q should be %q", origPath, pathpkg.Clean(origPath)),
// There are two different resolutions applied.
// First, there is Go 1.5 vendoring (golang.org/s/go15vendor).
// If vendor expansion doesn't trigger, then the path is also subject to
-// Go 1.11 vgo legacy conversion (golang.org/issue/25069).
+// Go 1.11 module legacy conversion (golang.org/issue/25069).
func ResolveImportPath(parent *Package, path string) (found string) {
- if vgo.Enabled() {
+ if cfg.ModulesEnabled {
parentPath := ""
if parent != nil {
parentPath = parent.ImportPath
}
- if _, p, e := vgo.Lookup(parentPath, path); e == nil {
+ if _, p, e := ModLookup(parentPath, path); e == nil {
return p
}
return path
// of fmt before it attempts to load as a command-line argument.
// Because loads are cached, the later load will be a no-op,
// so it is important that the first load can fill in CmdlinePkg correctly.
- // Hence the call to an explicit matching check here.
+ // Hence the call to a separate matching check here.
p.Internal.CmdlinePkg = isCmdlinePkg(p)
+ p.Internal.CmdlinePkgLiteral = isCmdlinePkgLiteral(p)
p.Internal.Asmflags = BuildAsmflags.For(p)
p.Internal.Gcflags = BuildGcflags.For(p)
// Install cross-compiled binaries to subdirectories of bin.
elem = full
}
- if p.Internal.Build.BinDir == "" && vgo.Enabled() {
- p.Internal.Build.BinDir = vgo.BinDir()
+ if p.Internal.Build.BinDir == "" && cfg.ModulesEnabled {
+ p.Internal.Build.BinDir = ModBinDir()
}
if p.Internal.Build.BinDir != "" {
// Install to GOBIN or bin of GOPATH entry.
return
}
- if vgo.Enabled() {
- p.Module = vgo.PackageModuleInfo(p.ImportPath)
+ if cfg.ModulesEnabled {
+ p.Module = ModPackageModuleInfo(p.ImportPath)
if p.Name == "main" {
- p.Internal.BuildInfo = vgo.PackageBuildInfo(p.ImportPath, p.Deps)
+ p.Internal.BuildInfo = ModPackageBuildInfo(p.ImportPath, p.Deps)
}
}
}
if cmdlineMatchers == nil {
SetCmdlinePatterns(search.CleanImportPaths(args))
}
- return vgo.ImportPaths(args)
+ if cfg.ModulesEnabled {
+ return ModImportPaths(args)
+ }
+ return search.ImportPaths(args)
}
func ImportPathsForGoGet(args []string) []string {
}
ctxt.ReadDir = func(string) ([]os.FileInfo, error) { return dirent, nil }
- vgo.AddImports(gofiles)
+ if cfg.ModulesEnabled {
+ ModImportFromFiles(gofiles)
+ }
var err error
if dir == "" {
}
if cfg.GOBIN != "" {
pkg.Target = filepath.Join(cfg.GOBIN, exe)
- } else if vgo.Enabled() {
- pkg.Target = filepath.Join(vgo.BinDir(), exe)
+ } else if cfg.ModulesEnabled {
+ pkg.Target = filepath.Join(ModBinDir(), exe)
}
}
// MatchPackage(pattern, cwd)(p) reports whether package p matches pattern in the working directory cwd.
func MatchPackage(pattern, cwd string) func(*Package) bool {
switch {
- case strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == "..":
+ case search.IsRelativePath(pattern):
// Split pattern into leading pattern-free directory path
// (including all . and .. elements) and the final pattern.
var dir string
--- /dev/null
+// Copyright 2018 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 modcmd implements the ``go mod'' command.
+package modcmd
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modfile"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/module"
+ "cmd/go/internal/par"
+)
+
+var CmdMod = &base.Command{
+ UsageLine: "mod [-v] [maintenance flags]",
+ Short: "module maintenance",
+ Long: `
+Mod performs module maintenance operations as specified by the
+following flags, which may be combined.
+
+The -v flag enables additional output about operations performed.
+
+The first group of operations provide low-level editing operations
+for manipulating go.mod from the command line or in scripts or
+other tools. They read only go.mod itself; they do not look up any
+information about the modules involved.
+
+The -init flag initializes and writes a new go.mod to the current directory,
+in effect creating a new module rooted at the current directory.
+The file go.mod must not already exist.
+If possible, mod will guess the module path from import comments
+(see 'go help importpath') or from version control configuration.
+To override this guess, use the -module flag.
+(Without -init, mod applies to the current module.)
+
+The -module flag changes (or, with -init, sets) the module's path
+(the go.mod file's module line).
+
+The -require=path@version and -droprequire=path flags
+add and drop a requirement on the given module path and version.
+Note that -require overrides any existing requirements on path.
+These flags are mainly for tools that understand the module graph.
+Users should prefer 'go get path@version' or 'go get path@none',
+which make other go.mod adjustments as needed to satisfy
+constraints imposed by other modules.
+
+The -exclude=path@version and -dropexclude=path@version flags
+add and drop an exclusion for the given module path and version.
+Note that -exclude=path@version is a no-op if that exclusion already exists.
+
+The -replace=old@v=new@w and -dropreplace=old@v flags
+add and drop a replacement of the given module path and version pair.
+If the @v in old@v is omitted, the replacement applies to all versions
+with the old module path. If the @v in new@v is omitted, the
+new path should be a directory on the local system, not a module path.
+Note that -replace overrides any existing replacements for old@v.
+
+These editing flags (-require, -droprequire, -exclude, -dropexclude,
+-replace, and -dropreplace) may be repeated.
+
+The -fmt flag reformats the go.mod file without making other changes.
+This reformatting is also implied by any other modifications that use or
+rewrite the go.mod file. The only time this flag is needed is if no other
+flags are specified, as in 'go mod -fmt'.
+
+The -graph flag prints the module requirement graph (with replacements applied)
+in text form. Each line in the output has two space-separated fields: a module
+and one of its requirements. Each module is identified as a string of the form
+path@version, except for the main module, which has no @version suffix.
+
+The -json flag prints the go.mod file in JSON format corresponding to these
+Go types:
+
+ type Module struct {
+ Path string
+ Version string
+ }
+
+ type GoMod struct {
+ Module Module
+ Require []Require
+ Exclude []Module
+ Replace []Replace
+ }
+
+ type Require struct {
+ Path string
+ Version string
+ Indirect bool
+ }
+
+ type Replace string {
+ Old Module
+ New Module
+ }
+
+Note that this only describes the go.mod file itself, not other modules
+referred to indirectly. For the full set of modules available to a build,
+use 'go list -m -json all'.
+
+The next group of operations provide higher-level editing and maintenance
+of a module, beyond the go.mod file.
+
+The -packages flag prints a list of packages in the module.
+It only identifies directories containing Go source code;
+it does not check that those directories contain code that builds.
+
+The -fix flag updates go.mod to use canonical version identifiers and
+to be semantically consistent. For example, consider this go.mod file:
+
+ module M
+
+ require (
+ A v1
+ B v1.0.0
+ C v1.0.0
+ D v1.2.3
+ E dev
+ )
+
+ exclude D v1.2.3
+
+First, -fix rewrites non-canonical version identifiers to semver form, so
+A's v1 becomes v1.0.0 and E's dev becomes the pseudo-version for the latest
+commit on the dev branch, perhaps v0.0.0-20180523231146-b3f5c0f6e5f1.
+
+Next, -fix updates requirements to respect exclusions, so the requirement
+on the excluded D v1.2.3 is updated to use the next available version of D,
+perhaps D v1.2.4 or D v1.3.0.
+
+Finally, -fix removes redundant or misleading requirements.
+For example, if A v1.0.0 itself requires B v1.2.0 and C v1.0.0,
+then go.mod's requirement of B v1.0.0 is misleading (superseded
+by B's need for v1.2.0), and its requirement of C v1.0.0 is redundant
+(implied by B's need for the same version), so both will be removed.
+
+Although -fix runs the fix-up operation in isolation, the fix-up also
+runs automatically any time a go command uses the module graph,
+to update go.mod to reflect reality. For example, the -sync, -vendor,
+and -verify flags all effectively imply -fix. And because the module
+graph defines the meaning of import statements, any commands
+that load packages—'go build', 'go test', 'go list', and so on—also
+effectively imply 'go mod -fix'.
+
+The -sync flag synchronizes go.mod with the source code in the module.
+It adds any missing modules necessary to build the current module's
+packages and dependencies, and it removes unused modules that
+don't provide any relevant packages.
+
+The -vendor flag resets the module's vendor directory to include all
+packages needed to build and test all the module's packages.
+It does not include any test code for the vendored packages.
+
+The -verify flag checks that the dependencies of the current module,
+which are stored in a local downloaded source cache, have not been
+modified since being downloaded. If all the modules are unmodified,
+-verify prints "all modules verified." Otherwise it reports which
+modules have been changed and causes 'go mod' to exit with a
+non-zero status.
+ `,
+}
+
+var (
+ modV = CmdMod.Flag.Bool("v", false, "")
+
+ modFmt = CmdMod.Flag.Bool("fmt", false, "")
+ modFix = CmdMod.Flag.Bool("fix", false, "")
+ modGraph = CmdMod.Flag.Bool("graph", false, "")
+ modJSON = CmdMod.Flag.Bool("json", false, "")
+ modPackages = CmdMod.Flag.Bool("packages", false, "")
+ modSync = CmdMod.Flag.Bool("sync", false, "")
+ modVendor = CmdMod.Flag.Bool("vendor", false, "")
+ modVerify = CmdMod.Flag.Bool("verify", false, "")
+
+ modEdits []func(*modfile.File) // edits specified in flags
+)
+
+type flagFunc func(string)
+
+func (f flagFunc) String() string { return "" }
+func (f flagFunc) Set(s string) error { f(s); return nil }
+
+func init() {
+ CmdMod.Run = runMod // break init cycle
+
+ CmdMod.Flag.BoolVar(&modload.CmdModInit, "init", modload.CmdModInit, "")
+ CmdMod.Flag.StringVar(&modload.CmdModModule, "module", modload.CmdModModule, "")
+
+ CmdMod.Flag.Var(flagFunc(flagRequire), "require", "")
+ CmdMod.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
+ CmdMod.Flag.Var(flagFunc(flagExclude), "exclude", "")
+ CmdMod.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
+ CmdMod.Flag.Var(flagFunc(flagReplace), "replace", "")
+ CmdMod.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
+
+ base.AddBuildFlagsNX(&CmdMod.Flag)
+}
+
+func runMod(cmd *base.Command, args []string) {
+ if modload.Init(); !modload.Enabled() {
+ base.Fatalf("go mod: cannot use outside module")
+ }
+ if len(args) != 0 {
+ base.Fatalf("go mod: mod takes no arguments")
+ }
+
+ anyFlags :=
+ modload.CmdModInit ||
+ modload.CmdModModule != "" ||
+ *modVendor ||
+ *modVerify ||
+ *modJSON ||
+ *modFmt ||
+ *modFix ||
+ *modGraph ||
+ *modPackages ||
+ *modSync ||
+ len(modEdits) > 0
+
+ if !anyFlags {
+ base.Fatalf("go mod: no flags specified (see 'go help mod').")
+ }
+
+ if modload.CmdModModule != "" {
+ if err := module.CheckPath(modload.CmdModModule); err != nil {
+ base.Fatalf("go mod: invalid -module: %v", err)
+ }
+ }
+
+ if modload.CmdModInit {
+ if _, err := os.Stat("go.mod"); err == nil {
+ base.Fatalf("go mod -init: go.mod already exists")
+ }
+ }
+ modload.InitMod()
+
+ // Syntactic edits.
+
+ modFile := modload.ModFile()
+ if modload.CmdModModule != "" {
+ modFile.AddModuleStmt(modload.CmdModModule)
+ }
+
+ if len(modEdits) > 0 {
+ for _, edit := range modEdits {
+ edit(modFile)
+ }
+ }
+ modFile.SortBlocks()
+ modload.WriteGoMod() // write back syntactic changes
+
+ // Semantic edits.
+
+ needBuildList := *modFix || *modGraph
+
+ if *modSync || *modVendor || needBuildList {
+ var pkgs []string
+ if *modSync || *modVendor {
+ pkgs = modload.LoadALL()
+ } else {
+ modload.LoadBuildList()
+ }
+ if *modSync {
+ // LoadALL already added missing modules.
+ // Remove unused modules.
+ used := map[module.Version]bool{modload.Target: true}
+ for _, pkg := range pkgs {
+ used[modload.PackageModule(pkg)] = true
+ }
+
+ inGoMod := make(map[string]bool)
+ for _, r := range modload.ModFile().Require {
+ inGoMod[r.Mod.Path] = true
+ }
+
+ var keep []module.Version
+ for _, m := range modload.BuildList() {
+ if used[m] {
+ keep = append(keep, m)
+ } else if *modV && inGoMod[m.Path] {
+ fmt.Fprintf(os.Stderr, "unused %s\n", m.Path)
+ }
+ }
+ modload.SetBuildList(keep)
+ }
+ modload.WriteGoMod()
+ if *modVendor {
+ runVendor()
+ }
+ }
+
+ // Read-only queries, processed only after updating go.mod.
+
+ if *modJSON {
+ modPrintJSON()
+ }
+
+ if *modGraph {
+ modPrintGraph()
+ }
+
+ if *modPackages {
+ for _, pkg := range modload.TargetPackages() {
+ fmt.Printf("%s\n", pkg)
+ }
+ }
+
+ if *modVerify {
+ runVerify()
+ }
+}
+
+// parsePathVersion parses -flag=arg expecting arg to be path@version.
+func parsePathVersion(flag, arg string) (path, version string) {
+ i := strings.Index(arg, "@")
+ if i < 0 {
+ base.Fatalf("go mod: -%s=%s: need path@version", flag, arg)
+ }
+ path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
+ if err := module.CheckPath(path); err != nil {
+ base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
+ }
+
+ // We don't call modfile.CheckPathVersion, because that insists
+ // on versions being in semver form, but here we want to allow
+ // versions like "master" or "1234abcdef", which the go command will resolve
+ // the next time it runs (or during -fix).
+ // Even so, we need to make sure the version is a valid token.
+ if modfile.MustQuote(version) {
+ base.Fatalf("go mod: -%s=%s: invalid version %q", flag, arg, version)
+ }
+
+ return path, version
+}
+
+// parsePath parses -flag=arg expecting arg to be path (not path@version).
+func parsePath(flag, arg string) (path string) {
+ if strings.Contains(arg, "@") {
+ base.Fatalf("go mod: -%s=%s: need just path, not path@version", flag, arg)
+ }
+ path = arg
+ if err := module.CheckPath(path); err != nil {
+ base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
+ }
+ return path
+}
+
+// flagRequire implements the -require flag.
+func flagRequire(arg string) {
+ path, version := parsePathVersion("require", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.AddRequire(path, version); err != nil {
+ base.Fatalf("go mod: -require=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropRequire implements the -droprequire flag.
+func flagDropRequire(arg string) {
+ path := parsePath("droprequire", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.DropRequire(path); err != nil {
+ base.Fatalf("go mod: -droprequire=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagExclude implements the -exclude flag.
+func flagExclude(arg string) {
+ path, version := parsePathVersion("exclude", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.AddExclude(path, version); err != nil {
+ base.Fatalf("go mod: -exclude=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropExclude implements the -dropexclude flag.
+func flagDropExclude(arg string) {
+ path, version := parsePathVersion("dropexclude", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.DropExclude(path, version); err != nil {
+ base.Fatalf("go mod: -dropexclude=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagReplace implements the -replace flag.
+func flagReplace(arg string) {
+ var i int
+ if i = strings.Index(arg, "="); i < 0 {
+ base.Fatalf("go mod: -replace=%s: need old@v=new[@v] (missing =)", arg)
+ }
+ old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+2:])
+ if strings.HasPrefix(new, ">") {
+ base.Fatalf("go mod: -replace=%s: separator between old and new is =, not =>", arg)
+ }
+ var oldPath, oldVersion string
+ if i = strings.Index(old, "@"); i < 0 {
+ oldPath = old
+ } else {
+ oldPath, oldVersion = strings.TrimSpace(old[:i]), strings.TrimSpace(old[i+1:])
+ }
+ if err := module.CheckPath(oldPath); err != nil {
+ base.Fatalf("go mod: -replace=%s: invalid old path: %v", arg, err)
+ }
+ if oldPath != old && modfile.MustQuote(oldVersion) {
+ base.Fatalf("go mod: -replace=%s: invalid old version %q", arg, oldVersion)
+ }
+ var newPath, newVersion string
+ if i = strings.Index(new, "@"); i >= 0 {
+ newPath, newVersion = strings.TrimSpace(new[:i]), strings.TrimSpace(new[i+1:])
+ if err := module.CheckPath(newPath); err != nil {
+ base.Fatalf("go mod: -replace=%s: invalid new path: %v", arg, err)
+ }
+ if modfile.MustQuote(newVersion) {
+ base.Fatalf("go mod: -replace=%s: invalid new version %q", arg, newVersion)
+ }
+ } else {
+ if !modfile.IsDirectoryPath(new) {
+ base.Fatalf("go mod: -replace=%s: unversioned new path must be local directory", arg)
+ }
+ newPath = new
+ }
+
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
+ base.Fatalf("go mod: -replace=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropReplace implements the -dropreplace flag.
+func flagDropReplace(arg string) {
+ path, version := parsePathVersion("dropreplace", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.DropReplace(path, version); err != nil {
+ base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+ }
+ })
+}
+
+// fileJSON is the -json output data structure.
+type fileJSON struct {
+ Module module.Version
+ Require []requireJSON
+ Exclude []module.Version
+ Replace []replaceJSON
+}
+
+type requireJSON struct {
+ Path string
+ Version string `json:",omitempty"`
+ Indirect bool `json:",omitempty"`
+}
+
+type replaceJSON struct {
+ Old module.Version
+ New module.Version
+}
+
+// modPrintJSON prints the -json output.
+func modPrintJSON() {
+ modFile := modload.ModFile()
+
+ var f fileJSON
+ f.Module = modFile.Module.Mod
+ for _, r := range modFile.Require {
+ f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect})
+ }
+ for _, x := range modFile.Exclude {
+ f.Exclude = append(f.Exclude, x.Mod)
+ }
+ for _, r := range modFile.Replace {
+ f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
+ }
+ data, err := json.MarshalIndent(&f, "", "\t")
+ if err != nil {
+ base.Fatalf("go mod -json: internal error: %v", err)
+ }
+ data = append(data, '\n')
+ os.Stdout.Write(data)
+}
+
+// modPrintGraph prints the -graph output.
+func modPrintGraph() {
+ reqs := modload.Reqs()
+
+ format := func(m module.Version) string {
+ if m.Version == "" {
+ return m.Path
+ }
+ return m.Path + "@" + m.Version
+ }
+
+ // Note: using par.Work only to manage work queue.
+ // No parallelism here, so no locking.
+ var out []string
+ var deps int // index in out where deps start
+ var work par.Work
+ work.Add(modload.Target)
+ work.Do(1, func(item interface{}) {
+ m := item.(module.Version)
+ list, _ := reqs.Required(m)
+ for _, r := range list {
+ work.Add(r)
+ out = append(out, format(m)+" "+format(r)+"\n")
+ }
+ if m == modload.Target {
+ deps = len(out)
+ }
+ })
+
+ sort.Slice(out[deps:], func(i, j int) bool {
+ return out[deps+i][0] < out[deps+j][0]
+ })
+
+ w := bufio.NewWriter(os.Stdout)
+ for _, line := range out {
+ w.WriteString(line)
+ }
+ w.Flush()
+}
--- /dev/null
+// Copyright 2018 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 modcmd
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/module"
+)
+
+func runVendor() {
+ pkgs := modload.LoadVendor()
+
+ vdir := filepath.Join(modload.ModRoot, "vendor")
+ if err := os.RemoveAll(vdir); err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+
+ modpkgs := make(map[module.Version][]string)
+ for _, pkg := range pkgs {
+ m := modload.PackageModule(pkg)
+ if m == modload.Target {
+ continue
+ }
+ modpkgs[m] = append(modpkgs[m], pkg)
+ }
+
+ var buf bytes.Buffer
+ for _, m := range modload.BuildList()[1:] {
+ if pkgs := modpkgs[m]; len(pkgs) > 0 {
+ repl := ""
+ if r := modload.Replacement(m); r.Path != "" {
+ repl = " => " + r.Path
+ if r.Version != "" {
+ repl += " " + r.Version
+ }
+ }
+ fmt.Fprintf(&buf, "# %s %s%s\n", m.Path, m.Version, repl)
+ if *modV {
+ fmt.Fprintf(os.Stderr, "# %s %s%s\n", m.Path, m.Version, repl)
+ }
+ for _, pkg := range pkgs {
+ fmt.Fprintf(&buf, "%s\n", pkg)
+ if *modV {
+ fmt.Fprintf(os.Stderr, "%s\n", pkg)
+ }
+ vendorPkg(vdir, pkg)
+ }
+ }
+ }
+ if buf.Len() == 0 {
+ fmt.Fprintf(os.Stderr, "go: no dependencies to vendor\n")
+ return
+ }
+ if err := ioutil.WriteFile(filepath.Join(vdir, "modules.txt"), buf.Bytes(), 0666); err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+}
+
+func vendorPkg(vdir, pkg string) {
+ realPath := modload.ImportMap(pkg)
+ if realPath != pkg && modload.ImportMap(realPath) != "" {
+ fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
+ }
+
+ dst := filepath.Join(vdir, pkg)
+ src := modload.PackageDir(realPath)
+ if src == "" {
+ fmt.Fprintf(os.Stderr, "internal error: no pkg for %s -> %s\n", pkg, realPath)
+ }
+ copyDir(dst, src, matchNonTest)
+ if m := modload.PackageModule(realPath); m.Path != "" {
+ copyMetadata(m.Path, realPath, dst, src)
+ }
+}
+
+type metakey struct {
+ modPath string
+ dst string
+}
+
+var copiedMetadata = make(map[metakey]bool)
+
+// copyMetadata copies metadata files from parents of src to parents of dst,
+// stopping after processing the src parent for modPath.
+func copyMetadata(modPath, pkg, dst, src string) {
+ for parent := 0; ; parent++ {
+ if copiedMetadata[metakey{modPath, dst}] {
+ break
+ }
+ copiedMetadata[metakey{modPath, dst}] = true
+ if parent > 0 {
+ copyDir(dst, src, matchMetadata)
+ }
+ if modPath == pkg {
+ break
+ }
+ pkg = filepath.Dir(pkg)
+ dst = filepath.Dir(dst)
+ src = filepath.Dir(src)
+ }
+}
+
+// metaPrefixes is the list of metadata file prefixes.
+// Vendoring copies metadata files from parents of copied directories.
+// Note that this list could be arbitrarily extended, and it is longer
+// in other tools (such as godep or dep). By using this limited set of
+// prefixes and also insisting on capitalized file names, we are trying
+// to nudge people toward more agreement on the naming
+// and also trying to avoid false positives.
+var metaPrefixes = []string{
+ "AUTHORS",
+ "CONTRIBUTORS",
+ "COPYLEFT",
+ "COPYING",
+ "COPYRIGHT",
+ "LEGAL",
+ "LICENSE",
+ "NOTICE",
+ "PATENTS",
+}
+
+// matchMetadata reports whether info is a metadata file.
+func matchMetadata(info os.FileInfo) bool {
+ name := info.Name()
+ for _, p := range metaPrefixes {
+ if strings.HasPrefix(name, p) {
+ return true
+ }
+ }
+ return false
+}
+
+// matchNonTest reports whether info is any non-test file (including non-Go files).
+func matchNonTest(info os.FileInfo) bool {
+ return !strings.HasSuffix(info.Name(), "_test.go")
+}
+
+// copyDir copies all regular files satisfying match(info) from src to dst.
+func copyDir(dst, src string, match func(os.FileInfo) bool) {
+ files, err := ioutil.ReadDir(src)
+ if err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+ if err := os.MkdirAll(dst, 0777); err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+ for _, file := range files {
+ if file.IsDir() || !file.Mode().IsRegular() || !match(file) {
+ continue
+ }
+ r, err := os.Open(filepath.Join(src, file.Name()))
+ if err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+ w, err := os.Create(filepath.Join(dst, file.Name()))
+ if err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+ if _, err := io.Copy(w, r); err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+ r.Close()
+ if err := w.Close(); err != nil {
+ base.Fatalf("go vendor: %v", err)
+ }
+ }
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package vgo
+package modcmd
import (
"bytes"
"cmd/go/internal/base"
"cmd/go/internal/dirhash"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modload"
"cmd/go/internal/module"
)
-var CmdVerify = &base.Command{
- UsageLine: "verify",
- Run: runVerify,
- Short: "verify downloaded modules against expected hashes",
- Long: `
-Verify checks that the dependencies of the current module,
-which are stored in a local downloaded source cache,
-have not been modified since being downloaded.
-
-If all the modules are unmodified, verify prints
-
- all modules verified
-
-and exits successfully (status 0). Otherwise, verify reports
-which modules have been changed and exits with a non-zero status.
- `,
-}
-
-func runVerify(cmd *base.Command, args []string) {
- if Init(); !Enabled() {
- base.Fatalf("vgo verify: cannot use outside module")
- }
- if len(args) != 0 {
- // TODO: take arguments
- base.Fatalf("vgo verify: verify takes no arguments")
- }
-
- // Make go.mod consistent but don't load any packages.
- InitMod()
- iterate(func(*loader) {})
- writeGoMod()
-
+func runVerify() {
ok := true
- for _, mod := range buildList[1:] {
+ for _, mod := range modload.LoadBuildList()[1:] {
ok = verifyMod(mod) && ok
}
if ok {
func verifyMod(mod module.Version) bool {
ok := true
- zip := filepath.Join(srcV, "cache", mod.Path, "/@v/", mod.Version+".zip")
+ zip := filepath.Join(modfetch.SrcMod, "cache/download", mod.Path, "/@v/", mod.Version+".zip")
_, zipErr := os.Stat(zip)
- dir := filepath.Join(srcV, mod.Path+"@"+mod.Version)
+ dir := filepath.Join(modfetch.SrcMod, mod.Path+"@"+mod.Version)
_, dirErr := os.Stat(dir)
data, err := ioutil.ReadFile(zip + "hash")
if err != nil {
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package modfetch
+package modconv
import (
"fmt"
"os"
"sort"
"strings"
+ "sync"
- "cmd/go/internal/modconv"
+ "cmd/go/internal/base"
+ "cmd/go/internal/modfetch"
"cmd/go/internal/modfile"
+ "cmd/go/internal/module"
+ "cmd/go/internal/par"
"cmd/go/internal/semver"
)
if i >= 0 {
j = strings.LastIndex(file[:i], "/")
}
- convert := modconv.Converters[file[i+1:]]
+ convert := Converters[file[i+1:]]
if convert == nil && j != -2 {
- convert = modconv.Converters[file[j+1:]]
+ convert = Converters[file[j+1:]]
}
if convert == nil {
return fmt.Errorf("unknown legacy config file %s", file)
}
- require, err := convert(file, data)
+ mf, err := convert(file, data)
if err != nil {
return fmt.Errorf("parsing %s: %v", file, err)
}
// Convert requirements block, which may use raw SHA1 hashes as versions,
// to valid semver requirement list, respecting major versions.
- need := make(map[string]string)
- for _, r := range require {
- if r.Path == "" {
+ var work par.Work
+ for _, r := range mf.Require {
+ m := r.Mod
+ if m.Path == "" {
continue
}
+ work.Add(r.Mod)
+ }
- // TODO: Something better here.
- if strings.HasPrefix(r.Path, "github.com/") || strings.HasPrefix(r.Path, "golang.org/x/") {
- f := strings.Split(r.Path, "/")
- if len(f) > 3 {
- r.Path = strings.Join(f[:3], "/")
- }
- }
-
- repo, err := Lookup(r.Path)
+ var (
+ mu sync.Mutex
+ need = make(map[string]string)
+ )
+ work.Do(10, func(item interface{}) {
+ r := item.(module.Version)
+ repo, info, err := modfetch.ImportRepoRev(r.Path, r.Version)
if err != nil {
- fmt.Fprintf(os.Stderr, "vgo: lookup %s: %v\n", r.Path, err)
- continue
- }
- info, err := repo.Stat(r.Version)
- if err != nil {
- fmt.Fprintf(os.Stderr, "vgo: stat %s@%s: %v\n", r.Path, r.Version, err)
- continue
+ fmt.Fprintf(os.Stderr, "go: converting %s: stat %s@%s: %v\n", base.ShortPath(file), r.Path, r.Version, err)
+ return
}
+ mu.Lock()
path := repo.ModulePath()
need[path] = semver.Max(need[path], info.Version)
- }
+ mu.Unlock()
+ })
var paths []string
for path := range need {
}
sort.Strings(paths)
for _, path := range paths {
- f.AddRequire(path, need[path])
+ f.AddNewRequire(path, need[path], false)
}
+ for _, r := range mf.Replace {
+ err := f.AddReplace(r.Old.Path, r.Old.Version, r.New.Path, r.New.Version)
+ if err != nil {
+ return fmt.Errorf("add replace: %v", err)
+ }
+ }
+ f.Cleanup()
return nil
}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package modfetch
+package modconv
import (
"bytes"
+ "fmt"
"internal/testenv"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
"strings"
"testing"
"cmd/go/internal/cfg"
- "cmd/go/internal/modconv"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modfetch/codehost"
"cmd/go/internal/modfile"
+ "cmd/go/internal/module"
)
+func TestMain(m *testing.M) {
+ os.Exit(testMain(m))
+}
+
+func testMain(m *testing.M) int {
+ if _, err := exec.LookPath("git"); err != nil {
+ fmt.Fprintln(os.Stderr, "skipping because git binary not found")
+ fmt.Println("PASS")
+ return 0
+ }
+
+ dir, err := ioutil.TempDir("", "modconv-test-")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ modfetch.SrcMod = filepath.Join(dir, "src/mod")
+ codehost.WorkRoot = filepath.Join(dir, "codework")
+
+ return m.Run()
+}
+
func TestConvertLegacyConfig(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
require (
github.com/AdRoll/goamz v0.0.0-20150130162828-d3664b76d905
github.com/MSOpenTech/azure-sdk-for-go v0.0.0-20150323223030-d90753bcad2e
- github.com/Sirupsen/logrus v0.0.0-20150409230825-55eb11d21d2a
+ github.com/Sirupsen/logrus v0.7.3
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b
github.com/bugsnag/panicwrap v0.0.0-20141110184334-e5f9854865b9
if err != nil {
t.Fatal(err)
}
- repo, err := Lookup(tt.path)
- if err != nil {
- t.Fatal(err)
- }
- out, err := repo.GoMod(tt.vers)
+ dir, err := modfetch.Download(module.Version{Path: tt.path, Version: tt.vers})
if err != nil {
t.Fatal(err)
}
- prefix := modconv.Prefix + "\n"
- if !bytes.HasPrefix(out, []byte(prefix)) {
- t.Fatalf("go.mod missing prefix %q:\n%s", prefix, out)
- }
- out = out[len(prefix):]
- if !bytes.Equal(out, want) {
- t.Fatalf("final go.mod:\n%s\n\nwant:\n%s", out, want)
+
+ for name := range Converters {
+ file := filepath.Join(dir, name)
+ data, err := ioutil.ReadFile(file)
+ if err == nil {
+ f := new(modfile.File)
+ f.AddModuleStmt(tt.path)
+ if err := ConvertLegacyConfig(f, filepath.ToSlash(file), data); err != nil {
+ t.Fatal(err)
+ }
+ out, err := f.Format()
+ if err != nil {
+ t.Fatalf("format after conversion: %v", err)
+ }
+ if !bytes.Equal(out, want) {
+ t.Fatalf("final go.mod:\n%s\n\nwant:\n%s", out, want)
+ }
+ return
+ }
}
+ t.Fatalf("no converter found for %s@%s", tt.path, tt.vers)
})
}
}
"strconv"
"strings"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
"cmd/go/internal/semver"
)
-func ParseGopkgLock(file string, data []byte) ([]module.Version, error) {
+func ParseGopkgLock(file string, data []byte) (*modfile.File, error) {
+ mf := new(modfile.File)
var list []module.Version
var r *module.Version
for lineno, line := range strings.Split(string(data), "\n") {
if r.Path == "" || r.Version == "" {
return nil, fmt.Errorf("%s: empty [[projects]] stanza (%s)", file, r.Path)
}
+ mf.Require = append(mf.Require, &modfile.Require{Mod: r})
}
- return list, nil
+ return mf, nil
}
package modconv
import (
- "cmd/go/internal/module"
"strings"
+
+ "cmd/go/internal/modfile"
+ "cmd/go/internal/module"
)
-func ParseGlideLock(file string, data []byte) ([]module.Version, error) {
- var list []module.Version
+func ParseGlideLock(file string, data []byte) (*modfile.File, error) {
+ mf := new(modfile.File)
imports := false
name := ""
for lineno, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, " version:") {
version := strings.TrimSpace(line[len(" version:"):])
if name != "" && version != "" {
- list = append(list, module.Version{Path: name, Version: version})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: name, Version: version}})
}
}
}
- return list, nil
+ return mf, nil
}
import (
"strings"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
)
-func ParseGLOCKFILE(file string, data []byte) ([]module.Version, error) {
- var list []module.Version
+func ParseGLOCKFILE(file string, data []byte) (*modfile.File, error) {
+ mf := new(modfile.File)
for lineno, line := range strings.Split(string(data), "\n") {
lineno++
f := strings.Fields(line)
if len(f) >= 2 && f[0] != "cmd" {
- list = append(list, module.Version{Path: f[0], Version: f[1]})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: f[0], Version: f[1]}})
}
}
- return list, nil
+ return mf, nil
}
import (
"encoding/json"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
)
-func ParseGodepsJSON(file string, data []byte) ([]module.Version, error) {
+func ParseGodepsJSON(file string, data []byte) (*modfile.File, error) {
var cfg struct {
ImportPath string
Deps []struct {
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
- var list []module.Version
+ mf := new(modfile.File)
for _, d := range cfg.Deps {
- list = append(list, module.Version{Path: d.ImportPath, Version: d.Rev})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: d.ImportPath, Version: d.Rev}})
}
- return list, nil
+ return mf, nil
}
package modconv
-import "cmd/go/internal/module"
+import "cmd/go/internal/modfile"
-var Converters = map[string]func(string, []byte) ([]module.Version, error){
+var Converters = map[string]func(string, []byte) (*modfile.File, error){
"GLOCKFILE": ParseGLOCKFILE,
"Godeps/Godeps.json": ParseGodepsJSON,
"Gopkg.lock": ParseGopkgLock,
"vendor/manifest": ParseVendorManifest,
"vendor/vendor.json": ParseVendorJSON,
}
-
-// Prefix is a line we write at the top of auto-converted go.mod files
-// for dependencies before caching them.
-// In case of bugs in the converter, if we bump this version number,
-// then all the cached copies will be ignored.
-const Prefix = "//vgo 0.0.4\n"
import (
"bytes"
"fmt"
- "internal/testenv"
"io/ioutil"
"path/filepath"
"testing"
}
func Test(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
-
tests, _ := filepath.Glob("testdata/*")
if len(tests) == 0 {
t.Fatalf("no tests found")
t.Error(err)
}
var buf bytes.Buffer
- for _, r := range out {
- fmt.Fprintf(&buf, "%s %s\n", r.Path, r.Version)
+ for _, r := range out.Require {
+ fmt.Fprintf(&buf, "%s %s\n", r.Mod.Path, r.Mod.Version)
}
if !bytes.Equal(buf.Bytes(), want) {
t.Errorf("have:\n%s\nwant:\n%s", buf.Bytes(), want)
import (
"strings"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
)
-func ParseDependenciesTSV(file string, data []byte) ([]module.Version, error) {
- var list []module.Version
+func ParseDependenciesTSV(file string, data []byte) (*modfile.File, error) {
+ mf := new(modfile.File)
for lineno, line := range strings.Split(string(data), "\n") {
lineno++
f := strings.Split(line, "\t")
if len(f) >= 3 {
- list = append(list, module.Version{Path: f[0], Version: f[2]})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: f[0], Version: f[2]}})
}
}
- return list, nil
+ return mf, nil
}
import (
"strings"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
)
-func ParseVendorConf(file string, data []byte) ([]module.Version, error) {
- var list []module.Version
+func ParseVendorConf(file string, data []byte) (*modfile.File, error) {
+ mf := new(modfile.File)
for lineno, line := range strings.Split(string(data), "\n") {
lineno++
if i := strings.Index(line, "#"); i >= 0 {
}
f := strings.Fields(line)
if len(f) >= 2 {
- list = append(list, module.Version{Path: f[0], Version: f[1]})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: f[0], Version: f[1]}})
}
}
- return list, nil
+ return mf, nil
}
import (
"encoding/json"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
)
-func ParseVendorJSON(file string, data []byte) ([]module.Version, error) {
+func ParseVendorJSON(file string, data []byte) (*modfile.File, error) {
var cfg struct {
Package []struct {
Path string
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
- var list []module.Version
+ mf := new(modfile.File)
for _, d := range cfg.Package {
- list = append(list, module.Version{Path: d.Path, Version: d.Revision})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: d.Path, Version: d.Revision}})
}
- return list, nil
+ return mf, nil
}
import (
"encoding/json"
+ "cmd/go/internal/modfile"
"cmd/go/internal/module"
)
-func ParseVendorManifest(file string, data []byte) ([]module.Version, error) {
+func ParseVendorManifest(file string, data []byte) (*modfile.File, error) {
var cfg struct {
Dependencies []struct {
ImportPath string
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
- var list []module.Version
+ mf := new(modfile.File)
for _, d := range cfg.Dependencies {
- list = append(list, module.Version{Path: d.ImportPath, Version: d.Revision})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: d.ImportPath, Version: d.Revision}})
}
- return list, nil
+ return mf, nil
}
package modconv
import (
- "cmd/go/internal/module"
"strings"
+
+ "cmd/go/internal/modfile"
+ "cmd/go/internal/module"
)
-func ParseVendorYML(file string, data []byte) ([]module.Version, error) {
- var list []module.Version
+func ParseVendorYML(file string, data []byte) (*modfile.File, error) {
+ mf := new(modfile.File)
vendors := false
path := ""
for lineno, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, " rev:") {
rev := strings.TrimSpace(line[len(" rev:"):])
if path != "" && rev != "" {
- list = append(list, module.Version{Path: path, Version: rev})
+ mf.Require = append(mf.Require, &modfile.Require{Mod: module.Version{Path: path, Version: rev}})
}
}
}
- return list, nil
+ return mf, nil
}
+++ /dev/null
-// Copyright 2018 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 bitbucket
-
-import (
- "fmt"
- "strings"
-
- "cmd/go/internal/modfetch/codehost"
- "cmd/go/internal/modfetch/gitrepo"
-)
-
-func Lookup(path string) (codehost.Repo, error) {
- f := strings.Split(path, "/")
- if len(f) < 3 || f[0] != "bitbucket.org" {
- return nil, fmt.Errorf("bitbucket repo must be bitbucket.org/org/project")
- }
- path = f[0] + "/" + f[1] + "/" + f[2]
- return gitrepo.Repo("https://"+path, path)
-}
--- /dev/null
+// Copyright 2018 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 modfetch
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modfetch/codehost"
+ "cmd/go/internal/par"
+ "cmd/go/internal/semver"
+)
+
+var QuietLookup bool // do not print about lookups
+
+var SrcMod string // $GOPATH/src/mod; set by package modload
+
+// A cachingRepo is a cache around an underlying Repo,
+// avoiding redundant calls to ModulePath, Versions, Stat, Latest, and GoMod (but not Zip).
+// It is also safe for simultaneous use by multiple goroutines
+// (so that it can be returned from Lookup multiple times).
+// It serializes calls to the underlying Repo.
+type cachingRepo struct {
+ path string
+ cache par.Cache // cache for all operations
+ r Repo
+}
+
+func newCachingRepo(r Repo) *cachingRepo {
+ return &cachingRepo{
+ r: r,
+ path: r.ModulePath(),
+ }
+}
+
+func (r *cachingRepo) ModulePath() string {
+ return r.path
+}
+
+func (r *cachingRepo) Versions(prefix string) ([]string, error) {
+ type cached struct {
+ list []string
+ err error
+ }
+ c := r.cache.Do("versions:"+prefix, func() interface{} {
+ list, err := r.r.Versions(prefix)
+ return cached{list, err}
+ }).(cached)
+
+ if c.err != nil {
+ return nil, c.err
+ }
+ return append([]string(nil), c.list...), nil
+}
+
+type cachedInfo struct {
+ info *RevInfo
+ err error
+}
+
+func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
+ c := r.cache.Do("stat:"+rev, func() interface{} {
+ file, info, err := readDiskStat(r.path, rev)
+ if err == nil {
+ return cachedInfo{info, nil}
+ }
+
+ if !QuietLookup {
+ fmt.Fprintf(os.Stderr, "go: finding %s %s\n", r.path, rev)
+ }
+ info, err = r.r.Stat(rev)
+ if err == nil {
+ if err := writeDiskStat(file, info); err != nil {
+ fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err)
+ }
+ // If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78,
+ // then save the information under the proper version, for future use.
+ if info.Version != rev {
+ r.cache.Do("stat:"+info.Version, func() interface{} {
+ return cachedInfo{info, err}
+ })
+ }
+ }
+ return cachedInfo{info, err}
+ }).(cachedInfo)
+
+ if c.err != nil {
+ return nil, c.err
+ }
+ info := *c.info
+ return &info, nil
+}
+
+func (r *cachingRepo) Latest() (*RevInfo, error) {
+ c := r.cache.Do("latest:", func() interface{} {
+ if !QuietLookup {
+ fmt.Fprintf(os.Stderr, "go: finding %s latest\n", r.path)
+ }
+ info, err := r.r.Latest()
+
+ // Save info for likely future Stat call.
+ if err == nil {
+ r.cache.Do("stat:"+info.Version, func() interface{} {
+ return cachedInfo{info, err}
+ })
+ if file, _, err := readDiskStat(r.path, info.Version); err != nil {
+ writeDiskStat(file, info)
+ }
+ }
+
+ return cachedInfo{info, err}
+ }).(cachedInfo)
+
+ if c.err != nil {
+ return nil, c.err
+ }
+ info := *c.info
+ return &info, nil
+}
+
+func (r *cachingRepo) GoMod(rev string) ([]byte, error) {
+ type cached struct {
+ text []byte
+ err error
+ }
+ c := r.cache.Do("gomod:"+rev, func() interface{} {
+ file, text, err := readDiskGoMod(r.path, rev)
+ if err == nil {
+ // Note: readDiskGoMod already called checkGoMod.
+ return cached{text, nil}
+ }
+
+ // Convert rev to canonical version
+ // so that we use the right identifier in the go.sum check.
+ info, err := r.Stat(rev)
+ if err != nil {
+ return cached{nil, err}
+ }
+ rev = info.Version
+
+ text, err = r.r.GoMod(rev)
+ if err == nil {
+ checkGoMod(r.path, rev, text)
+ if err := writeDiskGoMod(file, text); err != nil {
+ fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err)
+ }
+ }
+ return cached{text, err}
+ }).(cached)
+
+ if c.err != nil {
+ return nil, c.err
+ }
+ return append([]byte(nil), c.text...), nil
+}
+
+func (r *cachingRepo) Zip(version, tmpdir string) (string, error) {
+ return r.r.Zip(version, tmpdir)
+}
+
+// Stat is like Lookup(path).Stat(rev) but avoids the
+// repository path resolution in Lookup if the result is
+// already cached on local disk.
+func Stat(path, rev string) (*RevInfo, error) {
+ _, info, err := readDiskStat(path, rev)
+ if err == nil {
+ return info, nil
+ }
+ repo, err := Lookup(path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.Stat(rev)
+}
+
+// GoMod is like Lookup(path).GoMod(rev) but avoids the
+// repository path resolution in Lookup if the result is
+// already cached on local disk.
+func GoMod(path, rev string) ([]byte, error) {
+ // Convert commit hash to pseudo-version
+ // to increase cache hit rate.
+ if !semver.IsValid(rev) {
+ info, err := Stat(path, rev)
+ if err != nil {
+ return nil, err
+ }
+ rev = info.Version
+ }
+ _, data, err := readDiskGoMod(path, rev)
+ if err == nil {
+ return data, nil
+ }
+ repo, err := Lookup(path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.GoMod(rev)
+}
+
+var errNotCached = fmt.Errorf("not in cache")
+
+// readDiskStat reads a cached stat result from disk,
+// returning the name of the cache file and the result.
+// If the read fails, the caller can use
+// writeDiskStat(file, info) to write a new cache entry.
+func readDiskStat(path, rev string) (file string, info *RevInfo, err error) {
+ file, data, err := readDiskCache(path, rev, "info")
+ if err != nil {
+ if file, info, err := readDiskStatByHash(path, rev); err == nil {
+ return file, info, nil
+ }
+ return file, nil, err
+ }
+ info = new(RevInfo)
+ if err := json.Unmarshal(data, info); err != nil {
+ return file, nil, errNotCached
+ }
+ return file, info, nil
+}
+
+// readDiskStatByHash is a fallback for readDiskStat for the case
+// where rev is a commit hash instead of a proper semantic version.
+// In that case, we look for a cached pseudo-version that matches
+// the commit hash. If we find one, we use it.
+// This matters most for converting legacy package management
+// configs, when we are often looking up commits by full hash.
+// Without this check we'd be doing network I/O to the remote repo
+// just to find out about a commit we already know about
+// (and have cached under its pseudo-version).
+func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error) {
+ if SrcMod == "" {
+ // Do not download to current directory.
+ return "", nil, errNotCached
+ }
+
+ if !codehost.AllHex(rev) || len(rev) < 12 {
+ return "", nil, errNotCached
+ }
+ rev = rev[:12]
+ dir, err := os.Open(filepath.Join(SrcMod, "cache/download", path, "@v"))
+ if err != nil {
+ return "", nil, errNotCached
+ }
+ names, err := dir.Readdirnames(-1)
+ dir.Close()
+ if err != nil {
+ return "", nil, errNotCached
+ }
+ suffix := "-" + rev + ".info"
+ for _, name := range names {
+ if strings.HasSuffix(name, suffix) && IsPseudoVersion(strings.TrimSuffix(name, ".info")) {
+ return readDiskStat(path, strings.TrimSuffix(name, ".info"))
+ }
+ }
+ return "", nil, errNotCached
+}
+
+// oldVgoPrefix is the prefix in the old auto-generated cached go.mod files.
+// We stopped trying to auto-generate the go.mod files. Now we use a trivial
+// go.mod with only a module line, and we've dropped the version prefix
+// entirely. If we see a version prefix, that means we're looking at an old copy
+// and should ignore it.
+var oldVgoPrefix = []byte("//vgo 0.0.")
+
+// readDiskGoMod reads a cached stat result from disk,
+// returning the name of the cache file and the result.
+// If the read fails, the caller can use
+// writeDiskGoMod(file, data) to write a new cache entry.
+func readDiskGoMod(path, rev string) (file string, data []byte, err error) {
+ file, data, err = readDiskCache(path, rev, "mod")
+
+ // If the file has an old auto-conversion prefix, pretend it's not there.
+ if bytes.HasPrefix(data, oldVgoPrefix) {
+ err = errNotCached
+ data = nil
+ }
+
+ if err == nil {
+ checkGoMod(path, rev, data)
+ }
+
+ return file, data, err
+}
+
+// readDiskCache is the generic "read from a cache file" implementation.
+// It takes the revision and an identifying suffix for the kind of data being cached.
+// It returns the name of the cache file and the content of the file.
+// If the read fails, the caller can use
+// writeDiskCache(file, data) to write a new cache entry.
+func readDiskCache(path, rev, suffix string) (file string, data []byte, err error) {
+ if !semver.IsValid(rev) || SrcMod == "" {
+ return "", nil, errNotCached
+ }
+ file = filepath.Join(SrcMod, "cache/download", path, "@v", rev+"."+suffix)
+ data, err = ioutil.ReadFile(file)
+ if err != nil {
+ return file, nil, errNotCached
+ }
+ return file, data, nil
+}
+
+// writeDiskStat writes a stat result cache entry.
+// The file name must have been returned by a previous call to readDiskStat.
+func writeDiskStat(file string, info *RevInfo) error {
+ if file == "" {
+ return nil
+ }
+ js, err := json.Marshal(info)
+ if err != nil {
+ return err
+ }
+ return writeDiskCache(file, js)
+}
+
+// writeDiskGoMod writes a go.mod cache entry.
+// The file name must have been returned by a previous call to readDiskGoMod.
+func writeDiskGoMod(file string, text []byte) error {
+ return writeDiskCache(file, text)
+}
+
+// writeDiskCache is the generic "write to a cache file" implementation.
+// The file must have been returned by a previous call to readDiskCache.
+func writeDiskCache(file string, data []byte) error {
+ if file == "" {
+ return nil
+ }
+ // Make sure directory for file exists.
+ if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil {
+ return err
+ }
+ // Write data to temp file next to target file.
+ f, err := ioutil.TempFile(filepath.Dir(file), filepath.Base(file)+".tmp-")
+ if err != nil {
+ return err
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ if _, err := f.Write(data); err != nil {
+ return err
+ }
+ if err := f.Close(); err != nil {
+ return err
+ }
+ // Rename temp file onto cache file,
+ // so that the cache file is always a complete file.
+ if err := os.Rename(f.Name(), file); err != nil {
+ return err
+ }
+
+ if strings.HasSuffix(file, ".mod") {
+ rewriteVersionList(filepath.Dir(file))
+ }
+ return nil
+}
+
+// rewriteVersionList rewrites the version list in dir
+// after a new *.mod file has been written.
+func rewriteVersionList(dir string) {
+ if filepath.Base(dir) != "@v" {
+ base.Fatalf("go: internal error: misuse of rewriteVersionList")
+ }
+
+ // TODO(rsc): We should do some kind of directory locking here,
+ // to avoid lost updates.
+
+ infos, err := ioutil.ReadDir(dir)
+ if err != nil {
+ return
+ }
+ var list []string
+ for _, info := range infos {
+ // We look for *.mod files on the theory that if we can't supply
+ // the .mod file then there's no point in listing that version,
+ // since it's unusable. (We can have *.info without *.mod.)
+ // We don't require *.zip files on the theory that for code only
+ // involved in module graph construction, many *.zip files
+ // will never be requested.
+ name := info.Name()
+ if strings.HasSuffix(name, ".mod") {
+ v := strings.TrimSuffix(name, ".mod")
+ if semver.IsValid(v) && semver.Canonical(v) == v {
+ list = append(list, v)
+ }
+ }
+ }
+ SortVersions(list)
+
+ var buf bytes.Buffer
+ for _, v := range list {
+ buf.WriteString(v)
+ buf.WriteString("\n")
+ }
+ listFile := filepath.Join(dir, "list")
+ old, _ := ioutil.ReadFile(listFile)
+ if bytes.Equal(buf.Bytes(), old) {
+ return
+ }
+ // TODO: Use rename to install file,
+ // so that readers never see an incomplete file.
+ ioutil.WriteFile(listFile, buf.Bytes(), 0666)
+}
--- /dev/null
+// Copyright 2018 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 modfetch
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestWriteDiskCache(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "go-writeCache-test-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ err = writeDiskCache(filepath.Join(tmpdir, "file"), []byte("data"))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
"os/exec"
"path/filepath"
"strings"
+ "sync"
"time"
"cmd/go/internal/cfg"
// A Repo represents a code hosting source.
// Typical implementations include local version control repositories,
// remote version control servers, and code hosting sites.
+// A Repo must be safe for simultaneous use by multiple goroutines.
type Repo interface {
- // Root returns the import path of the root directory of the repository.
- Root() string
-
// List lists all tags with the given prefix.
Tags(prefix string) (tags []string, err error)
// ReadFile reads the given file in the file tree corresponding to revision rev.
// It should refuse to read more than maxSize bytes.
+ //
+ // If the requested file does not exist it should return an error for which
+ // os.IsNotExist(err) returns true.
ReadFile(rev, file string, maxSize int64) (data []byte, err error)
// ReadZip downloads a zip file for the subdir subdirectory
type RevInfo struct {
Name string // complete ID in underlying repository
Short string // shortened ID, for use in pseudo-version
- Version string // TODO what is this?
+ Version string // version used in lookup
Time time.Time // commit time
+ Tags []string // known tags for commit
}
// AllHex reports whether the revision rev is entirely lower-case hexadecimal digits.
}
// WorkRoot is the root of the cached work directory.
-// It is set by cmd/go/internal/vgo.InitMod.
+// It is set by cmd/go/internal/modload.InitMod.
var WorkRoot string
// WorkDir returns the name of the cached work directory to use for the
key := typ + ":" + name
dir := filepath.Join(WorkRoot, fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
data, err := ioutil.ReadFile(dir + ".info")
- if err == nil {
+ info, err2 := os.Stat(dir)
+ if err == nil && err2 == nil && info.IsDir() {
+ // Info file and directory both already exist: reuse.
have := strings.TrimSuffix(string(data), "\n")
if have != key {
return "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key)
}
- _, err := os.Stat(dir)
- if err != nil {
- return "", fmt.Errorf("%s exists but %s does not", dir+".info", dir)
- }
if cfg.BuildX {
fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name)
}
return dir, nil
}
+ // Info file or directory missing. Start from scratch.
if cfg.BuildX {
fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name)
}
return text
}
+var dirLock sync.Map
+
// Run runs the command line in the given directory
// (an empty dir means the current directory).
// It returns the standard output and, for a non-zero exit,
// a *RunError indicating the command, exit status, and standard error.
// Standard error is unavailable for commands that exit successfully.
func Run(dir string, cmdline ...interface{}) ([]byte, error) {
+ if dir != "" {
+ muIface, ok := dirLock.Load(dir)
+ if !ok {
+ muIface, _ = dirLock.LoadOrStore(dir, new(sync.Mutex))
+ }
+ mu := muIface.(*sync.Mutex)
+ mu.Lock()
+ defer mu.Unlock()
+ }
+
cmd := str.StringList(cmdline...)
if cfg.BuildX {
- var cd string
+ var text string
if dir != "" {
- cd = "cd " + dir + "; "
+ text = "cd " + dir + "; "
}
- fmt.Fprintf(os.Stderr, "%s%s\n", cd, strings.Join(cmd, " "))
+ text += strings.Join(cmd, " ")
+ fmt.Fprintf(os.Stderr, "%s\n", text)
+ start := time.Now()
+ defer func() {
+ fmt.Fprintf(os.Stderr, "%.3fs # %s\n", time.Since(start).Seconds(), text)
+ }()
}
// TODO: Impose limits on command output size.
// TODO: Set environment to get English error messages.
--- /dev/null
+// Copyright 2018 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 codehost
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "cmd/go/internal/par"
+)
+
+// GitRepo returns the code repository at the given Git remote reference.
+func GitRepo(remote string) (Repo, error) {
+ return newGitRepoCached(remote, false)
+}
+
+// LocalGitRepo is like Repo but accepts both Git remote references
+// and paths to repositories on the local file system.
+func LocalGitRepo(remote string) (Repo, error) {
+ return newGitRepoCached(remote, true)
+}
+
+const gitWorkDirType = "git2"
+
+var gitRepoCache par.Cache
+
+func newGitRepoCached(remote string, localOK bool) (Repo, error) {
+ type key struct {
+ remote string
+ localOK bool
+ }
+ type cached struct {
+ repo Repo
+ err error
+ }
+
+ c := gitRepoCache.Do(key{remote, localOK}, func() interface{} {
+ repo, err := newGitRepo(remote, localOK)
+ return cached{repo, err}
+ }).(cached)
+
+ return c.repo, c.err
+}
+
+func newGitRepo(remote string, localOK bool) (Repo, error) {
+ r := &gitRepo{remote: remote}
+ if strings.Contains(remote, "://") {
+ // This is a remote path.
+ dir, err := WorkDir(gitWorkDirType, r.remote)
+ if err != nil {
+ return nil, err
+ }
+ r.dir = dir
+ if _, err := os.Stat(filepath.Join(dir, "objects")); err != nil {
+ if _, err := Run(dir, "git", "init", "--bare"); err != nil {
+ os.RemoveAll(dir)
+ return nil, err
+ }
+ // We could just say git fetch https://whatever later,
+ // but this lets us say git fetch origin instead, which
+ // is a little nicer. More importantly, using a named remote
+ // avoids a problem with Git LFS. See golang.org/issue/25605.
+ if _, err := Run(dir, "git", "remote", "add", "origin", r.remote); err != nil {
+ os.RemoveAll(dir)
+ return nil, err
+ }
+ r.remote = "origin"
+ }
+ } else {
+ // Local path.
+ // Disallow colon (not in ://) because sometimes
+ // that's rcp-style host:path syntax and sometimes it's not (c:\work).
+ // The go command has always insisted on URL syntax for ssh.
+ if strings.Contains(remote, ":") {
+ return nil, fmt.Errorf("git remote cannot use host:path syntax")
+ }
+ if !localOK {
+ return nil, fmt.Errorf("git remote must not be local directory")
+ }
+ r.local = true
+ info, err := os.Stat(remote)
+ if err != nil {
+ return nil, err
+ }
+ if !info.IsDir() {
+ return nil, fmt.Errorf("%s exists but is not a directory", remote)
+ }
+ r.dir = remote
+ }
+ return r, nil
+}
+
+type gitRepo struct {
+ remote string
+ local bool
+ dir string
+
+ mu sync.Mutex // protects fetchLevel, some git repo state
+ fetchLevel int
+
+ statCache par.Cache
+
+ refsOnce sync.Once
+ refs map[string]string
+ refsErr error
+
+ localTagsOnce sync.Once
+ localTags map[string]bool
+}
+
+const (
+ // How much have we fetched into the git repo (in this process)?
+ fetchNone = iota // nothing yet
+ fetchSome // shallow fetches of individual hashes
+ fetchAll // "fetch -t origin": get all remote branches and tags
+)
+
+// loadLocalTags loads tag references from the local git cache
+// into the map r.localTags.
+// Should only be called as r.localTagsOnce.Do(r.loadLocalTags).
+func (r *gitRepo) loadLocalTags() {
+ // The git protocol sends all known refs and ls-remote filters them on the client side,
+ // so we might as well record both heads and tags in one shot.
+ // Most of the time we only care about tags but sometimes we care about heads too.
+ out, err := Run(r.dir, "git", "tag", "-l")
+ if err != nil {
+ return
+ }
+
+ r.localTags = make(map[string]bool)
+ for _, line := range strings.Split(string(out), "\n") {
+ if line != "" {
+ r.localTags[line] = true
+ }
+ }
+}
+
+// loadRefs loads heads and tags references from the remote into the map r.refs.
+// Should only be called as r.refsOnce.Do(r.loadRefs).
+func (r *gitRepo) loadRefs() {
+ // The git protocol sends all known refs and ls-remote filters them on the client side,
+ // so we might as well record both heads and tags in one shot.
+ // Most of the time we only care about tags but sometimes we care about heads too.
+ out, err := Run(r.dir, "git", "ls-remote", "-q", r.remote)
+ if err != nil {
+ r.refsErr = err
+ return
+ }
+
+ r.refs = make(map[string]string)
+ for _, line := range strings.Split(string(out), "\n") {
+ f := strings.Fields(line)
+ if len(f) != 2 {
+ continue
+ }
+ if f[1] == "HEAD" || strings.HasPrefix(f[1], "refs/heads/") || strings.HasPrefix(f[1], "refs/tags/") {
+ r.refs[f[1]] = f[0]
+ }
+ }
+ for ref, hash := range r.refs {
+ if strings.HasSuffix(ref, "^{}") { // record unwrapped annotated tag as value of tag
+ r.refs[strings.TrimSuffix(ref, "^{}")] = hash
+ delete(r.refs, ref)
+ }
+ }
+}
+
+func (r *gitRepo) Tags(prefix string) ([]string, error) {
+ r.refsOnce.Do(r.loadRefs)
+ if r.refsErr != nil {
+ return nil, r.refsErr
+ }
+
+ tags := []string{}
+ for ref := range r.refs {
+ if !strings.HasPrefix(ref, "refs/tags/") {
+ continue
+ }
+ tag := ref[len("refs/tags/"):]
+ if !strings.HasPrefix(tag, prefix) {
+ continue
+ }
+ tags = append(tags, tag)
+ }
+ sort.Strings(tags)
+ return tags, nil
+}
+
+func (r *gitRepo) Latest() (*RevInfo, error) {
+ r.refsOnce.Do(r.loadRefs)
+ if r.refsErr != nil {
+ return nil, r.refsErr
+ }
+ if r.refs["HEAD"] == "" {
+ return nil, fmt.Errorf("no commits")
+ }
+ return r.Stat(r.refs["HEAD"])
+}
+
+// findRef finds some ref name for the given hash,
+// for use when the server requires giving a ref instead of a hash.
+// There may be multiple ref names for a given hash,
+// in which case this returns some name - it doesn't matter which.
+func (r *gitRepo) findRef(hash string) (ref string, ok bool) {
+ r.refsOnce.Do(r.loadRefs)
+ for ref, h := range r.refs {
+ if h == hash {
+ return ref, true
+ }
+ }
+ return "", false
+}
+
+func unshallow(gitDir string) []string {
+ if _, err := os.Stat(filepath.Join(gitDir, "shallow")); err == nil {
+ return []string{"--unshallow"}
+ }
+ return []string{}
+}
+
+// minHashDigits is the minimum number of digits to require
+// before accepting a hex digit sequence as potentially identifying
+// a specific commit in a git repo. (Of course, users can always
+// specify more digits, and many will paste in all 40 digits,
+// but many of git's commands default to printing short hashes
+// as 7 digits.)
+const minHashDigits = 7
+
+// stat stats the given rev in the local repository,
+// or else it fetches more info from the remote repository and tries again.
+func (r *gitRepo) stat(rev string) (*RevInfo, error) {
+ if r.local {
+ return r.statLocal(rev, rev)
+ }
+
+ // Fast path: maybe rev is a hash we already have locally.
+ if len(rev) >= minHashDigits && len(rev) <= 40 && AllHex(rev) {
+ if info, err := r.statLocal(rev, rev); err == nil {
+ return info, nil
+ }
+ }
+
+ // Maybe rev is a tag we already have locally.
+ // (Note that we're excluding branches, which can be stale.)
+ r.localTagsOnce.Do(r.loadLocalTags)
+ if r.localTags[rev] {
+ return r.statLocal(rev, "refs/tags/"+rev)
+ }
+
+ // Maybe rev is the name of a tag or branch on the remote server.
+ // Or maybe it's the prefix of a hash of a named ref.
+ // Try to resolve to both a ref (git name) and full (40-hex-digit) commit hash.
+ r.refsOnce.Do(r.loadRefs)
+ var ref, hash string
+ if r.refs["refs/tags/"+rev] != "" {
+ ref = "refs/tags/" + rev
+ hash = r.refs[ref]
+ // Keep rev as is: tags are assumed not to change meaning.
+ } else if r.refs["refs/heads/"+rev] != "" {
+ ref = "refs/heads/" + rev
+ hash = r.refs[ref]
+ rev = hash // Replace rev, because meaning of refs/heads/foo can change.
+ } else if rev == "HEAD" && r.refs["HEAD"] != "" {
+ ref = "HEAD"
+ hash = r.refs[ref]
+ rev = hash // Replace rev, because meaning of HEAD can change.
+ } else if len(rev) >= minHashDigits && len(rev) <= 40 && AllHex(rev) {
+ // At the least, we have a hash prefix we can look up after the fetch below.
+ // Maybe we can map it to a full hash using the known refs.
+ prefix := rev
+ // Check whether rev is prefix of known ref hash.
+ for k, h := range r.refs {
+ if strings.HasPrefix(h, prefix) {
+ if hash != "" && hash != h {
+ // Hash is an ambiguous hash prefix.
+ // More information will not change that.
+ return nil, fmt.Errorf("ambiguous revision %s", rev)
+ }
+ if ref == "" || ref > k { // Break ties deterministically when multiple refs point at same hash.
+ ref = k
+ }
+ rev = h
+ hash = h
+ }
+ }
+ if hash == "" && len(rev) == 40 { // Didn't find a ref, but rev is a full hash.
+ hash = rev
+ }
+ } else {
+ return nil, fmt.Errorf("unknown revision %s", rev)
+ }
+
+ // Protect r.fetchLevel and the "fetch more and more" sequence.
+ // TODO(rsc): Add LockDir and use it for protecting that
+ // sequence, so that multiple processes don't collide in their
+ // git commands.
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ // If we know a specific commit we need, fetch it.
+ if r.fetchLevel <= fetchSome && hash != "" && !r.local {
+ r.fetchLevel = fetchSome
+ var refspec string
+ if ref != "" && ref != "head" {
+ // If we do know the ref name, save the mapping locally
+ // so that (if it is a tag) it can show up in localTags
+ // on a future call. Also, some servers refuse to allow
+ // full hashes in ref specs, so prefer a ref name if known.
+ refspec = ref + ":" + ref
+ } else {
+ // Fetch the hash but give it a local name (refs/dummy),
+ // because that triggers the fetch behavior of creating any
+ // other known remote tags for the hash. We never use
+ // refs/dummy (it's not refs/tags/dummy) and it will be
+ // overwritten in the next command, and that's fine.
+ ref = hash
+ refspec = hash + ":refs/dummy"
+ }
+ _, err := Run(r.dir, "git", "fetch", "-f", "--depth=1", r.remote, refspec)
+ if err == nil {
+ return r.statLocal(rev, ref)
+ }
+ if !strings.Contains(err.Error(), "unadvertised object") && !strings.Contains(err.Error(), "no such remote ref") && !strings.Contains(err.Error(), "does not support shallow") {
+ return nil, err
+ }
+ }
+
+ // Last resort.
+ // Fetch all heads and tags and hope the hash we want is in the history.
+ if r.fetchLevel < fetchAll {
+ r.fetchLevel = fetchAll
+
+ // To work around a protocol version 2 bug that breaks --unshallow,
+ // add -c protocol.version=0.
+ // TODO(rsc): The bug is believed to be server-side, meaning only
+ // on Google's Git servers. Once the servers are fixed, drop the
+ // protocol.version=0. See Google-internal bug b/110495752.
+ var protoFlag []string
+ unshallowFlag := unshallow(r.dir)
+ if len(unshallowFlag) > 0 {
+ protoFlag = []string{"-c", "protocol.version=0"}
+ }
+ if _, err := Run(r.dir, "git", protoFlag, "fetch", unshallowFlag, "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
+ return nil, err
+ }
+ }
+
+ return r.statLocal(rev, rev)
+}
+
+// statLocal returns a RevInfo describing rev in the local git repository.
+// It uses version as info.Version.
+func (r *gitRepo) statLocal(version, rev string) (*RevInfo, error) {
+ out, err := Run(r.dir, "git", "log", "-n1", "--format=format:%H %ct %D", rev)
+ if err != nil {
+ return nil, fmt.Errorf("unknown revision %s", rev)
+ }
+ f := strings.Fields(string(out))
+ if len(f) < 2 {
+ return nil, fmt.Errorf("unexpected response from git log: %q", out)
+ }
+ hash := f[0]
+ if strings.HasPrefix(hash, version) {
+ version = hash // extend to full hash
+ }
+ t, err := strconv.ParseInt(f[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid time from git log: %q", out)
+ }
+
+ info := &RevInfo{
+ Name: hash,
+ Short: ShortenSHA1(hash),
+ Time: time.Unix(t, 0).UTC(),
+ Version: hash,
+ }
+
+ // Add tags. Output looks like:
+ // ede458df7cd0fdca520df19a33158086a8a68e81 1523994202 HEAD -> master, tag: v1.2.4-annotated, tag: v1.2.3, origin/master, origin/HEAD
+ for i := 2; i < len(f); i++ {
+ if f[i] == "tag:" {
+ i++
+ if i < len(f) {
+ info.Tags = append(info.Tags, strings.TrimSuffix(f[i], ","))
+ }
+ }
+ }
+ sort.Strings(info.Tags)
+
+ // Used hash as info.Version above.
+ // Use caller's suggested version if it appears in the tag list
+ // (filters out branch names, HEAD).
+ for _, tag := range info.Tags {
+ if version == tag {
+ info.Version = version
+ }
+ }
+
+ return info, nil
+}
+
+func (r *gitRepo) Stat(rev string) (*RevInfo, error) {
+ if rev == "latest" {
+ return r.Latest()
+ }
+ type cached struct {
+ info *RevInfo
+ err error
+ }
+ c := r.statCache.Do(rev, func() interface{} {
+ info, err := r.stat(rev)
+ return cached{info, err}
+ }).(cached)
+ return c.info, c.err
+}
+
+func (r *gitRepo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
+ // TODO: Could use git cat-file --batch.
+ info, err := r.Stat(rev) // download rev into local git repo
+ if err != nil {
+ return nil, err
+ }
+ out, err := Run(r.dir, "git", "cat-file", "blob", info.Name+":"+file)
+ if err != nil {
+ return nil, os.ErrNotExist
+ }
+ return out, nil
+}
+
+func (r *gitRepo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) {
+ // TODO: Use maxSize or drop it.
+ args := []string{}
+ if subdir != "" {
+ args = append(args, "--", subdir)
+ }
+ info, err := r.Stat(rev) // download rev into local git repo
+ if err != nil {
+ return nil, "", err
+ }
+
+ // Incredibly, git produces different archives depending on whether
+ // it is running on a Windows system or not, in an attempt to normalize
+ // text file line endings. Setting -c core.autocrlf=input means only
+ // translate files on the way into the repo, not on the way out (archive).
+ // The -c core.eol=lf should be unnecessary but set it anyway.
+ archive, err := Run(r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
+ if err != nil {
+ if bytes.Contains(err.(*RunError).Stderr, []byte("did not match any files")) {
+ return nil, "", os.ErrNotExist
+ }
+ return nil, "", err
+ }
+
+ return ioutil.NopCloser(bytes.NewReader(archive)), "", nil
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package gitrepo
+package codehost
import (
"archive/zip"
"bytes"
- "flag"
"fmt"
"internal/testenv"
"io/ioutil"
"log"
"os"
"os/exec"
+ "path"
"path/filepath"
"reflect"
- "runtime"
"strings"
"testing"
"time"
-
- "cmd/go/internal/modfetch/codehost"
)
func TestMain(m *testing.M) {
- // needed for initializing the test environment variables as testing.Short
- // and HasExternalNetwork
- flag.Parse()
os.Exit(testMain(m))
}
-const gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1"
+const (
+ gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1"
+ hgrepo1 = "https://vcs-test.golang.org/hg/hgrepo1"
+)
+
+var altRepos = []string{
+ "localGitRepo",
+ hgrepo1,
+}
+
+// TODO: Convert gitrepo1 to svn, bzr, fossil and add tests.
+// For now, at least the hgrepo1 tests check the general vcs.go logic.
// localGitRepo is like gitrepo1 but allows archive access.
var localGitRepo string
return 0
}
- if runtime.GOOS == "plan9" {
- fmt.Fprintln(os.Stderr, "skipping on plan9")
- fmt.Println("PASS")
- return 0
- }
-
dir, err := ioutil.TempDir("", "gitrepo-test-")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
- codehost.WorkRoot = dir
+ WorkRoot = dir
if testenv.HasExternalNetwork() && testenv.HasExec() {
// Clone gitrepo1 into a local directory.
// then git starts up all the usual protocol machinery,
// which will let us test remote git archive invocations.
localGitRepo = filepath.Join(dir, "gitrepo2")
- if _, err := codehost.Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil {
+ if _, err := Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil {
log.Fatal(err)
}
- if _, err := codehost.Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil {
+ if _, err := Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil {
log.Fatal(err)
}
}
return m.Run()
}
-func testRepo(remote string) (codehost.Repo, error) {
+func testRepo(remote string) (Repo, error) {
if remote == "localGitRepo" {
- remote = "file://" + filepath.ToSlash(localGitRepo)
+ return LocalGitRepo(filepath.ToSlash(localGitRepo))
}
- // Re ?root: nothing should care about the second argument,
- // so use a string that will be distinctive if it does show up.
- return LocalRepo(remote, "?root")
+ kind := "git"
+ for _, k := range []string{"hg"} {
+ if strings.Contains(remote, "/"+k+"/") {
+ kind = k
+ }
+ }
+ return NewRepo(kind, remote)
}
var tagsTests = []struct {
t.Errorf("Tags: incorrect tags\nhave %v\nwant %v", tags, tt.tags)
}
}
- t.Run(tt.repo+"/"+tt.prefix, f)
+ t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
if tt.repo == gitrepo1 {
- tt.repo = "localGitRepo"
- t.Run(tt.repo+"/"+tt.prefix, f)
+ for _, tt.repo = range altRepos {
+ t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
+ }
}
}
}
var latestTests = []struct {
repo string
- info *codehost.RevInfo
+ info *RevInfo
}{
{
gitrepo1,
- &codehost.RevInfo{
+ &RevInfo{
Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
Short: "ede458df7cd0",
Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+ Tags: []string{"v1.2.3", "v1.2.4-annotated"},
+ },
+ },
+ {
+ hgrepo1,
+ &RevInfo{
+ Name: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
+ Short: "18518c07eb8e",
+ Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
+ Time: time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC),
},
},
}
if err != nil {
t.Fatal(err)
}
- if *info != *tt.info {
+ if !reflect.DeepEqual(info, tt.info) {
t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
}
}
- t.Run(tt.repo, f)
+ t.Run(path.Base(tt.repo), f)
if tt.repo == gitrepo1 {
tt.repo = "localGitRepo"
- t.Run(tt.repo, f)
+ t.Run(path.Base(tt.repo), f)
}
}
}
}{
{
repo: gitrepo1,
- rev: "HEAD",
+ rev: "latest",
file: "README",
data: "",
},
repo: gitrepo1,
rev: "v2.3.4",
file: "another.txt",
- err: "file not found",
+ err: os.ErrNotExist.Error(),
},
}
t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data)
}
}
- t.Run(tt.repo+"/"+tt.rev+"/"+tt.file, f)
+ t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
if tt.repo == gitrepo1 {
- tt.repo = "localGitRepo"
- t.Run(tt.repo+"/"+tt.rev+"/"+tt.file, f)
+ for _, tt.repo = range altRepos {
+ t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
+ }
}
}
}
"prefix/v2": 3,
},
},
+ {
+ repo: hgrepo1,
+ rev: "v2.3.4",
+ subdir: "",
+ files: map[string]uint64{
+ "prefix/.hg_archival.txt": ^uint64(0),
+ "prefix/README": 0,
+ "prefix/v2": 3,
+ },
+ },
+
{
repo: gitrepo1,
rev: "v2",
"prefix/foo.txt": 13,
},
},
+ {
+ repo: hgrepo1,
+ rev: "v2",
+ subdir: "",
+ files: map[string]uint64{
+ "prefix/.hg_archival.txt": ^uint64(0),
+ "prefix/README": 0,
+ "prefix/v2": 3,
+ "prefix/another.txt": 8,
+ "prefix/foo.txt": 13,
+ },
+ },
+
{
repo: gitrepo1,
rev: "v3",
"prefix/README": 0,
},
},
+ {
+ repo: hgrepo1,
+ rev: "v3",
+ subdir: "",
+ files: map[string]uint64{
+ "prefix/.hg_archival.txt": ^uint64(0),
+ "prefix/.hgtags": 405,
+ "prefix/v3/sub/dir/file.txt": 16,
+ "prefix/README": 0,
+ },
+ },
+
{
repo: gitrepo1,
rev: "v3",
"prefix/v3/sub/dir/file.txt": 16,
},
},
+ {
+ repo: hgrepo1,
+ rev: "v3",
+ subdir: "v3/sub/dir",
+ files: map[string]uint64{
+ "prefix/v3/sub/dir/file.txt": 16,
+ },
+ },
+
{
repo: gitrepo1,
rev: "v3",
"prefix/v3/sub/dir/file.txt": 16,
},
},
+ {
+ repo: hgrepo1,
+ rev: "v3",
+ subdir: "v3/sub",
+ files: map[string]uint64{
+ "prefix/v3/sub/dir/file.txt": 16,
+ },
+ },
+
{
repo: gitrepo1,
rev: "aaaaaaaaab",
subdir: "",
- err: "cannot find hash",
+ err: "unknown revision",
},
+ {
+ repo: hgrepo1,
+ rev: "aaaaaaaaab",
+ subdir: "",
+ err: "unknown revision",
+ },
+
{
repo: "https://github.com/rsc/vgotest1",
rev: "submod/v1.0.4",
continue
}
have[f.Name] = true
- if f.UncompressedSize64 != size {
+ if size != ^uint64(0) && f.UncompressedSize64 != size {
t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size)
}
}
}
}
}
- t.Run(tt.repo+"/"+tt.rev+"/"+tt.subdir, f)
+ t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
if tt.repo == gitrepo1 {
tt.repo = "localGitRepo"
- t.Run(tt.repo+"/"+tt.rev+"/"+tt.subdir, f)
+ t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
}
}
}
+var hgmap = map[string]string{
+ "HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d", // not tip due to bad hgrepo1 conversion
+ "9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e",
+ "76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0",
+ "ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d",
+ "97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d",
+}
+
var statTests = []struct {
repo string
rev string
err string
- info *codehost.RevInfo
+ info *RevInfo
}{
{
repo: gitrepo1,
rev: "HEAD",
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
Short: "ede458df7cd0",
Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+ Tags: []string{"v1.2.3", "v1.2.4-annotated"},
},
},
{
repo: gitrepo1,
rev: "v2", // branch
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
Short: "9d02800338b8",
Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
Time: time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC),
+ Tags: []string{"v2.0.2"},
},
},
{
repo: gitrepo1,
rev: "v2.3.4", // badly-named branch (semver should be a tag)
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
Short: "76a00fb249b7",
Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
+ Tags: []string{"v2.0.1", "v2.3"},
},
},
{
repo: gitrepo1,
rev: "v2.3", // badly-named tag (we only respect full semver v2.3.0)
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
Short: "76a00fb249b7",
Version: "v2.3",
Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
+ Tags: []string{"v2.0.1", "v2.3"},
},
},
{
repo: gitrepo1,
rev: "v1.2.3", // tag
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
Short: "ede458df7cd0",
Version: "v1.2.3",
Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+ Tags: []string{"v1.2.3", "v1.2.4-annotated"},
},
},
{
repo: gitrepo1,
rev: "ede458df", // hash prefix in refs
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
Short: "ede458df7cd0",
Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+ Tags: []string{"v1.2.3", "v1.2.4-annotated"},
},
},
{
repo: gitrepo1,
rev: "97f6aa59", // hash prefix not in refs
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "97f6aa59c81c623494825b43d39e445566e429a4",
Short: "97f6aa59c81c",
Version: "97f6aa59c81c623494825b43d39e445566e429a4",
{
repo: gitrepo1,
rev: "v1.2.4-annotated", // annotated tag uses unwrapped commit hash
- info: &codehost.RevInfo{
+ info: &RevInfo{
Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
Short: "ede458df7cd0",
Version: "v1.2.4-annotated",
Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
+ Tags: []string{"v1.2.3", "v1.2.4-annotated"},
},
},
{
repo: gitrepo1,
rev: "aaaaaaaaab",
- err: "cannot find hash",
+ err: "unknown revision",
},
}
}
return
}
- if *info != *tt.info {
+ if !reflect.DeepEqual(info, tt.info) {
t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
}
}
- t.Run(filepath.Base(tt.repo)+"/"+tt.rev, f)
+ t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
if tt.repo == gitrepo1 {
- tt.repo = "localGitRepo"
- t.Run(filepath.Base(tt.repo)+"/"+tt.rev, f)
+ for _, tt.repo = range altRepos {
+ old := tt
+ var m map[string]string
+ if tt.repo == hgrepo1 {
+ m = hgmap
+ }
+ if tt.info != nil {
+ info := *tt.info
+ tt.info = &info
+ tt.info.Name = remap(tt.info.Name, m)
+ tt.info.Version = remap(tt.info.Version, m)
+ tt.info.Short = remap(tt.info.Short, m)
+ }
+ tt.rev = remap(tt.rev, m)
+ t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
+ tt = old
+ }
+ }
+ }
+}
+
+func remap(name string, m map[string]string) string {
+ if m[name] != "" {
+ return m[name]
+ }
+ if AllHex(name) {
+ for k, v := range m {
+ if strings.HasPrefix(k, name) {
+ return v[:len(name)]
+ }
}
}
+ return name
}
--- /dev/null
+// Copyright 2018 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 ignore
+
+// Interactive debugging shell for codehost.Repo implementations.
+
+package main
+
+import (
+ "archive/zip"
+ "bufio"
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+ "time"
+
+ "cmd/go/internal/modfetch/codehost"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go run shell.go vcs remote\n")
+ os.Exit(2)
+}
+
+func main() {
+ codehost.WorkRoot = "/tmp/vcswork"
+ log.SetFlags(0)
+ log.SetPrefix("shell: ")
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 2 {
+ usage()
+ }
+
+ repo, err := codehost.NewRepo(flag.Arg(0), flag.Arg(1))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ b := bufio.NewReader(os.Stdin)
+ for {
+ fmt.Fprintf(os.Stderr, ">>> ")
+ line, err := b.ReadString('\n')
+ if err != nil {
+ log.Fatal(err)
+ }
+ f := strings.Fields(line)
+ if len(f) == 0 {
+ continue
+ }
+ switch f[0] {
+ default:
+ fmt.Fprintf(os.Stderr, "?unknown command\n")
+ continue
+ case "tags":
+ prefix := ""
+ if len(f) == 2 {
+ prefix = f[1]
+ }
+ if len(f) > 2 {
+ fmt.Fprintf(os.Stderr, "?usage: tags [prefix]\n")
+ continue
+ }
+ tags, err := repo.Tags(prefix)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+ for _, tag := range tags {
+ fmt.Printf("%s\n", tag)
+ }
+
+ case "stat":
+ if len(f) != 2 {
+ fmt.Fprintf(os.Stderr, "?usage: stat rev\n")
+ continue
+ }
+ info, err := repo.Stat(f[1])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+ fmt.Printf("name=%s short=%s version=%s time=%s\n", info.Name, info.Short, info.Version, info.Time.UTC().Format(time.RFC3339))
+
+ case "read":
+ if len(f) != 3 {
+ fmt.Fprintf(os.Stderr, "?usage: read rev file\n")
+ continue
+ }
+ data, err := repo.ReadFile(f[1], f[2], 10<<20)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+ os.Stdout.Write(data)
+
+ case "zip":
+ if len(f) != 4 {
+ fmt.Fprintf(os.Stderr, "?usage: zip rev subdir output\n")
+ continue
+ }
+ subdir := f[2]
+ if subdir == "-" {
+ subdir = ""
+ }
+ rc, _, err := repo.ReadZip(f[1], subdir, 10<<20)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+ data, err := ioutil.ReadAll(rc)
+ rc.Close()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+
+ if f[3] != "-" {
+ if err := ioutil.WriteFile(f[3], data, 0666); err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+ }
+ z, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "?%s\n", err)
+ continue
+ }
+ for _, f := range z.File {
+ fmt.Printf("%s %d\n", f.Name, f.UncompressedSize64)
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright 2018 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 codehost
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "cmd/go/internal/par"
+ "cmd/go/internal/str"
+)
+
+func NewRepo(vcs, remote string) (Repo, error) {
+ type key struct {
+ vcs string
+ remote string
+ }
+ type cached struct {
+ repo Repo
+ err error
+ }
+ c := vcsRepoCache.Do(key{vcs, remote}, func() interface{} {
+ repo, err := newVCSRepo(vcs, remote)
+ return cached{repo, err}
+ }).(cached)
+
+ return c.repo, c.err
+}
+
+var vcsRepoCache par.Cache
+
+type vcsRepo struct {
+ remote string
+ cmd *vcsCmd
+ dir string
+
+ tagsOnce sync.Once
+ tags map[string]bool
+
+ branchesOnce sync.Once
+ branches map[string]bool
+
+ fetchOnce sync.Once
+ fetchErr error
+}
+
+func newVCSRepo(vcs, remote string) (Repo, error) {
+ if vcs == "git" {
+ return newGitRepo(remote, false)
+ }
+ cmd := vcsCmds[vcs]
+ if cmd == nil {
+ return nil, fmt.Errorf("unknown vcs: %s %s", vcs, remote)
+ }
+ if !strings.Contains(remote, "://") {
+ return nil, fmt.Errorf("invalid vcs remote: %s %s", vcs, remote)
+ }
+ r := &vcsRepo{remote: remote, cmd: cmd}
+ if cmd.init == nil {
+ return r, nil
+ }
+ dir, err := WorkDir(vcsWorkDirType+vcs, r.remote)
+ if err != nil {
+ return nil, err
+ }
+ r.dir = dir
+ if _, err := os.Stat(filepath.Join(dir, "."+vcs)); err != nil {
+ if _, err := Run(dir, cmd.init(r.remote)); err != nil {
+ os.RemoveAll(dir)
+ return nil, err
+ }
+ }
+ return r, nil
+}
+
+const vcsWorkDirType = "vcs1."
+
+type vcsCmd struct {
+ vcs string // vcs name "hg"
+ init func(remote string) []string // cmd to init repo to track remote
+ tags func(remote string) []string // cmd to list local tags
+ tagRE *regexp.Regexp // regexp to extract tag names from output of tags cmd
+ branches func(remote string) []string // cmd to list local branches
+ branchRE *regexp.Regexp // regexp to extract branch names from output of tags cmd
+ badLocalRevRE *regexp.Regexp // regexp of names that must not be served out of local cache without doing fetch first
+ statLocal func(rev, remote string) []string // cmd to stat local rev
+ parseStat func(rev, out string) (*RevInfo, error) // cmd to parse output of statLocal
+ fetch []string // cmd to fetch everything from remote
+ latest string // name of latest commit on remote (tip, HEAD, etc)
+ readFile func(rev, file, remote string) []string // cmd to read rev's file
+ readZip func(rev, subdir, remote, target string) []string // cmd to read rev's subdir as zip file
+}
+
+var re = regexp.MustCompile
+
+var vcsCmds = map[string]*vcsCmd{
+ "hg": {
+ vcs: "hg",
+ init: func(remote string) []string {
+ return []string{"hg", "clone", "-U", remote, "."}
+ },
+ tags: func(remote string) []string {
+ return []string{"hg", "tags", "-q"}
+ },
+ tagRE: re(`(?m)^[^\n]+$`),
+ branches: func(remote string) []string {
+ return []string{"hg", "branches", "-c", "-q"}
+ },
+ branchRE: re(`(?m)^[^\n]+$`),
+ badLocalRevRE: re(`(?m)^(tip)$`),
+ statLocal: func(rev, remote string) []string {
+ return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate} {tags}"}
+ },
+ parseStat: hgParseStat,
+ fetch: []string{"hg", "pull", "-f"},
+ latest: "tip",
+ readFile: func(rev, file, remote string) []string {
+ return []string{"hg", "cat", "-r", rev, file}
+ },
+ readZip: func(rev, subdir, remote, target string) []string {
+ pattern := []string{}
+ if subdir != "" {
+ pattern = []string{"-I", subdir + "/**"}
+ }
+ return str.StringList("hg", "archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/", pattern, target)
+ },
+ },
+
+ "svn": {
+ vcs: "svn",
+ init: nil, // no local checkout
+ tags: func(remote string) []string {
+ return []string{"svn", "list", strings.TrimSuffix(remote, "/trunk") + "/tags"}
+ },
+ tagRE: re(`(?m)^(.*?)/?$`),
+ statLocal: func(rev, remote string) []string {
+ suffix := "@" + rev
+ if rev == "latest" {
+ suffix = ""
+ }
+ return []string{"svn", "log", "-l1", "--xml", remote + suffix}
+ },
+ parseStat: svnParseStat,
+ latest: "latest",
+ readFile: func(rev, file, remote string) []string {
+ return []string{"svn", "cat", remote + "/" + file + "@" + rev}
+ },
+ // TODO: zip
+ },
+
+ "bzr": {
+ vcs: "bzr",
+ init: func(remote string) []string {
+ return []string{"bzr", "branch", "--use-existing-dir", remote, "."}
+ },
+ fetch: []string{
+ "bzr", "pull", "--overwrite-tags",
+ },
+ tags: func(remote string) []string {
+ return []string{"bzr", "tags"}
+ },
+ tagRE: re(`(?m)^\S+`),
+ badLocalRevRE: re(`^revno:-`),
+ statLocal: func(rev, remote string) []string {
+ return []string{"bzr", "log", "-l1", "--long", "--show-ids", "-r", rev}
+ },
+ parseStat: bzrParseStat,
+ latest: "revno:-1",
+ readFile: func(rev, file, remote string) []string {
+ return []string{"bzr", "cat", "-r", rev, file}
+ },
+ readZip: func(rev, subdir, remote, target string) []string {
+ extra := []string{}
+ if subdir != "" {
+ extra = []string{"./" + subdir}
+ }
+ return str.StringList("bzr", "export", "--format=zip", "-r", rev, "--root=prefix/", target, extra)
+ },
+ },
+
+ "fossil": {
+ vcs: "fossil",
+ init: func(remote string) []string {
+ return []string{"fossil", "clone", remote, ".fossil"}
+ },
+ fetch: []string{"fossil", "pull", "-R", ".fossil"},
+ tags: func(remote string) []string {
+ return []string{"fossil", "tag", "-R", ".fossil", "list"}
+ },
+ tagRE: re(`XXXTODO`),
+ statLocal: func(rev, remote string) []string {
+ return []string{"fossil", "info", "-R", ".fossil", rev}
+ },
+ parseStat: fossilParseStat,
+ latest: "trunk",
+ readFile: func(rev, file, remote string) []string {
+ return []string{"fossil", "cat", "-R", ".fossil", "-r", rev, file}
+ },
+ readZip: func(rev, subdir, remote, target string) []string {
+ extra := []string{}
+ if subdir != "" && !strings.ContainsAny(subdir, "*?[],") {
+ extra = []string{"--include", subdir}
+ }
+ // Note that vcsRepo.ReadZip below rewrites this command
+ // to run in a different directory, to work around a fossil bug.
+ return str.StringList("fossil", "zip", "-R", ".fossil", "--name", "prefix", extra, rev, target)
+ },
+ },
+}
+
+func (r *vcsRepo) loadTags() {
+ out, err := Run(r.dir, r.cmd.tags(r.remote))
+ if err != nil {
+ return
+ }
+
+ // Run tag-listing command and extract tags.
+ r.tags = make(map[string]bool)
+ for _, tag := range r.cmd.tagRE.FindAllString(string(out), -1) {
+ if r.cmd.badLocalRevRE != nil && r.cmd.badLocalRevRE.MatchString(tag) {
+ continue
+ }
+ r.tags[tag] = true
+ }
+}
+
+func (r *vcsRepo) loadBranches() {
+ if r.cmd.branches == nil {
+ return
+ }
+
+ out, err := Run(r.dir, r.cmd.branches(r.remote))
+ if err != nil {
+ return
+ }
+
+ r.branches = make(map[string]bool)
+ for _, branch := range r.cmd.branchRE.FindAllString(string(out), -1) {
+ if r.cmd.badLocalRevRE != nil && r.cmd.badLocalRevRE.MatchString(branch) {
+ continue
+ }
+ r.branches[branch] = true
+ }
+}
+
+func (r *vcsRepo) Tags(prefix string) ([]string, error) {
+ r.tagsOnce.Do(r.loadTags)
+
+ tags := []string{}
+ for tag := range r.tags {
+ if strings.HasPrefix(tag, prefix) {
+ tags = append(tags, tag)
+ }
+ }
+ sort.Strings(tags)
+ return tags, nil
+}
+
+func (r *vcsRepo) Stat(rev string) (*RevInfo, error) {
+ if rev == "latest" {
+ rev = r.cmd.latest
+ }
+ r.branchesOnce.Do(r.loadBranches)
+ revOK := (r.cmd.badLocalRevRE == nil || !r.cmd.badLocalRevRE.MatchString(rev)) && !r.branches[rev]
+ if revOK {
+ if info, err := r.statLocal(rev); err == nil {
+ return info, nil
+ }
+ }
+
+ r.fetchOnce.Do(r.fetch)
+ if r.fetchErr != nil {
+ return nil, r.fetchErr
+ }
+ info, err := r.statLocal(rev)
+ if err != nil {
+ return nil, err
+ }
+ if !revOK {
+ info.Version = info.Name
+ }
+ return info, nil
+}
+
+func (r *vcsRepo) fetch() {
+ _, r.fetchErr = Run(r.dir, r.cmd.fetch)
+}
+
+func (r *vcsRepo) statLocal(rev string) (*RevInfo, error) {
+ out, err := Run(r.dir, r.cmd.statLocal(rev, r.remote))
+ if err != nil {
+ return nil, fmt.Errorf("unknown revision %s", rev)
+ }
+ return r.cmd.parseStat(rev, string(out))
+}
+
+func (r *vcsRepo) Latest() (*RevInfo, error) {
+ return r.Stat("latest")
+}
+
+func (r *vcsRepo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
+ if rev == "latest" {
+ rev = r.cmd.latest
+ }
+ _, err := r.Stat(rev) // download rev into local repo
+ if err != nil {
+ return nil, err
+ }
+ out, err := Run(r.dir, r.cmd.readFile(rev, file, r.remote))
+ if err != nil {
+ return nil, os.ErrNotExist
+ }
+ return out, nil
+}
+
+func (r *vcsRepo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) {
+ if rev == "latest" {
+ rev = r.cmd.latest
+ }
+ f, err := ioutil.TempFile("", "go-readzip-*.zip")
+ if err != nil {
+ return nil, "", err
+ }
+ if r.cmd.vcs == "fossil" {
+ // If you run
+ // fossil zip -R .fossil --name prefix trunk /tmp/x.zip
+ // fossil fails with "unable to create directory /tmp" [sic].
+ // Change the command to run in /tmp instead,
+ // replacing the -R argument with an absolute path.
+ args := r.cmd.readZip(rev, subdir, r.remote, filepath.Base(f.Name()))
+ for i := range args {
+ if args[i] == ".fossil" {
+ args[i] = filepath.Join(r.dir, ".fossil")
+ }
+ }
+ _, err = Run(filepath.Dir(f.Name()), args)
+ } else {
+ _, err = Run(r.dir, r.cmd.readZip(rev, subdir, r.remote, f.Name()))
+ }
+ if err != nil {
+ f.Close()
+ os.Remove(f.Name())
+ return nil, "", err
+ }
+ return &deleteCloser{f}, "", nil
+}
+
+// deleteCloser is a file that gets deleted on Close.
+type deleteCloser struct {
+ *os.File
+}
+
+func (d *deleteCloser) Close() error {
+ defer os.Remove(d.File.Name())
+ return d.File.Close()
+}
+
+func hgParseStat(rev, out string) (*RevInfo, error) {
+ f := strings.Fields(string(out))
+ if len(f) < 3 {
+ return nil, fmt.Errorf("unexpected response from hg log: %q", out)
+ }
+ hash := f[0]
+ version := rev
+ if strings.HasPrefix(hash, version) {
+ version = hash // extend to full hash
+ }
+ t, err := strconv.ParseInt(f[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid time from hg log: %q", out)
+ }
+
+ var tags []string
+ for _, tag := range f[3:] {
+ if tag != "tip" {
+ tags = append(tags, tag)
+ }
+ }
+ sort.Strings(tags)
+
+ info := &RevInfo{
+ Name: hash,
+ Short: ShortenSHA1(hash),
+ Time: time.Unix(t, 0).UTC(),
+ Version: version,
+ Tags: tags,
+ }
+ return info, nil
+}
+
+func svnParseStat(rev, out string) (*RevInfo, error) {
+ var log struct {
+ Logentry struct {
+ Revision int64 `xml:"revision,attr"`
+ Date string `xml:"date"`
+ } `xml:"logentry"`
+ }
+ if err := xml.Unmarshal([]byte(out), &log); err != nil {
+ return nil, fmt.Errorf("unexpected response from svn log --xml: %v\n%s", err, out)
+ }
+
+ t, err := time.Parse(time.RFC3339, log.Logentry.Date)
+ if err != nil {
+ return nil, fmt.Errorf("unexpected response from svn log --xml: %v\n%s", err, out)
+ }
+
+ info := &RevInfo{
+ Name: fmt.Sprintf("%d", log.Logentry.Revision),
+ Short: fmt.Sprintf("%012d", log.Logentry.Revision),
+ Time: t.UTC(),
+ Version: rev,
+ }
+ return info, nil
+}
+
+func bzrParseStat(rev, out string) (*RevInfo, error) {
+ var revno int64
+ var tm time.Time
+ for _, line := range strings.Split(out, "\n") {
+ if line == "" || line[0] == ' ' || line[0] == '\t' {
+ // End of header, start of commit message.
+ break
+ }
+ if line[0] == '-' {
+ continue
+ }
+ i := strings.Index(line, ":")
+ if i < 0 {
+ // End of header, start of commit message.
+ break
+ }
+ key, val := line[:i], strings.TrimSpace(line[i+1:])
+ switch key {
+ case "revno":
+ if j := strings.Index(val, " "); j >= 0 {
+ val = val[:j]
+ }
+ i, err := strconv.ParseInt(val, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("unexpected revno from bzr log: %q", line)
+ }
+ revno = i
+ case "timestamp":
+ j := strings.Index(val, " ")
+ if j < 0 {
+ return nil, fmt.Errorf("unexpected timestamp from bzr log: %q", line)
+ }
+ t, err := time.Parse("2006-01-02 15:04:05 -0700", val[j+1:])
+ if err != nil {
+ return nil, fmt.Errorf("unexpected timestamp from bzr log: %q", line)
+ }
+ tm = t.UTC()
+ }
+ }
+ if revno == 0 || tm.IsZero() {
+ return nil, fmt.Errorf("unexpected response from bzr log: %q", out)
+ }
+
+ info := &RevInfo{
+ Name: fmt.Sprintf("%d", revno),
+ Short: fmt.Sprintf("%012d", revno),
+ Time: tm,
+ Version: rev,
+ }
+ return info, nil
+}
+
+func fossilParseStat(rev, out string) (*RevInfo, error) {
+ for _, line := range strings.Split(out, "\n") {
+ if strings.HasPrefix(line, "uuid:") {
+ f := strings.Fields(line)
+ if len(f) != 5 || len(f[1]) != 40 || f[4] != "UTC" {
+ return nil, fmt.Errorf("unexpected response from fossil info: %q", line)
+ }
+ t, err := time.Parse("2006-01-02 15:04:05", f[2]+" "+f[3])
+ if err != nil {
+ return nil, fmt.Errorf("unexpected response from fossil info: %q", line)
+ }
+ hash := f[1]
+ version := rev
+ if strings.HasPrefix(hash, version) {
+ version = hash // extend to full hash
+ }
+ info := &RevInfo{
+ Name: hash,
+ Short: ShortenSHA1(hash),
+ Time: t,
+ Version: version,
+ }
+ return info, nil
+ }
+ }
+ return nil, fmt.Errorf("unexpected response from fossil info: %q", out)
+}
import (
"archive/zip"
- "bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
- "path/filepath"
"regexp"
- "strconv"
"strings"
"time"
- "cmd/go/internal/modconv"
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/modfile"
"cmd/go/internal/module"
// A codeRepo implements modfetch.Repo using an underlying codehost.Repo.
type codeRepo struct {
- modPath string
+ modPath string
+
code codehost.Repo
codeRoot string
codeDir string
pseudoMajor string
}
-func newCodeRepo(code codehost.Repo, path string) (Repo, error) {
- codeRoot := code.Root()
- if !hasPathPrefix(path, codeRoot) {
- return nil, fmt.Errorf("mismatched repo: found %s for %s", codeRoot, path)
+func newCodeRepo(code codehost.Repo, root, path string) (Repo, error) {
+ if !hasPathPrefix(path, root) {
+ return nil, fmt.Errorf("mismatched repo: found %s for %s", root, path)
}
pathPrefix, pathMajor, ok := module.SplitPathVersion(path)
if !ok {
//
// Compute codeDir = bar, the subdirectory within the repo
// corresponding to the module root.
- codeDir := strings.Trim(strings.TrimPrefix(pathPrefix, codeRoot), "/")
+ codeDir := strings.Trim(strings.TrimPrefix(pathPrefix, root), "/")
if strings.HasPrefix(path, "gopkg.in/") {
// But gopkg.in is a special legacy case, in which pathPrefix does not start with codeRoot.
// For example we might have:
r := &codeRepo{
modPath: path,
code: code,
- codeRoot: codeRoot,
+ codeRoot: root,
codeDir: codeDir,
pathPrefix: pathPrefix,
pathMajor: pathMajor,
if r.codeDir != "" {
v = v[len(r.codeDir)+1:]
}
- if !semver.IsValid(v) || v != semver.Canonical(v) || isPseudoVersion(v) || !module.MatchPathMajor(v, r.pathMajor) {
+ if !semver.IsValid(v) || v != semver.Canonical(v) || IsPseudoVersion(v) || !module.MatchPathMajor(v, r.pathMajor) {
continue
}
list = append(list, v)
if err != nil {
return nil, err
}
- return r.convert(info)
+ return r.convert(info, rev)
}
func (r *codeRepo) Latest() (*RevInfo, error) {
if err != nil {
return nil, err
}
- return r.convert(info)
+ return r.convert(info, "")
}
-func (r *codeRepo) convert(info *codehost.RevInfo) (*RevInfo, error) {
- versionOK := func(v string) bool {
- return semver.IsValid(v) && v == semver.Canonical(v) && !isPseudoVersion(v) && module.MatchPathMajor(v, r.pathMajor)
+func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, error) {
+ info2 := &RevInfo{
+ Name: info.Name,
+ Short: info.Short,
+ Time: info.Time,
}
- v := info.Version
- if r.codeDir == "" {
- if !versionOK(v) {
- v = PseudoVersion(r.pseudoMajor, info.Time, info.Short)
- }
+
+ // Determine version.
+ if semver.IsValid(statVers) && statVers == semver.Canonical(statVers) && module.MatchPathMajor(statVers, r.pathMajor) {
+ // The original call was repo.Stat(statVers), and requestedVersion is OK, so use it.
+ info2.Version = statVers
} else {
- p := r.codeDir + "/"
- if strings.HasPrefix(v, p) && versionOK(v[len(p):]) {
+ // Otherwise derive a version from a code repo tag.
+ // Tag must have a prefix matching codeDir.
+ p := ""
+ if r.codeDir != "" {
+ p = r.codeDir + "/"
+ }
+
+ tagOK := func(v string) bool {
+ if !strings.HasPrefix(v, p) {
+ return false
+ }
v = v[len(p):]
+ return semver.IsValid(v) && v == semver.Canonical(v) && module.MatchPathMajor(v, r.pathMajor) && !IsPseudoVersion(v)
+ }
+
+ // If info.Version is OK, use it.
+ if tagOK(info.Version) {
+ info2.Version = info.Version[len(p):]
} else {
- v = PseudoVersion(r.pseudoMajor, info.Time, info.Short)
+ // Otherwise look through all known tags for latest in semver ordering.
+ for _, tag := range info.Tags {
+ if tagOK(tag) && semver.Compare(info2.Version, tag[len(p):]) < 0 {
+ info2.Version = tag[len(p):]
+ }
+ }
+ // Otherwise make a pseudo-version.
+ if info2.Version == "" {
+ info2.Version = PseudoVersion(r.pseudoMajor, info.Time, info.Short)
+ }
}
}
- info2 := &RevInfo{
- Name: info.Name,
- Short: info.Short,
- Time: info.Time,
- Version: v,
+ // Do not allow a successful stat of a pseudo-version for a subdirectory
+ // unless the subdirectory actually does have a go.mod.
+ if IsPseudoVersion(info2.Version) && r.codeDir != "" {
+ _, _, _, err := r.findDir(info2.Version)
+ if err != nil {
+ // TODO: It would be nice to return an error like "not a module".
+ // Right now we return "missing go.mod", which is a little confusing.
+ return nil, err
+ }
}
+
return info2, nil
}
func (r *codeRepo) revToRev(rev string) string {
if semver.IsValid(rev) {
- if isPseudoVersion(rev) {
+ if IsPseudoVersion(rev) {
i := strings.Index(rev, "-")
j := strings.Index(rev[i+1:], "-")
return rev[i+1+j+1:]
if err != nil {
return "", "", nil, err
}
- if r.pathMajor == "" || strings.HasPrefix(r.pathMajor, ".") {
- if r.codeDir == "" {
- return rev, "", nil, nil
- }
- file1 := path.Join(r.codeDir, "go.mod")
- gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
- if err1 != nil {
- return "", "", nil, fmt.Errorf("missing go.mod")
- }
- return rev, r.codeDir, gomod1, nil
- }
- // Suppose pathMajor is "/v2".
- // Either go.mod should claim v2 and v2/go.mod should not exist,
- // or v2/go.mod should exist and claim v2. Not both.
- // Note that we don't check the full path, just the major suffix,
- // because of replacement modules. This might be a fork of
- // the real module, found at a different path, usable only in
- // a replace directive.
+ // Load info about go.mod but delay consideration
+ // (except I/O error) until we rule out v2/go.mod.
file1 := path.Join(r.codeDir, "go.mod")
- file2 := path.Join(r.codeDir, r.pathMajor[1:], "go.mod")
gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
- gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
- found1 := err1 == nil && isMajor(gomod1, r.pathMajor)
- found2 := err2 == nil && isMajor(gomod2, r.pathMajor)
-
- if err2 == nil && !found2 {
- return "", "", nil, fmt.Errorf("%s has non-...%s module path", file2, r.pathMajor)
- }
- if found1 && found2 {
- return "", "", nil, fmt.Errorf("both %s and %s claim ...%s module", file1, file2, r.pathMajor)
- }
- if found2 {
- return rev, filepath.Join(r.codeDir, r.pathMajor), gomod2, nil
+ if err1 != nil && !os.IsNotExist(err1) {
+ return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.pathPrefix, file1, rev, err1)
+ }
+ mpath1 := modfile.ModulePath(gomod1)
+ found1 := err1 == nil && isMajor(mpath1, r.pathMajor)
+
+ var file2 string
+ if r.pathMajor != "" && !strings.HasPrefix(r.pathMajor, ".") {
+ // Suppose pathMajor is "/v2".
+ // Either go.mod should claim v2 and v2/go.mod should not exist,
+ // or v2/go.mod should exist and claim v2. Not both.
+ // Note that we don't check the full path, just the major suffix,
+ // because of replacement modules. This might be a fork of
+ // the real module, found at a different path, usable only in
+ // a replace directive.
+ dir2 := path.Join(r.codeDir, r.pathMajor[1:])
+ file2 = path.Join(dir2, "go.mod")
+ gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
+ if err2 != nil && !os.IsNotExist(err2) {
+ return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.pathPrefix, file2, rev, err2)
+ }
+ mpath2 := modfile.ModulePath(gomod2)
+ found2 := err2 == nil && isMajor(mpath2, r.pathMajor)
+
+ if found1 && found2 {
+ return "", "", nil, fmt.Errorf("%s/%s and ...%s/go.mod both have ...%s module paths at revision %s", r.pathPrefix, file1, r.pathMajor, r.pathMajor, rev)
+ }
+ if found2 {
+ return rev, dir2, gomod2, nil
+ }
+ if err2 == nil {
+ if mpath2 == "" {
+ return "", "", nil, fmt.Errorf("%s/%s is missing module path at revision %s", r.pathPrefix, file2, rev)
+ }
+ return "", "", nil, fmt.Errorf("%s/%s has non-...%s module path %q at revision %s", r.pathPrefix, file2, r.pathMajor, mpath2, rev)
+ }
}
+
+ // Not v2/go.mod, so it's either go.mod or nothing. Which is it?
if found1 {
+ // Explicit go.mod with matching module path OK.
return rev, r.codeDir, gomod1, nil
}
- return "", "", nil, fmt.Errorf("missing or invalid go.mod")
-}
-
-func isMajor(gomod []byte, pathMajor string) bool {
- return strings.HasSuffix(modPath(gomod), pathMajor)
-}
-
-var moduleStr = []byte("module")
-
-func modPath(mod []byte) string {
- for len(mod) > 0 {
- line := mod
- mod = nil
- if i := bytes.IndexByte(line, '\n'); i >= 0 {
- line, mod = line[:i], line[i+1:]
+ if err1 == nil {
+ // Explicit go.mod with non-matching module path disallowed.
+ suffix := ""
+ if file2 != "" {
+ suffix = fmt.Sprintf(" (and ...%s/go.mod does not exist)", r.pathMajor)
}
- line = bytes.TrimSpace(line)
- if !bytes.HasPrefix(line, moduleStr) {
- continue
+ if mpath1 == "" {
+ return "", "", nil, fmt.Errorf("%s is missing module path%s at revision %s", file1, suffix, rev)
}
- line = line[len(moduleStr):]
- n := len(line)
- line = bytes.TrimSpace(line)
- if len(line) == n || len(line) == 0 {
- continue
+ if r.pathMajor != "" { // ".v1", ".v2" for gopkg.in
+ return "", "", nil, fmt.Errorf("%s has non-...%s module path %q%s at revision %s", file1, r.pathMajor, mpath1, suffix, rev)
}
+ return "", "", nil, fmt.Errorf("%s has post-%s module path %q%s at revision %s", file1, semver.Major(version), mpath1, suffix, rev)
+ }
- if line[0] == '"' || line[0] == '`' {
- p, err := strconv.Unquote(string(line))
- if err != nil {
- return "" // malformed quoted string or multiline module path
- }
- return p
- }
+ if r.codeDir == "" && (r.pathMajor == "" || strings.HasPrefix(r.pathMajor, ".")) {
+ // Implicit go.mod at root of repo OK for v0/v1 and for gopkg.in.
+ return rev, "", nil, nil
+ }
- return string(line)
+ // Implicit go.mod below root of repo or at v2+ disallowed.
+ // Be clear about possibility of using either location for v2+.
+ if file2 != "" {
+ return "", "", nil, fmt.Errorf("missing %s/go.mod and ...%s/go.mod at revision %s", r.pathPrefix, r.pathMajor, rev)
}
- return "" // missing module path
+ return "", "", nil, fmt.Errorf("missing %s/go.mod at revision %s", r.pathPrefix, rev)
+}
+
+func isMajor(mpath, pathMajor string) bool {
+ if mpath == "" {
+ return false
+ }
+ if pathMajor == "" {
+ // mpath must NOT have version suffix.
+ i := len(mpath)
+ for i > 0 && '0' <= mpath[i-1] && mpath[i-1] <= '9' {
+ i--
+ }
+ if i < len(mpath) && i >= 2 && mpath[i-1] == 'v' && mpath[i-2] == '/' {
+ // Found valid suffix.
+ return false
+ }
+ return true
+ }
+ // Otherwise pathMajor is ".v1", ".v2" (gopkg.in), or "/v2", "/v3" etc.
+ return strings.HasSuffix(mpath, pathMajor)
}
func (r *codeRepo) GoMod(version string) (data []byte, err error) {
}
data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod)
if err != nil {
- if e := strings.ToLower(err.Error()); strings.Contains(e, "not found") || strings.Contains(e, "404") { // TODO
+ if os.IsNotExist(err) {
return r.legacyGoMod(rev, dir), nil
}
return nil, err
return data, nil
}
-var altConfigs = []string{
- "Gopkg.lock",
-
- "GLOCKFILE",
- "Godeps/Godeps.json",
- "dependencies.tsv",
- "glide.lock",
- "vendor.conf",
- "vendor.yml",
- "vendor/manifest",
- "vendor/vendor.json",
-}
-
func (r *codeRepo) legacyGoMod(rev, dir string) []byte {
- mf := new(modfile.File)
- mf.AddModuleStmt(r.modPath)
- for _, file := range altConfigs {
- data, err := r.code.ReadFile(rev, path.Join(dir, file), codehost.MaxGoMod)
- if err != nil {
- continue
- }
- convert := modconv.Converters[file]
- if convert == nil {
- continue
- }
- if err := ConvertLegacyConfig(mf, file, data); err != nil {
- continue
- }
- break
- }
- data, err := mf.Format()
- if err != nil {
- return []byte(fmt.Sprintf("%s\nmodule %q\n", modconv.Prefix, r.modPath))
- }
- return append([]byte(modconv.Prefix+"\n"), data...)
+ // We used to try to build a go.mod reflecting pre-existing
+ // package management metadata files, but the conversion
+ // was inherently imperfect (because those files don't have
+ // exactly the same semantics as go.mod) and, when done
+ // for dependencies in the middle of a build, impossible to
+ // correct. So we stopped.
+ // Return a fake go.mod that simply declares the module path.
+ return []byte(fmt.Sprintf("module %s\n", modfile.AutoQuote(r.modPath)))
}
func (r *codeRepo) modPrefix(rev string) string {
subdir := strings.Trim(strings.TrimPrefix(dir, actualDir), "/")
// Spool to local file.
- f, err := ioutil.TempFile(tmpdir, "vgo-codehost-")
+ f, err := ioutil.TempFile(tmpdir, "go-codehost-")
if err != nil {
dl.Close()
return "", err
if err != nil {
return "", err
}
- f2, err := ioutil.TempFile(tmpdir, "vgo-")
+ f2, err := ioutil.TempFile(tmpdir, "go-codezip-")
if err != nil {
return "", err
}
if !strings.HasPrefix(name, subdir) {
continue
}
+ if name == ".hg_archival.txt" {
+ // Inserted by hg archive.
+ // Not correct to drop from other version control systems, but too bad.
+ continue
+ }
name = strings.TrimPrefix(name, subdir)
if isVendoredPackage(name) {
continue
}
if !haveLICENSE && subdir != "" {
- if data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE); err == nil {
+ data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE)
+ if err == nil {
w, err := zw.Create(r.modPrefix(version) + "/LICENSE")
if err != nil {
return "", err
var pseudoVersionRE = regexp.MustCompile(`^v[0-9]+\.0\.0-[0-9]{14}-[A-Za-z0-9]+$`)
-func isPseudoVersion(v string) bool {
+// IsPseudoVersion reports whether v is a pseudo-version.
+func IsPseudoVersion(v string) bool {
return pseudoVersionRE.MatchString(v)
}
+
+// PseudoVersionTime returns the time stamp of the pseudo-version v.
+// It returns an error if v is not a pseudo-version or if the time stamp
+// embedded in the pseudo-version is not a valid time.
+func PseudoVersionTime(v string) (time.Time, error) {
+ if !IsPseudoVersion(v) {
+ return time.Time{}, fmt.Errorf("not a pseudo-version")
+ }
+ i := strings.Index(v, "-") + 1
+ j := i + strings.Index(v[i:], "-")
+ t, err := time.Parse("20060102150405", v[i:j])
+ if err != nil {
+ return time.Time{}, fmt.Errorf("malformed pseudo-version %q", v)
+ }
+ return t, nil
+}
"cmd/go/internal/modfetch/codehost"
)
-func init() {
- isTest = true
-}
-
func TestMain(m *testing.M) {
os.Exit(testMain(m))
}
return m.Run()
}
+const (
+ vgotest1git = "github.com/rsc/vgotest1"
+ vgotest1hg = "vcs-test.golang.org/hg/vgotest1.hg"
+)
+
+var altVgotests = []string{
+ vgotest1hg,
+}
+
var codeRepoTests = []struct {
path string
lookerr string
name: "80d85c5d4d17598a0e9055e7c175a32b415d6128",
short: "80d85c5d4d17",
time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
- ziperr: "missing go.mod",
+ ziperr: "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
},
{
path: "github.com/rsc/vgotest1",
- rev: "80d85",
- version: "v0.0.0-20180219231006-80d85c5d4d17",
+ rev: "80d85c5",
+ version: "v1.0.0",
name: "80d85c5d4d17598a0e9055e7c175a32b415d6128",
short: "80d85c5d4d17",
time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
{
path: "github.com/rsc/vgotest1",
rev: "mytag",
- version: "v0.0.0-20180219231006-80d85c5d4d17",
+ version: "v1.0.0",
name: "80d85c5d4d17598a0e9055e7c175a32b415d6128",
short: "80d85c5d4d17",
time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
},
{
path: "github.com/rsc/vgotest1/v2",
- rev: "80d85",
- version: "v2.0.0-20180219231006-80d85c5d4d17",
+ rev: "80d85c5",
+ version: "v2.0.0",
name: "80d85c5d4d17598a0e9055e7c175a32b415d6128",
short: "80d85c5d4d17",
time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
- gomoderr: "missing go.mod",
- ziperr: "missing go.mod",
+ gomoderr: "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
+ ziperr: "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
},
{
path: "github.com/rsc/vgotest1/v54321",
- rev: "80d85",
+ rev: "80d85c5",
version: "v54321.0.0-20180219231006-80d85c5d4d17",
name: "80d85c5d4d17598a0e9055e7c175a32b415d6128",
short: "80d85c5d4d17",
time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
- ziperr: "missing go.mod",
+ ziperr: "missing github.com/rsc/vgotest1/go.mod and .../v54321/go.mod at revision 80d85c5d4d17",
},
{
path: "github.com/rsc/vgotest1/submod",
rev: "v1.0.0",
- err: "unknown revision \"submod/v1.0.0\"",
+ err: "unknown revision submod/v1.0.0",
},
{
path: "github.com/rsc/vgotest1/submod",
rev: "v1.0.3",
- err: "unknown revision \"submod/v1.0.3\"",
+ err: "unknown revision submod/v1.0.3",
},
{
path: "github.com/rsc/vgotest1/submod",
name: "f18795870fb14388a21ef3ebc1d75911c8694f31",
short: "f18795870fb1",
time: time.Date(2018, 2, 19, 23, 16, 4, 0, time.UTC),
- gomoderr: "v2/go.mod has non-.../v2 module path",
+ gomoderr: "github.com/rsc/vgotest1/v2/go.mod has non-.../v2 module path \"github.com/rsc/vgotest\" at revision v2.0.3",
},
{
path: "github.com/rsc/vgotest1/v2",
name: "1f863feb76bc7029b78b21c5375644838962f88d",
short: "1f863feb76bc",
time: time.Date(2018, 2, 20, 0, 3, 38, 0, time.UTC),
- gomoderr: "both go.mod and v2/go.mod claim .../v2 module",
+ gomoderr: "github.com/rsc/vgotest1/go.mod and .../v2/go.mod both have .../v2 module paths at revision v2.0.4",
},
{
path: "github.com/rsc/vgotest1/v2",
time: time.Date(2018, 2, 20, 0, 3, 59, 0, time.UTC),
gomod: "module \"github.com/rsc/vgotest1/v2\" // v2/go.mod\n",
},
- {
- path: "go.googlesource.com/scratch",
- rev: "0f302529858",
- version: "v0.0.0-20180220024720-0f3025298580",
- name: "0f30252985809011f026b5a2d5cf456e021623da",
- short: "0f3025298580",
- time: time.Date(2018, 2, 20, 2, 47, 20, 0, time.UTC),
- gomod: "//vgo 0.0.4\n\nmodule go.googlesource.com/scratch\n",
- },
- {
- path: "go.googlesource.com/scratch/rsc",
- rev: "0f302529858",
- version: "v0.0.0-20180220024720-0f3025298580",
- name: "0f30252985809011f026b5a2d5cf456e021623da",
- short: "0f3025298580",
- time: time.Date(2018, 2, 20, 2, 47, 20, 0, time.UTC),
- gomod: "",
- },
- {
- path: "go.googlesource.com/scratch/cbro",
- rev: "0f302529858",
- version: "v0.0.0-20180220024720-0f3025298580",
- name: "0f30252985809011f026b5a2d5cf456e021623da",
- short: "0f3025298580",
- time: time.Date(2018, 2, 20, 2, 47, 20, 0, time.UTC),
- gomoderr: "missing go.mod",
- },
{
// redirect to github
path: "rsc.io/quote",
},
{
// package in subdirectory - custom domain
- path: "golang.org/x/net/context",
- lookerr: "module root is \"golang.org/x/net\"",
+ // In general we can't reject these definitively in Lookup,
+ // but gopkg.in is special.
+ path: "gopkg.in/yaml.v2/abc",
+ lookerr: "invalid module path \"gopkg.in/yaml.v2/abc\"",
},
{
// package in subdirectory - github
- path: "github.com/rsc/quote/buggy",
- rev: "c4d4236f",
- version: "v0.0.0-20180214154420-c4d4236f9242",
- name: "c4d4236f92427c64bfbcf1cc3f8142ab18f30b22",
- short: "c4d4236f9242",
- time: time.Date(2018, 2, 14, 15, 44, 20, 0, time.UTC),
- gomoderr: "missing go.mod",
+ // Because it's a package, Stat should fail entirely.
+ path: "github.com/rsc/quote/buggy",
+ rev: "c4d4236f",
+ err: "missing github.com/rsc/quote/buggy/go.mod at revision c4d4236f9242",
},
{
path: "gopkg.in/yaml.v2",
rev: "d670f940",
- version: "v2.0.0-20180109114331-d670f9405373",
+ version: "v2.0.0",
name: "d670f9405373e636a5a2765eea47fac0c9bc91a4",
short: "d670f9405373",
time: time.Date(2018, 1, 9, 11, 43, 31, 0, time.UTC),
- gomod: "//vgo 0.0.4\n\nmodule gopkg.in/yaml.v2\n",
+ gomod: "module gopkg.in/yaml.v2\n",
},
{
path: "gopkg.in/check.v1",
name: "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec",
short: "20d25e280405",
time: time.Date(2016, 12, 8, 18, 13, 25, 0, time.UTC),
- gomod: "//vgo 0.0.4\n\nmodule gopkg.in/check.v1\n",
+ gomod: "module gopkg.in/check.v1\n",
},
{
path: "gopkg.in/yaml.v2",
rev: "v2",
- version: "v2.0.0-20180328195020-5420a8b6744d",
+ version: "v2.2.1",
name: "5420a8b6744d3b0345ab293f6fcba19c978f1183",
short: "5420a8b6744d",
time: time.Date(2018, 3, 28, 19, 50, 20, 0, time.UTC),
{
path: "vcs-test.golang.org/go/mod/gitrepo1",
rev: "master",
- version: "v0.0.0-20180417194322-ede458df7cd0",
+ version: "v1.2.4-annotated",
name: "ede458df7cd0fdca520df19a33158086a8a68e81",
short: "ede458df7cd0",
time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
- gomod: "//vgo 0.0.4\n\nmodule vcs-test.golang.org/go/mod/gitrepo1\n",
+ gomod: "module vcs-test.golang.org/go/mod/gitrepo1\n",
},
{
path: "gopkg.in/natefinch/lumberjack.v2",
name: "a96e63847dc3c67d17befa69c303767e2f84e54f",
short: "a96e63847dc3",
time: time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC),
- gomod: "//vgo 0.0.4\n\nmodule gopkg.in/natefinch/lumberjack.v2\n",
+ gomod: "module gopkg.in/natefinch/lumberjack.v2\n",
},
{
path: "gopkg.in/natefinch/lumberjack.v2",
name: "a96e63847dc3c67d17befa69c303767e2f84e54f",
short: "a96e63847dc3",
time: time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC),
- gomod: "//vgo 0.0.4\n\nmodule gopkg.in/natefinch/lumberjack.v2\n",
+ gomod: "module gopkg.in/natefinch/lumberjack.v2\n",
},
}
}
defer os.RemoveAll(tmpdir)
for _, tt := range codeRepoTests {
- t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, func(t *testing.T) {
+ f := func(t *testing.T) {
repo, err := Lookup(tt.path)
- if err != nil {
- if tt.lookerr != "" {
- if err.Error() == tt.lookerr {
- return
- }
- t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookerr)
+ if tt.lookerr != "" {
+ if err != nil && err.Error() == tt.lookerr {
+ return
}
+ t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookerr)
+ }
+ if err != nil {
t.Fatalf("Lookup(%q): %v", tt.path, err)
}
if tt.mpath == "" {
t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
}
}
- })
+ }
+ t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, f)
+ if strings.HasPrefix(tt.path, vgotest1git) {
+ for _, alt := range altVgotests {
+ // Note: Communicating with f through tt; should be cleaned up.
+ old := tt
+ tt.path = alt + strings.TrimPrefix(tt.path, vgotest1git)
+ if strings.HasPrefix(tt.mpath, vgotest1git) {
+ tt.mpath = alt + strings.TrimPrefix(tt.mpath, vgotest1git)
+ }
+ var m map[string]string
+ if alt == vgotest1hg {
+ m = hgmap
+ }
+ tt.version = remap(tt.version, m)
+ tt.name = remap(tt.name, m)
+ tt.short = remap(tt.short, m)
+ tt.rev = remap(tt.rev, m)
+ tt.gomoderr = remap(tt.gomoderr, m)
+ tt.ziperr = remap(tt.ziperr, m)
+ t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, f)
+ tt = old
+ }
+ }
}
}
-var importTests = []struct {
- path string
- mpath string
- err string
-}{
- {
- path: "golang.org/x/net/context",
- mpath: "golang.org/x/net",
- },
- {
- path: "github.com/rsc/quote/buggy",
- mpath: "github.com/rsc/quote",
- },
- {
- path: "golang.org/x/net",
- mpath: "golang.org/x/net",
- },
- {
- path: "github.com/rsc/quote",
- mpath: "github.com/rsc/quote",
- },
- {
- path: "golang.org/x/foo/bar",
- err: "unknown module golang.org/x/foo/bar: no go-import tags",
- },
+var hgmap = map[string]string{
+ "github.com/rsc/vgotest1/": "vcs-test.golang.org/hg/vgotest1.hg/",
+ "f18795870fb14388a21ef3ebc1d75911c8694f31": "a9ad6d1d14eb544f459f446210c7eb3b009807c6",
+ "ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9": "f1fc0f22021b638d073d31c752847e7bf385def7",
+ "b769f2de407a4db81af9c5de0a06016d60d2ea09": "92c7eb888b4fac17f1c6bd2e1060a1b881a3b832",
+ "8afe2b2efed96e0880ecd2a69b98a53b8c2738b6": "4e58084d459ae7e79c8c2264d0e8e9a92eb5cd44",
+ "2f615117ce481c8efef46e0cc0b4b4dccfac8fea": "879ea98f7743c8eff54f59a918f3a24123d1cf46",
+ "80d85c5d4d17598a0e9055e7c175a32b415d6128": "e125018e286a4b09061079a81e7b537070b7ff71",
+ "1f863feb76bc7029b78b21c5375644838962f88d": "bf63880162304a9337477f3858f5b7e255c75459",
}
-func TestImport(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
-
- for _, tt := range importTests {
- t.Run(strings.Replace(tt.path, "/", "_", -1), func(t *testing.T) {
- repo, info, err := Import(tt.path, nil)
- if err != nil {
- if tt.err != "" {
- if err.Error() == tt.err {
- return
- }
- t.Errorf("Import(%q): %v, want error %q", tt.path, err, tt.err)
- }
- t.Fatalf("Lookup(%q): %v", tt.path, err)
- }
- if mpath := repo.ModulePath(); mpath != tt.mpath {
- t.Errorf("repo.ModulePath() = %q (%v), want %q", mpath, info.Version, tt.mpath)
+func remap(name string, m map[string]string) string {
+ if m[name] != "" {
+ return m[name]
+ }
+ if codehost.AllHex(name) {
+ for k, v := range m {
+ if strings.HasPrefix(k, name) {
+ return v[:len(name)]
}
- })
+ }
}
+ for k, v := range m {
+ name = strings.Replace(name, k, v, -1)
+ if codehost.AllHex(k) {
+ name = strings.Replace(name, k[:12], v[:12], -1)
+ }
+ }
+ return name
}
var codeRepoVersionsTests = []struct {
},
{
path: "gopkg.in/natefinch/lumberjack.v2",
- versions: []string{},
+ versions: nil,
},
}
path: "github.com/rsc/vgotest1",
version: "v0.0.0-20180219223237-a08abb797a67",
},
+ {
+ path: "github.com/rsc/vgotest1/subdir",
+ err: "missing github.com/rsc/vgotest1/subdir/go.mod at revision a08abb797a67",
+ },
{
path: "swtch.com/testmod",
version: "v1.1.1",
}
t.Fatalf("Latest(): %v", err)
}
+ if tt.err != "" {
+ t.Fatalf("Latest() = %v, want error %q", info.Version, tt.err)
+ }
if info.Version != tt.version {
t.Fatalf("Latest() = %v, want %v", info.Version, tt.version)
}
// fixedTagsRepo is a fake codehost.Repo that returns a fixed list of tags
type fixedTagsRepo struct {
- root string
tags []string
}
func (ch *fixedTagsRepo) Tags(string) ([]string, error) { return ch.tags, nil }
-func (ch *fixedTagsRepo) Root() string { return ch.root }
func (ch *fixedTagsRepo) Latest() (*codehost.RevInfo, error) { panic("not impl") }
func (ch *fixedTagsRepo) ReadFile(string, string, int64) ([]byte, error) { panic("not impl") }
func (ch *fixedTagsRepo) ReadZip(string, string, int64) (io.ReadCloser, string, error) {
func TestNonCanonicalSemver(t *testing.T) {
root := "golang.org/x/issue24476"
ch := &fixedTagsRepo{
- root: root,
tags: []string{
"", "huh?", "1.0.1",
// what about "version 1 dot dogcow"?
},
}
- cr, err := newCodeRepo(ch, root)
+ cr, err := newCodeRepo(ch, root, root)
if err != nil {
t.Fatal(err)
}
t.Fatal("unexpected versions returned:", v)
}
}
-
-var modPathTests = []struct {
- input []byte
- expected string
-}{
- {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
- {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
- {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
- {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
- {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"},
- {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"},
- {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"},
- {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"},
- {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"},
- {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"},
- {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""},
- {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"},
- {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"},
- {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"},
- {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"},
- {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"},
- {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
- {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
- {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
-}
-
-func TestModPath(t *testing.T) {
- for _, test := range modPathTests {
- t.Run(string(test.input), func(t *testing.T) {
- result := modPath(test.input)
- if result != test.expected {
- t.Fatalf("modPath(%s): %s, want %s", string(test.input), result, test.expected)
- }
- })
- }
-}
+++ /dev/null
-// Copyright 2018 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.
-
-// Support for custom domains.
-
-package modfetch
-
-import (
- "encoding/xml"
- "fmt"
- "io"
- "net/url"
- "os"
- "strings"
-
- "cmd/go/internal/modfetch/codehost"
- "cmd/go/internal/modfetch/gitrepo"
-)
-
-// metaImport represents the parsed <meta name="go-import"
-// content="prefix vcs reporoot" /> tags from HTML files.
-type metaImport struct {
- Prefix, VCS, RepoRoot string
-}
-
-func lookupCustomDomain(path string) (Repo, error) {
- dom := path
- if i := strings.Index(dom, "/"); i >= 0 {
- dom = dom[:i]
- }
- if !strings.Contains(dom, ".") {
- return nil, fmt.Errorf("unknown module %s: not a domain name", path)
- }
- var body io.ReadCloser
- err := webGetGoGet("https://"+path+"?go-get=1", &body)
- if body != nil {
- defer body.Close()
- }
- if err != nil {
- fmt.Fprintf(os.Stderr, "FindRepo: %v\n", err)
- return nil, err
- }
- // Note: accepting a non-200 OK here, so people can serve a
- // meta import in their http 404 page.
- imports, err := parseMetaGoImports(body)
- if err != nil {
- fmt.Fprintf(os.Stderr, "findRepo: %v\n", err)
- return nil, err
- }
- if len(imports) == 0 {
- return nil, fmt.Errorf("unknown module %s: no go-import tags", path)
- }
-
- // First look for new module definition.
- for _, imp := range imports {
- if path == imp.Prefix || strings.HasPrefix(path, imp.Prefix+"/") {
- if imp.VCS == "mod" {
- u, err := url.Parse(imp.RepoRoot)
- if err != nil {
- return nil, fmt.Errorf("invalid module URL %q", imp.RepoRoot)
- } else if u.Scheme != "https" {
- // TODO: Allow -insecure flag as a build flag?
- return nil, fmt.Errorf("invalid module URL %q: must be HTTPS", imp.RepoRoot)
- }
- return newProxyRepo(imp.RepoRoot, imp.Prefix), nil
- }
- }
- }
-
- // Fall back to redirections to known version control systems.
- for _, imp := range imports {
- if path == imp.Prefix {
- if !strings.HasPrefix(imp.RepoRoot, "https://") {
- // TODO: Allow -insecure flag as a build flag?
- return nil, fmt.Errorf("invalid server URL %q: must be HTTPS", imp.RepoRoot)
- }
- if imp.VCS == "git" {
- code, err := gitrepo.Repo(imp.RepoRoot, imp.Prefix)
- if err != nil {
- return nil, err
- }
- return newCodeRepo(code, path)
- }
- return nil, fmt.Errorf("unknown VCS, Repo: %s, %s", imp.VCS, imp.RepoRoot)
- }
- }
-
- // Check for redirect to repo root.
- for _, imp := range imports {
- if strings.HasPrefix(path, imp.Prefix+"/") {
- return nil, &ModuleSubdirError{imp.Prefix}
- }
- }
-
- return nil, fmt.Errorf("unknown module %s: no matching go-import tags", path)
-}
-
-type ModuleSubdirError struct {
- ModulePath string
-}
-
-func (e *ModuleSubdirError) Error() string {
- return fmt.Sprintf("module root is %q", e.ModulePath)
-}
-
-type customPrefix struct {
- codehost.Repo
- root string
-}
-
-func (c *customPrefix) Root() string {
- return c.root
-}
-
-// 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, err error) {
- d := xml.NewDecoder(r)
- d.CharsetReader = charsetReader
- d.Strict = false
- var t xml.Token
- for {
- t, err = d.RawToken()
- if err != nil {
- if err == io.EOF || len(imports) > 0 {
- 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],
- })
- }
- }
-}
-
-// 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 ""
-}
-
-// charsetReader returns a reader for the given charset. Currently
-// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
-// error which is printed by go get, so the user can find why the package
-// wasn't downloaded if the encoding is not supported. Note that, in
-// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
-// greater than 0x7f are not rejected).
-func charsetReader(charset string, input io.Reader) (io.Reader, error) {
- switch strings.ToLower(charset) {
- case "ascii":
- return input, nil
- default:
- return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
- }
-}
--- /dev/null
+// Copyright 2018 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 modfetch
+
+import (
+ "archive/zip"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "sync"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/dirhash"
+ "cmd/go/internal/module"
+ "cmd/go/internal/par"
+)
+
+var downloadCache par.Cache
+
+// Download downloads the specific module version to the
+// local download cache and returns the name of the directory
+// corresponding to the root of the module's file tree.
+func Download(mod module.Version) (dir string, err error) {
+ if SrcMod == "" {
+ // Do not download to current directory.
+ return "", fmt.Errorf("missing modfetch.SrcMod")
+ }
+
+ // The par.Cache here avoids duplicate work but also
+ // avoids conflicts from simultaneous calls by multiple goroutines
+ // for the same version.
+ type cached struct {
+ dir string
+ err error
+ }
+ c := downloadCache.Do(mod, func() interface{} {
+ modpath := mod.Path + "@" + mod.Version
+ dir = filepath.Join(SrcMod, modpath)
+ if files, _ := ioutil.ReadDir(dir); len(files) == 0 {
+ zipfile := filepath.Join(SrcMod, "cache/download", mod.Path, "@v", mod.Version+".zip")
+ if _, err := os.Stat(zipfile); err == nil {
+ // Use it.
+ // This should only happen if the mod/cache directory is preinitialized
+ // or if src/mod/path was removed but not src/mod/cache/download.
+ fmt.Fprintf(os.Stderr, "go: extracting %s %s\n", mod.Path, mod.Version)
+ } else {
+ if err := os.MkdirAll(filepath.Join(SrcMod, "cache/download", mod.Path, "@v"), 0777); err != nil {
+ return cached{"", err}
+ }
+ fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
+ if err := downloadZip(mod, zipfile); err != nil {
+ return cached{"", err}
+ }
+ }
+ if err := Unzip(dir, zipfile, modpath, 0); err != nil {
+ fmt.Fprintf(os.Stderr, "-> %s\n", err)
+ return cached{"", err}
+ }
+ }
+ checkSum(mod)
+ return cached{dir, nil}
+ }).(cached)
+ return c.dir, c.err
+}
+
+func downloadZip(mod module.Version, target string) error {
+ repo, err := Lookup(mod.Path)
+ if err != nil {
+ return err
+ }
+ tmpfile, err := repo.Zip(mod.Version, os.TempDir())
+ if err != nil {
+ return err
+ }
+ defer os.Remove(tmpfile)
+
+ // Double-check zip file looks OK.
+ z, err := zip.OpenReader(tmpfile)
+ if err != nil {
+ z.Close()
+ return err
+ }
+ prefix := mod.Path + "@" + mod.Version
+ for _, f := range z.File {
+ if !strings.HasPrefix(f.Name, prefix) {
+ z.Close()
+ return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
+ }
+ }
+ z.Close()
+
+ hash, err := dirhash.HashZip(tmpfile, dirhash.DefaultHash)
+ if err != nil {
+ return err
+ }
+ checkOneSum(mod, hash) // check before installing the zip file
+ r, err := os.Open(tmpfile)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+ w, err := os.Create(target)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(w, r); err != nil {
+ w.Close()
+ return fmt.Errorf("copying: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ return err
+ }
+ return ioutil.WriteFile(target+"hash", []byte(hash), 0666)
+}
+
+var GoSumFile string // path to go.sum; set by package modload
+
+var goSum struct {
+ mu sync.Mutex
+ m map[module.Version][]string // content of go.sum file (+ go.modverify if present)
+ enabled bool // whether to use go.sum at all
+ modverify string // path to go.modverify, to be deleted
+}
+
+// initGoSum initializes the go.sum data.
+// It reports whether use of go.sum is now enabled.
+// The goSum lock must be held.
+func initGoSum() bool {
+ if GoSumFile == "" {
+ return false
+ }
+ if goSum.m != nil {
+ return true
+ }
+
+ goSum.m = make(map[module.Version][]string)
+ data, err := ioutil.ReadFile(GoSumFile)
+ if err != nil && !os.IsNotExist(err) {
+ base.Fatalf("go: %v", err)
+ }
+ goSum.enabled = true
+ readGoSum(GoSumFile, data)
+
+ // Add old go.modverify file.
+ // We'll delete go.modverify in WriteGoSum.
+ alt := strings.TrimSuffix(GoSumFile, ".sum") + ".modverify"
+ if data, err := ioutil.ReadFile(alt); err == nil {
+ readGoSum(alt, data)
+ goSum.modverify = alt
+ }
+ return true
+}
+
+// emptyGoModHash is the hash of a 1-file tree containing a 0-length go.mod.
+// A bug caused us to write these into go.sum files for non-modules.
+// We detect and remove them.
+const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
+
+// readGoSum parses data, which is the content of file,
+// and adds it to goSum.m. The goSum lock must be held.
+func readGoSum(file string, data []byte) {
+ lineno := 0
+ for len(data) > 0 {
+ var line []byte
+ lineno++
+ i := bytes.IndexByte(data, '\n')
+ if i < 0 {
+ line, data = data, nil
+ } else {
+ line, data = data[:i], data[i+1:]
+ }
+ f := strings.Fields(string(line))
+ if len(f) == 0 {
+ // blank line; skip it
+ continue
+ }
+ if len(f) != 3 {
+ base.Fatalf("go: malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
+ }
+ if f[2] == emptyGoModHash {
+ // Old bug; drop it.
+ continue
+ }
+ mod := module.Version{Path: f[0], Version: f[1]}
+ goSum.m[mod] = append(goSum.m[mod], f[2])
+ }
+}
+
+// checkSum checks the given module's checksum.
+func checkSum(mod module.Version) {
+ if SrcMod == "" {
+ // Do not use current directory.
+ return
+ }
+
+ // Do the file I/O before acquiring the go.sum lock.
+ data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache/download", mod.Path, "@v", mod.Version+".ziphash"))
+ if err != nil {
+ if os.IsNotExist(err) {
+ // This can happen if someone does rm -rf GOPATH/src/cache/download. So it goes.
+ return
+ }
+ base.Fatalf("go: verifying %s@%s: %v", mod.Path, mod.Version, err)
+ }
+ h := strings.TrimSpace(string(data))
+ if !strings.HasPrefix(h, "h1:") {
+ base.Fatalf("go: verifying %s@%s: unexpected ziphash: %q", mod.Path, mod.Version, h)
+ }
+
+ checkOneSum(mod, h)
+}
+
+// checkGoMod checks the given module's go.mod checksum;
+// data is the go.mod content.
+func checkGoMod(path, version string, data []byte) {
+ h, err := dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
+ return ioutil.NopCloser(bytes.NewReader(data)), nil
+ })
+ if err != nil {
+ base.Fatalf("go: verifying %s %s go.mod: %v", path, version, err)
+ }
+
+ checkOneSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
+}
+
+// checkOneSum checks that the recorded hash for mod is h.
+func checkOneSum(mod module.Version, h string) {
+ goSum.mu.Lock()
+ defer goSum.mu.Unlock()
+ if !initGoSum() {
+ return
+ }
+
+ for _, vh := range goSum.m[mod] {
+ if h == vh {
+ return
+ }
+ if strings.HasPrefix(vh, "h1:") {
+ base.Fatalf("go: verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum: %v", mod.Path, mod.Version, h, vh)
+ }
+ }
+ if len(goSum.m[mod]) > 0 {
+ fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v", mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
+ }
+ goSum.m[mod] = append(goSum.m[mod], h)
+}
+
+// Sum returns the checksum for the downloaded copy of the given module,
+// if present in the download cache.
+func Sum(mod module.Version) string {
+ if SrcMod == "" {
+ // Do not use current directory.
+ return ""
+ }
+
+ data, err := ioutil.ReadFile(filepath.Join(SrcMod, "cache/download", mod.Path, "@v", mod.Version+".ziphash"))
+ if err != nil {
+ return ""
+ }
+ return strings.TrimSpace(string(data))
+}
+
+// WriteGoSum writes the go.sum file if it needs to be updated.
+func WriteGoSum() {
+ goSum.mu.Lock()
+ defer goSum.mu.Unlock()
+ if !initGoSum() {
+ return
+ }
+
+ var mods []module.Version
+ for m := range goSum.m {
+ mods = append(mods, m)
+ }
+ module.Sort(mods)
+ var buf bytes.Buffer
+ for _, m := range mods {
+ list := goSum.m[m]
+ sort.Strings(list)
+ for _, h := range list {
+ fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
+ }
+ }
+
+ data, _ := ioutil.ReadFile(GoSumFile)
+ if !bytes.Equal(data, buf.Bytes()) {
+ if err := ioutil.WriteFile(GoSumFile, buf.Bytes(), 0666); err != nil {
+ base.Fatalf("go: writing go.sum: %v", err)
+ }
+ }
+
+ if goSum.modverify != "" {
+ os.Remove(goSum.modverify)
+ }
+}
+++ /dev/null
-// Copyright 2018 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 github
-
-import (
- "fmt"
- "strings"
-
- "cmd/go/internal/modfetch/codehost"
- "cmd/go/internal/modfetch/gitrepo"
-)
-
-// Lookup returns the code repository enclosing the given module path,
-// which must begin with github.com/.
-func Lookup(path string) (codehost.Repo, error) {
- f := strings.Split(path, "/")
- if len(f) < 3 || f[0] != "github.com" {
- return nil, fmt.Errorf("github repo must be github.com/org/project")
- }
- path = f[0] + "/" + f[1] + "/" + f[2]
- return gitrepo.Repo("https://"+path, path)
-}
+++ /dev/null
-// Copyright 2018 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 gitrepo provides a Git-based implementation of codehost.Repo.
-package gitrepo
-
-import (
- "archive/zip"
- "bytes"
- "cmd/go/internal/modfetch/codehost"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
-)
-
-// Repo returns the code repository at the given Git remote reference.
-// The returned repo reports the given root as its module root.
-func Repo(remote, root string) (codehost.Repo, error) {
- return newRepo(remote, root, false)
-}
-
-// LocalRepo is like Repo but accepts both Git remote references
-// and paths to repositories on the local file system.
-// The returned repo reports the given root as its module root.
-func LocalRepo(remote, root string) (codehost.Repo, error) {
- return newRepo(remote, root, true)
-}
-
-const workDirType = "git2"
-
-func newRepo(remote, root string, localOK bool) (codehost.Repo, error) {
- r := &repo{remote: remote, root: root, canArchive: true}
- if strings.Contains(remote, "://") {
- // This is a remote path.
- dir, err := codehost.WorkDir(workDirType, r.remote)
- if err != nil {
- return nil, err
- }
- r.dir = dir
- if _, err := os.Stat(filepath.Join(dir, "objects")); err != nil {
- if _, err := codehost.Run(dir, "git", "init", "--bare"); err != nil {
- os.RemoveAll(dir)
- return nil, err
- }
- // We could just say git fetch https://whatever later,
- // but this lets us say git fetch origin instead, which
- // is a little nicer. More importantly, using a named remote
- // avoids a problem with Git LFS. See golang.org/issue/25605.
- if _, err := codehost.Run(dir, "git", "remote", "add", "origin", r.remote); err != nil {
- os.RemoveAll(dir)
- return nil, err
- }
- r.remote = "origin"
- }
- } else {
- // Local path.
- // Disallow colon (not in ://) because sometimes
- // that's rcp-style host:path syntax and sometimes it's not (c:\work).
- // The go command has always insisted on URL syntax for ssh.
- if strings.Contains(remote, ":") {
- return nil, fmt.Errorf("git remote cannot use host:path syntax")
- }
- if !localOK {
- return nil, fmt.Errorf("git remote must not be local directory")
- }
- r.local = true
- info, err := os.Stat(remote)
- if err != nil {
- return nil, err
- }
- if !info.IsDir() {
- return nil, fmt.Errorf("%s exists but is not a directory", remote)
- }
- r.dir = remote
- }
- return r, nil
-}
-
-type repo struct {
- remote string
- local bool
- root string
- dir string
- canArchive bool
-
- refsOnce sync.Once
- refs map[string]string
- refsErr error
-}
-
-func (r *repo) Root() string {
- return r.root
-}
-
-// loadRefs loads heads and tags references from the remote into the map r.refs.
-// Should only be called as r.refsOnce.Do(r.loadRefs).
-func (r *repo) loadRefs() {
- // The git protocol sends all known refs and ls-remote filters them on the client side,
- // so we might as well record both heads and tags in one shot.
- // Most of the time we only care about tags but sometimes we care about heads too.
- out, err := codehost.Run(r.dir, "git", "ls-remote", "-q", r.remote)
- if err != nil {
- r.refsErr = err
- return
- }
-
- r.refs = make(map[string]string)
- for _, line := range strings.Split(string(out), "\n") {
- f := strings.Fields(line)
- if len(f) != 2 {
- continue
- }
- if f[1] == "HEAD" || strings.HasPrefix(f[1], "refs/heads/") || strings.HasPrefix(f[1], "refs/tags/") {
- r.refs[f[1]] = f[0]
- }
- }
- for ref, hash := range r.refs {
- if strings.HasSuffix(ref, "^{}") { // record unwrapped annotated tag as value of tag
- r.refs[strings.TrimSuffix(ref, "^{}")] = hash
- delete(r.refs, ref)
- }
- }
-}
-
-func (r *repo) Tags(prefix string) ([]string, error) {
- r.refsOnce.Do(r.loadRefs)
- if r.refsErr != nil {
- return nil, r.refsErr
- }
-
- tags := []string{}
- for ref := range r.refs {
- if !strings.HasPrefix(ref, "refs/tags/") {
- continue
- }
- tag := ref[len("refs/tags/"):]
- if !strings.HasPrefix(tag, prefix) {
- continue
- }
- tags = append(tags, tag)
- }
- sort.Strings(tags)
- return tags, nil
-}
-
-func (r *repo) Latest() (*codehost.RevInfo, error) {
- r.refsOnce.Do(r.loadRefs)
- if r.refsErr != nil {
- return nil, r.refsErr
- }
- if r.refs["HEAD"] == "" {
- return nil, fmt.Errorf("no commits")
- }
- return r.Stat(r.refs["HEAD"])
-}
-
-// findRef finds some ref name for the given hash,
-// for use when the server requires giving a ref instead of a hash.
-// There may be multiple ref names for a given hash,
-// in which case this returns some name - it doesn't matter which.
-func (r *repo) findRef(hash string) (ref string, ok bool) {
- r.refsOnce.Do(r.loadRefs)
- for ref, h := range r.refs {
- if h == hash {
- return ref, true
- }
- }
- return "", false
-}
-
-func unshallow(gitDir string) []string {
- if _, err := os.Stat(filepath.Join(gitDir, "shallow")); err == nil {
- return []string{"--unshallow"}
- }
- return []string{}
-}
-
-// statOrArchive tries to stat the given rev in the local repository,
-// or else it tries to obtain an archive at the rev with the given arguments,
-// or else it falls back to aggressive fetching and then a local stat.
-// The archive step is an optimization for servers that support it
-// (most do not, but maybe that will change), to let us minimize
-// the amount of code downloaded.
-func (r *repo) statOrArchive(rev string, archiveArgs ...string) (info *codehost.RevInfo, archive []byte, err error) {
- // Do we have this rev?
- r.refsOnce.Do(r.loadRefs)
- var hash string
- if k := "refs/tags/" + rev; r.refs[k] != "" {
- hash = r.refs[k]
- } else if k := "refs/heads/" + rev; r.refs[k] != "" {
- hash = r.refs[k]
- rev = hash
- } else if rev == "HEAD" && r.refs["HEAD"] != "" {
- hash = r.refs["HEAD"]
- rev = hash
- } else if len(rev) >= 5 && len(rev) <= 40 && codehost.AllHex(rev) {
- hash = rev
- } else {
- return nil, nil, fmt.Errorf("unknown revision %q", rev)
- }
-
- out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash)
- if err == nil {
- hash = strings.TrimSpace(string(out))
- goto Found
- }
-
- // We don't have the rev. Can we fetch it?
- if r.local {
- return nil, nil, fmt.Errorf("unknown revision %q", rev)
- }
-
- if r.canArchive {
- // git archive with --remote requires a ref, not a hash.
- // Proceed only if we know a ref for this hash.
- if ref, ok := r.findRef(hash); ok {
- out, err := codehost.Run(r.dir, "git", "archive", "--format=zip", "--remote="+r.remote, "--prefix=prefix/", ref, archiveArgs)
- if err == nil {
- return &codehost.RevInfo{Version: rev}, out, nil
- }
- if bytes.Contains(err.(*codehost.RunError).Stderr, []byte("did not match any files")) {
- return nil, nil, fmt.Errorf("file not found")
- }
- if bytes.Contains(err.(*codehost.RunError).Stderr, []byte("Operation not supported by protocol")) {
- r.canArchive = false
- }
- }
- }
-
- // Maybe it's a prefix of a ref we know.
- // Iterating through all the refs is faster than doing unnecessary fetches.
- // This is not strictly correct, in that the short ref might be ambiguous
- // in the git repo as a whole, but not ambiguous in the list of named refs,
- // so that we will resolve it where the git server would not.
- // But this check avoids great expense, and preferring a known ref does
- // not seem like such a bad failure mode.
- if len(hash) >= 5 && len(hash) < 40 {
- var full string
- for _, h := range r.refs {
- if strings.HasPrefix(h, hash) {
- if full != "" {
- // Prefix is ambiguous even in the ref list!
- full = ""
- break
- }
- full = h
- }
- }
- if full != "" {
- hash = full
- }
- }
-
- // Fetch it.
- if len(hash) == 40 {
- name := hash
- if ref, ok := r.findRef(hash); ok {
- name = ref
- }
- if _, err = codehost.Run(r.dir, "git", "fetch", "--depth=1", r.remote, name); err == nil {
- goto Found
- }
- if !strings.Contains(err.Error(), "unadvertised object") && !strings.Contains(err.Error(), "no such remote ref") && !strings.Contains(err.Error(), "does not support shallow") {
- return nil, nil, err
- }
- }
-
- // It's a prefix, and we don't have a way to make the server resolve the prefix for us,
- // or it's a full hash but also an unadvertised object.
- // Download progressively more of the repo to look for it.
-
- // Fetch the main branch (non-shallow).
- if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), r.remote); err != nil {
- return nil, nil, err
- }
- if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil {
- hash = strings.TrimSpace(string(out))
- goto Found
- }
-
- // Fetch all tags (non-shallow).
- if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), "-f", "--tags", r.remote); err != nil {
- return nil, nil, err
- }
- if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil {
- hash = strings.TrimSpace(string(out))
- goto Found
- }
-
- // Fetch all branches (non-shallow).
- if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), "-f", r.remote, "refs/heads/*:refs/heads/*"); err != nil {
- return nil, nil, err
- }
- if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil {
- hash = strings.TrimSpace(string(out))
- goto Found
- }
-
- // Fetch all refs (non-shallow).
- if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), "-f", r.remote, "refs/*:refs/*"); err != nil {
- return nil, nil, err
- }
- if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil {
- hash = strings.TrimSpace(string(out))
- goto Found
- }
- return nil, nil, fmt.Errorf("cannot find hash %s", hash)
-Found:
-
- if strings.HasPrefix(hash, rev) {
- rev = hash
- }
-
- out, err = codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%ct", hash)
- if err != nil {
- return nil, nil, err
- }
- t, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64)
- if err != nil {
- return nil, nil, fmt.Errorf("invalid time from git log: %q", out)
- }
-
- info = &codehost.RevInfo{
- Name: hash,
- Short: codehost.ShortenSHA1(hash),
- Time: time.Unix(t, 0).UTC(),
- Version: rev,
- }
- return info, nil, nil
-}
-
-func (r *repo) Stat(rev string) (*codehost.RevInfo, error) {
- // If the server will give us a git archive, we can pull the
- // commit ID and the commit time out of the archive.
- // We want an archive as small as possible (for speed),
- // but we have to specify a pattern that matches at least one file name.
- // The pattern here matches README, .gitignore, .gitattributes,
- // and go.mod (and some other incidental file names);
- // hopefully most repos will have at least one of these.
- info, archive, err := r.statOrArchive(rev, "[Rg.][Ego][A.i][Dmt][Miao][Edgt]*")
- if err != nil {
- return nil, err
- }
- if archive != nil {
- return zip2info(archive, info.Version)
- }
- return info, nil
-}
-
-func (r *repo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
- info, archive, err := r.statOrArchive(rev, file)
- if err != nil {
- return nil, err
- }
- if archive != nil {
- return zip2file(archive, file, maxSize)
- }
- out, err := codehost.Run(r.dir, "git", "cat-file", "blob", info.Name+":"+file)
- if err != nil {
- return nil, fmt.Errorf("file not found")
- }
- return out, nil
-}
-
-func (r *repo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) {
- // TODO: Use maxSize or drop it.
- args := []string{}
- if subdir != "" {
- args = append(args, "--", subdir)
- }
- info, archive, err := r.statOrArchive(rev, args...)
- if err != nil {
- return nil, "", err
- }
- if archive == nil {
- archive, err = codehost.Run(r.dir, "git", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
- if err != nil {
- if bytes.Contains(err.(*codehost.RunError).Stderr, []byte("did not match any files")) {
- return nil, "", fmt.Errorf("file not found")
- }
- return nil, "", err
- }
- }
-
- return ioutil.NopCloser(bytes.NewReader(archive)), "", nil
-}
-
-func zip2info(archive []byte, rev string) (*codehost.RevInfo, error) {
- r, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive)))
- if err != nil {
- return nil, err
- }
- if r.Comment == "" {
- return nil, fmt.Errorf("missing commit ID in git zip comment")
- }
- hash := r.Comment
- if len(hash) != 40 || !codehost.AllHex(hash) {
- return nil, fmt.Errorf("invalid commit ID in git zip comment")
- }
- if len(r.File) == 0 {
- return nil, fmt.Errorf("git zip has no files")
- }
- info := &codehost.RevInfo{
- Name: hash,
- Short: codehost.ShortenSHA1(hash),
- Time: r.File[0].Modified.UTC(),
- Version: rev,
- }
- return info, nil
-}
-
-func zip2file(archive []byte, file string, maxSize int64) ([]byte, error) {
- r, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive)))
- if err != nil {
- return nil, err
- }
- for _, f := range r.File {
- if f.Name != "prefix/"+file {
- continue
- }
- rc, err := f.Open()
- if err != nil {
- return nil, err
- }
- defer rc.Close()
- l := &io.LimitedReader{R: rc, N: maxSize + 1}
- data, err := ioutil.ReadAll(l)
- if err != nil {
- return nil, err
- }
- if l.N <= 0 {
- return nil, fmt.Errorf("file %s too large", file)
- }
- return data, nil
- }
- return nil, fmt.Errorf("incomplete git zip archive: cannot find %s", file)
-}
+++ /dev/null
-// Copyright 2018 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 googlesource
-
-import (
- "fmt"
- "strings"
-
- "cmd/go/internal/modfetch/codehost"
- "cmd/go/internal/modfetch/gitrepo"
-)
-
-func Lookup(path string) (codehost.Repo, error) {
- i := strings.Index(path, "/")
- if i+1 == len(path) || !strings.HasSuffix(path[:i+1], ".googlesource.com/") {
- return nil, fmt.Errorf("not *.googlesource.com/*")
- }
- j := strings.Index(path[i+1:], "/")
- if j >= 0 {
- path = path[:i+1+j]
- }
- return gitrepo.Repo("https://"+path, path)
-}
+++ /dev/null
-// Copyright 2018 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.
-
-// TODO: Figure out what gopkg.in should do.
-
-package modfetch
-
-import (
- "cmd/go/internal/modfetch/codehost"
- "cmd/go/internal/modfetch/gitrepo"
- "cmd/go/internal/modfile"
- "fmt"
-)
-
-func gopkginLookup(path string) (codehost.Repo, error) {
- root, _, _, _, ok := modfile.ParseGopkgIn(path)
- if !ok {
- return nil, fmt.Errorf("invalid gopkg.in/ path: %q", path)
- }
- return gitrepo.Repo("https://"+root, root)
-}
list = append(list, f[0])
}
}
+ SortVersions(list)
return list, nil
}
defer body.Close()
// Spool to local file.
- f, err := ioutil.TempFile(tmpdir, "vgo-proxy-download-")
+ f, err := ioutil.TempFile(tmpdir, "go-proxy-download-")
if err != nil {
return "", err
}
+++ /dev/null
-// Copyright 2018 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 modfetch
-
-import (
- "cmd/go/internal/module"
- "cmd/go/internal/semver"
- "fmt"
- "strings"
-)
-
-// Query looks up a revision of a given module given a version query string.
-// The module must be a complete module path.
-// The version must take one of the following forms:
-//
-// - the literal string "latest", denoting the latest available tagged version
-// - v1.2.3, a semantic version string
-// - v1 or v1.2, an abbreviated semantic version string completed by adding zeroes (v1.0.0 or v1.2.0);
-// - >v1.2.3, denoting the earliest available version after v1.2.3
-// - <v1.2.3, denoting the latest available version before v1.2.3
-// - an RFC 3339 time stamp, denoting the latest available version at that time
-// - a Unix time expressed as seconds since 1970, denoting the latest available version at that time
-// - a repository commit identifier, denoting that version
-//
-// The time stamps can be followed by an optional @branch suffix to limit the
-// result to revisions on a particular branch name.
-//
-func Query(path, vers string, allowed func(module.Version) bool) (*RevInfo, error) {
- repo, err := Lookup(path)
- if err != nil {
- return nil, err
- }
-
- if strings.HasPrefix(vers, "v") && semver.IsValid(vers) {
- // TODO: This turns query for "v2" into Stat "v2.0.0",
- // but probably it should allow checking for a branch named "v2".
- vers = semver.Canonical(vers)
- if allowed != nil && !allowed(module.Version{Path: path, Version: vers}) {
- return nil, fmt.Errorf("%s@%s excluded", path, vers)
- }
- return repo.Stat(vers)
- }
- if strings.HasPrefix(vers, ">") || strings.HasPrefix(vers, "<") || vers == "latest" {
- var op string
- if vers != "latest" {
- if !semver.IsValid(vers[1:]) {
- return nil, fmt.Errorf("invalid semantic version in range %s", vers)
- }
- op, vers = vers[:1], vers[1:]
- }
- versions, err := repo.Versions("")
- if err != nil {
- return nil, err
- }
- if len(versions) == 0 && vers == "latest" {
- return repo.Latest()
- }
- if vers == "latest" {
- for i := len(versions) - 1; i >= 0; i-- {
- if allowed == nil || allowed(module.Version{Path: path, Version: versions[i]}) {
- return repo.Stat(versions[i])
- }
- }
- } else if op == "<" {
- for i := len(versions) - 1; i >= 0; i-- {
- if semver.Compare(versions[i], vers) < 0 && (allowed == nil || allowed(module.Version{Path: path, Version: versions[i]})) {
- return repo.Stat(versions[i])
- }
- }
- } else {
- for i := 0; i < len(versions); i++ {
- if semver.Compare(versions[i], vers) > 0 && (allowed == nil || allowed(module.Version{Path: path, Version: versions[i]})) {
- return repo.Stat(versions[i])
- }
- }
- }
- return nil, fmt.Errorf("no matching versions for %s%s", op, vers)
- }
- // TODO: Time queries, maybe.
-
- return repo.Stat(vers)
-}
package modfetch
import (
- "errors"
- pathpkg "path"
+ "fmt"
+ "os"
"sort"
- "strings"
"time"
- "cmd/go/internal/modfetch/bitbucket"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/get"
"cmd/go/internal/modfetch/codehost"
- "cmd/go/internal/modfetch/github"
- "cmd/go/internal/modfetch/googlesource"
- "cmd/go/internal/module"
+ "cmd/go/internal/par"
"cmd/go/internal/semver"
+ web "cmd/go/internal/web"
)
+const traceRepo = false // trace all repo actions, for debugging
+
// A Repo represents a repository storing all versions of a single module.
+// It must be safe for simultaneous use by multiple goroutines.
type Repo interface {
// ModulePath returns the module path.
ModulePath() string
Time time.Time // commit time
}
+// Re: module paths, import paths, repository roots, and lookups
+//
+// A module is a collection of Go packages stored in a file tree
+// with a go.mod file at the root of the tree.
+// The go.mod defines the module path, which is the import path
+// corresponding to the root of the file tree.
+// The import path of a directory within that file tree is the module path
+// joined with the name of the subdirectory relative to the root.
+//
+// For example, the module with path rsc.io/qr corresponds to the
+// file tree in the repository https://github.com/rsc/qr.
+// That file tree has a go.mod that says "module rsc.io/qr".
+// The package in the root directory has import path "rsc.io/qr".
+// The package in the gf256 subdirectory has import path "rsc.io/qr/gf256".
+// In this example, "rsc.io/qr" is both a module path and an import path.
+// But "rsc.io/qr/gf256" is only an import path, not a module path:
+// it names an importable package, but not a module.
+//
+// As a special case to incorporate code written before modules were
+// introduced, if a path p resolves using the pre-module "go get" lookup
+// to the root of a source code repository without a go.mod file,
+// that repository is treated as if it had a go.mod in its root directory
+// declaring module path p. (The go.mod is further considered to
+// contain requirements corresponding to any legacy version
+// tracking format such as Gopkg.lock, vendor/vendor.conf, and so on.)
+//
+// The presentation so far ignores the fact that a source code repository
+// has many different versions of a file tree, and those versions may
+// differ in whether a particular go.mod exists and what it contains.
+// In fact there is a well-defined mapping only from a module path, version
+// pair - often written path@version - to a particular file tree.
+// For example rsc.io/qr@v0.1.0 depends on the "implicit go.mod at root of
+// repository" rule, while rsc.io/qr@v0.2.0 has an explicit go.mod.
+// Because the "go get" import paths rsc.io/qr and github.com/rsc/qr
+// both redirect to the Git repository https://github.com/rsc/qr,
+// github.com/rsc/qr@v0.1.0 is the same file tree as rsc.io/qr@v0.1.0
+// but a different module (a different name). In contrast, since v0.2.0
+// of that repository has an explicit go.mod that declares path rsc.io/qr,
+// github.com/rsc/qr@v0.2.0 is an invalid module path, version pair.
+// Before modules, import comments would have had the same effect.
+//
+// The set of import paths associated with a given module path is
+// clearly not fixed: at the least, new directories with new import paths
+// can always be added. But another potential operation is to split a
+// subtree out of a module into its own module. If done carefully,
+// this operation can be done while preserving compatibility for clients.
+// For example, suppose that we want to split rsc.io/qr/gf256 into its
+// own module, so that there would be two modules rsc.io/qr and rsc.io/qr/gf256.
+// Then we can simultaneously issue rsc.io/qr v0.3.0 (dropping the gf256 subdirectory)
+// and rsc.io/qr/gf256 v0.1.0, including in their respective go.mod
+// cyclic requirements pointing at each other: rsc.io/qr v0.3.0 requires
+// rsc.io/qr/gf256 v0.1.0 and vice versa. Then a build can be
+// using an older rsc.io/qr module that includes the gf256 package, but if
+// it adds a requirement on either the newer rsc.io/qr or the newer
+// rsc.io/qr/gf256 module, it will automatically add the requirement
+// on the complementary half, ensuring both that rsc.io/qr/gf256 is
+// available for importing by the build and also that it is only defined
+// by a single module. The gf256 package could move back into the
+// original by another simultaneous release of rsc.io/qr v0.4.0 including
+// the gf256 subdirectory and an rsc.io/qr/gf256 v0.2.0 with no code
+// in its root directory, along with a new requirement cycle.
+// The ability to shift module boundaries in this way is expected to be
+// important in large-scale program refactorings, similar to the ones
+// described in https://talks.golang.org/2016/refactor.article.
+//
+// The possibility of shifting module boundaries reemphasizes
+// that you must know both the module path and its version
+// to determine the set of packages provided directly by that module.
+//
+// On top of all this, it is possible for a single code repository
+// to contain multiple modules, either in branches or subdirectories,
+// as a limited kind of monorepo. For example rsc.io/qr/v2,
+// the v2.x.x continuation of rsc.io/qr, is expected to be found
+// in v2-tagged commits in https://github.com/rsc/qr, either
+// in the root or in a v2 subdirectory, disambiguated by go.mod.
+// Again the precise file tree corresponding to a module
+// depends on which version we are considering.
+//
+// It is also possible for the underlying repository to change over time,
+// without changing the module path. If I copy the github repo over
+// to https://bitbucket.org/rsc/qr and update https://rsc.io/qr?go-get=1,
+// then clients of all versions should start fetching from bitbucket
+// instead of github. That is, in contrast to the exact file tree,
+// the location of the source code repository associated with a module path
+// does not depend on the module version. (This is by design, as the whole
+// point of these redirects is to allow package authors to establish a stable
+// name that can be updated as code moves from one service to another.)
+//
+// All of this is important background for the lookup APIs defined in this
+// file.
+//
+// The Lookup function takes a module path and returns a Repo representing
+// that module path. Lookup can do only a little with the path alone.
+// It can check that the path is well-formed (see semver.CheckPath)
+// and it can check that the path can be resolved to a target repository.
+// To avoid version control access except when absolutely necessary,
+// Lookup does not attempt to connect to the repository itself.
+//
+// The Import function takes an import path found in source code and
+// determines which module to add to the requirement list to satisfy
+// that import. It checks successive truncations of the import path
+// to determine possible modules and stops when it finds a module
+// in which the latest version satisfies the import path.
+//
+// The ImportRepoRev function is a variant of Import which is limited
+// to code in a source code repository at a particular revision identifier
+// (usually a commit hash or source code repository tag, not necessarily
+// a module version).
+// ImportRepoRev is used when converting legacy dependency requirements
+// from older systems into go.mod files. Those older systems worked
+// at either package or repository granularity, and most of the time they
+// recorded commit hashes, not tagged versions.
+
+var lookupCache par.Cache
+
// Lookup returns the module with the given module path.
+// A successful return does not guarantee that the module
+// has any defined versions.
func Lookup(path string) (Repo, error) {
+ if traceRepo {
+ defer logCall("Lookup(%q)", path)()
+ }
+
+ type cached struct {
+ r Repo
+ err error
+ }
+ c := lookupCache.Do(path, func() interface{} {
+ r, err := lookup(path)
+ if err == nil {
+ if traceRepo {
+ r = newLoggingRepo(r)
+ }
+ r = newCachingRepo(r)
+ }
+ return cached{r, err}
+ }).(cached)
+
+ return c.r, c.err
+}
+
+// lookup returns the module with the given module path.
+func lookup(path string) (r Repo, err error) {
+ if cfg.BuildGetmode != "" {
+ return nil, fmt.Errorf("module lookup disabled by -getmode=%s", cfg.BuildGetmode)
+ }
if proxyURL != "" {
return lookupProxy(path)
}
- if code, err := lookupCodeHost(path, false); err != errNotHosted {
- if err != nil {
- return nil, err
- }
- return newCodeRepo(code, path)
+
+ rr, err := get.RepoRootForImportPath(path, get.PreferMod, web.Secure)
+ if err != nil {
+ // We don't know where to find code for a module with this path.
+ return nil, err
}
- return lookupCustomDomain(path)
-}
-func Import(path string, allowed func(module.Version) bool) (Repo, *RevInfo, error) {
- try := func(path string) (Repo, *RevInfo, error) {
- r, err := Lookup(path)
- if err != nil {
- return nil, nil, err
- }
- info, err := Query(path, "latest", allowed)
- if err != nil {
- return nil, nil, err
- }
- _, err = r.GoMod(info.Version)
- if err != nil {
- return nil, nil, err
- }
- return r, info, nil
+ if rr.VCS == "mod" {
+ // Fetch module from proxy with base URL rr.Repo.
+ return newProxyRepo(rr.Repo, path), nil
}
- var firstErr error
- for {
- r, info, err := try(path)
- if err == nil {
- return r, info, nil
- }
- if firstErr == nil {
- firstErr = err
- }
- p := pathpkg.Dir(path)
- if p == "." {
- break
- }
- path = p
+ code, err := lookupCodeRepo(rr)
+ if err != nil {
+ return nil, err
+ }
+ return newCodeRepo(code, rr.Root, path)
+}
+
+func lookupCodeRepo(rr *get.RepoRoot) (codehost.Repo, error) {
+ code, err := codehost.NewRepo(rr.VCS, rr.Repo)
+ if err != nil {
+ return nil, fmt.Errorf("lookup %s: %v", rr.Root, err)
}
- return nil, nil, firstErr
+ return code, nil
}
-var errNotHosted = errors.New("not hosted")
+// ImportRepoRev returns the module and version to use to access
+// the given import path loaded from the source code repository that
+// the original "go get" would have used, at the specific repository revision
+// (typically a commit hash, but possibly also a source control tag).
+func ImportRepoRev(path, rev string) (Repo, *RevInfo, error) {
+ if cfg.BuildGetmode != "" {
+ return nil, nil, fmt.Errorf("repo version lookup disabled by -getmode=%s", cfg.BuildGetmode)
+ }
-var isTest bool
+ // Note: Because we are converting a code reference from a legacy
+ // version control system, we ignore meta tags about modules
+ // and use only direct source control entries (get.IgnoreMod).
+ rr, err := get.RepoRootForImportPath(path, get.IgnoreMod, web.Secure)
+ if err != nil {
+ return nil, nil, err
+ }
-func lookupCodeHost(path string, customDomain bool) (codehost.Repo, error) {
- switch {
- case strings.HasPrefix(path, "github.com/"):
- return github.Lookup(path)
- case strings.HasPrefix(path, "bitbucket.org/"):
- return bitbucket.Lookup(path)
- case customDomain && strings.HasSuffix(path[:strings.Index(path, "/")+1], ".googlesource.com/") ||
- isTest && strings.HasPrefix(path, "go.googlesource.com/scratch"):
- return googlesource.Lookup(path)
- case strings.HasPrefix(path, "gopkg.in/"):
- return gopkginLookup(path)
+ code, err := lookupCodeRepo(rr)
+ if err != nil {
+ return nil, nil, err
}
- return nil, errNotHosted
+
+ revInfo, err := code.Stat(rev)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // TODO: Look in repo to find path, check for go.mod files.
+ // For now we're just assuming rr.Root is the module path,
+ // which is true in the absence of go.mod files.
+
+ repo, err := newCodeRepo(code, rr.Root, rr.Root)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ info, err := repo.(*codeRepo).convert(revInfo, "")
+ if err != nil {
+ return nil, nil, err
+ }
+ return repo, info, nil
}
func SortVersions(list []string) {
return list[i] < list[j]
})
}
+
+// A loggingRepo is a wrapper around an underlying Repo
+// that prints a log message at the start and end of each call.
+// It can be inserted when debugging.
+type loggingRepo struct {
+ r Repo
+}
+
+func newLoggingRepo(r Repo) *loggingRepo {
+ return &loggingRepo{r}
+}
+
+// logCall prints a log message using format and args and then
+// also returns a function that will print the same message again,
+// along with the elapsed time.
+// Typical usage is:
+//
+// defer logCall("hello %s", arg)()
+//
+// Note the final ().
+func logCall(format string, args ...interface{}) func() {
+ start := time.Now()
+ fmt.Fprintf(os.Stderr, "+++ %s\n", fmt.Sprintf(format, args...))
+ return func() {
+ fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), fmt.Sprintf(format, args...))
+ }
+}
+
+func (l *loggingRepo) ModulePath() string {
+ return l.r.ModulePath()
+}
+
+func (l *loggingRepo) Versions(prefix string) (tags []string, err error) {
+ defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)()
+ return l.r.Versions(prefix)
+}
+
+func (l *loggingRepo) Stat(rev string) (*RevInfo, error) {
+ defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)()
+ return l.r.Stat(rev)
+}
+
+func (l *loggingRepo) Latest() (*RevInfo, error) {
+ defer logCall("Repo[%s]: Latest()", l.r.ModulePath())()
+ return l.r.Latest()
+}
+
+func (l *loggingRepo) GoMod(version string) ([]byte, error) {
+ defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)()
+ return l.r.GoMod(version)
+}
+
+func (l *loggingRepo) Zip(version, tmpdir string) (string, error) {
+ defer logCall("Repo[%s]: Zip(%q, %q)", l.r.ModulePath(), version, tmpdir)()
+ return l.r.Zip(version, tmpdir)
+}
"io/ioutil"
"os"
"path/filepath"
+ "sort"
"strings"
"cmd/go/internal/modfetch/codehost"
+ "cmd/go/internal/str"
)
func Unzip(dir, zipfile, prefix string, maxSize int64) error {
// Check total size.
var size int64
for _, zf := range z.File {
- if !strings.HasPrefix(zf.Name, prefix) {
+ if !str.HasPathPrefix(zf.Name, prefix) {
return fmt.Errorf("unzip %v: unexpected file name %s", zipfile, zf.Name)
}
- if strings.HasSuffix(zf.Name, "/") {
+ if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
continue
}
+ if filepath.Clean(zf.Name) != zf.Name || strings.HasPrefix(zf.Name[len(prefix)+1:], "/") {
+ return fmt.Errorf("unzip %v: invalid file name %s", zipfile, zf.Name)
+ }
s := int64(zf.UncompressedSize64)
if s < 0 || maxSize-size < s {
return fmt.Errorf("unzip %v: content too large", zipfile)
}
// Unzip, enforcing sizes checked earlier.
+ dirs := map[string]bool{dir: true}
for _, zf := range z.File {
- if strings.HasSuffix(zf.Name, "/") {
+ if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
continue
}
- dst := filepath.Join(dir, zf.Name[len(prefix):])
+ name := zf.Name[len(prefix):]
+ dst := filepath.Join(dir, name)
+ parent := filepath.Dir(dst)
+ for parent != dir {
+ dirs[parent] = true
+ parent = filepath.Dir(parent)
+ }
if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
return err
}
}
r, err := zf.Open()
if err != nil {
- r.Close()
w.Close()
return fmt.Errorf("unzip %v: %v", zipfile, err)
}
}
}
+ // Mark directories unwritable, best effort.
+ var dirlist []string
+ for dir := range dirs {
+ dirlist = append(dirlist, dir)
+ }
+ sort.Strings(dirlist)
+
+ // Run over list backward to chmod children before parents.
+ for i := len(dirlist) - 1; i >= 0; i-- {
+ os.Chmod(dir, 0555)
+ }
+
return nil
}
"bytes"
"fmt"
"os"
+ "strconv"
"strings"
"unicode"
"unicode/utf8"
return start, end
}
+func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
+ if hint == nil {
+ // If no hint given, add to the last statement of the given type.
+ Loop:
+ for i := len(x.Stmt) - 1; i >= 0; i-- {
+ stmt := x.Stmt[i]
+ switch stmt := stmt.(type) {
+ case *Line:
+ if stmt.Token != nil && stmt.Token[0] == tokens[0] {
+ hint = stmt
+ break Loop
+ }
+ case *LineBlock:
+ if stmt.Token[0] == tokens[0] {
+ hint = stmt
+ break Loop
+ }
+ }
+ }
+ }
+
+ if hint != nil {
+ for i, stmt := range x.Stmt {
+ switch stmt := stmt.(type) {
+ case *Line:
+ if stmt == hint {
+ // Convert line to line block.
+ stmt.InBlock = true
+ block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
+ stmt.Token = stmt.Token[1:]
+ x.Stmt[i] = block
+ new := &Line{Token: tokens[1:], InBlock: true}
+ block.Line = append(block.Line, new)
+ return new
+ }
+ case *LineBlock:
+ if stmt == hint {
+ new := &Line{Token: tokens[1:], InBlock: true}
+ stmt.Line = append(stmt.Line, new)
+ return new
+ }
+ for j, line := range stmt.Line {
+ if line == hint {
+ // Add new line after hint.
+ stmt.Line = append(stmt.Line, nil)
+ copy(stmt.Line[j+2:], stmt.Line[j+1:])
+ new := &Line{Token: tokens[1:], InBlock: true}
+ stmt.Line[j+1] = new
+ return new
+ }
+ }
+ }
+ }
+ }
+
+ new := &Line{Token: tokens}
+ x.Stmt = append(x.Stmt, new)
+ return new
+}
+
+func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
+ if line.InBlock {
+ tokens = tokens[1:]
+ }
+ line.Token = tokens
+}
+
+func (x *FileSyntax) removeLine(line *Line) {
+ line.Token = nil
+}
+
+// Cleanup cleans up the file syntax x after any edit operations.
+// To avoid quadratic behavior, removeLine marks the line as dead
+// by setting line.Token = nil but does not remove it from the slice
+// in which it appears. After edits have all been indicated,
+// calling Cleanup cleans out the dead lines.
+func (x *FileSyntax) Cleanup() {
+ w := 0
+ for _, stmt := range x.Stmt {
+ switch stmt := stmt.(type) {
+ case *Line:
+ if stmt.Token == nil {
+ continue
+ }
+ case *LineBlock:
+ ww := 0
+ for _, line := range stmt.Line {
+ if line.Token != nil {
+ stmt.Line[ww] = line
+ ww++
+ }
+ }
+ if ww == 0 {
+ continue
+ }
+ if ww == 1 {
+ // Collapse block into single line.
+ line := &Line{
+ Comments: Comments{
+ Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
+ Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
+ After: commentsAdd(stmt.Line[0].After, stmt.After),
+ },
+ Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
+ }
+ x.Stmt[w] = line
+ w++
+ continue
+ }
+ stmt.Line = stmt.Line[:ww]
+ }
+ x.Stmt[w] = stmt
+ w++
+ }
+ x.Stmt = x.Stmt[:w]
+}
+
+func commentsAdd(x, y []Comment) []Comment {
+ return append(x[:len(x):len(x)], y...)
+}
+
+func stringsAdd(x, y []string) []string {
+ return append(x[:len(x):len(x)], y...)
+}
+
// A CommentBlock represents a top-level block of comments separate
// from any rule.
type CommentBlock struct {
// A Line is a single line of tokens.
type Line struct {
Comments
- Start Position
- Token []string
- End Position
+ Start Position
+ Token []string
+ InBlock bool
+ End Position
}
func (x *Line) Span() (start, end Position) {
switch tok {
case '\n', _EOF, _EOL:
return &Line{
- Start: start,
- Token: token,
- End: end,
+ Start: start,
+ Token: token,
+ End: end,
+ InBlock: true,
}
default:
token = append(token, sym.text)
_STRING
_COMMENT
)
+
+var (
+ slashSlash = []byte("//")
+ moduleStr = []byte("module")
+)
+
+// ModulePath returns the module path from the gomod file text.
+// If it cannot find a module path, it returns an empty string.
+// It is tolerant of unrelated problems in the go.mod file.
+func ModulePath(mod []byte) string {
+ for len(mod) > 0 {
+ line := mod
+ mod = nil
+ if i := bytes.IndexByte(line, '\n'); i >= 0 {
+ line, mod = line[:i], line[i+1:]
+ }
+ if i := bytes.Index(line, slashSlash); i >= 0 {
+ line = line[:i]
+ }
+ line = bytes.TrimSpace(line)
+ if !bytes.HasPrefix(line, moduleStr) {
+ continue
+ }
+ line = line[len(moduleStr):]
+ n := len(line)
+ line = bytes.TrimSpace(line)
+ if len(line) == n || len(line) == 0 {
+ continue
+ }
+
+ if line[0] == '"' || line[0] == '`' {
+ p, err := strconv.Unquote(string(line))
+ if err != nil {
+ return "" // malformed quoted string or multiline module path
+ }
+ return p
+ }
+
+ return string(line)
+ }
+ return "" // missing module path
+}
}
t.Error(string(data))
}
+
+var modulePathTests = []struct {
+ input []byte
+ expected string
+}{
+ {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
+ {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
+ {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
+ {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
+ {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"},
+ {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"},
+ {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"},
+ {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"},
+ {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"},
+ {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"},
+ {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""},
+ {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"},
+ {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"},
+ {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"},
+ {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"},
+ {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"},
+ {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
+ {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
+ {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
+ {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
+ {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
+ {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
+ {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"},
+ {input: []byte("module \" \""), expected: " "},
+ {input: []byte("module "), expected: ""},
+ {input: []byte("module \" a/b/c \""), expected: " a/b/c "},
+ {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"},
+}
+
+func TestModulePath(t *testing.T) {
+ for _, test := range modulePathTests {
+ t.Run(string(test.input), func(t *testing.T) {
+ result := ModulePath(test.input)
+ if result != test.expected {
+ t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected)
+ }
+ })
+ }
+}
"cmd/go/internal/semver"
)
+// A File is the parsed, interpreted form of a go.mod file.
type File struct {
Module *Module
Require []*Require
Syntax *FileSyntax
}
+// A Module is the module statement.
type Module struct {
- Mod module.Version
- Major string
+ Mod module.Version
+ Syntax *Line
}
+// A Require is a single require statement.
type Require struct {
- Mod module.Version
- Syntax *Line
+ Mod module.Version
+ Indirect bool // has "// indirect" comment
+ Syntax *Line
}
+// An Exclude is a single exclude statement.
type Exclude struct {
Mod module.Version
Syntax *Line
}
+// A Replace is a single replace statement.
type Replace struct {
- Old module.Version
- New module.Version
-
+ Old module.Version
+ New module.Version
Syntax *Line
}
-func (f *File) AddModuleStmt(path string) {
- f.Module = &Module{
- Mod: module.Version{Path: path},
- }
+func (f *File) AddModuleStmt(path string) error {
if f.Syntax == nil {
f.Syntax = new(FileSyntax)
}
- f.Syntax.Stmt = append(f.Syntax.Stmt, &Line{
- Token: []string{"module", AutoQuote(path)},
- })
+ if f.Module == nil {
+ f.Module = &Module{
+ Mod: module.Version{Path: path},
+ Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
+ }
+ } else {
+ f.Module.Mod.Path = path
+ f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
+ }
+ return nil
}
func (f *File) AddComment(text string) {
// replace and exclude either. They don't matter, and it will work better for
// forward compatibility if we can depend on modules that have local changes.
- // TODO: For the target module (not dependencies), maybe we should
- // relax the semver requirement and rewrite the file with updated info
- // after resolving any versions. That would let people type commit hashes
- // or tags or branch names, and then vgo would fix them.
-
switch verb {
default:
fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
return
}
- f.Module = new(Module)
+ f.Module = &Module{Syntax: line}
if len(args) != 1 {
fmt.Fprintf(errs, "%s:%d: usage: module module/path [version]\n", f.Syntax.Name, line.Start.Line)
}
if verb == "require" {
f.Require = append(f.Require, &Require{
- Mod: module.Version{Path: s, Version: v},
- Syntax: line,
+ Mod: module.Version{Path: s, Version: v},
+ Syntax: line,
+ Indirect: isIndirect(line),
})
} else {
f.Exclude = append(f.Exclude, &Exclude{
})
}
case "replace":
- if len(args) < 4 || len(args) > 5 || args[2] != "=>" {
- fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3 => other/module v1.4\n\t or %s module/path v1.2.3 => ../local/directory", f.Syntax.Name, line.Start.Line, verb, verb)
+ arrow := 2
+ if len(args) >= 2 && args[1] == "=>" {
+ arrow = 1
+ }
+ if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
+ fmt.Fprintf(errs, "%s:%d: usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", f.Syntax.Name, line.Start.Line, verb, verb)
return
}
s, err := parseString(&args[0])
fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
return
}
- old := args[1]
- v, err := parseVersion(s, &args[1], fix)
- if err != nil {
- fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err)
- return
- }
v1, err := moduleMajorVersion(s)
if err != nil {
fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
return
}
- if v2 := semver.Major(v); v1 != v2 && (v1 != "v1" || v2 != "v0") {
- fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, v1, v2, v)
- return
+ var v string
+ if arrow == 2 {
+ old := args[1]
+ v, err = parseVersion(s, &args[1], fix)
+ if err != nil {
+ fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err)
+ return
+ }
+ if v2 := semver.Major(v); v1 != v2 && (v1 != "v1" || v2 != "v0") {
+ fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, v1, v2, v)
+ return
+ }
}
- ns, err := parseString(&args[3])
+ ns, err := parseString(&args[arrow+1])
if err != nil {
fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
return
}
nv := ""
- if len(args) == 4 {
- if !isDirectoryPath(ns) {
+ if len(args) == arrow+2 {
+ if !IsDirectoryPath(ns) {
fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)", f.Syntax.Name, line.Start.Line)
return
}
return
}
}
- if len(args) == 5 {
- old := args[4]
- nv, err = parseVersion(ns, &args[4], fix)
+ if len(args) == arrow+3 {
+ old := args[arrow+1]
+ nv, err = parseVersion(ns, &args[arrow+2], fix)
if err != nil {
fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err)
return
}
- if isDirectoryPath(ns) {
+ if IsDirectoryPath(ns) {
fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version", f.Syntax.Name, line.Start.Line, ns)
return
}
}
- // TODO: More sanity checks about directories vs module paths.
f.Replace = append(f.Replace, &Replace{
Old: module.Version{Path: s, Version: v},
New: module.Version{Path: ns, Version: nv},
}
}
-func isDirectoryPath(ns string) bool {
+// isIndirect reports whether line has a "// indirect" comment,
+// meaning it is in go.mod only for its effect on indirect dependencies,
+// so that it can be dropped entirely once the effective version of the
+// indirect dependency reaches the given minimum version.
+func isIndirect(line *Line) bool {
+ if len(line.Suffix) == 0 {
+ return false
+ }
+ f := strings.Fields(line.Suffix[0].Token)
+ return (len(f) == 2 && f[1] == "indirect" || len(f) > 2 && f[1] == "indirect;") && f[0] == "//"
+}
+
+// setIndirect sets line to have (or not have) a "// indirect" comment.
+func setIndirect(line *Line, indirect bool) {
+ if isIndirect(line) == indirect {
+ return
+ }
+ if indirect {
+ // Adding comment.
+ if len(line.Suffix) == 0 {
+ // New comment.
+ line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
+ return
+ }
+ // Insert at beginning of existing comment.
+ com := &line.Suffix[0]
+ space := " "
+ if len(com.Token) > 2 && com.Token[2] == ' ' || com.Token[2] == '\t' {
+ space = ""
+ }
+ com.Token = "// indirect;" + space + com.Token[2:]
+ return
+ }
+
+ // Removing comment.
+ f := strings.Fields(line.Suffix[0].Token)
+ if len(f) == 2 {
+ // Remove whole comment.
+ line.Suffix = nil
+ return
+ }
+
+ // Remove comment prefix.
+ com := &line.Suffix[0]
+ i := strings.Index(com.Token, "indirect;")
+ com.Token = "//" + com.Token[i+len("indirect;"):]
+}
+
+// IsDirectoryPath reports whether the given path should be interpreted
+// as a directory path. Just like on the go command line, relative paths
+// and rooted paths are directory paths; the rest are module paths.
+func IsDirectoryPath(ns string) bool {
// Because go.mod files can move from one system to another,
// we check all known path syntaxes, both Unix and Windows.
return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
}
-func mustQuote(t string) bool {
- for _, r := range t {
+// MustQuote reports whether s must be quoted in order to appear as
+// a single token in a go.mod line.
+func MustQuote(s string) bool {
+ for _, r := range s {
if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' {
return true
}
}
- return t == "" || strings.Contains(t, "//") || strings.Contains(t, "/*")
+ return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
}
// AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
// the quotation of s.
func AutoQuote(s string) string {
- if mustQuote(s) {
+ if MustQuote(s) {
return strconv.Quote(s)
}
return s
return Format(f.Syntax), nil
}
-func (x *File) AddRequire(path, vers string) {
- var syntax *Line
+// Cleanup cleans up the file f after any edit operations.
+// To avoid quadratic behavior, modifications like DropRequire
+// clear the entry but do not remove it from the slice.
+// Cleanup cleans out all the cleared entries.
+func (f *File) Cleanup() {
+ w := 0
+ for _, r := range f.Require {
+ if r.Mod.Path != "" {
+ f.Require[w] = r
+ w++
+ }
+ }
+ f.Require = f.Require[:w]
- for i, stmt := range x.Syntax.Stmt {
- switch stmt := stmt.(type) {
- case *LineBlock:
- if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
- syntax = &Line{Token: []string{AutoQuote(path), vers}}
- stmt.Line = append(stmt.Line, syntax)
- goto End
- }
- case *Line:
- if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
- stmt.Token = stmt.Token[1:]
- syntax = &Line{Token: []string{AutoQuote(path), vers}}
- x.Syntax.Stmt[i] = &LineBlock{
- Comments: stmt.Comments,
- Token: []string{"require"},
- Line: []*Line{
- stmt,
- syntax,
- },
- }
- goto End
+ w = 0
+ for _, x := range f.Exclude {
+ if x.Mod.Path != "" {
+ f.Exclude[w] = x
+ w++
+ }
+ }
+ f.Exclude = f.Exclude[:w]
+
+ w = 0
+ for _, r := range f.Replace {
+ if r.Old.Path != "" {
+ f.Replace[w] = r
+ w++
+ }
+ }
+ f.Replace = f.Replace[:w]
+
+ f.Syntax.Cleanup()
+}
+
+func (f *File) AddRequire(path, vers string) error {
+ need := true
+ for _, r := range f.Require {
+ if r.Mod.Path == path {
+ if need {
+ r.Mod.Version = vers
+ f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
+ need = false
+ } else {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Require{}
}
}
}
- syntax = &Line{Token: []string{"require", AutoQuote(path), vers}}
- x.Syntax.Stmt = append(x.Syntax.Stmt, syntax)
+ if need {
+ f.AddNewRequire(path, vers, false)
+ }
+ return nil
+}
-End:
- x.Require = append(x.Require, &Require{module.Version{Path: path, Version: vers}, syntax})
+func (f *File) AddNewRequire(path, vers string, indirect bool) {
+ line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
+ setIndirect(line, indirect)
+ f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line})
}
-func (f *File) SetRequire(req []module.Version) {
+func (f *File) SetRequire(req []*Require) {
need := make(map[string]string)
- for _, m := range req {
- need[m.Path] = m.Version
+ indirect := make(map[string]bool)
+ for _, r := range req {
+ need[r.Mod.Path] = r.Mod.Version
+ indirect[r.Mod.Path] = r.Indirect
}
for _, r := range f.Require {
if v, ok := need[r.Mod.Path]; ok {
r.Mod.Version = v
+ r.Indirect = indirect[r.Mod.Path]
}
}
if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
var newLines []*Line
for _, line := range stmt.Line {
- if p, err := strconv.Unquote(line.Token[0]); err == nil && need[p] != "" {
+ if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" {
line.Token[1] = need[p]
delete(need, p)
+ setIndirect(line, indirect[p])
newLines = append(newLines, line)
}
}
case *Line:
if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
- if p, err := strconv.Unquote(stmt.Token[1]); err == nil && need[p] != "" {
+ if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" {
stmt.Token[2] = need[p]
delete(need, p)
+ setIndirect(stmt, indirect[p])
} else {
continue // drop stmt
}
f.Syntax.Stmt = newStmts
for path, vers := range need {
- f.AddRequire(path, vers)
+ f.AddNewRequire(path, vers, indirect[path])
}
f.SortBlocks()
}
+func (f *File) DropRequire(path string) error {
+ for _, r := range f.Require {
+ if r.Mod.Path == path {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Require{}
+ }
+ }
+ return nil
+}
+
+func (f *File) AddExclude(path, vers string) error {
+ var hint *Line
+ for _, x := range f.Exclude {
+ if x.Mod.Path == path && x.Mod.Version == vers {
+ return nil
+ }
+ if x.Mod.Path == path {
+ hint = x.Syntax
+ }
+ }
+
+ f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
+ return nil
+}
+
+func (f *File) DropExclude(path, vers string) error {
+ for _, x := range f.Exclude {
+ if x.Mod.Path == path && x.Mod.Version == vers {
+ f.Syntax.removeLine(x.Syntax)
+ *x = Exclude{}
+ }
+ }
+ return nil
+}
+
+func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
+ need := true
+ old := module.Version{Path: oldPath, Version: oldVers}
+ new := module.Version{Path: newPath, Version: newVers}
+ tokens := []string{"replace", AutoQuote(oldPath)}
+ if oldVers != "" {
+ tokens = append(tokens, oldVers)
+ }
+ tokens = append(tokens, "=>", AutoQuote(newPath))
+ if newVers != "" {
+ tokens = append(tokens, newVers)
+ }
+
+ var hint *Line
+ for _, r := range f.Replace {
+ if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
+ if need {
+ // Found replacement for old; update to use new.
+ r.New = new
+ f.Syntax.updateLine(r.Syntax, tokens...)
+ need = false
+ continue
+ }
+ // Already added; delete other replacements for same.
+ f.Syntax.removeLine(r.Syntax)
+ *r = Replace{}
+ }
+ if r.Old.Path == oldPath {
+ hint = r.Syntax
+ }
+ }
+ if need {
+ f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
+ }
+ return nil
+}
+
+func (f *File) DropReplace(oldPath, oldVers string) error {
+ for _, r := range f.Replace {
+ if r.Old.Path == oldPath && r.Old.Version == oldVers {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Replace{}
+ }
+ }
+ return nil
+}
+
func (f *File) SortBlocks() {
f.removeDups() // otherwise sorting is unsafe
--- /dev/null
+// Copyright 2018 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 modfile
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+var addRequireTests = []struct {
+ in string
+ path string
+ vers string
+ out string
+}{
+ {
+ `
+ module m
+ require x.y/z v1.2.3
+ `,
+ "x.y/z", "v1.5.6",
+ `
+ module m
+ require x.y/z v1.5.6
+ `,
+ },
+ {
+ `
+ module m
+ require x.y/z v1.2.3
+ `,
+ "x.y/w", "v1.5.6",
+ `
+ module m
+ require (
+ x.y/z v1.2.3
+ x.y/w v1.5.6
+ )
+ `,
+ },
+ {
+ `
+ module m
+ require x.y/z v1.2.3
+ require x.y/q/v2 v2.3.4
+ `,
+ "x.y/w", "v1.5.6",
+ `
+ module m
+ require x.y/z v1.2.3
+ require (
+ x.y/q/v2 v2.3.4
+ x.y/w v1.5.6
+ )
+ `,
+ },
+}
+
+func TestAddRequire(t *testing.T) {
+ for i, tt := range addRequireTests {
+ t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
+ f, err := Parse("in", []byte(tt.in), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ g, err := Parse("out", []byte(tt.out), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ golden, err := g.Format()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := f.AddRequire(tt.path, tt.vers); err != nil {
+ t.Fatal(err)
+ }
+ out, err := f.Format()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(out, golden) {
+ t.Errorf("have:\n%s\nwant:\n%s", out, golden)
+ }
+ })
+ }
+}
xyz v1.3.4 => my/xyz v1.3.4-me
xyz v1.4.5 => "/tmp/my dir"
xyz v1.5.6 => my/xyz v1.5.6
+
+ xyz => my/other/xyz v1.5.4
)
"xyz" v1.3.4 => "my/xyz" "v1.3.4-me"
xyz "v1.4.5" => "/tmp/my dir"
xyz v1.5.6 => my/xyz v1.5.6
+
+ xyz => my/other/xyz v1.5.4
)
--- /dev/null
+// Copyright 2018 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 modget implements the module-aware ``go get'' command.
+package modget
+
+import (
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/get"
+ "cmd/go/internal/load"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/module"
+ "cmd/go/internal/mvs"
+ "cmd/go/internal/par"
+ "cmd/go/internal/search"
+ "cmd/go/internal/semver"
+ "cmd/go/internal/str"
+ "cmd/go/internal/work"
+ "fmt"
+ "os"
+ pathpkg "path"
+ "path/filepath"
+ "strings"
+)
+
+var CmdGet = &base.Command{
+ // Note: -d -m -u are listed explicitly because they are the most common get flags.
+ // Do not send CLs removing them because they're covered by [get flags].
+ UsageLine: "get [-d] [-m] [-u] [-v] [-insecure] [build flags] [packages]",
+ Short: "add dependencies to current module and install them",
+ Long: `
+Get resolves and adds dependencies to the current development module
+and then builds and installs them.
+
+The first step is to resolve which dependencies to add.
+
+For each named package or package pattern, get must decide which version of
+the corresponding module to use. By default, get chooses the latest tagged
+release version, such as v0.4.5 or v1.2.3. If there are no tagged release
+versions, get chooses the latest tagged prerelease version, such as
+v0.0.1-pre1. If there are no tagged versions at all, get chooses the latest
+known commit.
+
+This default version selection can be overridden by adding an @version
+suffix to the package argument, as in 'go get golang.org/x/text@v0.3.0'.
+For modules stored in source control repositories, the version suffix can
+also be a commit hash, branch identifier, or other syntax known to the
+source control system, as in 'go get golang.org/x/text@master'.
+The version suffix @latest explicitly requests the default behavior
+described above.
+
+If a module under consideration is already a dependency of the current
+development module, then get will update the required version.
+Specifying a version earlier than the current required version is valid and
+downgrades the dependency. The version suffix @none indicates that the
+dependency should be removed entirely.
+
+Although get defaults to using the latest version of the module containing
+a named package, it does not use the latest version of that module's
+dependencies. Instead it prefers to use the specific dependency versions
+requested by that module. For example, if the latest A requires module
+B v1.2.3, while B v1.2.4 and v1.3.1 are also available, then 'go get A'
+will use the latest A but then use B v1.2.3, as requested by A. (If there
+are competing requirements for a particular module, then 'go get' resolves
+those requirements by taking the maximum requested version.)
+
+The -u flag instructs get to update dependencies to use newer minor or
+patch releases when available. Continuing the previous example,
+'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3).
+
+The -u=patch flag (not -u patch) instructs get to update dependencies
+to use newer patch releases when available. Continuing the previous example,
+'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3).
+
+In general, adding a new dependency may require upgrading
+existing dependencies to keep a working build, and 'go get' does
+this automatically. Similarly, downgrading one dependency may
+require downgrading other dependenceis, and 'go get' does
+this automatically as well.
+
+The -m flag instructs get to stop here, after resolving, upgrading,
+and downgrading modules and updating go.mod. When using -m,
+each specified package path must be a module path as well,
+not the import path of a package below the module root.
+
+The -insecure flag permits fetching from repositories and resolving
+custom domains using insecure schemes such as HTTP. Use with caution.
+
+The second step is to download (if needed), build, and install
+the named packages.
+
+If an argument names a module but not a package (because there is no
+Go source code in the module's root directory), then the install step
+is skipped for that argument, instead of causing a build failure.
+For example 'go get golang.org/x/perf' succeeds even though there
+is no code corresponding to that import path.
+
+Note that package patterns are allowed and are expanded after resolving
+the module versions. For example, 'go get golang.org/x/perf/cmd/...'
+adds the latest golang.org/x/perf and then installs the commands in that
+latest version.
+
+The -d flag instructs get to download the source code needed to build
+the named packages, including downloading necessary dependencies,
+but not to build and install them.
+
+With no package arguments, 'go get' applies to the main module,
+and to the Go package in the current directory, if any. In particular,
+'go get -u' and 'go get -u=patch' update all the dependencies of the
+main module. With no package arguments and also without -u,
+'go get' is not much more than 'go install', and 'go get -d' not much
+more than 'go list'.
+
+For more about modules, see 'go help modules'.
+
+For more about specifying packages, see 'go help packages'.
+
+This text describes the behavior of get using modules to manage source
+code and dependencies. If instead the go command is running in GOPATH
+mode, the details of get's flags and effects change, as does 'go help get'.
+See 'go help modules' and 'go help gopath-get'.
+
+See also: go build, go install, go clean, go mod.
+ `,
+}
+
+// Note that this help text is a stopgap to make the module-aware get help text
+// available even in non-module settings. It should be deleted when the old get
+// is deleted. It should NOT be considered to set a precedent of having hierarchical
+// help names with dashes.
+var HelpModuleGet = &base.Command{
+ UsageLine: "module-get",
+ Short: "module-aware go get",
+ Long: `
+The 'go get' command changes behavior depending on whether the
+go command is running in module-aware mode or legacy GOPATH mode.
+This help text, accessible as 'go help module-get' even in legacy GOPATH mode,
+describes 'go get' as it operates in module-aware mode.
+
+Usage: ` + CmdGet.UsageLine + `
+` + CmdGet.Long,
+}
+
+var (
+ getD = CmdGet.Flag.Bool("d", false, "")
+ getF = CmdGet.Flag.Bool("f", false, "")
+ getFix = CmdGet.Flag.Bool("fix", false, "")
+ getM = CmdGet.Flag.Bool("m", false, "")
+ getT = CmdGet.Flag.Bool("t", false, "")
+ getU upgradeFlag
+ // -insecure is get.Insecure
+ // -v is cfg.BuildV
+)
+
+// upgradeFlag is a custom flag.Value for -u.
+type upgradeFlag string
+
+func (*upgradeFlag) IsBoolFlag() bool { return true } // allow -u
+
+func (v *upgradeFlag) Set(s string) error {
+ if s == "false" {
+ s = ""
+ }
+ *v = upgradeFlag(s)
+ return nil
+}
+
+func (v *upgradeFlag) String() string { return "" }
+
+func init() {
+ work.AddBuildFlags(CmdGet)
+ CmdGet.Run = runGet // break init loop
+ CmdGet.Flag.BoolVar(&get.Insecure, "insecure", get.Insecure, "")
+ CmdGet.Flag.Var(&getU, "u", "")
+}
+
+type Pkg struct {
+ Arg string
+ Path string
+ Vers string
+}
+
+func runGet(cmd *base.Command, args []string) {
+ switch getU {
+ case "", "patch", "true":
+ // ok
+ default:
+ base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
+ }
+ if *getF {
+ fmt.Fprintf(os.Stderr, "go get: -f flag is a no-op when using modules\n")
+ }
+ if *getFix {
+ fmt.Fprintf(os.Stderr, "go get: -fix flag is a no-op when using modules\n")
+ }
+ if *getT {
+ fmt.Fprintf(os.Stderr, "go get: -t flag is a no-op when using modules\n")
+ }
+
+ if cfg.BuildGetmode == "vendor" {
+ base.Fatalf("go get: disabled by -getmode=vendor")
+ }
+
+ modload.LoadBuildList()
+
+ // A task holds the state for processing a single get argument (path@vers).
+ type task struct {
+ arg string // original argument
+ path string // package path part of arg
+ forceModulePath bool // path must be interpreted as a module path
+ vers string // version part of arg
+ m module.Version // module version indicated by argument
+ req []module.Version // m's requirement list (not upgraded)
+ }
+
+ // Build task and install lists.
+ // The command-line arguments are of the form path@version
+ // or simply path, with implicit @latest. path@none is "downgrade away".
+ // At the end of the loop, we've resolved the list of arguments into
+ // a list of tasks (a path@vers that needs further processing)
+ // and a list of install targets (for the "go install" at the end).
+ var tasks []*task
+ var install []string
+ for _, arg := range search.CleanImportPaths(args) {
+ // Argument is module query path@vers, or else path with implicit @latest.
+ path := arg
+ vers := ""
+ if i := strings.Index(arg, "@"); i >= 0 {
+ path, vers = arg[:i], arg[i+1:]
+ }
+ if strings.Contains(vers, "@") || arg != path && vers == "" {
+ base.Errorf("go get %s: invalid module version syntax", arg)
+ continue
+ }
+ if vers != "none" {
+ install = append(install, path)
+ }
+
+ // Deciding which module to upgrade/downgrade for a particular argument is difficult.
+ // Patterns only make it more difficult.
+ // We impose restrictions to avoid needing to interlace pattern expansion,
+ // like in in modload.ImportPaths.
+ // Specifically, these patterns are supported:
+ //
+ // - Relative paths like ../../foo or ../../foo... are restricted to matching directories
+ // in the current module and therefore map to the current module.
+ // It's possible that the pattern matches no packages, but we will still treat it
+ // as mapping to the current module.
+ // TODO: In followup, could just expand the full list and remove the discrepancy.
+ // - The pattern "all" has its usual package meaning and maps to the list of modules
+ // from which the matched packages are drawn. This is potentially a subset of the
+ // module pattern "all". If module A requires B requires C but A does not import
+ // the parts of B that import C, the packages matched by "all" are only from A and B,
+ // so only A and B end up on the tasks list.
+ // TODO: Even in -m mode?
+ // - The patterns "std" and "cmd" expand to packages in the standard library,
+ // which aren't upgradable, so we skip over those.
+ // In -m mode they expand to non-module-paths, so they are disallowed.
+ // - Import path patterns like foo/bar... are matched against the module list,
+ // assuming any package match would imply a module pattern match.
+ // TODO: What about -m mode?
+ // - Import paths without patterns are left as is, for resolution by getQuery (eventually modload.Import).
+ //
+ if search.IsRelativePath(path) {
+ // Check that this relative pattern only matches directories in the current module,
+ // and then record the current module as the target.
+ dir := path
+ if i := strings.Index(path, "..."); i >= 0 {
+ dir, _ = pathpkg.Split(path[:i])
+ }
+ abs, err := filepath.Abs(dir)
+ if err != nil {
+ base.Errorf("go get %s: %v", arg, err)
+ continue
+ }
+ if !str.HasFilePathPrefix(abs, modload.ModRoot) {
+ base.Errorf("go get %s: directory %s is outside module root %s", arg, abs, modload.ModRoot)
+ continue
+ }
+ // TODO: Check if abs is inside a nested module.
+ tasks = append(tasks, &task{arg: arg, path: modload.Target.Path, vers: ""})
+ continue
+ }
+ if path == "all" {
+ if path != arg {
+ base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg)
+ }
+
+ // TODO: If *getM, should this be the module pattern "all"?
+
+ // This is the package pattern "all" not the module pattern "all":
+ // enumerate all the modules actually needed by builds of the packages
+ // in the main module, not incidental modules that happen to be
+ // in the package graph (and therefore build list).
+ // Note that LoadALL may add new modules to the build list to
+ // satisfy new imports, but vers == "latest" implicitly anyway,
+ // so we'll assume that's OK.
+ seen := make(map[module.Version]bool)
+ pkgs := modload.LoadALL()
+ for _, pkg := range pkgs {
+ m := modload.PackageModule(pkg)
+ if m.Path != "" && !seen[m] {
+ seen[m] = true
+ tasks = append(tasks, &task{arg: arg, path: m.Path, vers: "latest", forceModulePath: true})
+ }
+ }
+ continue
+ }
+ if search.IsMetaPackage(path) {
+ // Already handled "all", so this must be "std" or "cmd",
+ // which are entirely in the standard library.
+ if path != arg {
+ base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg)
+ }
+ if *getM {
+ base.Errorf("go get %s: cannot use pattern %q with -m", arg, arg)
+ continue
+ }
+ continue
+ }
+ if strings.Contains(path, "...") {
+ // Apply to modules in build list matched by pattern (golang.org/x/...), if any.
+ match := search.MatchPattern(path)
+ matched := false
+ for _, m := range modload.BuildList() {
+ if match(m.Path) || str.HasPathPrefix(path, m.Path) {
+ tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, forceModulePath: true})
+ matched = true
+ }
+ }
+ // If matched, we're done.
+ // Otherwise assume pattern is inside a single module
+ // (golang.org/x/text/unicode/...) and leave for usual lookup.
+ // Unless we're using -m.
+ if matched {
+ continue
+ }
+ if *getM {
+ base.Errorf("go get %s: pattern matches no modules in build list", arg)
+ continue
+ }
+ }
+ tasks = append(tasks, &task{arg: arg, path: path, vers: vers})
+ }
+ base.ExitIfErrors()
+
+ // Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks).
+ // Resolve each one and load direct requirements in parallel.
+ reqs := modload.Reqs()
+ var lookup par.Work
+ for _, t := range tasks {
+ lookup.Add(t)
+ }
+ lookup.Do(10, func(item interface{}) {
+ t := item.(*task)
+ m, err := getQuery(t.path, t.vers, t.forceModulePath)
+ if err != nil {
+ base.Errorf("go get %v: %v", t.arg, err)
+ return
+ }
+ t.m = m
+ if t.vers == "none" {
+ // Wait for downgrade step.
+ return
+ }
+ // If there is no -u, then we don't need to upgrade the
+ // collected requirements separately from the overall
+ // recalculation of the build list (modload.ReloadBuildList below),
+ // so don't bother doing it now. Doing it now wouldn't be
+ // any slower (because it would prime the cache for later)
+ // but the larger operation below can report more errors in a single run.
+ if getU != "" {
+ list, err := reqs.Required(m)
+ if err != nil {
+ base.Errorf("go get %v: %v", t.arg, err)
+ return
+ }
+ t.req = list
+ }
+ })
+ base.ExitIfErrors()
+
+ // Now we know the specific version of each path@vers along with its requirements.
+ // The final build list will be the union of three build lists:
+ // 1. the original build list
+ // 2. the modules named on the command line
+ // 3. the upgraded requirements of those modules (if upgrading)
+ // Start building those lists.
+ // This loop collects (2) and the not-yet-upgraded (3).
+ // Also, because the list of paths might have named multiple packages in a single module
+ // (or even the same package multiple times), now that we know the module for each
+ // package, this loop deduplicates multiple references to a given module.
+ // (If a module is mentioned multiple times, the listed target version must be the same each time.)
+ var named []module.Version
+ var required []module.Version
+ byPath := make(map[string]*task)
+ for _, t := range tasks {
+ prev, ok := byPath[t.m.Path]
+ if prev != nil && prev.m != t.m {
+ base.Errorf("go get: conflicting versions for module %s: %s and %s", t.m.Path, prev.m.Version, t.m.Version)
+ byPath[t.m.Path] = nil // sentinel to stop errors
+ continue
+ }
+ if ok {
+ continue // already added
+ }
+ byPath[t.m.Path] = t
+ named = append(named, t.m)
+ required = append(required, t.req...)
+ }
+ base.ExitIfErrors()
+
+ // If the modules named on the command line have any dependencies
+ // and we're supposed to upgrade dependencies,
+ // chase down the full list of upgraded dependencies.
+ // This turns required from a not-yet-upgraded (3) to the final (3).
+ // (See list above.)
+ if len(required) > 0 {
+ upgraded, err := mvs.UpgradeAll(upgradeTarget, &upgrader{
+ Reqs: modload.Reqs(),
+ targets: required,
+ patch: getU == "patch",
+ })
+ if err != nil {
+ base.Fatalf("go get: %v", err)
+ }
+ required = upgraded[1:] // slice off upgradeTarget
+ }
+
+ // Put together the final build list as described above (1) (2) (3).
+ // If we're not using -u, then len(required) == 0 and ReloadBuildList
+ // chases down the dependencies of all the named module versions
+ // in one operation.
+ var list []module.Version
+ list = append(list, modload.BuildList()...)
+ list = append(list, named...)
+ list = append(list, required...)
+ modload.SetBuildList(list)
+ modload.ReloadBuildList() // note: does not update go.mod
+
+ // Apply any needed downgrades.
+ var down []module.Version
+ for _, m := range modload.BuildList() {
+ t := byPath[m.Path]
+ if t != nil && semver.Compare(m.Version, t.m.Version) > 0 {
+ down = append(down, module.Version{Path: m.Path, Version: t.m.Version})
+ }
+ }
+ if len(down) > 0 {
+ list, err := mvs.Downgrade(modload.Target, modload.Reqs(), down...)
+ if err != nil {
+ base.Fatalf("go get: %v", err)
+ }
+ modload.SetBuildList(list)
+ modload.ReloadBuildList() // note: does not update go.mod
+ }
+
+ // Everything succeeded. Update go.mod.
+ modload.WriteGoMod()
+
+ // If -m was specified, we're done after the module work. No download, no build.
+ if *getM {
+ return
+ }
+
+ if len(install) > 0 {
+ work.BuildInit()
+ var pkgs []string
+ for _, p := range load.PackagesAndErrors(install) {
+ if p.Error == nil || !strings.HasPrefix(p.Error.Err, "no Go files") {
+ pkgs = append(pkgs, p.ImportPath)
+ }
+ }
+ // If -d was specified, we're done after the download: no build.
+ // (The load.PackagesAndErrors is what did the download
+ // of the named packages and their dependencies.)
+ if len(pkgs) > 0 && !*getD {
+ work.InstallPackages(pkgs)
+ }
+ }
+}
+
+// getQuery evaluates the given package path, version pair
+// to determine the underlying module version being requested.
+// If forceModulePath is set, getQuery must interpret path
+// as a module path.
+func getQuery(path, vers string, forceModulePath bool) (module.Version, error) {
+ if path == modload.Target.Path {
+ if vers != "" {
+ return module.Version{}, fmt.Errorf("cannot update main module to explicit version")
+ }
+ return modload.Target, nil
+ }
+
+ if vers == "" {
+ vers = "latest"
+ }
+
+ // First choice is always to assume path is a module path.
+ // If that works out, we're done.
+ info, err := modload.Query(path, vers, modload.Allowed)
+ if err == nil {
+ return module.Version{Path: path, Version: info.Version}, nil
+ }
+
+ // Even if the query fails, if the path is (or must be) a real module, then report the query error.
+ if forceModulePath || *getM || isModulePath(path) {
+ return module.Version{}, err
+ }
+
+ // Otherwise, interpret the package path as an import
+ // and determine what module that import would address
+ // if found in the current source code.
+ // Then apply the version to that module.
+ m, _, err := modload.Import(path)
+ if err != nil {
+ return module.Version{}, err
+ }
+ if m.Path == "" {
+ return module.Version{}, fmt.Errorf("package %q is not in a module", path)
+ }
+ info, err = modload.Query(m.Path, vers, modload.Allowed)
+ if err != nil {
+ return module.Version{}, err
+ }
+ return module.Version{Path: m.Path, Version: info.Version}, nil
+}
+
+// isModulePath reports whether path names an actual module,
+// defined as one with an accessible latest version.
+func isModulePath(path string) bool {
+ _, err := modload.Query(path, "latest", modload.Allowed)
+ return err == nil
+}
+
+// An upgrader adapts an underlying mvs.Reqs to apply an
+// upgrade policy to a list of targets and their dependencies.
+// If patch=false, the upgrader implements "get -u".
+// If patch=true, the upgrader implements "get -u=patch".
+type upgrader struct {
+ mvs.Reqs
+ targets []module.Version
+ patch bool
+}
+
+// upgradeTarget is a fake "target" requiring all the modules to be upgraded.
+var upgradeTarget = module.Version{Path: "upgrade target", Version: ""}
+
+// Required returns the requirement list for m.
+// Other than the upgradeTarget, we defer to u.Reqs.
+func (u *upgrader) Required(m module.Version) ([]module.Version, error) {
+ if m == upgradeTarget {
+ return u.targets, nil
+ }
+ return u.Reqs.Required(m)
+}
+
+// Upgrade returns the desired upgrade for m.
+// If m is a tagged version, then Upgrade returns the latest tagged version.
+// If m is a pseudo-version, then Upgrade returns the latest tagged version
+// when that version has a time-stamp newer than m.
+// Otherwise Upgrade returns m (preserving the pseudo-version).
+// This special case prevents accidental downgrades
+// when already using a pseudo-version newer than the latest tagged version.
+func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
+ // Note that query "latest" is not the same as
+ // using repo.Latest.
+ // The query only falls back to untagged versions
+ // if nothing is tagged. The Latest method
+ // only ever returns untagged versions,
+ // which is not what we want.
+ query := "latest"
+ if u.patch {
+ // For patch upgrade, query "v1.2".
+ query = semver.MajorMinor(m.Version)
+ }
+ info, err := modload.Query(m.Path, query, modload.Allowed)
+ if err != nil {
+ // Report error but return m, to let version selection continue.
+ // (Reporting the error will fail the command at the next base.ExitIfErrors.)
+ // Special case: if the error is "no matching versions" then don't
+ // even report the error. Because Query does not consider pseudo-versions,
+ // it may happen that we have a pseudo-version but during -u=patch
+ // the query v0.0 matches no versions (not even the one we're using).
+ if !strings.Contains(err.Error(), "no matching versions") {
+ base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err)
+ }
+ return m, nil
+ }
+
+ // If we're on a later prerelease, keep using it,
+ // even though normally an Upgrade will ignore prereleases.
+ if semver.Compare(info.Version, m.Version) < 0 {
+ return m, nil
+ }
+
+ // If we're on a pseudo-version chronologically after the latest tagged version, keep using it.
+ // This avoids some accidental downgrades.
+ if mTime, err := modfetch.PseudoVersionTime(m.Version); err == nil && info.Time.Before(mTime) {
+ return m, nil
+ }
+ return module.Version{Path: m.Path, Version: info.Version}, nil
+}
package modinfo
+import "time"
+
+// Note that these structs are publicly visible (part of go list's API)
+// and the fields are documented in the help text in ../list/list.go
+
type ModulePublic struct {
- Top bool
- Path string
- Version string
+ Path string `json:",omitempty"` // module path
+ Version string `json:",omitempty"` // module version
+ Versions []string `json:",omitempty"` // available module versions
+ Replace *ModulePublic `json:",omitempty"` // replaced by this module
+ Time *time.Time `json:",omitempty"` // time version was created
+ Update *ModulePublic `json:",omitempty"` // available update (with -u)
+ Main bool `json:",omitempty"` // is this the main module?
+ Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
+ Dir string `json:",omitempty"` // directory holding local copy of files, if any
+ Error *ModuleError `json:",omitempty"` // error loading module
+}
+
+type ModuleError struct {
+ Err string // error text
+}
+
+func (m *ModulePublic) String() string {
+ s := m.Path
+ if m.Version != "" {
+ s += " " + m.Version
+ if m.Update != nil {
+ s += " [" + m.Update.Version + "]"
+ }
+ }
+ if m.Replace != nil {
+ s += " => " + m.Replace.Path
+ if m.Replace.Version != "" {
+ s += " " + m.Replace.Version
+ if m.Replace.Update != nil {
+ s += " [" + m.Replace.Update.Version + "]"
+ }
+ }
+ }
+ return s
}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "bytes"
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modinfo"
+ "cmd/go/internal/module"
+ "cmd/go/internal/search"
+ "cmd/go/internal/semver"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var (
+ infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
+ infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
+)
+
+func isStandardImportPath(path string) bool {
+ if search.IsStandardImportPath(path) {
+ if _, err := os.Stat(filepath.Join(cfg.GOROOT, "src", path)); err == nil {
+ return true
+ }
+ if _, err := os.Stat(filepath.Join(cfg.GOROOT, "src/vendor", path)); err == nil {
+ return true
+ }
+ }
+ return false
+}
+
+func PackageModuleInfo(pkgpath string) *modinfo.ModulePublic {
+ if isStandardImportPath(pkgpath) || !Enabled() {
+ return nil
+ }
+ return moduleInfo(findModule(pkgpath, pkgpath), true)
+}
+
+func ModuleInfo(path string) *modinfo.ModulePublic {
+ if !Enabled() {
+ return nil
+ }
+
+ if i := strings.Index(path, "@"); i >= 0 {
+ return moduleInfo(module.Version{Path: path[:i], Version: path[i+1:]}, false)
+ }
+
+ for _, m := range BuildList() {
+ if m.Path == path {
+ return moduleInfo(m, true)
+ }
+ }
+
+ return &modinfo.ModulePublic{
+ Path: path,
+ Error: &modinfo.ModuleError{
+ Err: "module not in current build",
+ },
+ }
+}
+
+// addUpdate fills in m.Update if an updated version is available.
+func addUpdate(m *modinfo.ModulePublic) {
+ if m.Version != "" {
+ if info, err := Query(m.Path, "latest", Allowed); err == nil && info.Version != m.Version {
+ m.Update = &modinfo.ModulePublic{
+ Path: m.Path,
+ Version: info.Version,
+ Time: &info.Time,
+ }
+ }
+ }
+}
+
+// addVersions fills in m.Versions with the list of known versions.
+func addVersions(m *modinfo.ModulePublic) {
+ m.Versions, _ = versions(m.Path)
+}
+
+func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
+ if m == Target {
+ return &modinfo.ModulePublic{
+ Path: m.Path,
+ Version: m.Version,
+ Main: true,
+ Dir: ModRoot,
+ }
+ }
+
+ info := &modinfo.ModulePublic{
+ Path: m.Path,
+ Version: m.Version,
+ Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
+ }
+
+ if cfg.BuildGetmode == "vendor" {
+ info.Dir = filepath.Join(ModRoot, "vendor", m.Path)
+ return info
+ }
+
+ // complete fills in the extra fields in m.
+ complete := func(m *modinfo.ModulePublic) {
+ if m.Version != "" {
+ if q, err := Query(m.Path, m.Version, nil); err != nil {
+ m.Error = &modinfo.ModuleError{Err: err.Error()}
+ } else {
+ m.Version = q.Version
+ m.Time = &q.Time
+ }
+
+ if semver.IsValid(m.Version) {
+ dir := filepath.Join(modfetch.SrcMod, m.Path+"@"+m.Version)
+ if stat, err := os.Stat(dir); err == nil && stat.IsDir() {
+ m.Dir = dir
+ }
+ }
+ }
+ if cfg.BuildGetmode == "vendor" {
+ m.Dir = filepath.Join(ModRoot, "vendor", m.Path)
+ }
+ }
+
+ complete(info)
+
+ if r := Replacement(m); r.Path != "" {
+ info.Replace = &modinfo.ModulePublic{
+ Path: r.Path,
+ Version: r.Version,
+ }
+ if r.Version == "" {
+ if filepath.IsAbs(r.Path) {
+ info.Replace.Dir = r.Path
+ } else {
+ info.Replace.Dir = filepath.Join(ModRoot, r.Path)
+ }
+ }
+ complete(info.Replace)
+ info.Dir = info.Replace.Dir
+ info.Error = nil // ignore error loading original module version (it has been replaced)
+ }
+
+ return info
+}
+
+func PackageBuildInfo(path string, deps []string) string {
+ if isStandardImportPath(path) || !Enabled() {
+ return ""
+ }
+ target := findModule(path, path)
+ mdeps := make(map[module.Version]bool)
+ for _, dep := range deps {
+ if !isStandardImportPath(dep) {
+ mdeps[findModule(path, dep)] = true
+ }
+ }
+ var mods []module.Version
+ delete(mdeps, target)
+ for mod := range mdeps {
+ mods = append(mods, mod)
+ }
+ module.Sort(mods)
+
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "path\t%s\n", path)
+ tv := target.Version
+ if tv == "" {
+ tv = "(devel)"
+ }
+ fmt.Fprintf(&buf, "mod\t%s\t%s\t%s\n", target.Path, tv, modfetch.Sum(target))
+ for _, mod := range mods {
+ mv := mod.Version
+ if mv == "" {
+ mv = "(devel)"
+ }
+ r := Replacement(mod)
+ h := ""
+ if r.Path == "" {
+ h = "\t" + modfetch.Sum(mod)
+ }
+ fmt.Fprintf(&buf, "dep\t%s\t%s%s\n", mod.Path, mod.Version, h)
+ if r.Path != "" {
+ fmt.Fprintf(&buf, "=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
+ }
+ }
+ return buf.String()
+}
+
+func findModule(target, path string) module.Version {
+ // TODO: This should use loaded.
+ if path == "." {
+ return buildList[0]
+ }
+ for _, mod := range buildList {
+ if maybeInModule(path, mod.Path) {
+ return mod
+ }
+ }
+ base.Fatalf("build %v: cannot find module for path %v", target, path)
+ panic("unreachable")
+}
+
+func ModInfoProg(info string) []byte {
+ return []byte(fmt.Sprintf(`
+ package main
+ import _ "unsafe"
+ //go:linkname __debug_modinfo__ runtime/debug.modinfo
+ var __debug_modinfo__ string
+ func init() {
+ __debug_modinfo__ = %q
+ }
+ `, string(infoStart)+info+string(infoEnd)))
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import "cmd/go/internal/base"
+
+// TODO(rsc): The links out to research.swtch.com here should all be
+// replaced eventually with links to proper documentation.
+
+var HelpModules = &base.Command{
+ UsageLine: "modules",
+ Short: "modules, module versions, and more",
+ Long: `
+A module is a collection of related Go packages.
+Modules are the unit of source code interchange and versioning.
+The go command has direct support for working with modules,
+including recording and resolving dependencies on other modules.
+Modules replace the old GOPATH-based approach to specifying
+which source files are used in a given build.
+
+Experimental module support
+
+Go 1.11 includes experimental support for Go modules,
+including a new module-aware 'go get' command.
+We intend to keep revising this support, while preserving compatibility,
+until it can be declared official (no longer experimental),
+and then at a later point we may remove support for work
+in GOPATH and the old 'go get' command.
+
+The quickest way to take advantage of the new Go 1.11 module support
+is to check out your repository into a directory outside GOPATH/src,
+create a go.mod file (described in the next section) there, and run
+go commands from within that file tree.
+
+For more fine-grained control, the module support in Go 1.11 respects
+a temporary environment variable, GO111MODULE, which can be set to one
+of three string values: off, on, or auto (the default).
+If GO111MODULE=off, then the go command never uses the
+new module support. Instead it looks in vendor directories and GOPATH
+to find dependencies; we now refer to this as "GOPATH mode."
+If GO111MODULE=on, then the go command requires the use of modules,
+never consulting GOPATH. We refer to this as the command being
+module-aware or running in "module-aware mode".
+If GO111MODULE=auto or is unset, then the go command enables or
+disables module support based on the current directory.
+Module support is enabled only when the current directory is outside
+GOPATH/src and itself contains a go.mod file or is below a directory
+containing a go.mod file.
+
+Defining a module
+
+A module is defined by a tree of Go source files with a go.mod file
+in the tree's root directory. The directory containing the go.mod file
+is called the module root. Typically the module root will also correspond
+to a source code repository root (but in general it need not).
+The module is the set of all Go packages in the module root and its
+subdirectories, but excluding subtrees with their own go.mod files.
+
+The "module path" is the import path prefix corresponding to the module root.
+The go.mod file defines the module path and lists the specific versions
+of other modules that should be used when resolving imports during a build,
+by giving their module paths and versions.
+
+For example, this go.mod declares that the directory containing it is the root
+of the module with path example.com/m, and it also declares that the module
+depends on specific versions of golang.org/x/text and gopkg.in/yaml.v2:
+
+ module example.com/m
+
+ require (
+ golang.org/x/text v0.3.0
+ gopkg.in/yaml.v2 v2.1.0
+ )
+
+The go.mod file can also specify replacements and excluded versions
+that only apply when building the module directly; they are ignored
+when the module is incorporated into a larger build.
+For more about the go.mod file, see https://research.swtch.com/vgo-module.
+
+To start a new module, simply create a go.mod file in the root of the
+module's directory tree, containing only a module statement.
+The 'go mod' command can be used to do this:
+
+ go mod -init -module example.com/m
+
+In a project already using an existing dependency management tool like
+godep, glide, or dep, 'go mod -init' will also add require statements
+matching the existing configuration.
+
+Once the go.mod file exists, no additional steps are required:
+go commands like 'go build', 'go test', or even 'go list' will automatically
+add new dependencies as needed to satisfy imports.
+
+The main module and the build list
+
+The "main module" is the module containing the directory where the go command
+is run. The go command finds the module root by looking for a go.mod in the
+current directory, or else the current directory's parent directory,
+or else the parent's parent directory, and so on.
+
+The main module's go.mod file defines the precise set of packages available
+for use by the go command, through require, replace, and exclude statements.
+Dependency modules, found by following require statements, also contribute
+to the definition of that set of packages, but only through their go.mod
+files' require statements: any replace and exclude statements in dependency
+modules are ignored. The replace and exclude statements therefore allow the
+main module complete control over its own build, without also being subject
+to complete control by dependencies.
+
+The set of modules providing packages to builds is called the "build list".
+The build list initially contains only the main module. Then the go command
+adds to the list the exact module versions required by modules already
+on the list, recursively, until there is nothing left to add to the list.
+If multiple versions of a particular module are added to the list,
+then at the end only the latest version (according to semantic version
+ordering) is kept for use in the build.
+
+The 'go list' command provides information about the main module
+and the build list. For example:
+
+ go list -m # print path of main module
+ go list -m -f={{.Dir}} # print root directory of main module
+ go list -m all # print build list
+
+Maintaining module requirements
+
+The go.mod file is meant to be readable and editable by both
+programmers and tools. The go command itself automatically updates the go.mod file
+to maintain a standard formatting and the accuracy of require statements.
+
+Any go command that finds an unfamiliar import will look up the module
+containing that import and add the latest version of that module
+to go.mod automatically. In most cases, therefore, it suffices to
+add an import to source code and run 'go build', 'go test', or even 'go list':
+as part of analyzing the package, the go command will discover
+and resolve the import and update the go.mod file.
+
+Any go command can determine that a module requirement is
+missing and must be added, even when considering only a single
+package from the module. On the other hand, determining that a module requirement
+is no longer necessary and can be deleted requires a full view of
+all packages in the module, across all possible build configurations
+(architectures, operating systems, build tags, and so on).
+The 'go mod -sync' command builds that view and then
+adds any missing module requirements and removes unnecessary ones.
+
+As part of maintaining the require statements in go.mod, the go command
+tracks which ones provide packages imported directly by the current module
+and which ones provide packages only used indirectly by other module
+dependencies. Requirements needed only for indirect uses are marked with a
+"// indirect" comment in the go.mod file. Indirect requirements are
+automatically removed from the go.mod file once they are implied by other
+direct requirements. Indirect requirements only arise when using modules
+that fail to state some of their own dependencies or when explicitly
+upgrading a module's dependencies ahead of its own stated requirements.
+
+Because of this automatic maintenance, the information in go.mod is an
+up-to-date, readable description of the build.
+
+The 'go get' command updates go.mod to change the module versions used in a
+build. An upgrade of one module may imply upgrading others, and similarly a
+downgrade of one module may imply downgrading others. The 'go get' command
+makes these implied changes as well. If go.mod is edited directly, commands
+like 'go build' or 'go list' will assume that an upgrade is intended and
+automatically make any implied upgrades and update go.mod to reflect them.
+
+The 'go mod' command provides other functionality for use in maintaining
+and understanding modules and go.mod files. See 'go help mod'.
+
+Pseudo-versions
+
+The go.mod file and the go command more generally use semantic versions as
+the standard form for describing module versions, so that versions can be
+compared to determine which should be considered earlier or later than another.
+A module version like v1.2.3 is introduced by tagging a revision in the
+underlying source repository. Untagged revisions can be referred to
+using a "pseudo-version" of the form v0.0.0-yyyymmddhhmmss-abcdefabcdef,
+where the time is the commit time in UTC and the final suffix is the prefix
+of the commit hash. The time portion ensures that two pseudo-versions can
+be compared to determine which happened later, the commit hash identifes
+the underlying commit, and the v0.0.0- prefix identifies the pseudo-version
+as a pre-release before version v0.0.0, so that the go command prefers any
+tagged release over any pseudo-version.
+
+Pseudo-versions never need to be typed by hand: the go command will accept
+the plain commit hash and translate it into a pseudo-version (or a tagged
+version if available) automatically. This conversion is an example of a
+module query.
+
+Module queries
+
+The go command accepts a "module query" in place of a module version
+both on the command line and in the main module's go.mod file.
+(After evaluating a query found in the main module's go.mod file,
+the go command updates the file to replace the query with its result.)
+
+A fully-specified semantic version, such as "v1.2.3",
+evaluates to that specific version.
+
+A semantic version prefix, such as "v1" or "v1.2",
+evaluates to the latest available tagged version with that prefix.
+
+A semantic version comparison, such as "<v1.2.3" or ">=v1.5.6",
+evaluates to the available tagged version nearest to the comparison target
+(the latest version for < and <=, the earliest version for > and >=).
+
+The string "latest" matches the latest available tagged version,
+or else the underlying source repository's latest untagged revision.
+
+A revision identifier for the underlying source repository,
+such as a commit hash prefix, revision tag, or branch name,
+selects that specific code revision. If the revision is
+also tagged with a semantic version, the query evaluates to
+that semantic version. Otherwise the query evaluates to a
+pseudo-version for the commit.
+
+All queries prefer release versions to pre-release versions.
+For example, "<v1.2.3" will prefer to return "v1.2.2"
+instead of "v1.2.3-pre1", even though "v1.2.3-pre1" is nearer
+to the comparison target.
+
+Module versions disallowed by exclude statements in the
+main module's go.mod are considered unavailable and cannot
+be returned by queries.
+
+For example, these commands are all valid:
+
+ go get github.com/gorilla/mux@latest # same (@latest is default for 'go get')
+ go get github.com/gorilla/mux@v1.6.2 # records v1.6.2
+ go get github.com/gorilla/mux@e3702bed2 # records v1.6.2
+ go get github.com/gorilla/mux@c856192 # records v0.0.0-20180517173623-c85619274f5d
+ go get github.com/gorilla/mux@master # records current meaning of master
+
+
+Module compatibility and semantic versioning
+
+The go command requires that modules use semantic versions and expects that
+the versions accurately describe compatibility: it assumes that v1.5.4 is a
+backwards-compatible replacement for v1.5.3, v1.4.0, and even v1.0.0.
+More generally the go command expects that packages follow the
+"import compatibility rule", which says:
+
+"If an old package and a new package have the same import path,
+the new package must be backwards compatible with the old package."
+
+Because the go command assumes the import compatibility rule,
+a module definition can only set the minimum required version of one
+of its dependencies: it cannot set a maximum or exclude selected versions.
+Still, the import compatibility rule is not a guarantee: it may be that
+v1.5.4 is buggy and not a backwards-compatible replacement for v1.5.3.
+Because of this, the go command never updates from an older version
+to a newer version of a module unasked.
+
+In semantic versioning, changing the major version number indicates a lack
+of backwards compatibility with earlier versions. To preserve import
+compatibility, the go command requires that modules with major version v2
+or later use a module path with that major version as the final element.
+For example, version v2.0.0 of example.com/m must instead use module path
+example.com/m/v2, and packages in that module would use that path as
+their import path prefix, as in example.com/m/v2/sub/pkg. Including the
+major version number in the module path and import paths in this way is
+called "semantic import versioning". Pseudo-versions for modules with major
+version v2 and later begin with that major version instead of v0, as in
+v2.0.0-20180326061214-4fc5987536ef.
+
+The go command treats modules with different module paths as unrelated:
+it makes no connection between example.com/m and example.com/m/v2.
+Modules with different major versions can be used together in a build
+and are kept separate by the fact that their packages use different
+import paths.
+
+In semantic versioning, major version v0 is for initial development,
+indicating no expectations of stability or backwards compatibility.
+Major version v0 does not appear in the module path, because those
+versions are preparation for v1.0.0, and v1 does not appear in the
+module path either.
+
+As a special case, for historical reasons, module paths beginning with
+gopkg.in/ continue to use the conventions established on that system:
+the major version is always present, and it is preceded by a dot
+instead of a slash: gopkg.in/yaml.v1 and gopkg.in/yaml.v2, not
+gopkg.in/yaml and gopkg.in/yaml/v2.
+
+See https://research.swtch.com/vgo-import and https://semver.org/
+for more information.
+
+Module verification
+
+The go command maintains, in the main module's root directory alongside
+go.mod, a file named go.sum containing the expected cryptographic checksums
+of the content of specific module versions. Each time a dependency is
+used, its checksum is added to go.sum if missing or else required to match
+the existing entry in go.sum.
+
+The go command maintains a cache of downloaded packages and computes
+and records the cryptographic checksum of each package at download time.
+In normal operation, the go command checks these pre-computed checksums
+against the main module's go.sum file, instead of recomputing them on
+each command invocation. The 'go mod -verify' command checks that
+the cached copies of module downloads still match both their recorded
+checksums and the entries in go.sum.
+
+Modules and vendoring
+
+When using modules, the go command completely ignores vendor directories.
+
+By default, the go command satisfies dependencies by downloading modules
+from their sources and using those downloaded copies (after verification,
+as described in the previous section). To allow interoperation with older
+versions of Go, or to ensure that all files used for a build are stored
+together in a single file tree, 'go mod -vendor' creates a directory named
+vendor in the root directory of the main module and stores there all the
+packages from dependency modules that are needed to support builds and
+tests of packages in the main module.
+
+To build using the main module's top-level vendor directory to satisfy
+dependencies (disabling use of the usual network sources and local
+caches), use 'go build -getmode=vendor'. Note that only the main module's
+top-level vendor directory is used; vendor directories in other locations
+are still ignored.
+ `,
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "go/build"
+ "os"
+ pathpkg "path"
+ "path/filepath"
+ "strings"
+
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/module"
+ "cmd/go/internal/par"
+ "cmd/go/internal/search"
+)
+
+type ImportMissingError struct {
+ ImportPath string
+ Module module.Version
+}
+
+func (e *ImportMissingError) Error() string {
+ if e.Module.Path == "" {
+ return "cannot find module providing package " + e.ImportPath
+ }
+ return "missing module for import: " + e.Module.Path + "@" + e.Module.Version + " provides " + e.ImportPath
+}
+
+// Import finds the module and directory in the build list
+// containing the package with the given import path.
+// The answer must be unique: Import returns an error
+// if multiple modules attempt to provide the same package.
+// Import can return a module with an empty m.Path, for packages in the standard library.
+// Import can return an empty directory string, for fake packages like "C" and "unsafe".
+//
+// If the package cannot be found in the current build list,
+// Import returns an ImportMissingError as the error.
+// If Import can identify a module that could be added to supply the package,
+// the ImportMissingErr records that module.
+func Import(path string) (m module.Version, dir string, err error) {
+ if strings.Contains(path, "@") {
+ return module.Version{}, "", fmt.Errorf("import path should not have @version")
+ }
+ if build.IsLocalImport(path) {
+ return module.Version{}, "", fmt.Errorf("relative import not supported")
+ }
+ if path == "C" || path == "unsafe" {
+ // There's no directory for import "C" or import "unsafe".
+ return module.Version{}, "", nil
+ }
+
+ // Is the package in the standard library?
+ if search.IsStandardImportPath(path) {
+ if strings.HasPrefix(path, "golang_org/") {
+ return module.Version{}, filepath.Join(cfg.GOROOT, "src/vendor", path), nil
+ }
+ dir := filepath.Join(cfg.GOROOT, "src", path)
+ if _, err := os.Stat(dir); err == nil {
+ return module.Version{}, dir, nil
+ }
+ }
+
+ // -getmode=vendor is special.
+ // Everything must be in the main module or the main module's vendor directory.
+ if cfg.BuildGetmode == "vendor" {
+ mainDir, mainOK := dirInModule(path, Target.Path, ModRoot, true)
+ vendorDir, vendorOK := dirInModule(path, "", filepath.Join(ModRoot, "vendor"), false)
+ if mainOK && vendorOK {
+ return module.Version{}, "", fmt.Errorf("ambiguous import: found %s in multiple directories:\n\t%s\n\t%s", path, mainDir, vendorDir)
+ }
+ // Prefer to return main directory if there is one,
+ // Note that we're not checking that the package exists.
+ // We'll leave that for load.
+ if !vendorOK && mainDir != "" {
+ return Target, mainDir, nil
+ }
+ readVendorList()
+ return vendorMap[path], vendorDir, nil
+ }
+
+ // Check each module on the build list.
+ var dirs []string
+ var mods []module.Version
+ for _, m := range buildList {
+ if !maybeInModule(path, m.Path) {
+ // Avoid possibly downloading irrelevant modules.
+ continue
+ }
+ root, isLocal, err := fetch(m)
+ if err != nil {
+ // Report fetch error.
+ // Note that we don't know for sure this module is necessary,
+ // but it certainly _could_ provide the package, and even if we
+ // continue the loop and find the package in some other module,
+ // we need to look at this module to make sure the import is
+ // not ambiguous.
+ return module.Version{}, "", err
+ }
+ dir, ok := dirInModule(path, m.Path, root, isLocal)
+ if ok {
+ mods = append(mods, m)
+ dirs = append(dirs, dir)
+ }
+ }
+ if len(mods) == 1 {
+ return mods[0], dirs[0], nil
+ }
+ if len(mods) > 0 {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "ambiguous import: found %s in multiple modules:", path)
+ for i, m := range mods {
+ fmt.Fprintf(&buf, "\n\t%s", m.Path)
+ if m.Version != "" {
+ fmt.Fprintf(&buf, " %s", m.Version)
+ }
+ fmt.Fprintf(&buf, " (%s)", dirs[i])
+ }
+ return module.Version{}, "", errors.New(buf.String())
+ }
+
+ // Special case: if the path matches a module path,
+ // and we haven't found code in any module on the build list
+ // (since we haven't returned yet),
+ // force the use of the current module instead of
+ // looking for an alternate one.
+ // This helps "go get golang.org/x/net" even though
+ // there is no code in x/net.
+ for _, m := range buildList {
+ if m.Path == path {
+ root, isLocal, err := fetch(m)
+ if err != nil {
+ return module.Version{}, "", err
+ }
+ dir, _ := dirInModule(path, m.Path, root, isLocal)
+ return m, dir, nil
+ }
+ }
+
+ // Not on build list.
+
+ // Look up module containing the package, for addition to the build list.
+ // Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
+ if cfg.BuildGetmode == "local" {
+ return module.Version{}, "", fmt.Errorf("import lookup disabled by -getmode=local")
+ }
+
+ for p := path; p != "."; p = pathpkg.Dir(p) {
+ // We can't upgrade the main module.
+ // Note that this loop does consider upgrading other modules on the build list.
+ // If that's too aggressive we can skip all paths already on the build list,
+ // not just Target.Path, but for now let's try being aggressive.
+ if p == Target.Path {
+ // Can't move to a new version of main module.
+ continue
+ }
+
+ info, err := Query(p, "latest", Allowed)
+ if err != nil {
+ continue
+ }
+ m := module.Version{Path: p, Version: info.Version}
+ root, isLocal, err := fetch(m)
+ if err != nil {
+ continue
+ }
+ _, ok := dirInModule(path, m.Path, root, isLocal)
+ if ok {
+ return module.Version{}, "", &ImportMissingError{ImportPath: path, Module: m}
+ }
+
+ // Special case matching the one above:
+ // if m.Path matches path, assume adding it to the build list
+ // will either add the right code or the right code doesn't exist.
+ if m.Path == path {
+ return module.Version{}, "", &ImportMissingError{ImportPath: path, Module: m}
+ }
+ }
+
+ // Did not resolve import to any module.
+ // TODO(rsc): It would be nice to return a specific error encountered
+ // during the loop above if possible, but it's not clear how to pick
+ // out the right one.
+ return module.Version{}, "", &ImportMissingError{ImportPath: path}
+}
+
+// maybeInModule reports whether, syntactically,
+// a package with the given import path could be supplied
+// by a module with the given module path (mpath).
+func maybeInModule(path, mpath string) bool {
+ return mpath == path ||
+ len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
+}
+
+var haveGoModCache, haveGoFilesCache par.Cache
+
+// dirInModule locates the directory that would hold the package named by the given path,
+// if it were in the module with module path mpath and root mdir.
+// If path is syntactically not within mpath,
+// or if mdir is a local file tree (isLocal == true) and the directory
+// that would hold path is in a sub-module (covered by a go.mod below mdir),
+// dirInModule returns "", false.
+//
+// Otherwise, dirInModule returns the name of the directory where
+// Go source files would be expected, along with a boolean indicating
+// whether there are in fact Go source files in that directory.
+func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool) {
+ // Determine where to expect the package.
+ if path == mpath {
+ dir = mdir
+ } else if mpath == "" { // vendor directory
+ dir = filepath.Join(mdir, path)
+ } else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
+ dir = filepath.Join(mdir, path[len(mpath)+1:])
+ } else {
+ return "", false
+ }
+
+ // Check that there aren't other modules in the way.
+ // This check is unnecessary inside the module cache
+ // and important to skip in the vendor directory,
+ // where all the module trees have been overlaid.
+ // So we only check local module trees
+ // (the main module, and any directory trees pointed at by replace directives).
+ if isLocal {
+ for d := dir; d != mdir && len(d) > len(mdir); d = filepath.Dir(d) {
+ haveGoMod := haveGoModCache.Do(d, func() interface{} {
+ _, err := os.Stat(filepath.Join(d, "go.mod"))
+ return err == nil
+ }).(bool)
+
+ if haveGoMod {
+ return "", false
+ }
+ }
+ }
+
+ // Now committed to returning dir (not "").
+
+ // Are there Go source files in the directory?
+ // We don't care about build tags, not even "+build ignore".
+ // We're just looking for a plausible directory.
+ haveGoFiles = haveGoFilesCache.Do(dir, func() interface{} {
+ f, err := os.Open(dir)
+ if err != nil {
+ return false
+ }
+ defer f.Close()
+ names, _ := f.Readdirnames(-1)
+ for _, name := range names {
+ if strings.HasSuffix(name, ".go") {
+ info, err := os.Stat(filepath.Join(dir, name))
+ if err == nil && info.Mode().IsRegular() {
+ return true
+ }
+ }
+ }
+ return false
+ }).(bool)
+
+ return dir, haveGoFiles
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "internal/testenv"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+var importTests = []struct {
+ path string
+ err string
+}{
+ {
+ path: "golang.org/x/net/context",
+ err: "missing module for import: golang.org/x/net@.* provides golang.org/x/net/context",
+ },
+ {
+ path: "golang.org/x/net",
+ err: "missing module for import: golang.org/x/net@.* provides golang.org/x/net",
+ },
+ {
+ path: "golang.org/x/text",
+ err: "missing module for import: golang.org/x/text@.* provides golang.org/x/text",
+ },
+ {
+ path: "github.com/rsc/quote/buggy",
+ err: "missing module for import: github.com/rsc/quote@v1.5.2 provides github.com/rsc/quote/buggy",
+ },
+ {
+ path: "github.com/rsc/quote",
+ err: "missing module for import: github.com/rsc/quote@v1.5.2 provides github.com/rsc/quote",
+ },
+ {
+ path: "golang.org/x/foo/bar",
+ err: "cannot find module providing package golang.org/x/foo/bar",
+ },
+}
+
+func TestImport(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
+ for _, tt := range importTests {
+ t.Run(strings.Replace(tt.path, "/", "_", -1), func(t *testing.T) {
+ // Note that there is no build list, so Import should always fail.
+ m, dir, err := Import(tt.path)
+ if err == nil {
+ t.Fatalf("Import(%q) = %v, %v, nil; expected error", tt.path, m, dir)
+ }
+ if !regexp.MustCompile(tt.err).MatchString(err.Error()) {
+ t.Fatalf("Import(%q): error %q, want error matching %#q", tt.path, err, tt.err)
+ }
+ })
+ }
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package vgo
+package modload
import (
"bytes"
"cmd/go/internal/base"
+ "cmd/go/internal/cache"
"cmd/go/internal/cfg"
+ "cmd/go/internal/load"
"cmd/go/internal/modconv"
"cmd/go/internal/modfetch"
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/mvs"
"cmd/go/internal/search"
"cmd/go/internal/semver"
+ "cmd/go/internal/str"
"encoding/json"
- "flag"
"fmt"
"io/ioutil"
"os"
)
var (
- cwd string
- enabled = MustBeVgo
- MustBeVgo = mustBeVgo()
- initialized bool
+ cwd string
+ enabled = MustUseModules
+ MustUseModules = mustUseModules()
+ initialized bool
ModRoot string
modFile *modfile.File
Target module.Version
gopath string
- srcV string
+
+ CmdModInit bool // go mod -init flag
+ CmdModModule string // go mod -module flag
)
+// ModFile returns the parsed go.mod file.
+//
+// Note that after calling ImportPaths or LoadBuildList,
+// the require statements in the modfile.File are no longer
+// the source of truth and will be ignored: edits made directly
+// will be lost at the next call to WriteGoMod.
+// To make permanent changes to the require statements
+// in go.mod, edit it before calling ImportPaths or LoadBuildList.
+func ModFile() *modfile.File {
+ return modFile
+}
+
func BinDir() string {
if !Enabled() {
- panic("vgo.Bin")
+ panic("modload.BinDir")
}
return filepath.Join(gopath, "bin")
}
-func init() {
- flag.BoolVar(&MustBeVgo, "vgo", MustBeVgo, "require use of modules")
-}
-
-// mustBeVgo reports whether we are invoked as vgo
+// mustUseModules reports whether we are invoked as vgo
// (as opposed to go).
// If so, we only support builds with go.mod files.
-func mustBeVgo() bool {
+func mustUseModules() bool {
name := os.Args[0]
name = name[strings.LastIndex(name, "/")+1:]
name = name[strings.LastIndex(name, `\`)+1:]
}
initialized = true
- // If this is testgo - the test binary during cmd/go tests - then
- // do not let it look for a go.mod. Only use vgo support if the
- // global -vgo flag has been passed on the command line.
- if base := filepath.Base(os.Args[0]); (base == "testgo" || base == "testgo.exe") && !MustBeVgo {
+ env := os.Getenv("GO111MODULE")
+ switch env {
+ default:
+ base.Fatalf("go: unknown environment setting GO111MODULE=%s", env)
+ case "", "auto":
+ // leave MustUseModules alone
+ case "on":
+ MustUseModules = true
+ case "off":
+ if !MustUseModules {
+ return
+ }
+ }
+
+ // If this is testgo - the test binary during cmd/go tests -
+ // then do not let it look for a go.mod unless GO111MODULE has an explicit setting.
+ if base := filepath.Base(os.Args[0]); (base == "testgo" || base == "testgo.exe") && env == "" {
return
}
base.Fatalf("go: %v", err)
}
- root, _ := FindModuleRoot(cwd, "", MustBeVgo)
- if root == "" {
- // If invoked as vgo, insist on a mod file.
- if MustBeVgo {
- base.Fatalf("cannot determine module root; please create a go.mod file there")
+ if CmdModInit {
+ // Running 'go mod -init': go.mod will be created in current directory.
+ ModRoot = cwd
+ } else {
+ inGOPATH := false
+ for _, gopath := range filepath.SplitList(cfg.BuildContext.GOPATH) {
+ if gopath == "" {
+ continue
+ }
+ if str.HasFilePathPrefix(cwd, filepath.Join(gopath, "src")) {
+ inGOPATH = true
+ break
+ }
}
- return
+ if inGOPATH {
+ if !MustUseModules {
+ // No automatic enabling in GOPATH.
+ return
+ }
+ }
+ root, _ := FindModuleRoot(cwd, "", MustUseModules)
+ if root == "" {
+ // If invoked as vgo, insist on a mod file.
+ if MustUseModules {
+ base.Fatalf("go: cannot find main module root; see 'go help modules'")
+ }
+ return
+ }
+
+ ModRoot = root
+ }
+
+ if c := cache.Default(); c == nil {
+ // With modules, there are no install locations for packages
+ // other than the build cache.
+ base.Fatalf("go: cannot use modules with build cache disabled")
}
+
+ cfg.ModulesEnabled = true
enabled = true
- ModRoot = root
- search.SetModRoot(root)
+ load.ModBinDir = BinDir
+ load.ModLookup = Lookup
+ load.ModPackageModuleInfo = PackageModuleInfo
+ load.ModImportPaths = ImportPaths
+ load.ModPackageBuildInfo = PackageBuildInfo
+ load.ModInfoProg = ModInfoProg
+ load.ModImportFromFiles = ImportFromFiles
+
+ search.SetModRoot(ModRoot)
}
func Enabled() bool {
- return false // COMPLETELY OFF FOR NOW
- /*
- if !initialized {
- panic("vgo: Enabled called before Init")
- }
- return enabled
- */
+ if !initialized {
+ panic("go: Enabled called before Init")
+ }
+ return enabled
}
func InitMod() {
if _, err := os.Stat(filepath.Join(gopath, "go.mod")); err == nil {
base.Fatalf("$GOPATH/go.mod exists but should not")
}
- srcV = filepath.Join(list[0], "src/v")
- codehost.WorkRoot = filepath.Join(srcV, "cache/vcswork")
+
+ srcV := filepath.Join(list[0], "src/v")
+ srcMod := filepath.Join(list[0], "src/mod")
+ infoV, errV := os.Stat(srcV)
+ _, errMod := os.Stat(srcMod)
+ if errV == nil && infoV.IsDir() && errMod != nil && os.IsNotExist(errMod) {
+ os.Rename(srcV, srcMod)
+ }
+
+ modfetch.SrcMod = srcMod
+ modfetch.GoSumFile = filepath.Join(ModRoot, "go.sum")
+ codehost.WorkRoot = filepath.Join(srcMod, "cache/vcs")
+
+ if CmdModInit {
+ // Running go mod -init: do legacy module conversion
+ // (go.mod does not exist yet, and it's not our job to write it).
+ legacyModInit()
+ modFileToBuildList()
+ return
+ }
gomod := filepath.Join(ModRoot, "go.mod")
data, err := ioutil.ReadFile(gomod)
if err != nil {
- legacyModInit()
- return
+ if os.IsNotExist(err) {
+ legacyModInit()
+ modFileToBuildList()
+ WriteGoMod()
+ return
+ }
+ base.Fatalf("go: %v", err)
}
f, err := modfile.Parse(gomod, data, fixVersion)
if err != nil {
// Errors returned by modfile.Parse begin with file:line.
- base.Fatalf("vgo: errors parsing go.mod:\n%s\n", err)
+ base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
}
modFile = f
// Empty mod file. Must add module path.
path, err := FindModulePath(ModRoot)
if err != nil {
- base.Fatalf("vgo: %v", err)
+ base.Fatalf("go: %v", err)
}
f.AddModuleStmt(path)
}
for _, x := range f.Exclude {
excluded[x.Mod] = true
}
- Target = f.Module.Mod
- writeGoMod()
+ modFileToBuildList()
+ WriteGoMod()
}
-func allowed(m module.Version) bool {
+// modFileToBuildList initializes buildList from the modFile.
+func modFileToBuildList() {
+ Target = modFile.Module.Mod
+ list := []module.Version{Target}
+ for _, r := range modFile.Require {
+ list = append(list, r.Mod)
+ }
+ buildList = list
+}
+
+// Allowed reports whether module m is allowed (not excluded) by the main module's go.mod.
+func Allowed(m module.Version) bool {
return !excluded[m]
}
if modFile == nil {
path, err := FindModulePath(ModRoot)
if err != nil {
- base.Fatalf("vgo: %v", err)
+ base.Fatalf("go: %v", err)
}
- fmt.Fprintf(os.Stderr, "vgo: creating new go.mod: module %s\n", path)
+ fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", path)
modFile = new(modfile.File)
modFile.AddModuleStmt(path)
}
- Target = modFile.Module.Mod
for _, name := range altConfigs {
cfg := filepath.Join(ModRoot, name)
data, err := ioutil.ReadFile(cfg)
if convert == nil {
return
}
- fmt.Fprintf(os.Stderr, "vgo: copying requirements from %s\n", cfg)
+ fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(cfg))
cfg = filepath.ToSlash(cfg)
- if err := modfetch.ConvertLegacyConfig(modFile, cfg, data); err != nil {
- base.Fatalf("vgo: %v", err)
+ if err := modconv.ConvertLegacyConfig(modFile, cfg, data); err != nil {
+ base.Fatalf("go: %v", err)
}
if len(modFile.Syntax.Stmt) == 1 {
- // Add comment to prevent vgo from re-converting every time it runs.
- modFile.AddComment("// vgo: no requirements found in " + name)
+ // Add comment to avoid re-converting every time it runs.
+ modFile.AddComment("// go: no requirements found in " + name)
}
return
}
// Exported only for testing.
func FindModulePath(dir string) (string, error) {
- for _, gpdir := range filepath.SplitList(cfg.BuildContext.GOPATH) {
- src := filepath.Join(gpdir, "src") + string(filepath.Separator)
- if strings.HasPrefix(dir, src) {
- return filepath.ToSlash(dir[len(src):]), nil
- }
+ if CmdModModule != "" {
+ // Running go mod -init -module=x/y/z; return x/y/z.
+ return CmdModModule, nil
}
// Cast about for import comments,
// Look for Godeps.json declaring import path.
data, _ := ioutil.ReadFile(filepath.Join(dir, "Godeps/Godeps.json"))
- var cfg struct{ ImportPath string }
- json.Unmarshal(data, &cfg)
- if cfg.ImportPath != "" {
- return cfg.ImportPath, nil
+ var cfg1 struct{ ImportPath string }
+ json.Unmarshal(data, &cfg1)
+ if cfg1.ImportPath != "" {
+ return cfg1.ImportPath, nil
}
// Look for vendor.json declaring import path.
return cfg2.RootPath, nil
}
+ // Look for path in GOPATH.
+ for _, gpdir := range filepath.SplitList(cfg.BuildContext.GOPATH) {
+ src := filepath.Join(gpdir, "src") + string(filepath.Separator)
+ if strings.HasPrefix(dir, src) {
+ return filepath.ToSlash(dir[len(src):]), nil
+ }
+ }
+
// Look for .git/config with github origin as last resort.
data, _ = ioutil.ReadFile(filepath.Join(dir, ".git/config"))
if m := gitOriginRE.FindSubmatch(data); m != nil {
return path
}
-func writeGoMod() {
- writeModHash()
+// WriteGoMod writes the current build list back to go.mod.
+func WriteGoMod() {
+ modfetch.WriteGoSum()
- if buildList != nil {
- min, err := mvs.Req(Target, buildList, newReqs())
+ if loaded != nil {
+ var direct []string
+ for _, m := range buildList[1:] {
+ if loaded.direct[m.Path] {
+ direct = append(direct, m.Path)
+ }
+ }
+ min, err := mvs.Req(Target, buildList, direct, Reqs())
if err != nil {
- base.Fatalf("vgo: %v", err)
+ base.Fatalf("go: %v", err)
}
- modFile.SetRequire(min)
+ var list []*modfile.Require
+ for _, m := range min {
+ list = append(list, &modfile.Require{
+ Mod: m,
+ Indirect: !loaded.direct[m.Path],
+ })
+ }
+ modFile.SetRequire(list)
}
file := filepath.Join(ModRoot, "go.mod")
old, _ := ioutil.ReadFile(file)
+ modFile.Cleanup() // clean file after edits
new, err := modFile.Format()
if err != nil {
- base.Fatalf("vgo: %v", err)
+ base.Fatalf("go: %v", err)
}
if bytes.Equal(old, new) {
return
}
if err := ioutil.WriteFile(file, new, 0666); err != nil {
- base.Fatalf("vgo: %v", err)
+ base.Fatalf("go: %v", err)
}
}
return vers, nil
}
- info, err := modfetch.Query(path, vers, nil)
+ info, err := Query(path, vers, nil)
if err != nil {
return "", err
}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modinfo"
+ "cmd/go/internal/module"
+ "cmd/go/internal/par"
+ "cmd/go/internal/search"
+)
+
+func ListModules(args []string, listU, listVersions bool) []*modinfo.ModulePublic {
+ mods := listModules(args)
+ if listU || listVersions {
+ var work par.Work
+ for _, m := range mods {
+ work.Add(m)
+ if m.Replace != nil {
+ work.Add(m.Replace)
+ }
+ }
+ work.Do(10, func(item interface{}) {
+ m := item.(*modinfo.ModulePublic)
+ if listU {
+ addUpdate(m)
+ }
+ if listVersions {
+ addVersions(m)
+ }
+ })
+ }
+ return mods
+}
+
+func listModules(args []string) []*modinfo.ModulePublic {
+ LoadBuildList()
+ if len(args) == 0 {
+ return []*modinfo.ModulePublic{moduleInfo(buildList[0], true)}
+ }
+
+ var mods []*modinfo.ModulePublic
+ matchedBuildList := make([]bool, len(buildList))
+ for _, arg := range args {
+ if strings.Contains(arg, `\`) {
+ base.Fatalf("go: module paths never use backslash")
+ }
+ if search.IsRelativePath(arg) {
+ base.Fatalf("go: cannot use relative path %s to specify module", arg)
+ }
+ if i := strings.Index(arg, "@"); i >= 0 {
+ info, err := Query(arg[:i], arg[i+1:], nil)
+ if err != nil {
+ mods = append(mods, &modinfo.ModulePublic{
+ Path: arg[:i],
+ Version: arg[i+1:],
+ Error: &modinfo.ModuleError{
+ Err: err.Error(),
+ },
+ })
+ continue
+ }
+ mods = append(mods, moduleInfo(module.Version{Path: arg[:i], Version: info.Version}, false))
+ continue
+ }
+
+ // Module path or pattern.
+ var match func(string) bool
+ var literal bool
+ if arg == "all" {
+ match = func(string) bool { return true }
+ } else if strings.Contains(arg, "...") {
+ match = search.MatchPattern(arg)
+ } else {
+ match = func(p string) bool { return arg == p }
+ literal = true
+ }
+ matched := false
+ for i, m := range buildList {
+ if match(m.Path) {
+ matched = true
+ if !matchedBuildList[i] {
+ matchedBuildList[i] = true
+ mods = append(mods, moduleInfo(m, true))
+ }
+ }
+ }
+ if !matched {
+ if literal {
+ mods = append(mods, &modinfo.ModulePublic{
+ Path: arg,
+ Error: &modinfo.ModuleError{
+ Err: fmt.Sprintf("module %q is not a known dependency", arg),
+ },
+ })
+ } else {
+ fmt.Fprintf(os.Stderr, "warning: pattern %q matched no module dependencies\n", arg)
+ }
+ }
+ }
+
+ return mods
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "go/build"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "sync"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/imports"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modfile"
+ "cmd/go/internal/module"
+ "cmd/go/internal/mvs"
+ "cmd/go/internal/par"
+ "cmd/go/internal/search"
+ "cmd/go/internal/semver"
+)
+
+// buildList is the list of modules to use for building packages.
+// It is initialized by calling ImportPaths, ImportFromFiles,
+// LoadALL, or LoadBuildList, each of which uses loaded.load.
+//
+// Ideally, exactly ONE of those functions would be called,
+// and exactly once. Most of the time, that's true.
+// During "go get" it may not be. TODO(rsc): Figure out if
+// that restriction can be established, or else document why not.
+//
+var buildList []module.Version
+
+// loaded is the most recently-used package loader.
+// It holds details about individual packages.
+//
+// Note that loaded.buildList is only valid during a load operation;
+// afterward, it is copied back into the global buildList,
+// which should be used instead.
+var loaded *loader
+
+// ImportPaths returns the set of packages matching the args (patterns),
+// adding modules to the build list as needed to satisfy new imports.
+func ImportPaths(args []string) []string {
+ if Init(); !Enabled() {
+ return search.ImportPaths(args)
+ }
+ InitMod()
+
+ cleaned := search.CleanImportPaths(args)
+ loaded = newLoader()
+ var paths []string
+ loaded.load(func() []string {
+ var roots []string
+ paths = nil
+ for _, pkg := range cleaned {
+ switch {
+ case build.IsLocalImport(pkg):
+ list := []string{pkg}
+ if strings.Contains(pkg, "...") {
+ // TODO: Where is the go.mod cutoff?
+ list = warnPattern(pkg, search.AllPackagesInFS(pkg))
+ }
+ for _, pkg := range list {
+ dir := filepath.Join(cwd, pkg)
+ if dir == ModRoot {
+ pkg = Target.Path
+ } else if strings.HasPrefix(dir, ModRoot+string(filepath.Separator)) {
+ suffix := filepath.ToSlash(dir[len(ModRoot):])
+ if strings.HasPrefix(suffix, "/vendor/") {
+ // TODO getmode vendor check
+ pkg = strings.TrimPrefix(suffix, "/vendor/")
+ } else {
+ pkg = Target.Path + suffix
+ }
+ } else {
+ base.Errorf("go: package %s outside module root", pkg)
+ continue
+ }
+ roots = append(roots, pkg)
+ paths = append(paths, pkg)
+ }
+
+ case pkg == "all":
+ if loaded.testRoots {
+ loaded.testAll = true
+ }
+ // TODO: Don't print warnings multiple times.
+ roots = append(roots, warnPattern("all", matchPackages("...", loaded.tags, []module.Version{Target}))...)
+ paths = append(paths, "all") // will expand after load completes
+
+ case search.IsMetaPackage(pkg): // std, cmd
+ list := search.AllPackages(pkg)
+ roots = append(roots, list...)
+ paths = append(paths, list...)
+
+ case strings.Contains(pkg, "..."):
+ // TODO: Don't we need to reevaluate this one last time once the build list stops changing?
+ list := warnPattern(pkg, matchPackages(pkg, loaded.tags, buildList))
+ roots = append(roots, list...)
+ paths = append(paths, list...)
+
+ default:
+ roots = append(roots, pkg)
+ paths = append(paths, pkg)
+ }
+ }
+ return roots
+ })
+ WriteGoMod()
+
+ // Process paths to produce final paths list.
+ // Remove duplicates and expand "all".
+ have := make(map[string]bool)
+ var final []string
+ for _, path := range paths {
+ if have[path] {
+ continue
+ }
+ have[path] = true
+ if path == "all" {
+ for _, pkg := range loaded.pkgs {
+ if !have[pkg.path] {
+ have[pkg.path] = true
+ final = append(final, pkg.path)
+ }
+ }
+ continue
+ }
+ final = append(final, path)
+ }
+ return final
+}
+
+// warnPattern returns list, the result of matching pattern,
+// but if list is empty then first it prints a warning about
+// the pattern not matching any packages.
+func warnPattern(pattern string, list []string) []string {
+ if len(list) == 0 {
+ fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
+ }
+ return list
+}
+
+// ImportFromFiles adds modules to the build list as needed
+// to satisfy the imports in the named Go source files.
+func ImportFromFiles(gofiles []string) {
+ if Init(); !Enabled() {
+ return
+ }
+ InitMod()
+
+ imports, testImports, err := imports.ScanFiles(gofiles, imports.Tags())
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
+ loaded = newLoader()
+ loaded.load(func() []string {
+ var roots []string
+ roots = append(roots, imports...)
+ roots = append(roots, testImports...)
+ return roots
+ })
+ WriteGoMod()
+}
+
+// LoadBuildList loads and returns the build list from go.mod.
+// The loading of the build list happens automatically in ImportPaths:
+// LoadBuildList need only be called if ImportPaths is not
+// (typically in commands that care about the module but
+// no particular package).
+func LoadBuildList() []module.Version {
+ if Init(); !Enabled() {
+ base.Fatalf("go: LoadBuildList called but modules not enabled")
+ }
+ InitMod()
+ ReloadBuildList()
+ WriteGoMod()
+ return buildList
+}
+
+func ReloadBuildList() []module.Version {
+ loaded = newLoader()
+ loaded.load(func() []string { return nil })
+ return buildList
+}
+
+// LoadALL returns the set of all packages in the current module
+// and their dependencies in any other modules, without filtering
+// due to build tags, except "+build ignore".
+// It adds modules to the build list as needed to satisfy new imports.
+// This set is useful for deciding whether a particular import is needed
+// anywhere in a module.
+func LoadALL() []string {
+ return loadAll(true)
+}
+
+// LoadVendor is like LoadALL but only follows test dependencies
+// for tests in the main module. Tests in dependency modules are
+// ignored completely.
+// This set is useful for identifying the which packages to include in a vendor directory.
+func LoadVendor() []string {
+ return loadAll(false)
+}
+
+func loadAll(testAll bool) []string {
+ if Init(); !Enabled() {
+ panic("go: misuse of LoadALL/LoadVendor")
+ }
+ InitMod()
+
+ loaded = newLoader()
+ loaded.isALL = true
+ loaded.tags = anyTags
+ loaded.testAll = testAll
+ if !testAll {
+ loaded.testRoots = true
+ }
+ all := TargetPackages()
+ loaded.load(func() []string { return all })
+ WriteGoMod()
+
+ var paths []string
+ for _, pkg := range loaded.pkgs {
+ paths = append(paths, pkg.path)
+ }
+ return paths
+}
+
+// anyTags is a special tags map that satisfies nearly all build tag expressions.
+// Only "ignore" and malformed build tag requirements are considered false.
+var anyTags = map[string]bool{"*": true}
+
+// TargetPackages returns the list of packages in the target (top-level) module,
+// under all build tag settings.
+func TargetPackages() []string {
+ return matchPackages("...", anyTags, []module.Version{Target})
+}
+
+// BuildList returns the module build list,
+// typically constructed by a previous call to
+// LoadBuildList or ImportPaths.
+// The caller must not modify the returned list.
+func BuildList() []module.Version {
+ return buildList
+}
+
+// SetBuildList sets the module build list.
+// The caller is responsible for ensuring that the list is valid.
+// SetBuildList does not retain a reference to the original list.
+func SetBuildList(list []module.Version) {
+ buildList = append([]module.Version{}, list...)
+}
+
+// ImportMap returns the actual package import path
+// for an import path found in source code.
+// If the given import path does not appear in the source code
+// for the packages that have been loaded, ImportMap returns the empty string.
+func ImportMap(path string) string {
+ pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ if !ok {
+ return ""
+ }
+ return pkg.path
+}
+
+// PackageDir returns the directory containing the source code
+// for the package named by the import path.
+func PackageDir(path string) string {
+ pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ if !ok {
+ return ""
+ }
+ return pkg.dir
+}
+
+// PackageModule returns the module providing the package named by the import path.
+func PackageModule(path string) module.Version {
+ pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ if !ok {
+ return module.Version{}
+ }
+ return pkg.mod
+}
+
+// ModuleUsedDirectly reports whether the main module directly imports
+// some package in the module with the given path.
+func ModuleUsedDirectly(path string) bool {
+ return loaded.direct[path]
+}
+
+// Lookup XXX TODO.
+func Lookup(parentPath, path string) (dir, realPath string, err error) {
+ realPath = ImportMap(path)
+ if realPath == "" {
+ if isStandardImportPath(path) {
+ dir := filepath.Join(cfg.GOROOT, "src", path)
+ if _, err := os.Stat(dir); err == nil {
+ return dir, path, nil
+ }
+ }
+ return "", "", fmt.Errorf("no such package in module")
+ }
+ return PackageDir(realPath), realPath, nil
+}
+
+// A loader manages the process of loading information about
+// the required packages for a particular build,
+// checking that the packages are available in the module set,
+// and updating the module set if needed.
+// Loading is an iterative process: try to load all the needed packages,
+// but if imports are missing, try to resolve those imports, and repeat.
+//
+// Although most of the loading state is maintained in the loader struct,
+// one key piece - the build list - is a global, so that it can be modified
+// separate from the loading operation, such as during "go get"
+// upgrades/downgrades or in "go mod" operations.
+// TODO(rsc): It might be nice to make the loader take and return
+// a buildList rather than hard-coding use of the global.
+type loader struct {
+ tags map[string]bool // tags for scanDir
+ testRoots bool // include tests for roots
+ isALL bool // created with LoadALL
+ testAll bool // include tests for all packages
+
+ // reset on each iteration
+ roots []*loadPkg
+ pkgs []*loadPkg
+ work *par.Work // current work queue
+ pkgCache *par.Cache // map from string to *loadPkg
+
+ // computed at end of iterations
+ direct map[string]bool // imported directly by main module
+}
+
+func newLoader() *loader {
+ ld := new(loader)
+ ld.tags = imports.Tags()
+
+ switch cfg.CmdName {
+ case "test", "vet":
+ ld.testRoots = true
+ }
+ return ld
+}
+
+func (ld *loader) reset() {
+ ld.roots = nil
+ ld.pkgs = nil
+ ld.work = new(par.Work)
+ ld.pkgCache = new(par.Cache)
+}
+
+// A loadPkg records information about a single loaded package.
+type loadPkg struct {
+ path string // import path
+ mod module.Version // module providing package
+ dir string // directory containing source code
+ imports []*loadPkg // packages imported by this one
+ err error // error loading package
+ stack *loadPkg // package importing this one in minimal import stack for this pkg
+ test *loadPkg // package with test imports, if we need test
+ testOf *loadPkg
+ testImports []string // test-only imports, saved for use by pkg.test.
+}
+
+var errMissing = errors.New("cannot find package")
+
+// load attempts to load the build graph needed to process a set of root packages.
+// The set of root packages is defined by the addRoots function,
+// which must call add(path) with the import path of each root package.
+func (ld *loader) load(roots func() []string) {
+ var err error
+ buildList, err = mvs.BuildList(Target, Reqs())
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
+ added := make(map[string]bool)
+ for {
+ ld.reset()
+ if roots != nil {
+ // Note: the returned roots can change on each iteration,
+ // since the expansion of package patterns depends on the
+ // build list we're using.
+ for _, path := range roots() {
+ ld.work.Add(ld.pkg(path, true))
+ }
+ }
+ ld.work.Do(10, ld.doPkg)
+ ld.buildStacks()
+ numAdded := 0
+ haveMod := make(map[module.Version]bool)
+ for _, m := range buildList {
+ haveMod[m] = true
+ }
+ for _, pkg := range ld.pkgs {
+ if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" {
+ if added[pkg.path] {
+ base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
+ }
+ added[pkg.path] = true
+ numAdded++
+ if !haveMod[err.Module] {
+ haveMod[err.Module] = true
+ buildList = append(buildList, err.Module)
+ }
+ continue
+ }
+ if pkg.err != nil {
+ base.Errorf("go: %s: %s", pkg.stackText(), pkg.err)
+ }
+ }
+ base.ExitIfErrors()
+ if numAdded == 0 {
+ break
+ }
+
+ // Recompute buildList with all our additions.
+ buildList, err = mvs.BuildList(Target, Reqs())
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ }
+ base.ExitIfErrors()
+
+ // Compute directly referenced dependency modules.
+ ld.direct = make(map[string]bool)
+ for _, pkg := range ld.pkgs {
+ if pkg.mod == Target {
+ for _, dep := range pkg.imports {
+ if dep.mod.Path != "" {
+ ld.direct[dep.mod.Path] = true
+ }
+ }
+ }
+ }
+
+ // Mix in direct markings (really, lack of indirect markings)
+ // from go.mod, unless we scanned the whole module
+ // and can therefore be sure we know better than go.mod.
+ if !ld.isALL && modFile != nil {
+ for _, r := range modFile.Require {
+ if !r.Indirect {
+ ld.direct[r.Mod.Path] = true
+ }
+ }
+ }
+
+ // Check for visibility violations.
+ // TODO!
+}
+
+// pkg returns the *loadPkg for path, creating and queuing it if needed.
+// If the package should be tested, its test is created but not queued
+// (the test is queued after processing pkg).
+// If isRoot is true, the pkg is being queued as one of the roots of the work graph.
+func (ld *loader) pkg(path string, isRoot bool) *loadPkg {
+ return ld.pkgCache.Do(path, func() interface{} {
+ pkg := &loadPkg{
+ path: path,
+ }
+ if ld.testRoots && isRoot || ld.testAll {
+ test := &loadPkg{
+ path: path,
+ testOf: pkg,
+ }
+ pkg.test = test
+ }
+ if isRoot {
+ ld.roots = append(ld.roots, pkg)
+ }
+ ld.work.Add(pkg)
+ return pkg
+ }).(*loadPkg)
+}
+
+// doPkg processes a package on the work queue.
+func (ld *loader) doPkg(item interface{}) {
+ // TODO: what about replacements?
+ pkg := item.(*loadPkg)
+ var imports []string
+ if pkg.testOf != nil {
+ pkg.dir = pkg.testOf.dir
+ pkg.mod = pkg.testOf.mod
+ imports = pkg.testOf.testImports
+ } else {
+ pkg.mod, pkg.dir, pkg.err = ld.doImport(pkg.path)
+ if pkg.dir == "" {
+ return
+ }
+ var testImports []string
+ var err error
+ imports, testImports, err = scanDir(pkg.dir, ld.tags)
+ if err != nil {
+ if strings.HasPrefix(err.Error(), "no Go ") {
+ // Don't print about directories with no Go source files.
+ // Let the eventual real package load do that.
+ return
+ }
+ pkg.err = err
+ return
+ }
+ if pkg.test != nil {
+ pkg.testImports = testImports
+ }
+ }
+
+ for _, path := range imports {
+ pkg.imports = append(pkg.imports, ld.pkg(path, false))
+ }
+
+ // Now that pkg.dir, pkg.mod, pkg.testImports are set, we can queue pkg.test.
+ // TODO: All that's left is creating new imports. Why not just do it now?
+ if pkg.test != nil {
+ ld.work.Add(pkg.test)
+ }
+}
+
+// doImport finds the directory holding source code for the given import path.
+// It returns the module containing the package (if any),
+// the directory containing the package (if any),
+// and any error encountered.
+// Not all packages have modules: the ones in the standard library do not.
+// Not all packages have directories: "unsafe" and "C" do not.
+func (ld *loader) doImport(path string) (mod module.Version, dir string, err error) {
+ if strings.Contains(path, "@") {
+ // Leave for error during load.
+ return module.Version{}, "", nil
+ }
+ if build.IsLocalImport(path) {
+ // Leave for error during load.
+ // (Module mode does not allow local imports.)
+ return module.Version{}, "", nil
+ }
+
+ return Import(path)
+}
+
+// scanDir is like imports.ScanDir but elides known magic imports from the list,
+// so that we do not go looking for packages that don't really exist.
+//
+// The standard magic import is "C", for cgo.
+//
+// The only other known magic imports are appengine and appengine/*.
+// These are so old that they predate "go get" and did not use URL-like paths.
+// Most code today now uses google.golang.org/appengine instead,
+// but not all code has been so updated. When we mostly ignore build tags
+// during "go vendor", we look into "// +build appengine" files and
+// may see these legacy imports. We drop them so that the module
+// search does not look for modules to try to satisfy them.
+func scanDir(dir string, tags map[string]bool) (imports_, testImports []string, err error) {
+ imports_, testImports, err = imports.ScanDir(dir, tags)
+
+ filter := func(x []string) []string {
+ w := 0
+ for _, pkg := range x {
+ if pkg != "C" && pkg != "appengine" && !strings.HasPrefix(pkg, "appengine/") &&
+ pkg != "appengine_internal" && !strings.HasPrefix(pkg, "appengine_internal/") {
+ x[w] = pkg
+ w++
+ }
+ }
+ return x[:w]
+ }
+
+ return filter(imports_), filter(testImports), err
+}
+
+// buildStacks computes minimal import stacks for each package,
+// for use in error messages. When it completes, packages that
+// are part of the original root set have pkg.stack == nil,
+// and other packages have pkg.stack pointing at the next
+// package up the import stack in their minimal chain.
+// As a side effect, buildStacks also constructs ld.pkgs,
+// the list of all packages loaded.
+func (ld *loader) buildStacks() {
+ if len(ld.pkgs) > 0 {
+ panic("buildStacks")
+ }
+ for _, pkg := range ld.roots {
+ pkg.stack = pkg // sentinel to avoid processing in next loop
+ ld.pkgs = append(ld.pkgs, pkg)
+ }
+ for i := 0; i < len(ld.pkgs); i++ { // not range: appending to ld.pkgs in loop
+ pkg := ld.pkgs[i]
+ for _, next := range pkg.imports {
+ if next.stack == nil {
+ next.stack = pkg
+ ld.pkgs = append(ld.pkgs, next)
+ }
+ }
+ if next := pkg.test; next != nil && next.stack == nil {
+ next.stack = pkg
+ ld.pkgs = append(ld.pkgs, next)
+ }
+ }
+ for _, pkg := range ld.roots {
+ pkg.stack = nil
+ }
+}
+
+// stackText builds the import stack text to use when
+// reporting an error in pkg. It has the general form
+//
+// import root ->
+// import other ->
+// import other2 ->
+// import pkg
+//
+func (pkg *loadPkg) stackText() string {
+ var stack []*loadPkg
+ for p := pkg.stack; p != nil; p = p.stack {
+ stack = append(stack, p)
+ }
+
+ var buf bytes.Buffer
+ for i := len(stack) - 1; i >= 0; i-- {
+ p := stack[i]
+ if p.testOf != nil {
+ fmt.Fprintf(&buf, "test ->\n\t")
+ } else {
+ fmt.Fprintf(&buf, "import %q ->\n\t", p.path)
+ }
+ }
+ fmt.Fprintf(&buf, "import %q", pkg.path)
+ return buf.String()
+}
+
+// Replacement returns the replacement for mod, if any, from go.mod.
+// If there is no replacement for mod, Replacement returns
+// a module.Version with Path == "".
+func Replacement(mod module.Version) module.Version {
+ if modFile == nil {
+ // Happens during testing.
+ return module.Version{}
+ }
+ var found *modfile.Replace
+ for _, r := range modFile.Replace {
+ if r.Old.Path == mod.Path && (r.Old.Version == "" || r.Old.Version == mod.Version) {
+ found = r // keep going
+ }
+ }
+ if found == nil {
+ return module.Version{}
+ }
+ return found.New
+}
+
+// mvsReqs implements mvs.Reqs for module semantic versions,
+// with any exclusions or replacements applied internally.
+type mvsReqs struct {
+ buildList []module.Version
+ cache par.Cache
+}
+
+// Reqs returns the current module requirement graph.
+// Future calls to SetBuildList do not affect the operation
+// of the returned Reqs.
+func Reqs() mvs.Reqs {
+ r := &mvsReqs{
+ buildList: buildList,
+ }
+ return r
+}
+
+func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
+ type cached struct {
+ list []module.Version
+ err error
+ }
+
+ c := r.cache.Do(mod, func() interface{} {
+ list, err := r.required(mod)
+ if err != nil {
+ return cached{nil, err}
+ }
+ for i, mv := range list {
+ for excluded[mv] {
+ mv1, err := r.next(mv)
+ if err != nil {
+ return cached{nil, err}
+ }
+ if mv1.Version == "none" {
+ return cached{nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version)}
+ }
+ mv = mv1
+ }
+ list[i] = mv
+ }
+
+ return cached{list, nil}
+ }).(cached)
+
+ return c.list, c.err
+}
+
+var vendorOnce sync.Once
+
+var (
+ vendorList []module.Version
+ vendorMap map[string]module.Version
+)
+
+// readVendorList reads the list of vendored modules from vendor/modules.txt.
+func readVendorList() {
+ vendorOnce.Do(func() {
+ vendorList = nil
+ vendorMap = make(map[string]module.Version)
+ data, _ := ioutil.ReadFile(filepath.Join(ModRoot, "vendor/modules.txt"))
+ var m module.Version
+ for _, line := range strings.Split(string(data), "\n") {
+ if strings.HasPrefix(line, "# ") {
+ f := strings.Fields(line)
+ m = module.Version{}
+ if len(f) == 3 && semver.IsValid(f[2]) {
+ m = module.Version{Path: f[1], Version: f[2]}
+ vendorList = append(vendorList, m)
+ }
+ } else if m.Path != "" {
+ f := strings.Fields(line)
+ if len(f) == 1 {
+ vendorMap[f[0]] = m
+ }
+ }
+ }
+ })
+}
+
+func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
+ if mod == Target {
+ var list []module.Version
+ list = append(list, r.buildList[1:]...)
+ return list, nil
+ }
+
+ if cfg.BuildGetmode == "vendor" {
+ // For every module other than the target,
+ // return the full list of modules from modules.txt.
+ readVendorList()
+ return vendorList, nil
+ }
+
+ origPath := mod.Path
+ if repl := Replacement(mod); repl.Path != "" {
+ if repl.Version == "" {
+ // TODO: need to slip the new version into the tags list etc.
+ dir := repl.Path
+ if !filepath.IsAbs(dir) {
+ dir = filepath.Join(ModRoot, dir)
+ }
+ gomod := filepath.Join(dir, "go.mod")
+ data, err := ioutil.ReadFile(gomod)
+ if err != nil {
+ base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
+ return nil, ErrRequire
+ }
+ f, err := modfile.Parse(gomod, data, nil)
+ if err != nil {
+ base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
+ return nil, ErrRequire
+ }
+ var list []module.Version
+ for _, r := range f.Require {
+ list = append(list, r.Mod)
+ }
+ return list, nil
+ }
+ mod = repl
+ }
+
+ if mod.Version == "none" {
+ return nil, nil
+ }
+
+ if !semver.IsValid(mod.Version) {
+ // Disallow the broader queries supported by fetch.Lookup.
+ base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", mod.Path, mod.Version)
+ }
+
+ data, err := modfetch.GoMod(mod.Path, mod.Version)
+ if err != nil {
+ base.Errorf("go: %s@%s: %v\n", mod.Path, mod.Version, err)
+ return nil, ErrRequire
+ }
+ f, err := modfile.Parse("go.mod", data, nil)
+ if err != nil {
+ base.Errorf("go: %s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
+ return nil, ErrRequire
+ }
+
+ if f.Module == nil {
+ base.Errorf("go: %s@%s: parsing go.mod: missing module line", mod.Path, mod.Version)
+ return nil, ErrRequire
+ }
+ if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
+ base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
+ return nil, ErrRequire
+ }
+
+ var list []module.Version
+ for _, req := range f.Require {
+ list = append(list, req.Mod)
+ }
+ return list, nil
+}
+
+// ErrRequire is the sentinel error returned when Require encounters problems.
+// It prints the problems directly to standard error, so that multiple errors
+// can be displayed easily.
+var ErrRequire = errors.New("error loading module requirements")
+
+func (*mvsReqs) Max(v1, v2 string) string {
+ if v1 != "" && semver.Compare(v1, v2) == -1 {
+ return v2
+ }
+ return v1
+}
+
+// Upgrade is a no-op, here to implement mvs.Reqs.
+// The upgrade logic for go get -u is in ../modget/get.go.
+func (*mvsReqs) Upgrade(m module.Version) (module.Version, error) {
+ return m, nil
+}
+
+func versions(path string) ([]string, error) {
+ // Note: modfetch.Lookup and repo.Versions are cached,
+ // so there's no need for us to add extra caching here.
+ repo, err := modfetch.Lookup(path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.Versions("")
+}
+
+// Previous returns the tagged version of m.Path immediately prior to
+// m.Version, or version "none" if no prior version is tagged.
+func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
+ list, err := versions(m.Path)
+ if err != nil {
+ return module.Version{}, err
+ }
+ i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 })
+ if i > 0 {
+ return module.Version{Path: m.Path, Version: list[i-1]}, nil
+ }
+ return module.Version{Path: m.Path, Version: "none"}, nil
+}
+
+// next returns the next version of m.Path after m.Version.
+// It is only used by the exclusion processing in the Required method,
+// not called directly by MVS.
+func (*mvsReqs) next(m module.Version) (module.Version, error) {
+ list, err := versions(m.Path)
+ if err != nil {
+ return module.Version{}, err
+ }
+ i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 })
+ if i < len(list) {
+ return module.Version{Path: m.Path, Version: list[i]}, nil
+ }
+ return module.Version{Path: m.Path, Version: "none"}, nil
+}
+
+func fetch(mod module.Version) (dir string, isLocal bool, err error) {
+ if mod == Target {
+ return ModRoot, true, nil
+ }
+ if r := Replacement(mod); r.Path != "" {
+ if r.Version == "" {
+ dir = r.Path
+ if !filepath.IsAbs(dir) {
+ dir = filepath.Join(ModRoot, dir)
+ }
+ return dir, true, nil
+ }
+ mod = r
+ }
+
+ dir, err = modfetch.Download(mod)
+ return dir, false, err
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/module"
+ "cmd/go/internal/semver"
+ "fmt"
+ "strings"
+)
+
+// Query looks up a revision of a given module given a version query string.
+// The module must be a complete module path.
+// The version must take one of the following forms:
+//
+// - the literal string "latest", denoting the latest available, allowed tagged version,
+// with non-prereleases preferred over prereleases.
+// If there are no tagged versions in the repo, latest returns the most recent commit.
+// - v1, denoting the latest available tagged version v1.x.x.
+// - v1.2, denoting the latest available tagged version v1.2.x.
+// - v1.2.3, a semantic version string denoting that tagged version.
+// - <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3,
+// denoting the version closest to the target and satisfying the given operator,
+// with non-prereleases preferred over prereleases.
+// - a repository commit identifier, denoting that commit.
+//
+// If the allowed function is non-nil, Query excludes any versions for which allowed returns false.
+//
+func Query(path, query string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
+ if allowed == nil {
+ allowed = func(module.Version) bool { return true }
+ }
+
+ // Parse query to detect parse errors (and possibly handle query)
+ // before any network I/O.
+ badVersion := func(v string) (*modfetch.RevInfo, error) {
+ return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query)
+ }
+ var ok func(module.Version) bool
+ var preferOlder bool
+ switch {
+ case query == "latest":
+ ok = allowed
+
+ case strings.HasPrefix(query, "<="):
+ v := query[len("<="):]
+ if !semver.IsValid(v) {
+ return badVersion(v)
+ }
+ if isSemverPrefix(v) {
+ // Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
+ return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
+ }
+ ok = func(m module.Version) bool {
+ return semver.Compare(m.Version, v) <= 0 && allowed(m)
+ }
+
+ case strings.HasPrefix(query, "<"):
+ v := query[len("<"):]
+ if !semver.IsValid(v) {
+ return badVersion(v)
+ }
+ ok = func(m module.Version) bool {
+ return semver.Compare(m.Version, v) < 0 && allowed(m)
+ }
+
+ case strings.HasPrefix(query, ">="):
+ v := query[len(">="):]
+ if !semver.IsValid(v) {
+ return badVersion(v)
+ }
+ ok = func(m module.Version) bool {
+ return semver.Compare(m.Version, v) >= 0 && allowed(m)
+ }
+ preferOlder = true
+
+ case strings.HasPrefix(query, ">"):
+ v := query[len(">"):]
+ if !semver.IsValid(v) {
+ return badVersion(v)
+ }
+ if isSemverPrefix(v) {
+ // Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
+ return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
+ }
+ ok = func(m module.Version) bool {
+ return semver.Compare(m.Version, v) > 0 && allowed(m)
+ }
+ preferOlder = true
+
+ case semver.IsValid(query) && isSemverPrefix(query):
+ ok = func(m module.Version) bool {
+ return matchSemverPrefix(query, m.Version) && allowed(m)
+ }
+
+ case semver.IsValid(query):
+ vers := semver.Canonical(query)
+ if !allowed(module.Version{Path: path, Version: vers}) {
+ return nil, fmt.Errorf("%s@%s excluded", path, vers)
+ }
+ return modfetch.Stat(path, vers)
+
+ default:
+ // Direct lookup of semantic version or commit identifier.
+ info, err := modfetch.Stat(path, query)
+ if err != nil {
+ return nil, err
+ }
+ if !allowed(module.Version{Path: path, Version: info.Version}) {
+ return nil, fmt.Errorf("%s@%s excluded", path, info.Version)
+ }
+ return info, nil
+ }
+
+ // Load versions and execute query.
+ repo, err := modfetch.Lookup(path)
+ if err != nil {
+ return nil, err
+ }
+ versions, err := repo.Versions("")
+ if err != nil {
+ return nil, err
+ }
+
+ if preferOlder {
+ for _, v := range versions {
+ if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
+ return repo.Stat(v)
+ }
+ }
+ for _, v := range versions {
+ if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
+ return repo.Stat(v)
+ }
+ }
+ } else {
+ for i := len(versions) - 1; i >= 0; i-- {
+ v := versions[i]
+ if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
+ return repo.Stat(v)
+ }
+ }
+ for i := len(versions) - 1; i >= 0; i-- {
+ v := versions[i]
+ if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
+ return repo.Stat(v)
+ }
+ }
+ }
+
+ if query == "latest" {
+ // Special case for "latest": if no tags match, use latest commit in repo,
+ // provided it is not excluded.
+ if info, err := repo.Latest(); err == nil && allowed(module.Version{Path: path, Version: info.Version}) {
+ return info, nil
+ }
+ }
+
+ return nil, fmt.Errorf("no matching versions for query %q", query)
+}
+
+// isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3).
+// The caller is assumed to have checked that semver.IsValid(v) is true.
+func isSemverPrefix(v string) bool {
+ dots := 0
+ for i := 0; i < len(v); i++ {
+ switch v[i] {
+ case '-', '+':
+ return false
+ case '.':
+ dots++
+ if dots >= 2 {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// matchSemverPrefix reports whether the shortened semantic version p
+// matches the full-width (non-shortened) semantic version v.
+func matchSemverPrefix(p, v string) bool {
+ return len(v) > len(p) && v[len(p)] == '.' && v[:len(p)] == p
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "internal/testenv"
+ "io/ioutil"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modfetch/codehost"
+ "cmd/go/internal/module"
+)
+
+func TestMain(m *testing.M) {
+ os.Exit(testMain(m))
+}
+
+func testMain(m *testing.M) int {
+ dir, err := ioutil.TempDir("", "modload-test-")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ modfetch.SrcMod = filepath.Join(dir, "src/mod")
+ codehost.WorkRoot = filepath.Join(dir, "codework")
+ return m.Run()
+}
+
+var (
+ queryRepo = "vcs-test.golang.org/git/querytest.git"
+ queryRepoV2 = queryRepo + "/v2"
+ queryRepoV3 = queryRepo + "/v3"
+
+ // Empty version list (no semver tags), not actually empty.
+ emptyRepo = "vcs-test.golang.org/git/emptytest.git"
+)
+
+var queryTests = []struct {
+ path string
+ query string
+ allow string
+ vers string
+ err string
+}{
+ /*
+ git init
+ echo module vcs-test.golang.org/git/querytest.git >go.mod
+ git add go.mod
+ git commit -m v1 go.mod
+ git tag start
+ for i in v0.0.0-pre1 v0.0.0 v0.0.1 v0.0.2 v0.0.3 v0.1.0 v0.1.1 v0.1.2 v0.3.0 v1.0.0 v1.1.0 v1.9.0 v1.9.9 v1.9.10-pre1; do
+ echo before $i >status
+ git add status
+ git commit -m "before $i" status
+ echo at $i >status
+ git commit -m "at $i" status
+ git tag $i
+ done
+ git tag favorite v0.0.3
+
+ git branch v2 start
+ git checkout v2
+ echo module vcs-test.golang.org/git/querytest.git/v2 >go.mod
+ git commit -m v2 go.mod
+ for i in v2.0.0 v2.1.0 v2.2.0 v2.5.5; do
+ echo before $i >status
+ git add status
+ git commit -m "before $i" status
+ echo at $i >status
+ git commit -m "at $i" status
+ git tag $i
+ done
+ echo after v2.5.5 >status
+ git commit -m 'after v2.5.5' status
+ git checkout master
+ zip -r ../querytest.zip
+ gsutil cp ../querytest.zip gs://vcs-test/git/querytest.zip
+ curl 'https://vcs-test.golang.org/git/querytest?go-get=1'
+ */
+ {path: queryRepo, query: "<v0.0.0", vers: "v0.0.0-pre1"},
+ {path: queryRepo, query: "<v0.0.0-pre1", err: `no matching versions for query "<v0.0.0-pre1"`},
+ {path: queryRepo, query: "<=v0.0.0", vers: "v0.0.0"},
+ {path: queryRepo, query: ">v0.0.0", vers: "v0.0.1"},
+ {path: queryRepo, query: ">=v0.0.0", vers: "v0.0.0"},
+ {path: queryRepo, query: "v0.0.1", vers: "v0.0.1"},
+ {path: queryRepo, query: "v0.0.1+foo", vers: "v0.0.1"},
+ {path: queryRepo, query: "v0.0.99", err: `unknown revision v0.0.99`},
+ {path: queryRepo, query: "v0", vers: "v0.3.0"},
+ {path: queryRepo, query: "v0.1", vers: "v0.1.2"},
+ {path: queryRepo, query: "v0.2", err: `no matching versions for query "v0.2"`},
+ {path: queryRepo, query: "v0.0", vers: "v0.0.3"},
+ {path: queryRepo, query: "latest", vers: "v1.9.9"},
+ {path: queryRepo, query: "latest", allow: "NOMATCH", err: `no matching versions for query "latest"`},
+ {path: queryRepo, query: ">v1.9.9", vers: "v1.9.10-pre1"},
+ {path: queryRepo, query: ">v1.10.0", err: `no matching versions for query ">v1.10.0"`},
+ {path: queryRepo, query: ">=v1.10.0", err: `no matching versions for query ">=v1.10.0"`},
+ {path: queryRepo, query: "6cf84eb", vers: "v0.0.0-20180704023347-6cf84ebaea54"},
+ {path: queryRepo, query: "start", vers: "v0.0.0-20180704023101-5e9e31667ddf"},
+ {path: queryRepo, query: "7a1b6bf", vers: "v0.1.0"},
+
+ {path: queryRepoV2, query: "<v0.0.0", err: `no matching versions for query "<v0.0.0"`},
+ {path: queryRepoV2, query: "<=v0.0.0", err: `no matching versions for query "<=v0.0.0"`},
+ {path: queryRepoV2, query: ">v0.0.0", vers: "v2.0.0"},
+ {path: queryRepoV2, query: ">=v0.0.0", vers: "v2.0.0"},
+ {path: queryRepoV2, query: "v0.0.1+foo", vers: "v2.0.0-20180704023347-179bc86b1be3"},
+ {path: queryRepoV2, query: "latest", vers: "v2.5.5"},
+
+ {path: queryRepoV3, query: "latest", vers: "v3.0.0-20180704024501-e0cf3de987e6"},
+
+ {path: emptyRepo, query: "latest", vers: "v0.0.0-20180704023549-7bb914627242"},
+ {path: emptyRepo, query: ">v0.0.0", err: `no matching versions for query ">v0.0.0"`},
+ {path: emptyRepo, query: "<v10.0.0", err: `no matching versions for query "<v10.0.0"`},
+}
+
+func TestQuery(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
+ for _, tt := range queryTests {
+ allow := tt.allow
+ if allow == "" {
+ allow = "*"
+ }
+ allowed := func(m module.Version) bool {
+ ok, _ := path.Match(allow, m.Version)
+ return ok
+ }
+ t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.query+"/"+allow, func(t *testing.T) {
+ info, err := Query(tt.path, tt.query, allowed)
+ if tt.err != "" {
+ if err != nil && err.Error() == tt.err {
+ return
+ }
+ t.Fatalf("Query(%q, %q, %v): %v, want error %q", tt.path, tt.query, allow, err, tt.err)
+ }
+ if err != nil {
+ t.Fatalf("Query(%q, %q, %v): %v", tt.path, tt.query, allow, err)
+ }
+ if info.Version != tt.vers {
+ t.Errorf("Query(%q, %q, %v) = %v, want %v", tt.path, tt.query, allow, info.Version, tt.vers)
+ }
+ })
+ }
+}
--- /dev/null
+// Copyright 2018 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 modload
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/imports"
+ "cmd/go/internal/module"
+ "cmd/go/internal/search"
+)
+
+// matchPackages returns a list of packages in the list of modules
+// matching the pattern. Package loading assumes the given set of tags.
+func matchPackages(pattern string, tags map[string]bool, modules []module.Version) []string {
+ match := func(string) bool { return true }
+ treeCanMatch := func(string) bool { return true }
+ if !search.IsMetaPackage(pattern) {
+ match = search.MatchPattern(pattern)
+ treeCanMatch = search.TreeCanMatchPattern(pattern)
+ }
+
+ have := map[string]bool{
+ "builtin": true, // ignore pseudo-package that exists only for documentation
+ }
+ if !cfg.BuildContext.CgoEnabled {
+ have["runtime/cgo"] = true // ignore during walk
+ }
+ var pkgs []string
+
+ for _, mod := range modules {
+ if !treeCanMatch(mod.Path) {
+ continue
+ }
+ var root string
+ if mod.Version == "" {
+ root = ModRoot
+ } else {
+ var err error
+ root, _, err = fetch(mod)
+ if err != nil {
+ base.Errorf("go: %v", err)
+ continue
+ }
+ }
+ root = filepath.Clean(root)
+
+ filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+
+ want := true
+ // Avoid .foo, _foo, and testdata directory trees.
+ _, elem := filepath.Split(path)
+ if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
+ want = false
+ }
+
+ name := mod.Path + filepath.ToSlash(path[len(root):])
+ if !treeCanMatch(name) {
+ want = false
+ }
+
+ if !fi.IsDir() {
+ if fi.Mode()&os.ModeSymlink != 0 && want {
+ if target, err := os.Stat(path); err == nil && target.IsDir() {
+ fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
+ }
+ }
+ return nil
+ }
+
+ if !want {
+ return filepath.SkipDir
+ }
+ if path != root {
+ if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil {
+ return filepath.SkipDir
+ }
+ }
+
+ if !have[name] {
+ have[name] = true
+ if match(name) {
+ if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
+ pkgs = append(pkgs, name)
+ }
+ }
+ }
+
+ if elem == "vendor" {
+ return filepath.SkipDir
+ }
+ return nil
+ })
+ }
+ return pkgs
+}
import (
"fmt"
+ "sort"
"strings"
"unicode"
"unicode/utf8"
// A Version is defined by a module path and version pair.
type Version struct {
- Path string
- Version string
+ Path string
+
+ // Version is usually a semantic version in canonical form.
+ // There are two exceptions to this general rule.
+ // First, the top-level target of a build has no specific version
+ // and uses Version = "".
+ // Second, during MVS calculations the version "none" is used
+ // to represent the decision to take no version of a given module.
+ Version string `json:",omitempty"`
}
// Check checks that a given module path, version pair is valid.
}
return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:]
}
+
+// Sort sorts the list by Path, breaking ties by comparing Versions.
+func Sort(list []Version) {
+ sort.Slice(list, func(i, j int) bool {
+ mi := list[i]
+ mj := list[j]
+ if mi.Path != mj.Path {
+ return mi.Path < mj.Path
+ }
+ // To help go.sum formatting, allow version/file.
+ // Compare semver prefix by semver rules,
+ // file by string order.
+ vi := mi.Version
+ vj := mj.Version
+ var fi, fj string
+ if k := strings.Index(vi, "/"); k >= 0 {
+ vi, fi = vi[:k], vi[k:]
+ }
+ if k := strings.Index(vj, "/"); k >= 0 {
+ vj, fj = vj[:k], vj[k:]
+ }
+ if vi != vj {
+ return semver.Compare(vi, vj) < 0
+ }
+ return fi < fj
+ })
+}
import (
"fmt"
"sort"
+ "sync"
+ "cmd/go/internal/base"
"cmd/go/internal/module"
+ "cmd/go/internal/par"
)
+// A Reqs is the requirement graph on which Minimal Version Selection (MVS) operates.
+//
+// The version strings are opaque except for the special version "none"
+// (see the documentation for module.Version). In particular, MVS does not
+// assume that the version strings are semantic versions; instead, the Max method
+// gives access to the comparison operation.
+//
+// It must be safe to call methods on a Reqs from multiple goroutines simultaneously.
+// Because a Reqs may read the underlying graph from the network on demand,
+// the MVS algorithms parallelize the traversal to overlap network delays.
type Reqs interface {
+ // Required returns the module versions explicitly required by m itself.
+ // The caller must not modify the returned list.
Required(m module.Version) ([]module.Version, error)
+
+ // Max returns the maximum of v1 and v2 (it returns either v1 or v2).
+ //
+ // For all versions v, Max(v, "none") must be v,
+ // and for the tanget passed as the first argument to MVS functions,
+ // Max(target, v) must be target.
+ //
+ // Note that v1 < v2 can be written Max(v1, v2) != v1
+ // and similarly v1 <= v2 can be written Max(v1, v2) == v2.
Max(v1, v2 string) string
- Latest(path string) (module.Version, error)
+
+ // Upgrade returns the upgraded version of m,
+ // for use during an UpgradeAll operation.
+ // If m should be kept as is, Upgrade returns m.
+ // If m is not yet used in the build, then m.Version will be "none".
+ // More typically, m.Version will be the version required
+ // by some other module in the build.
+ //
+ // If no module version is available for the given path,
+ // Upgrade returns a non-nil error.
+ // TODO(rsc): Upgrade must be able to return errors,
+ // but should "no latest version" just return m instead?
+ Upgrade(m module.Version) (module.Version, error)
+
+ // Previous returns the version of m.Path immediately prior to m.Version,
+ // or "none" if no such version is known.
Previous(m module.Version) (module.Version, error)
}
// BuildList returns the build list for the target module.
func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) {
- return buildList(target, reqs, nil, nil)
+ return buildList(target, reqs, nil)
}
-func buildList(target module.Version, reqs Reqs, uses map[module.Version][]module.Version, vers map[string]string) ([]module.Version, error) {
+func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) module.Version) ([]module.Version, error) {
+ // Explore work graph in parallel in case reqs.Required
+ // does high-latency network operations.
+ var work par.Work
+ work.Add(target)
var (
- min = map[string]string{target.Path: target.Version}
- todo = []module.Version{target}
- seen = map[module.Version]bool{target: true}
+ mu sync.Mutex
+ min = map[string]string{target.Path: target.Version}
+ firstErr error
)
- for len(todo) > 0 {
- m := todo[len(todo)-1]
- todo = todo[:len(todo)-1]
- required, _ := reqs.Required(m)
+ work.Do(10, func(item interface{}) {
+ m := item.(module.Version)
+ required, err := reqs.Required(m)
+
+ mu.Lock()
+ if err != nil && firstErr == nil {
+ firstErr = err
+ }
+ if firstErr != nil {
+ mu.Unlock()
+ return
+ }
+ if v, ok := min[m.Path]; !ok || reqs.Max(v, m.Version) != v {
+ min[m.Path] = m.Version
+ }
+ mu.Unlock()
+
for _, r := range required {
- if uses != nil {
- uses[r] = append(uses[r], m)
+ if r.Path == "" {
+ base.Errorf("Required(%v) returned zero module in list", m)
+ continue
}
- if !seen[r] {
- if v, ok := min[r.Path]; !ok {
- min[r.Path] = r.Version
- } else if max := reqs.Max(v, r.Version); max != v {
- min[r.Path] = max
- }
- todo = append(todo, r)
- seen[r] = true
+ work.Add(r)
+ }
+
+ if upgrade != nil {
+ u := upgrade(m)
+ if u.Path == "" {
+ base.Errorf("Upgrade(%v) returned zero module", m)
+ return
}
+ work.Add(u)
}
- }
+ })
- if min[target.Path] != target.Version {
- panic("unbuildable") // TODO
+ if firstErr != nil {
+ return nil, firstErr
}
-
- if vers == nil {
- vers = make(map[string]string)
+ if v := min[target.Path]; v != target.Version {
+ panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic.
}
+
list := []module.Version{target}
+ listed := map[string]bool{target.Path: true}
for i := 0; i < len(list); i++ {
m := list[i]
required, err := reqs.Required(m)
if err != nil {
- // TODO: Check error is decent.
return nil, err
}
for _, r := range required {
v := min[r.Path]
- if reqs.Max(v, r.Version) != v {
- panic("mistake") // TODO
+ if r.Path != target.Path && reqs.Max(v, r.Version) != v {
+ panic(fmt.Sprintf("mistake: version %q does not satisfy requirement %+v", v, r)) // TODO: Don't panic.
}
- if _, ok := vers[r.Path]; !ok {
- vers[r.Path] = v
+ if !listed[r.Path] {
list = append(list, module.Version{Path: r.Path, Version: v})
+ listed[r.Path] = true
}
}
}
+
tail := list[1:]
sort.Slice(tail, func(i, j int) bool {
return tail[i].Path < tail[j].Path
}
// Req returns the minimal requirement list for the target module
-// that result in the given build list.
-func Req(target module.Version, list []module.Version, reqs Reqs) ([]module.Version, error) {
+// that results in the given build list, with the constraint that all
+// module paths listed in base must appear in the returned list.
+func Req(target module.Version, list []module.Version, base []string, reqs Reqs) ([]module.Version, error) {
+ // Note: Not running in parallel because we assume
+ // that list came from a previous operation that paged
+ // in all the requirements, so there's no I/O to overlap now.
+
// Compute postorder, cache requirements.
var postorder []module.Version
reqCache := map[module.Version][]module.Version{}
}
max := map[string]string{}
for _, m := range list {
- if max[m.Path] == "" {
- max[m.Path] = m.Version
+ if v, ok := max[m.Path]; ok {
+ max[m.Path] = reqs.Max(m.Version, v)
} else {
- max[m.Path] = reqs.Max(m.Version, max[m.Path])
+ max[m.Path] = m.Version
}
}
+ // First walk the base modules that must be listed.
var min []module.Version
+ for _, path := range base {
+ m := module.Version{Path: path, Version: max[path]}
+ min = append(min, m)
+ walk(m)
+ }
+ // Now the reverse postorder to bring in anything else.
for i := len(postorder) - 1; i >= 0; i-- {
m := postorder[i]
if max[m.Path] != m.Version {
// UpgradeAll returns a build list for the target module
// in which every module is upgraded to its latest version.
func UpgradeAll(target module.Version, reqs Reqs) ([]module.Version, error) {
- have := map[string]bool{target.Path: true}
- list := []module.Version{target}
- for i := 0; i < len(list); i++ {
- m := list[i]
- required, err := reqs.Required(m)
+ return buildList(target, reqs, func(m module.Version) module.Version {
+ if m.Path == target.Path {
+ return target
+ }
+
+ latest, err := reqs.Upgrade(m)
if err != nil {
panic(err) // TODO
}
- for _, r := range required {
- latest, err := reqs.Latest(r.Path)
- if err != nil {
- panic(err) // TODO
- }
- if reqs.Max(latest.Version, r.Version) != latest.Version {
- panic("mistake") // TODO
- }
- if !have[r.Path] {
- have[r.Path] = true
- list = append(list, module.Version{Path: r.Path, Version: latest.Version})
- }
- }
- }
- tail := list[1:]
- sort.Slice(tail, func(i, j int) bool {
- return tail[i].Path < tail[j].Path
+ m.Version = latest.Version
+ return m
})
- return list, nil
}
// Upgrade returns a build list for the target module
// Downgrade returns a build list for the target module
// in which the given additional modules are downgraded.
+//
+// The versions to be downgraded may be unreachable from reqs.Latest and
+// reqs.Previous, but the methods of reqs must otherwise handle such versions
+// correctly.
func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([]module.Version, error) {
list, err := reqs.Required(target)
if err != nil {
E1: D2
build A: A B C D2 E2
+# Upgrade from B1 to B2 should drop the transitive dep on D.
+name: drop
+A: B1 C1
+B1: D1
+B2:
+C2:
+D2:
+build A: A B1 C1 D1
+upgrade* A: A B2 C2
+
+name: simplify
+A: B1 C1
+B1: C2
+C1: D1
+C2:
+build A: A B1 C2
+
+name: up1
+A: B1 C1
+B1:
+B2:
+B3:
+B4:
+B5.hidden:
+C2:
+C3:
+build A: A B1 C1
+upgrade* A: A B4 C3
+
+name: up2
+A: B5.hidden C1
+B1:
+B2:
+B3:
+B4:
+B5.hidden:
+C2:
+C3:
+build A: A B5.hidden C1
+upgrade* A: A B5.hidden C3
+
name: down1
A: B2
B1: C1
B2.hidden:
C2:
downgrade A B2.hidden: A B2.hidden C2
+
+# Cycles involving the target.
+
+# The target must be the newest version of itself.
+name: cycle1
+A: B1
+B1: A1
+B2: A2
+B3: A3
+build A: A B1
+upgrade A B2: A B2
+upgrade* A: A B3
+
+# Requirements of older versions of the target
+# must not be carried over.
+name: cycle2
+A: B1
+A1: C1
+A2: D1
+B1: A1
+B2: A2
+C1: A2
+C2:
+D2:
+build A: A B1
+upgrade* A: A B2
+
+# Requirement minimization.
+
+name: req1
+A: B1 C1 D1 E1 F1
+B1: C1 E1 F1
+req A: B1 D1
+req A C: B1 C1 D1
+
+name: req2
+A: G1 H1
+G1: H1
+H1: G1
+req A: G1
+req A G: G1
+req A H: H1
`
func Test(t *testing.T) {
continue
case "upgradereq":
if len(kf) < 2 {
- t.Fatalf("upgrade takes at least one arguments: %q", line)
+ t.Fatalf("upgrade takes at least one argument: %q", line)
}
fns = append(fns, func(t *testing.T) {
list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...)
if err == nil {
- list, err = Req(m(kf[1]), list, reqs)
+ list, err = Req(m(kf[1]), list, nil, reqs)
}
checkList(t, key, list, err, val)
})
continue
case "upgrade":
if len(kf) < 2 {
- t.Fatalf("upgrade takes at least one arguments: %q", line)
+ t.Fatalf("upgrade takes at least one argument: %q", line)
}
fns = append(fns, func(t *testing.T) {
list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...)
continue
case "downgrade":
if len(kf) < 2 {
- t.Fatalf("downgrade takes at least one arguments: %q", line)
+ t.Fatalf("downgrade takes at least one argument: %q", line)
}
fns = append(fns, func(t *testing.T) {
list, err := Downgrade(m(kf[1]), reqs, ms(kf[1:])...)
checkList(t, key, list, err, val)
})
continue
+ case "req":
+ if len(kf) < 2 {
+ t.Fatalf("req takes at least one argument: %q", line)
+ }
+ fns = append(fns, func(t *testing.T) {
+ list, err := BuildList(m(kf[1]), reqs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ list, err = Req(m(kf[1]), list, kf[2:], reqs)
+ checkList(t, key, list, err, val)
+ })
+ continue
}
if len(kf) == 1 && 'A' <= key[0] && key[0] <= 'Z' {
var rs []module.Version
type reqsMap map[module.Version][]module.Version
func (r reqsMap) Max(v1, v2 string) string {
- if v1 == "none" {
+ if v1 == "none" || v2 == "" {
return v2
}
- if v2 == "none" {
+ if v2 == "none" || v1 == "" {
return v1
}
if v1 < v2 {
return v1
}
-func (r reqsMap) Latest(path string) (module.Version, error) {
- var m module.Version
+func (r reqsMap) Upgrade(m module.Version) (module.Version, error) {
+ var u module.Version
for k := range r {
- if k.Path == path && m.Version < k.Version {
- m = k
+ if k.Path == m.Path && u.Version < k.Version && !strings.HasSuffix(k.Version, ".hidden") {
+ u = k
}
}
- if m.Path == "" {
- return module.Version{}, &MissingModuleError{module.Version{Path: path, Version: ""}}
+ if u.Path == "" {
+ return module.Version{}, &MissingModuleError{module.Version{Path: m.Path, Version: ""}}
}
- return m, nil
+ return u, nil
}
func (r reqsMap) Previous(m module.Version) (module.Version, error) {
--- /dev/null
+// Copyright 2018 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 par implements parallel execution helpers.
+package par
+
+import (
+ "math/rand"
+ "sync"
+ "sync/atomic"
+)
+
+// Work manages a set of work items to be executed in parallel, at most once each.
+// The items in the set must all be valid map keys.
+type Work struct {
+ f func(interface{}) // function to run for each item
+ running int // total number of runners
+
+ mu sync.Mutex
+ added map[interface{}]bool // items added to set
+ todo []interface{} // items yet to be run
+ wait sync.Cond // wait when todo is empty
+ waiting int // number of runners waiting for todo
+}
+
+func (w *Work) init() {
+ if w.added == nil {
+ w.added = make(map[interface{}]bool)
+ }
+}
+
+// Add adds item to the work set, if it hasn't already been added.
+func (w *Work) Add(item interface{}) {
+ w.mu.Lock()
+ w.init()
+ if !w.added[item] {
+ w.added[item] = true
+ w.todo = append(w.todo, item)
+ if w.waiting > 0 {
+ w.wait.Signal()
+ }
+ }
+ w.mu.Unlock()
+}
+
+// Do runs f in parallel on items from the work set,
+// with at most n invocations of f running at a time.
+// It returns when everything added to the work set has been processed.
+// At least one item should have been added to the work set
+// before calling Do (or else Do returns immediately),
+// but it is allowed for f(item) to add new items to the set.
+// Do should only be used once on a given Work.
+func (w *Work) Do(n int, f func(item interface{})) {
+ if n < 1 {
+ panic("par.Work.Do: n < 1")
+ }
+ n = 1
+ if w.running >= 1 {
+ panic("par.Work.Do: already called Do")
+ }
+
+ w.running = n
+ w.f = f
+ w.wait.L = &w.mu
+
+ for i := 0; i < n-1; i++ {
+ go w.runner()
+ }
+ w.runner()
+}
+
+// runner executes work in w until both nothing is left to do
+// and all the runners are waiting for work.
+// (Then all the runners return.)
+func (w *Work) runner() {
+ for {
+ // Wait for something to do.
+ w.mu.Lock()
+ for len(w.todo) == 0 {
+ w.waiting++
+ if w.waiting == w.running {
+ // All done.
+ w.wait.Broadcast()
+ w.mu.Unlock()
+ return
+ }
+ w.wait.Wait()
+ w.waiting--
+ }
+
+ // Pick something to do at random,
+ // to eliminate pathological contention
+ // in case items added at about the same time
+ // are most likely to contend.
+ i := rand.Intn(len(w.todo))
+ item := w.todo[i]
+ w.todo[i] = w.todo[len(w.todo)-1]
+ w.todo = w.todo[:len(w.todo)-1]
+ w.mu.Unlock()
+
+ w.f(item)
+ }
+}
+
+// Cache runs an action once per key and caches the result.
+type Cache struct {
+ m sync.Map
+}
+
+type cacheEntry struct {
+ done uint32
+ mu sync.Mutex
+ result interface{}
+}
+
+// Do calls the function f if and only if Do is being called for the first time with this key.
+// No call to Do with a given key returns until the one call to f returns.
+// Do returns the value returned by the one call to f.
+func (c *Cache) Do(key interface{}, f func() interface{}) interface{} {
+ entryIface, ok := c.m.Load(key)
+ if !ok {
+ entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
+ }
+ e := entryIface.(*cacheEntry)
+ if atomic.LoadUint32(&e.done) == 0 {
+ e.mu.Lock()
+ if atomic.LoadUint32(&e.done) == 0 {
+ e.result = f()
+ atomic.StoreUint32(&e.done, 1)
+ }
+ e.mu.Unlock()
+ }
+ return e.result
+}
+
+// Get returns the cached result associated with key.
+// It returns nil if there is no such result.
+// If the result for key is being computed, Get does not wait for the computation to finish.
+func (c *Cache) Get(key interface{}) interface{} {
+ entryIface, ok := c.m.Load(key)
+ if !ok {
+ return nil
+ }
+ e := entryIface.(*cacheEntry)
+ if atomic.LoadUint32(&e.done) == 0 {
+ return nil
+ }
+ return e.result
+}
--- /dev/null
+// Copyright 2018 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 par
+
+import (
+ "sync/atomic"
+ "testing"
+)
+
+func TestWork(t *testing.T) {
+ var w Work
+
+ const N = 10000
+ n := int32(0)
+ w.Add(N)
+ w.Do(100, func(x interface{}) {
+ atomic.AddInt32(&n, 1)
+ i := x.(int)
+ if i >= 2 {
+ w.Add(i - 1)
+ w.Add(i - 2)
+ }
+ w.Add(i >> 1)
+ w.Add((i >> 1) ^ 1)
+ })
+ if n != N+1 {
+ t.Fatalf("ran %d items, expected %d", n, N+1)
+ }
+}
+
+func TestCache(t *testing.T) {
+ var cache Cache
+
+ n := 1
+ v := cache.Do(1, func() interface{} { n++; return n })
+ if v != 2 {
+ t.Fatalf("cache.Do(1) did not run f")
+ }
+ v = cache.Do(1, func() interface{} { n++; return n })
+ if v != 2 {
+ t.Fatalf("cache.Do(1) ran f again!")
+ }
+ v = cache.Do(2, func() interface{} { n++; return n })
+ if v != 3 {
+ t.Fatalf("cache.Do(2) did not run f")
+ }
+ v = cache.Do(1, func() interface{} { n++; return n })
+ if v != 2 {
+ t.Fatalf("cache.Do(1) did not returned saved value from original cache.Do(1)")
+ }
+}
if err != nil || !fi.IsDir() {
return nil
}
+ top := false
if path == dir {
// filepath.Walk starts at dir and recurses. For the recursive case,
// the path is the result of filepath.Join, which calls filepath.Clean.
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
// package, because prepending the prefix "./" to the unclean path would
// result in "././io", and match("././io") returns false.
+ top = true
path = filepath.Clean(path)
}
return filepath.SkipDir
}
+ if !top && cfg.ModulesEnabled {
+ // Ignore other modules found in subdirectories.
+ if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil {
+ return filepath.SkipDir
+ }
+ }
+
name := prefix + filepath.ToSlash(path)
if !match(name) {
return nil
// ImportPathsNoDotExpansion returns the import paths to use for the given
// command line, but it does no ... expansion.
-// TODO(vgo): Delete once old go get is gone.
+// TODO(rsc): Delete once old go get is gone.
func ImportPathsNoDotExpansion(args []string) []string {
args = CleanImportPaths(args)
var out []string
elem := path[:i]
return !strings.Contains(elem, ".")
}
+
+// IsRelativePath reports whether pattern should be interpreted as a directory
+// path relative to the current directory, as opposed to a pattern matching
+// import paths.
+func IsRelativePath(pattern string) bool {
+ return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
+}
return v[:1+len(pv.major)]
}
+// MajorMinor returns the major.minor version prefix of the semantic version v.
+// For example, MajorMinor("v2.1.0") == "v2.1".
+// If v is an invalid semantic version string, MajorMinor returns the empty string.
+func MajorMinor(v string) string {
+ pv, ok := parse(v)
+ if !ok {
+ return ""
+ }
+ i := 1 + len(pv.major)
+ if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
+ return v[:j]
+ }
+ return v[:i] + "." + pv.minor
+}
+
+// Prerelease returns the prerelease suffix of the semantic version v.
+// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
+// If v is an invalid semantic version string, Prerelease returns the empty string.
+func Prerelease(v string) string {
+ pv, ok := parse(v)
+ if !ok {
+ return ""
+ }
+ return pv.prerelease
+}
+
+// Build returns the build suffix of the semantic version v.
+// For example, Build("v2.1.0+meta") == "+meta".
+// If v is an invalid semantic version string, Build returns the empty string.
+func Build(v string) string {
+ pv, ok := parse(v)
+ if !ok {
+ return ""
+ }
+ return pv.build
+}
+
// Compare returns an integer comparing two versions according to
// according to semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
out string
}{
{"bad", ""},
+ {"v1-alpha.beta.gamma", ""},
{"v1-pre", ""},
{"v1+meta", ""},
{"v1-pre+meta", ""},
{"v1.2.3-zzz", "v1.2.3-zzz"},
{"v1.2.3", "v1.2.3"},
{"v1.2.3+meta", "v1.2.3"},
+ {"v1.2.3+meta-pre", "v1.2.3"},
}
func TestIsValid(t *testing.T) {
}
}
+func TestMajorMinor(t *testing.T) {
+ for _, tt := range tests {
+ out := MajorMinor(tt.in)
+ var want string
+ if tt.out != "" {
+ want = tt.in
+ if i := strings.Index(want, "+"); i >= 0 {
+ want = want[:i]
+ }
+ if i := strings.Index(want, "-"); i >= 0 {
+ want = want[:i]
+ }
+ switch strings.Count(want, ".") {
+ case 0:
+ want += ".0"
+ case 1:
+ // ok
+ case 2:
+ want = want[:strings.LastIndex(want, ".")]
+ }
+ }
+ if out != want {
+ t.Errorf("MajorMinor(%q) = %q, want %q", tt.in, out, want)
+ }
+ }
+}
+
+func TestPrerelease(t *testing.T) {
+ for _, tt := range tests {
+ pre := Prerelease(tt.in)
+ var want string
+ if tt.out != "" {
+ if i := strings.Index(tt.out, "-"); i >= 0 {
+ want = tt.out[i:]
+ }
+ }
+ if pre != want {
+ t.Errorf("Prerelease(%q) = %q, want %q", tt.in, pre, want)
+ }
+ }
+}
+
+func TestBuild(t *testing.T) {
+ for _, tt := range tests {
+ build := Build(tt.in)
+ var want string
+ if tt.out != "" {
+ if i := strings.Index(tt.in, "+"); i >= 0 {
+ want = tt.in[i:]
+ }
+ }
+ if build != want {
+ t.Errorf("Build(%q) = %q, want %q", tt.in, build, want)
+ }
+ }
+}
+
func TestCompare(t *testing.T) {
for i, ti := range tests {
for j, tj := range tests {
"strings"
)
-// HasFilePathPrefix reports whether the filesystem path s begins with the
-// elements in prefix.
+// HasPath reports whether the slash-separated path s
+// begins with the elements in prefix.
+func HasPathPrefix(s, prefix string) bool {
+ if len(s) == len(prefix) {
+ return s == prefix
+ }
+ if prefix == "" {
+ return true
+ }
+ if len(s) > len(prefix) {
+ if prefix != "" && prefix[len(prefix)-1] == '/' || s[len(prefix)] == '/' {
+ return s[:len(prefix)] == prefix
+ }
+ }
+ return false
+}
+
+// HasFilePathPrefix reports whether the filesystem path s
+// begins with the elements in prefix.
func HasFilePathPrefix(s, prefix string) bool {
sv := strings.ToUpper(filepath.VolumeName(s))
pv := strings.ToUpper(filepath.VolumeName(prefix))
return false
case len(s) == len(prefix):
return s == prefix
+ case prefix == "":
+ return true
case len(s) > len(prefix):
- if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
+ if prefix[len(prefix)-1] == filepath.Separator {
return strings.HasPrefix(s, prefix)
}
return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
--- /dev/null
+// Copyright 2018 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 txtar implements a trivial text-based file archive format.
+//
+// The goals for the format are:
+//
+// - be trivial enough to create and edit by hand.
+// - be able to store trees of text files describing go command test cases.
+// - diff nicely in git history and code reviews.
+//
+// Non-goals include being a completely general archive format,
+// storing binary data, storing file modes, storing special files like
+// symbolic links, and so on.
+//
+// Txtar format
+//
+// A txtar archive is zero or more comment lines and then a sequence of file entries.
+// Each file entry begins with a file marker line of the form "-- FILENAME --"
+// and is followed by zero or more file content lines making up the file data.
+// The comment or file content ends at the next file marker line.
+// The file marker line must begin with the three-byte sequence "-- "
+// and end with the three-byte sequence " --", but the enclosed
+// file name can be surrounding by additional white space,
+// all of which is stripped.
+//
+// If the txtar file is missing a trailing newline on the final line,
+// parsers should consider a final newline to be present anyway.
+//
+// There are no possible syntax errors in a txtar archive.
+package txtar
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "strings"
+)
+
+// An Archive is a collection of files.
+type Archive struct {
+ Comment []byte
+ Files []File
+}
+
+// A File is a single file in an archive.
+type File struct {
+ Name string // name of file ("foo/bar.txt")
+ Data []byte // text content of file
+}
+
+// Format returns the serialized form of an Archive.
+// It is assumed that the Archive data structure is well-formed:
+// a.Comment and all a.File[i].Data contain no file marker lines,
+// and all a.File[i].Name is non-empty.
+func Format(a *Archive) []byte {
+ var buf bytes.Buffer
+ buf.Write(fixNL(a.Comment))
+ for _, f := range a.Files {
+ fmt.Fprintf(&buf, "-- %s --\n", f.Name)
+ buf.Write(fixNL(f.Data))
+ }
+ return buf.Bytes()
+}
+
+// ParseFile parses the named file as an archive.
+func ParseFile(file string) (*Archive, error) {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, err
+ }
+ return Parse(data), nil
+}
+
+// Parse parses the serialized form of an Archive.
+// The returned Archive holds slices of data.
+func Parse(data []byte) *Archive {
+ a := new(Archive)
+ var name string
+ a.Comment, name, data = findFileMarker(data)
+ for name != "" {
+ f := File{name, nil}
+ f.Data, name, data = findFileMarker(data)
+ a.Files = append(a.Files, f)
+ }
+ return a
+}
+
+var (
+ newlineMarker = []byte("\n-- ")
+ marker = []byte("-- ")
+ markerEnd = []byte(" --")
+)
+
+// findFileMarker finds the next file marker in data,
+// extracts the file name, and returns the data before the marker,
+// the file name, and the data after the marker.
+// If there is no next marker, findFileMarker returns before = fixNL(data), name = "", after = nil.
+func findFileMarker(data []byte) (before []byte, name string, after []byte) {
+ var i int
+ for {
+ if name, after = isMarker(data[i:]); name != "" {
+ return data[:i], name, after
+ }
+ j := bytes.Index(data[i:], newlineMarker)
+ if j < 0 {
+ return fixNL(data), "", nil
+ }
+ i += j + 1 // positioned at start of new possible marker
+ }
+}
+
+// isMarker checks whether data begins with a file marker line.
+// If so, it returns the name from the line and the data after the line.
+// Otherwise it returns name == "" with an unspecified after.
+func isMarker(data []byte) (name string, after []byte) {
+ if !bytes.HasPrefix(data, marker) {
+ return "", nil
+ }
+ if i := bytes.IndexByte(data, '\n'); i >= 0 {
+ data, after = data[:i], data[i+1:]
+ }
+ if !bytes.HasSuffix(data, markerEnd) {
+ return "", nil
+ }
+ return strings.TrimSpace(string(data[len(marker) : len(data)-len(markerEnd)])), after
+}
+
+// If data is empty or ends in \n, fixNL returns data.
+// Otherwise fixNL returns a new slice consisting of data with a final \n added.
+func fixNL(data []byte) []byte {
+ if len(data) == 0 || data[len(data)-1] == '\n' {
+ return data
+ }
+ d := make([]byte, len(data)+1)
+ copy(d, data)
+ d[len(data)] = '\n'
+ return d
+}
--- /dev/null
+// Copyright 2018 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 txtar
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+var tests = []struct {
+ name string
+ text string
+ parsed *Archive
+}{
+ {
+ name: "basic",
+ text: `comment1
+comment2
+-- file1 --
+File 1 text.
+-- foo ---
+More file 1 text.
+-- file 2 --
+File 2 text.
+-- empty --
+-- noNL --
+hello world`,
+ parsed: &Archive{
+ Comment: []byte("comment1\ncomment2\n"),
+ Files: []File{
+ {"file1", []byte("File 1 text.\n-- foo ---\nMore file 1 text.\n")},
+ {"file 2", []byte("File 2 text.\n")},
+ {"empty", []byte{}},
+ {"noNL", []byte("hello world\n")},
+ },
+ },
+ },
+}
+
+func Test(t *testing.T) {
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := Parse([]byte(tt.text))
+ if !reflect.DeepEqual(a, tt.parsed) {
+ t.Fatalf("Parse: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))
+ }
+ text := Format(a)
+ a = Parse(text)
+ if !reflect.DeepEqual(a, tt.parsed) {
+ t.Fatalf("Parse after Format: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))
+ }
+ })
+ }
+}
+
+func shortArchive(a *Archive) string {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "comment: %q\n", a.Comment)
+ for _, f := range a.Files {
+ fmt.Fprintf(&buf, "file %q: %q\n", f.Name, f.Data)
+ }
+ return buf.String()
+}
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "bytes"
- "cmd/go/internal/base"
- "cmd/go/internal/cfg"
- "cmd/go/internal/modinfo"
- "cmd/go/internal/module"
- "cmd/go/internal/search"
- "encoding/hex"
- "fmt"
- "os"
- "path/filepath"
-)
-
-var (
- infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
- infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
-)
-
-func isStandardImportPath(path string) bool {
- if search.IsStandardImportPath(path) {
- if _, err := os.Stat(filepath.Join(cfg.GOROOT, "src", path)); err == nil {
- return true
- }
- if _, err := os.Stat(filepath.Join(cfg.GOROOT, "src/vendor", path)); err == nil {
- return true
- }
- }
- return false
-}
-
-func PackageModuleInfo(path string) *modinfo.ModulePublic {
- var info modinfo.ModulePublic
- if isStandardImportPath(path) || !Enabled() {
- return nil
- }
- target := findModule(path, path)
- info.Top = target.Path == buildList[0].Path
- info.Path = target.Path
- info.Version = target.Version
- return &info
-}
-
-func PackageBuildInfo(path string, deps []string) string {
- if isStandardImportPath(path) || !Enabled() {
- return ""
- }
- target := findModule(path, path)
- mdeps := make(map[module.Version]bool)
- for _, dep := range deps {
- if !isStandardImportPath(dep) {
- mdeps[findModule(path, dep)] = true
- }
- }
- var mods []module.Version
- delete(mdeps, target)
- for mod := range mdeps {
- mods = append(mods, mod)
- }
- sortModules(mods)
-
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "path\t%s\n", path)
- tv := target.Version
- if tv == "" {
- tv = "(devel)"
- }
- fmt.Fprintf(&buf, "mod\t%s\t%s\t%s\n", target.Path, tv, findModHash(target))
- for _, mod := range mods {
- mv := mod.Version
- if mv == "" {
- mv = "(devel)"
- }
- r := replaced(mod)
- h := ""
- if r == nil {
- h = "\t" + findModHash(mod)
- }
- fmt.Fprintf(&buf, "dep\t%s\t%s%s\n", mod.Path, mod.Version, h)
- if r := replaced(mod); r != nil {
- fmt.Fprintf(&buf, "=>\t%s\t%s\t%s\n", r.New.Path, r.New.Version, findModHash(r.New))
- }
- }
- return buf.String()
-}
-
-func findModule(target, path string) module.Version {
- if path == "." {
- return buildList[0]
- }
- for _, mod := range buildList {
- if importPathInModule(path, mod.Path) {
- return mod
- }
- }
- base.Fatalf("build %v: cannot find module for path %v", target, path)
- panic("unreachable")
-}
-
-func ModInfoProg(info string) []byte {
- return []byte(fmt.Sprintf(`
- package main
- import _ "unsafe"
- //go:linkname __debug_modinfo__ runtime/debug.modinfo
- var __debug_modinfo__ string
- func init() {
- __debug_modinfo__ = %q
- }
- `, string(infoStart)+info+string(infoEnd)))
-}
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "archive/zip"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "cmd/go/internal/base"
- "cmd/go/internal/dirhash"
- "cmd/go/internal/modfetch"
- "cmd/go/internal/module"
- "cmd/go/internal/semver"
-)
-
-// fetch returns the directory in the local download cache
-// holding the root of mod's source tree.
-// It downloads the module if needed.
-func fetch(mod module.Version) (dir string, err error) {
- if r := replaced(mod); r != nil {
- if r.New.Version == "" {
- dir = r.New.Path
- if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot, dir)
- }
- return dir, nil
- }
- mod = r.New
- }
-
- modpath := mod.Path + "@" + mod.Version
- dir = filepath.Join(srcV, modpath)
- if files, _ := ioutil.ReadDir(dir); len(files) == 0 {
- zipfile := filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".zip")
- if _, err := os.Stat(zipfile); err == nil {
- // Use it.
- // This should only happen if the v/cache directory is preinitialized
- // or if src/v/modpath was removed but not src/v/cache.
- fmt.Fprintf(os.Stderr, "vgo: extracting %s %s\n", mod.Path, mod.Version)
- } else {
- if err := os.MkdirAll(filepath.Join(srcV, "cache", mod.Path, "@v"), 0777); err != nil {
- return "", err
- }
- fmt.Fprintf(os.Stderr, "vgo: downloading %s %s\n", mod.Path, mod.Version)
- if err := downloadZip(mod, zipfile); err != nil {
- return "", err
- }
- }
- if err := modfetch.Unzip(dir, zipfile, modpath, 0); err != nil {
- fmt.Fprintf(os.Stderr, "-> %s\n", err)
- return "", err
- }
- }
- checkModHash(mod)
- return dir, nil
-}
-
-func downloadZip(mod module.Version, target string) error {
- repo, err := modfetch.Lookup(mod.Path)
- if err != nil {
- return err
- }
- tmpfile, err := repo.Zip(mod.Version, os.TempDir())
- if err != nil {
- return err
- }
- defer os.Remove(tmpfile)
-
- // Double-check zip file looks OK.
- z, err := zip.OpenReader(tmpfile)
- if err != nil {
- z.Close()
- return err
- }
- prefix := mod.Path + "@" + mod.Version
- for _, f := range z.File {
- if !strings.HasPrefix(f.Name, prefix) {
- z.Close()
- return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
- }
- }
- z.Close()
-
- hash, err := dirhash.HashZip(tmpfile, dirhash.DefaultHash)
- if err != nil {
- return err
- }
- r, err := os.Open(tmpfile)
- if err != nil {
- return err
- }
- defer r.Close()
- w, err := os.Create(target)
- if err != nil {
- return err
- }
- if _, err := io.Copy(w, r); err != nil {
- w.Close()
- return fmt.Errorf("copying: %v", err)
- }
- if err := w.Close(); err != nil {
- return err
- }
- return ioutil.WriteFile(target+"hash", []byte(hash), 0666)
-}
-
-var useModHash = false
-var modHash map[module.Version][]string
-
-func initModHash() {
- if modHash != nil {
- return
- }
- modHash = make(map[module.Version][]string)
- file := filepath.Join(ModRoot, "go.modverify")
- data, err := ioutil.ReadFile(file)
- if err != nil && os.IsNotExist(err) {
- return
- }
- if err != nil {
- base.Fatalf("vgo: %v", err)
- }
- useModHash = true
- lineno := 0
- for len(data) > 0 {
- var line []byte
- lineno++
- i := bytes.IndexByte(data, '\n')
- if i < 0 {
- line, data = data, nil
- } else {
- line, data = data[:i], data[i+1:]
- }
- f := strings.Fields(string(line))
- if len(f) == 0 {
- // blank line; skip it
- continue
- }
- if len(f) != 3 {
- base.Fatalf("vgo: malformed go.modverify:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
- }
- mod := module.Version{Path: f[0], Version: f[1]}
- modHash[mod] = append(modHash[mod], f[2])
- }
-}
-
-func checkModHash(mod module.Version) {
- initModHash()
- if !useModHash {
- return
- }
-
- data, err := ioutil.ReadFile(filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".ziphash"))
- if err != nil {
- base.Fatalf("vgo: verifying %s %s: %v", mod.Path, mod.Version, err)
- }
- h := strings.TrimSpace(string(data))
- if !strings.HasPrefix(h, "h1:") {
- base.Fatalf("vgo: verifying %s %s: unexpected ziphash: %q", mod.Path, mod.Version, h)
- }
-
- for _, vh := range modHash[mod] {
- if h == vh {
- return
- }
- if strings.HasPrefix(vh, "h1:") {
- base.Fatalf("vgo: verifying %s %s: module hash mismatch\n\tdownloaded: %v\n\tgo.modverify: %v", mod.Path, mod.Version, h, vh)
- }
- }
- if len(modHash[mod]) > 0 {
- fmt.Fprintf(os.Stderr, "warning: verifying %s %s: unknown hashes in go.modverify: %v; adding %v", mod.Path, mod.Version, strings.Join(modHash[mod], ", "), h)
- }
- modHash[mod] = append(modHash[mod], h)
-}
-
-func findModHash(mod module.Version) string {
- data, err := ioutil.ReadFile(filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".ziphash"))
- if err != nil {
- return ""
- }
- return strings.TrimSpace(string(data))
-}
-
-func writeModHash() {
- if !useModHash {
- return
- }
-
- var mods []module.Version
- for m := range modHash {
- mods = append(mods, m)
- }
- sortModules(mods)
- var buf bytes.Buffer
- for _, m := range mods {
- list := modHash[m]
- sort.Strings(list)
- for _, h := range list {
- fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
- }
- }
-
- file := filepath.Join(ModRoot, "go.modverify")
- data, _ := ioutil.ReadFile(filepath.Join(ModRoot, "go.modverify"))
- if bytes.Equal(data, buf.Bytes()) {
- return
- }
-
- if err := ioutil.WriteFile(file, buf.Bytes(), 0666); err != nil {
- base.Fatalf("vgo: writing go.modverify: %v", err)
- }
-}
-
-func sortModules(mods []module.Version) {
- sort.Slice(mods, func(i, j int) bool {
- mi := mods[i]
- mj := mods[j]
- if mi.Path != mj.Path {
- return mi.Path < mj.Path
- }
- return semver.Compare(mi.Version, mj.Version) < 0
- })
-}
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "strings"
-
- "cmd/go/internal/base"
- "cmd/go/internal/modfetch"
- "cmd/go/internal/module"
- "cmd/go/internal/mvs"
- "cmd/go/internal/semver"
-)
-
-var CmdGet = &base.Command{
- UsageLine: "get [build flags] [packages]",
- Short: "download and install versioned modules and dependencies",
- Long: `
-Get downloads the latest versions of modules containing the named packages,
-along with the versions of the dependencies required by those modules
-(not necessarily the latest ones).
-
-It then installs the named packages, like 'go install'.
-
-The -u flag causes get to download the latest version of dependencies as well.
-
-Each package being updated can be suffixed with @version to specify
-the desired version. Specifying a version older than the one currently
-in use causes a downgrade, which may in turn downgrade other
-modules using that one, to keep everything consistent.
-
-TODO: Make this documentation better once the semantic dust settles.
- `,
-}
-
-var getU = CmdGet.Flag.Bool("u", false, "")
-
-func init() {
- CmdGet.Run = runGet // break init loop
-}
-
-func runGet(cmd *base.Command, args []string) {
- if *getU && len(args) > 0 {
- base.Fatalf("vgo get: -u not supported with argument list")
- }
- if !*getU && len(args) == 0 {
- base.Fatalf("vgo get: need arguments or -u")
- }
-
- if *getU {
- isGetU = true
- ImportPaths([]string{"."})
- return
- }
-
- Init()
- InitMod()
- var upgrade []module.Version
- var downgrade []module.Version
- var newPkgs []string
- for _, pkg := range args {
- var path, vers string
- /* OLD CODE
- if n := strings.Count(pkg, "(") + strings.Count(pkg, ")"); n > 0 {
- i := strings.Index(pkg, "(")
- j := strings.Index(pkg, ")")
- if n != 2 || i < 0 || j <= i+1 || j != len(pkg)-1 && pkg[j+1] != '/' {
- base.Errorf("vgo get: invalid module version syntax: %s", pkg)
- continue
- }
- path, vers = pkg[:i], pkg[i+1:j]
- pkg = pkg[:i] + pkg[j+1:]
- */
- if i := strings.Index(pkg, "@"); i >= 0 {
- path, pkg, vers = pkg[:i], pkg[:i], pkg[i+1:]
- if strings.Contains(vers, "@") {
- base.Errorf("vgo get: invalid module version syntax: %s", pkg)
- continue
- }
- } else {
- path = pkg
- vers = "latest"
- }
- if vers == "none" {
- downgrade = append(downgrade, module.Version{Path: path, Version: ""})
- } else {
- info, err := modfetch.Query(path, vers, allowed)
- if err != nil {
- base.Errorf("vgo get %v: %v", pkg, err)
- continue
- }
- upgrade = append(upgrade, module.Version{Path: path, Version: info.Version})
- newPkgs = append(newPkgs, pkg)
- }
- }
- args = newPkgs
-
- // Upgrade.
- var err error
- buildList, err = mvs.Upgrade(Target, newReqs(), upgrade...)
- if err != nil {
- base.Fatalf("vgo get: %v", err)
- }
-
- importPaths([]string{"."})
-
- // Downgrade anything that went too far.
- version := make(map[string]string)
- for _, mod := range buildList {
- version[mod.Path] = mod.Version
- }
- for _, mod := range upgrade {
- if semver.Compare(mod.Version, version[mod.Path]) < 0 {
- downgrade = append(downgrade, mod)
- }
- }
-
- if len(downgrade) > 0 {
- buildList, err = mvs.Downgrade(Target, newReqs(buildList[1:]...), downgrade...)
- if err != nil {
- base.Fatalf("vgo get: %v", err)
- }
-
- // TODO: Check that everything we need to import is still available.
- /*
- local := v.matchPackages("all", v.Reqs[:1])
- for _, path := range local {
- dir, err := v.importDir(path)
- if err != nil {
- return err // TODO
- }
- imports, testImports, err := imports.ScanDir(dir, v.Tags)
- for _, path := range imports {
- xxx
- }
- for _, path := range testImports {
- xxx
- }
- }
- */
- }
- writeGoMod()
-
- if len(args) > 0 {
- InstallHook(args)
- }
-}
-
-// Call into "go install". Set by internal/work, which imports us.
-var InstallHook func([]string)
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "bufio"
- "fmt"
- "io"
- "os"
- "regexp"
- "unicode/utf8"
-
- "cmd/go/internal/base"
- "cmd/go/internal/modfetch"
- "cmd/go/internal/module"
-)
-
-func ListT(pkgs []string) {
- if Init(); !Enabled() {
- base.Fatalf("go list: cannot use -t outside module")
- }
- InitMod()
-
- if len(pkgs) == 0 {
- base.Fatalf("vgo list -t: need list of modules")
- }
-
- for _, pkg := range pkgs {
- repo, err := modfetch.Lookup(pkg)
- if err != nil {
- base.Errorf("vgo list -t: %v", err)
- continue
- }
- path := repo.ModulePath()
- fmt.Printf("%s\n", path)
- tags, err := repo.Versions("")
- if err != nil {
- base.Errorf("vgo list -t: %v", err)
- continue
- }
- for _, t := range tags {
- if excluded[module.Version{Path: path, Version: t}] {
- t += " # excluded"
- }
- fmt.Printf("\t%s\n", t)
- }
- }
-}
-
-func ListM() {
- if Init(); !Enabled() {
- base.Fatalf("go list: cannot use -m outside module")
- }
- InitMod()
- iterate(func(*loader) {})
- printListM(os.Stdout)
-}
-
-func printListM(w io.Writer) {
- var rows [][]string
- rows = append(rows, []string{"MODULE", "VERSION"})
- for _, mod := range buildList {
- v := mod.Version
- if v == "" {
- v = "-"
- }
- rows = append(rows, []string{mod.Path, v})
- if r := replaced(mod); r != nil {
- rows = append(rows, []string{" => " + r.New.Path, r.New.Version})
- }
- }
- printTable(w, rows)
-}
-
-func ListMU() {
- if Init(); !Enabled() {
- base.Fatalf("go list: cannot use -m outside module")
- }
- InitMod()
-
- quietLookup = true // do not chatter in v.Lookup
- iterate(func(*loader) {})
-
- var rows [][]string
- rows = append(rows, []string{"MODULE", "VERSION", "LATEST"})
- for _, mod := range buildList {
- var latest string
- v := mod.Version
- if v == "" {
- v = "-"
- latest = "-"
- } else {
- info, err := modfetch.Query(mod.Path, "latest", allowed)
- if err != nil {
- latest = "ERR: " + err.Error()
- } else {
- latest = info.Version
- if !isPseudoVersion(latest) && !info.Time.IsZero() {
- latest += info.Time.Local().Format(" (2006-01-02 15:04)")
- }
- }
- if !isPseudoVersion(mod.Version) {
- if info, err := modfetch.Query(mod.Path, mod.Version, nil); err == nil && !info.Time.IsZero() {
- v += info.Time.Local().Format(" (2006-01-02 15:04)")
- }
- }
- }
- if latest == v {
- latest = "-"
- }
- rows = append(rows, []string{mod.Path, v, latest})
- }
- printTable(os.Stdout, rows)
-}
-
-var pseudoVersionRE = regexp.MustCompile(`^v[0-9]+\.0\.0-[0-9]{14}-[A-Za-z0-9]+$`)
-
-func isPseudoVersion(v string) bool {
- return pseudoVersionRE.MatchString(v)
-}
-
-func printTable(w io.Writer, rows [][]string) {
- var max []int
- for _, row := range rows {
- for i, c := range row {
- n := utf8.RuneCountInString(c)
- if i >= len(max) {
- max = append(max, n)
- } else if max[i] < n {
- max[i] = n
- }
- }
- }
-
- b := bufio.NewWriter(w)
- for _, row := range rows {
- for len(row) > 0 && row[len(row)-1] == "" {
- row = row[:len(row)-1]
- }
- for i, c := range row {
- b.WriteString(c)
- if i+1 < len(row) {
- for j := utf8.RuneCountInString(c); j < max[i]+2; j++ {
- b.WriteRune(' ')
- }
- }
- }
- b.WriteRune('\n')
- }
- b.Flush()
-}
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "go/build"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "cmd/go/internal/base"
- "cmd/go/internal/cfg"
- "cmd/go/internal/imports"
- "cmd/go/internal/modconv"
- "cmd/go/internal/modfetch"
- "cmd/go/internal/modfile"
- "cmd/go/internal/module"
- "cmd/go/internal/mvs"
- "cmd/go/internal/search"
- "cmd/go/internal/semver"
-)
-
-type importLevel int
-
-const (
- levelNone importLevel = 0
- levelBuild importLevel = 1
- levelTest importLevel = 2
- levelTestRecursive importLevel = 3
-)
-
-var (
- buildList []module.Version
- tags map[string]bool
- importmap map[string]string
- pkgdir map[string]string
- pkgmod map[string]module.Version
- isGetU bool
-)
-
-func AddImports(gofiles []string) {
- if Init(); !Enabled() {
- return
- }
- InitMod()
-
- imports, testImports, err := imports.ScanFiles(gofiles, tags)
- if err != nil {
- base.Fatalf("vgo: %v", err)
- }
-
- iterate(func(ld *loader) {
- ld.importList(imports, levelBuild)
- ld.importList(testImports, levelBuild)
- })
- writeGoMod()
-}
-
-func ImportPaths(args []string) []string {
- if Init(); !Enabled() {
- return search.ImportPaths(args)
- }
- InitMod()
-
- paths := importPaths(args)
- writeGoMod()
- return paths
-}
-
-func importPaths(args []string) []string {
- level := levelBuild
- switch cfg.CmdName {
- case "test", "vet":
- level = levelTest
- }
- cleaned := search.CleanImportPaths(args)
- iterate(func(ld *loader) {
- args = expandImportPaths(cleaned)
- for i, pkg := range args {
- if pkg == "." || pkg == ".." || strings.HasPrefix(pkg, "./") || strings.HasPrefix(pkg, "../") {
- dir := filepath.Join(cwd, pkg)
- if dir == ModRoot {
- pkg = Target.Path
- } else if strings.HasPrefix(dir, ModRoot+string(filepath.Separator)) {
- pkg = Target.Path + filepath.ToSlash(dir[len(ModRoot):])
- } else {
- base.Errorf("vgo: package %s outside module root", pkg)
- continue
- }
- args[i] = pkg
- }
- ld.importPkg(pkg, level)
- }
- })
- return args
-}
-
-func Lookup(parentPath, path string) (dir, realPath string, err error) {
- realPath = importmap[path]
- if realPath == "" {
- if isStandardImportPath(path) {
- dir := filepath.Join(cfg.GOROOT, "src", path)
- if _, err := os.Stat(dir); err == nil {
- return dir, path, nil
- }
- }
- return "", "", fmt.Errorf("no such package in module")
- }
- return pkgdir[realPath], realPath, nil
-}
-
-func iterate(doImports func(*loader)) {
- var err error
- mvsOp := mvs.BuildList
- if isGetU {
- mvsOp = mvs.UpgradeAll
- }
- buildList, err = mvsOp(Target, newReqs())
- if err != nil {
- base.Fatalf("vgo: %v", err)
- }
-
- var ld *loader
- for {
- ld = newLoader()
- doImports(ld)
- if len(ld.missing) == 0 {
- break
- }
- for _, m := range ld.missing {
- findMissing(m)
- }
- base.ExitIfErrors()
- buildList, err = mvsOp(Target, newReqs())
- if err != nil {
- base.Fatalf("vgo: %v", err)
- }
- }
- base.ExitIfErrors()
-
- importmap = ld.importmap
- pkgdir = ld.pkgdir
- pkgmod = ld.pkgmod
-}
-
-type loader struct {
- imported map[string]importLevel
- importmap map[string]string
- pkgdir map[string]string
- pkgmod map[string]module.Version
- tags map[string]bool
- missing []missing
- imports []string
- stack []string
-}
-
-type missing struct {
- path string
- stack string
-}
-
-func newLoader() *loader {
- ld := &loader{
- imported: make(map[string]importLevel),
- importmap: make(map[string]string),
- pkgdir: make(map[string]string),
- pkgmod: make(map[string]module.Version),
- tags: imports.Tags(),
- }
- ld.imported["C"] = 100
- return ld
-}
-
-func (ld *loader) stackText() string {
- var buf bytes.Buffer
- for _, p := range ld.stack[:len(ld.stack)-1] {
- fmt.Fprintf(&buf, "import %q ->\n\t", p)
- }
- fmt.Fprintf(&buf, "import %q", ld.stack[len(ld.stack)-1])
- return buf.String()
-}
-
-func (ld *loader) importList(pkgs []string, level importLevel) {
- for _, pkg := range pkgs {
- ld.importPkg(pkg, level)
- }
-}
-
-func (ld *loader) importPkg(path string, level importLevel) {
- if ld.imported[path] >= level {
- return
- }
-
- ld.stack = append(ld.stack, path)
- defer func() {
- ld.stack = ld.stack[:len(ld.stack)-1]
- }()
-
- // Any rewritings go here.
- realPath := path
-
- ld.imported[path] = level
- ld.importmap[path] = realPath
- if realPath != path && ld.imported[realPath] >= level {
- // Already handled.
- return
- }
-
- dir := ld.importDir(realPath)
- if dir == "" {
- return
- }
-
- ld.pkgdir[realPath] = dir
-
- imports, testImports, err := imports.ScanDir(dir, ld.tags)
- if err != nil {
- base.Errorf("vgo: %s [%s]: %v", ld.stackText(), dir, err)
- return
- }
- nextLevel := level
- if level == levelTest {
- nextLevel = levelBuild
- }
- for _, pkg := range imports {
- ld.importPkg(pkg, nextLevel)
- }
- if level >= levelTest {
- for _, pkg := range testImports {
- ld.importPkg(pkg, nextLevel)
- }
- }
-}
-
-func (ld *loader) importDir(path string) string {
- if importPathInModule(path, Target.Path) {
- dir := ModRoot
- if len(path) > len(Target.Path) {
- dir = filepath.Join(dir, path[len(Target.Path)+1:])
- }
- ld.pkgmod[path] = Target
- return dir
- }
-
- i := strings.Index(path, "/")
- if i < 0 || !strings.Contains(path[:i], ".") {
- if strings.HasPrefix(path, "golang_org/") {
- return filepath.Join(cfg.GOROOT, "src/vendor", path)
- }
- dir := filepath.Join(cfg.GOROOT, "src", path)
- if _, err := os.Stat(dir); err == nil {
- return dir
- }
- }
-
- var mod1 module.Version
- var dir1 string
- for _, mod := range buildList {
- if !importPathInModule(path, mod.Path) {
- continue
- }
- dir, err := fetch(mod)
- if err != nil {
- base.Errorf("vgo: %s: %v", ld.stackText(), err)
- return ""
- }
- if len(path) > len(mod.Path) {
- dir = filepath.Join(dir, path[len(mod.Path)+1:])
- }
- if dir1 != "" {
- base.Errorf("vgo: %s: found in both %v %v and %v %v", ld.stackText(),
- mod1.Path, mod1.Version, mod.Path, mod.Version)
- return ""
- }
- dir1 = dir
- mod1 = mod
- }
- if dir1 != "" {
- ld.pkgmod[path] = mod1
- return dir1
- }
- ld.missing = append(ld.missing, missing{path, ld.stackText()})
- return ""
-}
-
-func replaced(mod module.Version) *modfile.Replace {
- var found *modfile.Replace
- for _, r := range modFile.Replace {
- if r.Old == mod {
- found = r // keep going
- }
- }
- return found
-}
-
-func importPathInModule(path, mpath string) bool {
- return mpath == path ||
- len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
-}
-
-var found = make(map[string]bool)
-
-func findMissing(m missing) {
- for _, mod := range buildList {
- if importPathInModule(m.path, mod.Path) {
- // Leave for ordinary build to complain about the missing import.
- return
- }
- }
- if build.IsLocalImport(m.path) {
- base.Errorf("vgo: relative import is not supported: %s", m.path)
- return
- }
- fmt.Fprintf(os.Stderr, "vgo: resolving import %q\n", m.path)
- repo, info, err := modfetch.Import(m.path, allowed)
- if err != nil {
- base.Errorf("vgo: %s: %v", m.stack, err)
- return
- }
- root := repo.ModulePath()
- fmt.Fprintf(os.Stderr, "vgo: finding %s (latest)\n", root)
- if found[root] {
- base.Fatalf("internal error: findmissing loop on %s", root)
- }
- found[root] = true
- fmt.Fprintf(os.Stderr, "vgo: adding %s %s\n", root, info.Version)
- buildList = append(buildList, module.Version{Path: root, Version: info.Version})
- modFile.AddRequire(root, info.Version)
-}
-
-type mvsReqs struct {
- extra []module.Version
-}
-
-func newReqs(extra ...module.Version) *mvsReqs {
- r := &mvsReqs{
- extra: extra,
- }
- return r
-}
-
-func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
- list, err := r.required(mod)
- if err != nil {
- return nil, err
- }
- if *getU {
- for i := range list {
- list[i].Version = "none"
- }
- return list, nil
- }
- for i, mv := range list {
- for excluded[mv] {
- mv1, err := r.Next(mv)
- if err != nil {
- return nil, err
- }
- if mv1.Version == "" {
- return nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version)
- }
- mv = mv1
- }
- list[i] = mv
- }
- return list, nil
-}
-
-var vgoVersion = []byte(modconv.Prefix)
-
-func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
- if mod == Target {
- var list []module.Version
- if buildList != nil {
- list = append(list, buildList[1:]...)
- return list, nil
- }
- for _, r := range modFile.Require {
- list = append(list, r.Mod)
- }
- list = append(list, r.extra...)
- return list, nil
- }
-
- origPath := mod.Path
- if repl := replaced(mod); repl != nil {
- if repl.New.Version == "" {
- // TODO: need to slip the new version into the tags list etc.
- dir := repl.New.Path
- if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot, dir)
- }
- gomod := filepath.Join(dir, "go.mod")
- data, err := ioutil.ReadFile(gomod)
- if err != nil {
- return nil, err
- }
- f, err := modfile.Parse(gomod, data, nil)
- if err != nil {
- return nil, err
- }
- var list []module.Version
- for _, r := range f.Require {
- list = append(list, r.Mod)
- }
- return list, nil
- }
- mod = repl.New
- }
-
- if mod.Version == "none" {
- return nil, nil
- }
-
- if !semver.IsValid(mod.Version) {
- // Disallow the broader queries supported by fetch.Lookup.
- panic(fmt.Errorf("invalid semantic version %q for %s", mod.Version, mod.Path))
- // TODO return nil, fmt.Errorf("invalid semantic version %q", mod.Version)
- }
-
- gomod := filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".mod")
- infofile := filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".info")
- var f *modfile.File
- if data, err := ioutil.ReadFile(gomod); err == nil {
- // If go.mod has a //vgo comment at the start,
- // it was auto-converted from a legacy lock file.
- // The auto-conversion details may have bugs and
- // may be fixed in newer versions of vgo.
- // We ignore cached go.mod files if they do not match
- // our own vgoVersion.
- if !bytes.HasPrefix(data, vgoVersion[:len("//vgo")]) || bytes.HasPrefix(data, vgoVersion) {
- f, err := modfile.Parse(gomod, data, nil)
- if err != nil {
- return nil, err
- }
- var list []module.Version
- for _, r := range f.Require {
- list = append(list, r.Mod)
- }
- return list, nil
- }
- f, err = modfile.Parse("go.mod", data, nil)
- if err != nil {
- return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
- }
- } else {
- if !quietLookup {
- fmt.Fprintf(os.Stderr, "vgo: finding %s %s\n", mod.Path, mod.Version)
- }
- repo, err := modfetch.Lookup(mod.Path)
- if err != nil {
- base.Errorf("vgo: %s: %v\n", mod.Path, err)
- return nil, err
- }
- info, err := repo.Stat(mod.Version)
- if err != nil {
- base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err)
- return nil, err
- }
- data, err := repo.GoMod(info.Version)
- if err != nil {
- base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err)
- return nil, err
- }
-
- f, err = modfile.Parse("go.mod", data, nil)
- if err != nil {
- return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
- }
-
- dir := filepath.Dir(gomod)
- if err := os.MkdirAll(dir, 0777); err != nil {
- return nil, fmt.Errorf("caching go.mod: %v", err)
- }
- js, err := json.Marshal(info)
- if err != nil {
- return nil, fmt.Errorf("internal error: json failure: %v", err)
- }
- if err := ioutil.WriteFile(infofile, js, 0666); err != nil {
- return nil, fmt.Errorf("caching info: %v", err)
- }
- if err := ioutil.WriteFile(gomod, data, 0666); err != nil {
- return nil, fmt.Errorf("caching go.mod: %v", err)
- }
- }
- if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
- return nil, fmt.Errorf("downloaded %q and got module %q", mod.Path, mpath)
- }
-
- var list []module.Version
- for _, req := range f.Require {
- list = append(list, req.Mod)
- }
- if false {
- fmt.Fprintf(os.Stderr, "REQLIST %v:\n", mod)
- for _, req := range list {
- fmt.Fprintf(os.Stderr, "\t%v\n", req)
- }
- }
- return list, nil
-}
-
-var quietLookup bool
-
-func (*mvsReqs) Max(v1, v2 string) string {
- if semver.Compare(v1, v2) == -1 {
- return v2
- }
- return v1
-}
-
-func (*mvsReqs) Latest(path string) (module.Version, error) {
- // Note that query "latest" is not the same as
- // using repo.Latest.
- // The query only falls back to untagged versions
- // if nothing is tagged. The Latest method
- // only ever returns untagged versions,
- // which is not what we want.
- fmt.Fprintf(os.Stderr, "vgo: finding %s latest\n", path)
- info, err := modfetch.Query(path, "latest", allowed)
- if err != nil {
- return module.Version{}, err
- }
- return module.Version{Path: path, Version: info.Version}, nil
-}
-
-var versionCache = make(map[string][]string)
-
-func versions(path string) ([]string, error) {
- list, ok := versionCache[path]
- if !ok {
- var err error
- repo, err := modfetch.Lookup(path)
- if err != nil {
- return nil, err
- }
- list, err = repo.Versions("")
- if err != nil {
- return nil, err
- }
- versionCache[path] = list
- }
- return list, nil
-}
-
-func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
- list, err := versions(m.Path)
- if err != nil {
- return module.Version{}, err
- }
- i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 })
- if i > 0 {
- return module.Version{Path: m.Path, Version: list[i-1]}, nil
- }
- return module.Version{Path: m.Path, Version: "none"}, nil
-}
-
-func (*mvsReqs) Next(m module.Version) (module.Version, error) {
- list, err := versions(m.Path)
- if err != nil {
- return module.Version{}, err
- }
- i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 })
- if i < len(list) {
- return module.Version{Path: m.Path, Version: list[i]}, nil
- }
- return module.Version{Path: m.Path, Version: "none"}, nil
-}
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "fmt"
- "go/build"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "cmd/go/internal/base"
- "cmd/go/internal/cfg"
- "cmd/go/internal/imports"
- "cmd/go/internal/module"
- "cmd/go/internal/search"
-)
-
-func expandImportPaths(args []string) []string {
- var out []string
- for _, a := range args {
- // TODO(rsc): Move a == "ALL" test into search.IsMetaPackage
- // once we officially lock in all the module work (tentatively, Go 1.12).
- if search.IsMetaPackage(a) || a == "ALL" {
- switch a {
- default:
- fmt.Fprintf(os.Stderr, "vgo: warning: %q matches no packages when using modules\n", a)
- case "all", "ALL":
- out = append(out, AllPackages(a)...)
- }
- continue
- }
- if strings.Contains(a, "...") {
- if build.IsLocalImport(a) {
- out = append(out, search.AllPackagesInFS(a)...)
- } else {
- out = append(out, AllPackages(a)...)
- }
- continue
- }
- out = append(out, a)
- }
- return out
-}
-
-// AllPackages returns all the packages that can be found
-// under the $GOPATH directories and $GOROOT matching pattern.
-// The pattern is either "all" (all packages), "std" (standard packages),
-// "cmd" (standard commands), 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
-}
-
-// MatchPackages returns a list of package paths matching pattern
-// (see go help packages for pattern syntax).
-func MatchPackages(pattern string) []string {
- if pattern == "std" || pattern == "cmd" {
- return nil
- }
- if pattern == "all" {
- return MatchAll()
- }
- if pattern == "ALL" {
- return MatchALL()
- }
-
- return matchPackages(pattern, buildList)
-}
-
-func matchPackages(pattern string, buildList []module.Version) []string {
- match := func(string) bool { return true }
- treeCanMatch := func(string) bool { return true }
- if !search.IsMetaPackage(pattern) && pattern != "ALL" {
- match = search.MatchPattern(pattern)
- treeCanMatch = search.TreeCanMatchPattern(pattern)
- }
-
- have := map[string]bool{
- "builtin": true, // ignore pseudo-package that exists only for documentation
- }
- if !cfg.BuildContext.CgoEnabled {
- have["runtime/cgo"] = true // ignore during walk
- }
- var pkgs []string
-
- for _, mod := range buildList {
- if !treeCanMatch(mod.Path) {
- continue
- }
- var root string
- if mod.Version == "" {
- root = ModRoot
- } else {
- var err error
- root, err = fetch(mod)
- if err != nil {
- base.Errorf("vgo: %v", err)
- continue
- }
- }
- root = filepath.Clean(root)
-
- filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- return nil
- }
-
- want := true
- // Avoid .foo, _foo, and testdata directory trees.
- _, elem := filepath.Split(path)
- if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
- want = false
- }
-
- name := mod.Path + filepath.ToSlash(path[len(root):])
- if !treeCanMatch(name) {
- want = false
- }
-
- if !fi.IsDir() {
- if fi.Mode()&os.ModeSymlink != 0 && want {
- if target, err := os.Stat(path); err == nil && target.IsDir() {
- fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
- }
- }
- return nil
- }
-
- if !want {
- return filepath.SkipDir
- }
- if path != root {
- if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil {
- return filepath.SkipDir
- }
- }
-
- if !have[name] {
- have[name] = true
- if match(name) {
- if _, _, err := imports.ScanDir(path, imports.Tags()); err != imports.ErrNoGo {
- pkgs = append(pkgs, name)
- }
- }
- }
-
- if elem == "vendor" {
- return filepath.SkipDir
- }
- return nil
- })
- }
- return pkgs
-}
-
-// MatchAll returns a list of the packages matching the pattern "all".
-// We redefine "all" to mean start with the packages in the current module
-// and then follow imports into other modules to add packages imported
-// (directly or indirectly) as part of builds in this module.
-// It does not include packages in other modules that are not needed
-// by builds of this module.
-func MatchAll() []string {
- return matchAll(imports.Tags())
-}
-
-// MatchALL returns a list of the packages matching the pattern "ALL".
-// The pattern "ALL" is like "all" but looks at all source files,
-// even ones that would be ignored by current build tag settings.
-// That's useful for identifying which packages to include in a vendor directory.
-func MatchALL() []string {
- return matchAll(map[string]bool{"*": true})
-}
-
-// matchAll is the common implementation of MatchAll and MatchALL,
-// which differ only in the set of tags to apply to select files.
-func matchAll(tags map[string]bool) []string {
- local := matchPackages("all", buildList[:1])
- ld := newLoader()
- ld.tags = tags
- ld.importList(local, levelTestRecursive)
- var all []string
- for _, pkg := range ld.importmap {
- if !isStandardImportPath(pkg) {
- all = append(all, pkg)
- }
- }
- sort.Strings(all)
- return all
-}
+++ /dev/null
-// Copyright 2018 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 vgo
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-
- "cmd/go/internal/base"
- "cmd/go/internal/module"
-)
-
-var CmdVendor = &base.Command{
- UsageLine: "vendor [-v]",
- Short: "vendor dependencies of current module",
- Long: `
-Vendor resets the module's vendor directory to include all
-packages needed to build and test all packages in the module
-and their dependencies.
-
-The -v flag causes vendor to print to standard error the
-module paths of the modules processed and the import paths
-of the packages copied.
- `,
-}
-
-var vendorV = CmdVendor.Flag.Bool("v", false, "")
-
-func init() {
- CmdVendor.Run = runVendor // break init cycle
-}
-
-func runVendor(cmd *base.Command, args []string) {
- if Init(); !Enabled() {
- base.Fatalf("vgo vendor: cannot use -m outside module")
- }
- if len(args) != 0 {
- base.Fatalf("vgo vendor: vendor takes no arguments")
- }
- InitMod()
- pkgs := ImportPaths([]string{"ALL"})
-
- vdir := filepath.Join(ModRoot, "vendor")
- if err := os.RemoveAll(vdir); err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
-
- modpkgs := make(map[module.Version][]string)
- for _, pkg := range pkgs {
- m := pkgmod[pkg]
- if m == Target {
- continue
- }
- modpkgs[m] = append(modpkgs[m], pkg)
- }
-
- var buf bytes.Buffer
- for _, m := range buildList[1:] {
- if pkgs := modpkgs[m]; len(pkgs) > 0 {
- repl := ""
- if r := replaced(m); r != nil {
- repl = " => " + r.New.Path
- if r.New.Version != "" {
- repl += " " + r.New.Version
- }
- }
- fmt.Fprintf(&buf, "# %s %s%s\n", m.Path, m.Version, repl)
- if *vendorV {
- fmt.Fprintf(os.Stderr, "# %s %s%s\n", m.Path, m.Version, repl)
- }
- for _, pkg := range pkgs {
- fmt.Fprintf(&buf, "%s\n", pkg)
- if *vendorV {
- fmt.Fprintf(os.Stderr, "%s\n", pkg)
- }
- vendorPkg(vdir, pkg)
- }
- }
- }
- if err := ioutil.WriteFile(filepath.Join(vdir, "vgo.list"), buf.Bytes(), 0666); err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
-}
-
-func vendorPkg(vdir, pkg string) {
- realPath := importmap[pkg]
- if realPath != pkg && importmap[realPath] != "" {
- fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg)
- }
-
- dst := filepath.Join(vdir, pkg)
- src := pkgdir[realPath]
- if src == "" {
- fmt.Fprintf(os.Stderr, "internal error: no pkg for %s -> %s\n", pkg, realPath)
- }
- copyDir(dst, src, false)
-}
-
-func copyDir(dst, src string, recursive bool) {
- files, err := ioutil.ReadDir(src)
- if err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
- if err := os.MkdirAll(dst, 0777); err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
- for _, file := range files {
- if file.IsDir() {
- if recursive || file.Name() == "testdata" {
- copyDir(filepath.Join(dst, file.Name()), filepath.Join(src, file.Name()), true)
- }
- continue
- }
- if !file.Mode().IsRegular() {
- continue
- }
- r, err := os.Open(filepath.Join(src, file.Name()))
- if err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
- w, err := os.Create(filepath.Join(dst, file.Name()))
- if err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
- if _, err := io.Copy(w, r); err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
- r.Close()
- if err := w.Close(); err != nil {
- base.Fatalf("vgo vendor: %v", err)
- }
- }
-}
-
-// hasPathPrefix reports whether the path s begins with the
-// elements in prefix.
-func hasPathPrefix(s, prefix string) bool {
- switch {
- default:
- return false
- case len(s) == len(prefix):
- return s == prefix
- case len(s) > len(prefix):
- if prefix != "" && prefix[len(prefix)-1] == '/' {
- return strings.HasPrefix(s, prefix)
- }
- return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
- }
-}
return err
}
-var githubMessage = `vgo: 403 response from api.github.com
+var githubMessage = `go: 403 response from api.github.com
GitHub applies fairly small rate limits to unauthenticated users, and
you appear to be hitting them. To authenticate, please visit
// depMode is the action (build or install) to use when building dependencies.
// To turn package main into an executable, call b.Link instead.
func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Action {
- if mode != ModeBuild && p.Internal.Local && p.Target == "" {
- // Imported via local path. No permanent target.
+ if mode != ModeBuild && (p.Internal.Local || p.Module != nil) && p.Target == "" {
+ // Imported via local path or using modules. No permanent target.
mode = ModeBuild
}
if mode != ModeBuild && p.Name == "main" {
arguments to pass on each gccgo compiler/linker invocation.
-gcflags '[pattern=]arg list'
arguments to pass on each go tool compile invocation.
+ -getmode mode
+ module download mode to use. See 'go help modules' for more.
-installsuffix suffix
a suffix to use in the name of the package installation directory,
in order to keep output separate from default builds.
cmd.Flag.StringVar(&cfg.BuildBuildmode, "buildmode", "default", "")
cmd.Flag.Var(&load.BuildGcflags, "gcflags", "")
cmd.Flag.Var(&load.BuildGccgoflags, "gccgoflags", "")
+ cmd.Flag.StringVar(&cfg.BuildGetmode, "getmode", "", "")
cmd.Flag.StringVar(&cfg.BuildContext.InstallSuffix, "installsuffix", "", "")
cmd.Flag.Var(&load.BuildLdflags, "ldflags", "")
cmd.Flag.BoolVar(&cfg.BuildLinkshared, "linkshared", false, "")
depMode = ModeInstall
}
- pkgs = pkgsFilter(load.Packages(args))
+ pkgs = omitTestOnly(pkgsFilter(load.Packages(args)))
// Special case -o /dev/null by not writing at all.
if cfg.BuildO == os.DevNull {
func runInstall(cmd *base.Command, args []string) {
BuildInit()
- InstallPackages(args, false)
+ InstallPackages(args)
}
-func InstallPackages(args []string, forGet bool) {
+// omitTestOnly returns pkgs with test-only packages removed.
+func omitTestOnly(pkgs []*load.Package) []*load.Package {
+ var list []*load.Package
+ for _, p := range pkgs {
+ if len(p.GoFiles)+len(p.CgoFiles) == 0 && !p.Internal.CmdlinePkgLiteral {
+ // Package has no source files,
+ // perhaps due to build tags or perhaps due to only having *_test.go files.
+ // Also, it is only being processed as the result of a wildcard match
+ // like ./..., not because it was listed as a literal path on the command line.
+ // Ignore it.
+ continue
+ }
+ list = append(list, p)
+ }
+ return list
+}
+
+func InstallPackages(args []string) {
if cfg.GOBIN != "" && !filepath.IsAbs(cfg.GOBIN) {
base.Fatalf("cannot install, GOBIN must be an absolute path")
}
- pkgs := pkgsFilter(load.PackagesForBuild(args))
-
+ pkgs := omitTestOnly(pkgsFilter(load.PackagesForBuild(args)))
for _, p := range pkgs {
- if p.Target == "" && (!p.Standard || p.ImportPath != "unsafe") {
+ if p.Target == "" {
switch {
+ case p.Standard && p.ImportPath == "unsafe":
+ // unsafe is a built-in package, has no target
+ case p.Name != "main" && p.Internal.Local && p.ConflictDir == "":
+ // Non-executables outside GOPATH need not have a target:
+ // we can use the cache to hold the built package archive for use in future builds.
+ // The ones inside GOPATH should have a target (in GOPATH/pkg)
+ // or else something is wrong and worth reporting (like a ConflictDir).
+ case p.Name != "main" && p.Module != nil:
+ // Non-executables have no target (except the cache) when building with modules.
case p.Internal.GobinSubdir:
base.Errorf("go %s: cannot install cross-compiled binaries when GOBIN is set", cfg.CmdName)
case p.Internal.CmdlineFiles:
a := &Action{Mode: "go install"}
var tools []*Action
for _, p := range pkgs {
- // During 'go get', don't attempt (and fail) to install packages with only tests.
- // TODO(rsc): It's not clear why 'go get' should be different from 'go install' here. See #20760.
- if forGet && len(p.GoFiles)+len(p.CgoFiles) == 0 && len(p.TestGoFiles)+len(p.XTestGoFiles) > 0 {
- continue
- }
// If p is a tool, delay the installation until the end of the build.
// This avoids installing assemblers/compilers that are being executed
// by other steps in the build.
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/str"
- "cmd/go/internal/vgo"
)
-func init() {
- vgo.InstallHook = func(args []string) {
- CmdInstall.Run(CmdInstall, args)
- }
-}
-
// actionList returns the list of actions in the dag rooted at root
// as visited in a depth-first post-order traversal.
func actionList(root *Action) []*Action {
fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, a1.built)
}
- if p.Internal.BuildInfo != "" {
- if err := b.writeFile(objdir+"_gomod_.go", vgo.ModInfoProg(p.Internal.BuildInfo)); err != nil {
+ if p.Internal.BuildInfo != "" && cfg.ModulesEnabled {
+ if err := b.writeFile(objdir+"_gomod_.go", load.ModInfoProg(p.Internal.BuildInfo)); err != nil {
return err
}
gofiles = append(gofiles, objdir+"_gomod_.go")
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
+ "cmd/go/internal/load"
"flag"
"fmt"
"os"
cfg.BuildContext.InstallSuffix += codegenArg[1:]
}
}
+
+ switch cfg.BuildGetmode {
+ case "":
+ // ok
+ case "local", "vendor":
+ // ok but check for modules
+ if load.ModLookup == nil {
+ base.Fatalf("build flag -getmode=%s only valid when using modules", cfg.BuildGetmode)
+ }
+ default:
+ base.Fatalf("-getmode=%s not supported (can be '', 'local', or 'vendor')", cfg.BuildGetmode)
+ }
}
"cmd/go/internal/get"
"cmd/go/internal/help"
"cmd/go/internal/list"
+ "cmd/go/internal/modcmd"
+ "cmd/go/internal/modget"
+ "cmd/go/internal/modload"
"cmd/go/internal/run"
"cmd/go/internal/test"
"cmd/go/internal/tool"
get.CmdGet,
work.CmdInstall,
list.CmdList,
+ modcmd.CmdMod,
run.CmdRun,
test.CmdTest,
tool.CmdTool,
help.HelpEnvironment,
help.HelpFileType,
help.HelpGopath,
+ get.HelpGopathGet,
help.HelpImportPath,
+ modload.HelpModules,
+ modget.HelpModuleGet,
help.HelpPackages,
test.HelpTestflag,
test.HelpTestfunc,
base.Usage()
}
+ if modload.MustUseModules {
+ // If running with modules force-enabled, change get now to change help message.
+ *get.CmdGet = *modget.CmdGet
+ }
+
cfg.CmdName = args[0] // for error messages
if args[0] == "help" {
help.Help(args[1:])
os.Exit(2)
}
+ switch args[0] {
+ case "mod":
+ // Skip modload.Init (which may insist on go.mod existing)
+ // so that go mod -init has a chance to write the file.
+ case "version":
+ // Skip modload.Init so that users can report bugs against
+ // go mod -init.
+ case "vendor":
+ fmt.Fprintf(os.Stderr, "go vendor is now go mod -vendor\n")
+ os.Exit(2)
+ case "verify":
+ fmt.Fprintf(os.Stderr, "go verify is now go mod -verify\n")
+ os.Exit(2)
+ default:
+ // Run modload.Init so that each subcommand doesn't have to worry about it.
+ // Also install the module get command instead of the GOPATH go get command
+ // if modules are enabled.
+ modload.Init()
+ if modload.Enabled() {
+ // Might not have done this above, so do it now.
+ *get.CmdGet = *modget.CmdGet
+ }
+ }
+
// Set environment (GOOS, GOARCH, etc) explicitly.
// In theory all the commands we invoke should have
// the same default computation of these as we do,
--- /dev/null
+// Copyright 2018 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_test
+
+import (
+ "bytes"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "testing"
+
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/modconv"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/txtar"
+)
+
+var cmdGoDir, _ = os.Getwd()
+
+// testGoModules returns a testgoData set up for running
+// tests of Go modules. It:
+//
+// - sets $GO111MODULE=on
+// - sets $GOPROXY to the URL of a module proxy serving from ./testdata/mod
+// - creates a new temp directory with subdirectories home, gopath, and work
+// - sets $GOPATH to the new temp gopath directory
+// - sets $HOME to the new temp home directory
+// - writes work/go.mod containing "module m"
+// - chdirs to the the new temp work directory
+//
+// The caller must defer tg.cleanup().
+//
+func testGoModules(t *testing.T) *testgoData {
+ tg := testgo(t)
+ tg.setenv("GO111MODULE", "on")
+ StartProxy()
+ tg.setenv("GOPROXY", proxyURL)
+ tg.makeTempdir()
+ tg.setenv(homeEnvName(), tg.path("home")) // for build cache
+ tg.setenv("GOPATH", tg.path("gopath")) // for download cache
+ tg.tempFile("work/go.mod", "module m")
+ tg.cd(tg.path("work"))
+
+ return tg
+}
+
+// extract clears the temp work directory and then
+// extracts the txtar archive named by file into that directory.
+// The file name is interpreted relative to the cmd/go directory,
+// so it usually begins with "testdata/".
+func (tg *testgoData) extract(file string) {
+ a, err := txtar.ParseFile(filepath.Join(cmdGoDir, file))
+ if err != nil {
+ tg.t.Fatal(err)
+ }
+ tg.cd(tg.path("."))
+ tg.must(removeAll(tg.path("work")))
+ tg.must(os.MkdirAll(tg.path("work"), 0777))
+ tg.cd(tg.path("work"))
+ for _, f := range a.Files {
+ tg.tempFile(filepath.Join("work", f.Name), string(f.Data))
+ }
+}
+
+func TestModGO111MODULE(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.tempFile("gp/src/x/y/z/go.mod", "module x/y/z")
+ tg.tempFile("gp/src/x/y/z/w/w.txt", "")
+ tg.tempFile("gp/foo/go.mod", "module example.com/mod")
+ tg.tempFile("gp/foo/bar/baz/quux.txt", "")
+ tg.tempFile("gp/bar/x.txt", "")
+ tg.setenv("GOPATH", tg.path("gp"))
+
+ // In GOPATH/src with go.mod.
+ tg.cd(tg.path("gp/src/x/y/z"))
+ tg.setenv("GO111MODULE", "auto")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ""`, "expected module mode disabled")
+
+ tg.cd(tg.path("gp/src/x/y/z/w"))
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ""`, "expected module mode disabled")
+
+ tg.setenv("GO111MODULE", "off")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ""`, "expected module mode disabled")
+
+ tg.setenv("GO111MODULE", "on")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ".*z[/\\]go.mod"`, "expected module mode enabled")
+
+ // In GOPATH/src without go.mod.
+ tg.cd(tg.path("gp/src/x/y"))
+ tg.setenv("GO111MODULE", "auto")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ""`, "expected module mode disabled")
+
+ tg.setenv("GO111MODULE", "off")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ""`, "expected module mode disabled")
+
+ tg.setenv("GO111MODULE", "on")
+ tg.runFail("env", "-json")
+ tg.grepStderr(`cannot find main module root`, "expected module mode failure")
+
+ // Outside GOPATH/src with go.mod.
+ tg.cd(tg.path("gp/foo"))
+ tg.setenv("GO111MODULE", "auto")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ".*foo[/\\]go.mod"`, "expected module mode enabled")
+
+ tg.cd(tg.path("gp/foo/bar/baz"))
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ".*foo[/\\]go.mod"`, "expected module mode enabled")
+
+ tg.setenv("GO111MODULE", "off")
+ tg.run("env", "-json")
+ tg.grepStdout(`"GOMOD": ""`, "expected module mode disabled")
+}
+
+func TestModVersionsInGOPATHMode(t *testing.T) {
+ tg := testgo(t)
+ tg.setenv("GO111MODULE", "off") // GOPATH mode
+ defer tg.cleanup()
+ tg.makeTempdir()
+
+ tg.runFail("get", "rsc.io/quote@v1.5.1")
+ tg.grepStderr(`go: cannot use path@version syntax in GOPATH mode`, "expected path@version error")
+
+ tg.runFail("build", "rsc.io/quote@v1.5.1")
+ tg.grepStderr(`can't load package:.* cannot use path@version syntax in GOPATH mode`, "expected path@version error")
+
+ tg.setenv("GO111MODULE", "on") // GOPATH mode
+ tg.tempFile("x/go.mod", "module x")
+ tg.cd(tg.path("x"))
+ tg.runFail("build", "rsc.io/quote@v1.5.1")
+ tg.grepStderr(`can't load package:.* can only use path@version syntax with 'go get'`, "expected path@version error")
+}
+
+func TestModFindModuleRoot(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x/Godeps"), 0777))
+ tg.must(os.MkdirAll(tg.path("x/vendor"), 0777))
+ tg.must(os.MkdirAll(tg.path("x/y/z"), 0777))
+ tg.must(os.MkdirAll(tg.path("x/.git"), 0777))
+ var files []string
+ for file := range modconv.Converters {
+ files = append(files, file)
+ }
+ files = append(files, "go.mod")
+ files = append(files, ".git/config")
+ sort.Strings(files)
+
+ for file := range modconv.Converters {
+ tg.must(ioutil.WriteFile(tg.path("x/"+file), []byte{}, 0666))
+ root, file1 := modload.FindModuleRoot(tg.path("x/y/z"), tg.path("."), true)
+ if root != tg.path("x") || file1 != file {
+ t.Errorf("%s: findModuleRoot = %q, %q, want %q, %q", file, root, file1, tg.path("x"), file)
+ }
+ tg.must(os.Remove(tg.path("x/" + file)))
+ }
+}
+
+func TestModFindModulePath(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte("package x // import \"x\"\n"), 0666))
+ path, err := modload.FindModulePath(tg.path("x"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if path != "x" {
+ t.Fatalf("FindModulePath = %q, want %q", path, "x")
+ }
+
+ // Windows line-ending.
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte("package x // import \"x\"\r\n"), 0666))
+ path, err = modload.FindModulePath(tg.path("x"))
+ if path != "x" || err != nil {
+ t.Fatalf("FindModulePath = %q, %v, want %q, nil", path, err, "x")
+ }
+
+ // Explicit setting in Godeps.json takes priority over implicit setting from GOPATH location.
+ tg.tempFile("gp/src/example.com/x/y/z/z.go", "package z")
+ gopath := cfg.BuildContext.GOPATH
+ defer func() {
+ cfg.BuildContext.GOPATH = gopath
+ }()
+ cfg.BuildContext.GOPATH = tg.path("gp")
+ path, err = modload.FindModulePath(tg.path("gp/src/example.com/x/y/z"))
+ if path != "example.com/x/y/z" || err != nil {
+ t.Fatalf("FindModulePath = %q, %v, want %q, nil", path, err, "example.com/x/y/z")
+ }
+
+ tg.tempFile("gp/src/example.com/x/y/z/Godeps/Godeps.json", `
+ {"ImportPath": "unexpected.com/z"}
+ `)
+ path, err = modload.FindModulePath(tg.path("gp/src/example.com/x/y/z"))
+ if path != "unexpected.com/z" || err != nil {
+ t.Fatalf("FindModulePath = %q, %v, want %q, nil", path, err, "unexpected.com/z")
+ }
+}
+
+func TestModImportModFails(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv("GO111MODULE", "off")
+ tg.tempFile("gopath/src/mod/foo/foo.go", "package foo")
+ tg.runFail("list", "mod/foo")
+ tg.grepStderr(`disallowed import path`, "expected disallowed because of module cache")
+}
+
+func TestModEdit(t *testing.T) {
+ // Test that local replacements work
+ // and that they can use a dummy name
+ // that isn't resolvable and need not even
+ // include a dot. See golang.org/issue/24100.
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.cd(tg.path("."))
+ tg.must(os.MkdirAll(tg.path("w"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x.go"), []byte("package x\n"), 0666))
+ tg.must(ioutil.WriteFile(tg.path("w/w.go"), []byte("package w\n"), 0666))
+
+ mustHaveGoMod := func(text string) {
+ t.Helper()
+ data, err := ioutil.ReadFile(tg.path("go.mod"))
+ tg.must(err)
+ if string(data) != text {
+ t.Fatalf("go.mod mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", string(data), text)
+ }
+ }
+
+ tg.runFail("mod", "-init")
+ tg.grepStderr(`cannot determine module path`, "")
+ _, err := os.Stat(tg.path("go.mod"))
+ if err == nil {
+ t.Fatalf("failed go mod -init created go.mod")
+ }
+
+ tg.run("mod", "-init", "-module", "x.x/y/z")
+ tg.grepStderr("creating new go.mod: module x.x/y/z", "")
+ mustHaveGoMod(`module x.x/y/z
+`)
+
+ tg.runFail("mod", "-init")
+ mustHaveGoMod(`module x.x/y/z
+`)
+
+ tg.run("mod",
+ "-droprequire=x.1",
+ "-require=x.1@v1.0.0",
+ "-require=x.2@v1.1.0",
+ "-droprequire=x.2",
+ "-exclude=x.1 @ v1.2.0",
+ "-exclude=x.1@v1.2.1",
+ "-replace=x.1@v1.3.0=>y.1@v1.4.0",
+ "-replace=x.1@v1.4.0 => ../z",
+ )
+ mustHaveGoMod(`module x.x/y/z
+
+require x.1 v1.0.0
+
+exclude (
+ x.1 v1.2.0
+ x.1 v1.2.1
+)
+
+replace (
+ x.1 v1.3.0 => y.1 v1.4.0
+ x.1 v1.4.0 => ../z
+)
+`)
+
+ tg.run("mod",
+ "-droprequire=x.1",
+ "-dropexclude=x.1@v1.2.1",
+ "-dropreplace=x.1@v1.3.0",
+ "-require=x.3@v1.99.0",
+ )
+ mustHaveGoMod(`module x.x/y/z
+
+exclude x.1 v1.2.0
+
+replace x.1 v1.4.0 => ../z
+
+require x.3 v1.99.0
+`)
+
+ tg.run("mod", "-json")
+ want := `{
+ "Module": {
+ "Path": "x.x/y/z"
+ },
+ "Require": [
+ {
+ "Path": "x.3",
+ "Version": "v1.99.0"
+ }
+ ],
+ "Exclude": [
+ {
+ "Path": "x.1",
+ "Version": "v1.2.0"
+ }
+ ],
+ "Replace": [
+ {
+ "Old": {
+ "Path": "x.1",
+ "Version": "v1.4.0"
+ },
+ "New": {
+ "Path": "../z"
+ }
+ }
+ ]
+}
+`
+ if have := tg.getStdout(); have != want {
+ t.Fatalf("go mod -json mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", have, want)
+ }
+
+ tg.run("mod",
+ "-replace=x.1@v1.3.0=>y.1/v2@v2.3.5",
+ "-replace=x.1@v1.4.0=>y.1/v2@v2.3.5",
+ )
+ mustHaveGoMod(`module x.x/y/z
+
+exclude x.1 v1.2.0
+
+replace (
+ x.1 v1.3.0 => y.1/v2 v2.3.5
+ x.1 v1.4.0 => y.1/v2 v2.3.5
+)
+
+require x.3 v1.99.0
+`)
+ tg.run("mod",
+ "-replace=x.1=>y.1/v2@v2.3.6",
+ )
+ mustHaveGoMod(`module x.x/y/z
+
+exclude x.1 v1.2.0
+
+replace x.1 => y.1/v2 v2.3.6
+
+require x.3 v1.99.0
+`)
+
+ tg.run("mod", "-packages")
+ want = `x.x/y/z
+x.x/y/z/w
+`
+ if have := tg.getStdout(); have != want {
+ t.Fatalf("go mod -packages mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", have, want)
+ }
+
+ data, err := ioutil.ReadFile(tg.path("go.mod"))
+ tg.must(err)
+ data = bytes.Replace(data, []byte("\n"), []byte("\r\n"), -1)
+ data = append(data, " \n"...)
+ tg.must(ioutil.WriteFile(tg.path("go.mod"), data, 0666))
+
+ tg.run("mod", "-fmt")
+ mustHaveGoMod(`module x.x/y/z
+
+exclude x.1 v1.2.0
+
+replace x.1 => y.1/v2 v2.3.6
+
+require x.3 v1.99.0
+`)
+}
+
+func TestModLocalModule(t *testing.T) {
+ // Test that local replacements work
+ // and that they can use a dummy name
+ // that isn't resolvable and need not even
+ // include a dot. See golang.org/issue/24100.
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x/y"), 0777))
+ tg.must(os.MkdirAll(tg.path("x/z"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/y/go.mod"), []byte(`
+ module x/y
+ require zz v1.0.0
+ replace zz v1.0.0 => ../z
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/y/y.go"), []byte(`package y; import _ "zz"`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/z/go.mod"), []byte(`
+ module x/z
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/z/z.go"), []byte(`package z`), 0666))
+ tg.cd(tg.path("x/y"))
+ tg.run("build")
+}
+
+func TestModTags(t *testing.T) {
+ // Test that build tags are used. See golang.org/issue/24053.
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`// +build tag1
+
+ package y
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/y.go"), []byte(`// +build tag2
+
+ package y
+ `), 0666))
+ tg.cd(tg.path("x"))
+
+ tg.runFail("list", "-f={{.GoFiles}}")
+ tg.grepStderr("build constraints exclude all Go files", "no Go source files without tags")
+
+ tg.run("list", "-f={{.GoFiles}}", "-tags=tag1")
+ tg.grepStdout(`\[x.go\]`, "Go source files for tag1")
+
+ tg.run("list", "-f={{.GoFiles}}", "-tags", "tag2")
+ tg.grepStdout(`\[y.go\]`, "Go source files for tag2")
+
+ tg.run("list", "-f={{.GoFiles}}", "-tags", "tag1 tag2")
+ tg.grepStdout(`\[x.go y.go\]`, "Go source files for tag1 and tag2")
+}
+
+func TestModFSPatterns(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x/vendor/v"), 0777))
+ tg.must(os.MkdirAll(tg.path("x/y/z/w"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module m
+ `), 0666))
+
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/vendor/v/v.go"), []byte(`package v; import "golang.org/x/crypto"`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/vendor/v.go"), []byte(`package main`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/y/y.go"), []byte(`package y`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/y/z/go.mod"), []byte(`syntax error`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/y/z/z.go"), []byte(`package z`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/y/z/w/w.go"), []byte(`package w`), 0666))
+
+ tg.cd(tg.path("x"))
+ for _, pattern := range []string{"all", "m/...", "./..."} {
+ tg.run("list", pattern)
+ tg.grepStdout(`^m$`, "expected m")
+ tg.grepStdout(`^m/vendor$`, "must see package named vendor")
+ tg.grepStdoutNot(`vendor/`, "must not see vendored packages")
+ tg.grepStdout(`^m/y$`, "expected m/y")
+ tg.grepStdoutNot(`^m/y/z`, "should ignore submodule m/y/z...")
+ }
+}
+
+func TestModGetVersions(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv(homeEnvName(), tg.path("home"))
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.cd(tg.path("x"))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x`), 0666))
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.1.0
+ `), 0666))
+ tg.run("get", "rsc.io/quote@v2.0.0")
+ tg.run("list", "-m", "all")
+ tg.grepStdout("rsc.io/quote.*v0.0.0-20180709153244-fd906ed3b100", "did downgrade to v0.0.0-*")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.2.0
+ `), 0666))
+ tg.run("get", "rsc.io/quote@v1.1.0")
+ tg.run("list", "-m", "-u", "all")
+ tg.grepStdout(`rsc.io/quote v1.1.0`, "did downgrade to v1.1.0")
+ tg.grepStdout(`rsc.io/quote v1.1.0 \[v1`, "did show upgrade to v1.2.0 or later")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.1.0
+ `), 0666))
+ tg.run("get", "rsc.io/quote@v1.2.0")
+ tg.run("list", "-m", "all")
+ tg.grepStdout("rsc.io/quote.*v1.2.0", "did upgrade to v1.2.0")
+
+ // @14c0d48ead0c should resolve,
+ // and also there should be no build error about not having Go files in the root.
+ tg.run("get", "golang.org/x/text@14c0d48ead0c")
+
+ // @14c0d48ead0c should resolve.
+ // Now there should be no build at all.
+ tg.run("get", "-m", "golang.org/x/text@14c0d48ead0c")
+
+ // language@7f39a6fea4fe9364 should not resolve with -m,
+ // because .../language is not a module path.
+ tg.runFail("get", "-m", "golang.org/x/text/language@14c0d48ead0c")
+
+ // language@7f39a6fea4fe9364 should resolve without -m.
+ // Because of -d, there should be no build at all.
+ tg.run("get", "-d", "-x", "golang.org/x/text/language@14c0d48ead0c")
+ tg.grepStderrNot(`compile|cp .*language\.a$`, "should not see compile steps")
+
+ // Dropping -d, we should see a build now.
+ tg.run("get", "-x", "golang.org/x/text/language@14c0d48ead0c")
+ tg.grepStderr(`compile|cp .*language\.a$`, "should see compile steps")
+
+ // Even with -d, we should see an error for unknown packages.
+ tg.runFail("get", "-x", "golang.org/x/text/foo@14c0d48ead0c")
+}
+
+func TestModGetUpgrade(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv(homeEnvName(), tg.path("home"))
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.cd(tg.path("x"))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x; import _ "rsc.io/quote"`), 0666))
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.5.1
+ `), 0666))
+
+ tg.run("get", "-x", "-u")
+ tg.run("list", "-m", "-f={{.Path}} {{.Version}}{{if .Indirect}} // indirect{{end}}", "all")
+ tg.grepStdout(`quote v1.5.2$`, "should have upgraded only to v1.5.2")
+ tg.grepStdout(`x/text [v0-9a-f.\-]+ // indirect`, "should list golang.org/x/text as indirect")
+
+ var gomod string
+ readGoMod := func() {
+ data, err := ioutil.ReadFile(tg.path("x/go.mod"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ gomod = string(data)
+ }
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote v1.5.2\n") {
+ t.Fatalf("expected rsc.io/quote direct requirement:\n%s", gomod)
+ }
+ if !regexp.MustCompile(`(?m)golang.org/x/text.* // indirect`).MatchString(gomod) {
+ t.Fatalf("expected golang.org/x/text indirect requirement:\n%s", gomod)
+ }
+
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x; import _ "golang.org/x/text"`), 0666))
+ tg.run("list") // rescans directory
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote v1.5.2\n") {
+ t.Fatalf("expected rsc.io/quote direct requirement:\n%s", gomod)
+ }
+ if !regexp.MustCompile(`(?m)golang.org/x/text[^/]+\n`).MatchString(gomod) {
+ t.Fatalf("expected golang.org/x/text DIRECT requirement:\n%s", gomod)
+ }
+
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x; import _ "rsc.io/quote"`), 0666))
+ tg.run("mod", "-sync") // rescans everything, can put // indirect marks back
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote v1.5.2\n") {
+ t.Fatalf("expected rsc.io/quote direct requirement:\n%s", gomod)
+ }
+ if !regexp.MustCompile(`(?m)golang.org/x/text.* // indirect\n`).MatchString(gomod) {
+ t.Fatalf("expected golang.org/x/text indirect requirement:\n%s", gomod)
+ }
+
+ tg.run("get", "rsc.io/quote@v0.0.0-20180214005840-23179ee8a569") // should record as (time-corrected) pseudo-version
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote v0.0.0-20180214005840-23179ee8a569\n") {
+ t.Fatalf("expected rsc.io/quote v0.0.0-20180214005840-23179ee8a569 (not v1.5.1)\n%s", gomod)
+ }
+
+ tg.run("get", "rsc.io/quote@23179ee") // should record as v1.5.1
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote v1.5.1\n") {
+ t.Fatalf("expected rsc.io/quote v1.5.1 (not 23179ee)\n%s", gomod)
+ }
+
+ tg.run("mod", "-require", "rsc.io/quote@23179ee") // should record as 23179ee
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote 23179ee\n") {
+ t.Fatalf("expected rsc.io/quote 23179ee\n%s", gomod)
+ }
+
+ tg.run("mod", "-fix") // fixup in any future go command should find v1.5.1 again
+ readGoMod()
+ if !strings.Contains(gomod, "rsc.io/quote v1.5.1\n") {
+ t.Fatalf("expected rsc.io/quote v1.5.1\n%s", gomod)
+ }
+
+ tg.run("get", "-m", "rsc.io/quote@dd9747d")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`quote v0.0.0-20180628003336-dd9747d19b04$`, "should have moved to pseudo-commit")
+
+ tg.run("get", "-m", "-u")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`quote v0.0.0-20180628003336-dd9747d19b04$`, "should have stayed on pseudo-commit")
+
+ tg.run("get", "-m", "rsc.io/quote@e7a685a342")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`quote v0.0.0-20180214005133-e7a685a342c0$`, "should have moved to new pseudo-commit")
+
+ tg.run("get", "-m", "-u")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`quote v1.5.2$`, "should have moved off pseudo-commit")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ `), 0666))
+ tg.run("list")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`quote v1.5.2$`, "should have added quote v1.5.2")
+ tg.grepStdoutNot(`v1.5.3-pre1`, "should not mention v1.5.3-pre1")
+
+ tg.run("list", "-m", "-versions", "rsc.io/quote")
+ want := "rsc.io/quote v1.0.0 v1.1.0 v1.2.0 v1.2.1 v1.3.0 v1.4.0 v1.5.0 v1.5.1 v1.5.2 v1.5.3-pre1\n"
+ if tg.getStdout() != want {
+ t.Errorf("go list versions:\nhave:\n%s\nwant:\n%s", tg.getStdout(), want)
+ }
+
+ tg.run("list", "-m", "rsc.io/quote@>v1.5.2")
+ tg.grepStdout(`v1.5.3-pre1`, "expected to find v1.5.3-pre1")
+ tg.run("list", "-m", "rsc.io/quote@<v1.5.4")
+ tg.grepStdout(`v1.5.2$`, "expected to find v1.5.2 (NOT v1.5.3-pre1)")
+
+ tg.runFail("list", "-m", "rsc.io/quote@>v1.5.3")
+ tg.grepStderr(`go list -m rsc.io/quote: no matching versions for query ">v1.5.3"`, "expected no matching version")
+
+ tg.run("list", "-m", "-e", "-f={{.Error.Err}}", "rsc.io/quote@>v1.5.3")
+ tg.grepStdout(`no matching versions for query ">v1.5.3"`, "expected no matching version")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.4.0
+ `), 0666))
+
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`rsc.io/sampler v1.0.0`, "expected sampler v1.0.0")
+
+ tg.run("get", "-m", "-u=patch", "rsc.io/quote")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`rsc.io/quote v1.5.2`, "expected quote v1.5.2") // rsc.io/quote gets implicit @latest (not -u=patch)
+ tg.grepStdout(`rsc.io/sampler v1.3.1`, "expected sampler v1.3.1") // even though v1.5.2 requires v1.3.0
+ tg.grepStdout(`golang.org/x/text v0.0.0-`, "expected x/text pseudo-version") // can't jump from v0.0.0- to v0.3.0
+
+ tg.run("get", "-m", "-u=patch", "rsc.io/quote@v1.2.0")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`rsc.io/quote v1.2.0`, "expected quote v1.2.0") // not v1.2.1: -u=patch applies to deps of args, not args
+ tg.grepStdout(`rsc.io/sampler v1.3.1`, "expected sampler line to stay") // even though v1.2.0 does not require sampler?
+
+ tg.run("get", "-m", "-u=patch")
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`rsc.io/quote v1.2.1`, "expected quote v1.2.1") // -u=patch with no args applies to deps of main module
+ tg.grepStdout(`rsc.io/sampler v1.3.1`, "expected sampler line to stay")
+ tg.grepStdout(`golang.org/x/text v0.0.0-`, "expected x/text pseudo-version") // even though x/text v0.3.0 is tagged
+
+ tg.run("get", "-m", "rsc.io/quote@v1.5.1")
+ tg.run("mod", "-vendor")
+ tg.setenv("GOPATH", tg.path("empty"))
+ tg.setenv("GOPROXY", "file:///nonexist")
+
+ tg.run("list", "-getmode=vendor", "all")
+ tg.run("list", "-getmode=vendor", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}", "all")
+ tg.grepStdout(`rsc.io/quote v1.5.1 .*vendor[\\/]rsc.io[\\/]quote`, "expected vendored rsc.io/quote")
+ tg.grepStdout(`golang.org/x/text v0.0.0.* .*vendor[\\/]golang.org[\\/]x[\\/]text`, "expected vendored golang.org/x/text")
+
+ tg.runFail("list", "-getmode=vendor", "-m", "rsc.io/quote@latest")
+ tg.grepStderr(`module lookup disabled by -getmode=vendor`, "expected disabled")
+ tg.runFail("get", "-getmode=vendor", "-u")
+ tg.grepStderr(`go get: disabled by -getmode=vendor`, "expected disabled")
+}
+
+func TestModBadDomain(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.tempFile("work/x.go", `
+ package x
+
+ import _ "appengine"
+ import _ "nonexistent.rsc.io" // domain does not exist
+ `)
+
+ tg.runFail("get", "appengine")
+ tg.grepStderr(`cannot find module providing package appengine`, "expected module error ")
+ tg.runFail("get", "x/y.z")
+ tg.grepStderr(`cannot find module providing package x/y.z`, "expected module error")
+
+ tg.runFail("build")
+ tg.grepStderrNot("unknown module appengine: not a domain name", "expected nothing about appengine")
+ tg.grepStderr("cannot find module providing package nonexistent.rsc.io", "expected error for nonexistent.rsc.io")
+}
+
+func TestModSync(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ write := func(name, text string) {
+ name = tg.path(name)
+ dir := filepath.Dir(name)
+ tg.must(os.MkdirAll(dir, 0777))
+ tg.must(ioutil.WriteFile(name, []byte(text), 0666))
+ }
+
+ write("m/go.mod", `
+module m
+
+require (
+ x.1 v1.0.0
+ y.1 v1.0.0
+ w.1 v1.2.0
+)
+
+replace x.1 v1.0.0 => ../x
+replace y.1 v1.0.0 => ../y
+replace z.1 v1.1.0 => ../z
+replace z.1 v1.2.0 => ../z
+replace w.1 => ../w
+`)
+ write("m/m.go", `
+package m
+
+import _ "x.1"
+import _ "z.1/sub"
+`)
+
+ write("w/go.mod", `
+module w
+`)
+ write("w/w.go", `
+package w
+`)
+
+ write("x/go.mod", `
+module x
+require w.1 v1.1.0
+require z.1 v1.1.0
+`)
+ write("x/x.go", `
+package x
+
+import _ "w.1"
+`)
+
+ write("y/go.mod", `
+module y
+require z.1 v1.2.0
+`)
+
+ write("z/go.mod", `
+module z
+`)
+ write("z/sub/sub.go", `
+package sub
+`)
+
+ tg.cd(tg.path("m"))
+ tg.run("mod", "-sync", "-v")
+ tg.grepStderr(`^unused y.1`, "need y.1 unused")
+ tg.grepStderrNot(`^unused [^y]`, "only y.1 should be unused")
+
+ tg.run("list", "-m", "all")
+ tg.grepStdoutNot(`^y.1`, "y should be gone")
+ tg.grepStdout(`^w.1\s+v1.2.0`, "need w.1 to stay at v1.2.0")
+ tg.grepStdout(`^z.1\s+v1.2.0`, "need z.1 to stay at v1.2.0 even though y is gone")
+}
+
+func TestModVendor(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.extract("testdata/vendormod.txt")
+
+ tg.run("list", "-m", "all")
+ tg.grepStdout(`^x`, "expected to see module x")
+ tg.grepStdout(`=> ./x`, "expected to see replacement for module x")
+ tg.grepStdout(`^w`, "expected to see module w")
+
+ if !testing.Short() {
+ tg.run("build")
+ tg.runFail("build", "-getmode=vendor")
+ }
+
+ tg.run("list", "-f={{.Dir}}", "x")
+ tg.grepStdout(`work[/\\]x$`, "expected x in work/x")
+
+ mustHaveVendor := func(name string) {
+ t.Helper()
+ tg.mustExist(filepath.Join(tg.path("work/vendor"), name))
+ }
+ mustNotHaveVendor := func(name string) {
+ t.Helper()
+ tg.mustNotExist(filepath.Join(tg.path("work/vendor"), name))
+ }
+
+ tg.run("mod", "-vendor", "-v")
+ tg.grepStderr(`^# x v1.0.0 => ./x`, "expected to see module x with replacement")
+ tg.grepStderr(`^x`, "expected to see package x")
+ tg.grepStderr(`^# y v1.0.0 => ./y`, "expected to see module y with replacement")
+ tg.grepStderr(`^y`, "expected to see package y")
+ tg.grepStderr(`^# z v1.0.0 => ./z`, "expected to see module z with replacement")
+ tg.grepStderr(`^z`, "expected to see package z")
+ tg.grepStderrNot(`w`, "expected NOT to see unused module w")
+
+ tg.run("list", "-f={{.Dir}}", "x")
+ tg.grepStdout(`work[/\\]x$`, "expected x in work/x")
+
+ tg.run("list", "-f={{.Dir}}", "-m", "x")
+ tg.grepStdout(`work[/\\]x$`, "expected x in work/x")
+
+ tg.run("list", "-getmode=vendor", "-f={{.Dir}}", "x")
+ tg.grepStdout(`work[/\\]vendor[/\\]x$`, "expected x in work/vendor/x in -get=vendor mode")
+
+ tg.run("list", "-getmode=vendor", "-f={{.Dir}}", "-m", "x")
+ tg.grepStdout(`work[/\\]vendor[/\\]x$`, "expected x in work/vendor/x in -get=vendor mode")
+
+ tg.run("list", "-f={{.Dir}}", "w")
+ tg.grepStdout(`work[/\\]w$`, "expected w in work/w")
+ tg.runFail("list", "-getmode=vendor", "-f={{.Dir}}", "w")
+ tg.grepStderr(`work[/\\]vendor[/\\]w`, "want error about work/vendor/w not existing")
+
+ tg.run("list", "-getmode=local", "-f={{.Dir}}", "w")
+ tg.grepStdout(`work[/\\]w`, "expected w in work/w")
+
+ tg.runFail("list", "-getmode=local", "-f={{.Dir}}", "newpkg")
+ tg.grepStderr(`disabled by -getmode=local`, "expected -getmode=local to avoid network")
+
+ mustNotHaveVendor("x/testdata")
+ mustNotHaveVendor("a/foo/bar/b/main_test.go")
+
+ mustHaveVendor("a/foo/AUTHORS.txt")
+ mustHaveVendor("a/foo/CONTRIBUTORS")
+ mustHaveVendor("a/foo/LICENSE")
+ mustHaveVendor("a/foo/PATENTS")
+ mustHaveVendor("a/foo/COPYING")
+ mustHaveVendor("a/foo/COPYLEFT")
+ mustHaveVendor("x/NOTICE!")
+ mustHaveVendor("mysite/myname/mypkg/LICENSE.txt")
+
+ mustNotHaveVendor("a/foo/licensed-to-kill")
+ mustNotHaveVendor("w")
+ mustNotHaveVendor("w/LICENSE") // w wasn't copied at all
+ mustNotHaveVendor("x/x2")
+ mustNotHaveVendor("x/x2/LICENSE") // x/x2 wasn't copied at all
+
+ if !testing.Short() {
+ tg.run("build")
+ tg.run("build", "-getmode=vendor")
+ tg.run("test", "-getmode=vendor", ".", "./subdir")
+ tg.run("test", "-getmode=vendor", "./...")
+ }
+}
+
+func TestModList(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv(homeEnvName(), tg.path("."))
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`
+ package x
+ import _ "rsc.io/quote"
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.5.1
+ replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1
+ `), 0666))
+ tg.cd(tg.path("x"))
+
+ tg.run("list", "-m", "-f={{.Main}}: {{.Dir}}")
+ tg.grepStdout(`^true: `, "expected main module to have Main=true")
+ tg.grepStdout(regexp.QuoteMeta(tg.path("x")), "expected Dir of main module to be present")
+
+ tg.run("list", "-m", "-f={{.Main}}: {{.Dir}}", "rsc.io/quote")
+ tg.grepStdout(`^false: `, "expected non-main module to have Main=false")
+ tg.grepStdoutNot(`quote@`, "should not have local copy of code")
+
+ tg.run("list", "-f={{.Dir}}", "rsc.io/quote") // downloads code to load package
+ tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.5.1`, "expected cached copy of code")
+ dir := strings.TrimSpace(tg.getStdout())
+ info, err := os.Stat(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if info.Mode()&0222 != 0 {
+ t.Fatalf("%s should be unwritable", dir)
+ }
+
+ tg.run("list", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}{{with .Replace}} => {{.Version}} {{.Dir}}{{end}}", "all")
+ tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.5.1`, "expected cached copy of code")
+ tg.grepStdout(`v1.3.0 .*mod[\\/]rsc.io[\\/]sampler@v1.3.1 => v1.3.1 .*@v1.3.1`, "expected v1.3.1 replacement")
+
+ // check that list std works; also check that rsc.io/quote/buggy is a listable package
+ tg.run("list", "std", "rsc.io/quote/buggy")
+ tg.grepStdout("^math/big", "expected standard library")
+
+ tg.run("list", "-m", "-e", "-f={{.Path}} {{.Error.Err}}", "nonexist", "rsc.io/quote/buggy")
+ tg.grepStdout(`^nonexist module "nonexist" is not a known dependency`, "expected error via template")
+ tg.grepStdout(`^rsc.io/quote/buggy module "rsc.io/quote/buggy" is not a known dependency`, "expected error via template")
+
+ tg.runFail("list", "-m", "nonexist", "rsc.io/quote/buggy")
+ tg.grepStderr(`go list -m nonexist: module "nonexist" is not a known dependency`, "expected error on stderr")
+ tg.grepStderr(`go list -m rsc.io/quote/buggy: module "rsc.io/quote/buggy" is not a known dependency`, "expected error on stderr")
+
+ // Check that module loader does not interfere with list -e (golang.org/issue/24149).
+ tg.run("list", "-e", "-f={{.ImportPath}} {{.Error.Err}}", "database")
+ tg.grepStdout(`^database no Go files in `, "expected error via template")
+ tg.runFail("list", "database")
+ tg.grepStderr(`package database: no Go files`, "expected error on stderr")
+
+}
+
+func TestModInitLegacy(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv(homeEnvName(), tg.path("."))
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`
+ package x
+ `), 0666))
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/Gopkg.lock"), []byte(`
+[[projects]]
+ name = "rsc.io/sampler"
+ version = "v1.0.0"
+ `), 0666))
+
+ tg.cd(tg.path("x"))
+ tg.run("build", "-v")
+ tg.grepStderr("copying requirements from .*Gopkg.lock", "did not copy Gopkg.lock")
+ tg.run("list", "-m", "all")
+ tg.grepStderrNot("copying requirements from .*Gopkg.lock", "should not copy Gopkg.lock again")
+ tg.grepStdout("rsc.io/sampler.*v1.0.0", "did not copy Gopkg.lock")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/Gopkg.lock"), []byte(`
+ `), 0666))
+
+ tg.run("list")
+ tg.grepStderr("copying requirements from .*Gopkg.lock", "did not copy Gopkg.lock")
+ tg.run("list")
+ tg.grepStderrNot("copying requirements from .*Gopkg.lock", "should not copy Gopkg.lock again")
+}
+
+func TestModQueryExcluded(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x; import _ "github.com/gorilla/mux"`), 0666))
+ gomod := []byte(`
+ module x
+
+ exclude rsc.io/quote v1.5.0
+ `)
+
+ tg.setenv(homeEnvName(), tg.path("home"))
+ tg.cd(tg.path("x"))
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), gomod, 0666))
+ tg.runFail("get", "rsc.io/quote@v1.5.0")
+ tg.grepStderr("rsc.io/quote@v1.5.0 excluded", "print version excluded")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), gomod, 0666))
+ tg.run("get", "rsc.io/quote@v1.5.1")
+ tg.grepStderr("rsc.io/quote v1.5.1", "find version 1.5.1")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), gomod, 0666))
+ tg.run("get", "rsc.io/quote@>=v1.5")
+ tg.run("list", "-m", "...quote")
+ tg.grepStdout("rsc.io/quote v1.5.[1-9]", "expected version 1.5.1 or later")
+}
+
+func TestModRequireExcluded(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x; import _ "rsc.io/quote"`), 0666))
+
+ tg.setenv(homeEnvName(), tg.path("home"))
+ tg.cd(tg.path("x"))
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ exclude rsc.io/sampler latest
+ require rsc.io/sampler latest
+ `), 0666))
+ tg.runFail("build")
+ tg.grepStderr("no newer version available", "only available version excluded")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ exclude rsc.io/quote v1.5.1
+ require rsc.io/quote v1.5.1
+ `), 0666))
+ tg.run("build")
+ tg.grepStderr("rsc.io/quote v1.5.2", "find version 1.5.2")
+
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ exclude rsc.io/quote v1.5.2
+ require rsc.io/quote v1.5.1
+ `), 0666))
+ tg.run("build")
+ tg.grepStderr("rsc.io/quote v1.5.1", "find version 1.5.1")
+}
+
+func TestModInitLegacy2(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv(homeEnvName(), tg.path("."))
+
+ // Testing that on Windows the path x/Gopkg.lock turning into x\Gopkg.lock does not confuse converter.
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/Gopkg.lock"), []byte(`
+ [[projects]]
+ name = "rsc.io/quote"
+ packages = ["."]
+ revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
+ version = "v1.4.0"`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/main.go"), []byte("package x // import \"x\"\n import _ \"github.com/pkg/errors\""), 0666))
+ tg.cd(tg.path("x"))
+ tg.run("list", "-m", "all")
+
+ // If the conversion just ignored the Gopkg.lock entirely
+ // it would choose a newer version (like v1.5.2 or maybe
+ // something even newer). Check for the older version to
+ // make sure Gopkg.lock was properly used.
+ tg.grepStdout("v1.4.0", "expected rsc.io/quote at v1.4.0")
+}
+
+func TestModVerify(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ gopath := tg.path("gp")
+ tg.setenv("GOPATH", gopath)
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require rsc.io/quote v1.1.0
+ `), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`package x; import _ "rsc.io/quote"`), 0666))
+
+ // With correct go.sum,verify succeeds but avoids download.
+ tg.must(ioutil.WriteFile(tg.path("x/go.sum"), []byte(`rsc.io/quote v1.1.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
+`), 0666))
+ tg.cd(tg.path("x"))
+ tg.run("mod", "-verify")
+ tg.mustNotExist(filepath.Join(gopath, "src/mod/cache/download/rsc.io/quote/@v/v1.1.0.zip"))
+ tg.mustNotExist(filepath.Join(gopath, "src/mod/github.com/pkg"))
+
+ // With incorrect sum, sync (which must download) fails.
+ // Even if the incorrect sum is in the old legacy go.modverify file.
+ tg.must(ioutil.WriteFile(tg.path("x/go.sum"), []byte(`
+`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/go.modverify"), []byte(`rsc.io/quote v1.1.0 h1:a3YaZoizPtXyv6ZsJ74oo2L4/bwOSTKMY7MAyo4O/1c=
+`), 0666))
+ tg.runFail("mod", "-sync") // downloads pkg/errors
+ tg.grepStderr("checksum mismatch", "must detect mismatch")
+ tg.mustNotExist(filepath.Join(gopath, "src/mod/cache/download/rsc.io/quote/@v/v1.1.0.zip"))
+ tg.mustNotExist(filepath.Join(gopath, "src/mod/github.com/pkg"))
+
+ // With corrected sum, sync works.
+ tg.must(ioutil.WriteFile(tg.path("x/go.modverify"), []byte(`rsc.io/quote v1.1.0 h1:a3YaZoizPtXyv6ZsJ74oo2L4/bwOSTKMY7MAyo4O/0c=
+`), 0666))
+ tg.run("mod", "-sync")
+ tg.mustExist(filepath.Join(gopath, "src/mod/cache/download/rsc.io/quote/@v/v1.1.0.zip"))
+ tg.mustExist(filepath.Join(gopath, "src/mod/rsc.io"))
+ tg.mustNotExist(tg.path("x/go.modverify")) // moved into go.sum
+
+ // Sync should have added sum for go.mod.
+ data, err := ioutil.ReadFile(tg.path("x/go.sum"))
+ if !strings.Contains(string(data), "\nrsc.io/quote v1.1.0/go.mod ") {
+ t.Fatalf("cannot find go.mod hash in go.sum: %v\n%s", err, data)
+ }
+
+ // Verify should work too.
+ tg.run("mod", "-verify")
+
+ // Even the most basic attempt to load the module graph should detect incorrect go.mod files.
+ tg.run("mod", "-graph") // loads module graph, is OK
+ tg.must(ioutil.WriteFile(tg.path("x/go.sum"), []byte(`rsc.io/quote v1.1.0 a3YaZoizPtXyv6ZsJ74oo2L4/bwOSTKMY7MAyo4O/1c=
+rsc.io/quote v1.1.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl1=
+`), 0666))
+ tg.runFail("mod", "-graph") // loads module graph, fails (even though sum is in old go.modverify file)
+ tg.grepStderr("go.mod: checksum mismatch", "must detect mismatch")
+
+ // go.sum should be created and updated automatically.
+ tg.must(os.Remove(tg.path("x/go.sum")))
+ tg.run("mod", "-graph")
+ tg.mustExist(tg.path("x/go.sum"))
+ data, err = ioutil.ReadFile(tg.path("x/go.sum"))
+ if !strings.Contains(string(data), " v1.1.0/go.mod ") {
+ t.Fatalf("cannot find go.mod hash in go.sum: %v\n%s", err, data)
+ }
+ if strings.Contains(string(data), " v1.1.0 ") {
+ t.Fatalf("unexpected module tree hash in go.sum: %v\n%s", err, data)
+ }
+ tg.run("mod", "-sync")
+ data, err = ioutil.ReadFile(tg.path("x/go.sum"))
+ if !strings.Contains(string(data), " v1.1.0/go.mod ") {
+ t.Fatalf("cannot find go.mod hash in go.sum: %v\n%s", err, data)
+ }
+ if !strings.Contains(string(data), " v1.1.0 ") {
+ t.Fatalf("cannot find module tree hash in go.sum: %v\n%s", err, data)
+ }
+
+ tg.must(os.Remove(filepath.Join(gopath, "src/mod/cache/download/rsc.io/quote/@v/v1.1.0.ziphash")))
+ tg.run("mod", "-sync") // ignores missing ziphash file for ordinary go.sum validation
+
+ tg.runFail("mod", "-verify") // explicit verify fails with missing ziphash
+
+ tg.run("mod", "-droprequire", "rsc.io/quote")
+ tg.run("list", "rsc.io/quote/buggy")
+ data, err = ioutil.ReadFile(tg.path("x/go.sum"))
+ if strings.Contains(string(data), "buggy") {
+ t.Fatalf("did not expect buggy in go.sum:\n%s", data)
+ }
+ if !strings.Contains(string(data), "rsc.io/quote v1.5.2/go.mod") {
+ t.Fatalf("did expect rsc.io/quote go.mod in go.sum:\n%s", data)
+ }
+
+ tg.run("mod", "-droprequire", "rsc.io/quote")
+ tg.runFail("list", "rsc.io/quote/buggy/foo")
+ data, err = ioutil.ReadFile(tg.path("x/go.sum"))
+ if strings.Contains(string(data), "buggy") {
+ t.Fatalf("did not expect buggy in go.sum:\n%s", data)
+ }
+ if !strings.Contains(string(data), "rsc.io/quote v1.5.2/go.mod") {
+ t.Fatalf("did expect rsc.io/quote go.mod in go.sum:\n%s", data)
+ }
+
+ tg.run("mod", "-droprequire", "rsc.io/quote")
+ tg.runFail("list", "rsc.io/quote/morebuggy")
+ if strings.Contains(string(data), "morebuggy") {
+ t.Fatalf("did not expect morebuggy in go.sum:\n%s", data)
+ }
+ if !strings.Contains(string(data), "rsc.io/quote v1.5.2/go.mod") {
+ t.Fatalf("did expect rsc.io/quote go.mod in go.sum:\n%s", data)
+ }
+}
+
+func TestModFileProxy(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv("GOPATH", tg.path("gp1"))
+
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/main.go"), []byte(`package x; import _ "rsc.io/quote"`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`module x
+ require rsc.io/quote v1.5.1`), 0666))
+ tg.cd(tg.path("x"))
+ tg.run("list", "all")
+ tg.run("list", "-getmode=local", "all")
+ tg.mustExist(tg.path("gp1/src/mod/cache/download/rsc.io/quote/@v/list"))
+
+ // @v/list should contain version list.
+ data, err := ioutil.ReadFile(tg.path("gp1/src/mod/cache/download/rsc.io/quote/@v/list"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !strings.Contains(string(data), "v1.5.1\n") {
+ t.Fatalf("cannot find v1.5.1 in @v/list:\n%s", data)
+ }
+
+ tg.setenv("GOPROXY", "file:///nonexist")
+ tg.run("list", "-getmode=local", "all")
+
+ tg.setenv("GOPATH", tg.path("gp2"))
+ tg.runFail("list", "-getmode=local", "all")
+ tg.runFail("list", "all") // because GOPROXY is bogus
+
+ tg.setenv("GOPROXY", "file://"+filepath.ToSlash(tg.path("gp1/src/mod/cache/download")))
+ tg.runFail("list", "-getmode=local", "all")
+ tg.run("list", "all")
+ tg.mustExist(tg.path("gp2/src/mod/cache/download/rsc.io/quote/@v/list"))
+}
+
+func TestModVendorNoDeps(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/main.go"), []byte(`package x`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`module x`), 0666))
+ tg.cd(tg.path("x"))
+ tg.run("mod", "-vendor")
+ tg.grepStderr("go: no dependencies to vendor", "print vendor info")
+}
+
+func TestModVersionNoModule(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.cd(tg.path("."))
+ tg.run("version")
+}
+
+func TestModImportDomainRoot(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv("GOPATH", tg.path("."))
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/main.go"), []byte(`
+ package x
+ import _ "example.com"`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte("module x"), 0666))
+ tg.cd(tg.path("x"))
+ tg.run("build")
+}
+
+func TestModSyncPrintJson(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ tg.setenv("GOPATH", tg.path("."))
+ tg.must(os.MkdirAll(tg.path("x"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x/main.go"), []byte(`
+ package x
+ import "rsc.io/quote"
+ func main() {
+ _ = mux.NewRouter()
+ }`), 0666))
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte("module x"), 0666))
+ tg.cd(tg.path("x"))
+ tg.run("mod", "-sync", "-json")
+ count := tg.grepCountBoth(`"Path": "rsc.io/quote",`)
+ if count != 1 {
+ t.Fatal("produces duplicate imports")
+ }
+ // test quoted module path
+ tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
+ module x
+ require (
+ "rsc.io/sampler" v1.3.0
+ "rsc.io/quote" v1.5.2
+ )`), 0666))
+ tg.run("mod", "-sync", "-json")
+ count = tg.grepCountBoth(`"Path": "rsc.io/quote",`)
+ if count != 1 {
+ t.Fatal("produces duplicate imports")
+ }
+}
+
+func TestModMultiVersion(t *testing.T) {
+ tg := testGoModules(t)
+ defer tg.cleanup()
+
+ checkModules := func(dirs ...string) {
+ t.Helper()
+ tg.run("list", "-deps", "-f", "{{.ImportPath}}: {{.Dir}}")
+ for _, line := range strings.Split(tg.getStdout(), "\n") {
+ line = strings.Replace(line, `\`, `/`, -1) // windows!
+ if strings.HasPrefix(line, "rsc.io/quote: ") {
+ if strings.Contains(line, "/src/mod/") {
+ t.Fatalf("rsc.io/quote should not be from module cache: %v", line)
+ }
+ } else if strings.Contains(line, "rsc.io/quote") {
+ if !strings.Contains(line, "/src/mod/") {
+ t.Fatalf("rsc.io/quote/* should be from module cache: %v", line)
+ }
+ }
+ }
+ }
+
+ tg.extract("testdata/mod/rsc.io_quote_v1.5.2.txt")
+ checkModules()
+
+ // These are git checkouts from rsc.io/quote not downlaoded modules.
+ // As such they contain extra files like spurious pieces of other modules.
+ tg.extract("testdata/rsc.io_quote_0d003b9.txt") // wraps v2
+ checkModules()
+
+ tg.extract("testdata/rsc.io_quote_b44a0b1.txt") // adds go.mod
+ checkModules()
+
+ tg.extract("testdata/rsc.io_quote_fe488b8.txt") // adds broken v3 subdirectory
+ tg.run("list", "./...") // should ignore v3 because v3/go.mod exists
+ checkModules()
+
+ tg.extract("testdata/rsc.io_quote_a91498b.txt") // wraps v3
+ checkModules() // looks up v3 from internet, not v3 subdirectory
+
+ tg.extract("testdata/rsc.io_quote_5d9f230.txt") // adds go.mod
+ checkModules() // knows which v3 to use (still needs download from internet, cached from last step)
+}
--- /dev/null
+// Copyright 2018 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_test
+
+import (
+ "archive/zip"
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modfetch/codehost"
+ "cmd/go/internal/module"
+ "cmd/go/internal/par"
+ "cmd/go/internal/semver"
+ "cmd/go/internal/txtar"
+)
+
+var (
+ proxyAddr = flag.String("proxy", "", "run proxy on this network address instead of running any tests")
+ proxyURL string
+)
+
+var proxyOnce sync.Once
+
+// StartProxy starts the Go module proxy running on *proxyAddr (like "localhost:1234")
+// and sets proxyURL to the GOPROXY setting to use to access the proxy.
+// Subsequent calls are no-ops.
+//
+// The proxy serves from testdata/mod. See testdata/mod/README.
+func StartProxy() {
+ proxyOnce.Do(func() {
+ fmt.Fprintf(os.Stderr, "go test proxy starting\n")
+ readModList()
+ addr := *proxyAddr
+ if addr == "" {
+ addr = "localhost:0"
+ }
+ l, err := net.Listen("tcp", addr)
+ if err != nil {
+ log.Fatal(err)
+ }
+ *proxyAddr = l.Addr().String()
+ proxyURL = "http://" + *proxyAddr + "/mod"
+ fmt.Fprintf(os.Stderr, "go test proxy running at GOPROXY=%s\n", proxyURL)
+ go func() {
+ log.Fatalf("go proxy: http.Serve: %v", http.Serve(l, http.HandlerFunc(proxyHandler)))
+ }()
+ })
+}
+
+var modList []module.Version
+
+func readModList() {
+ infos, err := ioutil.ReadDir("testdata/mod")
+ if err != nil {
+ log.Fatal(err)
+ }
+ for _, info := range infos {
+ name := info.Name()
+ if !strings.HasSuffix(name, ".txt") {
+ continue
+ }
+ name = strings.TrimSuffix(name, ".txt")
+ i := strings.LastIndex(name, "_v")
+ if i < 0 {
+ continue
+ }
+ path := strings.Replace(name[:i], "_", "/", -1)
+ vers := name[i+1:]
+ modList = append(modList, module.Version{Path: path, Version: vers})
+ }
+}
+
+var zipCache par.Cache
+
+// proxyHandler serves the Go module proxy protocol.
+// See the proxy section of https://research.swtch.com/vgo-module.
+func proxyHandler(w http.ResponseWriter, r *http.Request) {
+ if !strings.HasPrefix(r.URL.Path, "/mod/") {
+ http.NotFound(w, r)
+ return
+ }
+ path := strings.TrimPrefix(r.URL.Path, "/mod/")
+ i := strings.Index(path, "/@v/")
+ if i < 0 {
+ http.NotFound(w, r)
+ return
+ }
+ path, file := path[:i], path[i+len("/@v/"):]
+ if file == "list" {
+ n := 0
+ for _, m := range modList {
+ if m.Path == path && !modfetch.IsPseudoVersion(m.Version) {
+ if err := module.Check(m.Path, m.Version); err == nil {
+ fmt.Fprintf(w, "%s\n", m.Version)
+ n++
+ }
+ }
+ }
+ if n == 0 {
+ http.NotFound(w, r)
+ }
+ return
+ }
+
+ i = strings.LastIndex(file, ".")
+ if i < 0 {
+ http.NotFound(w, r)
+ return
+ }
+ vers, ext := file[:i], file[i+1:]
+
+ if codehost.AllHex(vers) {
+ var best string
+ // Convert commit hash (only) to known version.
+ // Use latest version in semver priority, to match similar logic
+ // in the repo-based module server (see modfetch.(*codeRepo).convert).
+ for _, m := range modList {
+ if m.Path == path && semver.Compare(best, m.Version) < 0 {
+ var hash string
+ if modfetch.IsPseudoVersion(m.Version) {
+ hash = m.Version[strings.LastIndex(m.Version, "-")+1:]
+ } else {
+ hash = findHash(m)
+ }
+ if strings.HasPrefix(hash, vers) || strings.HasPrefix(vers, hash) {
+ best = m.Version
+ }
+ }
+ }
+ if best != "" {
+ vers = best
+ }
+ }
+
+ a := readArchive(path, vers)
+ if a == nil {
+ http.Error(w, "cannot load archive", 500)
+ return
+ }
+
+ switch ext {
+ case "info", "mod":
+ want := "." + ext
+ for _, f := range a.Files {
+ if f.Name == want {
+ w.Write(f.Data)
+ return
+ }
+ }
+
+ case "zip":
+ type cached struct {
+ zip []byte
+ err error
+ }
+ c := zipCache.Do(a, func() interface{} {
+ var buf bytes.Buffer
+ z := zip.NewWriter(&buf)
+ for _, f := range a.Files {
+ if strings.HasPrefix(f.Name, ".") {
+ continue
+ }
+ zf, err := z.Create(path + "@" + vers + "/" + f.Name)
+ if err != nil {
+ return cached{nil, err}
+ }
+ if _, err := zf.Write(f.Data); err != nil {
+ return cached{nil, err}
+ }
+ }
+ if err := z.Close(); err != nil {
+ return cached{nil, err}
+ }
+ return cached{buf.Bytes(), nil}
+ }).(cached)
+
+ if c.err != nil {
+ http.Error(w, c.err.Error(), 500)
+ return
+ }
+ w.Write(c.zip)
+ return
+
+ }
+ http.NotFound(w, r)
+}
+
+func findHash(m module.Version) string {
+ a := readArchive(m.Path, m.Version)
+ if a == nil {
+ return ""
+ }
+ var data []byte
+ for _, f := range a.Files {
+ if f.Name == ".info" {
+ data = f.Data
+ break
+ }
+ }
+ var info struct{ Short string }
+ json.Unmarshal(data, &info)
+ return info.Short
+}
+
+var archiveCache par.Cache
+
+func readArchive(path, vers string) *txtar.Archive {
+ prefix := strings.Replace(path, "/", "_", -1)
+ name := filepath.Join(cmdGoDir, "testdata/mod", prefix+"_"+vers+".txt")
+ a := archiveCache.Do(name, func() interface{} {
+ a, err := txtar.ParseFile(name)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
+ }
+ a = nil
+ }
+ return a
+ }).(*txtar.Archive)
+ return a
+}
--- /dev/null
+// Copyright 2018 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 ignore
+
+// Addmod adds a module as a txtar archive to the testdata/mod directory.
+//
+// Usage:
+//
+// go run addmod.go path@version...
+//
+// It should only be used for very small modules - we do not want to check
+// very large files into testdata/mod.
+//
+// It is acceptable to edit the archive afterward to remove or shorten files.
+// See mod/README for more information.
+//
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "../internal/txtar"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go run addmod.go path@version...\n")
+ os.Exit(2)
+}
+
+var tmpdir string
+
+func fatalf(format string, args ...interface{}) {
+ os.RemoveAll(tmpdir)
+ log.Fatalf(format, args...)
+}
+
+const goCmd = "vgo"
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() == 0 {
+ usage()
+ }
+
+ log.SetPrefix("addmod: ")
+ log.SetFlags(0)
+
+ var err error
+ tmpdir, err = ioutil.TempDir("", "addmod-")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ run := func(command string, args ...string) string {
+ cmd := exec.Command(command, args...)
+ cmd.Dir = tmpdir
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ out, err := cmd.Output()
+ if err != nil {
+ fatalf("%s %s: %v\n%s", command, strings.Join(args, " "), err, stderr.Bytes())
+ }
+ return string(out)
+ }
+
+ gopath := strings.TrimSpace(run("go", "env", "GOPATH"))
+ if gopath == "" {
+ fatalf("cannot find GOPATH")
+ }
+
+ exitCode := 0
+ for _, arg := range flag.Args() {
+ if err := ioutil.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte("module m\n"), 0666); err != nil {
+ fatalf("%v", err)
+ }
+ run(goCmd, "get", "-d", arg)
+ path := arg
+ if i := strings.Index(path, "@"); i >= 0 {
+ path = path[:i]
+ }
+ out := run(goCmd, "list", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}", path)
+ f := strings.Fields(out)
+ if len(f) != 3 {
+ log.Printf("go list -m %s: unexpected output %q", arg, out)
+ exitCode = 1
+ continue
+ }
+ path, vers, dir := f[0], f[1], f[2]
+ mod, err := ioutil.ReadFile(filepath.Join(gopath, "src/mod/cache/download", path, "@v", vers+".mod"))
+ if err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+ info, err := ioutil.ReadFile(filepath.Join(gopath, "src/mod/cache/download", path, "@v", vers+".info"))
+ if err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+
+ a := new(txtar.Archive)
+ title := arg
+ if !strings.Contains(arg, "@") {
+ title += "@" + vers
+ }
+ a.Comment = []byte(fmt.Sprintf("module %s\n\n", title))
+ a.Files = []txtar.File{
+ {Name: ".mod", Data: mod},
+ {Name: ".info", Data: info},
+ }
+ dir = filepath.Clean(dir)
+ err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if !info.Mode().IsRegular() {
+ return nil
+ }
+ name := info.Name()
+ if name == "go.mod" || strings.HasSuffix(name, ".go") {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ a.Files = append(a.Files, txtar.File{Name: strings.TrimPrefix(path, dir+string(filepath.Separator)), Data: data})
+ }
+ return nil
+ })
+ if err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+
+ data := txtar.Format(a)
+ target := filepath.Join("mod", strings.Replace(path, "/", "_", -1)+"_"+vers+".txt")
+ if err := ioutil.WriteFile(target, data, 0666); err != nil {
+ log.Printf("%s: %v", arg, err)
+ exitCode = 1
+ continue
+ }
+ }
+ os.RemoveAll(tmpdir)
+ os.Exit(exitCode)
+}
--- /dev/null
+This directory holds Go modules served by a Go module proxy
+that runs on localhost during tests, both to make tests avoid
+requiring specific network servers and also to make them
+significantly faster.
+
+A small go get'able test module can be added here by running
+
+ cd cmd/go/testdata
+ go run addmod.go path@vers
+
+where path and vers are the module path and version to add here.
+
+For interactive experimentation using this set of modules, run:
+
+ cd cmd/go
+ go test -proxy=localhost:1234 &
+ export GOPROXY=http://localhost:1234/mod
+
+and then run go commands as usual.
+
+Modules saved to this directory should be small: a few kilobytes at most.
+It is acceptable to edit the archives created by addmod.go to remove
+or shorten files. It is also acceptable to write module archives by hand:
+they need not be backed by some public git repo.
+
+Each module archive is named path_vers.txt, where slashes in path
+have been replaced with underscores. The archive must contain
+two files ".info" and ".mod", to be served as the info and mod files
+in the proxy protocol (see https://research.swtch.com/vgo-module).
+The remaining files are served as the content of the module zip file.
+The path@vers prefix required of files in the zip file is added
+automatically by the proxy: the files in the archive have names without
+the prefix, like plain "go.mod", "x.go", and so on.
+
+See ../addmod.go and ../savedir.go for tools to generate txtar files,
+although again it is also fine to write them by hand.
--- /dev/null
+Written by hand.
+Test case for module at root of domain.
+
+-- .mod --
+module example.com
+-- .info --
+{"Version": "v1.0.0"}
+-- x.go --
+package x
--- /dev/null
+written by hand - just enough to compile rsc.io/sampler, rsc.io/quote
+
+-- .mod --
+module golang.org/x/text
+-- .info --
+{"Version":"v0.0.0-20170915032832-14c0d48ead0c","Name":"v0.0.0-20170915032832-14c0d48ead0c","Short":"14c0d48ead0c","Time":"2017-09-15T03:28:32Z"}
+-- go.mod --
+module golang.org/x/text
+-- language/lang.go --
+// Copyright 2018 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.
+
+// This is a tiny version of golang.org/x/text.
+
+package language
+
+import "strings"
+
+type Tag string
+
+func Make(s string) Tag { return Tag(s) }
+
+func (t Tag) String() string { return string(t) }
+
+func NewMatcher(tags []Tag) Matcher { return &matcher{tags} }
+
+type Matcher interface {
+ Match(...Tag) (Tag, int, int)
+}
+
+type matcher struct {
+ tags []Tag
+}
+
+func (m *matcher) Match(prefs ...Tag) (Tag, int, int) {
+ for _, pref := range prefs {
+ for _, tag := range m.tags {
+ if tag == pref || strings.HasPrefix(string(pref), string(tag+"-")) || strings.HasPrefix(string(tag), string(pref+"-")) {
+ return tag, 0, 0
+ }
+ }
+ }
+ return m.tags[0], 0, 0
+}
--- /dev/null
+written by hand - just enough to compile rsc.io/sampler, rsc.io/quote
+
+-- .mod --
+module golang.org/x/text
+-- .info --
+{"Version":"v0.3.0","Name":"","Short":"","Time":"2017-09-16T03:28:32Z"}
+-- go.mod --
+module golang.org/x/text
+-- language/lang.go --
+// Copyright 2018 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.
+
+// This is a tiny version of golang.org/x/text.
+
+package language
+
+import "strings"
+
+type Tag string
+
+func Make(s string) Tag { return Tag(s) }
+
+func (t Tag) String() string { return string(t) }
+
+func NewMatcher(tags []Tag) Matcher { return &matcher{tags} }
+
+type Matcher interface {
+ Match(...Tag) (Tag, int, int)
+}
+
+type matcher struct {
+ tags []Tag
+}
+
+func (m *matcher) Match(prefs ...Tag) (Tag, int, int) {
+ for _, pref := range prefs {
+ for _, tag := range m.tags {
+ if tag == pref || strings.HasPrefix(string(pref), string(tag+"-")) || strings.HasPrefix(string(tag), string(pref+"-")) {
+ return tag, 0, 0
+ }
+ }
+ }
+ return m.tags[0], 0, 0
+}
--- /dev/null
+rsc.io/quote@e7a685a342
+
+-- .mod --
+module "rsc.io/quote"
+-- .info --
+{"Version":"v0.0.0-20180214005133-e7a685a342c0","Name":"e7a685a342c001acc3eb7f5eafa82980480042c7","Short":"e7a685a342c0","Time":"2018-02-14T00:51:33Z"}
+-- go.mod --
+module "rsc.io/quote"
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory. Share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
--- /dev/null
+rsc.io/quote@v0.0.0-20180214005840-23179ee8a569
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v0.0.0-20180214005840-23179ee8a569","Name":"23179ee8a569bb05d896ae05c6503ec69a19f99f","Short":"23179ee8a569","Time":"2018-02-14T00:58:40Z"}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@dd9747d
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v0.0.0-20180628003336-dd9747d19b04","Name":"dd9747d19b041365fbddf0399ddba6bff5eb1b3e","Short":"dd9747d19b04","Time":"2018-06-28T00:33:36Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+AN EVEN WORSE CHANGE!
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v2.0.0
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v0.0.0-20180709153244-fd906ed3b100","Name":"fd906ed3b100e47181ffa9ec36d82294525c9109","Short":"fd906ed3b100","Time":"2018-07-09T15:32:44Z"}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV2() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func GlassV2() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func GoV2() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func OptV2() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v0.0.0-20180709160352-0d003b9c4bfa
+
+-- .mod --
+module rsc.io/quote
+
+require rsc.io/sampler v1.3.0
+-- .info --
+{"Version":"v0.0.0-20180709160352-0d003b9c4bfa","Name":"0d003b9c4bfac881641be8eb1598b782a467a97f","Short":"0d003b9c4bfa","Time":"2018-07-09T16:03:52Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require rsc.io/sampler v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v2"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV2()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV2()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV2()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV2()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v0.0.0-20180709162749-b44a0b17b2d1
+
+-- .mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v2 v2.0.1
+ rsc.io/sampler v1.3.0
+)
+-- .info --
+{"Version":"v0.0.0-20180709162749-b44a0b17b2d1","Name":"b44a0b17b2d1fe4c98a8d0e7a68c9bf9e762799a","Short":"b44a0b17b2d1","Time":"2018-07-09T16:27:49Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v2 v2.0.1
+ rsc.io/sampler v1.3.0
+)
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v2"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV2()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV2()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV2()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV2()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v0.0.0-20180709162816-fe488b867524
+
+-- .mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v2 v2.0.1
+ rsc.io/sampler v1.3.0
+)
+-- .info --
+{"Version":"v0.0.0-20180709162816-fe488b867524","Name":"fe488b867524806e861c3f4f43ae6946a42ca3f1","Short":"fe488b867524","Time":"2018-07-09T16:28:16Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v2 v2.0.1
+ rsc.io/sampler v1.3.0
+)
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v2"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV2()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV2()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV2()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV2()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v0.0.0-20180709162918-a91498bed0a7
+
+-- .mod --
+module rsc.io/quote
+
+require rsc.io/sampler v1.3.0
+-- .info --
+{"Version":"v0.0.0-20180709162918-a91498bed0a7","Name":"a91498bed0a73d4bb9c1fb2597925f7883bc40a7","Short":"a91498bed0a7","Time":"2018-07-09T16:29:18Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require rsc.io/sampler v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v3"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV3()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV3()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV3()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV3()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v0.0.0-20180710144737-5d9f230bcfba
+
+-- .mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v3 v3.0.0
+ rsc.io/sampler v1.3.0
+)
+-- .info --
+{"Version":"v0.0.0-20180710144737-5d9f230bcfba","Name":"5d9f230bcfbae514bb6c2215694c2ce7273fc604","Short":"5d9f230bcfba","Time":"2018-07-10T14:47:37Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v3 v3.0.0
+ rsc.io/sampler v1.3.0
+)
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v3"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV3()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV3()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV3()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV3()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.0.0
+
+-- .mod --
+module "rsc.io/quote"
+-- .info --
+{"Version":"v1.0.0","Name":"f488df80bcdbd3e5bafdc24ad7d1e79e83edd7e6","Short":"f488df80bcdb","Time":"2018-02-14T00:45:20Z"}
+-- go.mod --
+module "rsc.io/quote"
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.1.0
+
+-- .mod --
+module "rsc.io/quote"
+-- .info --
+{"Version":"v1.1.0","Name":"cfd7145f43f92a8d56b4a3dd603795a3291381a9","Short":"cfd7145f43f9","Time":"2018-02-14T00:46:44Z"}
+-- go.mod --
+module "rsc.io/quote"
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.2.0
+
+-- .mod --
+module "rsc.io/quote"
+-- .info --
+{"Version":"v1.2.0","Name":"d8a3de91045c932a1c71e545308fe97571d6d65c","Short":"d8a3de91045c","Time":"2018-02-14T00:47:51Z"}
+-- go.mod --
+module "rsc.io/quote"
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+// Go returns a Go proverb.
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory. Share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.2.1
+
+-- .mod --
+module "rsc.io/quote"
+-- .info --
+{"Version":"v1.2.1","Name":"5c1f03b64ab7aa958798a569a31924655dc41e76","Short":"5c1f03b64ab7","Time":"2018-02-14T00:54:20Z"}
+-- go.mod --
+module "rsc.io/quote"
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.3.0
+
+-- .mod --
+module "rsc.io/quote"
+-- .info --
+{"Version":"v1.3.0","Name":"84de74b35823c1e49634f2262f1a58cfc951ebae","Short":"84de74b35823","Time":"2018-02-14T00:54:53Z"}
+-- go.mod --
+module "rsc.io/quote"
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.4.0
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.0.0
+-- .info --
+{"Version":"v1.4.0","Name":"19e8b977bd2f437798c2cc2dcfe8a1c0f169481b","Short":"19e8b977bd2f","Time":"2018-02-14T00:56:05Z"}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.0.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.5.0
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v1.5.0","Name":"3ba1e30dc83bd52c990132b9dfb1688a9d22de13","Short":"3ba1e30dc83b","Time":"2018-02-14T00:58:15Z"}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import "testing"
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@23179ee8a569
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v1.5.1","Name":"23179ee8a569bb05d896ae05c6503ec69a19f99f","Short":"23179ee8a569","Time":"2018-02-14T00:58:40Z"}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.5.2
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v1.5.2","Name":"c4d4236f92427c64bfbcf1cc3f8142ab18f30b22","Short":"c4d4236f9242","Time":"2018-02-14T15:44:20Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v1.5.3-pre1
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v1.5.3-pre1","Name":"2473dfd877c95382420e47686aa9076bf58c79e0","Short":"2473dfd877c9","Time":"2018-06-28T00:32:53Z"}
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// A CHANGE!
+
+// Hello returns a greeting.
+func Hello() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote@v2.0.0 && cp mod/rsc.io_quote_v0.0.0-20180709153244-fd906ed3b100.txt mod/rsc.io_quote_v2.0.0.txt
+
+-- .mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- .info --
+{"Version":"v0.0.0-20180709153244-fd906ed3b100","Name":"fd906ed3b100e47181ffa9ec36d82294525c9109","Short":"fd906ed3b100","Time":"2018-07-09T15:32:44Z"}
+-- go.mod --
+module "rsc.io/quote"
+
+require "rsc.io/sampler" v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV2() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func GlassV2() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func GoV2() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func OptV2() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote/v2@v2.0.1
+
+-- .mod --
+module rsc.io/quote/v2
+
+require rsc.io/sampler v1.3.0
+-- .info --
+{"Version":"v2.0.1","Name":"754f68430672776c84704e2d10209a6ec700cd64","Short":"754f68430672","Time":"2018-07-09T16:25:34Z"}
+-- go.mod --
+module rsc.io/quote/v2
+
+require rsc.io/sampler v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV2() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func GlassV2() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func GoV2() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func OptV2() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
--- /dev/null
+rsc.io/quote/v3@v3.0.0
+
+-- .mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- .info --
+{"Version":"v3.0.0","Name":"d88915d7e77ed0fd35d0a022a2f244e2202fd8c8","Short":"d88915d7e77e","Time":"2018-07-09T15:34:46Z"}
+-- go.mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV3() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func GlassV3() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func GoV3() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func OptV3() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
--- /dev/null
+rsc.io/sampler@v1.0.0
+
+-- .mod --
+module "rsc.io/sampler"
+-- .info --
+{"Version":"v1.0.0","Name":"60bef405c52117ad21d2adb10872b95cf17f8fca","Short":"60bef405c521","Time":"2018-02-13T18:05:54Z"}
+-- go.mod --
+module "rsc.io/sampler"
+-- sampler.go --
+// Copyright 2018 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 sampler shows simple texts.
+package sampler // import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func Hello() string {
+ return "Hello, world."
+}
--- /dev/null
+rsc.io/sampler@v1.2.0
+
+-- .mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- .info --
+{"Version":"v1.2.0","Name":"25f24110b153246056eccc14a3a4cd81afaff586","Short":"25f24110b153","Time":"2018-02-13T18:13:45Z"}
+-- go.mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- hello.go --
+// Copyright 2018 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.
+
+// Translations by Google Translate.
+
+package sampler
+
+var hello = newText(`
+
+English: en: Hello, world.
+French: fr: Bonjour le monde.
+Spanish: es: Hola Mundo.
+
+`)
+-- hello_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var helloTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "Hello, world.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Bonjour la monde.",
+ },
+}
+
+func TestHello(t *testing.T) {
+ for _, tt := range helloTests {
+ text := Hello(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- sampler.go --
+// Copyright 2018 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 sampler shows simple texts.
+package sampler // import "rsc.io/sampler"
+
+import (
+ "os"
+ "strings"
+
+ "golang.org/x/text/language"
+)
+
+// DefaultUserPrefs returns the default user language preferences.
+// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment
+// variables, in that order.
+func DefaultUserPrefs() []language.Tag {
+ var prefs []language.Tag
+ for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+ if env := os.Getenv(k); env != "" {
+ prefs = append(prefs, language.Make(env))
+ }
+ }
+ return prefs
+}
+
+// Hello returns a localized greeting.
+// If no prefs are given, Hello uses DefaultUserPrefs.
+func Hello(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return hello.find(prefs)
+}
+
+// A text is a localized text.
+type text struct {
+ byTag map[string]string
+ matcher language.Matcher
+}
+
+// newText creates a new localized text, given a list of translations.
+func newText(s string) *text {
+ t := &text{
+ byTag: make(map[string]string),
+ }
+ var tags []language.Tag
+ for _, line := range strings.Split(s, "\n") {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ f := strings.Split(line, ": ")
+ if len(f) != 3 {
+ continue
+ }
+ tag := language.Make(f[1])
+ tags = append(tags, tag)
+ t.byTag[tag.String()] = f[2]
+ }
+ t.matcher = language.NewMatcher(tags)
+ return t
+}
+
+// find finds the text to use for the given language tag preferences.
+func (t *text) find(prefs []language.Tag) string {
+ tag, _, _ := t.matcher.Match(prefs...)
+ s := t.byTag[tag.String()]
+ if strings.HasPrefix(s, "RTL ") {
+ s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E"
+ }
+ return s
+}
--- /dev/null
+generated by ./addmod.bash rsc.io/sampler@v1.2.1
+
+-- .mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- .info --
+{"Version":"v1.2.1","Name":"cac3af4f8a0ab40054fa6f8d423108a63a1255bb","Short":"cac3af4f8a0a","Time":"2018-02-13T18:16:22Z"}EOF
+-- hello.go --
+// Copyright 2018 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.
+
+// Translations by Google Translate.
+
+package sampler
+
+var hello = newText(`
+
+English: en: Hello, world.
+French: fr: Bonjour le monde.
+Spanish: es: Hola Mundo.
+
+`)
+-- hello_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var helloTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "Hello, world.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Bonjour le monde.",
+ },
+}
+
+func TestHello(t *testing.T) {
+ for _, tt := range helloTests {
+ text := Hello(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- sampler.go --
+// Copyright 2018 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 sampler shows simple texts.
+package sampler // import "rsc.io/sampler"
+
+import (
+ "os"
+ "strings"
+
+ "golang.org/x/text/language"
+)
+
+// DefaultUserPrefs returns the default user language preferences.
+// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment
+// variables, in that order.
+func DefaultUserPrefs() []language.Tag {
+ var prefs []language.Tag
+ for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+ if env := os.Getenv(k); env != "" {
+ prefs = append(prefs, language.Make(env))
+ }
+ }
+ return prefs
+}
+
+// Hello returns a localized greeting.
+// If no prefs are given, Hello uses DefaultUserPrefs.
+func Hello(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return hello.find(prefs)
+}
+
+// A text is a localized text.
+type text struct {
+ byTag map[string]string
+ matcher language.Matcher
+}
+
+// newText creates a new localized text, given a list of translations.
+func newText(s string) *text {
+ t := &text{
+ byTag: make(map[string]string),
+ }
+ var tags []language.Tag
+ for _, line := range strings.Split(s, "\n") {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ f := strings.Split(line, ": ")
+ if len(f) != 3 {
+ continue
+ }
+ tag := language.Make(f[1])
+ tags = append(tags, tag)
+ t.byTag[tag.String()] = f[2]
+ }
+ t.matcher = language.NewMatcher(tags)
+ return t
+}
+
+// find finds the text to use for the given language tag preferences.
+func (t *text) find(prefs []language.Tag) string {
+ tag, _, _ := t.matcher.Match(prefs...)
+ s := t.byTag[tag.String()]
+ if strings.HasPrefix(s, "RTL ") {
+ s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E"
+ }
+ return s
+}
--- /dev/null
+rsc.io/sampler@v1.3.0
+
+-- .mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- .info --
+{"Version":"v1.3.0","Name":"0cc034b51e57ed7832d4c67d526f75a900996e5c","Short":"0cc034b51e57","Time":"2018-02-13T19:05:03Z"}
+-- glass.go --
+// Copyright 2018 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.
+
+// Translations from Frank da Cruz, Ethan Mollick, and many others.
+// See http://kermitproject.org/utf8.html.
+// http://www.oocities.org/nodotus/hbglass.html
+// https://en.wikipedia.org/wiki/I_Can_Eat_Glass
+
+package sampler
+
+var glass = newText(`
+
+English: en: I can eat glass and it doesn't hurt me.
+French: fr: Je peux manger du verre, ça ne me fait pas mal.
+Spanish: es: Puedo comer vidrio, no me hace daño.
+
+`)
+-- glass_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var glassTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "I can eat glass and it doesn't hurt me.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Je peux manger du verre, ça ne me fait pas mal.",
+ },
+}
+
+func TestGlass(t *testing.T) {
+ for _, tt := range glassTests {
+ text := Glass(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Glass(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- go.mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- hello.go --
+// Copyright 2018 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.
+
+// Translations by Google Translate.
+
+package sampler
+
+var hello = newText(`
+
+English: en: Hello, world.
+French: fr: Bonjour le monde.
+Spanish: es: Hola Mundo.
+
+`)
+-- hello_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var helloTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "Hello, world.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Bonjour le monde.",
+ },
+}
+
+func TestHello(t *testing.T) {
+ for _, tt := range helloTests {
+ text := Hello(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- sampler.go --
+// Copyright 2018 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 sampler shows simple texts.
+package sampler // import "rsc.io/sampler"
+
+import (
+ "os"
+ "strings"
+
+ "golang.org/x/text/language"
+)
+
+// DefaultUserPrefs returns the default user language preferences.
+// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment
+// variables, in that order.
+func DefaultUserPrefs() []language.Tag {
+ var prefs []language.Tag
+ for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+ if env := os.Getenv(k); env != "" {
+ prefs = append(prefs, language.Make(env))
+ }
+ }
+ return prefs
+}
+
+// Hello returns a localized greeting.
+// If no prefs are given, Hello uses DefaultUserPrefs.
+func Hello(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return hello.find(prefs)
+}
+
+// Glass returns a localized silly phrase.
+// If no prefs are given, Glass uses DefaultUserPrefs.
+func Glass(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return glass.find(prefs)
+}
+
+// A text is a localized text.
+type text struct {
+ byTag map[string]string
+ matcher language.Matcher
+}
+
+// newText creates a new localized text, given a list of translations.
+func newText(s string) *text {
+ t := &text{
+ byTag: make(map[string]string),
+ }
+ var tags []language.Tag
+ for _, line := range strings.Split(s, "\n") {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ f := strings.Split(line, ": ")
+ if len(f) != 3 {
+ continue
+ }
+ tag := language.Make(f[1])
+ tags = append(tags, tag)
+ t.byTag[tag.String()] = f[2]
+ }
+ t.matcher = language.NewMatcher(tags)
+ return t
+}
+
+// find finds the text to use for the given language tag preferences.
+func (t *text) find(prefs []language.Tag) string {
+ tag, _, _ := t.matcher.Match(prefs...)
+ s := t.byTag[tag.String()]
+ if strings.HasPrefix(s, "RTL ") {
+ s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E"
+ }
+ return s
+}
--- /dev/null
+rsc.io/sampler@v1.3.1
+
+-- .mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- .info --
+{"Version":"v1.3.1","Name":"f545d0289d06e2add4556ea6a15fc4938014bf87","Short":"f545d0289d06","Time":"2018-02-14T16:34:12Z"}
+-- glass.go --
+// Copyright 2018 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.
+
+// Translations from Frank da Cruz, Ethan Mollick, and many others.
+// See http://kermitproject.org/utf8.html.
+// http://www.oocities.org/nodotus/hbglass.html
+// https://en.wikipedia.org/wiki/I_Can_Eat_Glass
+
+package sampler
+
+var glass = newText(`
+
+English: en: I can eat glass and it doesn't hurt me.
+French: fr: Je peux manger du verre, ça ne me fait pas mal.
+Spanish: es: Puedo comer vidrio, no me hace daño.
+
+`)
+-- glass_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var glassTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "I can eat glass and it doesn't hurt me.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Je peux manger du verre, ça ne me fait pas mal.",
+ },
+}
+
+func TestGlass(t *testing.T) {
+ for _, tt := range glassTests {
+ text := Glass(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Glass(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- go.mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- hello.go --
+// Copyright 2018 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.
+
+// Translations by Google Translate.
+
+package sampler
+
+var hello = newText(`
+
+English: en: Hello, world.
+French: fr: Bonjour le monde.
+Spanish: es: Hola Mundo.
+
+`)
+-- hello_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var helloTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "Hello, world.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Bonjour le monde.",
+ },
+}
+
+func TestHello(t *testing.T) {
+ for _, tt := range helloTests {
+ text := Hello(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- sampler.go --
+// Copyright 2018 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 sampler shows simple texts in a variety of languages.
+package sampler // import "rsc.io/sampler"
+
+import (
+ "os"
+ "strings"
+
+ "golang.org/x/text/language"
+)
+
+// DefaultUserPrefs returns the default user language preferences.
+// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment
+// variables, in that order.
+func DefaultUserPrefs() []language.Tag {
+ var prefs []language.Tag
+ for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+ if env := os.Getenv(k); env != "" {
+ prefs = append(prefs, language.Make(env))
+ }
+ }
+ return prefs
+}
+
+// Hello returns a localized greeting.
+// If no prefs are given, Hello uses DefaultUserPrefs.
+func Hello(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return hello.find(prefs)
+}
+
+// Glass returns a localized silly phrase.
+// If no prefs are given, Glass uses DefaultUserPrefs.
+func Glass(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return glass.find(prefs)
+}
+
+// A text is a localized text.
+type text struct {
+ byTag map[string]string
+ matcher language.Matcher
+}
+
+// newText creates a new localized text, given a list of translations.
+func newText(s string) *text {
+ t := &text{
+ byTag: make(map[string]string),
+ }
+ var tags []language.Tag
+ for _, line := range strings.Split(s, "\n") {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ f := strings.Split(line, ": ")
+ if len(f) != 3 {
+ continue
+ }
+ tag := language.Make(f[1])
+ tags = append(tags, tag)
+ t.byTag[tag.String()] = f[2]
+ }
+ t.matcher = language.NewMatcher(tags)
+ return t
+}
+
+// find finds the text to use for the given language tag preferences.
+func (t *text) find(prefs []language.Tag) string {
+ tag, _, _ := t.matcher.Match(prefs...)
+ s := t.byTag[tag.String()]
+ if strings.HasPrefix(s, "RTL ") {
+ s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E"
+ }
+ return s
+}
--- /dev/null
+rsc.io/sampler@v1.99.99
+
+-- .mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- .info --
+{"Version":"v1.99.99","Name":"732a3c400797d8835f2af34a9561f155bef85435","Short":"732a3c400797","Time":"2018-02-13T22:20:19Z"}
+-- go.mod --
+module "rsc.io/sampler"
+
+require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c
+-- hello.go --
+// Copyright 2018 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.
+
+// Translations by Google Translate.
+
+package sampler
+
+var hello = newText(`
+
+English: en: 99 bottles of beer on the wall, 99 bottles of beer, ...
+
+`)
+-- hello_test.go --
+// Copyright 2018 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 sampler
+
+import (
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+var helloTests = []struct {
+ prefs []language.Tag
+ text string
+}{
+ {
+ []language.Tag{language.Make("en-US"), language.Make("fr")},
+ "Hello, world.",
+ },
+ {
+ []language.Tag{language.Make("fr"), language.Make("en-US")},
+ "Bonjour le monde.",
+ },
+}
+
+func TestHello(t *testing.T) {
+ for _, tt := range helloTests {
+ text := Hello(tt.prefs...)
+ if text != tt.text {
+ t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text)
+ }
+ }
+}
+-- sampler.go --
+// Copyright 2018 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 sampler shows simple texts.
+package sampler // import "rsc.io/sampler"
+
+import (
+ "os"
+ "strings"
+
+ "golang.org/x/text/language"
+)
+
+// DefaultUserPrefs returns the default user language preferences.
+// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment
+// variables, in that order.
+func DefaultUserPrefs() []language.Tag {
+ var prefs []language.Tag
+ for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
+ if env := os.Getenv(k); env != "" {
+ prefs = append(prefs, language.Make(env))
+ }
+ }
+ return prefs
+}
+
+// Hello returns a localized greeting.
+// If no prefs are given, Hello uses DefaultUserPrefs.
+func Hello(prefs ...language.Tag) string {
+ if len(prefs) == 0 {
+ prefs = DefaultUserPrefs()
+ }
+ return hello.find(prefs)
+}
+
+func Glass() string {
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// A text is a localized text.
+type text struct {
+ byTag map[string]string
+ matcher language.Matcher
+}
+
+// newText creates a new localized text, given a list of translations.
+func newText(s string) *text {
+ t := &text{
+ byTag: make(map[string]string),
+ }
+ var tags []language.Tag
+ for _, line := range strings.Split(s, "\n") {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ f := strings.Split(line, ": ")
+ if len(f) != 3 {
+ continue
+ }
+ tag := language.Make(f[1])
+ tags = append(tags, tag)
+ t.byTag[tag.String()] = f[2]
+ }
+ t.matcher = language.NewMatcher(tags)
+ return t
+}
+
+// find finds the text to use for the given language tag preferences.
+func (t *text) find(prefs []language.Tag) string {
+ tag, _, _ := t.matcher.Match(prefs...)
+ s := t.byTag[tag.String()]
+ if strings.HasPrefix(s, "RTL ") {
+ s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E"
+ }
+ return s
+}
--- /dev/null
+generated by: go run savedir.go .
+
+-- LICENSE --
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-- README.md --
+This package collects pithy sayings.
+
+It's part of a demonstration of
+[package versioning in Go](https://research.swtch.com/vgo1).
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require rsc.io/sampler v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v2"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV2()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV2()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV2()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV2()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
+-- v3/go.mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- v3/go.sum --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/sampler v1.99.99 h1:7i08f/p5TBU5joCPW3GjWG1ZFCmr28ybGqlXtelhEK8=
+rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- v3/quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV3() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func GlassV3() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func GoV3() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func OptV3() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
--- /dev/null
+generated by: go run savedir.go .
+
+-- LICENSE --
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-- README.md --
+This package collects pithy sayings.
+
+It's part of a demonstration of
+[package versioning in Go](https://research.swtch.com/vgo1).
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v3 v3.0.0
+ rsc.io/sampler v1.3.0
+)
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v3"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV3()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV3()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV3()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV3()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
+-- v3/go.mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- v3/go.sum --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/sampler v1.99.99 h1:7i08f/p5TBU5joCPW3GjWG1ZFCmr28ybGqlXtelhEK8=
+rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- v3/quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV3() string {
+ return sampler.Hello()
--- /dev/null
+generated by: go run savedir.go .
+
+-- LICENSE --
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-- README.md --
+This package collects pithy sayings.
+
+It's part of a demonstration of
+[package versioning in Go](https://research.swtch.com/vgo1).
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require rsc.io/sampler v1.3.0
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v3"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV3()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV3()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV3()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV3()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
+-- v3/go.mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- v3/go.sum --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/sampler v1.99.99 h1:7i08f/p5TBU5joCPW3GjWG1ZFCmr28ybGqlXtelhEK8=
+rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- v3/quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV3() string {
+ return sampler.Hello()
--- /dev/null
+generated by: go run savedir.go .
+
+-- LICENSE --
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-- README.md --
+This package collects pithy sayings.
+
+It's part of a demonstration of
+[package versioning in Go](https://research.swtch.com/vgo1).
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v2 v2.0.1
+ rsc.io/sampler v1.3.0
+)
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v2"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV2()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV2()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV2()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV2()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
+-- v3/go.mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- v3/go.sum --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/sampler v1.99.99 h1:7i08f/p5TBU5joCPW3GjWG1ZFCmr28ybGqlXtelhEK8=
+rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- v3/quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV3() string {
+ return sampler.Hello()
+}
+
+// Glass returns a useful phrase for world travelers.
+func GlassV3() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return "I can eat glass and it doesn't hurt me."
+}
+
+// Go returns a Go proverb.
+func GoV3() string {
+ return "Don't communicate by sharing memory, share memory by communicating."
+}
+
+// Opt returns an optimization truth.
+func OptV3() string {
+ // Wisdom from ken.
+ return "If a program is too slow, it must have a loop."
+}
--- /dev/null
+generated by: go run savedir.go .
+
+-- LICENSE --
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-- README.md --
+This package collects pithy sayings.
+
+It's part of a demonstration of
+[package versioning in Go](https://research.swtch.com/vgo1).
+-- buggy/buggy_test.go --
+// Copyright 2018 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 buggy
+
+import "testing"
+
+func Test(t *testing.T) {
+ t.Fatal("buggy!")
+}
+-- go.mod --
+module rsc.io/quote
+
+require (
+ rsc.io/quote/v2 v2.0.1
+ rsc.io/sampler v1.3.0
+)
+-- quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/quote/v2"
+
+// Hello returns a greeting.
+func Hello() string {
+ return quote.HelloV2()
+}
+
+// Glass returns a useful phrase for world travelers.
+func Glass() string {
+ // See http://www.oocities.org/nodotus/hbglass.html.
+ return quote.GlassV2()
+}
+
+// Go returns a Go proverb.
+func Go() string {
+ return quote.GoV2()
+}
+
+// Opt returns an optimization truth.
+func Opt() string {
+ // Wisdom from ken.
+ return quote.OptV2()
+}
+-- quote_test.go --
+// Copyright 2018 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 quote
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHello(t *testing.T) {
+ hello := "Hello, world."
+ if out := Hello(); out != hello {
+ t.Errorf("Hello() = %q, want %q", out, hello)
+ }
+}
+
+func TestGlass(t *testing.T) {
+ glass := "I can eat glass and it doesn't hurt me."
+ if out := Glass(); out != glass {
+ t.Errorf("Glass() = %q, want %q", out, glass)
+ }
+}
+
+func TestGo(t *testing.T) {
+ go1 := "Don't communicate by sharing memory, share memory by communicating."
+ if out := Go(); out != go1 {
+ t.Errorf("Go() = %q, want %q", out, go1)
+ }
+}
+
+func TestOpt(t *testing.T) {
+ opt := "If a program is too slow, it must have a loop."
+ if out := Opt(); out != opt {
+ t.Errorf("Opt() = %q, want %q", out, opt)
+ }
+}
+-- v3/go.mod --
+module rsc.io/quote/v3
+
+require rsc.io/sampler v1.3.0
+
+-- v3/go.sum --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/sampler v1.99.99 h1:7i08f/p5TBU5joCPW3GjWG1ZFCmr28ybGqlXtelhEK8=
+rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- v3/quote.go --
+// Copyright 2018 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 quote collects pithy sayings.
+package quote // import "rsc.io/quote"
+
+import "rsc.io/sampler"
+
+// Hello returns a greeting.
+func HelloV3() string {
+ return sampler.Hello()
--- /dev/null
+// Copyright 2018 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 ignore
+
+// Savedir archives a directory tree as a txtar archive printed to standard output.
+//
+// Usage:
+//
+// go run savedir.go /path/to/dir >saved.txt
+//
+// Typically the tree is later extracted during a test with tg.extract("testdata/saved.txt").
+//
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "unicode/utf8"
+
+ "../internal/txtar"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go run savedir.go dir >saved.txt\n")
+ os.Exit(2)
+}
+
+const goCmd = "vgo"
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 1 {
+ usage()
+ }
+
+ log.SetPrefix("savedir: ")
+ log.SetFlags(0)
+
+ dir := flag.Arg(0)
+
+ a := new(txtar.Archive)
+ dir = filepath.Clean(dir)
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if path == dir {
+ return nil
+ }
+ name := info.Name()
+ if strings.HasPrefix(name, ".") {
+ if info.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+ if !info.Mode().IsRegular() {
+ return nil
+ }
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if !utf8.Valid(data) {
+ log.Printf("%s: ignoring invalid UTF-8 data", path)
+ return nil
+ }
+ a.Files = append(a.Files, txtar.File{Name: strings.TrimPrefix(path, dir+string(filepath.Separator)), Data: data})
+ return nil
+ })
+
+ data := txtar.Format(a)
+ os.Stdout.Write(data)
+}
--- /dev/null
+package pkg1
+
+import "fmt"
+
+func F() {
+ fmt.Println("pkg1")
+}
--- /dev/null
+package pkg2
+
+import "fmt"
+
+func F() {
+ fmt.Println("pkg2")
+}
--- /dev/null
+package pkg2
--- /dev/null
+package pkg3
+
+import "fmt"
+
+func F() {
+ fmt.Println("pkg3")
+}
--- /dev/null
+package pkg3
+
+import "testing"
+
+func TestF(t *testing.T) {
+ F()
+}
--- /dev/null
+// This package is not a test-only package,
+// but it still matches the pattern ./testdata/testonly... when in cmd/go.
+
+package main
+
+func main() {}
--- /dev/null
+generated by: go run savedir.go vendormod
+
+-- a/foo/AUTHORS.txt --
+-- a/foo/CONTRIBUTORS --
+-- a/foo/LICENSE --
+-- a/foo/PATENTS --
+-- a/foo/COPYING --
+-- a/foo/COPYLEFT --
+-- a/foo/licensed-to-kill --
+-- w/LICENSE --
+-- x/NOTICE! --
+-- x/x2/LICENSE --
+-- mypkg/LICENSE.txt --
+-- a/foo/bar/b/main.go --
+package b
+-- a/foo/bar/b/main_test.go --
+package b
+
+import (
+ "os"
+ "testing"
+)
+
+func TestDir(t *testing.T) {
+ if _, err := os.Stat("../testdata/1"); err != nil {
+ t.Fatalf("testdata: %v", err)
+ }
+}
+-- a/foo/bar/c/main.go --
+package c
+-- a/foo/bar/c/main_test.go --
+package c
+
+import (
+ "os"
+ "testing"
+)
+
+func TestDir(t *testing.T) {
+ if _, err := os.Stat("../../../testdata/1"); err != nil {
+ t.Fatalf("testdata: %v", err)
+ }
+ if _, err := os.Stat("./testdata/1"); err != nil {
+ t.Fatalf("testdata: %v", err)
+ }
+}
+-- a/foo/bar/c/testdata/1 --
+-- a/foo/bar/testdata/1 --
+-- a/go.mod --
+module a
+-- a/main.go --
+package a
+-- a/main_test.go --
+package a
+
+import (
+ "os"
+ "testing"
+)
+
+func TestDir(t *testing.T) {
+ if _, err := os.Stat("./testdata/1"); err != nil {
+ t.Fatalf("testdata: %v", err)
+ }
+}
+-- a/testdata/1 --
+-- appengine.go --
+// +build appengine
+
+package m
+
+import _ "appengine"
+import _ "appengine/datastore"
+-- go.mod --
+module m
+
+require (
+ a v1.0.0
+ mysite/myname/mypkg v1.0.0
+ w v1.0.0 // indirect
+ x v1.0.0
+ y v1.0.0
+ z v1.0.0
+)
+
+replace (
+ a v1.0.0 => ./a
+ mysite/myname/mypkg v1.0.0 => ./mypkg
+ w v1.0.0 => ./w
+ x v1.0.0 => ./x
+ y v1.0.0 => ./y
+ z v1.0.0 => ./z
+)
+-- mypkg/go.mod --
+module me
+-- mypkg/mydir/d.go --
+package mydir
+-- subdir/v1_test.go --
+package m
+
+import _ "mysite/myname/mypkg/mydir"
+-- testdata1.go --
+package m
+
+import _ "a"
+-- testdata2.go --
+package m
+
+import _ "a/foo/bar/b"
+import _ "a/foo/bar/c"
+-- v1.go --
+package m
+
+import _ "x"
+-- v2.go --
+// +build abc
+
+package mMmMmMm
+
+import _ "y"
+-- v3.go --
+// +build !abc
+
+package m
+
+import _ "z"
+-- v4.go --
+// +build notmytag
+
+package m
+
+import _ "x/x1"
+-- w/go.mod --
+module w
+-- w/w.go --
+package w
+-- x/go.mod --
+module x
+-- x/testdata/x.txt --
+placeholder - want directory with no go files
+-- x/x.go --
+package x
+-- x/x1/x1.go --
+// +build notmytag
+
+package x1
+-- x/x2/dummy.txt --
+dummy
+-- x/x_test.go --
+package x
+
+import _ "w"
+-- y/go.mod --
+module y
+-- y/y.go --
+package y
+-- z/go.mod --
+module z
+-- z/z.go --
+package z
+++ /dev/null
-module m
-
-replace x v1.0.0 => ./x
-
-replace y v1.0.0 => ./y
-
-replace z v1.0.0 => ./z
-
-replace w v1.0.0 => ./w
-
-require (
- w v1.0.0
- x v1.0.0
- y v1.0.0
- z v1.0.0
-)
+++ /dev/null
-package m
-
-import _ "x"
+++ /dev/null
-// +build abc
-
-package mMmMmMm
-
-import _ "y"
+++ /dev/null
-// +build !abc
-
-package m
-
-import _ "z"
// Module legacy support does path rewriting very similar to vendoring.
-func TestModLegacy(t *testing.T) {
+func TestLegacyMod(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata/modlegacy"))
tg.run("build", "old/p1", "new/p1")
}
-func TestModLegacyGet(t *testing.T) {
+func TestLegacyModGet(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
tg := testgo(t)