Skip to content

Commit 43ed11e

Browse files
committedFeb 11, 2024
feat: add graph subcommand to print dependency graph
Signed-off-by: Akash Kumar <[email protected]>
1 parent 7ac7e82 commit 43ed11e

File tree

5 files changed

+188
-10
lines changed

5 files changed

+188
-10
lines changed
 

‎go.mod

+1
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

+2
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

+1
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

+116-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ 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"
@@ -567,7 +568,7 @@ func (c *KpmClient) AddDepToPkg(kclPkg *pkg.KclPkg, d *pkg.Dependency) error {
567568
}
568569

569570
// download all the dependencies.
570-
changedDeps, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
571+
changedDeps, _, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
571572

572573
if err != nil {
573574
return err
@@ -1068,6 +1069,59 @@ func (c *KpmClient) ParseOciOptionFromString(oci string, tag string) (*opt.OciOp
10681069
return ociOpt, nil
10691070
}
10701071

1072+
// PrintDependencyGraph will print the dependency graph of kcl package dependencies
1073+
func (c *KpmClient) PrintDependencyGraph(kclPkg *pkg.KclPkg) error {
1074+
// create the main graph with a single root vertex.
1075+
root := fmt.Sprint(kclPkg.GetPkgName())
1076+
mainGraph := graph.New(graph.StringHash, graph.Directed())
1077+
err := mainGraph.AddVertex(root)
1078+
if err != nil {
1079+
return err
1080+
}
1081+
1082+
// get the dependency graphs and merge them into the main graph at root vertex.
1083+
_, depGraphs, err := c.downloadDeps(kclPkg.Dependencies, kclPkg.ModFile.Dependencies)
1084+
if err != nil {
1085+
return err
1086+
}
1087+
1088+
for _, g := range depGraphs {
1089+
mainGraph, err = graph.Union(mainGraph, g)
1090+
if err != nil {
1091+
return err
1092+
}
1093+
src, err := FindSource(g)
1094+
if err != nil {
1095+
return err
1096+
}
1097+
err = mainGraph.AddEdge(root, src)
1098+
if err != nil {
1099+
return err
1100+
}
1101+
}
1102+
1103+
adjMap, err := mainGraph.AdjacencyMap()
1104+
if err != nil {
1105+
return err
1106+
}
1107+
1108+
// print the dependency graph to stdout.
1109+
err = graph.BFS(mainGraph, root, func(source string) bool {
1110+
for target := range adjMap[source] {
1111+
reporter.ReportMsgTo(
1112+
fmt.Sprint(source, target),
1113+
c.logWriter,
1114+
)
1115+
}
1116+
return false
1117+
})
1118+
if err != nil {
1119+
return err
1120+
}
1121+
1122+
return nil
1123+
}
1124+
10711125
// dependencyExists will check whether the dependency exists in the local filesystem.
10721126
func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependencies) *pkg.Dependency {
10731127

@@ -1092,15 +1146,24 @@ func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependen
10921146
}
10931147

10941148
// downloadDeps will download all the dependencies of the current kcl package.
1095-
func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencies) (*pkg.Dependencies, error) {
1149+
func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencies) (*pkg.Dependencies, []graph.Graph[string, string], error) {
10961150
newDeps := pkg.Dependencies{
10971151
Deps: make(map[string]pkg.Dependency),
10981152
}
10991153

1154+
depGraphs := make([]graph.Graph[string, string], len(deps.Deps))
1155+
i := 0
1156+
11001157
// Traverse all dependencies in kcl.mod
11011158
for _, d := range deps.Deps {
1159+
depGraphs[i] = graph.New(graph.StringHash, graph.Directed())
1160+
err := depGraphs[i].AddVertex(fmt.Sprintf("%s@%s", d.Name, d.Version))
1161+
if err != nil {
1162+
return nil, nil, err
1163+
}
1164+
i++
11021165
if len(d.Name) == 0 {
1103-
return nil, errors.InvalidDependency
1166+
return nil, nil, errors.InvalidDependency
11041167
}
11051168

11061169
existDep := c.dependencyExists(&d, &lockDeps)
@@ -1112,7 +1175,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11121175
expectedSum := lockDeps.Deps[d.Name].Sum
11131176
// Clean the cache
11141177
if len(c.homePath) == 0 || len(d.FullName) == 0 {
1115-
return nil, errors.InternalBug
1178+
return nil, nil, errors.InternalBug
11161179
}
11171180
dir := filepath.Join(c.homePath, d.FullName)
11181181
os.RemoveAll(dir)
@@ -1121,15 +1184,15 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11211184

11221185
lockedDep, err := c.Download(&d, dir)
11231186
if err != nil {
1124-
return nil, err
1187+
return nil, nil, err
11251188
}
11261189

11271190
if !lockedDep.IsFromLocal() {
11281191
if !c.noSumCheck && expectedSum != "" &&
11291192
lockedDep.Sum != expectedSum &&
11301193
existDep != nil &&
11311194
existDep.FullName == d.FullName {
1132-
return nil, reporter.NewErrorEvent(
1195+
return nil, nil, reporter.NewErrorEvent(
11331196
reporter.CheckSumMismatch,
11341197
errors.CheckSumMismatchError,
11351198
fmt.Sprintf("checksum for '%s' changed in lock file", lockedDep.Name),
@@ -1142,6 +1205,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11421205
lockDeps.Deps[d.Name] = *lockedDep
11431206
}
11441207

1208+
i = 0
11451209
// Recursively download the dependencies of the new dependencies.
11461210
for _, d := range newDeps.Deps {
11471211
// Load kcl.mod file of the new downloaded dependencies.
@@ -1154,13 +1218,34 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11541218
if os.IsNotExist(err) {
11551219
continue
11561220
}
1157-
return nil, err
1221+
return nil, nil, err
11581222
}
11591223

11601224
// Download the dependencies.
1161-
nested, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps)
1225+
nested, nestedDepGraphs, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps)
11621226
if err != nil {
1163-
return nil, err
1227+
return nil, nil, err
1228+
}
1229+
1230+
// merge the depGraph with the nestedDepGraphs.
1231+
src, err := FindSource(depGraphs[i])
1232+
if err != nil {
1233+
return nil, nil, err
1234+
}
1235+
1236+
for _, g := range nestedDepGraphs {
1237+
depGraphs[i], err = graph.Union(g, depGraphs[i])
1238+
if err != nil {
1239+
return nil, nil, err
1240+
}
1241+
srcOfNestedg, err := FindSource(g)
1242+
if err != nil {
1243+
return nil, nil, err
1244+
}
1245+
err = depGraphs[i].AddEdge(src, srcOfNestedg)
1246+
if err != nil {
1247+
return nil, nil, err
1248+
}
11641249
}
11651250

11661251
// Update kcl.mod.
@@ -1169,9 +1254,10 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
11691254
newDeps.Deps[d.Name] = d
11701255
}
11711256
}
1257+
i++
11721258
}
11731259

1174-
return &newDeps, nil
1260+
return &newDeps, depGraphs, nil
11751261
}
11761262

11771263
// pullTarFromOci will pull a kcl package tar file from oci registry.
@@ -1244,3 +1330,23 @@ func check(dep pkg.Dependency, newDepPath string) bool {
12441330

12451331
return dep.Sum == sum
12461332
}
1333+
1334+
func FindSource[K comparable, T any](g graph.Graph[K, T]) (K, error) {
1335+
var src K
1336+
if !g.Traits().IsDirected {
1337+
return src, fmt.Errorf("cannot find source of a non-DAG graph ")
1338+
}
1339+
1340+
predecessorMap, err := g.PredecessorMap()
1341+
if err != nil {
1342+
return src, fmt.Errorf("failed to get predecessor map: %w", err)
1343+
}
1344+
1345+
for vertex, predecessors := range predecessorMap {
1346+
if len(predecessors) == 0 {
1347+
src = vertex
1348+
break
1349+
}
1350+
}
1351+
return src, nil
1352+
}

‎pkg/cmd/cmd_graph.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2024 The KCL Authors. All rights reserved.
2+
3+
package cmd
4+
5+
import (
6+
"os"
7+
8+
"github.com/urfave/cli/v2"
9+
"kcl-lang.io/kpm/pkg/client"
10+
"kcl-lang.io/kpm/pkg/env"
11+
pkg "kcl-lang.io/kpm/pkg/package"
12+
"kcl-lang.io/kpm/pkg/reporter"
13+
)
14+
15+
// NewGraphCmd new a Command for `kpm graph`.
16+
func NewGraphCmd(kpmcli *client.KpmClient) *cli.Command {
17+
return &cli.Command{
18+
Hidden: false,
19+
Name: "graph",
20+
Usage: "prints the module dependency graph",
21+
Action: func(c *cli.Context) error {
22+
return KpmGraph(c, kpmcli)
23+
},
24+
}
25+
}
26+
27+
func KpmGraph(c *cli.Context, kpmcli *client.KpmClient) error {
28+
// acquire the lock of the package cache.
29+
err := kpmcli.AcquirePackageCacheLock()
30+
if err != nil {
31+
return err
32+
}
33+
34+
defer func() {
35+
// release the lock of the package cache after the function returns.
36+
releaseErr := kpmcli.ReleasePackageCacheLock()
37+
if releaseErr != nil && err == nil {
38+
err = releaseErr
39+
}
40+
}()
41+
42+
pwd, err := os.Getwd()
43+
44+
if err != nil {
45+
return reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.")
46+
}
47+
48+
globalPkgPath, err := env.GetAbsPkgPath()
49+
if err != nil {
50+
return err
51+
}
52+
53+
kclPkg, err := pkg.LoadKclPkg(pwd)
54+
if err != nil {
55+
return err
56+
}
57+
58+
err = kclPkg.ValidateKpmHome(globalPkgPath)
59+
if err != (*reporter.KpmEvent)(nil) {
60+
return err
61+
}
62+
63+
err = kpmcli.PrintDependencyGraph(kclPkg)
64+
if err != nil {
65+
return err
66+
}
67+
return nil
68+
}

0 commit comments

Comments
 (0)
Please sign in to comment.