mkindex.go

  1// +build ignore
  2
  3// Copyright 2013 The Go Authors. All rights reserved.
  4// Use of this source code is governed by a BSD-style
  5// license that can be found in the LICENSE file.
  6
  7// Command mkindex creates the file "pkgindex.go" containing an index of the Go
  8// standard library. The file is intended to be built as part of the imports
  9// package, so that the package may be used in environments where a GOROOT is
 10// not available (such as App Engine).
 11package main
 12
 13import (
 14	"bytes"
 15	"fmt"
 16	"go/ast"
 17	"go/build"
 18	"go/format"
 19	"go/parser"
 20	"go/token"
 21	"io/ioutil"
 22	"log"
 23	"os"
 24	"path"
 25	"path/filepath"
 26	"strings"
 27)
 28
 29var (
 30	pkgIndex = make(map[string][]pkg)
 31	exports  = make(map[string]map[string]bool)
 32)
 33
 34func main() {
 35	// Don't use GOPATH.
 36	ctx := build.Default
 37	ctx.GOPATH = ""
 38
 39	// Populate pkgIndex global from GOROOT.
 40	for _, path := range ctx.SrcDirs() {
 41		f, err := os.Open(path)
 42		if err != nil {
 43			log.Print(err)
 44			continue
 45		}
 46		children, err := f.Readdir(-1)
 47		f.Close()
 48		if err != nil {
 49			log.Print(err)
 50			continue
 51		}
 52		for _, child := range children {
 53			if child.IsDir() {
 54				loadPkg(path, child.Name())
 55			}
 56		}
 57	}
 58	// Populate exports global.
 59	for _, ps := range pkgIndex {
 60		for _, p := range ps {
 61			e := loadExports(p.dir)
 62			if e != nil {
 63				exports[p.dir] = e
 64			}
 65		}
 66	}
 67
 68	// Construct source file.
 69	var buf bytes.Buffer
 70	fmt.Fprint(&buf, pkgIndexHead)
 71	fmt.Fprintf(&buf, "var pkgIndexMaster = %#v\n", pkgIndex)
 72	fmt.Fprintf(&buf, "var exportsMaster = %#v\n", exports)
 73	src := buf.Bytes()
 74
 75	// Replace main.pkg type name with pkg.
 76	src = bytes.Replace(src, []byte("main.pkg"), []byte("pkg"), -1)
 77	// Replace actual GOROOT with "/go".
 78	src = bytes.Replace(src, []byte(ctx.GOROOT), []byte("/go"), -1)
 79	// Add some line wrapping.
 80	src = bytes.Replace(src, []byte("}, "), []byte("},\n"), -1)
 81	src = bytes.Replace(src, []byte("true, "), []byte("true,\n"), -1)
 82
 83	var err error
 84	src, err = format.Source(src)
 85	if err != nil {
 86		log.Fatal(err)
 87	}
 88
 89	// Write out source file.
 90	err = ioutil.WriteFile("pkgindex.go", src, 0644)
 91	if err != nil {
 92		log.Fatal(err)
 93	}
 94}
 95
 96const pkgIndexHead = `package imports
 97
 98func init() {
 99	pkgIndexOnce.Do(func() {
100		pkgIndex.m = pkgIndexMaster
101	})
102	loadExports = func(dir string) map[string]bool {
103		return exportsMaster[dir]
104	}
105}
106`
107
108type pkg struct {
109	importpath string // full pkg import path, e.g. "net/http"
110	dir        string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
111}
112
113var fset = token.NewFileSet()
114
115func loadPkg(root, importpath string) {
116	shortName := path.Base(importpath)
117	if shortName == "testdata" {
118		return
119	}
120
121	dir := filepath.Join(root, importpath)
122	pkgIndex[shortName] = append(pkgIndex[shortName], pkg{
123		importpath: importpath,
124		dir:        dir,
125	})
126
127	pkgDir, err := os.Open(dir)
128	if err != nil {
129		return
130	}
131	children, err := pkgDir.Readdir(-1)
132	pkgDir.Close()
133	if err != nil {
134		return
135	}
136	for _, child := range children {
137		name := child.Name()
138		if name == "" {
139			continue
140		}
141		if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
142			continue
143		}
144		if child.IsDir() {
145			loadPkg(root, filepath.Join(importpath, name))
146		}
147	}
148}
149
150func loadExports(dir string) map[string]bool {
151	exports := make(map[string]bool)
152	buildPkg, err := build.ImportDir(dir, 0)
153	if err != nil {
154		if strings.Contains(err.Error(), "no buildable Go source files in") {
155			return nil
156		}
157		log.Printf("could not import %q: %v", dir, err)
158		return nil
159	}
160	for _, file := range buildPkg.GoFiles {
161		f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
162		if err != nil {
163			log.Printf("could not parse %q: %v", file, err)
164			continue
165		}
166		for name := range f.Scope.Objects {
167			if ast.IsExported(name) {
168				exports[name] = true
169			}
170		}
171	}
172	return exports
173}