// PackageDoc gets the documentation for the package with the specified import // path and writes it to out. func PackageDoc(ctxt *build.Context, fset *token.FileSet, srcDir string, importPath string) (*Doc, error) { buildPkg, err := ctxt.Import(importPath, srcDir, build.ImportComment) if err != nil { return nil, err } // only parse .go files in the specified package filter := func(info os.FileInfo) bool { for _, fname := range buildPkg.GoFiles { if fname == info.Name() { return true } } return false } // TODO we've already parsed the files via go/loader...can we avoid doing it again? pkgs, err := parser.ParseDir(fset, buildPkg.Dir, filter, parser.PackageClauseOnly|parser.ParseComments) if err != nil { return nil, err } if astPkg, ok := pkgs[buildPkg.Name]; ok { docPkg := doc.New(astPkg, importPath, 0) // TODO: we could also include package-level constants, vars, and functions (like the go doc command) return &Doc{ Name: buildPkg.Name, Decl: "package " + buildPkg.Name, // TODO: add '// import "pkg"' (like godoc) Doc: docPkg.Doc, }, nil } return nil, errors.New("No documentation found for " + buildPkg.Name) }
func resolvePackageSpec(ctx *build.Context, cwd string, src io.Reader, spec string) string { if strings.HasSuffix(spec, ".go") { d := path.Dir(spec) if !buildutil.IsAbsPath(ctx, d) { d = buildutil.JoinPath(ctx, cwd, d) } if bpkg, err := ctx.ImportDir(d, build.FindOnly); err == nil { return bpkg.ImportPath } } path := spec switch { case strings.HasPrefix(spec, "."): if bpkg, err := ctx.Import(spec, cwd, build.FindOnly); err == nil { path = bpkg.ImportPath } case strings.HasPrefix(spec, "/"): path = spec[1:] default: if p, ok := readImports(cwd, src)[spec]; ok { path = p } } return strings.TrimSuffix(path, "/") }
// ContainingPackage returns the package containing filename. // // If filename is not absolute, it is interpreted relative to working directory dir. // All I/O is via the build context's file system interface, if any. // // The '...Files []string' fields of the resulting build.Package are not // populated (build.FindOnly mode). // // TODO(adonovan): call this from oracle when the tree thaws. // func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) { if !IsAbsPath(ctxt, filename) { filename = JoinPath(ctxt, dir, filename) } // We must not assume the file tree uses // "/" always, // `\` always, // or os.PathSeparator (which varies by platform), // but to make any progress, we are forced to assume that // paths will not use `\` unless the PathSeparator // is also `\`, thus we can rely on filepath.ToSlash for some sanity. dirSlash := path.Dir(filepath.ToSlash(filename)) + "/" // We assume that no source root (GOPATH[i] or GOROOT) contains any other. for _, srcdir := range ctxt.SrcDirs() { srcdirSlash := filepath.ToSlash(srcdir) + "/" if dirHasPrefix(dirSlash, srcdirSlash) { importPath := dirSlash[len(srcdirSlash) : len(dirSlash)-len("/")] return ctxt.Import(importPath, dir, build.FindOnly) } } return nil, fmt.Errorf("can't find package containing %s", filename) }
// subpackages returns the set of packages in the given srcDir whose // import paths start with dir. func subpackages(ctxt *build.Context, srcDir string, dir string) map[string]bool { subs := map[string]bool{dir: true} // Find all packages under srcDir whose import paths start with dir. buildutil.ForEachPackage(ctxt, func(pkg string, err error) { if err != nil { log.Fatalf("unexpected error in ForEachPackage: %v", err) } if !strings.HasPrefix(pkg, path.Join(dir, "")) { return } p, err := ctxt.Import(pkg, "", build.FindOnly) if err != nil { log.Fatalf("unexpected: package %s can not be located by build context: %s", pkg, err) } if p.SrcRoot == "" { log.Fatalf("unexpected: could not determine srcDir for package %s: %s", pkg, err) } if p.SrcRoot != srcDir { return } subs[pkg] = true }) return subs }
//Import imports a package. // //path is run through ToImport. // //If ctx is nil, the default context is used. // //N.B. we require a pointer to a build.Context for caching. //Two build contexts with identical values that are not represented //by the same pointer will have all packages imported by them //cached separately. func Import(ctx *build.Context, path string) (*Package, error) { if ctx == nil { ctx = defaultctx } root, path, err := ToImport(path) if err != nil { return nil, err } ident := ident{ctx, path} if pkg := pkgget(ident); pkg != nil { return pkg, nil } p, err := ctx.Import(path, root, 0) if err != nil { return nil, err } pkg := &Package{ Context: ctx, Build: p, } pkgset(ident, pkg) return pkg, nil }
// defaultFindPackage locates the specified (possibly empty) package // using go/build logic. It returns an error if not found. func defaultFindPackage(ctxt *build.Context, path string) (*build.Package, error) { // Import(srcDir="") disables local imports, e.g. import "./foo". bp, err := ctxt.Import(path, "", 0) if _, ok := err.(*build.NoGoError); ok { return bp, nil // empty directory is not an error } return bp, err }
// find_global_file returns the file path of the compiled package corresponding to the specified // import, and a boolean stating whether such path is valid. // TODO: Return only one value, possibly empty string if not found. func find_global_file(imp string, context build.Context) (string, bool) { // gocode synthetically generates the builtin package // "unsafe", since the "unsafe.a" package doesn't really exist. // Thus, when the user request for the package "unsafe" we // would return synthetic global file that would be used // just as a key name to find this synthetic package if imp == "unsafe" { return "unsafe", true } pkgfile := fmt.Sprintf("%s.a", imp) // if lib-path is defined, use it if g_config.LibPath != "" { for _, p := range filepath.SplitList(g_config.LibPath) { pkg_path := filepath.Join(p, pkgfile) if file_exists(pkg_path) { log_found_package_maybe(imp, pkg_path) return pkg_path, true } // Also check the relevant pkg/OS_ARCH dir for the libpath, if provided. pkgdir := fmt.Sprintf("%s_%s", context.GOOS, context.GOARCH) pkg_path = filepath.Join(p, "pkg", pkgdir, pkgfile) if file_exists(pkg_path) { log_found_package_maybe(imp, pkg_path) return pkg_path, true } // Also check the relevant pkg/OS/ARCH dir for the libpath, if provided. pkg_path = filepath.Join(p, "pkg", context.GOOS, context.GOARCH, pkgfile) if file_exists(pkg_path) { log_found_package_maybe(imp, pkg_path) return pkg_path, true } } } p, err := context.Import(imp, "", build.AllowBinary|build.FindOnly) if err == nil { if g_config.Autobuild { err = autobuild(p) if err != nil && *g_debug { log.Printf("Autobuild error: %s\n", err) } } if file_exists(p.PkgObj) { log_found_package_maybe(imp, p.PkgObj) return p.PkgObj, true } } if *g_debug { log.Printf("Import path %q was not resolved\n", imp) log.Println("Gocode's build context is:") log_build_context(context) } return "", false }
// importRuntime locates the the runtime package and parses its files // to *ast.Files. This is used to generate runtime type structures. func parseRuntime(buildctx *build.Context, fset *token.FileSet) ([]*ast.File, error) { buildpkg, err := buildctx.Import("github.com/go-llvm/llgo/pkg/runtime", "", 0) if err != nil { return nil, err } filenames := make([]string, len(buildpkg.GoFiles)) for i, f := range buildpkg.GoFiles { filenames[i] = path.Join(buildpkg.Dir, f) } return parseFiles(fset, filenames) }
// findPackageMember returns the type and position of the declaration of // pkg.member by loading and parsing the files of that package. // srcdir is the directory in which the import appears. func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) { bp, err := ctxt.Import(pkg, srcdir, 0) if err != nil { return 0, token.NoPos, err // no files for package } // TODO(adonovan): opt: parallelize. for _, fname := range bp.GoFiles { filename := filepath.Join(bp.Dir, fname) // Parse the file, opening it the file via the build.Context // so that we observe the effects of the -modified flag. f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0)) if f == nil { continue } // Find a package-level decl called 'member'. for _, decl := range f.Decls { switch decl := decl.(type) { case *ast.GenDecl: for _, spec := range decl.Specs { switch spec := spec.(type) { case *ast.ValueSpec: // const or var for _, id := range spec.Names { if id.Name == member { return decl.Tok, id.Pos(), nil } } case *ast.TypeSpec: if spec.Name.Name == member { return token.TYPE, spec.Name.Pos(), nil } case *ast.AliasSpec: if spec.Name.Name == member { return decl.Tok, spec.Name.Pos(), nil } } } case *ast.FuncDecl: if decl.Recv == nil && decl.Name.Name == member { return token.FUNC, decl.Name.Pos(), nil } } } } return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg) }
func explort(ctx build.Context, pkg *build.Package, visited map[string]bool) { for _, packageName := range pkg.Imports { if !visited[packageName] { visited[packageName] = true if *verbose || strings.Contains(packageName, ".") { fmt.Printf("%s\n", packageName) } if ! (packageName == "C") { child, err := ctx.Import(packageName, pkg.Dir, build.AllowBinary) if err != nil { log.Fatalf("error on import: %s", err); } explort(ctx, child, visited) } } } }
func completePackageArg(ctx *build.Context, cwd string, src io.Reader, arg string) (completions []string) { switch { case arg == ".": completions = []string{"./", "../"} case arg == "..": completions = []string{"../"} case strings.HasPrefix(arg, "."): // Complete using relative directory. bpkg, err := ctx.Import(".", cwd, build.FindOnly) if err != nil { return nil } dir, name := path.Split(arg) fis, err := buildutil.ReadDir(ctx, buildutil.JoinPath(ctx, bpkg.Dir, dir)) if err != nil { return nil } for _, fi := range fis { if !fi.IsDir() || strings.HasPrefix(fi.Name(), ".") { continue } if strings.HasPrefix(fi.Name(), name) { completions = append(completions, path.Join(dir, fi.Name())+"/") } } case strings.HasPrefix(arg, "/"): // Complete using full import path. completions = completePackageArgByPath(ctx, cwd, arg) default: // Complete with package names imported in current file. for n := range readImports(cwd, src) { if strings.HasPrefix(n, arg) { completions = append(completions, n) } } } if len(completions) == 0 { completions = []string{arg} } sort.Strings(completions) return completions }
func importer(ctx *build.Context, srcDir string, vendor map[string]string) ast.Importer { return func(imports map[string]*ast.Object, importPath string) (*ast.Object, error) { pkg := imports[importPath] if pkg != nil { return pkg, nil } var name string bpkg, err := ctx.Import(importPath, srcDir, 0) if err != nil { name = guessPackageNameFromPath(importPath) } else { name = bpkg.Name vendor[importPath] = bpkg.ImportPath } pkg = ast.NewObj(ast.Pkg, name) pkg.Data = ast.NewScope(nil) imports[importPath] = pkg return pkg, nil } }
// MakeGoBuildLoader returns an implementation of the SourceLoader // function prototype that locates packages using the go/build // libraries. It may return nil upon gross misconfiguration // (e.g. os.Getwd() failed). // // ctxt specifies the go/build.Context to use; if nil, the default // Context is used. // func MakeGoBuildLoader(ctxt *build.Context) SourceLoader { srcDir, err := os.Getwd() if err != nil { return nil // serious misconfiguration } if ctxt == nil { ctxt = &build.Default } return func(fset *token.FileSet, path string) (files []*ast.File, err error) { // TODO(adonovan): fix: Do we need cwd? Shouldn't // ImportDir(path) / $GOROOT suffice? bp, err := ctxt.Import(path, srcDir, 0) if err != nil { return // import failed } files, err = ParseFiles(fset, bp.Dir, bp.GoFiles...) if err != nil { return nil, err } return } }
// Build scans the specified Go workspace and builds the forward and // reverse import dependency graphs for all its packages. // It also returns a mapping from canonical import paths to errors for packages // whose loading was not entirely successful. // A package may appear in the graph and in the errors mapping. // All package paths are canonical and may contain "/vendor/". func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { type importEdge struct { from, to string } type pathError struct { path string err error } ch := make(chan interface{}) go func() { sema := make(chan int, 20) // I/O concurrency limiting semaphore var wg sync.WaitGroup buildutil.ForEachPackage(ctxt, func(path string, err error) { if err != nil { ch <- pathError{path, err} return } wg.Add(1) go func() { defer wg.Done() sema <- 1 bp, err := ctxt.Import(path, "", 0) <-sema if err != nil { if _, ok := err.(*build.NoGoError); ok { // empty directory is not an error } else { ch <- pathError{path, err} } // Even in error cases, Import usually returns a package. } // absolutize resolves an import path relative // to the current package bp. // The absolute form may contain "vendor". // // The vendoring feature slows down Build by 3×. // Here are timings from a 1400 package workspace: // 1100ms: current code (with vendor check) // 880ms: with a nonblocking cache around ctxt.IsDir // 840ms: nonblocking cache with duplicate suppression // 340ms: original code (no vendor check) // TODO(adonovan): optimize, somehow. memo := make(map[string]string) absolutize := func(path string) string { canon, ok := memo[path] if !ok { sema <- 1 bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly) <-sema if bp2 != nil { canon = bp2.ImportPath } else { canon = path } memo[path] = canon } return canon } if bp != nil { for _, imp := range bp.Imports { ch <- importEdge{path, absolutize(imp)} } for _, imp := range bp.TestImports { ch <- importEdge{path, absolutize(imp)} } for _, imp := range bp.XTestImports { ch <- importEdge{path, absolutize(imp)} } } }() }) wg.Wait() close(ch) }() forward = make(Graph) reverse = make(Graph) for e := range ch { switch e := e.(type) { case pathError: if errors == nil { errors = make(map[string]error) } errors[e.path] = e.err case importEdge: if e.to == "C" { continue // "C" is fake } forward.addEdge(e.from, e.to) reverse.addEdge(e.to, e.from) } } return forward, reverse, errors }
// loadPackage returns details about the Go package named by the import // path, interpreting local import paths relative to the srcDir directory. func loadPackage(ctx *build.Context, importPath string, srcDir string, flags int) (*pkg, error) { bpkg, err := ctx.Import(importPath, srcDir, build.ImportComment) if _, ok := err.(*build.NoGoError); ok { return &pkg{Build: bpkg}, nil } if err != nil { return nil, err } pkg := &pkg{ FSet: token.NewFileSet(), Build: bpkg, } files := make(map[string]*ast.File) for _, name := range append(pkg.Build.GoFiles, pkg.Build.CgoFiles...) { file, err := pkg.parseFile(ctx, name) if err != nil { pkg.Errors = append(pkg.Errors, err) continue } files[name] = file } vendor := make(map[string]string) pkg.AST, _ = ast.NewPackage(pkg.FSet, files, importer(ctx, bpkg.Dir, vendor), nil) if flags&loadPackageFixVendor != 0 { for _, f := range pkg.AST.Files { for _, i := range f.Imports { if lit := i.Path; lit != nil { if s, err := strconv.Unquote(lit.Value); err != nil { if p, ok := vendor[s]; ok { lit.Value = strconv.Quote(p) } } } } } } if flags&loadPackageDoc != 0 { mode := godoc.Mode(0) if pkg.Build.ImportPath == "builtin" || flags&loadPackageUnexported != 0 { mode |= godoc.AllDecls } pkg.GoDoc = godoc.New(pkg.AST, pkg.Build.ImportPath, mode) if pkg.Build.ImportPath == "builtin" { for _, t := range pkg.GoDoc.Types { pkg.GoDoc.Funcs = append(pkg.GoDoc.Funcs, t.Funcs...) t.Funcs = nil } sort.Sort(byFuncName(pkg.GoDoc.Funcs)) } } if flags&loadPackageExamples != 0 { for _, name := range append(pkg.Build.TestGoFiles, pkg.Build.XTestGoFiles...) { file, err := pkg.parseFile(ctx, name) if err != nil { pkg.Errors = append(pkg.Errors, err) continue } pkg.Examples = append(pkg.Examples, godoc.Examples(file)...) } } return pkg, nil }
func getPackage(pkgpath string) (*gobuild.Package, error) { var ctx gobuild.Context = gobuild.Default ctx.GOARCH = GOARCH ctx.GOOS = GOOS ctx.BuildTags = append(ctx.BuildTags[:], "llgo") //ctx.Compiler = "llgo" // Attempt to find an overlay package path, // which we'll use in ReadDir below. overlayentries := make(map[string]bool) overlaypkgpath := llgoPkgPrefix + pkgpath overlaypkg, err := ctx.Import(overlaypkgpath, "", gobuild.FindOnly) if err != nil { overlaypkg = nil } // ReadDir is overridden to return a fake ".s" // file for each ".ll" file in the directory. ctx.ReadDir = func(dir string) (fi []os.FileInfo, err error) { fi, err = ioutil.ReadDir(dir) if err != nil { return nil, err } entries := make(map[string]os.FileInfo) for _, info := range fi { entries[info.Name()] = info } // Overlay all files in the overlay package dir. // If we find any .ll files, replace the suffix // with .s. if overlaypkg != nil { fi, err = ioutil.ReadDir(overlaypkg.Dir) } if err == nil { // Check for .ll files in the overlay dir if // we have one, else in the standard package dir. for _, info := range fi { name := info.Name() if strings.HasSuffix(name, ".ll") { name = name[:len(name)-3] + ".s" info = &renamedFileInfo{info, name} } overlayentries[name] = true entries[name] = info } } fi = make([]os.FileInfo, 0, len(entries)) for _, info := range entries { fi = append(fi, info) } return fi, nil } // OpenFile is overridden to return the contents // of the ".ll" file found in ReadDir above. The // returned ReadCloser is wrapped to transform // LLVM IR comments to use "//", as expected by // go/build when looking for build tags. ctx.OpenFile = func(path string) (io.ReadCloser, error) { base := filepath.Base(path) overlay := overlayentries[base] if overlay { if overlaypkg != nil { path = filepath.Join(overlaypkg.Dir, base) } if strings.HasSuffix(path, ".s") { path := path[:len(path)-2] + ".ll" var r io.ReadCloser var err error r, err = os.Open(path) if err == nil { r = build.NewLLVMIRReader(r) } return r, err } } return os.Open(path) } pkg, err := ctx.Import(pkgpath, "", 0) if err != nil { return nil, err } else { if overlaypkg == nil { overlaypkg = pkg } for i, filename := range pkg.GoFiles { pkgdir := pkg.Dir if overlayentries[filename] { pkgdir = overlaypkg.Dir } pkg.GoFiles[i] = path.Join(pkgdir, filename) } for i, filename := range pkg.CFiles { pkgdir := pkg.Dir if overlayentries[filename] { pkgdir = overlaypkg.Dir } pkg.CFiles[i] = path.Join(pkgdir, filename) } for i, filename := range pkg.SFiles { pkgdir := pkg.Dir if overlayentries[filename] { pkgdir = overlaypkg.Dir filename = filename[:len(filename)-2] + ".ll" } else { err := fmt.Errorf("No matching .ll file for %q", filename) return nil, err } pkg.SFiles[i] = path.Join(pkgdir, filename) } } return pkg, nil }
func (p *Package) parser(ctx build.Context) (*doc.Package, error) { bp, err := ctx.Import(p.pkg, "", 0) if err != nil { return nil, err } ctxName := contextName(&ctx) p.bps[ctxName] = bp var files []string files = append(files, bp.GoFiles...) files = append(files, bp.CgoFiles...) fs := make(map[string]*ast.File) var afiles []*ast.File for _, file := range files { fileName := filepath.Join(bp.Dir, file) f, ok := p.files[fileName] if !ok { f, err = parser.ParseFile(p.fset, fileName, nil, parser.ParseComments) if err != nil { return nil, err } p.files[fileName] = f } pkgName := f.Name.Name if pkgName == bp.Name { fs[fileName] = f } afiles = append(afiles, f) } apkg, err := ast.NewPackage(p.fset, fs, nil, nil) if err != nil { //fmt.Println(err) } dpkg := doc.New(apkg, bp.Name, doc.AllMethods|doc.AllDecls) p.dpkg[contextName(&ctx)] = dpkg updata := func(typ DocType, ctx string, key string, value interface{}) { if p.keys[typ] == nil { p.keys[typ] = make(map[string]map[string]interface{}) } if p.keys[typ][key] == nil { p.keys[typ][key] = make(map[string]interface{}) } p.keys[typ][key][ctx] = value } //types for _, v := range dpkg.Types { typ := parserTypeDecl(v.Decl) if typ == NilType { continue } updata(typ, ctxName, v.Name, v) //type func for _, f := range v.Funcs { updata(Factor, ctxName, f.Name, f) } //type var for _, f := range v.Vars { for _, name := range f.Names { updata(Var, ctxName, name, f) } } //type const for _, f := range v.Consts { for _, name := range f.Names { updata(Const, ctxName, name, f) } } } //funcs for _, v := range dpkg.Funcs { updata(Func, ctxName, v.Name, v) } //consts for _, v := range dpkg.Consts { for _, name := range v.Names { updata(Const, ctxName, name, v) } } //vars for _, v := range dpkg.Vars { for _, name := range v.Names { updata(Var, ctxName, name, v) } } return dpkg, nil }
// parseFromFlag interprets the "-from" flag value as a renaming specification. // See FromFlagUsage for valid formats. func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) { var spec spec var main string // sans "::x" suffix switch parts := strings.Split(fromFlag, "::"); len(parts) { case 1: main = parts[0] case 2: main = parts[0] spec.searchFor = parts[1] if parts[1] == "" { // error } default: return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag) } // main is one of: // filename.go // importpath // importpath.member // (importpath.type).fieldormethod if strings.HasSuffix(main, ".go") { // filename.go if spec.searchFor == "" { return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main) } spec.filename = main if !buildutil.FileExists(ctxt, spec.filename) { return nil, fmt.Errorf("no such file: %s", spec.filename) } bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) if err != nil { return nil, err } spec.pkg = bp.ImportPath } else if a, b := splitAtLastDot(main); b == "" { // importpath e.g. "encoding/json" if spec.searchFor == "" { return nil, fmt.Errorf("-from %q: package import path %q must have a ::name suffix", main, a) } spec.pkg = a } else if strings.HasPrefix(a, "(") && strings.HasSuffix(a, ")") { // field/method of type e.g. (encoding/json.Decoder).Decode c, d := splitAtLastDot(a[1 : len(a)-1]) if d == "" { return nil, fmt.Errorf("-from %q: not a package-level named type: %q", a) } spec.pkg = c // e.g. "encoding/json" spec.pkgMember = d // e.g. "Decoder" spec.typeMember = b // e.g. "Decode" spec.fromName = b } else { // package member e.g. "encoding/json.HTMLEscape" spec.pkg = a // e.g. "encoding/json" spec.pkgMember = b // e.g. "HTMLEscape" spec.fromName = b } if spec.searchFor != "" { spec.fromName = spec.searchFor } // Sanitize the package. // TODO(adonovan): test with relative packages. May need loader changes. bp, err := ctxt.Import(spec.pkg, ".", build.FindOnly) if err != nil { return nil, fmt.Errorf("can't find package %q", spec.pkg) } spec.pkg = bp.ImportPath if !isValidIdentifier(spec.fromName) { return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName) } if Verbose { fmt.Fprintf(os.Stderr, "-from spec: %+v\n", spec) } return &spec, nil }
// parseFromFlag interprets the "-from" flag value as a renaming specification. // See Usage in rename.go for valid formats. func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) { var spec spec var main string // sans "::x" suffix switch parts := strings.Split(fromFlag, "::"); len(parts) { case 1: main = parts[0] case 2: main = parts[0] spec.searchFor = parts[1] if parts[1] == "" { // error } default: return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag) } if strings.HasSuffix(main, ".go") { // main is "filename.go" if spec.searchFor == "" { return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main) } spec.filename = main if !buildutil.FileExists(ctxt, spec.filename) { return nil, fmt.Errorf("no such file: %s", spec.filename) } bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) if err != nil { return nil, err } spec.pkg = bp.ImportPath } else { // main is one of: // "importpath" // "importpath".member // (*"importpath".type).fieldormethod (parens and star optional) if err := parseObjectSpec(&spec, main); err != nil { return nil, err } } if spec.searchFor != "" { spec.fromName = spec.searchFor } cwd, err := os.Getwd() if err != nil { return nil, err } // Sanitize the package. bp, err := ctxt.Import(spec.pkg, cwd, build.FindOnly) if err != nil { return nil, fmt.Errorf("can't find package %q", spec.pkg) } spec.pkg = bp.ImportPath if !isValidIdentifier(spec.fromName) { return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName) } if Verbose { log.Printf("-from spec: %+v", spec) } return &spec, nil }
// Builds scans the specified Go workspace and builds the forward and // reverse import dependency graphs for all its packages. // It also returns a mapping from import paths to errors for packages // that could not be loaded. func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { // The (sole) graph builder goroutine receives a stream of import // edges from the package loading goroutine. forward = make(Graph) reverse = make(Graph) edgec := make(chan [2]string) go func() { for edge := range edgec { if edge[1] == "C" { continue // "C" is fake } forward.addEdge(edge[0], edge[1]) reverse.addEdge(edge[1], edge[0]) } }() // The (sole) error goroutine receives a stream of ReadDir and // Import errors. type pathError struct { path string err error } errorc := make(chan pathError) go func() { for e := range errorc { if errors == nil { errors = make(map[string]error) } errors[e.path] = e.err } }() var wg sync.WaitGroup buildutil.ForEachPackage(ctxt, func(path string, err error) { if err != nil { errorc <- pathError{path, err} return } wg.Add(1) // The import goroutines load the metadata for each package. go func(path string) { defer wg.Done() bp, err := ctxt.Import(path, "", 0) if _, ok := err.(*build.NoGoError); ok { return // empty directory is not an error } if err != nil { errorc <- pathError{path, err} return } for _, imp := range bp.Imports { edgec <- [2]string{path, imp} } for _, imp := range bp.TestImports { edgec <- [2]string{path, imp} } for _, imp := range bp.XTestImports { edgec <- [2]string{path, imp} } }(path) }) wg.Wait() close(edgec) close(errorc) return forward, reverse, errors }
// Builds scans the specified Go workspace and builds the forward and // reverse import dependency graphs for all its packages. // It also returns a mapping from import paths to errors for packages // that could not be loaded. func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { type importEdge struct { from, to string } type pathError struct { path string err error } ch := make(chan interface{}) var wg sync.WaitGroup buildutil.ForEachPackage(ctxt, func(path string, err error) { wg.Add(1) go func() { defer wg.Done() if err != nil { ch <- pathError{path, err} return } bp, err := ctxt.Import(path, "", 0) if _, ok := err.(*build.NoGoError); ok { return // empty directory is not an error } if err != nil { ch <- pathError{path, err} return } for _, imp := range bp.Imports { ch <- importEdge{path, imp} } for _, imp := range bp.TestImports { ch <- importEdge{path, imp} } for _, imp := range bp.XTestImports { ch <- importEdge{path, imp} } }() }) go func() { wg.Wait() close(ch) }() forward = make(Graph) reverse = make(Graph) for e := range ch { switch e := e.(type) { case pathError: if errors == nil { errors = make(map[string]error) } errors[e.path] = e.err case importEdge: if e.to == "C" { continue // "C" is fake } forward.addEdge(e.from, e.to) reverse.addEdge(e.to, e.from) } } return forward, reverse, errors }
// imports returns a map of all import directories used by the app. // The return value maps full directory names to original import names. func imports(ctxt *build.Context, srcDir string) (map[string]string, error) { result := make(map[string]string) type importFrom struct { path, fromDir string } var imports []importFrom visited := make(map[importFrom]bool) pkg, err := ctxt.ImportDir(srcDir, 0) if err != nil { return nil, err } for _, v := range pkg.Imports { imports = append(imports, importFrom{ path: v, fromDir: srcDir, }) } // Resolve all non-standard-library imports for len(imports) != 0 { i := imports[0] imports = imports[1:] // shift if i.path == "C" { // ignore cgo continue } if _, ok := visited[i]; ok { // already scanned continue } visited[i] = true abs, err := filepath.Abs(i.fromDir) if err != nil { return nil, fmt.Errorf("unable to get absolute directory of %q: %v", i.fromDir, err) } pkg, err := ctxt.Import(i.path, abs, 0) if err != nil { return nil, fmt.Errorf("unable to find import %s, imported from %q: %v", i.path, i.fromDir, err) } // TODO(cbro): handle packages that are vendored by multiple imports correctly. if pkg.Goroot { // ignore standard library imports continue } vlogf("Located %q (imported from %q) -> %q", i.path, i.fromDir, pkg.Dir) result[pkg.Dir] = i.path for _, v := range pkg.Imports { imports = append(imports, importFrom{ path: v, fromDir: pkg.Dir, }) } } return result, nil }