Skip to content

Commit 0224b99

Browse files
authored
Merge pull request #264 from AkashKumar7902/dependency-graph
feat: add graph subcommand to print dependency graph
2 parents f67db1b + 6fcfb5c commit 0224b99

File tree

9 files changed

+295
-10
lines changed

9 files changed

+295
-10
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ require (
3535
github.com/docker/go-connections v0.4.0 // indirect
3636
github.com/docker/go-metrics v0.0.1 // indirect
3737
github.com/docker/go-units v0.5.0 // indirect
38+
github.com/dominikbraun/graph v0.23.0 // indirect
3839
github.com/emirpasic/gods v1.18.1 // indirect
3940
github.com/fatih/color v1.10.0 // indirect
4041
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz
113113
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
114114
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
115115
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
116+
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
117+
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
116118
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
117119
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
118120
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=

kpm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func main() {
2525
app.UsageText = "kpm <command> [arguments]..."
2626
app.Commands = []*cli.Command{
2727
cmd.NewInitCmd(kpmcli),
28+
cmd.NewGraphCmd(kpmcli),
2829
cmd.NewAddCmd(kpmcli),
2930
cmd.NewPkgCmd(kpmcli),
3031
cmd.NewMetadataCmd(kpmcli),

pkg/client/client.go

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import (
99
"reflect"
1010
"strings"
1111

12+
"github.com/dominikbraun/graph"
1213
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1314
"github.com/otiai10/copy"
1415
"kcl-lang.io/kcl-go/pkg/kcl"
1516
"kcl-lang.io/kpm/pkg/constants"
1617
"kcl-lang.io/kpm/pkg/env"
1718
"kcl-lang.io/kpm/pkg/errors"
1819
"kcl-lang.io/kpm/pkg/git"
20+
pkgGraph "kcl-lang.io/kpm/pkg/graph"
1921
"kcl-lang.io/kpm/pkg/oci"
2022
"kcl-lang.io/kpm/pkg/opt"
2123
pkg "kcl-lang.io/kpm/pkg/package"
@@ -594,7 +596,7 @@ func (c *KpmClient) AddDepToPkg(kclPkg *pkg.KclPkg, d *pkg.Dependency) error {
594596
}
595597

596598
// download all the dependencies.
597-
changedDeps, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
599+
changedDeps, _, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
598600

599601
if err != nil {
600602
return err
@@ -1095,6 +1097,36 @@ func (c *KpmClient) ParseOciOptionFromString(oci string, tag string) (*opt.OciOp
10951097
return ociOpt, nil
10961098
}
10971099

1100+
// GetDependencyGraph will get the dependency graph of kcl package dependencies
1101+
func (c *KpmClient) GetDependencyGraph(kclPkg *pkg.KclPkg) (graph.Graph[string, string], error) {
1102+
_, depGraph, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
1103+
if err != nil {
1104+
return nil, err
1105+
}
1106+
1107+
sources, err := pkgGraph.FindSources(depGraph)
1108+
if err != nil {
1109+
return nil, err
1110+
}
1111+
1112+
// add the root vertex(package name) to the dependency graph.
1113+
root := fmt.Sprintf("%s@%s", kclPkg.GetPkgName(), kclPkg.GetPkgVersion())
1114+
err = depGraph.AddVertex(root)
1115+
if err != nil {
1116+
return nil, err
1117+
}
1118+
1119+
// make an edge between the root vertex and all the sources of the dependency graph.
1120+
for _, source := range sources {
1121+
err = depGraph.AddEdge(root, source)
1122+
if err != nil {
1123+
return nil, err
1124+
}
1125+
}
1126+
1127+
return depGraph, nil
1128+
}
1129+
10981130
// dependencyExists will check whether the dependency exists in the local filesystem.
10991131
func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependencies) *pkg.Dependency {
11001132

@@ -1119,15 +1151,15 @@ func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependen
11191151
}
11201152

11211153
// downloadDeps will download all the dependencies of the current kcl package.
1122-
func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencies) (*pkg.Dependencies, error) {
1154+
func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencies) (*pkg.Dependencies, graph.Graph[string, string], error) {
11231155
newDeps := pkg.Dependencies{
11241156
Deps: make(map[string]pkg.Dependency),
11251157
}
11261158

11271159
// Traverse all dependencies in kcl.mod
11281160
for _, d := range deps.Deps {
11291161
if len(d.Name) == 0 {
1130-
return nil, errors.InvalidDependency
1162+
return nil, nil, errors.InvalidDependency
11311163
}
11321164

11331165
existDep := c.dependencyExists(&d, &lockDeps)
@@ -1139,7 +1171,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11391171
expectedSum := lockDeps.Deps[d.Name].Sum
11401172
// Clean the cache
11411173
if len(c.homePath) == 0 || len(d.FullName) == 0 {
1142-
return nil, errors.InternalBug
1174+
return nil, nil, errors.InternalBug
11431175
}
11441176
dir := filepath.Join(c.homePath, d.FullName)
11451177
os.RemoveAll(dir)
@@ -1148,15 +1180,15 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11481180

11491181
lockedDep, err := c.Download(&d, dir)
11501182
if err != nil {
1151-
return nil, err
1183+
return nil, nil, err
11521184
}
11531185

11541186
if !lockedDep.IsFromLocal() {
11551187
if !c.noSumCheck && expectedSum != "" &&
11561188
lockedDep.Sum != expectedSum &&
11571189
existDep != nil &&
11581190
existDep.FullName == d.FullName {
1159-
return nil, reporter.NewErrorEvent(
1191+
return nil, nil, reporter.NewErrorEvent(
11601192
reporter.CheckSumMismatch,
11611193
errors.CheckSumMismatchError,
11621194
fmt.Sprintf("checksum for '%s' changed in lock file", lockedDep.Name),
@@ -1169,6 +1201,8 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11691201
lockDeps.Deps[d.Name] = *lockedDep
11701202
}
11711203

1204+
depGraph := graph.New(graph.StringHash, graph.Directed())
1205+
11721206
// Recursively download the dependencies of the new dependencies.
11731207
for _, d := range newDeps.Deps {
11741208
// Load kcl.mod file of the new downloaded dependencies.
@@ -1181,13 +1215,37 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11811215
if os.IsNotExist(err) {
11821216
continue
11831217
}
1184-
return nil, err
1218+
return nil, nil, err
11851219
}
11861220

11871221
// Download the dependencies.
1188-
nested, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps)
1222+
nested, nestedDepGraph, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps)
11891223
if err != nil {
1190-
return nil, err
1224+
return nil, nil, err
1225+
}
1226+
1227+
source := fmt.Sprintf("%s@%s", d.Name, d.Version)
1228+
err = depGraph.AddVertex(source)
1229+
if err != nil && err != graph.ErrVertexAlreadyExists {
1230+
return nil, nil, err
1231+
}
1232+
1233+
sourcesOfNestedDepGraph, err := pkgGraph.FindSources(nestedDepGraph)
1234+
if err != nil {
1235+
return nil, nil, err
1236+
}
1237+
1238+
depGraph, err = pkgGraph.Union(depGraph, nestedDepGraph)
1239+
if err != nil {
1240+
return nil, nil, err
1241+
}
1242+
1243+
// make an edge between the source of all nested dep graph and main dep graph
1244+
for _, sourceOfNestedDepGraph := range sourcesOfNestedDepGraph {
1245+
err = depGraph.AddEdge(source, sourceOfNestedDepGraph)
1246+
if err != nil && err != graph.ErrEdgeAlreadyExists {
1247+
return nil, nil, err
1248+
}
11911249
}
11921250

11931251
// Update kcl.mod.
@@ -1198,7 +1256,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11981256
}
11991257
}
12001258

1201-
return &newDeps, nil
1259+
return &newDeps, depGraph, nil
12021260
}
12031261

12041262
// pullTarFromOci will pull a kcl package tar file from oci registry.

pkg/client/client_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"testing"
1414

15+
"github.com/dominikbraun/graph"
1516
"github.com/otiai10/copy"
1617
"github.com/stretchr/testify/assert"
1718
"kcl-lang.io/kpm/pkg/env"
@@ -117,6 +118,45 @@ func TestDownloadLatestOci(t *testing.T) {
117118
assert.Equal(t, err, nil)
118119
}
119120

121+
func TestDependencyGraph(t *testing.T) {
122+
testDir := getTestDir("test_dependency_graph")
123+
assert.Equal(t, utils.DirExists(filepath.Join(testDir, "kcl.mod.lock")), false)
124+
kpmcli, err := NewKpmClient()
125+
assert.Equal(t, err, nil)
126+
kclPkg, err := kpmcli.LoadPkgFromPath(testDir)
127+
assert.Equal(t, err, nil)
128+
129+
depGraph, err := kpmcli.GetDependencyGraph(kclPkg)
130+
assert.Equal(t, err, nil)
131+
adjMap, err := depGraph.AdjacencyMap()
132+
assert.Equal(t, err, nil)
133+
134+
edgeProp := graph.EdgeProperties{
135+
Attributes: map[string]string{},
136+
Weight: 0,
137+
Data: nil,
138+
}
139+
assert.Equal(t, adjMap,
140+
map[string]map[string]graph.Edge[string]{
141+
"dependency_graph@0.0.1": {
142+
"teleport@0.1.0": {Source: "dependency_graph@0.0.1", Target: "teleport@0.1.0", Properties: edgeProp},
143+
"rabbitmq@0.0.1": {Source: "dependency_graph@0.0.1", Target: "rabbitmq@0.0.1", Properties: edgeProp},
144+
"agent@0.1.0": {Source: "dependency_graph@0.0.1", Target: "agent@0.1.0", Properties: edgeProp},
145+
},
146+
"teleport@0.1.0": {
147+
"k8s@1.28": {Source: "teleport@0.1.0", Target: "k8s@1.28", Properties: edgeProp},
148+
},
149+
"rabbitmq@0.0.1": {
150+
"k8s@1.28": {Source: "rabbitmq@0.0.1", Target: "k8s@1.28", Properties: edgeProp},
151+
},
152+
"agent@0.1.0": {
153+
"k8s@1.28": {Source: "agent@0.1.0", Target: "k8s@1.28", Properties: edgeProp},
154+
},
155+
"k8s@1.28": {},
156+
},
157+
)
158+
}
159+
120160
func TestInitEmptyPkg(t *testing.T) {
121161
testDir := initTestDir("test_init_empty_mod")
122162
kclPkg := pkg.NewKclPkg(&opt.InitOptions{Name: "test_name", InitPath: testDir})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "dependency_graph"
3+
edition = "0.0.1"
4+
version = "0.0.1"
5+
6+
[dependencies]
7+
rabbitmq = "0.0.1"
8+
teleport = "0.1.0"
9+
agent = "0.1.0"

pkg/cmd/cmd_graph.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2024 The KCL Authors. All rights reserved.
2+
3+
package cmd
4+
5+
import (
6+
"fmt"
7+
"os"
8+
9+
"github.com/dominikbraun/graph"
10+
"github.com/urfave/cli/v2"
11+
"kcl-lang.io/kpm/pkg/client"
12+
"kcl-lang.io/kpm/pkg/env"
13+
pkg "kcl-lang.io/kpm/pkg/package"
14+
"kcl-lang.io/kpm/pkg/reporter"
15+
)
16+
17+
// NewGraphCmd new a Command for `kpm graph`.
18+
func NewGraphCmd(kpmcli *client.KpmClient) *cli.Command {
19+
return &cli.Command{
20+
Hidden: false,
21+
Name: "graph",
22+
Usage: "prints the module dependency graph",
23+
Action: func(c *cli.Context) error {
24+
return KpmGraph(c, kpmcli)
25+
},
26+
}
27+
}
28+
29+
func KpmGraph(c *cli.Context, kpmcli *client.KpmClient) error {
30+
// acquire the lock of the package cache.
31+
err := kpmcli.AcquirePackageCacheLock()
32+
if err != nil {
33+
return err
34+
}
35+
36+
defer func() {
37+
// release the lock of the package cache after the function returns.
38+
releaseErr := kpmcli.ReleasePackageCacheLock()
39+
if releaseErr != nil && err == nil {
40+
err = releaseErr
41+
}
42+
}()
43+
44+
pwd, err := os.Getwd()
45+
46+
if err != nil {
47+
return reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.")
48+
}
49+
50+
globalPkgPath, err := env.GetAbsPkgPath()
51+
if err != nil {
52+
return err
53+
}
54+
55+
kclPkg, err := pkg.LoadKclPkg(pwd)
56+
if err != nil {
57+
return err
58+
}
59+
60+
err = kclPkg.ValidateKpmHome(globalPkgPath)
61+
if err != (*reporter.KpmEvent)(nil) {
62+
return err
63+
}
64+
65+
depGraph, err := kpmcli.GetDependencyGraph(kclPkg)
66+
if err != nil {
67+
return err
68+
}
69+
70+
adjMap, err := depGraph.AdjacencyMap()
71+
if err != nil {
72+
return err
73+
}
74+
75+
// print the dependency graph to stdout.
76+
root := fmt.Sprintf("%s@%s", kclPkg.GetPkgName(), kclPkg.GetPkgVersion())
77+
err = graph.BFS(depGraph, root, func(source string) bool {
78+
for target := range adjMap[source] {
79+
reporter.ReportMsgTo(
80+
fmt.Sprint(source, " ", target),
81+
kpmcli.GetLogWriter(),
82+
)
83+
}
84+
return false
85+
})
86+
if err != nil {
87+
return err
88+
}
89+
return nil
90+
}

0 commit comments

Comments
 (0)