go/build: rewrite TestDependencies to be cleaner, more correct

TestDependencies defines the dependency policy
(what can depend on what) for the standard library.

The standard library has outgrown the idea of writing
the policy as a plain map literal. Also, the checker was
ignoring vendored packages, which makes it miss real
problems.

This commit adds a little language for describing
partial orders and rewrites the policy in that language.

It also changes the checker to look inside vendored
packages and adds those to the policy as well.

This turned up one important problem: net is depending
on fmt, unicode via golang.org/x/net/dns/dnsmessage,
filed as #40070.

This is a test-only change, so it should be appropriate
even for the release freeze, especially since it identified
a real bug.

Change-Id: I9b79f30761f167b8587204c959baa973583e39f2
Reviewed-on: https://go-review.googlesource.com/c/go/+/241078
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index d3bbf08..bd0ebce 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -20,475 +20,470 @@
 	"testing"
 )
 
-// pkgDeps defines the expected dependencies between packages in
+// depsRules defines the expected dependencies between packages in
 // the Go source tree. It is a statement of policy.
-// Changes should not be made to this map without prior discussion.
-//
-// The map contains two kinds of entries:
-// 1) Lower-case keys are standard import paths and list the
-// allowed imports in that package.
-// 2) Upper-case keys define aliases for package sets, which can then
-// be used as dependencies by other rules.
 //
 // DO NOT CHANGE THIS DATA TO FIX BUILDS.
+// Existing packages should not have their constraints relaxed
+// without prior discussion.
+// Negative assertions should almost never be removed.
 //
-var pkgDeps = map[string][]string{
-	// L0 is the lowest level, core, nearly unavoidable packages.
-	"errors":                  {"runtime", "internal/reflectlite"},
-	"io":                      {"errors", "sync", "sync/atomic"},
-	"runtime":                 {"unsafe", "runtime/internal/atomic", "runtime/internal/sys", "runtime/internal/math", "internal/cpu", "internal/bytealg"},
-	"runtime/internal/sys":    {},
-	"runtime/internal/atomic": {"unsafe", "internal/cpu"},
-	"runtime/internal/math":   {"runtime/internal/sys"},
-	"internal/race":           {"runtime", "unsafe"},
-	"sync":                    {"internal/race", "runtime", "sync/atomic", "unsafe"},
-	"sync/atomic":             {"unsafe"},
-	"unsafe":                  {},
-	"internal/cpu":            {},
-	"internal/bytealg":        {"unsafe", "internal/cpu"},
-	"internal/reflectlite":    {"runtime", "unsafe", "internal/unsafeheader"},
-	"internal/unsafeheader":   {"unsafe"},
+// The general syntax of a rule is:
+//
+//		a, b < c, d;
+//
+// which means c and d come after a and b in the partial order
+// (that is, c and d can import a and b),
+// but doesn't provide a relative order between a vs b or c vs d.
+//
+// The rules can chain together, as in:
+//
+//		e < f, g < h;
+//
+// which is equivalent to
+//
+//		e < f, g;
+//		f, g < h;
+//
+// Except for the special bottom element "NONE", each name
+// must appear exactly once on the right-hand side of a rule.
+// That rule serves as the definition of the allowed dependencies
+// for that name. The definition must appear before any uses
+// of the name on the left-hand side of a rule. (That is, the
+// rules themselves must be ordered according to the partial
+// order, for easier reading by people.)
+//
+// Negative assertions double-check the partial order:
+//
+//		i !< j
+//
+// means that it must NOT be the case that i < j.
+// Negative assertions may appear anywhere in the rules,
+// even before i and j have been defined.
+//
+// Comments begin with #.
+//
+// All-caps names are pseudo-names for specific points
+// in the dependency lattice.
+//
+var depsRules = `
+	# No dependencies allowed for any of these packages.
+	NONE
+	< container/list, container/ring,
+	  internal/cfg, internal/cpu,
+	  internal/goversion, internal/nettrace,
+	  unicode/utf8, unicode/utf16, unicode,
+	  unsafe;
 
-	"L0": {
-		"errors",
-		"io",
-		"runtime",
-		"runtime/internal/atomic",
-		"sync",
-		"sync/atomic",
-		"unsafe",
-		"internal/cpu",
-		"internal/bytealg",
-		"internal/reflectlite",
-	},
+	# RUNTIME is the core runtime group of packages, all of them very light-weight.
+	internal/cpu, unsafe
+	< internal/bytealg
+	< internal/unsafeheader
+	< runtime/internal/sys
+	< runtime/internal/atomic
+	< runtime/internal/math
+	< runtime
+	< sync/atomic
+	< internal/race
+	< sync
+	< internal/reflectlite
+	< errors
+	< internal/oserror, math/bits
+	< RUNTIME;
 
-	// L1 adds simple functions and strings processing,
-	// but not Unicode tables.
-	"math":          {"internal/cpu", "unsafe", "math/bits"},
-	"math/bits":     {"unsafe"},
-	"math/cmplx":    {"math", "math/bits"},
-	"math/rand":     {"L0", "math"},
-	"strconv":       {"L0", "unicode/utf8", "math", "math/bits"},
-	"unicode/utf16": {},
-	"unicode/utf8":  {},
+	RUNTIME
+	< sort
+	< container/heap;
 
-	"L1": {
-		"L0",
-		"math",
-		"math/bits",
-		"math/cmplx",
-		"math/rand",
-		"sort",
-		"strconv",
-		"unicode/utf16",
-		"unicode/utf8",
-	},
+	RUNTIME
+	< io;
 
-	// L2 adds Unicode and strings processing.
-	"bufio":   {"L0", "unicode/utf8", "bytes", "strings"},
-	"bytes":   {"L0", "unicode", "unicode/utf8"},
-	"path":    {"L0", "unicode/utf8", "strings"},
-	"strings": {"L0", "unicode", "unicode/utf8"},
-	"unicode": {},
+	reflect !< sort;
 
-	"L2": {
-		"L1",
-		"bufio",
-		"bytes",
-		"path",
-		"strings",
-		"unicode",
-	},
+	# SYSCALL is RUNTIME plus the packages necessary for basic system calls.
+	RUNTIME, unicode/utf8, unicode/utf16, io
+	< internal/syscall/windows/sysdll, syscall/js
+	< syscall
+	< internal/syscall/unix, internal/syscall/windows, internal/syscall/windows/registry
+	< internal/syscall/execenv
+	< SYSCALL;
 
-	// L3 adds reflection and some basic utility packages
-	// and interface definitions, but nothing that makes
-	// system calls.
-	"crypto":                 {"L2", "hash"}, // interfaces
-	"crypto/cipher":          {"L2", "crypto/subtle", "crypto/internal/subtle", "encoding/binary"},
-	"crypto/internal/subtle": {"unsafe", "reflect"}, // reflect behind a appengine tag
-	"crypto/subtle":          {},
-	"encoding/base32":        {"L2"},
-	"encoding/base64":        {"L2", "encoding/binary"},
-	"encoding/binary":        {"L2", "reflect"},
-	"hash":                   {"L2"}, // interfaces
-	"hash/adler32":           {"L2", "hash"},
-	"hash/crc32":             {"L2", "hash"},
-	"hash/crc64":             {"L2", "hash"},
-	"hash/fnv":               {"L2", "hash"},
-	"hash/maphash":           {"L2", "hash"},
-	"image":                  {"L2", "image/color"}, // interfaces
-	"image/color":            {"L2"},                // interfaces
-	"image/color/palette":    {"L2", "image/color"},
-	"internal/fmtsort":       {"reflect", "sort"},
-	"reflect":                {"L2", "internal/unsafeheader"},
-	"sort":                   {"internal/reflectlite"},
+	# TIME is SYSCALL plus the core packages about time, including context.
+	SYSCALL
+	< time/tzdata
+	< time
+	< context
+	< TIME;
 
-	"L3": {
-		"L2",
-		"crypto",
-		"crypto/cipher",
-		"crypto/internal/subtle",
-		"crypto/subtle",
-		"encoding/base32",
-		"encoding/base64",
-		"encoding/binary",
-		"hash",
-		"hash/adler32",
-		"hash/crc32",
-		"hash/crc64",
-		"hash/fnv",
-		"image",
-		"image/color",
-		"image/color/palette",
-		"internal/fmtsort",
-		"internal/oserror",
-		"reflect",
-	},
+	# MATH is RUNTIME plus the basic math packages.
+	RUNTIME
+	< math
+	< MATH;
 
-	// End of linear dependency definitions.
+	unicode !< math;
 
-	// Operating system access.
-	"syscall":                           {"L0", "internal/oserror", "internal/race", "internal/syscall/windows/sysdll", "internal/unsafeheader", "syscall/js", "unicode/utf16"},
-	"syscall/js":                        {"L0"},
-	"internal/oserror":                  {"L0"},
-	"internal/syscall/unix":             {"L0", "syscall"},
-	"internal/syscall/windows":          {"L0", "syscall", "internal/syscall/windows/sysdll", "internal/unsafeheader", "unicode/utf16"},
-	"internal/syscall/windows/registry": {"L0", "syscall", "internal/syscall/windows/sysdll", "unicode/utf16"},
-	"internal/syscall/execenv":          {"L0", "syscall", "internal/syscall/windows", "unicode/utf16"},
-	"time": {
-		// "L0" without the "io" package:
-		"errors",
-		"runtime",
-		"runtime/internal/atomic",
-		"sync",
-		"sync/atomic",
-		"unsafe",
-		// Other time dependencies:
-		"internal/syscall/windows/registry",
-		"syscall",
-		"syscall/js",
-		"time/tzdata",
-	},
-	"time/tzdata": {"L0", "syscall"},
+	MATH
+	< math/cmplx;
 
-	"internal/cfg":     {"L0"},
-	"internal/poll":    {"L0", "internal/oserror", "internal/race", "syscall", "time", "unicode/utf16", "unicode/utf8", "internal/syscall/windows", "internal/syscall/unix"},
-	"internal/testlog": {"L0"},
-	"os":               {"L1", "os", "syscall", "time", "internal/oserror", "internal/poll", "internal/syscall/windows", "internal/syscall/unix", "internal/syscall/execenv", "internal/testlog"},
-	"path/filepath":    {"L2", "os", "syscall", "internal/syscall/windows"},
-	"io/ioutil":        {"L2", "os", "path/filepath", "time"},
-	"os/exec":          {"L2", "os", "context", "path/filepath", "syscall", "internal/syscall/execenv"},
-	"os/signal":        {"L2", "os", "syscall"},
+	MATH
+	< math/rand;
 
-	// OS enables basic operating system functionality,
-	// but not direct use of package syscall, nor os/signal.
-	"OS": {
-		"io/ioutil",
-		"os",
-		"os/exec",
-		"path/filepath",
-		"time",
-	},
+	MATH, unicode/utf8
+	< strconv;
 
-	// Formatted I/O: few dependencies (L1) but we must add reflect and internal/fmtsort.
-	"fmt": {"L1", "os", "reflect", "internal/fmtsort"},
-	"log": {"L1", "os", "fmt", "time"},
+	unicode !< strconv;
 
-	// Packages used by testing must be low-level (L2+fmt).
-	"regexp":         {"L2", "regexp/syntax"},
-	"regexp/syntax":  {"L2"},
-	"runtime/debug":  {"L2", "fmt", "io/ioutil", "os", "time"},
-	"runtime/pprof":  {"L2", "compress/gzip", "context", "encoding/binary", "fmt", "io/ioutil", "os", "syscall", "text/tabwriter", "time"},
-	"runtime/trace":  {"L0", "context", "fmt"},
-	"text/tabwriter": {"L2"},
+	# STR is basic string and buffer manipulation.
+	RUNTIME, io, unicode/utf8, unicode/utf16, unicode
+	< bytes, strings
+	< bufio, path;
 
-	"testing":                  {"L2", "flag", "fmt", "internal/race", "io/ioutil", "os", "runtime/debug", "runtime/pprof", "runtime/trace", "time"},
-	"testing/iotest":           {"L2", "log"},
-	"testing/quick":            {"L2", "flag", "fmt", "reflect", "time"},
-	"internal/obscuretestdata": {"L2", "OS", "encoding/base64"},
-	"internal/testenv":         {"L2", "OS", "flag", "testing", "syscall", "internal/cfg"},
-	"internal/lazyregexp":      {"L2", "OS", "regexp"},
-	"internal/lazytemplate":    {"L2", "OS", "text/template"},
+	bufio, path, strconv
+	< STR;
 
-	// L4 is defined as L3+fmt+log+time, because in general once
-	// you're using L3 packages, use of fmt, log, or time is not a big deal.
-	"L4": {
-		"L3",
-		"fmt",
-		"log",
-		"time",
-	},
+	# OS is basic OS access, including helpers (path/filepath, os/exec, etc).
+	# OS includes string routines, but those must be layered above package os.
+	# OS does not include reflection.
+	TIME, io, sort
+	< internal/testlog
+	< internal/poll
+	< os
+	< os/signal;
 
-	// Go parser.
-	"go/ast":     {"L4", "OS", "go/scanner", "go/token"},
-	"go/doc":     {"L4", "OS", "go/ast", "go/token", "regexp", "internal/lazyregexp", "text/template"},
-	"go/parser":  {"L4", "OS", "go/ast", "go/scanner", "go/token"},
-	"go/printer": {"L4", "OS", "go/ast", "go/scanner", "go/token", "text/tabwriter"},
-	"go/scanner": {"L4", "OS", "go/token"},
-	"go/token":   {"L4"},
+	unicode, fmt !< os, os/signal;
 
-	"GOPARSER": {
-		"go/ast",
-		"go/doc",
-		"go/parser",
-		"go/printer",
-		"go/scanner",
-		"go/token",
-	},
+	os/signal, STR
+	< path/filepath
+	< io/ioutil, os/exec
+	< OS;
 
-	"go/format":       {"L4", "GOPARSER", "internal/format"},
-	"internal/format": {"L4", "GOPARSER"},
+	reflect !< OS;
 
-	// Go type checking.
-	"go/constant":               {"L4", "go/token", "math/big"},
-	"go/importer":               {"L4", "go/build", "go/internal/gccgoimporter", "go/internal/gcimporter", "go/internal/srcimporter", "go/token", "go/types"},
-	"go/internal/gcimporter":    {"L4", "OS", "go/build", "go/constant", "go/token", "go/types", "text/scanner"},
-	"go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "internal/xcoff", "text/scanner"},
-	"go/internal/srcimporter":   {"L4", "OS", "fmt", "go/ast", "go/build", "go/parser", "go/token", "go/types", "path/filepath"},
-	"go/types":                  {"L4", "GOPARSER", "container/heap", "go/constant"},
+	OS
+	< golang.org/x/sys/cpu, internal/goroot;
 
-	// One of a kind.
-	"archive/tar":               {"L4", "OS", "syscall", "os/user"},
-	"archive/zip":               {"L4", "OS", "compress/flate"},
-	"container/heap":            {"sort"},
-	"compress/bzip2":            {"L4"},
-	"compress/flate":            {"L4"},
-	"compress/gzip":             {"L4", "compress/flate"},
-	"compress/lzw":              {"L4"},
-	"compress/zlib":             {"L4", "compress/flate"},
-	"context":                   {"errors", "internal/reflectlite", "sync", "sync/atomic", "time"},
-	"database/sql":              {"L4", "container/list", "context", "database/sql/driver", "database/sql/internal"},
-	"database/sql/driver":       {"L4", "context", "time", "database/sql/internal"},
-	"debug/dwarf":               {"L4"},
-	"debug/elf":                 {"L4", "OS", "debug/dwarf", "compress/zlib"},
-	"debug/gosym":               {"L4"},
-	"debug/macho":               {"L4", "OS", "debug/dwarf", "compress/zlib"},
-	"debug/pe":                  {"L4", "OS", "debug/dwarf", "compress/zlib"},
-	"debug/plan9obj":            {"L4", "OS"},
-	"encoding":                  {"L4"},
-	"encoding/ascii85":          {"L4"},
-	"encoding/asn1":             {"L4", "math/big"},
-	"encoding/csv":              {"L4"},
-	"encoding/gob":              {"L4", "OS", "encoding"},
-	"encoding/hex":              {"L4"},
-	"encoding/json":             {"L4", "encoding"},
-	"encoding/pem":              {"L4"},
-	"encoding/xml":              {"L4", "encoding"},
-	"flag":                      {"L4", "OS"},
-	"go/build":                  {"L4", "OS", "GOPARSER", "internal/goroot", "internal/goversion"},
-	"html":                      {"L4"},
-	"image/draw":                {"L4", "image/internal/imageutil"},
-	"image/gif":                 {"L4", "compress/lzw", "image/color/palette", "image/draw"},
-	"image/internal/imageutil":  {"L4"},
-	"image/jpeg":                {"L4", "image/internal/imageutil"},
-	"image/png":                 {"L4", "compress/zlib"},
-	"index/suffixarray":         {"L4", "regexp"},
-	"internal/goroot":           {"L4", "OS"},
-	"internal/singleflight":     {"sync"},
-	"internal/trace":            {"L4", "OS", "container/heap"},
-	"internal/xcoff":            {"L4", "OS", "debug/dwarf"},
-	"math/big":                  {"L4"},
-	"mime":                      {"L4", "OS", "syscall", "internal/syscall/windows/registry"},
-	"mime/quotedprintable":      {"L4"},
-	"net/internal/socktest":     {"L4", "OS", "syscall", "internal/syscall/windows"},
-	"net/url":                   {"L4"},
-	"plugin":                    {"L0", "OS", "CGO"},
-	"internal/profile":          {"L4", "OS", "compress/gzip", "regexp"},
-	"testing/internal/testdeps": {"L4", "internal/testlog", "runtime/pprof", "regexp"},
-	"text/scanner":              {"L4", "OS"},
-	"text/template/parse":       {"L4"},
+	# FMT is OS (which includes string routines) plus reflect and fmt.
+	# It does not include package log, which should be avoided in core packages.
+	strconv, unicode
+	< reflect;
 
-	"html/template": {
-		"L4", "OS", "encoding/json", "html", "text/template",
-		"text/template/parse",
-	},
-	"text/template": {
-		"L4", "OS", "net/url", "text/template/parse",
-	},
+	os, reflect
+	< internal/fmtsort
+	< fmt;
 
-	// Cgo.
-	// If you add a dependency on CGO, you must add the package to
-	// cgoPackages in cmd/dist/test.go.
-	"runtime/cgo": {"L0", "C"},
-	"CGO":         {"C", "runtime/cgo"},
+	OS, fmt
+	< FMT;
 
-	// Fake entry to satisfy the pseudo-import "C"
-	// that shows up in programs that use cgo.
-	"C": {},
+	log !< FMT;
 
-	// Race detector/MSan uses cgo.
-	"runtime/race": {"C"},
-	"runtime/msan": {"C"},
+	# Misc packages needing only FMT.
+	FMT
+	< flag,
+	  html,
+	  mime/quotedprintable,
+	  net/internal/socktest,
+	  net/url,
+	  runtime/debug,
+	  runtime/trace,
+	  text/scanner,
+	  text/tabwriter;
 
-	// Plan 9 alone needs io/ioutil and os.
-	"os/user": {"L4", "CGO", "io/ioutil", "os", "syscall", "internal/syscall/windows", "internal/syscall/windows/registry"},
+	# encodings
+	# core ones do not use fmt.
+	io, strconv
+	< encoding;
 
-	// Internal package used only for testing.
-	"os/signal/internal/pty": {"CGO", "fmt", "os", "syscall"},
+	encoding, reflect
+	< encoding/binary
+	< encoding/base32, encoding/base64;
 
-	// Basic networking.
-	// Because net must be used by any package that wants to
-	// do networking portably, it must have a small dependency set: just L0+basic os.
-	"net": {
-		"L0", "CGO",
-		"context", "math/rand", "os", "sort", "syscall", "time",
-		"internal/nettrace", "internal/poll", "internal/syscall/unix",
-		"internal/syscall/windows", "internal/singleflight", "internal/race",
-		"golang.org/x/net/dns/dnsmessage", "golang.org/x/net/lif", "golang.org/x/net/route",
-	},
+	fmt !< encoding/base32, encoding/base64;
 
-	// NET enables use of basic network-related packages.
-	"NET": {
-		"net",
-		"mime",
-		"net/textproto",
-		"net/url",
-	},
+	FMT, encoding/base32, encoding/base64
+	< encoding/ascii85, encoding/csv, encoding/gob, encoding/hex,
+	  encoding/json, encoding/pem, encoding/xml, mime;
 
-	// Uses of networking.
-	"log/syslog":    {"L4", "OS", "net"},
-	"net/mail":      {"L4", "NET", "OS", "mime"},
-	"net/textproto": {"L4", "OS", "net"},
+	# hashes
+	io
+	< hash
+	< hash/adler32, hash/crc32, hash/crc64, hash/fnv, hash/maphash;
 
-	// Core crypto.
-	"crypto/aes":               {"L3"},
-	"crypto/des":               {"L3"},
-	"crypto/hmac":              {"L3"},
-	"crypto/internal/randutil": {"io", "sync"},
-	"crypto/md5":               {"L3"},
-	"crypto/rc4":               {"L3"},
-	"crypto/sha1":              {"L3"},
-	"crypto/sha256":            {"L3"},
-	"crypto/sha512":            {"L3"},
+	# math/big
+	FMT, encoding/binary, math/rand
+	< math/big;
 
-	"CRYPTO": {
-		"crypto/aes",
-		"crypto/des",
-		"crypto/hmac",
-		"crypto/internal/randutil",
-		"crypto/md5",
-		"crypto/rc4",
-		"crypto/sha1",
-		"crypto/sha256",
-		"crypto/sha512",
-		"golang.org/x/crypto/chacha20poly1305",
-		"golang.org/x/crypto/curve25519",
-		"golang.org/x/crypto/poly1305",
-	},
+	# compression
+	FMT, encoding/binary, hash/adler32, hash/crc32
+	< compress/bzip2, compress/flate, compress/lzw
+	< archive/zip, compress/gzip, compress/zlib;
 
-	// Random byte, number generation.
-	// This would be part of core crypto except that it imports
-	// math/big, which imports fmt.
-	"crypto/rand": {"L4", "CRYPTO", "OS", "math/big", "syscall", "syscall/js", "internal/syscall/unix"},
+	# templates
+	FMT
+	< text/template/parse;
 
-	// Not part of CRYPTO because it imports crypto/rand and crypto/sha512.
-	"crypto/ed25519":                       {"L3", "CRYPTO", "crypto/rand", "crypto/ed25519/internal/edwards25519"},
-	"crypto/ed25519/internal/edwards25519": {"encoding/binary"},
+	net/url, text/template/parse
+	< text/template
+	< internal/lazytemplate;
 
-	// Mathematical crypto: dependencies on fmt (L4) and math/big.
-	// We could avoid some of the fmt, but math/big imports fmt anyway.
-	"crypto/dsa": {"L4", "CRYPTO", "math/big"},
-	"crypto/ecdsa": {
-		"L4", "CRYPTO", "crypto/elliptic", "math/big",
-		"golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/cryptobyte/asn1",
-	},
-	"crypto/elliptic": {"L4", "CRYPTO", "math/big"},
-	"crypto/rsa":      {"L4", "CRYPTO", "crypto/rand", "math/big"},
+	encoding/json, html, text/template
+	< html/template;
 
-	"CRYPTO-MATH": {
-		"CRYPTO",
-		"crypto/dsa",
-		"crypto/ecdsa",
-		"crypto/elliptic",
-		"crypto/rand",
-		"crypto/rsa",
-		"encoding/asn1",
-		"math/big",
-	},
+	# regexp
+	FMT
+	< regexp/syntax
+	< regexp
+	< internal/lazyregexp;
 
-	// SSL/TLS.
-	"crypto/tls": {
-		"L4", "CRYPTO-MATH", "OS", "golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/hkdf",
-		"container/list", "context", "crypto/x509", "encoding/pem", "net", "syscall", "crypto/ed25519",
-	},
-	"crypto/x509": {
-		"L4", "CRYPTO-MATH", "OS", "CGO", "crypto/ed25519", "crypto/x509/internal/macOS",
-		"crypto/x509/pkix", "encoding/pem", "encoding/hex", "net", "os/user", "syscall", "net/url",
-		"golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/cryptobyte/asn1",
-	},
-	"crypto/x509/pkix":           {"L4", "CRYPTO-MATH", "encoding/hex"},
-	"crypto/x509/internal/macOS": {"L4"},
+	# suffix array
+	encoding/binary, regexp
+	< index/suffixarray;
 
-	// Simple net+crypto-aware packages.
-	"mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto", "mime/quotedprintable"},
-	"net/smtp":       {"L4", "CRYPTO", "NET", "crypto/tls"},
+	# executable parsing
+	FMT, encoding/binary, compress/zlib
+	< debug/dwarf
+	< debug/elf, debug/gosym, debug/macho, debug/pe, debug/plan9obj, internal/xcoff
+	< DEBUG;
 
-	// HTTP, kingpin of dependencies.
-	"net/http": {
-		"L4", "NET", "OS",
-		"compress/gzip",
-		"container/list",
-		"context",
-		"crypto/rand",
-		"crypto/tls",
-		"golang.org/x/net/http/httpguts",
-		"golang.org/x/net/http/httpproxy",
-		"golang.org/x/net/http2/hpack",
-		"golang.org/x/net/idna",
-		"golang.org/x/text/unicode/norm",
-		"golang.org/x/text/width",
-		"internal/nettrace",
-		"mime/multipart",
-		"net/http/httptrace",
-		"net/http/internal",
-		"runtime/debug",
-		"syscall/js",
-	},
-	"net/http/internal":  {"L4"},
-	"net/http/httptrace": {"context", "crypto/tls", "internal/nettrace", "net", "net/textproto", "reflect", "time"},
+	# go parser and friends.
+	FMT
+	< go/token
+	< go/scanner
+	< go/ast
+	< go/parser;
 
-	// HTTP-using packages.
-	"expvar":             {"L4", "OS", "encoding/json", "net/http"},
-	"net/http/cgi":       {"L4", "NET", "OS", "crypto/tls", "net/http", "regexp", "golang.org/x/net/http/httpguts"},
-	"net/http/cookiejar": {"L4", "NET", "net/http"},
-	"net/http/fcgi":      {"L4", "NET", "OS", "context", "net/http", "net/http/cgi"},
-	"net/http/httptest": {
-		"L4", "NET", "OS", "crypto/tls", "flag", "net/http", "net/http/internal", "crypto/x509",
-		"golang.org/x/net/http/httpguts",
-	},
-	"net/http/httputil": {"L4", "NET", "OS", "context", "net/http", "net/http/internal", "golang.org/x/net/http/httpguts"},
-	"net/http/pprof":    {"L4", "OS", "context", "html/template", "net/http", "runtime/pprof", "runtime/trace", "internal/profile"},
-	"net/rpc":           {"L4", "NET", "encoding/gob", "html/template", "net/http", "go/token"},
-	"net/rpc/jsonrpc":   {"L4", "NET", "encoding/json", "net/rpc"},
-}
+	go/parser, text/tabwriter
+	< go/printer
+	< go/format;
 
-// isMacro reports whether p is a package dependency macro
-// (uppercase name).
-func isMacro(p string) bool {
-	return 'A' <= p[0] && p[0] <= 'Z'
-}
+	go/parser, internal/lazyregexp, text/template
+	< go/doc;
 
-func allowed(pkg string) map[string]bool {
-	m := map[string]bool{}
-	var allow func(string)
-	allow = func(p string) {
-		if m[p] {
-			return
-		}
-		m[p] = true // set even for macros, to avoid loop on cycle
+	math/big, go/token
+	< go/constant;
 
-		// Upper-case names are macro-expanded.
-		if isMacro(p) {
-			for _, pp := range pkgDeps[p] {
-				allow(pp)
-			}
-		}
-	}
-	for _, pp := range pkgDeps[pkg] {
-		allow(pp)
-	}
-	return m
-}
+	container/heap, go/constant, go/parser
+	< go/types;
+
+	go/doc, go/parser, internal/goroot, internal/goversion
+	< go/build;
+
+	DEBUG, go/build, go/types, text/scanner
+	< go/internal/gcimporter, go/internal/gccgoimporter, go/internal/srcimporter
+	< go/importer;
+
+	# databases
+	FMT
+	< database/sql/internal
+	< database/sql/driver
+	< database/sql;
+
+	# images
+	FMT, compress/lzw, compress/zlib
+	< image/color
+	< image, image/color/palette
+	< image/internal/imageutil
+	< image/draw
+	< image/gif, image/jpeg, image/png;
+
+	# cgo, delayed as long as possible.
+	# If you add a dependency on CGO, you must add the package
+	# to cgoPackages in cmd/dist/test.go as well.
+	RUNTIME
+	< C
+	< runtime/cgo
+	< CGO
+	< runtime/race, runtime/msan;
+
+	# Bulk of the standard library must not use cgo.
+	# The prohibition stops at net and os/user.
+	C !< fmt, go/types, CRYPTO-MATH;
+
+	CGO, OS
+	< plugin;
+
+	CGO, FMT
+	< os/user
+	< archive/tar;
+
+	sync
+	< internal/singleflight;
+
+	os
+	< golang.org/x/net/dns/dnsmessage,
+	  golang.org/x/net/lif,
+	  golang.org/x/net/route;
+
+	# net is unavoidable when doing any networking,
+	# so large dependencies must be kept out.
+	# This is a long-looking list but most of these
+	# are small with few dependencies.
+	# math/rand should probably be removed at some point.
+	CGO,
+	golang.org/x/net/dns/dnsmessage,
+	golang.org/x/net/lif,
+	golang.org/x/net/route,
+	internal/nettrace,
+	internal/poll,
+	internal/singleflight,
+	internal/race,
+	math/rand,
+	os
+	< net;
+
+	fmt, unicode !< net;
+
+	# NET is net plus net-helper packages.
+	FMT, net
+	< net/textproto;
+
+	mime, net/textproto, net/url
+	< NET;
+
+	# logging - most packages should not import; http and up is allowed
+	FMT
+	< log;
+
+	log !< crypto/tls, database/sql, go/importer, testing;
+
+	FMT, log, net
+	< log/syslog;
+
+	NET, log
+	< net/mail;
+
+	# CRYPTO is core crypto algorithms - no cgo, fmt, net.
+	# Unfortunately, stuck with reflect via encoding/binary.
+	encoding/binary, golang.org/x/sys/cpu, hash
+	< crypto
+	< crypto/subtle
+	< crypto/internal/subtle
+	< crypto/cipher
+	< crypto/aes, crypto/des, crypto/hmac, crypto/md5, crypto/rc4,
+	  crypto/sha1, crypto/sha256, crypto/sha512
+	< CRYPTO;
+
+	CGO, fmt, net !< CRYPTO;
+
+	# CRYPTO-MATH is core bignum-based crypto - no cgo, net; fmt now ok.
+	CRYPTO, FMT, math/big
+	< crypto/rand
+	< crypto/internal/randutil
+	< crypto/ed25519/internal/edwards25519
+	< crypto/ed25519
+	< encoding/asn1
+	< golang.org/x/crypto/cryptobyte/asn1
+	< golang.org/x/crypto/cryptobyte
+	< golang.org/x/crypto/curve25519
+	< crypto/dsa, crypto/elliptic, crypto/rsa
+	< crypto/ecdsa
+	< CRYPTO-MATH;
+
+	CGO, net !< CRYPTO-MATH;
+
+	# TLS, Prince of Dependencies.
+	CGO, CRYPTO-MATH, NET, container/list, encoding/hex, encoding/pem
+	< golang.org/x/crypto/internal/subtle
+	< golang.org/x/crypto/chacha20
+	< golang.org/x/crypto/poly1305
+	< golang.org/x/crypto/chacha20poly1305
+	< golang.org/x/crypto/hkdf
+	< crypto/x509/internal/macOS
+	< crypto/x509/pkix
+	< crypto/x509
+	< crypto/tls;
+
+	# crypto-aware packages
+
+	NET, crypto/rand, mime/quotedprintable
+	< mime/multipart;
+
+	crypto/tls
+	< net/smtp;
+
+	# HTTP, King of Dependencies.
+
+	FMT
+	< golang.org/x/net/http2/hpack, net/http/internal;
+
+	FMT, NET, container/list, encoding/binary, log
+	< golang.org/x/text/transform
+	< golang.org/x/text/unicode/norm
+	< golang.org/x/text/unicode/bidi
+	< golang.org/x/text/secure/bidirule
+	< golang.org/x/net/idna
+	< golang.org/x/net/http/httpguts, golang.org/x/net/http/httpproxy;
+
+	NET, crypto/tls
+	< net/http/httptrace;
+
+	compress/gzip,
+	golang.org/x/net/http/httpguts,
+	golang.org/x/net/http/httpproxy,
+	golang.org/x/net/http2/hpack,
+	net/http/internal,
+	net/http/httptrace,
+	mime/multipart,
+	log
+	< net/http;
+
+	# HTTP-aware packages
+
+	encoding/json, net/http
+	< expvar;
+
+	net/http
+	< net/http/cookiejar, net/http/httputil;
+
+	net/http, flag
+	< net/http/httptest;
+
+	net/http, regexp
+	< net/http/cgi
+	< net/http/fcgi;
+
+	# Profiling
+	FMT, compress/gzip, encoding/binary, text/tabwriter
+	< runtime/pprof;
+
+	OS, compress/gzip, regexp
+	< internal/profile;
+
+	html/template, internal/profile, net/http, runtime/pprof, runtime/trace
+	< net/http/pprof;
+
+	# RPC
+	encoding/gob, encoding/json, go/token, html/template, net/http
+	< net/rpc
+	< net/rpc/jsonrpc;
+
+	# Test-only
+	log
+	< testing/iotest;
+
+	FMT, flag, math/rand
+	< testing/quick;
+
+	FMT, flag, runtime/debug, runtime/trace
+	< testing;
+
+	internal/testlog, runtime/pprof, regexp
+	< testing/internal/testdeps;
+
+	OS, flag, testing, internal/cfg
+	< internal/testenv;
+
+	OS, encoding/base64
+	< internal/obscuretestdata;
+
+	CGO, OS, fmt
+	< os/signal/internal/pty;
+
+	NET, testing
+	< golang.org/x/net/nettest;
+
+	FMT, container/heap, math/rand
+	< internal/trace;
+`
 
 // listStdPkgs returns the same list of packages as "go list std".
 func listStdPkgs(goroot string) ([]string, error) {
@@ -507,11 +502,11 @@
 		}
 
 		name := filepath.ToSlash(path[len(src):])
-		if name == "builtin" || name == "cmd" || strings.Contains(name, "golang.org/x/") {
+		if name == "builtin" || name == "cmd" {
 			return filepath.SkipDir
 		}
 
-		pkgs = append(pkgs, name)
+		pkgs = append(pkgs, strings.TrimPrefix(name, "vendor/"))
 		return nil
 	}
 	if err := filepath.Walk(src, walkFn); err != nil {
@@ -536,6 +531,7 @@
 	sort.Strings(all)
 
 	sawImport := map[string]map[string]bool{} // from package => to package => true
+	policy := depsPolicy(t)
 
 	for _, pkg := range all {
 		imports, err := findImports(pkg)
@@ -546,7 +542,7 @@
 		if sawImport[pkg] == nil {
 			sawImport[pkg] = map[string]bool{}
 		}
-		ok := allowed(pkg)
+		ok := policy[pkg]
 		var bad []string
 		for _, imp := range imports {
 			sawImport[pkg][imp] = true
@@ -573,26 +569,16 @@
 		}
 		return ""
 	}
-
-	// Also test some high-level policy goals are being met by not finding
-	// these dependency paths:
-	badPaths := []struct{ from, to string }{
-		{"net", "unicode"},
-		{"os", "unicode"},
-	}
-
-	for _, path := range badPaths {
-		if how := depPath(path.from, path.to); how != "" {
-			t.Errorf("policy violation: %s", how)
-		}
-	}
-
 }
 
 var buildIgnore = []byte("\n// +build ignore")
 
 func findImports(pkg string) ([]string, error) {
-	dir := filepath.Join(Default.GOROOT, "src", pkg)
+	vpkg := pkg
+	if strings.HasPrefix(pkg, "golang.org") {
+		vpkg = "vendor/" + pkg
+	}
+	dir := filepath.Join(Default.GOROOT, "src", vpkg)
 	files, err := ioutil.ReadDir(dir)
 	if err != nil {
 		return nil, err
@@ -635,3 +621,187 @@
 	sort.Strings(imports)
 	return imports, nil
 }
+
+// depsPolicy returns a map m such that m[p][d] == true when p can import d.
+func depsPolicy(t *testing.T) map[string]map[string]bool {
+	allowed := map[string]map[string]bool{"NONE": {}}
+	disallowed := [][2][]string{}
+
+	parseDepsRules(t, func(deps []string, op string, users []string) {
+		if op == "!<" {
+			disallowed = append(disallowed, [2][]string{deps, users})
+			return
+		}
+		for _, u := range users {
+			if allowed[u] != nil {
+				t.Errorf("multiple deps lists for %s", u)
+			}
+			allowed[u] = make(map[string]bool)
+			for _, d := range deps {
+				if allowed[d] == nil {
+					t.Errorf("use of %s before its deps list", d)
+				}
+				allowed[u][d] = true
+			}
+		}
+	})
+
+	// Check for missing deps info.
+	for _, deps := range allowed {
+		for d := range deps {
+			if allowed[d] == nil {
+				t.Errorf("missing deps list for %s", d)
+			}
+		}
+	}
+
+	// Complete transitive allowed deps.
+	for k := range allowed {
+		for i := range allowed {
+			for j := range allowed {
+				if i != k && k != j && allowed[i][k] && allowed[k][j] {
+					if i == j {
+						// Can only happen along with a "use of X before deps" error above,
+						// but this error is more specific - it makes clear that reordering the
+						// rules will not be enough to fix the problem.
+						t.Errorf("deps policy cycle: %s < %s < %s", j, k, i)
+					}
+					allowed[i][j] = true
+				}
+			}
+		}
+	}
+
+	// Check negative assertions against completed allowed deps.
+	for _, bad := range disallowed {
+		deps, users := bad[0], bad[1]
+		for _, d := range deps {
+			for _, u := range users {
+				if allowed[u][d] {
+					t.Errorf("deps policy incorrect: assertion failed: %s !< %s", d, u)
+				}
+			}
+		}
+	}
+
+	if t.Failed() {
+		t.FailNow()
+	}
+
+	return allowed
+}
+
+// parseDepsRules parses depsRules, calling save(deps, op, users)
+// for each deps < users or deps !< users rule
+// (op is "<" or "!<").
+func parseDepsRules(t *testing.T, save func(deps []string, op string, users []string)) {
+	p := &depsParser{t: t, lineno: 1, text: depsRules}
+
+	var prev []string
+	var op string
+	for {
+		list, tok := p.nextList()
+		if tok == "" {
+			if prev == nil {
+				break
+			}
+			p.syntaxError("unexpected EOF")
+		}
+		if prev != nil {
+			save(prev, op, list)
+		}
+		prev = list
+		if tok == ";" {
+			prev = nil
+			op = ""
+			continue
+		}
+		if tok != "<" && tok != "!<" {
+			p.syntaxError("missing <")
+		}
+		op = tok
+	}
+}
+
+// A depsParser parses the depsRules syntax described above.
+type depsParser struct {
+	t        *testing.T
+	lineno   int
+	lastWord string
+	text     string
+}
+
+// syntaxError reports a parsing error.
+func (p *depsParser) syntaxError(msg string) {
+	p.t.Fatalf("deps:%d: syntax error: %s near %s", p.lineno, msg, p.lastWord)
+}
+
+// nextList parses and returns a comma-separated list of names.
+func (p *depsParser) nextList() (list []string, token string) {
+	for {
+		tok := p.nextToken()
+		switch tok {
+		case "":
+			if len(list) == 0 {
+				return nil, ""
+			}
+			fallthrough
+		case ",", "<", "!<", ";":
+			p.syntaxError("bad list syntax")
+		}
+		list = append(list, tok)
+
+		tok = p.nextToken()
+		if tok != "," {
+			return list, tok
+		}
+	}
+}
+
+// nextToken returns the next token in the deps rules,
+// one of ";" "," "<" "!<" or a name.
+func (p *depsParser) nextToken() string {
+	for {
+		if p.text == "" {
+			return ""
+		}
+		switch p.text[0] {
+		case ';', ',', '<':
+			t := p.text[:1]
+			p.text = p.text[1:]
+			return t
+
+		case '!':
+			if len(p.text) < 2 || p.text[1] != '<' {
+				p.syntaxError("unexpected token !")
+			}
+			p.text = p.text[2:]
+			return "!<"
+
+		case '#':
+			i := strings.Index(p.text, "\n")
+			if i < 0 {
+				i = len(p.text)
+			}
+			p.text = p.text[i:]
+			continue
+
+		case '\n':
+			p.lineno++
+			fallthrough
+		case ' ', '\t':
+			p.text = p.text[1:]
+			continue
+
+		default:
+			i := strings.IndexAny(p.text, "!;,<#\n \t")
+			if i < 0 {
+				i = len(p.text)
+			}
+			t := p.text[:i]
+			p.text = p.text[i:]
+			p.lastWord = t
+			return t
+		}
+	}
+}