diff --git a/pkg/loader/loader.go b/pkg/loader/loader.go index 35c271335..b4c60b3ad 100644 --- a/pkg/loader/loader.go +++ b/pkg/loader/loader.go @@ -25,6 +25,8 @@ import ( "go/types" "io/ioutil" "os" + "path" + "path/filepath" "sync" "golang.org/x/tools/go/packages" @@ -329,7 +331,7 @@ func LoadRoots(roots ...string) ([]*Package, error) { // // This is generally only useful for use in testing when you need to modify // loading settings to load from a fake location. -func LoadRootsWithConfig(cfg *packages.Config, roots ...string) ([]*Package, error) { +func LoadRootsWithConfig(cfg *packages.Config, roots ...string) (pkgs []*Package, retErr error) { l := &loader{ cfg: cfg, packages: make(map[*packages.Package]*Package), @@ -341,13 +343,77 @@ func LoadRootsWithConfig(cfg *packages.Config, roots ...string) ([]*Package, err // put our build flags first so that callers can override them l.cfg.BuildFlags = append([]string{"-tags", "ignore_autogenerated"}, l.cfg.BuildFlags...) - rawPkgs, err := packages.Load(l.cfg, roots...) - if err != nil { + // check each root to see if it should be expanded to include nested modules + var goModDirs []string + findGoModules := func(p string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() && path.Base(p) == "go.mod" { + absPath, err := filepath.Abs(p) + if err != nil { + return err + } + goModDirs = append(goModDirs, path.Join(path.Dir(absPath), "...")) + } + return nil + } + for i, r := range roots { + // skip roots whose last path element is not four dot characters + if filepath.Base(r) != "...." { + continue + } + // update the root to no longer descend into nested modules + roots[i] = r[:len(r)-1] + // add any nested modules to the list of Go module directories to + // process later + if err := filepath.WalkDir(r[:len(r)-4], findGoModules); err != nil { + return nil, err + } + } + + // loadRoots parses the provided file paths and returns their load packages + loadRoots := func(roots ...string) error { + rawPkgs, err := packages.Load(l.cfg, roots...) + if err != nil { + return err + } + for _, rawPkg := range rawPkgs { + l.Roots = append(l.Roots, l.packageFor(rawPkg)) + } + return nil + } + + // load the packages from the main module + if err := loadRoots(roots...); err != nil { return nil, err } - for _, rawPkg := range rawPkgs { - l.Roots = append(l.Roots, l.packageFor(rawPkg)) + if len(goModDirs) > 0 { + // ensure the working directory is updated back to its original location + // as we switch into the directory of each Go module to process them to + // accommodate the package loader + workingDir, err := os.Getwd() + if err != nil { + return nil, err + } + defer func() { + retErr = os.Chdir(workingDir) + }() + + // load the packages from the nested modules + for _, p := range goModDirs { + // change the working directory to the root of the nested module + // so the package loader does not complain about processing a + // directory that is not a member of the root module + if err := os.Chdir(path.Dir(p)); err != nil { + return nil, err + } + // load the packages from the nested module + if err := loadRoots("./..."); err != nil { + return nil, err + } + } } return l.Roots, nil