cmd/go: add global ignore mechanism

This CL adds the ignore directive which enables users to tell the Go
Command to skip traversing into a given directory.

This behaves similar to how '_' or 'testdata' are currently treated.
This mainly has benefits for go list and go mod tidy.
This does not affect what is packed into a module.

Fixes: #42965
Change-Id: I232e27c1a065bb6eb2d210dbddad0208426a1fdd
Reviewed-on: https://go-review.googlesource.com/c/go/+/643355
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Michael Matloob <matloob@google.com>
This commit is contained in:
Sam Thanawalla 2025-01-08 18:30:50 +00:00
parent b450b5409d
commit f529d56508
13 changed files with 796 additions and 22 deletions

View file

@ -1259,9 +1259,13 @@
// The -tool=path and -droptool=path flags add and drop a tool declaration
// for the given path.
//
// The -ignore=path and -dropignore=path flags add and drop a ignore declaration
// for the given path.
//
// The -godebug, -dropgodebug, -require, -droprequire, -exclude, -dropexclude,
// -replace, -dropreplace, -retract, -dropretract, -tool, and -droptool editing
// flags may be repeated, and the changes are applied in the order given.
// -replace, -dropreplace, -retract, -dropretract, -tool, -droptool, -ignore,
// and -dropignore editing flags may be repeated, and the changes are applied
// in the order given.
//
// The -print flag prints the final go.mod in its text format instead of
// writing it back to go.mod.
@ -1316,6 +1320,10 @@
// Path string
// }
//
// type Ignore struct {
// Path string
// }
//
// Retract entries representing a single version (not an interval) will have
// the "Low" and "High" fields set to the same value.
//

View file

@ -2927,8 +2927,7 @@ func PackagesAndErrors(ctx context.Context, opts PackageOpts, patterns []string)
}
matches, _ = modload.LoadPackages(ctx, modOpts, patterns...)
} else {
noModRoots := []string{}
matches = search.ImportPaths(patterns, noModRoots)
matches = search.ImportPaths(patterns)
}
var (

View file

@ -90,9 +90,13 @@ like "v1.2.3" or a closed interval like "[v1.1.0,v1.1.9]". Note that
The -tool=path and -droptool=path flags add and drop a tool declaration
for the given path.
The -ignore=path and -dropignore=path flags add and drop a ignore declaration
for the given path.
The -godebug, -dropgodebug, -require, -droprequire, -exclude, -dropexclude,
-replace, -dropreplace, -retract, -dropretract, -tool, and -droptool editing
flags may be repeated, and the changes are applied in the order given.
-replace, -dropreplace, -retract, -dropretract, -tool, -droptool, -ignore,
and -dropignore editing flags may be repeated, and the changes are applied
in the order given.
The -print flag prints the final go.mod in its text format instead of
writing it back to go.mod.
@ -147,6 +151,10 @@ writing it back to go.mod. The JSON output corresponds to these Go types:
Path string
}
type Ignore struct {
Path string
}
Retract entries representing a single version (not an interval) will have
the "Low" and "High" fields set to the same value.
@ -190,6 +198,8 @@ func init() {
cmdEdit.Flag.Var(flagFunc(flagDropRetract), "dropretract", "")
cmdEdit.Flag.Var(flagFunc(flagTool), "tool", "")
cmdEdit.Flag.Var(flagFunc(flagDropTool), "droptool", "")
cmdEdit.Flag.Var(flagFunc(flagIgnore), "ignore", "")
cmdEdit.Flag.Var(flagFunc(flagDropIgnore), "dropignore", "")
base.AddBuildFlagsNX(&cmdEdit.Flag)
base.AddChdirFlag(&cmdEdit.Flag)
@ -546,6 +556,24 @@ func flagDropTool(arg string) {
})
}
// flagIgnore implements the -ignore flag.
func flagIgnore(arg string) {
edits = append(edits, func(f *modfile.File) {
if err := f.AddIgnore(arg); err != nil {
base.Fatalf("go: -ignore=%s: %v", arg, err)
}
})
}
// flagDropIgnore implements the -dropignore flag.
func flagDropIgnore(arg string) {
edits = append(edits, func(f *modfile.File) {
if err := f.DropIgnore(arg); err != nil {
base.Fatalf("go: -dropignore=%s: %v", arg, err)
}
})
}
// fileJSON is the -json output data structure.
type fileJSON struct {
Module editModuleJSON
@ -556,6 +584,7 @@ type fileJSON struct {
Replace []replaceJSON
Retract []retractJSON
Tool []toolJSON
Ignore []ignoreJSON
}
type editModuleJSON struct {
@ -584,6 +613,10 @@ type toolJSON struct {
Path string
}
type ignoreJSON struct {
Path string
}
// editPrintJSON prints the -json output.
func editPrintJSON(modFile *modfile.File) {
var f fileJSON
@ -614,6 +647,9 @@ func editPrintJSON(modFile *modfile.File) {
for _, t := range modFile.Tool {
f.Tool = append(f.Tool, toolJSON{t.Path})
}
for _, i := range modFile.Ignore {
f.Ignore = append(f.Ignore, ignoreJSON{i.Path})
}
data, err := json.MarshalIndent(&f, "", "\t")
if err != nil {
base.Fatalf("go: internal error: %v", err)

View file

@ -94,6 +94,7 @@ type modFileIndex struct {
require map[module.Version]requireMeta
replace map[module.Version]module.Version
exclude map[module.Version]bool
ignore []string
}
type requireMeta struct {
@ -455,7 +456,11 @@ func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsF
for _, x := range modFile.Exclude {
i.exclude[x.Mod] = true
}
if modFile.Ignore != nil {
for _, x := range modFile.Ignore {
i.ignore = append(i.ignore, x.Path)
}
}
return i
}
@ -539,6 +544,7 @@ type modFileSummary struct {
module module.Version
goVersion string
toolchain string
ignore []string
pruning modPruning
require []module.Version
retract []retraction
@ -714,6 +720,11 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
if f.Toolchain != nil {
summary.toolchain = f.Toolchain.Name
}
if f.Ignore != nil {
for _, i := range f.Ignore {
summary.ignore = append(summary.ignore, i.Path)
}
}
if len(f.Require) > 0 {
summary.require = make([]module.Version, 0, len(f.Require)+1)
for _, req := range f.Require {

View file

@ -74,7 +74,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
)
q := par.NewQueue(runtime.GOMAXPROCS(0))
ignorePatternsMap := parseIgnorePatterns(ctx, treeCanMatch, modules)
walkPkgs := func(root, importPathRoot string, prune pruning) {
_, span := trace.StartSpan(ctx, "walkPkgs "+root)
defer span.Done()
@ -82,7 +82,8 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
// If the root itself is a symlink to a directory,
// we want to follow it (see https://go.dev/issue/50807).
// Add a trailing separator to force that to happen.
root = str.WithFilePathSeparator(filepath.Clean(root))
cleanRoot := filepath.Clean(root)
root = str.WithFilePathSeparator(cleanRoot)
err := fsys.WalkDir(root, func(pkgDir string, d fs.DirEntry, err error) error {
if err != nil {
m.AddError(err)
@ -91,6 +92,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
want := true
elem := ""
relPkgDir := filepath.ToSlash(pkgDir[len(root):])
// Don't use GOROOT/src but do walk down into it.
if pkgDir == root {
@ -102,10 +104,15 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
_, elem = filepath.Split(pkgDir)
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
want = false
} else if ignorePatternsMap[cleanRoot] != nil && ignorePatternsMap[cleanRoot].ShouldIgnore(relPkgDir) {
if cfg.BuildX {
fmt.Fprintf(os.Stderr, "# ignoring directory %s\n", pkgDir)
}
want = false
}
}
name := path.Join(importPathRoot, filepath.ToSlash(pkgDir[len(root):]))
name := path.Join(importPathRoot, relPkgDir)
if !treeCanMatch(name) {
want = false
}
@ -303,3 +310,43 @@ func MatchInModule(ctx context.Context, pattern string, m module.Version, tags m
}
return match
}
// parseIgnorePatterns collects all ignore patterns associated with the
// provided list of modules.
// It returns a map of module root -> *search.IgnorePatterns.
func parseIgnorePatterns(ctx context.Context, treeCanMatch func(string) bool, modules []module.Version) map[string]*search.IgnorePatterns {
ignorePatternsMap := make(map[string]*search.IgnorePatterns)
for _, mod := range modules {
if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
continue
}
var modRoot string
var ignorePatterns []string
if MainModules.Contains(mod.Path) {
modRoot = MainModules.ModRoot(mod)
if modRoot == "" {
continue
}
modIndex := MainModules.Index(mod)
if modIndex == nil {
continue
}
ignorePatterns = modIndex.ignore
} else if cfg.BuildMod != "vendor" {
// Skip getting ignore patterns for vendored modules because they
// do not have go.mod files.
var err error
modRoot, _, err = fetch(ctx, mod)
if err != nil {
continue
}
summary, err := goModSummary(mod)
if err != nil {
continue
}
ignorePatterns = summary.ignore
}
ignorePatternsMap[modRoot] = search.NewIgnorePatterns(ignorePatterns)
}
return ignorePatternsMap
}

View file

@ -17,6 +17,8 @@ import (
"path"
"path/filepath"
"strings"
"golang.org/x/mod/modfile"
)
// A Match represents the result of matching a single package pattern.
@ -208,6 +210,69 @@ func (m *Match) MatchPackages() {
}
}
// IgnorePatterns is normalized with normalizePath.
type IgnorePatterns struct {
relativePatterns []string
anyPatterns []string
}
// ShouldIgnore returns true if the given directory should be ignored
// based on the ignore patterns.
//
// An ignore pattern "x" will cause any file or directory named "x"
// (and its entire subtree) to be ignored, regardless of its location
// within the module.
//
// An ignore pattern "./x" will only cause the specific file or directory
// named "x" at the root of the module to be ignored.
// Wildcards in ignore patterns are not supported.
func (ignorePatterns *IgnorePatterns) ShouldIgnore(dir string) bool {
if dir == "" {
return false
}
dir = normalizePath(dir)
for _, pattern := range ignorePatterns.relativePatterns {
if strings.HasPrefix(dir, pattern) {
return true
}
}
for _, pattern := range ignorePatterns.anyPatterns {
if strings.Contains(dir, pattern) {
return true
}
}
return false
}
func NewIgnorePatterns(patterns []string) *IgnorePatterns {
var relativePatterns, anyPatterns []string
for _, pattern := range patterns {
ignorePatternPath, isRelative := strings.CutPrefix(pattern, "./")
ignorePatternPath = normalizePath(ignorePatternPath)
if isRelative {
relativePatterns = append(relativePatterns, ignorePatternPath)
} else {
anyPatterns = append(anyPatterns, ignorePatternPath)
}
}
return &IgnorePatterns{
relativePatterns: relativePatterns,
anyPatterns: anyPatterns,
}
}
// normalizePath adds slashes to the front and end of the given path.
func normalizePath(path string) string {
path = filepath.ToSlash(path)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if !strings.HasSuffix(path, "/") {
path += "/"
}
return path
}
// MatchDirs sets m.Dirs to a non-nil slice containing all directories that
// potentially match a local pattern. The pattern must begin with an absolute
// path, or "./", or "../". On Windows, the pattern may use slash or backslash
@ -253,16 +318,18 @@ func (m *Match) MatchDirs(modRoots []string) {
// We need to preserve the ./ for pattern matching
// and in the returned import paths.
if len(modRoots) > 1 {
var modRoot string
if len(modRoots) > 0 {
abs, err := filepath.Abs(dir)
if err != nil {
m.AddError(err)
return
}
var found bool
for _, modRoot := range modRoots {
if modRoot != "" && str.HasFilePathPrefix(abs, modRoot) {
for _, mr := range modRoots {
if mr != "" && str.HasFilePathPrefix(abs, mr) {
found = true
modRoot = mr
}
}
if !found {
@ -274,6 +341,7 @@ func (m *Match) MatchDirs(modRoots []string) {
}
}
ignorePatterns := parseIgnorePatterns(modRoot)
// If dir is actually a symlink to a directory,
// we want to follow it (see https://go.dev/issue/50807).
// Add a trailing separator to force that to happen.
@ -305,6 +373,17 @@ func (m *Match) MatchDirs(modRoots []string) {
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
return filepath.SkipDir
}
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
if ignorePatterns != nil && ignorePatterns.ShouldIgnore(InDir(absPath, modRoot)) {
if cfg.BuildX {
fmt.Fprintf(os.Stderr, "# ignoring directory %s\n", absPath)
}
return filepath.SkipDir
}
if !top && cfg.ModulesEnabled {
// Ignore other modules found in subdirectories.
@ -353,20 +432,20 @@ func WarnUnmatched(matches []*Match) {
// ImportPaths returns the matching paths to use for the given command line.
// It calls ImportPathsQuiet and then WarnUnmatched.
func ImportPaths(patterns, modRoots []string) []*Match {
matches := ImportPathsQuiet(patterns, modRoots)
func ImportPaths(patterns []string) []*Match {
matches := ImportPathsQuiet(patterns)
WarnUnmatched(matches)
return matches
}
// ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
func ImportPathsQuiet(patterns, modRoots []string) []*Match {
func ImportPathsQuiet(patterns []string) []*Match {
patterns = CleanPatterns(patterns)
out := make([]*Match, 0, len(patterns))
for _, a := range patterns {
m := NewMatch(a)
if m.IsLocal() {
m.MatchDirs(modRoots)
m.MatchDirs(nil)
// Change the file import path to a regular import path if the package
// is in GOPATH or GOROOT. We don't report errors here; LoadImport
@ -509,3 +588,25 @@ func InDir(path, dir string) string {
}
return ""
}
// parseIgnorePatterns reads the go.mod file at the given module root
// and extracts the ignore patterns defined within it.
// If modRoot is empty, it returns nil.
func parseIgnorePatterns(modRoot string) *IgnorePatterns {
if modRoot == "" {
return nil
}
data, err := os.ReadFile(filepath.Join(modRoot, "go.mod"))
if err != nil {
return nil
}
modFile, err := modfile.Parse("go.mod", data, nil)
if err != nil {
return nil
}
var patterns []string
for _, i := range modFile.Ignore {
patterns = append(patterns, i.Path)
}
return NewIgnorePatterns(patterns)
}

View file

@ -19,7 +19,7 @@ env GOCACHE=$WORK/tmp/cache
# loading to properly affect the import graph.)
go build runtime/cgo
go build -x -o main main.go
go build -x -o main ./...
cp stderr commands.txt
cat header.txt commands.txt
cp stdout test.sh
@ -42,6 +42,13 @@ import "C"
func main() {
print("hello\n")
}
-- go.mod --
module example
go 1.24
ignore foo
-- foo/foo.txt --
-- header.txt --
set -e
-- hello.txt --

View file

@ -0,0 +1,147 @@
# go build ./... should skip 'ignore' directives
# See golang.org/issue/42965
env ROOT=$WORK${/}gopath${/}src
# no ignore directive; should not skip any directories.
cp go.mod.orig go.mod
go build -x ./...
stderr 'packagefile example/foo/secret'
stderr 'packagefile example/pkg/foo'
stderr 'packagefile example/pkg/fo'
! stderr 'ignoring directory'
# ignored ./foo should be skipped.
cp go.mod.relative go.mod
go build -x ./...
stderr 'packagefile example/pkg/foo'
stderr 'packagefile example/pkg/fo'
! stderr 'packagefile example/foo/secret'
stderr 'ignoring directory '$ROOT''${/}'foo'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored foo; any foo should be skipped.
cp go.mod.any go.mod
go build -x ./...
stderr 'packagefile example/pkg/fo'
! stderr 'packagefile example/pkg/foo'
! stderr 'packagefile example/foo/secret'
stderr 'ignoring directory '$ROOT''${/}'foo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# non-existent ignore; should not skip any directories.
cp go.mod.dne go.mod
go build -x ./...
stderr 'packagefile example/foo/secret'
stderr 'packagefile example/pkg/foo'
stderr 'packagefile example/pkg/fo'
! stderr 'ignoring directory'
# ignored fo; should not skip foo/ and should skip fo/
cp go.mod.partial go.mod
go build -x ./...
! stderr 'ignoring directory '$ROOT''${/}'foo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'fo$'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored pkg/foo; should skip pkg/foo/
cp go.mod.tree go.mod
go build -x ./...
stderr 'packagefile example/foo/secret'
stderr 'packagefile example/pkg/fo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored /pkg/foo/; should skip pkg/foo/
cp go.mod.sep1 go.mod
go build -x ./...
stderr 'packagefile example/foo/secret'
stderr 'packagefile example/pkg/fo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored pkg/foo/; should skip pkg/foo/
cp go.mod.sep2 go.mod
go build -x ./...
stderr 'packagefile example/foo/secret'
stderr 'packagefile example/pkg/fo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored /pkg/foo; should skip pkg/foo/
cp go.mod.sep3 go.mod
go build -x ./...
stderr 'packagefile example/foo/secret'
stderr 'packagefile example/pkg/fo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
-- foo/secret/secret.go --
package main
func main() {}
-- pkg/foo/foo.go --
package main
func main() {}
-- pkg/fo/fo.go --
package main
func main() {}
-- go.mod.orig --
module example
go 1.24
-- go.mod.relative --
module example
go 1.24
ignore ./foo
-- go.mod.any --
module example
go 1.24
ignore foo
-- go.mod.dne --
module example
go 1.24
ignore bar
-- go.mod.partial --
module example
go 1.24
ignore fo
-- go.mod.tree --
module example
go 1.24
ignore pkg/foo
-- go.mod.sep1 --
module example
go 1.24
ignore /pkg/foo/
-- go.mod.sep2 --
module example
go 1.24
ignore pkg/foo/
-- go.mod.sep3 --
module example
go 1.24
ignore /pkg/foo
-- main.go --
package main
func main() {}

View file

@ -0,0 +1,151 @@
# go list should skip 'ignore' directives
# See golang.org/issue/42965
env ROOT=$WORK${/}gopath${/}src
# no ignore directive; should not skip any directories.
cp go.mod.orig go.mod
go list -x ./...
stdout 'example/foo/secret'
stdout 'example/pkg/foo'
stdout 'example/pkg/fo$'
! stderr 'ignoring directory'
# ignored ./foo should be skipped.
cp go.mod.relative go.mod
go list -x ./...
stdout 'example/pkg/foo'
stdout 'example/pkg/fo$'
! stdout 'example/foo/secret'
stderr 'ignoring directory '$ROOT''${/}'foo'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored foo; any foo should be skipped.
cp go.mod.any go.mod
go list -x ./...
stdout 'example/pkg/fo$'
! stdout 'example/pkg/foo'
! stdout 'example/foo/secret'
stderr 'ignoring directory '$ROOT''${/}'foo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# non-existent ignore; should not skip any directories.
cp go.mod.dne go.mod
go list -x ./...
stdout 'example/foo/secret'
stdout 'example/pkg/foo'
stdout 'example/pkg/fo$'
! stderr 'ignoring directory'
# ignored fo; should not skip foo/ and should skip fo/
cp go.mod.partial go.mod
go list -x ./...
! stderr 'ignoring directory '$ROOT''${/}'foo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'fo$'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored pkg/foo; should skip pkg/foo/
cp go.mod.tree go.mod
go list -x ./...
stdout 'example/foo/secret'
stdout 'example/pkg/fo$'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored /pkg/foo/; should skip pkg/foo/
cp go.mod.sep1 go.mod
go list -x ./...
stdout 'example/foo/secret'
stdout 'example/pkg/fo$'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored pkg/foo/; should skip pkg/foo/
cp go.mod.sep2 go.mod
go list -x ./...
stdout 'example/foo/secret'
stdout 'example/pkg/fo$'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
# ignored /pkg/foo; should skip pkg/foo/
cp go.mod.sep3 go.mod
go list -x ./...
stdout 'example/foo/secret'
stdout 'example/pkg/fo$'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
-- foo/secret/secret.go --
package secret
const Secret = "this should be ignored"
-- pkg/foo/foo.go --
package foo
const Bar = "Hello from foo!"
-- pkg/fo/fo.go --
package fo
const Gar = "Hello from fo!"
-- go.mod.orig --
module example
go 1.24
-- go.mod.relative --
module example
go 1.24
ignore ./foo
-- go.mod.any --
module example
go 1.24
ignore foo
-- go.mod.dne --
module example
go 1.24
ignore bar
-- go.mod.partial --
module example
go 1.24
ignore fo
-- go.mod.tree --
module example
go 1.24
ignore pkg/foo
-- go.mod.sep1 --
module example
go 1.24
ignore /pkg/foo/
-- go.mod.sep2 --
module example
go 1.24
ignore pkg/foo/
-- go.mod.sep3 --
module example
go 1.24
ignore /pkg/foo
-- main.go --
package main
func main() {}

View file

@ -0,0 +1,76 @@
# go list should skip 'ignore' directives with respect to module boundaries.
# See golang.org/issue/42965
env ROOT=$WORK${/}gopath${/}src
# Lists all packages known to the Go toolchain.
# Since go list already does not traverse into other modules found in
# subdirectories, it should only ignore the root node_modules.
go list -x all
stdout 'example$'
stdout 'example/depA'
stderr 'ignoring directory '$ROOT''${/}'node_modules'
! stderr 'ignoring directory '$ROOT''${/}'depA'${/}'node_modules'
# Lists all packages within the current Go module.
# Since go list already does not traverse into other modules found in
# subdirectories, it should only ignore the root node_modules.
go list -x ./...
stdout 'example$'
stderr 'ignoring directory '$ROOT''${/}'node_modules'
! stderr 'ignoring directory '$ROOT''${/}'depA'${/}'node_modules'
# Lists all packages belonging to the module whose import path starts with
# example.
# In this case, go list will traverse into each module that starts with example.
# So it should ignore the root node_modules and the subdirectories' node_modules.
go list -x example/...
stdout 'example$'
stdout 'example/depA'
stderr 'ignoring directory '$ROOT''${/}'node_modules'
stderr 'ignoring directory '$ROOT''${/}'depA'${/}'node_modules'
# Entering the submodule should now cause go list to ignore depA/node_modules.
cd depA
go list -x all
stdout 'example/depA'
stderr 'ignoring directory '$ROOT''${/}'depA'${/}'node_modules'
! stderr 'ignoring directory '$ROOT''${/}'node_modules'
go list -x ./...
stdout 'example/depA'
stderr 'ignoring directory '$ROOT''${/}'depA'${/}'node_modules'
! stderr 'ignoring directory '$ROOT''${/}'node_modules'
-- depA/go.mod --
module example/depA
go 1.24
ignore ./node_modules
-- depA/depA.go --
package depA
const Foo = "This is Foo!"
-- depA/node_modules/some_pkg/index.js --
console.log("This should be ignored!");
-- node_modules/some_pkg/index.js --
console.log("This should be ignored!");
-- go.mod --
module example
go 1.24
ignore ./node_modules
require example/depA v1.0.0
replace example/depA => ./depA
-- main.go --
package main
import (
"fmt"
"example/depA"
)
func main() {
fmt.Println("test")
fmt.Println(depA.Foo)
}

View file

@ -0,0 +1,89 @@
# go list should skip 'ignore' directives in workspaces
# See golang.org/issue/42965
env ROOT=$WORK${/}gopath${/}src
# go list ./... should only consider the current module's ignore directive
cd moduleA
go list -x ./...
stdout 'moduleA$'
stdout 'moduleA/pkg$'
stderr 'ignoring directory '$ROOT''${/}'moduleA'${/}'node_modules'
# go list ./... should only consider the current module's ignore directive
cd ../moduleB
go list -x ./...
stdout 'moduleB$'
! stdout 'moduleB/pkg/helper'
stderr 'ignoring directory '$ROOT''${/}'moduleB'${/}'pkg'
# go list should respect module boundaries for ignore directives.
# moduleA ignores './node_modules', moduleB ignores 'pkg'
cd ..
go list -x all
stderr 'ignoring directory '$ROOT''${/}'moduleA'${/}'node_modules'
stderr 'ignoring directory '$ROOT''${/}'moduleB'${/}'pkg'
! stderr 'ignoring directory '$ROOT''${/}'moduleA'${/}'pkg'
stdout 'moduleA$'
stdout 'moduleA/pkg$'
stdout 'moduleB$'
stdout 'moduleB/pkg/helper'
-- go.work --
go 1.24
use (
./moduleA
./moduleB
)
-- moduleA/go.mod --
module moduleA
go 1.24
ignore ./node_modules
-- moduleA/main.go --
package main
import (
"fmt"
"moduleB/pkg/helper"
)
func main() {
fmt.Println("Running moduleA")
fmt.Println(helper.Message())
fmt.Println(hello.Hello())
}
-- moduleA/node_modules/some_pkg/index.js --
console.log("This should be ignored!");
-- moduleA/pkg/hello.go --
package hello
func Hello() string {
return "Hello from moduleA"
}
-- moduleB/go.mod --
module moduleB
go 1.24
ignore pkg
-- moduleB/main.go --
package main
import "fmt"
func main() {
fmt.Println("Running moduleB")
}
-- moduleB/pkg/helper/helper.go --
package helper
func Message() string {
return "Helper from moduleB"
}

View file

@ -107,6 +107,16 @@ cmpenv go.mod go.mod.edit
go mod edit -droptool example.com/tool
cmpenv go.mod go.mod.start
# go mod edit -ignore
cd $WORK/i
cp go.mod.start go.mod
go mod edit -ignore example.com/ignore
cmpenv go.mod go.mod.edit
go mod edit -dropignore example.com/ignore2
cmpenv go.mod go.mod.edit
go mod edit -dropignore example.com/ignore
cmpenv go.mod go.mod.start
-- x.go --
package x
@ -195,7 +205,8 @@ require x.3 v1.99.0
"High": "v1.4.0"
}
],
"Tool": null
"Tool": null,
"Ignore": null
}
-- $WORK/go.mod.edit3 --
module x.x/y/z
@ -333,7 +344,8 @@ retract (
"Rationale": "c"
}
],
"Tool": null
"Tool": null,
"Ignore": null
}
-- $WORK/go.mod.deprecation --
// Deprecated: and the new one is not ready yet
@ -348,7 +360,8 @@ module m
"Exclude": null,
"Replace": null,
"Retract": null,
"Tool": null
"Tool": null,
"Ignore": null
}
-- $WORK/go.mod.empty --
-- $WORK/go.mod.empty.json --
@ -360,7 +373,8 @@ module m
"Exclude": null,
"Replace": null,
"Retract": null,
"Tool": null
"Tool": null,
"Ignore": null
}
-- $WORK/g/go.mod.start --
module g
@ -382,3 +396,13 @@ module g
go 1.24
tool example.com/tool
-- $WORK/i/go.mod.start --
module g
go 1.24
-- $WORK/i/go.mod.edit --
module g
go 1.24
ignore example.com/ignore

View file

@ -0,0 +1,78 @@
# go mod tidy should skip 'ignore' directives
# See golang.org/issue/42965
env ROOT=$WORK${/}gopath${/}src
# no ignore directive; should not skip any directories.
cp go.mod.orig go.mod
go mod tidy -x
! stderr 'ignoring directory'
# ignored ./foo should be skipped.
cp go.mod.relative go.mod
go mod tidy -x
stderr 'ignoring directory '$ROOT''${/}'foo'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'fo$'
# ignored foo; any foo should be skipped.
cp go.mod.any go.mod
go mod tidy -x
stderr 'ignoring directory '$ROOT''${/}'foo'
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'fo$'
# non-existent ignore; should not skip any directories.
cp go.mod.dne go.mod
go mod tidy -x
! stderr 'ignoring directory'
# ignored fo; should not skip foo/ but should skip fo/
cp go.mod.partial go.mod
go mod tidy -x
stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'fo$'
! stderr 'ignoring directory '$ROOT''${/}'pkg'${/}'foo'
-- foo/secret/secret.go --
package secret
const Secret = "this should be ignored"
-- pkg/foo/foo.go --
package example/pkg/foo
const Bar = "Hello from foo!"
-- pkg/fo/fo.go --
package fo
const Gar = "Hello from fo!"
-- go.mod.orig --
module example
go 1.24
-- go.mod.relative --
module example
go 1.24
ignore ./foo
-- go.mod.any --
module example
go 1.24
ignore foo
-- go.mod.dne --
module example
go 1.24
ignore bar
-- go.mod.partial --
module example
go 1.24
ignore fo
-- main.go --
package main
func main() {}