Skip to content

Commit

Permalink
cmd/go/internal/modload: use a structured error for 'ambiguous import'
Browse files Browse the repository at this point in the history
This consolidates the construction of 'ambiguous import' errors to a
single location, ensuring consistency, and lays the groundwork for
automatic resolution in the future.

While we're at it, change "found" to "found package" to try to make
the cause of the error clearer.

Updates #32128
Updates #27899

Change-Id: I14a93593320e5c60d20b0eb686d0d5355763c30c
Reviewed-on: https://go-review.googlesource.com/c/go/+/196298
Run-TryBot: Bryan C. Mills <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Jay Conrod <[email protected]>
  • Loading branch information
Bryan C. Mills committed Sep 19, 2019
1 parent 83feeed commit d6c2f1e
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 12 deletions.
49 changes: 37 additions & 12 deletions src/cmd/go/internal/modload/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package modload

import (
"bytes"
"errors"
"fmt"
"go/build"
Expand Down Expand Up @@ -52,6 +51,41 @@ func (e *ImportMissingError) Unwrap() error {
return e.QueryErr
}

// An AmbiguousImportError indicates an import of a package found in multiple
// modules in the build list, or found in both the main module and its vendor
// directory.
type AmbiguousImportError struct {
ImportPath string
Dirs []string
Modules []module.Version // Either empty or 1:1 with Dirs.
}

func (e *AmbiguousImportError) Error() string {
locType := "modules"
if len(e.Modules) == 0 {
locType = "directories"
}

var buf strings.Builder
fmt.Fprintf(&buf, "ambiguous import: found package %s in multiple %s:", e.ImportPath, locType)

for i, dir := range e.Dirs {
buf.WriteString("\n\t")
if i < len(e.Modules) {
m := e.Modules[i]
buf.WriteString(m.Path)
if m.Version != "" {
fmt.Fprintf(&buf, " %s", m.Version)
}
fmt.Fprintf(&buf, " (%s)", dir)
} else {
buf.WriteString(dir)
}
}

return buf.String()
}

// 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
Expand Down Expand Up @@ -96,7 +130,7 @@ func Import(path string) (m module.Version, dir string, err error) {
mainDir, mainOK := dirInModule(path, targetPrefix, 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)
return module.Version{}, "", &AmbiguousImportError{ImportPath: path, Dirs: []string{mainDir, vendorDir}}
}
// Prefer to return main directory if there is one,
// Note that we're not checking that the package exists.
Expand Down Expand Up @@ -136,16 +170,7 @@ func Import(path string) (m module.Version, dir string, err error) {
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())
return module.Version{}, "", &AmbiguousImportError{ImportPath: path, Dirs: dirs, Modules: mods}
}

// Look up module containing the package, for addition to the build list.
Expand Down
49 changes: 49 additions & 0 deletions src/cmd/go/testdata/script/mod_ambiguous_import.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
env GO111MODULE=on

cd $WORK

# An import provided by two different modules should be flagged as an error.
! go build ./importx
stderr '^importx[/\\]importx.go:2:8: ambiguous import: found package example.com/a/x in multiple modules:\n\texample.com/a v0.1.0 \('$WORK'[/\\]a[/\\]x\)\n\texample.com/a/x v0.1.0 \('$WORK'[/\\]ax\)$'

# However, it should not be an error if that import is unused.
go build ./importy

# An import provided by both the main module and the vendor directory
# should be flagged as an error only when -mod=vendor is set.
# TODO: This error message is a bit redundant.
mkdir vendor/example.com/m/importy
cp $WORK/importy/importy.go vendor/example.com/m/importy/importy.go
go build example.com/m/importy
! go build -mod=vendor example.com/m/importy
stderr '^can.t load package: package example.com/m/importy: ambiguous import: found package example.com/m/importy in multiple directories:\n\t'$WORK'[/\\]importy\n\t'$WORK'[/\\]vendor[/\\]example.com[/\\]m[/\\]importy$'

-- $WORK/go.mod --
module example.com/m
go 1.14
require (
example.com/a v0.1.0
example.com/a/x v0.1.0
)
replace (
example.com/a v0.1.0 => ./a
example.com/a/x v0.1.0 => ./ax
)
-- $WORK/importx/importx.go --
package importx
import _ "example.com/a/x"
-- $WORK/importy/importy.go --
package importy
import _ "example.com/a/y"
-- $WORK/a/go.mod --
module example.com/a
go 1.14
-- $WORK/a/x/x.go --
package x
-- $WORK/a/y/y.go --
package y
-- $WORK/ax/go.mod --
module example.com/a/x
go 1.14
-- $WORK/ax/x.go --
package x

0 comments on commit d6c2f1e

Please sign in to comment.