Skip to content

Commit

Permalink
refactor: build dep-graph
Browse files Browse the repository at this point in the history
Build a Snyk dep-graph from a MavenGraph.

Implement graph pruning. Will be unused today as 'mvn dependency:tree'
already hides repeat branches, but if we ever want to move to -Dverbose
and parse larger graphs this will help prune away repeat dependency sub
graphs.

Test describes the modules behaviour.
  • Loading branch information
gitphill committed Aug 10, 2022
1 parent 8908d19 commit 2105f20
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 0 deletions.
55 changes: 55 additions & 0 deletions lib/parse/dep-graph.ts
@@ -0,0 +1,55 @@
import type { MavenGraph, MavenGraphNode } from './types';

import { DepGraph, DepGraphBuilder, PkgInfo } from '@snyk/dep-graph';
import { parseDependency } from './dependency';

export function buildDepGraph(mavenGraph: MavenGraph): DepGraph {
const { rootId, nodes } = mavenGraph;
const root = getPkgInfo(rootId);
const builder = new DepGraphBuilder({ name: 'maven' }, root);
const visited: string[] = [];
const queue: QueueItem[] = [];
queue.push(...getItems(rootId, nodes[rootId]));

// breadth first search
while (queue.length > 0) {
const item = queue.shift();
if (!item) continue;
const { id, parentId } = item;
const pkgInfo = getPkgInfo(id);
if (visited.includes(id)) {
const prunedId = id + ':pruned';
builder.addPkgNode(pkgInfo, prunedId, { labels: { pruned: 'true' } });
builder.connectDep(parentId, prunedId);
continue; // don't queue any more children
}
const parentNodeId = parentId === rootId ? builder.rootNodeId : parentId;
builder.addPkgNode(pkgInfo, id);
builder.connectDep(parentNodeId, id);
queue.push(...getItems(id, nodes[id]));
visited.push(id);
}

return builder.build();
}

interface QueueItem {
id: string;
parentId: string;
}

function getItems(parentId: string, node?: MavenGraphNode): QueueItem[] {
const items: QueueItem[] = [];
for (const id of node?.dependsOn || []) {
items.push({ id, parentId });
}
return items;
}

function getPkgInfo(value: string): PkgInfo {
const { groupId, artifactId, version } = parseDependency(value);
return {
name: `${groupId}:${artifactId}`,
version,
};
}
144 changes: 144 additions & 0 deletions tests/functional/parse/dep-graph.test.ts
@@ -0,0 +1,144 @@
import { test } from 'tap';
import { buildDepGraph } from '../../../lib/parse/dep-graph';

test('buildDepGraph', async (t) => {
const depGraph = buildDepGraph({
rootId: 'test:root:jar:1.2.3',
nodes: {
'test:root:jar:1.2.3': {
dependsOn: ['test:a:jar:1.0.0', 'test:c:jar:1.0.0', 'test:d:jar:1.0.0'],
},
'test:a:jar:1.0.0': {
dependsOn: ['test:b:jar:1.0.0'],
},
'test:b:jar:1.0.0': {
dependsOn: ['test:a:jar:1.0.0'], // pruned (cyclic)
},
'test:c:jar:1.0.0': {
dependsOn: ['test:d:jar:1.0.0'], // pruned (first seen at top level)
},
'test:d:jar:1.0.0': {
dependsOn: [],
},
},
});
t.same(
depGraph.toJSON(),
{
schemaVersion: '1.2.0',
pkgManager: {
name: 'maven',
},
pkgs: [
{
id: 'test:root@1.2.3',
info: {
name: 'test:root',
version: '1.2.3',
},
},
{
id: 'test:a@1.0.0',
info: {
name: 'test:a',
version: '1.0.0',
},
},
{
id: 'test:c@1.0.0',
info: {
name: 'test:c',
version: '1.0.0',
},
},
{
id: 'test:d@1.0.0',
info: {
name: 'test:d',
version: '1.0.0',
},
},
{
id: 'test:b@1.0.0',
info: {
name: 'test:b',
version: '1.0.0',
},
},
],
graph: {
rootNodeId: 'root-node',
nodes: [
{
nodeId: 'root-node',
pkgId: 'test:root@1.2.3',
deps: [
{
nodeId: 'test:a:jar:1.0.0',
},
{
nodeId: 'test:c:jar:1.0.0',
},
{
nodeId: 'test:d:jar:1.0.0',
},
],
},
{
nodeId: 'test:a:jar:1.0.0',
pkgId: 'test:a@1.0.0',
deps: [
{
nodeId: 'test:b:jar:1.0.0',
},
],
},
{
nodeId: 'test:c:jar:1.0.0',
pkgId: 'test:c@1.0.0',
deps: [
{
nodeId: 'test:d:jar:1.0.0:pruned',
},
],
},
{
nodeId: 'test:d:jar:1.0.0',
pkgId: 'test:d@1.0.0',
deps: [],
},
{
nodeId: 'test:b:jar:1.0.0',
pkgId: 'test:b@1.0.0',
deps: [
{
nodeId: 'test:a:jar:1.0.0:pruned',
},
],
},
{
nodeId: 'test:d:jar:1.0.0:pruned',
pkgId: 'test:d@1.0.0',
deps: [],
info: {
labels: {
pruned: 'true',
},
},
},
{
nodeId: 'test:a:jar:1.0.0:pruned',
pkgId: 'test:a@1.0.0',
deps: [],
info: {
labels: {
pruned: 'true',
},
},
},
],
},
},
'contains expected dep-graph',
);
});

0 comments on commit 2105f20

Please sign in to comment.