aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2018-04-25 11:06:41 -0400
committerRuss Cox <rsc@golang.org>2018-06-04 14:58:00 +0000
commit05604d7450b7cfcd946762039ee46e9ab85297d6 (patch)
tree404537ab22d52d0846da2ff39c8e990d39d02f0c
parent6130a52f21dffb50aa57d7665a05805cbf044c5c (diff)
downloadgo-05604d7450b7cfcd946762039ee46e9ab85297d6.tar.gz
go-05604d7450b7cfcd946762039ee46e9ab85297d6.zip
[release-branch.go1.9] cmd/go: add minimal module-awareness for legacy operation
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>
-rw-r--r--src/cmd/go/internal/get/discovery.go7
-rw-r--r--src/cmd/go/internal/get/get.go2
-rw-r--r--src/cmd/go/internal/get/pkg_test.go14
-rw-r--r--src/cmd/go/internal/list/list.go4
-rw-r--r--src/cmd/go/internal/load/pkg.go289
-rw-r--r--src/cmd/go/internal/test/test.go37
-rw-r--r--src/cmd/go/internal/work/build.go9
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/go.mod1
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/new.go3
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/p1/p1.go7
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/p2/p2.go1
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/sub/go.mod1
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/sub/inner/go.mod1
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/sub/inner/x/x.go1
-rw-r--r--src/cmd/go/testdata/modlegacy/src/new/sub/x/v1/y/y.go1
-rw-r--r--src/cmd/go/testdata/modlegacy/src/old/p1/p1.go5
-rw-r--r--src/cmd/go/testdata/modlegacy/src/old/p2/p2.go1
-rw-r--r--src/cmd/go/vendor_test.go31
18 files changed, 343 insertions, 72 deletions
diff --git a/src/cmd/go/internal/get/discovery.go b/src/cmd/go/internal/get/discovery.go
index b2918dbb4f..97aa1d7e8d 100644
--- a/src/cmd/go/internal/get/discovery.go
+++ b/src/cmd/go/internal/get/discovery.go
@@ -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],
diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go
index e5dda643e4..54460ec38f 100644
--- a/src/cmd/go/internal/get/get.go
+++ b/src/cmd/go/internal/get/get.go
@@ -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")
}
diff --git a/src/cmd/go/internal/get/pkg_test.go b/src/cmd/go/internal/get/pkg_test.go
index b8937a57ec..1179d86693 100644
--- a/src/cmd/go/internal/get/pkg_test.go
+++ b/src/cmd/go/internal/get/pkg_test.go
@@ -48,6 +48,20 @@ var parseMetaGoImportsTests = []struct {
},
},
{
+ `<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">
</head>`,
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 241d0894c0..6ef3314ea3 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -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)
}
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 6ca89d946d..de3f5b8fa2 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -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(),
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index ebebffd777..839add34ee 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -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 {
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index e931629105..346ceac7a9 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -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
index 0000000000..d0dd46d314
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/go.mod
@@ -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
index 0000000000..e99c47a6a8
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/new.go
@@ -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
index 0000000000..4539f40919
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/p1/p1.go
@@ -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
index 0000000000..9b9052f541
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/p2/p2.go
@@ -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
index 0000000000..484d20c6b2
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/sub/go.mod
@@ -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
index 0000000000..ba3934541f
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/sub/inner/go.mod
@@ -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
index 0000000000..823aafd071
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/sub/inner/x/x.go
@@ -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
index 0000000000..789ca715ec
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/new/sub/x/v1/y/y.go
@@ -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
index 0000000000..90527483ab
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/old/p1/p1.go
@@ -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
index 0000000000..9b9052f541
--- /dev/null
+++ b/src/cmd/go/testdata/modlegacy/src/old/p2/p2.go
@@ -0,0 +1 @@
+package p2
diff --git a/src/cmd/go/vendor_test.go b/src/cmd/go/vendor_test.go
index 739ce5a5a4..d68e4f94fe 100644
--- a/src/cmd/go/vendor_test.go
+++ b/src/cmd/go/vendor_test.go
@@ -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")
+}