| // Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package pkgs |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "go/build" |
| "os" |
| "path/filepath" |
| "runtime" |
| "sort" |
| "strings" |
| "sync" |
| |
| "github.com/visualfc/gotools/command" |
| "github.com/visualfc/gotools/goapi" |
| ) |
| |
| var Command = &command.Command{ |
| Run: runPkgs, |
| UsageLine: "pkgs", |
| Short: "print liteide_stub version", |
| Long: `Version prints the liteide_stub version.`, |
| } |
| |
| var ( |
| pkgsList bool |
| pkgsJson bool |
| pkgsFind string |
| pkgsStd bool |
| pkgsPkgOnly bool |
| pkgsSkipGoroot bool |
| ) |
| |
| func init() { |
| Command.Flag.BoolVar(&pkgsList, "list", false, "list all package") |
| Command.Flag.BoolVar(&pkgsJson, "json", false, "json format") |
| Command.Flag.BoolVar(&pkgsStd, "std", false, "std library") |
| Command.Flag.BoolVar(&pkgsPkgOnly, "pkg", false, "pkg only") |
| Command.Flag.BoolVar(&pkgsSkipGoroot, "skip_goroot", false, "skip goroot") |
| Command.Flag.StringVar(&pkgsFind, "find", "", "find package by name") |
| } |
| |
| func runPkgs(cmd *command.Command, args []string) error { |
| runtime.GOMAXPROCS(runtime.NumCPU()) |
| if len(args) != 0 { |
| cmd.Usage() |
| return os.ErrInvalid |
| } |
| //pkgIndexOnce.Do(loadPkgsList) |
| var pp PathPkgsIndex |
| pp.LoadIndex() |
| pp.Sort() |
| if pkgsList { |
| for _, pi := range pp.indexs { |
| for _, pkg := range pi.pkgs { |
| if pkgsPkgOnly && pkg.IsCommand() { |
| continue |
| } |
| if pkgsJson { |
| var p GoPackage |
| p.copyBuild(pkg) |
| b, err := json.MarshalIndent(&p, "", "\t") |
| if err == nil { |
| cmd.Stdout.Write(b) |
| cmd.Stdout.Write([]byte{'\n'}) |
| } |
| } else { |
| cmd.Println(pkg.ImportPath) |
| } |
| } |
| } |
| } else if pkgsFind != "" { |
| for _, pi := range pp.indexs { |
| for _, pkg := range pi.pkgs { |
| if pkg.Name == pkgsFind { |
| if pkgsPkgOnly && pkg.IsCommand() { |
| continue |
| } |
| if pkgsJson { |
| var p GoPackage |
| p.copyBuild(pkg) |
| b, err := json.MarshalIndent(p, "", "\t") |
| if err == nil { |
| cmd.Stdout.Write(b) |
| cmd.Stdout.Write([]byte{'\n'}) |
| } |
| } else { |
| cmd.Println(pkg.Name) |
| } |
| break |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| // A Package describes a single package found in a directory. |
| type GoPackage struct { |
| // Note: These fields are part of the go command's public API. |
| // See list.go. It is okay to add fields, but not to change or |
| // remove existing ones. Keep in sync with list.go |
| Dir string `json:",omitempty"` // directory containing package sources |
| ImportPath string `json:",omitempty"` // import path of package in dir |
| Name string `json:",omitempty"` // package name |
| Doc string `json:",omitempty"` // package documentation string |
| Target string `json:",omitempty"` // install path |
| Goroot bool `json:",omitempty"` // is this package found in the Go root? |
| Standard bool `json:",omitempty"` // is this package part of the standard Go library? |
| Stale bool `json:",omitempty"` // would 'go install' do anything for this package? |
| Root string `json:",omitempty"` // Go root or Go path dir containing this package |
| ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory |
| |
| // Source files |
| GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) |
| CgoFiles []string `json:",omitempty"` // .go sources files that import "C" |
| IgnoredGoFiles []string `json:",omitempty"` // .go sources ignored due to build constraints |
| CFiles []string `json:",omitempty"` // .c source files |
| CXXFiles []string `json:",omitempty"` // .cc, .cpp and .cxx source files |
| MFiles []string `json:",omitempty"` // .m source files |
| HFiles []string `json:",omitempty"` // .h, .hh, .hpp and .hxx source files |
| SFiles []string `json:",omitempty"` // .s source files |
| SwigFiles []string `json:",omitempty"` // .swig files |
| SwigCXXFiles []string `json:",omitempty"` // .swigcxx files |
| SysoFiles []string `json:",omitempty"` // .syso system object files added to package |
| |
| // Cgo directives |
| CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler |
| CgoCPPFLAGS []string `json:",omitempty"` // cgo: flags for C preprocessor |
| CgoCXXFLAGS []string `json:",omitempty"` // cgo: flags for C++ compiler |
| CgoLDFLAGS []string `json:",omitempty"` // cgo: flags for linker |
| CgoPkgConfig []string `json:",omitempty"` // cgo: pkg-config names |
| |
| // Dependency information |
| Imports []string `json:",omitempty"` // import paths used by this package |
| Deps []string `json:",omitempty"` // all (recursively) imported dependencies |
| |
| // Error information |
| Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies? |
| |
| // Test information |
| TestGoFiles []string `json:",omitempty"` // _test.go files in package |
| TestImports []string `json:",omitempty"` // imports from TestGoFiles |
| XTestGoFiles []string `json:",omitempty"` // _test.go files outside package |
| XTestImports []string `json:",omitempty"` // imports from XTestGoFiles |
| |
| // Unexported fields are not part of the public API. |
| build *build.Package |
| pkgdir string // overrides build.PkgDir |
| imports []*goapi.Package |
| deps []*goapi.Package |
| gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths |
| sfiles []string |
| allgofiles []string // gofiles + IgnoredGoFiles, absolute paths |
| target string // installed file for this package (may be executable) |
| fake bool // synthesized package |
| forceBuild bool // this package must be rebuilt |
| forceLibrary bool // this package is a library (even if named "main") |
| cmdline bool // defined by files listed on command line |
| local bool // imported via local path (./ or ../) |
| localPrefix string // interpret ./ and ../ imports relative to this prefix |
| exeName string // desired name for temporary executable |
| coverMode string // preprocess Go source files with the coverage tool in this mode |
| coverVars map[string]*CoverVar // variables created by coverage analysis |
| omitDWARF bool // tell linker not to write DWARF information |
| } |
| |
| // CoverVar holds the name of the generated coverage variables targeting the named file. |
| type CoverVar struct { |
| File string // local file name |
| Var string // name of count struct |
| } |
| |
| func (p *GoPackage) copyBuild(pp *build.Package) { |
| p.build = pp |
| |
| p.Dir = pp.Dir |
| p.ImportPath = pp.ImportPath |
| p.Name = pp.Name |
| p.Doc = pp.Doc |
| p.Root = pp.Root |
| p.ConflictDir = pp.ConflictDir |
| // TODO? Target |
| p.Goroot = pp.Goroot |
| p.Standard = p.Goroot && p.ImportPath != "" && !strings.Contains(p.ImportPath, ".") |
| p.GoFiles = pp.GoFiles |
| p.CgoFiles = pp.CgoFiles |
| p.IgnoredGoFiles = pp.IgnoredGoFiles |
| p.CFiles = pp.CFiles |
| p.CXXFiles = pp.CXXFiles |
| p.MFiles = pp.MFiles |
| p.HFiles = pp.HFiles |
| p.SFiles = pp.SFiles |
| p.SwigFiles = pp.SwigFiles |
| p.SwigCXXFiles = pp.SwigCXXFiles |
| p.SysoFiles = pp.SysoFiles |
| p.CgoCFLAGS = pp.CgoCFLAGS |
| p.CgoCPPFLAGS = pp.CgoCPPFLAGS |
| p.CgoCXXFLAGS = pp.CgoCXXFLAGS |
| p.CgoLDFLAGS = pp.CgoLDFLAGS |
| p.CgoPkgConfig = pp.CgoPkgConfig |
| p.Imports = pp.Imports |
| p.TestGoFiles = pp.TestGoFiles |
| p.TestImports = pp.TestImports |
| p.XTestGoFiles = pp.XTestGoFiles |
| p.XTestImports = pp.XTestImports |
| } |
| |
| type PathPkgsIndex struct { |
| indexs []*PkgsIndex |
| } |
| |
| func (p *PathPkgsIndex) LoadIndex() { |
| var wg sync.WaitGroup |
| var context = build.Default |
| if pkgsStd { |
| context.GOPATH = "" |
| } |
| var srcDirs []string |
| goroot := context.GOROOT |
| gopath := context.GOPATH |
| context.GOPATH = "" |
| |
| if !pkgsSkipGoroot { |
| //go1.4 go/src/ |
| //go1.3 go/src/pkg; go/src/cmd |
| _, err := os.Stat(filepath.Join(goroot, "src/pkg/runtime")) |
| if err == nil { |
| for _, v := range context.SrcDirs() { |
| if strings.HasSuffix(v, "pkg") { |
| srcDirs = append(srcDirs, v[:len(v)-3]+"cmd") |
| } |
| srcDirs = append(srcDirs, v) |
| } |
| } else { |
| srcDirs = append(srcDirs, filepath.Join(goroot, "src")) |
| } |
| } |
| |
| context.GOPATH = gopath |
| context.GOROOT = "" |
| for _, v := range context.SrcDirs() { |
| srcDirs = append(srcDirs, v) |
| } |
| context.GOROOT = goroot |
| for _, path := range srcDirs { |
| pi := &PkgsIndex{} |
| p.indexs = append(p.indexs, pi) |
| pkgsGate.enter() |
| f, err := os.Open(path) |
| if err != nil { |
| pkgsGate.leave() |
| fmt.Fprint(os.Stderr, err) |
| continue |
| } |
| children, err := f.Readdir(-1) |
| f.Close() |
| pkgsGate.leave() |
| if err != nil { |
| fmt.Fprint(os.Stderr, err) |
| continue |
| } |
| for _, child := range children { |
| if child.IsDir() { |
| wg.Add(1) |
| go func(path, name string) { |
| defer wg.Done() |
| pi.loadPkgsPath(&wg, path, name) |
| }(path, child.Name()) |
| } |
| } |
| } |
| wg.Wait() |
| } |
| |
| func (p *PathPkgsIndex) Sort() { |
| for _, v := range p.indexs { |
| v.sort() |
| } |
| } |
| |
| type PkgsIndex struct { |
| sync.Mutex |
| pkgs []*build.Package |
| } |
| |
| func (p *PkgsIndex) sort() { |
| sort.Sort(PkgSlice(p.pkgs)) |
| } |
| |
| type PkgSlice []*build.Package |
| |
| func (p PkgSlice) Len() int { |
| return len([]*build.Package(p)) |
| } |
| |
| func (p PkgSlice) Less(i, j int) bool { |
| if p[i].IsCommand() && !p[j].IsCommand() { |
| return true |
| } else if !p[i].IsCommand() && p[j].IsCommand() { |
| return false |
| } |
| return p[i].ImportPath < p[j].ImportPath |
| } |
| |
| func (p PkgSlice) Swap(i, j int) { |
| p[i], p[j] = p[j], p[i] |
| } |
| |
| // pkgsgate protects the OS & filesystem from too much concurrency. |
| // Too much disk I/O -> too many threads -> swapping and bad scheduling. |
| // gate is a semaphore for limiting concurrency. |
| type gate chan struct{} |
| |
| func (g gate) enter() { g <- struct{}{} } |
| func (g gate) leave() { <-g } |
| |
| var pkgsGate = make(gate, 8) |
| |
| func (p *PkgsIndex) loadPkgsPath(wg *sync.WaitGroup, root, pkgrelpath string) { |
| importpath := filepath.ToSlash(pkgrelpath) |
| dir := filepath.Join(root, importpath) |
| |
| pkgsGate.enter() |
| defer pkgsGate.leave() |
| pkgDir, err := os.Open(dir) |
| if err != nil { |
| return |
| } |
| children, err := pkgDir.Readdir(-1) |
| pkgDir.Close() |
| if err != nil { |
| return |
| } |
| // hasGo tracks whether a directory actually appears to be a |
| // Go source code directory. If $GOPATH == $HOME, and |
| // $HOME/src has lots of other large non-Go projects in it, |
| // then the calls to importPathToName below can be expensive. |
| hasGo := false |
| for _, child := range children { |
| name := child.Name() |
| if name == "" { |
| continue |
| } |
| if c := name[0]; c == '.' || ('0' <= c && c <= '9') { |
| continue |
| } |
| if strings.HasSuffix(name, ".go") { |
| hasGo = true |
| } |
| if child.IsDir() { |
| if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || name == "testdata" { |
| continue |
| } |
| wg.Add(1) |
| go func(root, name string) { |
| defer wg.Done() |
| p.loadPkgsPath(wg, root, name) |
| }(root, filepath.Join(importpath, name)) |
| } |
| } |
| if hasGo { |
| buildPkg, err := build.ImportDir(dir, 0) |
| if err == nil { |
| if buildPkg.ImportPath == "." { |
| buildPkg.ImportPath = filepath.ToSlash(pkgrelpath) |
| buildPkg.Root = root |
| buildPkg.Goroot = true |
| } |
| p.Lock() |
| p.pkgs = append(p.pkgs, buildPkg) |
| p.Unlock() |
| } |
| } |
| } |