blob: c4e2a8dd00fd77611268f882049da6c2c03368af [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.
// The following enables go generate to generate the doc.go file.
//go:generate go run $JIRI_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
package main
import (
"fmt"
"go/build"
"v.io/jiri/profiles/profilescmdline"
"v.io/jiri/profiles/profilesreader"
"v.io/x/lib/cmdline"
)
var (
flagStyle string
flagDirect bool
flagGoroot bool
flagTest bool
flagXTest bool
mergePoliciesFlag profilesreader.MergePolicies
)
const (
styleSet = "set"
styleIndent = "indent"
styleDot = "dot"
descDirect = "Only show direct dependencies, rather than showing transitive dependencies."
descGoroot = "Show $GOROOT packages."
descTest = "Show imports from test files in the same package."
descXTest = "Show imports from test files in the same package or in the *_test package."
)
func init() {
cmdList.Flags.StringVar(&flagStyle, "style", styleSet, `
List dependencies with the given style:
set - As a sorted set of unique packages.
indent - As a hierarchical list with pretty indentation.
dot - As a DOT graph (http://www.graphviz.org)
`)
cmdList.Flags.BoolVar(&flagDirect, "direct", false, descDirect)
cmdList.Flags.BoolVar(&flagGoroot, "goroot", false, descGoroot)
cmdList.Flags.BoolVar(&flagTest, "test", false, descTest)
cmdList.Flags.BoolVar(&flagXTest, "xtest", false, descXTest)
cmdListImporters.Flags.BoolVar(&flagDirect, "direct", false, descDirect)
cmdListImporters.Flags.BoolVar(&flagGoroot, "goroot", false, descGoroot)
cmdListImporters.Flags.BoolVar(&flagTest, "test", false, descTest)
cmdListImporters.Flags.BoolVar(&flagXTest, "xtest", false, descXTest)
mergePoliciesFlag = profilesreader.JiriMergePolicies()
profilescmdline.RegisterMergePoliciesFlag(&cmdList.Flags, &mergePoliciesFlag)
profilescmdline.RegisterMergePoliciesFlag(&cmdListImporters.Flags, &mergePoliciesFlag)
}
var readerFlags profilescmdline.ReaderFlagValues
func main() {
cmdline.Main(cmdRoot)
}
func depOptsFromFlags() depOpts {
return depOpts{
DirectOnly: flagDirect,
IncludeGoroot: flagGoroot,
IncludeTest: flagTest,
IncludeXTest: flagXTest,
}
}
var cmdRoot = &cmdline.Command{
Name: "godepcop",
Short: "Check Go package dependencies against user-defined rules",
Long: `
Command godepcop checks Go package dependencies against constraints described in
.godepcop files. In addition to user-defined constraints, the Go 1.5 internal
package rules are also enforced.
`,
Children: []*cmdline.Command{cmdCheck, cmdList, cmdListImporters},
}
var cmdCheck = &cmdline.Command{
Runner: cmdline.RunnerFunc(runCheck),
Name: "check",
ArgsName: "<packages>",
ArgsLong: "<packages> is a list of packages to check",
Short: "Check package dependency constraints",
Long: `
Check package dependency constraints.
Every Go package directory may contain an optional .godepcop file. Each file
specifies dependency rules, which either allow or deny imports by that package.
The files are traversed hierarchically, from the deepmost package to the root of
the source tree, until a matching rule is found. If no matching rule is found,
the default behavior is to allow the dependency, to support packages that do not
have any dependency rules.
The .godepcop file is encoded in XML:
<godepcop>
<pkg allow="pattern1/..."/>
<pkg allow="pattern2"/>
<pkg deny="..."/>
<test allow="pattern3"/>
<xtest allow="..."/>
</godepcop>
Each element in godepcop is a rule, which either allows or denies imports based
on the given pattern. Patterns that end with "/..." are special: "foo/..."
means that foo and all its subpackages match the rule. The special-case pattern
"..." means that all packages in GOPATH, but not GOROOT, match the rule.
There are three groups of rules:
pkg - Rules applied to all imports from the package.
test - Extra rules for imports from all test files.
xtest - Extra rules for imports from test files in the *_test package.
Rules in each group are processed in the order they appear in the .godepcop
file. The transitive closure of the following imports are checked for each
package P:
P.Imports - check pkg rules
P.Imports+P.TestImports - check test and pkg rules
P.Imports+P.TestImports+P.XTestImports - check xtest, test and pkg rules
`}
func runCheck(env *cmdline.Env, args []string) error {
// Gather packages specified in args.
paths, err := listPackagePaths(env, args...)
if err != nil {
return err
}
var pkgs []*build.Package
for _, path := range paths {
pkg, err := importPackage(path)
if err != nil {
return err
}
pkgs = append(pkgs, pkg)
}
// Check each package.
var violations []violation
for _, pkg := range pkgs {
v, err := checkDeps(pkg)
if err != nil {
return err
}
violations = append(violations, v...)
}
for _, v := range violations {
fmt.Fprintf(env.Stdout, "%q not allowed to import %q (%v)\n", v.Src.ImportPath, v.Dst.ImportPath, v.Err)
}
if len(violations) > 0 {
return fmt.Errorf("dependency violation")
}
return nil
}
var cmdList = &cmdline.Command{
Runner: cmdline.RunnerFunc(runList),
Name: "list",
ArgsName: "<packages>",
ArgsLong: "<packages> is a list of packages",
Short: "List packages imported by the given packages",
Long: `
List packages imported by the given <packages>.
Lists all transitive imports by default; set the -direct flag to limit the
listing to direct imports by the given <packages>.
Elides $GOROOT packages by default; set the -goroot flag to include packages in
$GOROOT. If any of the given <packages> are $GOROOT packages, list behaves as
if -goroot were set to true.
Lists each imported package exactly once when using the default -style=set. See
the -style flag for alternate output styles.
`}
func runList(env *cmdline.Env, args []string) error {
// Gather packages specified in args.
paths, err := listPackagePaths(env, args...)
if err != nil {
return err
}
var pkgs []*build.Package
opts := depOptsFromFlags()
for _, path := range paths {
pkg, err := importPackage(path)
if err != nil {
return err
}
if pkg.Goroot {
// If any package in args is a GOROOT package, always include GOROOT deps.
opts.IncludeGoroot = true
}
pkgs = append(pkgs, pkg)
}
switch flagStyle {
case styleIndent:
// Print indented deps for each package.
for _, pkg := range pkgs {
if err := opts.PrintIndent(env.Stdout, pkg); err != nil {
return err
}
}
case styleDot:
if err := printDot(env.Stdout, pkgs, opts); err != nil {
return err
}
default:
// Print deps for all combined packages.
deps := make(map[string]*build.Package)
for _, pkg := range pkgs {
if err := opts.Deps(pkg, deps); err != nil {
return err
}
}
for _, dep := range sortPackages(deps) {
fmt.Fprintln(env.Stdout, dep.ImportPath)
}
}
return nil
}
var cmdListImporters = &cmdline.Command{
Runner: cmdline.RunnerFunc(runListImporters),
Name: "list-importers",
ArgsName: "<packages>",
ArgsLong: "<packages> is a list of packages",
Short: "List packages that import the given packages",
Long: `
List packages that import the given <packages>; the reverse of "list". The
listed packages are called "importers".
Lists all transitive importers by default; set the -direct flag to limit the
listing to importers that directly import the given <packages>.
Elides $GOROOT packages by default; set the -goroot flag to include importers in
$GOROOT. If any of the given <packages> are $GOROOT packages, list-importers
behaves as if -goroot were set to true.
Lists each importer package exactly once.
`}
func runListImporters(env *cmdline.Env, args []string) error {
// Gather target packages specified in args.
targetPaths, err := listPackagePaths(env, args...)
if err != nil {
return err
}
targets := make(map[string]*build.Package)
opts := depOptsFromFlags()
for _, path := range targetPaths {
pkg, err := importPackage(path)
if err != nil {
return err
}
if pkg.Goroot {
// If any package in args is a GOROOT package, always include GOROOT deps.
opts.IncludeGoroot = true
}
targets[path] = pkg
}
// Gather all known packages.
allPaths, err := listPackagePaths(env, "all")
if err != nil {
return err
}
// Print every package that has dependencies that overlap with the targets.
matches := make(map[string]*build.Package)
for _, path := range allPaths {
pkg, err := importPackage(path)
if err != nil {
return err
}
deps := make(map[string]*build.Package)
if err := opts.Deps(pkg, deps); err != nil {
return err
}
if hasOverlap(deps, targets) {
matches[path] = pkg
}
}
for _, pkg := range sortPackages(matches) {
fmt.Fprintln(env.Stdout, pkg.ImportPath)
}
return nil
}