blob: fe47e5ceda9e049a225da602fe8b40e3ad497522 [file] [log] [blame]
// 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()
}
}
}