1 Star 0 Fork 0

api-go / govendor

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
resolve.go 13.96 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package context
import (
"go/ast"
"go/parser"
"go/token"
"path"
"sort"
"strconv"
"strings"
"github.com/kardianos/govendor/internal/pathos"
filepath "github.com/kardianos/govendor/internal/vfilepath"
os "github.com/kardianos/govendor/internal/vos"
"github.com/kardianos/govendor/pkgspec"
)
var knownOS = make(map[string]bool)
var knownArch = make(map[string]bool)
func init() {
for _, v := range strings.Fields(goosList) {
knownOS[v] = true
}
for _, v := range strings.Fields(goarchList) {
knownArch[v] = true
}
}
// loadPackage sets up the context with package information and
// is called before any initial operation is performed.
func (ctx *Context) loadPackage() error {
ctx.loaded = true
ctx.dirty = false
ctx.statusCache = nil
ctx.Package = make(map[string]*Package, len(ctx.Package))
// We following the root symlink only in case the root of the repo is symlinked into the GOPATH
// This could happen during on some CI that didn't checkout into the GOPATH
rootdir, err := filepath.EvalSymlinks(ctx.RootDir)
if err != nil {
return err
}
err = filepath.Walk(rootdir, func(path string, info os.FileInfo, err error) error {
if info == nil {
return err
}
if !info.IsDir() {
// We replace the directory path (followed by the symlink), to the real go repo package name/path
// ex : replace "<somewhere>/govendor.source.repo" to "github.com/kardianos/govendor"
path = strings.Replace(path, rootdir, ctx.RootDir, 1)
_, err = ctx.addFileImports(path, ctx.RootGopath)
return err
}
name := info.Name()
// Still go into "_workspace" to aid godep migration.
if name == "_workspace" {
return nil
}
switch name[0] {
case '.', '_':
return filepath.SkipDir
}
switch name {
case "testdata", "node_modules":
return filepath.SkipDir
}
return nil
})
if err != nil {
return err
}
// Finally, set any unset status.
return ctx.determinePackageStatus()
}
func (ctx *Context) getFileTags(pathname string, f *ast.File) (tags *TagSet, imports []string, err error) {
_, filenameExt := filepath.Split(pathname)
if strings.HasSuffix(pathname, ".go") == false {
return nil, nil, nil
}
if f == nil {
f, err = parser.ParseFile(token.NewFileSet(), pathname, nil, parser.ImportsOnly|parser.ParseComments)
if f == nil {
return nil, nil, nil
}
}
tags = &TagSet{}
if strings.HasSuffix(f.Name.Name, "_test") {
tags.AddFileTag("test")
}
pkgNameNormalized := strings.TrimSuffix(f.Name.Name, "_test")
// Files with package name "documentation" should be ignored, per go build tool.
if pkgNameNormalized == "documentation" {
return nil, nil, nil
}
filename := filenameExt[:len(filenameExt)-3]
l := strings.Split(filename, "_")
if n := len(l); n > 1 && l[n-1] == "test" {
l = l[:n-1]
tags.AddFileTag("test")
}
n := len(l)
if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
tags.AddFileTag(l[n-2])
tags.AddFileTag(l[n-1])
}
if n >= 1 && knownOS[l[n-1]] {
tags.AddFileTag(l[n-1])
}
if n >= 1 && knownArch[l[n-1]] {
tags.AddFileTag(l[n-1])
}
const buildPrefix = "// +build "
for _, cc := range f.Comments {
for _, c := range cc.List {
if strings.HasPrefix(c.Text, buildPrefix) {
text := strings.TrimPrefix(c.Text, buildPrefix)
tags.AddBuildTags(text)
}
}
}
imports = make([]string, 0, len(f.Imports))
for i := range f.Imports {
imp := f.Imports[i].Path.Value
imp, err = strconv.Unquote(imp)
if err != nil {
// Best errort
continue
}
imports = append(imports, imp)
}
return tags, imports, nil
}
// addFileImports is called from loadPackage and resolveUnknown.
func (ctx *Context) addFileImports(pathname, gopath string) (*Package, error) {
dir, filenameExt := filepath.Split(pathname)
importPath := pathos.FileTrimPrefix(dir, gopath)
importPath = pathos.SlashToImportPath(importPath)
importPath = strings.Trim(importPath, "/")
if strings.HasSuffix(pathname, ".go") == false {
return nil, nil
}
// No need to add the same file more than once.
for _, pkg := range ctx.Package {
if pathos.FileStringEquals(pkg.Dir, dir) == false {
continue
}
for _, f := range pkg.Files {
if pathos.FileStringEquals(f.Path, pathname) {
return nil, nil
}
}
for _, f := range pkg.ignoreFile {
if pathos.FileStringEquals(f, filenameExt) {
return nil, nil
}
}
}
// Ignore error here and continue on best effort.
f, _ := parser.ParseFile(token.NewFileSet(), pathname, nil, parser.ImportsOnly|parser.ParseComments)
if f == nil {
return nil, nil
}
pkgNameNormalized := strings.TrimSuffix(f.Name.Name, "_test")
// Files with package name "documentation" should be ignored, per go build tool.
if pkgNameNormalized == "documentation" {
return nil, nil
}
tags, _, err := ctx.getFileTags(pathname, f)
if err != nil {
return nil, err
}
// If file has "// +build ignore", can mix package main with normal package.
// For now, just ignore ignored packages.
if tags.IgnoreItem() {
return nil, nil
}
pkg, found := ctx.Package[importPath]
if !found {
status := Status{
Type: TypePackage,
Location: LocationUnknown,
Presence: PresenceFound,
}
if pkgNameNormalized == "main" {
status.Type = TypeProgram
}
pkg = ctx.setPackage(dir, importPath, importPath, gopath, status)
ctx.Package[importPath] = pkg
}
if pkg.Status.Location != LocationLocal {
if tags.IgnoreItem(ctx.ignoreTag...) {
pkg.ignoreFile = append(pkg.ignoreFile, filenameExt)
return pkg, nil
}
// package excluded if non-local && same name or sub-package of an excluded package
for _, exclude := range ctx.excludePackage {
if importPath == exclude || strings.HasPrefix(importPath, exclude+"/") {
pkg.Status.Presence = PresenceExcluded
}
}
}
pf := &File{
Package: pkg,
Path: pathname,
Imports: make([]string, len(f.Imports)),
}
pkg.Files = append(pkg.Files, pf)
for i := range f.Imports {
imp := f.Imports[i].Path.Value
imp, err = strconv.Unquote(imp)
if err != nil {
// Best effort only.
continue
}
if strings.HasPrefix(imp, "./") {
imp = path.Join(importPath, imp)
}
pf.Imports[i] = imp
if pkg.Status.Presence != PresenceExcluded { // do not add package imports if it was explicitly excluded
_, err = ctx.addSingleImport(pkg.Dir, imp, pkg.IncludeTree)
if err != nil {
return pkg, err
}
}
}
// Record any import comment for file.
var ic *ast.Comment
if f.Name != nil {
pos := f.Name.Pos()
big:
// Find the next comment after the package name.
for _, cblock := range f.Comments {
for _, c := range cblock.List {
if c.Pos() > pos {
ic = c
break big
}
}
}
}
if ic != nil {
// If it starts with the import text, assume it is the import comment.
if index := strings.Index(ic.Text, " import "); index > 0 && index < 5 {
q := strings.TrimSpace(ic.Text[index+len(" import "):])
pf.ImportComment, err = strconv.Unquote(q)
if err != nil {
pf.ImportComment = q
}
}
}
return pkg, nil
}
func (ctx *Context) setPackage(dir, canonical, local, gopath string, status Status) *Package {
if pkg, exists := ctx.Package[local]; exists {
return pkg
}
at := 0
vMiddle := "/" + pathos.SlashToImportPath(ctx.VendorDiscoverFolder) + "/"
vStart := pathos.SlashToImportPath(ctx.VendorDiscoverFolder) + "/"
switch {
case strings.Contains(canonical, vMiddle):
at = strings.LastIndex(canonical, vMiddle) + len(vMiddle)
case strings.HasPrefix(canonical, vStart):
at = strings.LastIndex(canonical, vStart) + len(vStart)
}
originDir := dir
inVendor := false
tree := false
origin := ""
if at > 0 {
canonical = canonical[at:]
inVendor = true
if status.Location == LocationUnknown {
p := path.Join(ctx.RootImportPath, ctx.VendorDiscoverFolder)
if strings.HasPrefix(local, p) {
status.Location = LocationVendor
od, _, err := ctx.findImportDir("", canonical)
if err == nil {
originDir = od
}
}
}
}
if vp := ctx.VendorFilePackagePath(canonical); vp != nil {
tree = vp.Tree
origin = vp.Origin
}
// Set originDir correctly if origin is set.
if len(origin) > 0 {
od, _, err := ctx.findImportDir("", origin)
if err == nil {
originDir = od
}
}
if status.Location == LocationUnknown && filepath.HasPrefixDir(canonical, ctx.RootImportPath) {
status.Location = LocationLocal
}
spec, err := pkgspec.Parse("", canonical)
if err != nil {
panic(err)
}
if len(origin) > 0 && origin != canonical {
spec.Origin = origin
}
spec.IncludeTree = tree
pkg := &Package{
OriginDir: originDir,
Dir: dir,
Pkg: spec,
Local: local,
Gopath: gopath,
Status: status,
inVendor: inVendor,
}
ctx.Package[local] = pkg
return pkg
}
var testNeedsSortOrder = false
func (ctx *Context) addSingleImport(pkgInDir, imp string, tree bool) (*Package, error) {
// Do not check for existing package right away. If a external package
// has been added and we are looking in a vendor package, this won't work.
// We need to search any relative vendor folders first.
// Also need to check for vendor paths that won't use the local path in import path.
for _, pkg := range ctx.Package {
if pkg.Path == imp && pkg.inVendor && pathos.FileHasPrefix(pkg.Dir, pkgInDir) {
return nil, nil
}
}
dir, gopath, err := ctx.findImportDir(pkgInDir, imp)
if err != nil {
if _, is := err.(ErrNotInGOPATH); is {
presence := PresenceMissing
// excluded packages, don't need to be present
for _, exclude := range ctx.excludePackage {
if imp == exclude || strings.HasPrefix(imp, exclude+"/") {
presence = PresenceExcluded
}
}
return ctx.setPackage("", imp, imp, "", Status{
Type: TypePackage,
Location: LocationNotFound,
Presence: presence,
}), nil
}
return nil, err
}
if pathos.FileStringEquals(gopath, ctx.Goroot) {
return ctx.setPackage(dir, imp, imp, ctx.Goroot, Status{
Type: TypePackage,
Location: LocationStandard,
Presence: PresenceFound,
}), nil
}
if tree {
return ctx.setPackage(dir, imp, imp, ctx.RootGopath, Status{
Type: TypePackage,
Location: LocationVendor,
Presence: PresenceFound,
}), nil
}
df, err := os.Open(dir)
if err != nil {
return nil, err
}
info, err := df.Readdir(-1)
df.Close()
if err != nil {
return nil, err
}
if testNeedsSortOrder {
sort.Sort(fileInfoSort(info))
}
var pkg *Package
for _, fi := range info {
if fi.IsDir() {
continue
}
switch fi.Name()[0] {
case '.', '_':
continue
}
if pathos.FileStringEquals(dir, pkgInDir) {
continue
}
path := filepath.Join(dir, fi.Name())
tryPkg, err := ctx.addFileImports(path, gopath)
if tryPkg != nil {
pkg = tryPkg
}
if err != nil {
return pkg, err
}
}
return pkg, nil
}
func (ctx *Context) determinePackageStatus() error {
// Add any packages in the vendor file but not in GOPATH or vendor dir.
for _, vp := range ctx.VendorFile.Package {
if vp.Remove {
continue
}
if _, found := ctx.Package[vp.Path]; found {
continue
}
pkg, err := ctx.addSingleImport(ctx.RootDir, vp.Path, vp.Tree)
if err != nil {
return err
}
if pkg != nil {
pkg.Origin = vp.Origin
pkg.inTree = vp.Tree
pkg.inVendor = true
}
}
// Determine the status of remaining imports.
for _, pkg := range ctx.Package {
if pkg.Status.Location != LocationUnknown {
continue
}
if filepath.HasPrefixDir(pkg.Path, ctx.RootImportPath) {
pkg.Status.Location = LocationLocal
continue
}
pkg.Status.Location = LocationExternal
}
ctx.updatePackageReferences()
// Mark sub-tree packages as "tree", but leave any existing bit (unused) on the
// parent most tree package.
for path, pkg := range ctx.Package {
if vp := ctx.VendorFilePackagePath(pkg.Path); vp != nil && vp.Tree {
// Remove internal tree references.
del := make([]string, 0, 6)
for opath, opkg := range pkg.referenced {
if strings.HasPrefix(opkg.Path, pkg.Path+"/") {
del = append(del, opath)
}
}
delete(pkg.referenced, pkg.Local) // remove any self reference
for _, d := range del {
delete(pkg.referenced, d)
}
continue
}
if parentTrees := ctx.findPackageParentTree(pkg); len(parentTrees) > 0 {
pkg.Status.Presence = PresenceTree
// Transfer all references from the child to the top parent.
if parentPkg := ctx.Package[parentTrees[0]]; parentPkg != nil {
for opath, opkg := range pkg.referenced {
// Do not transfer internal references.
if strings.HasPrefix(opkg.Path, parentPkg.Path+"/") {
continue
}
parentPkg.referenced[opath] = opkg
}
pkg.referenced = make(map[string]*Package, 0)
for _, opkg := range ctx.Package {
if _, has := opkg.referenced[path]; has {
opkg.referenced[parentPkg.Local] = parentPkg
delete(opkg.referenced, path)
}
}
}
}
}
ctx.updatePackageReferences()
// Determine any un-used internal vendor imports.
for i := 0; i <= looplimit; i++ {
altered := false
for path, pkg := range ctx.Package {
if pkg.Status.Presence == PresenceUnused || pkg.Status.Presence == PresenceTree || pkg.Status.Type == TypeProgram {
continue
}
if len(pkg.referenced) > 0 || pkg.Status.Location != LocationVendor {
continue
}
altered = true
pkg.Status.Presence = PresenceUnused
for _, other := range ctx.Package {
delete(other.referenced, path)
}
}
if !altered {
break
}
if i == looplimit {
panic("determinePackageStatus loop limit")
}
}
ctx.updatePackageReferences()
// Unused external references may have worked their way in through
// vendor file. Remove any external leafs.
for i := 0; i <= looplimit; i++ {
altered := false
for path, pkg := range ctx.Package {
if len(pkg.referenced) > 0 || pkg.Status.Location != LocationExternal {
continue
}
altered = true
delete(ctx.Package, path)
pkg.Status.Presence = PresenceUnused
for _, other := range ctx.Package {
delete(other.referenced, path)
}
continue
}
if !altered {
break
}
if i == looplimit {
panic("determinePackageStatus loop limit")
}
}
return nil
}
1
https://gitee.com/netscript/govendor.git
git@gitee.com:netscript/govendor.git
netscript
govendor
govendor
v1.0.9

搜索帮助