-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathgraph_utils.ts
150 lines (135 loc) · 4.86 KB
/
graph_utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright 2023 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
import * as fs from 'fs';
import * as path from 'path';
export const DEPENDENCY_GRAPH = JSON.parse(
fs.readFileSync(path.join(__dirname, 'package_dependencies.json'), 'utf8'));
// This is a reverse dependencies graph. Each entry in the graph lists the
// packages that depend on it.
export const REVERSE_DEPENDENCY_GRAPH = transposeGraph(DEPENDENCY_GRAPH);
// Topologically sort the dependency tree and arrange
// steps in dependency order.
export const DEPENDENCY_ORDER = topologicalSort(DEPENDENCY_GRAPH);
export type Graph<V extends Iterable<string> = Iterable<string>> = {
[node: string]: V
}
/**
* Verify that an object is a valid graph.
*/
export function verifyGraph(graph: Graph) {
const nodes = new Set(Object.keys(graph));
for (const [node, edges] of Object.entries(graph)) {
for (const edge of edges) {
if (!nodes.has(edge)) {
throw new Error(
`Graph edge ${edge} of node ${node} not found in the graph`);
}
}
}
}
/**
* Transpose a directed graph i.e. reverse the direction of the edges.
*/
export function transposeGraph(graph: Graph) {
verifyGraph(graph);
const transposed: Graph<Set<string>> = {};
for (const [nodeName, connectedNodes] of Object.entries(graph)) {
for (const connectedNode of connectedNodes) {
if (!transposed[connectedNode]) {
transposed[connectedNode] = new Set();
}
if (!transposed[nodeName]) {
// Make sure the node itself ends up in the transposed graph.
transposed[nodeName] = new Set();
}
transposed[connectedNode].add(nodeName);
}
}
return transposed;
}
/**
* Topologically sort a directed acyclic graph.
*
* Returns a list of graph nodes such that, by following edges,
* you can only move forward in the list, not backward.
*/
export function topologicalSort(graph: Graph) {
// We can't use a standard sorting algorithm because
// often, two packages won't have any dependency relationship
// between each other, meaning they are incomparable.
verifyGraph(graph);
const sorted: string[] = [];
while (sorted.length < Object.keys(graph).length) {
// Find nodes not yet in 'sorted' that have edges
// only to nodes already in 'sorted'
const emptyNodes = Object.entries(graph)
.filter(([node, edges]) => {
if (sorted.includes(node)) {
return false;
}
for (const edge of edges) {
if (!sorted.includes(edge)) {
return false;
}
}
return true;
})
.map(([node, edges]) => node);
// If there are no such nodes, then the graph has a cycle.
if (emptyNodes.length === 0) {
throw new Error('Dependency graph has a cycle.');
}
for (let node of emptyNodes) {
sorted.push(node);
}
}
return sorted;
}
/**
* Find all subnodes in the subgraph generated by taking the transitive
* closure at `node`.
*/
export function findSubgraph(node: string, graph: Graph, subnodes = new Set()) {
const directSubnodes = graph[node];
if (directSubnodes) {
for (const directSubnode of directSubnodes) {
if (!subnodes.has(directSubnode)) {
subnodes.add(directSubnode);
findSubgraph(directSubnode, graph, subnodes);
}
}
}
return subnodes;
}
/**
* Find the transitive closure of dependencies of the given packages.
*/
export function findDeps(packages: Iterable<string>): Set<string> {
return new Set(
[...packages]
.map(packageName => findSubgraph(packageName, DEPENDENCY_GRAPH))
.reduce((a, b) => [...a, ...b], []));
}
/**
* Find the reverse dependencies of the given packages, i.e. find the
* set of packages that include at least one of the given packages in
* their transitive closure of dependencies.
*/
export function findReverseDeps(packages: Iterable<string>): Set<string> {
return new Set(
[...packages]
.map(packageName => findSubgraph(packageName, REVERSE_DEPENDENCY_GRAPH))
.reduce((a, b) => [...a, ...b], []));
}