From 7c0d2d1fad7bcbc78dadc347cf2968642477967f Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Sun, 11 Feb 2024 22:54:32 +0530 Subject: [PATCH] dependency graph may be cyclic Signed-off-by: Akash Kumar --- pkg/client/client.go | 90 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 86ed0a99..851e999d 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -1071,26 +1071,26 @@ func (c *KpmClient) ParseOciOptionFromString(oci string, tag string) (*opt.OciOp // PrintDependencyGraph will print the dependency graph of kcl package dependencies func (c *KpmClient) PrintDependencyGraph(kclPkg *pkg.KclPkg) error { - _, depGraph, err := c.downloadDeps(kclPkg.Dependencies, kclPkg.ModFile.Dependencies) + _, depGraph, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies) if err != nil { return err } - // add the root vertex(package name) to the dependency graph. - root := fmt.Sprint(kclPkg.GetPkgName()) - err = depGraph.AddVertex(root) + sources, err := FindSource(depGraph) if err != nil { return err } - sources, err := FindSource(depGraph) + // add the root vertex(package name) to the dependency graph. + root := fmt.Sprintf("%s@%s", kclPkg.GetPkgName(), kclPkg.GetPkgVersion()) + err = depGraph.AddVertex(root) if err != nil { return err } // make an edge between the root vertex and all the sources of the dependency graph. for _, source := range sources { - err = depGraph.AddEdge(source, root) + err = depGraph.AddEdge(root, source) if err != nil { return err } @@ -1105,7 +1105,7 @@ func (c *KpmClient) PrintDependencyGraph(kclPkg *pkg.KclPkg) error { err = graph.BFS(depGraph, root, func(source string) bool { for target := range adjMap[source] { reporter.ReportMsgTo( - fmt.Sprint(source, target), + fmt.Sprint(source, " ", target), c.logWriter, ) } @@ -1194,13 +1194,6 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie depGraph := graph.New(graph.StringHash, graph.Directed()) - for _, d := range newDeps.Deps { - err := depGraph.AddVertex(fmt.Sprintf("%s@%s", d.Name, d.Version)) - if err != nil { - return nil, nil, err - } - } - // Recursively download the dependencies of the new dependencies. for _, d := range newDeps.Deps { // Load kcl.mod file of the new downloaded dependencies. @@ -1223,12 +1216,19 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie } source := fmt.Sprintf("%s@%s", d.Name, d.Version) + err = depGraph.AddVertex(source) + if err != nil { + if err != graph.ErrVertexAlreadyExists { + return nil, nil, err + } + } + sourcesOfNestedDepGraph, err := FindSource(nestedDepGraph) if err != nil { return nil, nil, err } - depGraph, err = graph.Union(depGraph, nestedDepGraph) + depGraph, err = Union(depGraph, nestedDepGraph) if err != nil { return nil, nil, err } @@ -1236,7 +1236,7 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie // make an edge between the source of all nested dep graph and main dep graph for _, sourceOfNestedDepGraph := range sourcesOfNestedDepGraph { err = depGraph.AddEdge(source, sourceOfNestedDepGraph) - if err != nil { + if err != nil && err != graph.ErrEdgeAlreadyExists { return nil, nil, err } } @@ -1341,3 +1341,61 @@ func FindSource[K comparable, T any](g graph.Graph[K, T]) ([]K, error) { } return sources, nil } + +// Union combines two given graphs into a new graph. The vertex hashes in both +// graphs are expected to be unique. The two input graphs will remain unchanged. +// +// Both graphs should be either directed or undirected. All traits for the new +// graph will be derived from g. +// +// If the same vertex/edge happens to be in both g and h, then an error will not be +// thrown as happens in original Union function and successful operation takes place. +func Union[K comparable, T any](g, h graph.Graph[K, T]) (graph.Graph[K, T], error) { + union, err := g.Clone() + if err != nil { + return union, fmt.Errorf("failed to clone g: %w", err) + } + + adjacencyMap, err := h.AdjacencyMap() + if err != nil { + return union, fmt.Errorf("failed to get adjacency map: %w", err) + } + + addedEdges := make(map[K]map[K]struct{}) + + for currentHash := range adjacencyMap { + vertex, err := h.Vertex(currentHash) + if err != nil { + return union, fmt.Errorf("failed to get vertex %v: %w", currentHash, err) + } + + err = union.AddVertex(vertex) + if err != nil && err != graph.ErrVertexAlreadyExists { + return union, fmt.Errorf("failed to add vertex %v: %w", currentHash, err) + } + } + + for _, adjacencies := range adjacencyMap { + for _, edge := range adjacencies { + if _, sourceOK := addedEdges[edge.Source]; sourceOK { + if _, targetOK := addedEdges[edge.Source][edge.Target]; targetOK { + // If the edge addedEdges[source][target] exists, the edge + // has already been created and thus can be skipped here. + continue + } + } + + err = union.AddEdge(edge.Source, edge.Target) + if err != nil && err != graph.ErrEdgeAlreadyExists { + return union, fmt.Errorf("failed to add edge (%v, %v): %w", edge.Source, edge.Target, err) + } + + if _, ok := addedEdges[edge.Source]; !ok { + addedEdges[edge.Source] = make(map[K]struct{}) + } + addedEdges[edge.Source][edge.Target] = struct{}{} + } + } + + return union, nil +}