Skip to content

Commit 40f830c

Browse files
committed
Modify imports package to avoid disk IO, update to use Go 1.5 and js packages.
Generate zstdlib.go, include Go 1.5, skip syscall. Use a modified version of cmd/api tool to include gopherjs/js package. Add go generate step for imports package to update.sh.
1 parent 5a3b554 commit 40f830c

File tree

7 files changed

+3160
-7746
lines changed

7 files changed

+3160
-7746
lines changed

playground/internal/imports/fix.go

Lines changed: 6 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,10 @@
55
package imports
66

77
import (
8-
"fmt"
98
"go/ast"
10-
"go/build"
11-
"go/parser"
129
"go/token"
13-
"os"
1410
"path"
15-
"path/filepath"
1611
"strings"
17-
"sync"
1812

1913
"golang.org/x/tools/go/ast/astutil"
2014
)
@@ -145,243 +139,41 @@ func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
145139
}
146140

147141
// importPathToName returns the package name for the given import path.
148-
var importPathToName = importPathToNameGoPath
142+
var importPathToName = importPathToNameBasic
149143

150144
// importPathToNameBasic assumes the package name is the base of import path.
151145
func importPathToNameBasic(importPath string) (packageName string) {
152146
return path.Base(importPath)
153147
}
154148

155-
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
156-
// If there's a problem, it falls back to using importPathToNameBasic.
157-
func importPathToNameGoPath(importPath string) (packageName string) {
158-
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
159-
return buildPkg.Name
160-
} else {
161-
return importPathToNameBasic(importPath)
162-
}
163-
}
164-
165149
type pkg struct {
166150
importpath string // full pkg import path, e.g. "net/http"
167151
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
168152
}
169153

170-
var pkgIndexOnce sync.Once
171-
172-
var pkgIndex struct {
173-
sync.Mutex
174-
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
175-
}
176-
177-
// gate is a semaphore for limiting concurrency.
178-
type gate chan struct{}
179-
180-
func (g gate) enter() { g <- struct{}{} }
181-
func (g gate) leave() { <-g }
182-
183-
// fsgate protects the OS & filesystem from too much concurrency.
184-
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
185-
var fsgate = make(gate, 8)
186-
187-
func loadPkgIndex() {
188-
pkgIndex.Lock()
189-
pkgIndex.m = make(map[string][]pkg)
190-
pkgIndex.Unlock()
191-
192-
var wg sync.WaitGroup
193-
for _, path := range build.Default.SrcDirs() {
194-
fsgate.enter()
195-
f, err := os.Open(path)
196-
if err != nil {
197-
fsgate.leave()
198-
fmt.Fprint(os.Stderr, err)
199-
continue
200-
}
201-
children, err := f.Readdir(-1)
202-
f.Close()
203-
fsgate.leave()
204-
if err != nil {
205-
fmt.Fprint(os.Stderr, err)
206-
continue
207-
}
208-
for _, child := range children {
209-
if child.IsDir() {
210-
wg.Add(1)
211-
go func(path, name string) {
212-
defer wg.Done()
213-
loadPkg(&wg, path, name)
214-
}(path, child.Name())
215-
}
216-
}
217-
}
218-
wg.Wait()
219-
}
220-
221-
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
222-
importpath := filepath.ToSlash(pkgrelpath)
223-
dir := filepath.Join(root, importpath)
224-
225-
fsgate.enter()
226-
defer fsgate.leave()
227-
pkgDir, err := os.Open(dir)
228-
if err != nil {
229-
return
230-
}
231-
children, err := pkgDir.Readdir(-1)
232-
pkgDir.Close()
233-
if err != nil {
234-
return
235-
}
236-
// hasGo tracks whether a directory actually appears to be a
237-
// Go source code directory. If $GOPATH == $HOME, and
238-
// $HOME/src has lots of other large non-Go projects in it,
239-
// then the calls to importPathToName below can be expensive.
240-
hasGo := false
241-
for _, child := range children {
242-
// Avoid .foo, _foo, and testdata directory trees.
243-
name := child.Name()
244-
if name == "" || name[0] == '.' || name[0] == '_' || name == "testdata" {
245-
continue
246-
}
247-
if strings.HasSuffix(name, ".go") {
248-
hasGo = true
249-
}
250-
if child.IsDir() {
251-
wg.Add(1)
252-
go func(root, name string) {
253-
defer wg.Done()
254-
loadPkg(wg, root, name)
255-
}(root, filepath.Join(importpath, name))
256-
}
257-
}
258-
if hasGo {
259-
shortName := importPathToName(importpath)
260-
pkgIndex.Lock()
261-
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
262-
importpath: importpath,
263-
dir: dir,
264-
})
265-
pkgIndex.Unlock()
266-
}
267-
268-
}
269-
270-
// loadExports returns a list exports for a package.
271-
var loadExports = loadExportsGoPath
272-
273-
func loadExportsGoPath(dir string) map[string]bool {
274-
exports := make(map[string]bool)
275-
buildPkg, err := build.ImportDir(dir, 0)
276-
if err != nil {
277-
if strings.Contains(err.Error(), "no buildable Go source files in") {
278-
return nil
279-
}
280-
fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err)
281-
return nil
282-
}
283-
fset := token.NewFileSet()
284-
for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} {
285-
for _, file := range files {
286-
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
287-
if err != nil {
288-
fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err)
289-
continue
290-
}
291-
for name := range f.Scope.Objects {
292-
if ast.IsExported(name) {
293-
exports[name] = true
294-
}
295-
}
296-
}
297-
}
298-
return exports
299-
}
300-
301154
// findImport searches for a package with the given symbols.
302155
// If no package is found, findImport returns "".
303156
// Declared as a variable rather than a function so goimports can be easily
304157
// extended by adding a file with an init function.
305-
var findImport = findImportGoPath
306-
307-
func findImportGoPath(pkgName string, symbols map[string]bool) (string, bool, error) {
308-
// Fast path for the standard library.
309-
// In the common case we hopefully never have to scan the GOPATH, which can
310-
// be slow with moving disks.
311-
if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok {
312-
return pkg, rename, nil
313-
}
314-
315-
// TODO(sameer): look at the import lines for other Go files in the
316-
// local directory, since the user is likely to import the same packages
317-
// in the current Go file. Return rename=true when the other Go files
318-
// use a renamed package that's also used in the current file.
319-
320-
pkgIndexOnce.Do(loadPkgIndex)
321-
322-
// Collect exports for packages with matching names.
323-
var wg sync.WaitGroup
324-
var pkgsMu sync.Mutex // guards pkgs
325-
// full importpath => exported symbol => True
326-
// e.g. "net/http" => "Client" => True
327-
pkgs := make(map[string]map[string]bool)
328-
pkgIndex.Lock()
329-
for _, pkg := range pkgIndex.m[pkgName] {
330-
wg.Add(1)
331-
go func(importpath, dir string) {
332-
defer wg.Done()
333-
exports := loadExports(dir)
334-
if exports != nil {
335-
pkgsMu.Lock()
336-
pkgs[importpath] = exports
337-
pkgsMu.Unlock()
338-
}
339-
}(pkg.importpath, pkg.dir)
340-
}
341-
pkgIndex.Unlock()
342-
wg.Wait()
343-
344-
// Filter out packages missing required exported symbols.
345-
for symbol := range symbols {
346-
for importpath, exports := range pkgs {
347-
if !exports[symbol] {
348-
delete(pkgs, importpath)
349-
}
350-
}
351-
}
352-
if len(pkgs) == 0 {
353-
return "", false, nil
354-
}
355-
356-
// If there are multiple candidate packages, the shortest one wins.
357-
// This is a heuristic to prefer the standard library (e.g. "bytes")
358-
// over e.g. "github.com/foo/bar/bytes".
359-
shortest := ""
360-
for importPath := range pkgs {
361-
if shortest == "" || len(importPath) < len(shortest) {
362-
shortest = importPath
363-
}
364-
}
365-
return shortest, false, nil
366-
}
158+
var findImport = findImportStdlib
367159

368160
type visitFn func(node ast.Node) ast.Visitor
369161

370162
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
371163
return fn(node)
372164
}
373165

374-
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) {
166+
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename bool, err error) {
375167
for symbol := range symbols {
376168
path := stdlib[shortPkg+"."+symbol]
377169
if path == "" {
378-
return "", false, false
170+
return "", false, nil
379171
}
380172
if importPath != "" && importPath != path {
381173
// Ambiguous. Symbols pointed to different things.
382-
return "", false, false
174+
return "", false, nil
383175
}
384176
importPath = path
385177
}
386-
return importPath, false, importPath != ""
178+
return importPath, false, nil
387179
}

playground/internal/imports/gen.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:generate go run mkapi.go -output=gopherjs.txt -gopath github.com/gopherjs/gopherjs/js
2+
//go:generate go run mkstdlib.go -output=zstdlib
3+
//go:generate rm gopherjs.txt
4+
5+
package imports

playground/internal/imports/imports.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
// Package imports implements a Go pretty-printer (like package "go/format")
66
// that also adds or removes import statements as necessary.
7-
package imports // import "golang.org/x/tools/imports"
7+
package imports
88

99
import (
1010
"bufio"

playground/internal/imports/mkapi.go

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
// +build generate
2+
13
// Copyright 2011 The Go Authors. All rights reserved.
24
// Use of this source code is governed by a BSD-style
35
// license that can be found in the LICENSE file.
46

57
// Binary api computes the exported API of a set of Go packages.
8+
//
9+
// It's a copy of cmd/api tool, but with a patch to enable
10+
// operating on packages in GOPATH workspaces merged in
11+
// (https://github.com/golang/go/issues/4993#issuecomment-66075958),
12+
// and an output flag added for go generate usage.
613
package main
714

815
import (
@@ -29,12 +36,14 @@ import (
2936

3037
// Flags
3138
var (
32-
checkFile = flag.String("c", "", "optional comma-separated filename(s) to check API against")
33-
allowNew = flag.Bool("allow_new", true, "allow API additions")
34-
exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool")
35-
nextFile = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.")
36-
verbose = flag.Bool("v", false, "verbose debugging")
37-
forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
39+
output = flag.String("output", "", "output file name; if empty, then print to stdout")
40+
includeGopath = flag.Bool("gopath", false, "include GOPATH packages")
41+
checkFile = flag.String("c", "", "optional comma-separated filename(s) to check API against")
42+
allowNew = flag.Bool("allow_new", true, "allow API additions")
43+
exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool")
44+
nextFile = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.")
45+
verbose = flag.Bool("v", false, "verbose debugging")
46+
forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
3847
)
3948

4049
// contexts are the default contexts which are scanned, unless
@@ -126,6 +135,12 @@ func main() {
126135
var pkgNames []string
127136
if flag.NArg() > 0 {
128137
pkgNames = flag.Args()
138+
} else if *includeGopath {
139+
alls, err := exec.Command("go", "list", "all").Output()
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
pkgNames = strings.Fields(string(alls))
129144
} else {
130145
stds, err := exec.Command("go", "list", "std").Output()
131146
if err != nil {
@@ -186,7 +201,19 @@ func main() {
186201
}
187202
}()
188203

189-
bw := bufio.NewWriter(os.Stdout)
204+
var out io.Writer
205+
switch *output {
206+
case "":
207+
out = os.Stdout
208+
default:
209+
f, err := os.OpenFile(*output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
210+
if err != nil {
211+
log.Fatalf("opening file %q for output: %v", *output, err)
212+
}
213+
defer f.Close()
214+
out = f
215+
}
216+
bw := bufio.NewWriter(out)
190217
defer bw.Flush()
191218

192219
if *checkFile == "" {
@@ -428,17 +455,27 @@ func (w *Walker) Import(name string) (*types.Package, error) {
428455
}
429456
w.imported[name] = &importing
430457

431-
// Determine package files.
432-
dir := filepath.Join(w.root, filepath.FromSlash(name))
433-
if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
434-
log.Fatalf("no source in tree for package %q", pkg)
435-
}
436-
437458
context := w.context
438459
if context == nil {
439460
context = &build.Default
440461
}
441462

463+
// Determine package files.
464+
var dir string
465+
// if 'name' is not a std package, then include GOPATH
466+
if pkg == nil && *includeGopath {
467+
c_pkg, err := build.Default.Import(name, "", build.FindOnly)
468+
if err != nil {
469+
log.Fatalf("failed to find import %q", name)
470+
}
471+
dir = c_pkg.Dir
472+
} else {
473+
dir = filepath.Join(w.root, filepath.FromSlash(name))
474+
if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
475+
log.Fatalf("no source in tree for package %q (import name %q)", pkg, name)
476+
}
477+
}
478+
442479
// Look in cache.
443480
// If we've already done an import with the same set
444481
// of relevant tags, reuse the result.

0 commit comments

Comments
 (0)