x/exp/apidiff: copy changes from x/tools/internal/apidiff

We copied golang.org/x/exp/apidiff to x/tools a few months ago in
anticipation of developing gorelease in x/tools, which would depend on
apidiff.

We've decided to develop gorelease here in x/exp instead, which means
the copy in x/tools is no longer needed. This CL copies changes made
to the copy in x/tools since it was made. Another CL will delete the
copy in x/tools.

Change-Id: Ied79138616c2f3b2f49a0ee5ca95ff3179351354
Reviewed-on: https://go-review.googlesource.com/c/exp/+/197298
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/apidiff/README.md b/apidiff/README.md
index 04a1f4e..3d9576c 100644
--- a/apidiff/README.md
+++ b/apidiff/README.md
@@ -265,7 +265,7 @@
 var y int64 = x // fails with new: different types in assignment
 ```
 
-A change to the value of a constant can break compatiblity if the value is used
+A change to the value of a constant can break compatibility if the value is used
 in an array type:
 
 ```
diff --git a/apidiff/apidiff.go b/apidiff/apidiff.go
index dc0f0e7..76669d8 100644
--- a/apidiff/apidiff.go
+++ b/apidiff/apidiff.go
@@ -24,10 +24,14 @@
 func Changes(old, new *types.Package) Report {
 	d := newDiffer(old, new)
 	d.checkPackage()
-	return Report{
-		Incompatible: d.incompatibles.collect(),
-		Compatible:   d.compatibles.collect(),
+	r := Report{}
+	for _, m := range d.incompatibles.collect() {
+		r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
 	}
+	for _, m := range d.compatibles.collect() {
+		r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
+	}
+	return r
 }
 
 type differ struct {
diff --git a/apidiff/apidiff_test.go b/apidiff/apidiff_test.go
index 4dcb4d8..5f23542 100644
--- a/apidiff/apidiff_test.go
+++ b/apidiff/apidiff_test.go
@@ -6,8 +6,10 @@
 	"go/types"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"reflect"
+	"runtime"
 	"sort"
 	"strings"
 	"testing"
@@ -26,22 +28,24 @@
 	sort.Strings(wanti)
 	sort.Strings(wantc)
 
-	oldpkg, err := load("apidiff/old", dir)
+	oldpkg, err := load(t, "apidiff/old", dir)
 	if err != nil {
 		t.Fatal(err)
 	}
-	newpkg, err := load("apidiff/new", dir)
+	newpkg, err := load(t, "apidiff/new", dir)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	report := Changes(oldpkg.Types, newpkg.Types)
 
-	if !reflect.DeepEqual(report.Incompatible, wanti) {
-		t.Errorf("incompatibles: got %v\nwant %v\n", report.Incompatible, wanti)
+	got := report.messages(false)
+	if !reflect.DeepEqual(got, wanti) {
+		t.Errorf("incompatibles: got %v\nwant %v\n", got, wanti)
 	}
-	if !reflect.DeepEqual(report.Compatible, wantc) {
-		t.Errorf("compatibles: got %v\nwant %v\n", report.Compatible, wantc)
+	got = report.messages(true)
+	if !reflect.DeepEqual(got, wantc) {
+		t.Errorf("compatibles: got %v\nwant %v\n", got, wantc)
 	}
 }
 
@@ -113,7 +117,9 @@
 	return
 }
 
-func load(importPath, goPath string) (*packages.Package, error) {
+func load(t *testing.T, importPath, goPath string) (*packages.Package, error) {
+	needsGoPackages(t)
+
 	cfg := &packages.Config{
 		Mode: packages.LoadTypes,
 	}
@@ -132,7 +138,7 @@
 }
 
 func TestExportedFields(t *testing.T) {
-	pkg, err := load("golang.org/x/exp/apidiff/testdata/exported_fields", "")
+	pkg, err := load(t, "golang.org/x/exp/apidiff/testdata/exported_fields", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -164,3 +170,69 @@
 		}
 	}
 }
+
+// needsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
+// the current process environment is not present in the path.
+//
+// Copied and adapted from golang.org/x/tools/internal/testenv.
+func needsGoPackages(t *testing.T) {
+	t.Helper()
+
+	tool := os.Getenv("GOPACKAGESDRIVER")
+	switch tool {
+	case "off":
+		// "off" forces go/packages to use the go command.
+		tool = "go"
+	case "":
+		if _, err := exec.LookPath("gopackagesdriver"); err == nil {
+			tool = "gopackagesdriver"
+		} else {
+			tool = "go"
+		}
+	}
+
+	needsTool(t, tool)
+}
+
+// needsTool skips t if the named tool is not present in the path.
+//
+// Copied and adapted from golang.org/x/tools/internal/testenv.
+func needsTool(t *testing.T, tool string) {
+	_, err := exec.LookPath(tool)
+	if err == nil {
+		return
+	}
+
+	t.Helper()
+	if allowMissingTool(tool) {
+		t.Skipf("skipping because %s tool not available: %v", tool, err)
+	} else {
+		t.Fatalf("%s tool not available: %v", tool, err)
+	}
+}
+
+func allowMissingTool(tool string) bool {
+	if runtime.GOOS == "android" {
+		// Android builds generally run tests on a separate machine from the build,
+		// so don't expect any external tools to be available.
+		return true
+	}
+
+	if tool == "go" && os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
+		// Work around a misconfigured builder (see https://golang.org/issue/33950).
+		return true
+	}
+
+	// If a developer is actively working on this test, we expect them to have all
+	// of its dependencies installed. However, if it's just a dependency of some
+	// other module (for example, being run via 'go test all'), we should be more
+	// tolerant of unusual environments.
+	return !packageMainIsDevel()
+}
+
+// packageMainIsDevel reports whether the module containing package main
+// is a development version (if module information is available).
+//
+// Builds in GOPATH mode and builds that lack module information are assumed to
+// be development versions.
+var packageMainIsDevel = func() bool { return true }
diff --git a/apidiff/report.go b/apidiff/report.go
index fd346b1..ce79e27 100644
--- a/apidiff/report.go
+++ b/apidiff/report.go
@@ -8,7 +8,23 @@
 
 // Report describes the changes detected by Changes.
 type Report struct {
-	Incompatible, Compatible []string
+	Changes []Change
+}
+
+// A Change describes a single API change.
+type Change struct {
+	Message    string
+	Compatible bool
+}
+
+func (r Report) messages(compatible bool) []string {
+	var msgs []string
+	for _, c := range r.Changes {
+		if c.Compatible == compatible {
+			msgs = append(msgs, c.Message)
+		}
+	}
+	return msgs
 }
 
 func (r Report) String() string {
@@ -28,13 +44,13 @@
 
 func (r Report) TextIncompatible(w io.Writer, withHeader bool) error {
 	if withHeader {
-		return r.writeMessages(w, "Incompatible changes:", r.Incompatible)
+		return r.writeMessages(w, "Incompatible changes:", r.messages(false))
 	}
-	return r.writeMessages(w, "", r.Incompatible)
+	return r.writeMessages(w, "", r.messages(false))
 }
 
 func (r Report) TextCompatible(w io.Writer) error {
-	return r.writeMessages(w, "Compatible changes:", r.Compatible)
+	return r.writeMessages(w, "Compatible changes:", r.messages(true))
 }
 
 func (r Report) writeMessages(w io.Writer, header string, msgs []string) error {
diff --git a/go.mod b/go.mod
index 06739c5..9edc3b5 100644
--- a/go.mod
+++ b/go.mod
@@ -10,5 +10,5 @@
 	golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
 	golang.org/x/mod v0.1.0
 	golang.org/x/sys v0.0.0-20190412213103-97732733099d
-	golang.org/x/tools v0.0.0-20190816200558-6889da9d5479
+	golang.org/x/tools v0.0.0-20190925164712-ae58c0ff6b32
 )
diff --git a/go.sum b/go.sum
index 2a7c68a..6ce4043 100644
--- a/go.sum
+++ b/go.sum
@@ -23,6 +23,6 @@
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479 h1:lfN2PY/jymfnxkNHlbBF5DwPsUvhqUnrdgfK01iH2s0=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190925164712-ae58c0ff6b32 h1:xE6VFETO5vvJp3W3iihukTHFkQiu9yrTMJNKu20CwE4=
+golang.org/x/tools v0.0.0-20190925164712-ae58c0ff6b32/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=