import (
"bytes"
+ "encoding/json"
"errors"
"fmt"
"go/build"
type PackageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error
- Err string // the error itself
- IsImportCycle bool `json:"-"` // the error is an import cycle
- Hard bool `json:"-"` // whether the error is soft or hard; soft errors are ignored in some places
+ Err error // the error itself
+ IsImportCycle bool // the error is an import cycle
+ Hard bool // whether the error is soft or hard; soft errors are ignored in some places
}
func (p *PackageError) Error() string {
if p.Pos != "" {
// Omit import stack. The full path to the file where the error
// is the most important thing.
- return p.Pos + ": " + p.Err
+ return p.Pos + ": " + p.Err.Error()
}
- if len(p.ImportStack) == 0 {
- return p.Err
+
+ // If the error is an ImportPathError, and the last path on the stack appears
+ // in the error message, omit that path from the stack to avoid repetition.
+ // If an ImportPathError wraps another ImportPathError that matches the
+ // last path on the stack, we don't omit the path. An error like
+ // "package A imports B: error loading C caused by B" would not be clearer
+ // if "imports B" were omitted.
+ stack := p.ImportStack
+ var ierr ImportPathError
+ if len(stack) > 0 && errors.As(p.Err, &ierr) && ierr.ImportPath() == stack[len(stack)-1] {
+ stack = stack[:len(stack)-1]
+ }
+ if len(stack) == 0 {
+ return p.Err.Error()
+ }
+ return "package " + strings.Join(stack, "\n\timports ") + ": " + p.Err.Error()
+}
+
+// PackageError implements MarshalJSON so that Err is marshaled as a string
+// and non-essential fields are omitted.
+func (p *PackageError) MarshalJSON() ([]byte, error) {
+ perr := struct {
+ ImportStack []string
+ Pos string
+ Err string
+ }{p.ImportStack, p.Pos, p.Err.Error()}
+ return json.Marshal(perr)
+}
+
+// ImportPathError is a type of error that prevents a package from being loaded
+// for a given import path. When such a package is loaded, a *Package is
+// returned with Err wrapping an ImportPathError: the error is attached to
+// the imported package, not the importing package.
+//
+// The string returned by ImportPath must appear in the string returned by
+// Error. Errors that wrap ImportPathError (such as PackageError) may omit
+// the import path.
+type ImportPathError interface {
+ error
+ ImportPath() string
+}
+
+type importError struct {
+ importPath string
+ err error // created with fmt.Errorf
+}
+
+var _ ImportPathError = (*importError)(nil)
+
+func ImportErrorf(path, format string, args ...interface{}) ImportPathError {
+ err := &importError{importPath: path, err: fmt.Errorf(format, args...)}
+ if errStr := err.Error(); !strings.Contains(errStr, path) {
+ panic(fmt.Sprintf("path %q not in error %q", path, errStr))
}
- return "package " + strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err
+ return err
+}
+
+func (e *importError) Error() string {
+ return e.err.Error()
+}
+
+func (e *importError) Unwrap() error {
+ // Don't return e.err directly, since we're only wrapping an error if %w
+ // was passed to ImportErrorf.
+ return errors.Unwrap(e.err)
+}
+
+func (e *importError) ImportPath() string {
+ return e.importPath
}
// An ImportStack is a stack of import paths, possibly with the suffix " (test)" appended.
ImportPath: path,
Error: &PackageError{
ImportStack: stk.Copy(),
- Err: err.Error(),
+ Err: err,
},
},
}
if !cfg.ModulesEnabled && path != cleanImport(path) {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: fmt.Sprintf("non-canonical import path: %q should be %q", path, pathpkg.Clean(path)),
+ Err: fmt.Errorf("non-canonical import path: %q should be %q", path, pathpkg.Clean(path)),
}
p.Incomplete = true
}
perr := *p
perr.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: fmt.Sprintf("import %q is a program, not an importable package", path),
+ Err: ImportErrorf(path, "import %q is a program, not an importable package", path),
}
return setErrorPos(&perr, importPos)
}
if p.Internal.Local && parent != nil && !parent.Internal.Local {
perr := *p
- errMsg := fmt.Sprintf("local import %q in non-local package", path)
+ var err error
if path == "." {
- errMsg = "cannot import current directory"
+ err = ImportErrorf(path, "%s: cannot import current directory", path)
+ } else {
+ err = ImportErrorf(path, "local import %q in non-local package", path)
}
perr.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: errMsg,
+ Err: err,
}
return setErrorPos(&perr, importPos)
}
if p.Error == nil {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: "import cycle not allowed",
+ Err: errors.New("import cycle not allowed"),
IsImportCycle: true,
}
}
perr := *p
perr.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: "use of internal package " + p.ImportPath + " not allowed",
+ Err: ImportErrorf(p.ImportPath, "use of internal package "+p.ImportPath+" not allowed"),
}
perr.Incomplete = true
return &perr
perr := *p
perr.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: "must be imported as " + path[i+len("vendor/"):],
+ Err: ImportErrorf(path, "%s must be imported as %s", path, path[i+len("vendor/"):]),
}
perr.Incomplete = true
return &perr
perr := *p
perr.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: "use of vendored package not allowed",
+ Err: errors.New("use of vendored package not allowed"),
}
perr.Incomplete = true
return &perr
err = base.ExpandScanner(err)
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: err.Error(),
+ Err: err,
}
return
}
// Report an error when the old code.google.com/p/go.tools paths are used.
if InstallTargetDir(p) == StalePath {
newPath := strings.Replace(p.ImportPath, "code.google.com/p/go.", "golang.org/x/", 1)
- e := fmt.Sprintf("the %v command has moved; use %v instead.", p.ImportPath, newPath)
+ e := ImportErrorf(p.ImportPath, "the %v command has moved; use %v instead.", p.ImportPath, newPath)
p.Error = &PackageError{Err: e}
return
}
if f1 != "" {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: fmt.Sprintf("case-insensitive file name collision: %q and %q", f1, f2),
+ Err: fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2),
}
return
}
if !SafeArg(file) || strings.HasPrefix(file, "_cgo_") {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: fmt.Sprintf("invalid input file name %q", file),
+ Err: fmt.Errorf("invalid input file name %q", file),
}
return
}
if name := pathpkg.Base(p.ImportPath); !SafeArg(name) {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: fmt.Sprintf("invalid input directory name %q", name),
+ Err: fmt.Errorf("invalid input directory name %q", name),
}
return
}
if !SafeArg(p.ImportPath) {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: fmt.Sprintf("invalid import path %q", p.ImportPath),
+ Err: ImportErrorf(p.ImportPath, "invalid import path %q", p.ImportPath),
}
return
}
// code; see issue #16050).
}
- setError := func(msg string) {
+ setError := func(err error) {
p.Error = &PackageError{
ImportStack: stk.Copy(),
- Err: msg,
+ Err: err,
}
}
// The gc toolchain only permits C source files with cgo or SWIG.
if len(p.CFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() && cfg.BuildContext.Compiler == "gc" {
- setError(fmt.Sprintf("C source files not allowed when not using cgo or SWIG: %s", strings.Join(p.CFiles, " ")))
+ setError(fmt.Errorf("C source files not allowed when not using cgo or SWIG: %s", strings.Join(p.CFiles, " ")))
return
}
// C++, Objective-C, and Fortran source files are permitted only with cgo or SWIG,
// regardless of toolchain.
if len(p.CXXFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() {
- setError(fmt.Sprintf("C++ source files not allowed when not using cgo or SWIG: %s", strings.Join(p.CXXFiles, " ")))
+ setError(fmt.Errorf("C++ source files not allowed when not using cgo or SWIG: %s", strings.Join(p.CXXFiles, " ")))
return
}
if len(p.MFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() {
- setError(fmt.Sprintf("Objective-C source files not allowed when not using cgo or SWIG: %s", strings.Join(p.MFiles, " ")))
+ setError(fmt.Errorf("Objective-C source files not allowed when not using cgo or SWIG: %s", strings.Join(p.MFiles, " ")))
return
}
if len(p.FFiles) > 0 && !p.UsesCgo() && !p.UsesSwig() {
- setError(fmt.Sprintf("Fortran source files not allowed when not using cgo or SWIG: %s", strings.Join(p.FFiles, " ")))
+ setError(fmt.Errorf("Fortran source files not allowed when not using cgo or SWIG: %s", strings.Join(p.FFiles, " ")))
return
}
if other := foldPath[fold]; other == "" {
foldPath[fold] = p.ImportPath
} else if other != p.ImportPath {
- setError(fmt.Sprintf("case-insensitive import collision: %q and %q", p.ImportPath, other))
+ setError(ImportErrorf(p.ImportPath, "case-insensitive import collision: %q and %q", p.ImportPath, other))
return
}
pkg.Internal.CmdlineFiles = true
pkg.Name = f
pkg.Error = &PackageError{
- Err: fmt.Sprintf("named files must be .go files: %s", pkg.Name),
+ Err: fmt.Errorf("named files must be .go files: %s", pkg.Name),
}
return pkg
}
"time"
"cmd/go/internal/cfg"
+ "cmd/go/internal/load"
"cmd/go/internal/modfetch"
"cmd/go/internal/module"
"cmd/go/internal/par"
)
type ImportMissingError struct {
- ImportPath string
- Module module.Version
- QueryErr error
+ Path string
+ Module module.Version
+ QueryErr error
// newMissingVersion is set to a newer version of Module if one is present
// in the build list. When set, we can't automatically upgrade.
newMissingVersion string
}
+var _ load.ImportPathError = (*ImportMissingError)(nil)
+
func (e *ImportMissingError) Error() string {
if e.Module.Path == "" {
- if str.HasPathPrefix(e.ImportPath, "cmd") {
- return fmt.Sprintf("package %s is not in GOROOT (%s)", e.ImportPath, filepath.Join(cfg.GOROOT, "src", e.ImportPath))
+ if str.HasPathPrefix(e.Path, "cmd") {
+ return fmt.Sprintf("package %s is not in GOROOT (%s)", e.Path, filepath.Join(cfg.GOROOT, "src", e.Path))
}
if e.QueryErr != nil {
- return fmt.Sprintf("cannot find module providing package %s: %v", e.ImportPath, e.QueryErr)
+ return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr)
}
- return "cannot find module providing package " + e.ImportPath
+ return "cannot find module providing package " + e.Path
}
- return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.ImportPath)
+ return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path)
}
func (e *ImportMissingError) Unwrap() error {
return e.QueryErr
}
+func (e *ImportMissingError) ImportPath() string {
+ return e.Path
+}
+
// 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.
return module.Version{}, dir, nil
}
if str.HasPathPrefix(path, "cmd") {
- return module.Version{}, "", &ImportMissingError{ImportPath: path}
+ return module.Version{}, "", &ImportMissingError{Path: path}
}
// -mod=vendor is special.
}
_, ok := dirInModule(path, m.Path, root, isLocal)
if ok {
- return m, "", &ImportMissingError{ImportPath: path, Module: m}
+ return m, "", &ImportMissingError{Path: path, Module: m}
}
}
}
if errors.Is(err, os.ErrNotExist) {
// Return "cannot find module providing package […]" instead of whatever
// low-level error QueryPackage produced.
- return module.Version{}, "", &ImportMissingError{ImportPath: path, QueryErr: err}
+ return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: err}
} else {
return module.Version{}, "", err
}
}
}
}
- return m, "", &ImportMissingError{ImportPath: path, Module: m, newMissingVersion: newMissingVersion}
+ return m, "", &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
}
// maybeInModule reports whether, syntactically,