blob: f49668257d526fc9a0af401cd29f146a1664684a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"go/build"
"regexp"
"strings"
)
type result int
const (
resultUndecided result = iota
resultApproved
resultRejected
)
func (r result) String() string {
return []string{"undecided", "approved", "rejected"}[int(r)]
}
type violation struct {
Src, Dst *build.Package
Err error
}
func enforceRule(r rule, pkg *build.Package) (result, error) {
if r.Pattern() == "..." {
switch {
case pkg.Goroot:
return resultUndecided, nil
case r.IsDeny():
return resultRejected, nil
}
return resultApproved, nil
}
re := regexp.QuoteMeta(r.Pattern())
if strings.HasSuffix(re, `/\.\.\.`) {
re = re[:len(re)-len(`/\.\.\.`)] + `(/.*)?`
}
switch matched, err := regexp.MatchString("^"+re+"$", pkg.ImportPath); {
case err != nil:
return resultUndecided, err
case !matched:
return resultUndecided, nil
case r.IsDeny():
return resultRejected, nil
}
return resultApproved, nil
}
// verifyGo15InternalRule implements support for the internal package rule,
// which is supposed to be enabled for GOPATH packages in Go 1.5. This logic
// can be removed after Go 1.5 is released.
//
// https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU
func verifyGo15InternalRule(src, dst string) bool {
// The rule is that package "a/b/c/internal/d/e/f" can only be imported by
// code rooted at "a/b/c". The doc above isn't clear, but if there are
// multiple occurrences of "internal", we apply the rule to the last (deepest)
// occurrence.
//
// The only tricky part is to ensure path components are matched correctly.
// E.g. when looking for the last path component that is "internal", we don't
// want to match "Xinternal" or "internalX".
const internal = "/internal"
var root string
if strings.HasSuffix(dst, internal) {
root = dst[:len(dst)-len(internal)]
} else if index := strings.LastIndex(dst, internal+"/"); index != -1 {
root = dst[:index]
}
return root == "" || src == root || strings.HasPrefix(src, root+"/")
}
var errGo15Internal = errors.New("violates Go 1.5 internal package rule")
func checkDep(pkg, dep *build.Package, mode checkMode) (*violation, error) {
it := newConfigIter(pkg)
for it.Advance() {
// Collect the ordered rules from this config for the given mode.
cfg := it.Value()
var rules []rule
switch mode {
case modePkg:
rules = cfg.PkgRules
case modeTest:
rules = append(cfg.TestRules, cfg.PkgRules...)
case modeXTest:
rules = append(cfg.XTestRules, cfg.TestRules...)
rules = append(rules, cfg.PkgRules...)
}
// Enforce each rule in order.
for _, rule := range rules {
switch result, err := enforceRule(rule, dep); {
case err != nil:
return nil, err
case result == resultApproved:
return nil, nil
case result == resultRejected:
err := fmt.Errorf(`violates %s deny rule %q in %s`, mode, rule.Pattern(), cfg.Path)
return &violation{pkg, dep, err}, nil
}
}
}
if err := it.Err(); err != nil {
return nil, err
}
// All config files have been checked without an approved or rejected result;
// treat this as an approved result. This also handles the case where no
// config files have been specified.
return nil, nil
}
func checkDeps(pkg *build.Package) ([]violation, error) {
var violations []violation
// First check direct dependencies against the Go 1.5 internal package rule.
optsDirect := depOpts{DirectOnly: true, IncludeGoroot: true, IncludeTest: true, IncludeXTest: true}
depsDirect := make(map[string]*build.Package)
if err := optsDirect.Deps(pkg, depsDirect); err != nil {
return nil, err
}
for _, dep := range sortPackages(depsDirect) {
if !verifyGo15InternalRule(pkg.ImportPath, dep.ImportPath) {
violations = append(violations, violation{pkg, dep, errGo15Internal})
}
}
// Now check transitive dependencies against the rules in .godepcop files.
// Each mode is checked independently, since the .godepcop configuration rules
// may be different.
for _, mode := range []checkMode{modePkg, modeTest, modeXTest} {
opts := depOpts{IncludeGoroot: true}
switch mode {
case modeTest:
opts.IncludeTest = true
case modeXTest:
opts.IncludeTest = true
opts.IncludeXTest = true
}
deps := make(map[string]*build.Package)
if err := opts.Deps(pkg, deps); err != nil {
return nil, err
}
for _, dep := range sortPackages(deps) {
v, err := checkDep(pkg, dep, mode)
if err != nil {
return nil, err
}
if v != nil {
violations = append(violations, *v)
}
}
}
return violations, nil
}
type checkMode int
const (
modePkg checkMode = iota
modeTest
modeXTest
)
func (mode checkMode) String() string {
return []string{"pkg", "test", "xtest"}[mode]
}