"go/scanner"
"go/token"
"io/fs"
- "io/ioutil"
"os"
"path"
pathpkg "path"
"cmd/go/internal/str"
"cmd/go/internal/trace"
"cmd/internal/sys"
+
+ "golang.org/x/mod/module"
)
var IgnoreImports bool // control whether we ignore imports in packages
// Embedded files
EmbedPatterns []string `json:",omitempty"` // //go:embed patterns
- EmbedFiles []string `json:",omitempty"` // files and directories matched by EmbedPatterns
+ EmbedFiles []string `json:",omitempty"` // files matched by EmbedPatterns
// Cgo directives
CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler
TestGoFiles []string `json:",omitempty"` // _test.go files in package
TestImports []string `json:",omitempty"` // imports from TestGoFiles
TestEmbedPatterns []string `json:",omitempty"` // //go:embed patterns
- TestEmbedFiles []string `json:",omitempty"` // //files matched by EmbedPatterns
+ TestEmbedFiles []string `json:",omitempty"` // files matched by TestEmbedPatterns
XTestGoFiles []string `json:",omitempty"` // _test.go files outside package
XTestImports []string `json:",omitempty"` // imports from XTestGoFiles
XTestEmbedPatterns []string `json:",omitempty"` // //go:embed patterns
- XTestEmbedFiles []string `json:",omitempty"` // //files matched by EmbedPatterns
+ XTestEmbedFiles []string `json:",omitempty"` // files matched by XTestEmbedPatterns
}
// AllFiles returns the names of all the files considered for the package.
}
if path != stk.Top() {
- p = setErrorPos(p, importPos)
+ p.Error.setPos(importPos)
}
}
}
func (p *PackageError) Error() string {
+ // TODO(#43696): decide when to print the stack or the position based on
+ // the error type and whether the package is in the main module.
+ // Document the rationale.
if p.Pos != "" && (len(p.ImportStack) == 0 || !p.alwaysPrintStack) {
// Omit import stack. The full path to the file where the error
// is the most important thing.
return json.Marshal(perr)
}
+func (p *PackageError) setPos(posList []token.Position) {
+ if len(posList) == 0 {
+ return
+ }
+ pos := posList[0]
+ pos.Filename = base.ShortPath(pos.Filename)
+ p.Pos = pos.String()
+}
+
// 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
Err: ImportErrorf(path, "non-canonical import path %q: should be %q", path, pathpkg.Clean(path)),
}
p.Incomplete = true
- setErrorPos(p, importPos)
+ p.Error.setPos(importPos)
}
}
// Checked on every import because the rules depend on the code doing the importing.
if perr := disallowInternal(srcDir, parent, parentPath, p, stk); perr != p {
- return setErrorPos(perr, importPos)
+ perr.Error.setPos(importPos)
+ return perr
}
if mode&ResolveImport != 0 {
if perr := disallowVendor(srcDir, path, parentPath, p, stk); perr != p {
- return setErrorPos(perr, importPos)
+ perr.Error.setPos(importPos)
+ return perr
}
}
ImportStack: stk.Copy(),
Err: ImportErrorf(path, "import %q is a program, not an importable package", path),
}
- return setErrorPos(&perr, importPos)
+ perr.Error.setPos(importPos)
+ return &perr
}
if p.Internal.Local && parent != nil && !parent.Internal.Local {
ImportStack: stk.Copy(),
Err: err,
}
- return setErrorPos(&perr, importPos)
+ perr.Error.setPos(importPos)
+ return &perr
}
return p
}
-func setErrorPos(p *Package, importPos []token.Position) *Package {
- if len(importPos) > 0 {
- pos := importPos[0]
- pos.Filename = base.ShortPath(pos.Filename)
- p.Error.Pos = pos.String()
- }
- return p
-}
-
// loadPackageData loads information needed to construct a *Package. The result
// is cached, and later calls to loadPackageData for the same package will return
// the same data.
}
if strings.Contains(path, "@") {
- if cfg.ModulesEnabled {
- return nil, false, errors.New("can only use path@version syntax with 'go get'")
- } else {
- return nil, false, errors.New("cannot use path@version syntax in GOPATH mode")
- }
+ return nil, false, errors.New("can only use path@version syntax with 'go get' and 'go install' in module-aware mode")
}
// Determine canonical package path and directory.
// goModPath returns the module path in the go.mod in dir, if any.
func goModPath(dir string) (path string) {
return goModPathCache.Do(dir, func() interface{} {
- data, err := ioutil.ReadFile(filepath.Join(dir, "go.mod"))
+ data, err := os.ReadFile(filepath.Join(dir, "go.mod"))
if err != nil {
return ""
}
// Otherwise it is not possible to vendor just a/b/c and still import the
// non-vendored a/b. See golang.org/issue/13832.
func hasGoFiles(dir string) bool {
- fis, _ := ioutil.ReadDir(dir)
- for _, fi := range fis {
- if !fi.IsDir() && strings.HasSuffix(fi.Name(), ".go") {
+ files, _ := os.ReadDir(dir)
+ for _, f := range files {
+ if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
return true
}
}
// must be either in an explicit command-line argument,
// or on the importer side (indicated by a non-empty importPos).
if path != stk.Top() && len(importPos) > 0 {
- p = setErrorPos(p, importPos)
+ p.Error.setPos(importPos)
}
}
}
p.setLoadPackageDataError(err, path, stk, importPos)
}
- p.EmbedFiles, p.Internal.Embed, err = p.resolveEmbed(p.EmbedPatterns)
- if err != nil {
- setError(err)
- }
-
useBindir := p.Name == "main"
if !p.Standard {
switch cfg.BuildBuildmode {
// not work for any package that lacks a Target — such as a non-main
// package in module mode. We should probably fix that.
shlibnamefile := p.Target[:len(p.Target)-2] + ".shlibname"
- shlib, err := ioutil.ReadFile(shlibnamefile)
+ shlib, err := os.ReadFile(shlibnamefile)
if err != nil && !os.IsNotExist(err) {
base.Fatalf("reading shlibname: %v", err)
}
return
}
+ // Errors after this point are caused by this package, not the importing
+ // package. Pushing the path here prevents us from reporting the error
+ // with the position of the import declaration.
stk.Push(path)
defer stk.Pop()
+ p.EmbedFiles, p.Internal.Embed, err = resolveEmbed(p.Dir, p.EmbedPatterns)
+ if err != nil {
+ p.Incomplete = true
+ setError(err)
+ embedErr := err.(*EmbedError)
+ p.Error.setPos(p.Internal.Build.EmbedPatternPos[embedErr.Pattern])
+ }
+
// Check for case-insensitive collision of input files.
// To avoid problems on case-insensitive files, we reject any package
// where two different input files have equal names under a case-insensitive
}
}
+// An EmbedError indicates a problem with a go:embed directive.
+type EmbedError struct {
+ Pattern string
+ Err error
+}
+
+func (e *EmbedError) Error() string {
+ return fmt.Sprintf("pattern %s: %v", e.Pattern, e.Err)
+}
+
+func (e *EmbedError) Unwrap() error {
+ return e.Err
+}
+
// ResolveEmbed resolves //go:embed patterns and returns only the file list.
-// For use by go list to compute p.TestEmbedFiles and p.XTestEmbedFiles.
-func (p *Package) ResolveEmbed(patterns []string) []string {
- files, _, _ := p.resolveEmbed(patterns)
- return files
+// For use by go mod vendor to find embedded files it should copy into the
+// vendor directory.
+// TODO(#42504): Once go mod vendor uses load.PackagesAndErrors, just
+// call (*Package).ResolveEmbed
+func ResolveEmbed(dir string, patterns []string) ([]string, error) {
+ files, _, err := resolveEmbed(dir, patterns)
+ return files, err
}
// resolveEmbed resolves //go:embed patterns to precise file lists.
// It sets files to the list of unique files matched (for go list),
// and it sets pmap to the more precise mapping from
// patterns to files.
-// TODO(rsc): All these messages need position information for better error reports.
-func (p *Package) resolveEmbed(patterns []string) (files []string, pmap map[string][]string, err error) {
+func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[string][]string, err error) {
+ var pattern string
+ defer func() {
+ if err != nil {
+ err = &EmbedError{
+ Pattern: pattern,
+ Err: err,
+ }
+ }
+ }()
+
+ // TODO(rsc): All these messages need position information for better error reports.
pmap = make(map[string][]string)
have := make(map[string]int)
dirOK := make(map[string]bool)
pid := 0 // pattern ID, to allow reuse of have map
- for _, pattern := range patterns {
+ for _, pattern = range patterns {
pid++
// Check pattern is valid for //go:embed.
if _, err := path.Match(pattern, ""); err != nil || !validEmbedPattern(pattern) {
- return nil, nil, fmt.Errorf("pattern %s: invalid pattern syntax", pattern)
+ return nil, nil, fmt.Errorf("invalid pattern syntax")
}
// Glob to find matches.
- match, err := fsys.Glob(p.Dir + string(filepath.Separator) + filepath.FromSlash(pattern))
+ match, err := fsys.Glob(pkgdir + string(filepath.Separator) + filepath.FromSlash(pattern))
if err != nil {
- return nil, nil, fmt.Errorf("pattern %s: %v", pattern, err)
+ return nil, nil, err
}
// Filter list of matches down to the ones that will still exist when
// then there may be other things lying around, like symbolic links or .git directories.)
var list []string
for _, file := range match {
- rel := filepath.ToSlash(file[len(p.Dir)+1:]) // file, relative to p.Dir
+ rel := filepath.ToSlash(file[len(pkgdir)+1:]) // file, relative to p.Dir
what := "file"
info, err := fsys.Lstat(file)
// Check that directories along path do not begin a new module
// (do not contain a go.mod).
- for dir := file; len(dir) > len(p.Dir)+1 && !dirOK[dir]; dir = filepath.Dir(dir) {
+ for dir := file; len(dir) > len(pkgdir)+1 && !dirOK[dir]; dir = filepath.Dir(dir) {
if _, err := fsys.Stat(filepath.Join(dir, "go.mod")); err == nil {
- return nil, nil, fmt.Errorf("pattern %s: cannot embed %s %s: in different module", pattern, what, rel)
+ return nil, nil, fmt.Errorf("cannot embed %s %s: in different module", what, rel)
}
if dir != file {
if info, err := fsys.Lstat(dir); err == nil && !info.IsDir() {
- return nil, nil, fmt.Errorf("pattern %s: cannot embed %s %s: in non-directory %s", pattern, what, rel, dir[len(p.Dir)+1:])
+ return nil, nil, fmt.Errorf("cannot embed %s %s: in non-directory %s", what, rel, dir[len(pkgdir)+1:])
}
}
dirOK[dir] = true
if elem := filepath.Base(dir); isBadEmbedName(elem) {
if dir == file {
- return nil, nil, fmt.Errorf("pattern %s: cannot embed %s %s: invalid name %s", pattern, what, rel, elem)
+ return nil, nil, fmt.Errorf("cannot embed %s %s: invalid name %s", what, rel, elem)
} else {
- return nil, nil, fmt.Errorf("pattern %s: cannot embed %s %s: in invalid directory %s", pattern, what, rel, elem)
+ return nil, nil, fmt.Errorf("cannot embed %s %s: in invalid directory %s", what, rel, elem)
}
}
}
switch {
default:
- return nil, nil, fmt.Errorf("pattern %s: cannot embed irregular file %s", pattern, rel)
+ return nil, nil, fmt.Errorf("cannot embed irregular file %s", rel)
case info.Mode().IsRegular():
if have[rel] != pid {
if err != nil {
return err
}
- rel := filepath.ToSlash(path[len(p.Dir)+1:])
+ rel := filepath.ToSlash(path[len(pkgdir)+1:])
+ name := info.Name()
+ if path != file && (isBadEmbedName(name) || name[0] == '.' || name[0] == '_') {
+ // Ignore bad names, assuming they won't go into modules.
+ // Also avoid hidden files that user may not know about.
+ // See golang.org/issue/42328.
+ if info.IsDir() {
+ return fs.SkipDir
+ }
+ return nil
+ }
if info.IsDir() {
if _, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil {
return filepath.SkipDir
if !info.Mode().IsRegular() {
return nil
}
- if isBadEmbedName(info.Name()) {
- // Ignore bad names, assuming they won't go into modules.
- return nil
- }
count++
if have[rel] != pid {
have[rel] = pid
return nil, nil, err
}
if count == 0 {
- return nil, nil, fmt.Errorf("pattern %s: cannot embed directory %s: contains no embeddable files", pattern, rel)
+ return nil, nil, fmt.Errorf("cannot embed directory %s: contains no embeddable files", rel)
}
}
}
if len(list) == 0 {
- return nil, nil, fmt.Errorf("pattern %s: no matching files found", pattern)
+ return nil, nil, fmt.Errorf("no matching files found")
}
sort.Strings(list)
pmap[pattern] = list
// can't or won't be included in modules and therefore shouldn't be treated
// as existing for embedding.
func isBadEmbedName(name string) bool {
+ if err := module.CheckFilePath(name); err != nil {
+ return true
+ }
switch name {
+ // Empty string should be impossible but make it bad.
+ case "":
+ return true
// Version control directories won't be present in module.
case ".bzr", ".hg", ".git", ".svn":
return true
// argument where needed.
var ModResolveTests bool
-// Packages returns the packages named by the
-// command line arguments 'args'. If a named package
-// cannot be loaded at all (for example, if the directory does not exist),
-// then packages prints an error and does not include that
-// package in the results. However, if errors occur trying
-// to load dependencies of a named package, the named
-// package is still returned, with p.Incomplete = true
-// and details in p.DepsErrors.
-func Packages(ctx context.Context, args []string) []*Package {
- var pkgs []*Package
- for _, pkg := range PackagesAndErrors(ctx, args) {
- if pkg.Error != nil {
- base.Errorf("%v", pkg.Error)
- continue
- }
- pkgs = append(pkgs, pkg)
- }
- return pkgs
-}
-
-// PackagesAndErrors is like 'packages' but returns a
-// *Package for every argument, even the ones that
-// cannot be loaded at all.
-// The packages that fail to load will have p.Error != nil.
+// PackagesAndErrors returns the packages named by the command line arguments
+// 'patterns'. If a named package cannot be loaded, PackagesAndErrors returns
+// a *Package with the Error field describing the failure. If errors are found
+// loading imported packages, the DepsErrors field is set. The Incomplete field
+// may be set as well.
+//
+// To obtain a flat list of packages, use PackageList.
+// To report errors loading packages, use ReportPackageErrors.
func PackagesAndErrors(ctx context.Context, patterns []string) []*Package {
ctx, span := trace.StartSpan(ctx, "load.PackagesAndErrors")
defer span.Done()
return pkgs
}
-func setToolFlags(pkgs ...*Package) {
- for _, p := range PackageList(pkgs) {
- p.Internal.Asmflags = BuildAsmflags.For(p)
- p.Internal.Gcflags = BuildGcflags.For(p)
- p.Internal.Ldflags = BuildLdflags.For(p)
- p.Internal.Gccgoflags = BuildGccgoflags.For(p)
- }
-}
-
-// PackagesForBuild is like Packages but exits
-// if any of the packages or their dependencies have errors
-// (cannot be built).
-func PackagesForBuild(ctx context.Context, args []string) []*Package {
- pkgs := PackagesAndErrors(ctx, args)
+// CheckPackageErrors prints errors encountered loading pkgs and their
+// dependencies, then exits with a non-zero status if any errors were found.
+func CheckPackageErrors(pkgs []*Package) {
printed := map[*PackageError]bool{}
for _, pkg := range pkgs {
if pkg.Error != nil {
seen[pkg.ImportPath] = true
}
base.ExitIfErrors()
+}
- return pkgs
+func setToolFlags(pkgs ...*Package) {
+ for _, p := range PackageList(pkgs) {
+ p.Internal.Asmflags = BuildAsmflags.For(p)
+ p.Internal.Gcflags = BuildGcflags.For(p)
+ p.Internal.Ldflags = BuildLdflags.For(p)
+ p.Internal.Gccgoflags = BuildGccgoflags.For(p)
+ }
}
// GoFilesPackage creates a package for building a collection of Go files