~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
184 lines · 5.0 KB Raw History
package main

import (
	"sort"
	"strings"

	"golang.org/x/tools/go/packages"
)

var layerRank = map[string]int{
	"api":         0,
	"assets":      0,
	"contract":    1,
	"kit":         2,
	"transport":   3,
	"impl":        4,
	"controllers": 5,
	"app":         6,
	"cmd":         7,
}

const layerOrder = "api <- contract <- kit <- transport <- impl <- controllers <- app <- cmd"

var appRoots = []string{"internal/app", "internal/stack", "internal/devcore", "pkg"}

var toolRoots = []string{"cmd", "test", "hack", "internal/apigen", "internal/lexgen"}

var backendPrefixes = []string{
	"internal/compute", "internal/vault", "internal/pds", "internal/atprotorepo", "internal/events",
	"internal/vmruntime", "internal/hostsvc", "internal/fcapi", "internal/guestnet", "internal/guestdisk", "internal/guestimage",
	"internal/policyengine/runs", "internal/relay", "internal/ingress", "internal/ssh", "internal/dispatcher",
	"internal/gateway", "internal/guestcp", "internal/market", "internal/lease", "internal/postgresctl", "internal/ttlctl",
	"internal/clusterprovider", "internal/tenantstore",
}

var hostLibraries = []string{
	"internal/vmruntime", "internal/hostsvc", "internal/fcapi", "internal/guestnet", "internal/guestdisk", "internal/guestimage",
	"internal/postgresctl", "internal/ttlctl", "internal/clusterprovider",
}

var backendBanned = []string{
	"k8s.io/client-go", "sigs.k8s.io/controller-runtime", "sigs.k8s.io/multicluster-runtime", "github.com/kcp-dev",
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
	"internal/bus", "internal/controllers", "internal/app", "internal/kcpserver", "internal/kcpclient",
	"internal/kcpdatastore", "internal/stack",
}

func (c *checker) layerOf(path string) string {
	if !c.local(path) {
		return ""
	}
	rel := c.rel(path)
	switch {
	case under(rel, "api"):
		return "api"
	case under(rel, "internal/contract"):
		return "contract"
	case under(rel, "internal/kit"):
		return "kit"
	case strings.HasPrefix(rel, "internal/controllers/runtime/"):
		return "transport"
	case under(rel, "internal/controllers"):
		return "controllers"
	case under(rel, appRoots...) || mainPackage(rel):
		return "app"
	case under(rel, toolRoots...) || rel == ".":
		return "cmd"
	case under(rel, "internal"):
		return "impl"
	}
	return "assets"
}

func mainPackage(rel string) bool {
	name, ok := strings.CutPrefix(rel, "internal/")
	return ok && !strings.Contains(name, "/") && strings.HasSuffix(name, "main")
}

func (c *checker) layers() {
	for _, p := range c.sortedLocal() {
		from := c.layerOf(p.PkgPath)
		for _, imp := range sortedImports(p) {
			if !c.local(imp) {
				if from == "contract" && strings.Contains(strings.SplitN(imp, "/", 2)[0], ".") {
					c.fail("layers", c.rel(p.PkgPath), "imports %s; a contract imports api and the standard library only", imp)
				}
				continue
			}
			to := c.layerOf(imp)
			switch {
			case from == "contract" && to != "api" && to != "contract":
				c.fail("layers", c.rel(p.PkgPath), "imports %s (%s); a contract imports api and the standard library only", c.rel(imp), to)
			case layerRank[to] > layerRank[from]:
				c.fail("layers", c.rel(p.PkgPath), "(%s) imports %s (%s); imports point down %s", from, c.rel(imp), to, layerOrder)
			}
		}
	}
}

func (c *checker) isBackend(path string) bool {
	return c.local(path) && under(c.rel(path), backendPrefixes...)
}

func (c *checker) banned(path string) string {
	name := path
	if c.local(path) {
		name = c.rel(path)
	}
	for _, b := range backendBanned {
		if under(name, b) {
			return b
		}
	}
	return ""
}

func (c *checker) backends() {
	for _, p := range c.sortedLocal() {
		if !c.isBackend(p.PkgPath) {
			continue
		}
		parent := map[string]string{p.PkgPath: ""}
		queue := []string{p.PkgPath}
		reported := map[string]bool{}
		for len(queue) > 0 {
			cur := queue[0]
			queue = queue[1:]
			if cur != p.PkgPath {
				if b := c.banned(cur); b != "" {
					if !reported[b] {
						reported[b] = true
						c.fail("backend", c.rel(p.PkgPath), "depends on %s via %s; a backend package runs without kcp, the bus or a controller runtime", b, c.chain(parent, cur))
					}
					continue
				}
			}
			dep := c.byPath[cur]
			if dep == nil {
				continue
			}
			for _, imp := range sortedImports(dep) {
				if _, seen := parent[imp]; !seen {
					parent[imp] = cur
					queue = append(queue, imp)
				}
			}
		}
	}
}

func (c *checker) chain(parent map[string]string, at string) string {
	var hops []string
	for cur := at; cur != ""; cur = parent[cur] {
		name := cur
		if c.local(cur) {
			name = c.rel(cur)
		}
		hops = append(hops, name)
	}
	for i, j := 0, len(hops)-1; i < j; i, j = i+1, j-1 {
		hops[i], hops[j] = hops[j], hops[i]
	}
	return strings.Join(hops, " -> ")
}

func (c *checker) sortedLocal() []*packages.Package {
	var out []*packages.Package
	for path, p := range c.byPath {
		if c.local(path) {
			out = append(out, p)
		}
	}
	sort.Slice(out, func(i, j int) bool { return out[i].PkgPath < out[j].PkgPath })
	return out
}

func sortedImports(p *packages.Package) []string {
	out := make([]string, 0, len(p.Imports))
	for imp := range p.Imports {
		out = append(out, imp)
	}
	sort.Strings(out)
	return out
}