Commit b2a1fb74 authored by Robert Griesemer's avatar Robert Griesemer

cmd/api: update api checker to use go/types from std repo

The old code checked out a specific version of go/types from the
x/tools repo. With go/types being part of the std repo, this is
not necessary anymore.

Also, for the same reason, the api tool is now built like any
other regular command. There's no need to build it for each run.
Removed the respective +build tags.

Change-Id: I5088e4867223d676957084c24651ec05452ac495
Reviewed-on: https://go-review.googlesource.com/8564Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 5a97747c
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build api_tool
// Binary api computes the exported API of a set of Go packages. // Binary api computes the exported API of a set of Go packages.
package main package main
...@@ -16,6 +14,7 @@ import ( ...@@ -16,6 +14,7 @@ import (
"go/build" "go/build"
"go/parser" "go/parser"
"go/token" "go/token"
"go/types"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
...@@ -26,8 +25,6 @@ import ( ...@@ -26,8 +25,6 @@ import (
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
"code.google.com/p/go.tools/go/types"
) )
// Flags // Flags
...@@ -608,12 +605,14 @@ func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { ...@@ -608,12 +605,14 @@ func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
case *types.Chan: case *types.Chan:
var s string var s string
switch typ.Dir() { switch typ.Dir() {
case ast.SEND: case types.SendOnly:
s = "chan<- " s = "chan<- "
case ast.RECV: case types.RecvOnly:
s = "<-chan " s = "<-chan "
default: case types.SendRecv:
s = "chan " s = "chan "
default:
panic("unreachable")
} }
buf.WriteString(s) buf.WriteString(s)
w.writeType(buf, typ.Elem()) w.writeType(buf, typ.Elem())
...@@ -633,7 +632,7 @@ func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { ...@@ -633,7 +632,7 @@ func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
} }
func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) { func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
w.writeParams(buf, sig.Params(), sig.IsVariadic()) w.writeParams(buf, sig.Params(), sig.Variadic())
switch res := sig.Results(); res.Len() { switch res := sig.Results(); res.Len() {
case 0: case 0:
// nothing to do // nothing to do
...@@ -705,10 +704,10 @@ func (w *Walker) emitType(obj *types.TypeName) { ...@@ -705,10 +704,10 @@ func (w *Walker) emitType(obj *types.TypeName) {
// emit methods with value receiver // emit methods with value receiver
var methodNames map[string]bool var methodNames map[string]bool
vset := typ.MethodSet() vset := types.NewMethodSet(typ)
for i, n := 0, vset.Len(); i < n; i++ { for i, n := 0, vset.Len(); i < n; i++ {
m := vset.At(i) m := vset.At(i)
if m.Obj().IsExported() { if m.Obj().Exported() {
w.emitMethod(m) w.emitMethod(m)
if methodNames == nil { if methodNames == nil {
methodNames = make(map[string]bool) methodNames = make(map[string]bool)
...@@ -720,10 +719,10 @@ func (w *Walker) emitType(obj *types.TypeName) { ...@@ -720,10 +719,10 @@ func (w *Walker) emitType(obj *types.TypeName) {
// emit methods with pointer receiver; exclude // emit methods with pointer receiver; exclude
// methods that we have emitted already // methods that we have emitted already
// (the method set of *T includes the methods of T) // (the method set of *T includes the methods of T)
pset := types.NewPointer(typ).MethodSet() pset := types.NewMethodSet(types.NewPointer(typ))
for i, n := 0, pset.Len(); i < n; i++ { for i, n := 0, pset.Len(); i < n; i++ {
m := pset.At(i) m := pset.At(i)
if m.Obj().IsExported() && !methodNames[m.Obj().Name()] { if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
w.emitMethod(m) w.emitMethod(m)
} }
} }
...@@ -736,7 +735,7 @@ func (w *Walker) emitStructType(name string, typ *types.Struct) { ...@@ -736,7 +735,7 @@ func (w *Walker) emitStructType(name string, typ *types.Struct) {
for i := 0; i < typ.NumFields(); i++ { for i := 0; i < typ.NumFields(); i++ {
f := typ.Field(i) f := typ.Field(i)
if !f.IsExported() { if !f.Exported() {
continue continue
} }
typ := f.Type() typ := f.Type()
...@@ -753,10 +752,10 @@ func (w *Walker) emitIfaceType(name string, typ *types.Interface) { ...@@ -753,10 +752,10 @@ func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
var methodNames []string var methodNames []string
complete := true complete := true
mset := typ.MethodSet() mset := types.NewMethodSet(typ)
for i, n := 0, mset.Len(); i < n; i++ { for i, n := 0, mset.Len(); i < n; i++ {
m := mset.At(i).Obj().(*types.Func) m := mset.At(i).Obj().(*types.Func)
if !m.IsExported() { if !m.Exported() {
complete = false complete = false
continue continue
} }
...@@ -807,7 +806,7 @@ func (w *Walker) emitMethod(m *types.Selection) { ...@@ -807,7 +806,7 @@ func (w *Walker) emitMethod(m *types.Selection) {
if p, _ := recv.(*types.Pointer); p != nil { if p, _ := recv.(*types.Pointer); p != nil {
base = p.Elem() base = p.Elem()
} }
if obj := base.(*types.Named).Obj(); !obj.IsExported() { if obj := base.(*types.Named).Obj(); !obj.Exported() {
log.Fatalf("exported method with unexported receiver base type: %s", m) log.Fatalf("exported method with unexported receiver base type: %s", m)
} }
} }
......
// +build api_tool
// Copyright 2011 The Go Authors. All rights reserved. // Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
...@@ -15,6 +13,7 @@ import ( ...@@ -15,6 +13,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"strings" "strings"
"testing" "testing"
...@@ -25,6 +24,13 @@ var ( ...@@ -25,6 +24,13 @@ var (
) )
func TestGolden(t *testing.T) { func TestGolden(t *testing.T) {
// test fails on NaCl - skip for now
// (goapi_test.go:35: open testdata/src/pkg: No such file or directory)
// TODO(gri) fix this ASAP
if runtime.GOOS == "nacl" {
return
}
td, err := os.Open("testdata/src/pkg") td, err := os.Open("testdata/src/pkg")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -38,7 +44,8 @@ func TestGolden(t *testing.T) { ...@@ -38,7 +44,8 @@ func TestGolden(t *testing.T) {
continue continue
} }
goldenFile := filepath.Join("testdata", "src", fi.Name(), "golden.txt") // TODO(gri) remove extra pkg directory eventually
goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt")
w := NewWalker(nil, "testdata/src/pkg") w := NewWalker(nil, "testdata/src/pkg")
w.export(w.Import(fi.Name())) w.export(w.Import(fi.Name()))
......
...@@ -4,32 +4,18 @@ ...@@ -4,32 +4,18 @@
// +build ignore // +build ignore
// The run program is invoked via "go run" from src/run.bash or // The run program is invoked via the dist tool.
// src/run.bat conditionally builds and runs the cmd/api tool. // To invoke manually: go tool dist test -run api --no-rebuild
//
// TODO(bradfitz): the "conditional" condition is always true.
// We should only do this if the user has the hg codereview extension
// enabled and verifies that the go.tools subrepo is checked out with
// a suitably recently version. In prep for the cmd/api rewrite.
package main package main
import ( import (
"fmt" "fmt"
"log" "log"
"net/http"
"os" "os"
"os/exec" "os/exec"
"os/user"
"path/filepath" "path/filepath"
"runtime"
"strings"
) )
// goToolsVersion is the git revision of the x/tools subrepo we need
// to build cmd/api. This only needs to be updated whenever a go/types
// bug fix is needed by the cmd/api tool.
const goToolsVersion = "875ff2496f865e" // aka hg 6698ca2900e2
var goroot string var goroot string
func main() { func main() {
...@@ -38,22 +24,8 @@ func main() { ...@@ -38,22 +24,8 @@ func main() {
if goroot == "" { if goroot == "" {
log.Fatal("No $GOROOT set.") log.Fatal("No $GOROOT set.")
} }
_, err := exec.LookPath("git")
if err != nil {
fmt.Println("Skipping cmd/api checks; git not available")
return
}
gopath := prepGoPath()
cmd := exec.Command("go", "install", "--tags=api_tool", "cmd/api")
cmd.Env = append(filterOut(os.Environ(), "GOARCH", "GOPATH"), "GOPATH="+gopath)
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("Error installing cmd/api: %v\n%s", err, out)
}
out, err = exec.Command("go", "tool", "api", out, err := exec.Command("go", "tool", "api",
"-c", file("go1", "go1.1", "go1.2", "go1.3", "go1.4"), "-c", file("go1", "go1.1", "go1.2", "go1.3", "go1.4"),
"-next", file("next"), "-next", file("next"),
"-except", file("except")).CombinedOutput() "-except", file("except")).CombinedOutput()
...@@ -63,22 +35,6 @@ func main() { ...@@ -63,22 +35,6 @@ func main() {
fmt.Print(string(out)) fmt.Print(string(out))
} }
// filterOut returns a copy of the src environment without environment
// variables from remove.
// TODO: delete when issue 6201 is fixed.
func filterOut(src []string, remove ...string) (out []string) {
S:
for _, s := range src {
for _, r := range remove {
if strings.HasPrefix(s, r) && strings.HasPrefix(s, r+"=") {
continue S
}
}
out = append(out, s)
}
return
}
// file expands s to $GOROOT/api/s.txt. // file expands s to $GOROOT/api/s.txt.
// If there are more than 1, they're comma-separated. // If there are more than 1, they're comma-separated.
func file(s ...string) string { func file(s ...string) string {
...@@ -87,119 +43,3 @@ func file(s ...string) string { ...@@ -87,119 +43,3 @@ func file(s ...string) string {
} }
return filepath.Join(goroot, "api", s[0]+".txt") return filepath.Join(goroot, "api", s[0]+".txt")
} }
// prepGoPath returns a GOPATH for the "go" tool to compile the API tool with.
// It tries to re-use a go.tools checkout from a previous run if possible,
// else it hg clones it.
func prepGoPath() string {
// Use a builder-specific temp directory name, so builders running
// two copies don't trample on each other: https://golang.org/issue/9407
// We don't use io.TempDir or a PID or timestamp here because we do
// want this to be stable between runs, to minimize "git clone" calls
// in the common case.
var tempBase = fmt.Sprintf("go.tools.TMP.%s.%s", runtime.GOOS, runtime.GOARCH)
username := ""
u, err := user.Current()
if err == nil {
username = u.Username
} else {
username = os.Getenv("USER")
if username == "" {
username = "nobody"
}
}
// The GOPATH we'll return
gopath := filepath.Join(os.TempDir(), "gopath-api-"+cleanUsername(username)+"-"+cleanUsername(strings.Fields(runtime.Version())[0]), goToolsVersion)
// cloneDir is where we run "git clone".
cloneDir := filepath.Join(gopath, "src", "code.google.com", "p")
// The dir we clone into. We only atomically rename it to finalDir on
// clone success.
tmpDir := filepath.Join(cloneDir, tempBase)
// finalDir is where the checkout will live once it's complete.
finalDir := filepath.Join(cloneDir, "go.tools")
if goToolsCheckoutGood(finalDir) {
return gopath
}
os.RemoveAll(finalDir) // in case it's there but corrupt
os.RemoveAll(tmpDir) // in case of aborted hg clone before
if err := os.MkdirAll(cloneDir, 0700); err != nil {
log.Fatal(err)
}
cmd := exec.Command("git", "clone", "https://go.googlesource.com/tools", tempBase)
cmd.Dir = cloneDir
out, err := cmd.CombinedOutput()
if err != nil {
if _, err := http.Head("http://ip.appspot.com/"); err != nil {
log.Printf("# Skipping API check; network appears to be unavailable")
os.Exit(0)
}
log.Fatalf("Error running git clone on x/tools: %v\n%s", err, out)
}
cmd = exec.Command("git", "reset", "--hard", goToolsVersion)
cmd.Dir = tmpDir
out, err = cmd.CombinedOutput()
if err != nil {
log.Fatalf("Error updating x/tools in %v to %v: %v, %s", tmpDir, goToolsVersion, err, out)
}
if err := os.Rename(tmpDir, finalDir); err != nil {
if os.IsExist(err) {
// A different builder beat us into putting this repo into
// its final place. But that's fine; if it's there, it's
// the right version and we can use it.
//
// This happens on the Go project's Windows builders
// especially, where we have two builders (386 and amd64)
// running at the same time, trying to compete for moving
// it into place.
os.RemoveAll(tmpDir)
} else {
log.Fatal(err)
}
}
return gopath
}
func cleanUsername(n string) string {
b := make([]rune, len(n))
for i, r := range n {
if r == '\\' || r == '/' || r == ':' {
b[i] = '_'
} else {
b[i] = r
}
}
return string(b)
}
func goToolsCheckoutGood(dir string) bool {
if _, err := os.Stat(dir); err != nil {
return false
}
cmd := exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
return false
}
id := strings.TrimSpace(string(out))
if !strings.HasPrefix(id, goToolsVersion) {
return false
}
cmd = exec.Command("git", "status", "--porcelain")
cmd.Dir = dir
out, err = cmd.Output()
if err != nil || strings.TrimSpace(string(out)) != "" {
return false
}
return true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment