forked from ebhomengo/niki
1
0
Fork 0
niki/vendor/github.com/KyleBanks/depth/pkg.go

289 lines
4.3 KiB
Go
Raw Normal View History

2024-05-14 13:07:09 +00:00
package depth
import (
"bytes"
"go/build"
"path"
"sort"
"strings"
)
// Pkg represents a Go source package, and its dependencies.
2024-05-14 13:07:09 +00:00
type Pkg struct {
Name string `json:"name"`
2024-05-14 13:07:09 +00:00
SrcDir string `json:"-"`
Internal bool `json:"internal"`
2024-05-14 13:07:09 +00:00
Resolved bool `json:"resolved"`
Test bool `json:"-"`
Tree *Tree `json:"-"`
Parent *Pkg `json:"-"`
Deps []Pkg `json:"deps"`
2024-05-14 13:07:09 +00:00
Raw *build.Package `json:"-"`
}
// Resolve recursively finds all dependencies for the Pkg and the packages it depends on.
2024-05-14 13:07:09 +00:00
func (p *Pkg) Resolve(i Importer) {
2024-05-14 13:07:09 +00:00
// Resolved is always true, regardless of if we skip the import,
2024-05-14 13:07:09 +00:00
// it is only false if there is an error while importing.
2024-05-14 13:07:09 +00:00
p.Resolved = true
name := p.cleanName()
2024-05-14 13:07:09 +00:00
if name == "" {
2024-05-14 13:07:09 +00:00
return
2024-05-14 13:07:09 +00:00
}
// Stop resolving imports if we've reached max depth or found a duplicate.
2024-05-14 13:07:09 +00:00
var importMode build.ImportMode
2024-05-14 13:07:09 +00:00
if p.Tree.hasSeenImport(name) || p.Tree.isAtMaxDepth(p) {
2024-05-14 13:07:09 +00:00
importMode = build.FindOnly
2024-05-14 13:07:09 +00:00
}
pkg, err := i.Import(name, p.SrcDir, importMode)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
// TODO: Check the error type?
2024-05-14 13:07:09 +00:00
p.Resolved = false
2024-05-14 13:07:09 +00:00
return
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
p.Raw = pkg
// Update the name with the fully qualified import path.
2024-05-14 13:07:09 +00:00
p.Name = pkg.ImportPath
// If this is an internal dependency, we may need to skip it.
2024-05-14 13:07:09 +00:00
if pkg.Goroot {
2024-05-14 13:07:09 +00:00
p.Internal = true
2024-05-14 13:07:09 +00:00
if !p.Tree.shouldResolveInternal(p) {
2024-05-14 13:07:09 +00:00
return
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
//first we set the regular dependencies, then we add the test dependencies
2024-05-14 13:07:09 +00:00
//sharing the same set. This allows us to mark all test-only deps linearly
2024-05-14 13:07:09 +00:00
unique := make(map[string]struct{})
2024-05-14 13:07:09 +00:00
p.setDeps(i, pkg.Imports, pkg.Dir, unique, false)
2024-05-14 13:07:09 +00:00
if p.Tree.ResolveTest {
2024-05-14 13:07:09 +00:00
p.setDeps(i, append(pkg.TestImports, pkg.XTestImports...), pkg.Dir, unique, true)
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
// setDeps takes a slice of import paths and the source directory they are relative to,
2024-05-14 13:07:09 +00:00
// and creates the Deps of the Pkg. Each dependency is also further resolved prior to being added
2024-05-14 13:07:09 +00:00
// to the Pkg.
2024-05-14 13:07:09 +00:00
func (p *Pkg) setDeps(i Importer, imports []string, srcDir string, unique map[string]struct{}, isTest bool) {
2024-05-14 13:07:09 +00:00
for _, imp := range imports {
2024-05-14 13:07:09 +00:00
// Mostly for testing files where cyclic imports are allowed.
2024-05-14 13:07:09 +00:00
if imp == p.Name {
2024-05-14 13:07:09 +00:00
continue
2024-05-14 13:07:09 +00:00
}
// Skip duplicates.
2024-05-14 13:07:09 +00:00
if _, ok := unique[imp]; ok {
2024-05-14 13:07:09 +00:00
continue
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
unique[imp] = struct{}{}
p.addDep(i, imp, srcDir, isTest)
2024-05-14 13:07:09 +00:00
}
sort.Sort(byInternalAndName(p.Deps))
2024-05-14 13:07:09 +00:00
}
// addDep creates a Pkg and it's dependencies from an imported package name.
2024-05-14 13:07:09 +00:00
func (p *Pkg) addDep(i Importer, name string, srcDir string, isTest bool) {
2024-05-14 13:07:09 +00:00
dep := Pkg{
Name: name,
2024-05-14 13:07:09 +00:00
SrcDir: srcDir,
Tree: p.Tree,
2024-05-14 13:07:09 +00:00
Parent: p,
Test: isTest,
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
dep.Resolve(i)
p.Deps = append(p.Deps, dep)
2024-05-14 13:07:09 +00:00
}
// isParent goes recursively up the chain of Pkgs to determine if the name provided is ever a
2024-05-14 13:07:09 +00:00
// parent of the current Pkg.
2024-05-14 13:07:09 +00:00
func (p *Pkg) isParent(name string) bool {
2024-05-14 13:07:09 +00:00
if p.Parent == nil {
2024-05-14 13:07:09 +00:00
return false
2024-05-14 13:07:09 +00:00
}
if p.Parent.Name == name {
2024-05-14 13:07:09 +00:00
return true
2024-05-14 13:07:09 +00:00
}
return p.Parent.isParent(name)
2024-05-14 13:07:09 +00:00
}
// depth returns the depth of the Pkg within the Tree.
2024-05-14 13:07:09 +00:00
func (p *Pkg) depth() int {
2024-05-14 13:07:09 +00:00
if p.Parent == nil {
2024-05-14 13:07:09 +00:00
return 0
2024-05-14 13:07:09 +00:00
}
return p.Parent.depth() + 1
2024-05-14 13:07:09 +00:00
}
// cleanName returns a cleaned version of the Pkg name used for resolving dependencies.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// If an empty string is returned, dependencies should not be resolved.
2024-05-14 13:07:09 +00:00
func (p *Pkg) cleanName() string {
2024-05-14 13:07:09 +00:00
name := p.Name
// C 'package' cannot be resolved.
2024-05-14 13:07:09 +00:00
if name == "C" {
2024-05-14 13:07:09 +00:00
return ""
2024-05-14 13:07:09 +00:00
}
// Internal golang_org/* packages must be prefixed with vendor/
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// Thanks to @davecheney for this:
2024-05-14 13:07:09 +00:00
// https://github.com/davecheney/graphpkg/blob/master/main.go#L46
2024-05-14 13:07:09 +00:00
if strings.HasPrefix(name, "golang_org") {
2024-05-14 13:07:09 +00:00
name = path.Join("vendor", name)
2024-05-14 13:07:09 +00:00
}
return name
2024-05-14 13:07:09 +00:00
}
// String returns a string representation of the Pkg containing the Pkg name and status.
2024-05-14 13:07:09 +00:00
func (p *Pkg) String() string {
2024-05-14 13:07:09 +00:00
b := bytes.NewBufferString(p.Name)
if !p.Resolved {
2024-05-14 13:07:09 +00:00
b.Write([]byte(" (unresolved)"))
2024-05-14 13:07:09 +00:00
}
return b.String()
2024-05-14 13:07:09 +00:00
}
// byInternalAndName ensures a slice of Pkgs are sorted such that the internal stdlib
2024-05-14 13:07:09 +00:00
// packages are always above external packages (ie. github.com/whatever).
2024-05-14 13:07:09 +00:00
type byInternalAndName []Pkg
func (b byInternalAndName) Len() int {
2024-05-14 13:07:09 +00:00
return len(b)
2024-05-14 13:07:09 +00:00
}
func (b byInternalAndName) Swap(i, j int) {
2024-05-14 13:07:09 +00:00
b[i], b[j] = b[j], b[i]
2024-05-14 13:07:09 +00:00
}
func (b byInternalAndName) Less(i, j int) bool {
2024-05-14 13:07:09 +00:00
if b[i].Internal && !b[j].Internal {
2024-05-14 13:07:09 +00:00
return true
2024-05-14 13:07:09 +00:00
} else if !b[i].Internal && b[j].Internal {
2024-05-14 13:07:09 +00:00
return false
2024-05-14 13:07:09 +00:00
}
return b[i].Name < b[j].Name
2024-05-14 13:07:09 +00:00
}