Skip to content

Commit 08506eb

Browse files
Merge pull request #18543 from juanvallejo/jvallejo/analysis-deps-yours
Automatic merge from submit-queue (batch tested with PRs 18999, 18543). adds dependency analysis Depends on #18466 Depends on #18606 Adds dependency graph analysis. Outputs "yours", "mine", "ours" dependencies. Usage: ```bash $ ./depcheck analyze --root=github.com/openshift/origin --entry=cmd/... --entry=pkg/... --entry=tools/... --entry=test/... --openshift --dep=github.com/openshift/origin/vendor/k8s.io/kubernetes ``` [Output of the command above](https://gist.github.com/juanvallejo/d83d2f56e4f7345c3f37769237cb12a1) cc @deads2k
2 parents 3caa07e + 4792756 commit 08506eb

File tree

4 files changed

+415
-0
lines changed

4 files changed

+415
-0
lines changed

tools/depcheck/pkg/analyze/analyze.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package analyze
2+
3+
import (
4+
"github.com/openshift/origin/tools/depcheck/pkg/graph"
5+
)
6+
7+
func FindExclusiveDependencies(g *graph.MutableDirectedGraph, targetNodes []*graph.Node) []*graph.Node {
8+
newGraph := g.Copy()
9+
for _, target := range targetNodes {
10+
newGraph.RemoveNode(target)
11+
}
12+
13+
return newGraph.PruneOrphans()
14+
}

tools/depcheck/pkg/cmd/analyze.go

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"strings"
8+
9+
"github.com/spf13/cobra"
10+
11+
"github.com/gonum/graph/path"
12+
13+
"github.com/openshift/origin/tools/depcheck/pkg/analyze"
14+
"github.com/openshift/origin/tools/depcheck/pkg/graph"
15+
)
16+
17+
var analyzeImportsExample = `# analyze a dependency graph against one of its vendor packages
18+
%[1]s analyze --root=github.com/openshift/origin --entry=pkg/foo/... --dep=github.com/openshift/origin/vendor/k8s.io/kubernetes
19+
20+
# analyze a dependency graph against one of its vendor packages using OpenShift defaults
21+
%[1]s analyze --root=github.com/openshift/origin --entry=cmd/... --entry=pkg/... --entry=tools/... --entry=test/... --openshift --dep=github.com/openshift/origin/vendor/k8s.io/kubernetes
22+
`
23+
24+
type AnalyzeOptions struct {
25+
GraphOptions *graph.GraphOptions
26+
27+
// Packages to analyze
28+
Dependencies []string
29+
30+
Out io.Writer
31+
ErrOut io.Writer
32+
}
33+
34+
type AnalyzeFlags struct {
35+
GraphFlags *graph.GraphFlags
36+
37+
Dependencies []string
38+
}
39+
40+
func (o *AnalyzeFlags) ToOptions(out, errout io.Writer) (*AnalyzeOptions, error) {
41+
graphOpts, err := o.GraphFlags.ToOptions(out, errout)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
return &AnalyzeOptions{
47+
GraphOptions: graphOpts,
48+
Dependencies: o.Dependencies,
49+
50+
Out: out,
51+
ErrOut: errout,
52+
}, nil
53+
}
54+
55+
func NewCmdAnalyzeImports(parent string, out, errout io.Writer) *cobra.Command {
56+
analyzeFlags := &AnalyzeFlags{
57+
GraphFlags: &graph.GraphFlags{},
58+
}
59+
60+
cmd := &cobra.Command{
61+
Use: "analyze --root=github.com/openshift/origin --entry=pkg/foo/... --dep pkg/vendor/bar",
62+
Short: "Creates and analyzes a dependency graph against a specified subpackage",
63+
Long: "Creates and analyzes a dependency graph against a specified subpackage",
64+
Example: fmt.Sprintf(traceImportsExample, parent),
65+
RunE: func(c *cobra.Command, args []string) error {
66+
opts, err := analyzeFlags.ToOptions(out, errout)
67+
if err != nil {
68+
return err
69+
}
70+
71+
if err := opts.Complete(); err != nil {
72+
return err
73+
}
74+
if err := opts.Validate(); err != nil {
75+
return err
76+
}
77+
if err := opts.Run(); err != nil {
78+
return err
79+
}
80+
81+
return nil
82+
},
83+
}
84+
85+
analyzeFlags.GraphFlags.AddFlags(cmd)
86+
cmd.Flags().StringSliceVarP(&analyzeFlags.Dependencies, "dep", "d", analyzeFlags.Dependencies, "import path of the dependency to analyze. Multiple --dep values may be provided.")
87+
return cmd
88+
}
89+
90+
func (o *AnalyzeOptions) Complete() error {
91+
return o.GraphOptions.Complete()
92+
}
93+
94+
func (o *AnalyzeOptions) Validate() error {
95+
if err := o.GraphOptions.Validate(); err != nil {
96+
return err
97+
}
98+
if len(o.Dependencies) == 0 {
99+
return errors.New("at least one --dep package must be specified")
100+
}
101+
102+
return nil
103+
}
104+
105+
func (o *AnalyzeOptions) Run() error {
106+
g, err := o.GraphOptions.BuildGraph()
107+
if err != nil {
108+
return err
109+
}
110+
111+
return o.analyzeGraph(g)
112+
}
113+
114+
// analyzeGraph receives a MutableDirectedGraph and outputs
115+
// - "Yours": a list of every node in the set of dependencies unique to a target (--dep) node.
116+
// - "Mine": a list of every node in the set of dependencies unique to the root nodes.
117+
// - "Ours": a list of every node in the overlapping set between the "Yours" and "Mine" sets.
118+
func (o *AnalyzeOptions) analyzeGraph(g *graph.MutableDirectedGraph) error {
119+
yours, mine, ours, err := o.calculateDependencies(g)
120+
if err != nil {
121+
return err
122+
}
123+
124+
fmt.Printf("Analyzing a total of %v packages\n", len(g.Nodes()))
125+
fmt.Println()
126+
127+
fmt.Printf("\"Yours\": %v dependencies exclusive to %q\n", len(yours), o.Dependencies)
128+
for _, n := range yours {
129+
fmt.Printf(" - %s\n", n)
130+
}
131+
fmt.Println()
132+
133+
fmt.Printf("\"Mine\": %v direct (first-level) dependencies exclusive to the origin repo\n", len(mine))
134+
for _, n := range mine {
135+
fmt.Printf(" - %s\n", n)
136+
}
137+
fmt.Println()
138+
139+
fmt.Printf("\"Ours\": %v shared dependencies between the origin repo and %v\n", len(ours), o.Dependencies)
140+
for _, n := range ours {
141+
fmt.Printf(" - %s\n", n)
142+
}
143+
144+
return nil
145+
}
146+
147+
func (o *AnalyzeOptions) calculateDependencies(g *graph.MutableDirectedGraph) ([]*graph.Node, []*graph.Node, []*graph.Node, error) {
148+
yoursRoots := []*graph.Node{}
149+
for _, dep := range o.Dependencies {
150+
n, exists := g.NodeByName(dep)
151+
if !exists {
152+
return nil, nil, nil, fmt.Errorf("unable to find dependency with import path %q", dep)
153+
}
154+
node, ok := n.(*graph.Node)
155+
if !ok {
156+
return nil, nil, nil, fmt.Errorf("expected node to analyze to be of type *graph.Node. Got: %v", n)
157+
}
158+
159+
yoursRoots = append(yoursRoots, node)
160+
}
161+
162+
yours := analyze.FindExclusiveDependencies(g, yoursRoots)
163+
164+
// calculate root repo packages, as well as their
165+
// immediate vendor package dependencies
166+
unfilteredMine := map[int]*graph.Node{}
167+
for _, n := range g.Nodes() {
168+
node, ok := n.(*graph.Node)
169+
if !ok {
170+
return nil, nil, nil, fmt.Errorf("expected node to analyze to be of type *graph.Node. Got: %v", n)
171+
}
172+
if isVendorPackage(node) {
173+
continue
174+
}
175+
176+
// obtain immediate vendor package deps from the current node
177+
// and aggregate those as well
178+
for _, v := range g.From(n) {
179+
if !isVendorPackage(v.(*graph.Node)) {
180+
continue
181+
}
182+
183+
unfilteredMine[v.ID()] = v.(*graph.Node)
184+
}
185+
}
186+
187+
mine := []*graph.Node{}
188+
ours := []*graph.Node{}
189+
for _, n := range unfilteredMine {
190+
// determine if the current origin node is reachable from any of the "yours" packages
191+
if isReachableFrom(g, yours, n) {
192+
ours = append(ours, n)
193+
continue
194+
}
195+
196+
mine = append(mine, n)
197+
}
198+
199+
return yours, mine, ours, nil
200+
}
201+
202+
// isVendorPackage receives a *graph.Node and
203+
// returns true if the given node's unique name is in the vendor path.
204+
func isVendorPackage(n *graph.Node) bool {
205+
if strings.Contains(n.UniqueName, "/vendor/") {
206+
return true
207+
}
208+
209+
return false
210+
}
211+
212+
// isReachableFrom receives a set of root nodes and determines
213+
// if a given destination node can be reached from any of them.
214+
func isReachableFrom(g *graph.MutableDirectedGraph, roots []*graph.Node, dest *graph.Node) bool {
215+
for _, root := range roots {
216+
// no negative edge weights, use Dijkstra
217+
paths := path.DijkstraFrom(root, g)
218+
if pathTo, _ := paths.To(dest); len(pathTo) > 0 {
219+
return true
220+
}
221+
}
222+
223+
return false
224+
}

0 commit comments

Comments
 (0)