Skip to content

Commit

Permalink
feat: add graph subcommand to print dependency graph
Browse files Browse the repository at this point in the history
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
  • Loading branch information
AkashKumar7902 committed Feb 11, 2024
1 parent 7ac7e82 commit 114c5cf
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 10 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dominikbraun/graph v0.23.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.10.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHz
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
Expand Down
1 change: 1 addition & 0 deletions kpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func main() {
app.UsageText = "kpm <command> [arguments]..."
app.Commands = []*cli.Command{
cmd.NewInitCmd(kpmcli),
// cmd.NewGraphCmd(kpmcli),
cmd.NewAddCmd(kpmcli),
cmd.NewPkgCmd(kpmcli),
cmd.NewMetadataCmd(kpmcli),
Expand Down
107 changes: 97 additions & 10 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"reflect"
"strings"

"github.com/dominikbraun/graph"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/otiai10/copy"
"kcl-lang.io/kcl-go/pkg/kcl"
Expand Down Expand Up @@ -567,7 +568,7 @@ func (c *KpmClient) AddDepToPkg(kclPkg *pkg.KclPkg, d *pkg.Dependency) error {
}

// download all the dependencies.
changedDeps, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
changedDeps, _, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)

if err != nil {
return err
Expand Down Expand Up @@ -1068,6 +1069,40 @@ func (c *KpmClient) ParseOciOptionFromString(oci string, tag string) (*opt.OciOp
return ociOpt, nil
}

// PrintDependencyGraph will print the dependency graph of kcl package dependencies
func (c *KpmClient) PrintDependencyGraph(kclPkg *pkg.KclPkg) error {
// create the main graph with a single root vertex.
root := fmt.Sprint(kclPkg.GetPkgName())
mainGraph := graph.New(graph.StringHash, graph.Directed())
err := mainGraph.AddVertex(root)
if err != nil {
return err
}

// get the dependency graphs and merge them into the main graph at root vertex.
_, depGraphs, err := c.downloadDeps(kclPkg.Dependencies, kclPkg.ModFile.Dependencies)
if err != nil {
return err
}

for _, g := range depGraphs {
mainGraph, err = graph.Union(mainGraph, g)
if err != nil {
return err
}
src, err := FindSource(g)
if err != nil {
return err
}
err = mainGraph.AddEdge(root, src)
if err != nil {
return err
}
}

return nil
}

// dependencyExists will check whether the dependency exists in the local filesystem.
func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependencies) *pkg.Dependency {

Expand All @@ -1092,15 +1127,24 @@ func (c *KpmClient) dependencyExists(dep *pkg.Dependency, lockDeps *pkg.Dependen
}

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

depGraphs := make([]graph.Graph[string, string], len(deps.Deps))
i := 0

// Traverse all dependencies in kcl.mod
for _, d := range deps.Deps {
depGraphs[i] = graph.New(graph.StringHash, graph.Directed())
err := depGraphs[i].AddVertex(fmt.Sprintf("%s@%s", d.Name, d.Version))
if err != nil {
return nil, nil, err
}
i++
if len(d.Name) == 0 {
return nil, errors.InvalidDependency
return nil, nil, errors.InvalidDependency
}

existDep := c.dependencyExists(&d, &lockDeps)
Expand All @@ -1112,7 +1156,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
expectedSum := lockDeps.Deps[d.Name].Sum
// Clean the cache
if len(c.homePath) == 0 || len(d.FullName) == 0 {
return nil, errors.InternalBug
return nil, nil, errors.InternalBug
}
dir := filepath.Join(c.homePath, d.FullName)
os.RemoveAll(dir)
Expand All @@ -1121,15 +1165,15 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie

lockedDep, err := c.Download(&d, dir)
if err != nil {
return nil, err
return nil, nil, err
}

if !lockedDep.IsFromLocal() {
if !c.noSumCheck && expectedSum != "" &&
lockedDep.Sum != expectedSum &&
existDep != nil &&
existDep.FullName == d.FullName {
return nil, reporter.NewErrorEvent(
return nil, nil, reporter.NewErrorEvent(
reporter.CheckSumMismatch,
errors.CheckSumMismatchError,
fmt.Sprintf("checksum for '%s' changed in lock file", lockedDep.Name),
Expand All @@ -1142,6 +1186,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
lockDeps.Deps[d.Name] = *lockedDep
}

i = 0
// Recursively download the dependencies of the new dependencies.
for _, d := range newDeps.Deps {
// Load kcl.mod file of the new downloaded dependencies.
Expand All @@ -1154,13 +1199,34 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
if os.IsNotExist(err) {
continue
}
return nil, err
return nil, nil, err
}

// Download the dependencies.
nested, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps)
nested, nestedDepGraphs, err := c.downloadDeps(deppkg.ModFile.Dependencies, lockDeps)
if err != nil {
return nil, err
return nil, nil, err
}

// merge the depGraph with the nestedDepGraphs.
src, err := FindSource(depGraphs[i])
if err != nil {
return nil, nil, err
}

for _, g := range nestedDepGraphs {
depGraphs[i], err = graph.Union(g, depGraphs[i])
if err != nil {
return nil, nil, err
}
srcOfNestedg, err := FindSource(g)
if err != nil {
return nil, nil, err
}
err = depGraphs[i].AddEdge(src, srcOfNestedg)
if err != nil {
return nil, nil, err
}
}

// Update kcl.mod.
Expand All @@ -1169,9 +1235,10 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
newDeps.Deps[d.Name] = d
}
}
i++
}

return &newDeps, nil
return &newDeps, depGraphs, nil
}

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

return dep.Sum == sum
}

func FindSource[K comparable, T any](g graph.Graph[K, T]) (K, error) {
var src K
if !g.Traits().IsDirected {
return src, fmt.Errorf("cannot find source of a non-DAG graph ")
}

predecessorMap, err := g.PredecessorMap()
if err != nil {
return src, fmt.Errorf("failed to get predecessor map: %w", err)
}

for vertex, predecessors := range predecessorMap {
if len(predecessors) == 0 {
src = vertex
break
}
}
return src, nil
}
68 changes: 68 additions & 0 deletions pkg/cmd/cmd_graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2024 The KCL Authors. All rights reserved.

package cmd

import (
"os"

"github.com/urfave/cli/v2"
"kcl-lang.io/kpm/pkg/client"
"kcl-lang.io/kpm/pkg/env"
pkg "kcl-lang.io/kpm/pkg/package"
"kcl-lang.io/kpm/pkg/reporter"
)

// NewGraphCmd new a Command for `kpm graph`.
func NewGraphCmd(kpmcli *client.KpmClient) *cli.Command {
return &cli.Command{
Hidden: false,
Name: "graph",
Usage: "prints the module dependency graph",
Action: func(c *cli.Context) error {
return KpmGraph(c, kpmcli)
},
}
}

func KpmGraph(c *cli.Context, kpmcli *client.KpmClient) error {
// acquire the lock of the package cache.
err := kpmcli.AcquirePackageCacheLock()
if err != nil {
return err
}

defer func() {
// release the lock of the package cache after the function returns.
releaseErr := kpmcli.ReleasePackageCacheLock()
if releaseErr != nil && err == nil {
err = releaseErr
}
}()

pwd, err := os.Getwd()

if err != nil {
return reporter.NewErrorEvent(reporter.Bug, err, "internal bugs, please contact us to fix it.")
}

globalPkgPath, err := env.GetAbsPkgPath()
if err != nil {
return err
}

kclPkg, err := pkg.LoadKclPkg(pwd)
if err != nil {
return err
}

err = kclPkg.ValidateKpmHome(globalPkgPath)
if err != (*reporter.KpmEvent)(nil) {
return err
}

err = kpmcli.PrintDependencyGraph(kclPkg)
if err != nil {
return err
}
return nil
}

0 comments on commit 114c5cf

Please sign in to comment.