]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.9] cmd/go: add minimal module-awareness for legacy operation
authorRuss Cox <rsc@golang.org>
Wed, 25 Apr 2018 15:06:41 +0000 (11:06 -0400)
committerRuss Cox <rsc@golang.org>
Mon, 4 Jun 2018 14:58:00 +0000 (14:58 +0000)
We want authors to be able to publish code that works with both
the current standard go command and the planned new go command
support for modules. If authors have tagged their code v2 or later,
semantic import versioning means the import paths must include a
v2 path element after the path prefix naming the module.
One option for making this convention compatible with original go get
is to move code into a v2 subdirectory of the root.
That makes sense for some authors, but many authors would prefer
not to move all the code into a v2 subdirectory for a transition and
then move it back up once we everyone has a module-aware go command.

Instead, this CL teaches the old (non-module-aware) go command
a tiny amount about modules and their import paths, to expand
the options for authors who want to publish compatible packages.
If an author has a v2 of a package, say my/thing/v2/sub/pkg,
in the my/thing repo's sub/pkg subdirectory (no v2 in the file system path),
then old go get continues to import that package as my/thing/sub/pkg.
But when go get is processing code in any module (code in a tree with
a go.mod file) and encounters a path like my/thing/v2/sub/pkg,
it will check to see if my/thing/go.mod says "module my/thing/v2".
If so, the go command will read the import my/thing/v2/sub/pkg
as if it said my/thing/sub/pkg, which is the correct "old" import path
for the package in question.

This CL will be back-ported to Go 1.10 and Go 1.9 as well.

Once users have updated to the latest Go point releases containing
this new logic, authors will be able to update to using modules
within their own repos, including using semantic import paths
with vN path elements, and old go get will still be able to consume
those repositories.

This CL also makes "go get" ignore meta go-import lines using
the new "mod" VCS type. This allows a package to specify both
a "mod" type and a "git" type, to present more efficient module
access to module-aware go but still present a Git repo to the old
"go get".

Fixes #24751.
Fixes #25069.

This backport to Go 1.9 also had to pick up p.Internal.RawImports
from CL 74750 and CL 74356 and use it to prepare an updated
set of -importmap arguments for the compiler. (The old code only
understood vendor-related rewriting of import paths.)

Backport fixes #25140.

Change-Id: I378955613a0d63834d4f50f121f4db7e4d87dc0a
Reviewed-on: https://go-review.googlesource.com/115298
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
18 files changed:
src/cmd/go/internal/get/discovery.go
src/cmd/go/internal/get/get.go
src/cmd/go/internal/get/pkg_test.go
src/cmd/go/internal/list/list.go
src/cmd/go/internal/load/pkg.go
src/cmd/go/internal/test/test.go
src/cmd/go/internal/work/build.go
src/cmd/go/testdata/modlegacy/src/new/go.mod [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/new.go [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/p1/p1.go [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/p2/p2.go [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/sub/go.mod [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/sub/inner/go.mod [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/sub/inner/x/x.go [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/new/sub/x/v1/y/y.go [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/old/p1/p1.go [new file with mode: 0644]
src/cmd/go/testdata/modlegacy/src/old/p2/p2.go [new file with mode: 0644]
src/cmd/go/vendor_test.go

index b2918dbb4f31aaa4488856e68113fee8efecdf2c..97aa1d7e8d6344d2061c01f1de48966ffc24f000 100644 (file)
@@ -55,6 +55,13 @@ func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) {
                        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],
index e5dda643e40c02626c87dfe1abd041e411bf21fc..54460ec38fcb6b0db1ef3ae133f86d5e1e6f9e06 100644 (file)
@@ -210,7 +210,7 @@ var downloadRootCache = map[string]bool{}
 // download runs the download half of the get command
 // for the package named by the argument.
 func download(arg string, parent *load.Package, stk *load.ImportStack, mode int) {
-       if mode&load.UseVendor != 0 {
+       if mode&load.ResolveImport != 0 {
                // Caller is responsible for expanding vendor paths.
                panic("internal error: download mode has useVendor set")
        }
index b8937a57ec9122d036edfe559c297c9ca4e9c8dc..1179d86693acee898af4fb29fe989565f1107ac0 100644 (file)
@@ -47,6 +47,20 @@ var parseMetaGoImportsTests = []struct {
                        {"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">`,
+               []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">`,
+               []metaImport{
+                       {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
+               },
+       },
        {
                `<head>
                <meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
index 241d0894c01431eb2169924f4087b6d9dc2a53fd..6ef3314ea3b687a80d16db6500c7692754fad410 100644 (file)
@@ -201,8 +201,8 @@ func runList(cmd *base.Command, args []string) {
 
        for _, pkg := range loadpkgs(args) {
                // Show vendor-expanded paths in listing
-               pkg.TestImports = pkg.Vendored(pkg.TestImports)
-               pkg.XTestImports = pkg.Vendored(pkg.XTestImports)
+               pkg.TestImports = pkg.Resolve(pkg.TestImports)
+               pkg.XTestImports = pkg.Resolve(pkg.XTestImports)
 
                do(&pkg.PackagePublic)
        }
index 6ca89d946db45d32e76df09ccab47fa5ab10d778..de3f5b8fa26466ffd92770a4bc728edd3ce501fd 100644 (file)
@@ -6,6 +6,7 @@
 package load
 
 import (
+       "bytes"
        "crypto/sha1"
        "fmt"
        "go/build"
@@ -16,6 +17,7 @@ import (
        "path/filepath"
        "runtime"
        "sort"
+       "strconv"
        "strings"
        "unicode"
        "unicode/utf8"
@@ -122,8 +124,9 @@ func (p *Package) AllFiles() []string {
 type PackageInternal struct {
        // Unexported fields are not part of the public API.
        Build        *build.Package
-       Pkgdir       string // overrides build.PkgDir
-       Imports      []*Package
+       Pkgdir       string     // overrides build.PkgDir
+       Imports      []*Package // this package's direct imports
+       RawImports   []string   // this package's original imports as they appear in the text of the program
        Deps         []*Package
        GoFiles      []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths
        SFiles       []string
@@ -169,7 +172,7 @@ func (e *NoGoError) Error() string {
        return "no Go files in " + e.Package.Dir
 }
 
-// Vendored returns the vendor-resolved version of imports,
+// Resolve returns the resolved version of imports,
 // which should be p.TestImports or p.XTestImports, NOT p.Imports.
 // The imports in p.TestImports and p.XTestImports are not recursively
 // loaded during the initial load of p, so they list the imports found in
@@ -179,14 +182,14 @@ func (e *NoGoError) Error() string {
 // can produce better error messages if it starts with the original paths.
 // The initial load of p loads all the non-test imports and rewrites
 // the vendored paths, so nothing should ever call p.vendored(p.Imports).
-func (p *Package) Vendored(imports []string) []string {
+func (p *Package) Resolve(imports []string) []string {
        if len(imports) > 0 && len(p.Imports) > 0 && &imports[0] == &p.Imports[0] {
-               panic("internal error: p.vendored(p.Imports) called")
+               panic("internal error: p.Resolve(p.Imports) called")
        }
        seen := make(map[string]bool)
        var all []string
        for _, path := range imports {
-               path = VendoredImportPath(p, path)
+               path = ResolveImportPath(p, path)
                if !seen[path] {
                        seen[path] = true
                        all = append(all, path)
@@ -245,6 +248,7 @@ func (p *Package) copyBuild(pp *build.Package) {
        // We modify p.Imports in place, so make copy now.
        p.Imports = make([]string, len(pp.Imports))
        copy(p.Imports, pp.Imports)
+       p.Internal.RawImports = pp.Imports
        p.TestGoFiles = pp.TestGoFiles
        p.TestImports = pp.TestImports
        p.XTestGoFiles = pp.XTestGoFiles
@@ -380,16 +384,16 @@ func makeImportValid(r rune) rune {
 
 // Mode flags for loadImport and download (in get.go).
 const (
-       // useVendor means that loadImport should do vendor expansion
-       // (provided the vendoring experiment is enabled).
-       // That is, useVendor means that the import path came from
-       // a source file and has not been vendor-expanded yet.
-       // Every import path should be loaded initially with useVendor,
-       // and then the expanded version (with the /vendor/ in it) gets
-       // recorded as the canonical import path. At that point, future loads
-       // of that package must not pass useVendor, because
+       // ResolveImport means that loadImport should do import path expansion.
+       // That is, ResolveImport means that the import path came from
+       // a source file and has not been expanded yet to account for
+       // vendoring or possible module adjustment.
+       // Every import path should be loaded initially with ResolveImport,
+       // and then the expanded version (for example with the /vendor/ in it)
+       // gets recorded as the canonical import path. At that point, future loads
+       // of that package must not pass ResolveImport, because
        // disallowVendor will reject direct use of paths containing /vendor/.
-       UseVendor = 1 << iota
+       ResolveImport = 1 << iota
 
        // getTestDeps is for download (part of "go get") and indicates
        // that test dependencies should be fetched too.
@@ -414,12 +418,12 @@ func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPo
        isLocal := build.IsLocalImport(path)
        if isLocal {
                importPath = dirToImportPath(filepath.Join(srcDir, path))
-       } else if mode&UseVendor != 0 {
-               // We do our own vendor resolution, because we want to
+       } else if mode&ResolveImport != 0 {
+               // We do our own path resolution, because we want to
                // find out the key to use in packageCache without the
                // overhead of repeated calls to buildContext.Import.
                // The code is also needed in a few other places anyway.
-               path = VendoredImportPath(parent, path)
+               path = ResolveImportPath(parent, path)
                importPath = path
        }
 
@@ -439,7 +443,7 @@ func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPo
                // TODO: After Go 1, decide when to pass build.AllowBinary here.
                // See issue 3268 for mistakes to avoid.
                buildMode := build.ImportComment
-               if mode&UseVendor == 0 || path != origPath {
+               if mode&ResolveImport == 0 || path != origPath {
                        // Not vendoring, or we already found the vendored path.
                        buildMode |= build.IgnoreVendor
                }
@@ -470,7 +474,7 @@ func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPo
        if perr := disallowInternal(srcDir, p, stk); perr != p {
                return setErrorPos(perr, importPos)
        }
-       if mode&UseVendor != 0 {
+       if mode&ResolveImport != 0 {
                if perr := disallowVendor(srcDir, origPath, p, stk); perr != p {
                        return setErrorPos(perr, importPos)
                }
@@ -529,24 +533,31 @@ func isDir(path string) bool {
        return result
 }
 
-// VendoredImportPath returns the expansion of path when it appears in parent.
-// If parent is x/y/z, then path might expand to x/y/z/vendor/path, x/y/vendor/path,
-// x/vendor/path, vendor/path, or else stay path if none of those exist.
-// VendoredImportPath returns the expanded path or, if no expansion is found, the original.
-func VendoredImportPath(parent *Package, path string) (found string) {
-       if parent == nil || parent.Root == "" {
-               return path
-       }
+// ResolveImportPath returns the true meaning of path when it appears in parent.
+// 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).
+func ResolveImportPath(parent *Package, path string) (found string) {
+       found = VendoredImportPath(parent, path)
+       if found != path {
+               return found
+       }
+       return ModuleImportPath(parent, path)
+}
 
-       dir := filepath.Clean(parent.Dir)
-       root := filepath.Join(parent.Root, "src")
-       if !hasFilePathPrefix(dir, root) || parent.ImportPath != "command-line-arguments" && filepath.Join(root, parent.ImportPath) != dir {
+// dirAndRoot returns the source directory and workspace root
+// for the package p, guaranteeing that root is a path prefix of dir.
+func dirAndRoot(p *Package) (dir, root string) {
+       dir = filepath.Clean(p.Dir)
+       root = filepath.Join(p.Root, "src")
+       if !hasFilePathPrefix(dir, root) || p.ImportPath != "command-line-arguments" && filepath.Join(root, p.ImportPath) != dir {
                // Look for symlinks before reporting error.
                dir = expandPath(dir)
                root = expandPath(root)
        }
 
-       if !hasFilePathPrefix(dir, root) || len(dir) <= len(root) || dir[len(root)] != filepath.Separator || parent.ImportPath != "command-line-arguments" && !parent.Internal.Local && filepath.Join(root, parent.ImportPath) != dir {
+       if !hasFilePathPrefix(dir, root) || len(dir) <= len(root) || dir[len(root)] != filepath.Separator || p.ImportPath != "command-line-arguments" && !p.Internal.Local && filepath.Join(root, p.ImportPath) != dir {
                base.Fatalf("unexpected directory layout:\n"+
                        "       import path: %s\n"+
                        "       root: %s\n"+
@@ -554,14 +565,28 @@ func VendoredImportPath(parent *Package, path string) (found string) {
                        "       expand root: %s\n"+
                        "       expand dir: %s\n"+
                        "       separator: %s",
-                       parent.ImportPath,
-                       filepath.Join(parent.Root, "src"),
-                       filepath.Clean(parent.Dir),
+                       p.ImportPath,
+                       filepath.Join(p.Root, "src"),
+                       filepath.Clean(p.Dir),
                        root,
                        dir,
                        string(filepath.Separator))
        }
 
+       return dir, root
+}
+
+// VendoredImportPath returns the vendor-expansion of path when it appears in parent.
+// If parent is x/y/z, then path might expand to x/y/z/vendor/path, x/y/vendor/path,
+// x/vendor/path, vendor/path, or else stay path if none of those exist.
+// VendoredImportPath returns the expanded path or, if no expansion is found, the original.
+func VendoredImportPath(parent *Package, path string) (found string) {
+       if parent == nil || parent.Root == "" {
+               return path
+       }
+
+       dir, root := dirAndRoot(parent)
+
        vpath := "vendor/" + path
        for i := len(dir); i >= len(root); i-- {
                if i < len(dir) && dir[i] != filepath.Separator {
@@ -604,6 +629,164 @@ func VendoredImportPath(parent *Package, path string) (found string) {
        return path
 }
 
+var (
+       modulePrefix   = []byte("\nmodule ")
+       goModPathCache = make(map[string]string)
+)
+
+// goModPath returns the module path in the go.mod in dir, if any.
+func goModPath(dir string) (path string) {
+       path, ok := goModPathCache[dir]
+       if ok {
+               return path
+       }
+       defer func() {
+               goModPathCache[dir] = path
+       }()
+
+       data, err := ioutil.ReadFile(filepath.Join(dir, "go.mod"))
+       if err != nil {
+               return ""
+       }
+       var i int
+       if bytes.HasPrefix(data, modulePrefix[1:]) {
+               i = 0
+       } else {
+               i = bytes.Index(data, modulePrefix)
+               if i < 0 {
+                       return ""
+               }
+               i++
+       }
+       line := data[i:]
+
+       // Cut line at \n, drop trailing \r if present.
+       if j := bytes.IndexByte(line, '\n'); j >= 0 {
+               line = line[:j]
+       }
+       if line[len(line)-1] == '\r' {
+               line = line[:len(line)-1]
+       }
+       line = line[len("module "):]
+
+       // If quoted, unquote.
+       path = strings.TrimSpace(string(line))
+       if path != "" && path[0] == '"' {
+               s, err := strconv.Unquote(path)
+               if err != nil {
+                       return ""
+               }
+               path = s
+       }
+       return path
+}
+
+// findVersionElement returns the slice indices of the final version element /vN in path.
+// If there is no such element, it returns -1, -1.
+func findVersionElement(path string) (i, j int) {
+       j = len(path)
+       for i = len(path) - 1; i >= 0; i-- {
+               if path[i] == '/' {
+                       if isVersionElement(path[i:j]) {
+                               return i, j
+                       }
+                       j = i
+               }
+       }
+       return -1, -1
+}
+
+// isVersionElement reports whether s is a well-formed path version element:
+// v2, v3, v10, etc, but not v0, v05, v1.
+func isVersionElement(s string) bool {
+       if len(s) < 3 || s[0] != '/' || s[1] != 'v' || s[2] == '0' || s[2] == '1' && len(s) == 3 {
+               return false
+       }
+       for i := 2; i < len(s); i++ {
+               if s[i] < '0' || '9' < s[i] {
+                       return false
+               }
+       }
+       return true
+}
+
+// ModuleImportPath translates import paths found in go modules
+// back down to paths that can be resolved in ordinary builds.
+//
+// Define “new” code as code with a go.mod file in the same directory
+// or a parent directory. If an import in new code says x/y/v2/z but
+// x/y/v2/z does not exist and x/y/go.mod says “module x/y/v2”,
+// then go build will read the import as x/y/z instead.
+// See golang.org/issue/25069.
+func ModuleImportPath(parent *Package, path string) (found string) {
+       if parent == nil || parent.Root == "" {
+               return path
+       }
+
+       // If there are no vN elements in path, leave it alone.
+       // (The code below would do the same, but only after
+       // some other file system accesses that we can avoid
+       // here by returning early.)
+       if i, _ := findVersionElement(path); i < 0 {
+               return path
+       }
+
+       dir, root := dirAndRoot(parent)
+
+       // Consider dir and parents, up to and including root.
+       for i := len(dir); i >= len(root); i-- {
+               if i < len(dir) && dir[i] != filepath.Separator {
+                       continue
+               }
+               if goModPath(dir[:i]) != "" {
+                       goto HaveGoMod
+               }
+       }
+       // This code is not in a tree with a go.mod,
+       // so apply no changes to the path.
+       return path
+
+HaveGoMod:
+       // This import is in a tree with a go.mod.
+       // Allow it to refer to code in GOPATH/src/x/y/z as x/y/v2/z
+       // if GOPATH/src/x/y/go.mod says module "x/y/v2",
+
+       // If x/y/v2/z exists, use it unmodified.
+       if bp, _ := cfg.BuildContext.Import(path, "", build.IgnoreVendor); bp.Dir != "" {
+               return path
+       }
+
+       // Otherwise look for a go.mod supplying a version element.
+       // Some version-like elements may appear in paths but not
+       // be module versions; we skip over those to look for module
+       // versions. For example the module m/v2 might have a
+       // package m/v2/api/v1/foo.
+       limit := len(path)
+       for limit > 0 {
+               i, j := findVersionElement(path[:limit])
+               if i < 0 {
+                       return path
+               }
+               if bp, _ := cfg.BuildContext.Import(path[:i], "", build.IgnoreVendor); bp.Dir != "" {
+                       if mpath := goModPath(bp.Dir); mpath != "" {
+                               // Found a valid go.mod file, so we're stopping the search.
+                               // If the path is m/v2/p and we found m/go.mod that says
+                               // "module m/v2", then we return "m/p".
+                               if mpath == path[:j] {
+                                       return path[:i] + path[j:]
+                               }
+                               // Otherwise just return the original path.
+                               // We didn't find anything worth rewriting,
+                               // and the go.mod indicates that we should
+                               // not consider parent directories.
+                               return path
+                       }
+               }
+               limit = i
+       }
+       return path
+}
+
 // hasGoFiles reports whether dir contains any files with names ending in .go.
 // For a vendor check we must exclude directories that contain no .go files.
 // Otherwise it is not possible to vendor just a/b/c and still import the
@@ -842,23 +1025,23 @@ const (
 
 // goTools is a map of Go program import path to install target directory.
 var GoTools = map[string]targetDir{
-       "cmd/addr2line": ToTool,
-       "cmd/api":       ToTool,
-       "cmd/asm":       ToTool,
-       "cmd/compile":   ToTool,
-       "cmd/cgo":       ToTool,
-       "cmd/cover":     ToTool,
-       "cmd/dist":      ToTool,
-       "cmd/doc":       ToTool,
-       "cmd/fix":       ToTool,
-       "cmd/link":      ToTool,
-       "cmd/newlink":   ToTool,
-       "cmd/nm":        ToTool,
-       "cmd/objdump":   ToTool,
-       "cmd/pack":      ToTool,
-       "cmd/pprof":     ToTool,
-       "cmd/trace":     ToTool,
-       "cmd/vet":       ToTool,
+       "cmd/addr2line":                        ToTool,
+       "cmd/api":                              ToTool,
+       "cmd/asm":                              ToTool,
+       "cmd/compile":                          ToTool,
+       "cmd/cgo":                              ToTool,
+       "cmd/cover":                            ToTool,
+       "cmd/dist":                             ToTool,
+       "cmd/doc":                              ToTool,
+       "cmd/fix":                              ToTool,
+       "cmd/link":                             ToTool,
+       "cmd/newlink":                          ToTool,
+       "cmd/nm":                               ToTool,
+       "cmd/objdump":                          ToTool,
+       "cmd/pack":                             ToTool,
+       "cmd/pprof":                            ToTool,
+       "cmd/trace":                            ToTool,
+       "cmd/vet":                              ToTool,
        "code.google.com/p/go.tools/cmd/cover": StalePath,
        "code.google.com/p/go.tools/cmd/godoc": StalePath,
        "code.google.com/p/go.tools/cmd/vet":   StalePath,
@@ -1112,7 +1295,7 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) *Package
                if path == "C" {
                        continue
                }
-               p1 := LoadImport(path, p.Dir, p, stk, p.Internal.Build.ImportPos[path], UseVendor)
+               p1 := LoadImport(path, p.Dir, p, stk, p.Internal.Build.ImportPos[path], ResolveImport)
                if p.Standard && p.Error == nil && !p1.Standard && p1.Error == nil {
                        p.Error = &PackageError{
                                ImportStack: stk.Copy(),
index ebebffd77777dbce5f864249b5312fb7f42eabb3..839add34ee99c0f1b8e1d4ca5a5ce3cc7168cd2f 100644 (file)
@@ -429,7 +429,7 @@ var testMainDeps = map[string]bool{
        // Dependencies for testmain.
        "testing":                   true,
        "testing/internal/testdeps": true,
-       "os": true,
+       "os":                        true,
 }
 
 func runTest(cmd *base.Command, args []string) {
@@ -499,10 +499,10 @@ func runTest(cmd *base.Command, args []string) {
                        for _, path := range p.Imports {
                                deps[path] = true
                        }
-                       for _, path := range p.Vendored(p.TestImports) {
+                       for _, path := range p.Resolve(p.TestImports) {
                                deps[path] = true
                        }
-                       for _, path := range p.Vendored(p.XTestImports) {
+                       for _, path := range p.Resolve(p.XTestImports) {
                                deps[path] = true
                        }
                }
@@ -715,8 +715,9 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
        var imports, ximports []*load.Package
        var stk load.ImportStack
        stk.Push(p.ImportPath + " (test)")
+       rawTestImports := str.StringList(p.TestImports)
        for i, path := range p.TestImports {
-               p1 := load.LoadImport(path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], load.UseVendor)
+               p1 := load.LoadImport(path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], load.ResolveImport)
                if p1.Error != nil {
                        return nil, nil, nil, p1.Error
                }
@@ -742,8 +743,9 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
        stk.Pop()
        stk.Push(p.ImportPath + "_test")
        pxtestNeedsPtest := false
+       rawXTestImports := str.StringList(p.XTestImports)
        for i, path := range p.XTestImports {
-               p1 := load.LoadImport(path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], load.UseVendor)
+               p1 := load.LoadImport(path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], load.ResolveImport)
                if p1.Error != nil {
                        return nil, nil, nil, p1.Error
                }
@@ -810,8 +812,20 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
                ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)
                ptest.GoFiles = append(ptest.GoFiles, p.TestGoFiles...)
                ptest.Internal.Target = ""
-               ptest.Imports = str.StringList(p.Imports, p.TestImports)
-               ptest.Internal.Imports = append(append([]*load.Package{}, p.Internal.Imports...), imports...)
+               // Note: The preparation of the compiler import map requires that common
+               // indexes in ptest.Imports, ptest.Internal.Imports, and ptest.Internal.RawImports
+               // all line up (but RawImports can be shorter than the others).
+               // That is, for 0 ≤ i < len(RawImports),
+               // RawImports[i] is the import string in the program text,
+               // Imports[i] is the expanded import string (vendoring applied or relative path expanded away),
+               // and Internal.Imports[i] is the corresponding *Package.
+               // Any implicitly added imports appear in Imports and Internal.Imports
+               // but not RawImports (because they were not in the source code).
+               // We insert TestImports, imports, and rawTestImports at the start of
+               // these lists to preserve the alignment.
+               ptest.Imports = str.StringList(p.TestImports, p.Imports)
+               ptest.Internal.Imports = append(imports, p.Internal.Imports...)
+               ptest.Internal.RawImports = str.StringList(rawTestImports, p.Internal.RawImports)
                ptest.Internal.Pkgdir = testDir
                ptest.Internal.Fake = true
                ptest.Internal.ForceLibrary = true
@@ -856,10 +870,11 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
                                Build: &build.Package{
                                        ImportPos: p.Internal.Build.XTestImportPos,
                                },
-                               Imports:  ximports,
-                               Pkgdir:   testDir,
-                               Fake:     true,
-                               External: true,
+                               Imports:    ximports,
+                               RawImports: rawXTestImports,
+                               Pkgdir:     testDir,
+                               Fake:       true,
+                               External:   true,
                        },
                }
                if pxtestNeedsPtest {
index e93162910553593e0a90fc77ae48622337a6e34e..346ceac7a9cf6b6d23e18d621056a4ebf164c213 100644 (file)
@@ -2262,11 +2262,10 @@ func (gcToolchain) gc(b *Builder, p *load.Package, archive, obj string, asmhdr b
                gcargs = append(gcargs, "-dwarf=false")
        }
 
-       for _, path := range p.Imports {
-               if i := strings.LastIndex(path, "/vendor/"); i >= 0 {
-                       gcargs = append(gcargs, "-importmap", path[i+len("/vendor/"):]+"="+path)
-               } else if strings.HasPrefix(path, "vendor/") {
-                       gcargs = append(gcargs, "-importmap", path[len("vendor/"):]+"="+path)
+       for i, raw := range p.Internal.RawImports {
+               final := p.Imports[i]
+               if final != raw {
+                       gcargs = append(gcargs, "-importmap", raw+"="+final)
                }
        }
 
diff --git a/src/cmd/go/testdata/modlegacy/src/new/go.mod b/src/cmd/go/testdata/modlegacy/src/new/go.mod
new file mode 100644 (file)
index 0000000..d0dd46d
--- /dev/null
@@ -0,0 +1 @@
+module "new/v2"
diff --git a/src/cmd/go/testdata/modlegacy/src/new/new.go b/src/cmd/go/testdata/modlegacy/src/new/new.go
new file mode 100644 (file)
index 0000000..e99c47a
--- /dev/null
@@ -0,0 +1,3 @@
+package new
+
+import _ "new/v2/p2"
diff --git a/src/cmd/go/testdata/modlegacy/src/new/p1/p1.go b/src/cmd/go/testdata/modlegacy/src/new/p1/p1.go
new file mode 100644 (file)
index 0000000..4539f40
--- /dev/null
@@ -0,0 +1,7 @@
+package p1
+
+import _ "old/p2"
+import _ "new/v2"
+import _ "new/v2/p2"
+import _ "new/sub/v2/x/v1/y" // v2 is module, v1 is directory in module
+import _ "new/sub/inner/x"   // new/sub/inner/go.mod overrides new/sub/go.mod
diff --git a/src/cmd/go/testdata/modlegacy/src/new/p2/p2.go b/src/cmd/go/testdata/modlegacy/src/new/p2/p2.go
new file mode 100644 (file)
index 0000000..9b9052f
--- /dev/null
@@ -0,0 +1 @@
+package p2
diff --git a/src/cmd/go/testdata/modlegacy/src/new/sub/go.mod b/src/cmd/go/testdata/modlegacy/src/new/sub/go.mod
new file mode 100644 (file)
index 0000000..484d20c
--- /dev/null
@@ -0,0 +1 @@
+module new/sub/v2
diff --git a/src/cmd/go/testdata/modlegacy/src/new/sub/inner/go.mod b/src/cmd/go/testdata/modlegacy/src/new/sub/inner/go.mod
new file mode 100644 (file)
index 0000000..ba39345
--- /dev/null
@@ -0,0 +1 @@
+module new/sub/inner
diff --git a/src/cmd/go/testdata/modlegacy/src/new/sub/inner/x/x.go b/src/cmd/go/testdata/modlegacy/src/new/sub/inner/x/x.go
new file mode 100644 (file)
index 0000000..823aafd
--- /dev/null
@@ -0,0 +1 @@
+package x
diff --git a/src/cmd/go/testdata/modlegacy/src/new/sub/x/v1/y/y.go b/src/cmd/go/testdata/modlegacy/src/new/sub/x/v1/y/y.go
new file mode 100644 (file)
index 0000000..789ca71
--- /dev/null
@@ -0,0 +1 @@
+package y
diff --git a/src/cmd/go/testdata/modlegacy/src/old/p1/p1.go b/src/cmd/go/testdata/modlegacy/src/old/p1/p1.go
new file mode 100644 (file)
index 0000000..9052748
--- /dev/null
@@ -0,0 +1,5 @@
+package p1
+
+import _ "old/p2"
+import _ "new/p1"
+import _ "new"
diff --git a/src/cmd/go/testdata/modlegacy/src/old/p2/p2.go b/src/cmd/go/testdata/modlegacy/src/old/p2/p2.go
new file mode 100644 (file)
index 0000000..9b9052f
--- /dev/null
@@ -0,0 +1 @@
+package p2
index 739ce5a5a45db5ccb13b8e429c4811438d81665f..d68e4f94fe55f98e19f9ea4de56989ebb453fe73 100644 (file)
@@ -327,3 +327,34 @@ func TestVendor12156(t *testing.T) {
        tg.grepStderrNot("panic", "panicked")
        tg.grepStderr(`cannot find package "x"`, "wrong error")
 }
+
+// Module legacy support does path rewriting very similar to vendoring.
+
+func TestModLegacy(t *testing.T) {
+       tg := testgo(t)
+       defer tg.cleanup()
+       tg.setenv("GOPATH", filepath.Join(tg.pwd(), "testdata/modlegacy"))
+       tg.run("list", "-f", "{{.Imports}}", "old/p1")
+       tg.grepStdout("new/p1", "old/p1 should import new/p1")
+       tg.run("list", "-f", "{{.Imports}}", "new/p1")
+       tg.grepStdout("new/p2", "new/p1 should import new/p2 (not new/v2/p2)")
+       tg.grepStdoutNot("new/v2", "new/p1 should NOT import new/v2*")
+       tg.grepStdout("new/sub/x/v1/y", "new/p1 should import new/sub/x/v1/y (not new/sub/v2/x/v1/y)")
+       tg.grepStdoutNot("new/sub/v2", "new/p1 should NOT import new/sub/v2*")
+       tg.grepStdout("new/sub/inner/x", "new/p1 should import new/sub/inner/x (no rewrites)")
+       tg.run("build", "old/p1", "new/p1")
+}
+
+func TestModLegacyGet(t *testing.T) {
+       testenv.MustHaveExternalNetwork(t)
+
+       tg := testgo(t)
+       defer tg.cleanup()
+       tg.makeTempdir()
+       tg.setenv("GOPATH", tg.path("."))
+       tg.run("get", "vcs-test.golang.org/git/modlegacy1-old.git/p1")
+       tg.run("list", "-f", "{{.Deps}}", "vcs-test.golang.org/git/modlegacy1-old.git/p1")
+       tg.grepStdout("new.git/p2", "old/p1 should depend on new/p2")
+       tg.grepStdoutNot("new.git/v2/p2", "old/p1 should NOT depend on new/v2/p2")
+       tg.run("build", "vcs-test.golang.org/git/modlegacy1-old.git/p1", "vcs-test.golang.org/git/modlegacy1-new.git/p1")
+}