Skip to content

Commit

Permalink
Add PackagesInDirMatchingRootModule function to pkgpath (#351)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmiyake authored Sep 4, 2024
1 parent 81b8e2a commit 77b0615
Show file tree
Hide file tree
Showing 89 changed files with 36,888 additions and 9 deletions.
3 changes: 3 additions & 0 deletions pkgpath/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ require (
github.com/palantir/pkg v1.1.0
github.com/palantir/pkg/matcher v1.2.0
github.com/stretchr/testify v1.9.0
golang.org/x/mod v0.20.0
golang.org/x/tools v0.24.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sync v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 6 additions & 0 deletions pkgpath/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
128 changes: 119 additions & 9 deletions pkgpath/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import (
"go/build"
"go/parser"
"go/token"
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strings"

"github.com/palantir/pkg/matcher"
"golang.org/x/mod/modfile"
gopackages "golang.org/x/tools/go/packages"
)

// DefaultGoPkgExcludeMatcher returns a matcher that matches names that standard Go tools generally exclude as Go
Expand Down Expand Up @@ -228,36 +231,80 @@ func PackagesFromPaths(rootDir string, relPaths []string) (Packages, error) {
// PackagesInDir creates a Packages that contains all of the packages rooted at the provided directory. Every directory
// rooted in the provided directory whose path does not match the provided exclude matcher is considered as a package.
func PackagesInDir(rootDir string, exclude matcher.Matcher) (Packages, error) {
return packagesInDir(rootDir, nil, exclude)
}

// PackagesInDirMatchingRootModule creates a Packages that contains all of the packages rooted at the provided directory
// that are part of the same module as the root directory. Every directory rooted in the provided directory whose path
// does not match the provided exclude matcher and is part of the same module as the module of the root directory is
// considered as a package.
func PackagesInDirMatchingRootModule(rootDir string, exclude matcher.Matcher) (Packages, error) {
return packagesInDir(rootDir, nonRootModuleExcluder(rootDir), exclude)
}

// packagesInDir creates a Packages that contains all of the packages rooted at the provided directory. Every
// directory rooted in the provided directory whose path does not match the provided exclude matcher is considered as a
// package. A directory is only considered if pkgDirExcluder and pkgFileExcluder do not match it. If a directory is
// considered, its package will be determined based on considering only the files in the directory that do not match the
// pkgFileExcluder. If pkgDirExcluder returns a value that indicates that the subdirectories should be skipped, those
// subdirectories will not be considered.
func packagesInDir(rootDir string, pkgDirExcluder pkgExcluder, pkgFileExcluder matcher.Matcher) (Packages, error) {
dirAbsolutePath, err := filepath.Abs(rootDir)
if err != nil {
return nil, fmt.Errorf("failed to convert %s to absolute path: %v", rootDir, err)
}

allPkgs := make(map[string]string)
if err := filepath.Walk(dirAbsolutePath, func(currPath string, currInfo os.FileInfo, err error) error {
currRelPath, currRelPathErr := filepath.Rel(dirAbsolutePath, currPath)

// skip current path if it matches an exclude
if currRelPathErr == nil && exclude != nil && exclude.Match(currRelPath) {
return nil
}

if err := filepath.WalkDir(dirAbsolutePath, func(currPath string, currInfo fs.DirEntry, err error) error {
// if there was any error reading path, return error
if err != nil {
return err
}

// skip path if it is not a directory
if !currInfo.IsDir() {
return nil
}

// determine relative path for package
currRelPath, currRelPathErr := filepath.Rel(dirAbsolutePath, currPath)
if currRelPathErr != nil {
return currRelPathErr
}

skipDir := false

// if pkgDirExcluder is non-nil, check if directory should be excluded
if pkgDirExcluder != nil {
// determine whether current directory should be excluded
excludeDir, skipAllSubDirs := pkgDirExcluder.Exclude(currRelPath)

// update value of "skipDir" parameter: this will be used later (even if current directory is not excluded,
// if this value is true, the subdirectories will still be skipped).
skipDir = skipAllSubDirs

// if current directory should be excluded, return. Use "skipDir" parameter to determine whether all
// subdirectories should be skipped.
if excludeDir {
if skipDir {
return filepath.SkipDir
}
return nil
}
}

// if pkgFileExcluder is non-nil, check if directory should be excluded
if pkgFileExcluder != nil && pkgFileExcluder.Match(currRelPath) {
if skipDir {
return filepath.SkipDir
}
return nil
}

// create a filter for processing package files that only passes if it does not match an exclude
filter := func(info os.FileInfo) bool {
// if exclude exists and matches the file, skip it
if exclude != nil && exclude.Match(path.Join(currRelPath, info.Name())) {
if pkgFileExcluder != nil && pkgFileExcluder.Match(path.Join(currRelPath, info.Name())) {
return false
}
// process file if it would be included in build context (handles things like build tags)
Expand All @@ -274,6 +321,12 @@ func PackagesInDir(rootDir string, exclude matcher.Matcher) (Packages, error) {
allPkgs[currPath] = pkgName
}

// if this directory matched but return value indicates that subdirectories should not be considered, return
// filepath.SkipDir
if skipDir {
return filepath.SkipDir
}

return nil
}); err != nil {
return nil, err
Expand All @@ -282,6 +335,63 @@ func PackagesInDir(rootDir string, exclude matcher.Matcher) (Packages, error) {
return createPkgsWithValidation(dirAbsolutePath, allPkgs)
}

func nonRootModuleExcluder(wd string) pkgExcluder {
wdPkgs, err := gopackages.Load(&gopackages.Config{
Mode: gopackages.NeedModule,
Dir: wd,
}, ".")
if err != nil || len(wdPkgs) == 0 || wdPkgs[0].Module == nil || wdPkgs[0].Module.Path == "" {
return nil
}

wdModulePath := wdPkgs[0].Module.Path
return pkgExcluderFn(func(relPath string) (exclude, excludeAllSubdirs bool) {
fullPath := filepath.Join(wd, relPath)
dirEntries, readDirErr := os.ReadDir(fullPath)
// path cannot be read: do not exclude
if readDirErr != nil {
return false, false
}
var goModFilePath string
for _, entry := range dirEntries {
// skip all entries that are not the "go.mod" file
if entry.IsDir() || entry.Name() != "go.mod" {
continue
}
goModFilePath = filepath.Join(fullPath, entry.Name())
break
}
// no "go.mod" file in directory: do not exclude
if goModFilePath == "" {
return false, false
}

// "go.mod" file exists in directory
modFileBytes, err := os.ReadFile(goModFilePath)

// "go.mod" file cannot be read: do not exclude
if err != nil {
return false, false
}
// if module path of directory does not match root module, exclude this directory and all subdirectories
differentModule := modfile.ModulePath(modFileBytes) != wdModulePath
return differentModule, differentModule
})
}

type pkgExcluder interface {
// Exclude returns true if the package at the specified path should be excluded when matching packages. The relpath
// parameter will always be a directory. If the second return value is true, then no subdirectories of the package
// directories will be considered.
Exclude(relPath string) (exclude, excludeAllSubdirs bool)
}

type pkgExcluderFn func(relPath string) (exclude, excludeAllSubdirs bool)

func (fn pkgExcluderFn) Exclude(relPath string) (exclude, excludeAllSubdirs bool) {
return fn(relPath)
}

func createPkgsWithValidation(rootDir string, pkgs map[string]string) (*packages, error) {
if !path.IsAbs(rootDir) {
return nil, fmt.Errorf("rootDir %s is not an absolute path", rootDir)
Expand Down
27 changes: 27 additions & 0 deletions pkgpath/vendor/golang.org/x/mod/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions pkgpath/vendor/golang.org/x/mod/PATENTS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions pkgpath/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 77b0615

Please sign in to comment.