diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index d3bbf087c3565c..bd0ebce1c7dfbb 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -20,475 +20,470 @@ import ( "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"}, - - "L0": { - "errors", - "io", - "runtime", - "runtime/internal/atomic", - "sync", - "sync/atomic", - "unsafe", - "internal/cpu", - "internal/bytealg", - "internal/reflectlite", - }, - - // 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": {}, - - "L1": { - "L0", - "math", - "math/bits", - "math/cmplx", - "math/rand", - "sort", - "strconv", - "unicode/utf16", - "unicode/utf8", - }, - - // 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": {}, - - "L2": { - "L1", - "bufio", - "bytes", - "path", - "strings", - "unicode", - }, - - // 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"}, - - "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", - }, - - // End of linear dependency definitions. - - // 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"}, - - "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"}, - - // 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", - }, - - // 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"}, - - // 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"}, - - "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"}, - - // 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", - }, - - // 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"}, - - "GOPARSER": { - "go/ast", - "go/doc", - "go/parser", - "go/printer", - "go/scanner", - "go/token", - }, - - "go/format": {"L4", "GOPARSER", "internal/format"}, - "internal/format": {"L4", "GOPARSER"}, - - // 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"}, - - // 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"}, - - "html/template": { - "L4", "OS", "encoding/json", "html", "text/template", - "text/template/parse", - }, - "text/template": { - "L4", "OS", "net/url", "text/template/parse", - }, - - // 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"}, - - // Fake entry to satisfy the pseudo-import "C" - // that shows up in programs that use cgo. - "C": {}, - - // Race detector/MSan uses cgo. - "runtime/race": {"C"}, - "runtime/msan": {"C"}, - - // Plan 9 alone needs io/ioutil and os. - "os/user": {"L4", "CGO", "io/ioutil", "os", "syscall", "internal/syscall/windows", "internal/syscall/windows/registry"}, - - // Internal package used only for testing. - "os/signal/internal/pty": {"CGO", "fmt", "os", "syscall"}, - - // 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", - }, - - // NET enables use of basic network-related packages. - "NET": { - "net", - "mime", - "net/textproto", - "net/url", - }, - - // Uses of networking. - "log/syslog": {"L4", "OS", "net"}, - "net/mail": {"L4", "NET", "OS", "mime"}, - "net/textproto": {"L4", "OS", "net"}, - - // 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"}, - - "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", - }, - - // 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"}, - - // 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"}, - - // 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"}, - - "CRYPTO-MATH": { - "CRYPTO", - "crypto/dsa", - "crypto/ecdsa", - "crypto/elliptic", - "crypto/rand", - "crypto/rsa", - "encoding/asn1", - "math/big", - }, - - // 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"}, - - // Simple net+crypto-aware packages. - "mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto", "mime/quotedprintable"}, - "net/smtp": {"L4", "CRYPTO", "NET", "crypto/tls"}, - - // 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"}, - - // 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"}, -} +// 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; + + # 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; + + RUNTIME + < sort + < container/heap; + + RUNTIME + < io; + + reflect !< sort; + + # 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; + + # TIME is SYSCALL plus the core packages about time, including context. + SYSCALL + < time/tzdata + < time + < context + < TIME; + + # MATH is RUNTIME plus the basic math packages. + RUNTIME + < math + < MATH; + + unicode !< math; + + MATH + < math/cmplx; + + MATH + < math/rand; + + MATH, unicode/utf8 + < strconv; + + unicode !< strconv; + + # STR is basic string and buffer manipulation. + RUNTIME, io, unicode/utf8, unicode/utf16, unicode + < bytes, strings + < bufio, path; + + bufio, path, strconv + < STR; + + # 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; + + unicode, fmt !< os, os/signal; + + os/signal, STR + < path/filepath + < io/ioutil, os/exec + < OS; + + reflect !< OS; + + OS + < golang.org/x/sys/cpu, internal/goroot; + + # 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; + + os, reflect + < internal/fmtsort + < fmt; + + OS, fmt + < FMT; + + log !< FMT; + + # Misc packages needing only FMT. + FMT + < flag, + html, + mime/quotedprintable, + net/internal/socktest, + net/url, + runtime/debug, + runtime/trace, + text/scanner, + text/tabwriter; + + # encodings + # core ones do not use fmt. + io, strconv + < encoding; + + encoding, reflect + < encoding/binary + < encoding/base32, encoding/base64; + + fmt !< encoding/base32, encoding/base64; + + FMT, encoding/base32, encoding/base64 + < encoding/ascii85, encoding/csv, encoding/gob, encoding/hex, + encoding/json, encoding/pem, encoding/xml, mime; + + # hashes + io + < hash + < hash/adler32, hash/crc32, hash/crc64, hash/fnv, hash/maphash; + + # math/big + FMT, encoding/binary, math/rand + < math/big; + + # compression + FMT, encoding/binary, hash/adler32, hash/crc32 + < compress/bzip2, compress/flate, compress/lzw + < archive/zip, compress/gzip, compress/zlib; + + # templates + FMT + < text/template/parse; + + net/url, text/template/parse + < text/template + < internal/lazytemplate; + + encoding/json, html, text/template + < html/template; + + # regexp + FMT + < regexp/syntax + < regexp + < internal/lazyregexp; + + # suffix array + encoding/binary, regexp + < index/suffixarray; + + # executable parsing + FMT, encoding/binary, compress/zlib + < debug/dwarf + < debug/elf, debug/gosym, debug/macho, debug/pe, debug/plan9obj, internal/xcoff + < DEBUG; + + # go parser and friends. + FMT + < go/token + < go/scanner + < go/ast + < go/parser; + + go/parser, text/tabwriter + < go/printer + < go/format; + + go/parser, internal/lazyregexp, text/template + < go/doc; + + math/big, go/token + < go/constant; + + 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 -// isMacro reports whether p is a package dependency macro -// (uppercase name). -func isMacro(p string) bool { - return 'A' <= p[0] && p[0] <= 'Z' -} + encoding/json, net/http + < expvar; -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 + 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; - // 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 -} + 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 @@ func listStdPkgs(goroot string) ([]string, error) { } 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 @@ func TestDependencies(t *testing.T) { 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 @@ func TestDependencies(t *testing.T) { 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 @@ func TestDependencies(t *testing.T) { } 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 @@ func findImports(pkg string) ([]string, error) { 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 + } + } +}