@@ -45,6 +45,9 @@ type DependencyOverview struct {
45
45
func getDepInfo (mainModules []string ) * DependencyOverview {
46
46
// get output of "go mod graph" in a string
47
47
goModGraph := exec .Command ("go" , "mod" , "graph" )
48
+ if dir != "" {
49
+ goModGraph .Dir = dir
50
+ }
48
51
goModGraphOutput , err := goModGraph .Output ()
49
52
if err != nil {
50
53
log .Fatal (err )
@@ -112,38 +115,131 @@ func sliceContains(val []Chain, key Chain) bool {
112
115
return false
113
116
}
114
117
118
+ type module struct {
119
+ name string
120
+ version string
121
+ }
122
+
123
+ func parseModule (s string ) module {
124
+ if strings .Contains (s , "@" ) {
125
+ parts := strings .SplitN (s , "@" , 2 )
126
+ return module {name : parts [0 ], version : parts [1 ]}
127
+ }
128
+ return module {name : s }
129
+ }
130
+
115
131
func generateGraph (goModGraphOutputString string , mainModules []string ) DependencyOverview {
116
132
depGraph := DependencyOverview {MainModules : mainModules }
133
+ versionedGraph := make (map [module ][]module )
134
+ var lhss []module
117
135
graph := make (map [string ][]string )
118
136
scanner := bufio .NewScanner (strings .NewReader (goModGraphOutputString ))
119
137
138
+ var versionedMainModules []module
139
+ var seenVersionedMainModules = map [module ]bool {}
120
140
for scanner .Scan () {
121
141
line := scanner .Text ()
122
142
words := strings .Fields (line )
123
- // remove versions
124
- words [0 ] = (strings .Split (words [0 ], "@" ))[0 ]
125
- words [1 ] = (strings .Split (words [1 ], "@" ))[0 ]
126
143
127
- // we don't want to add the same dep again
128
- if ! contains (graph [words [0 ]], words [1 ]) {
129
- graph [words [0 ]] = append (graph [words [0 ]], words [1 ])
144
+ lhs := parseModule (words [0 ])
145
+ if len (versionedMainModules ) == 0 || contains (mainModules , lhs .name ) {
146
+ if ! seenVersionedMainModules [lhs ] {
147
+ // remember our root module and listed main modules
148
+ versionedMainModules = append (versionedMainModules , lhs )
149
+ seenVersionedMainModules [lhs ] = true
150
+ }
130
151
}
131
-
132
152
if len (depGraph .MainModules ) == 0 {
133
- depGraph .MainModules = append (depGraph .MainModules , words [0 ])
153
+ // record the first module we see as the main module by default
154
+ depGraph .MainModules = append (depGraph .MainModules , lhs .name )
134
155
}
156
+ rhs := parseModule (words [1 ])
157
+
158
+ // remember the order we observed lhs modules in
159
+ if len (versionedGraph [lhs ]) == 0 {
160
+ lhss = append (lhss , lhs )
161
+ }
162
+ // record this lhs -> rhs relationship
163
+ versionedGraph [lhs ] = append (versionedGraph [lhs ], rhs )
164
+ }
165
+
166
+ // record effective versions of modules required by our main modules
167
+ // in go1.17+, the main module records effective versions of all dependencies, even indirect ones
168
+ effectiveVersions := map [string ]string {}
169
+ for _ , mm := range versionedMainModules {
170
+ for _ , m := range versionedGraph [mm ] {
171
+ if effectiveVersions [m .name ] < m .version {
172
+ effectiveVersions [m .name ] = m .version
173
+ }
174
+ }
175
+ }
135
176
136
- // if the LHS is a mainModule
137
- // then RHS is a direct dep else transitive dep
138
- if contains (depGraph .MainModules , words [0 ]) && contains (depGraph .MainModules , words [1 ]) {
177
+ type edge struct {
178
+ from module
179
+ to module
180
+ }
181
+
182
+ // figure out which modules in the graph are reachable from the effective versions required by our main modules
183
+ reachableModules := map [string ]module {}
184
+ // start with our main modules
185
+ var toVisit []edge
186
+ for _ , m := range versionedMainModules {
187
+ toVisit = append (toVisit , edge {to : m })
188
+ }
189
+ for len (toVisit ) > 0 {
190
+ from := toVisit [0 ].from
191
+ v := toVisit [0 ].to
192
+ toVisit = toVisit [1 :]
193
+ if _ , reachable := reachableModules [v .name ]; reachable {
194
+ // already flagged as reachable
195
+ continue
196
+ }
197
+ // mark as reachable
198
+ reachableModules [v .name ] = from
199
+ if effectiveVersion , ok := effectiveVersions [v .name ]; ok && effectiveVersion > v .version {
200
+ // replace with the effective version if applicable
201
+ v .version = effectiveVersion
202
+ } else {
203
+ // set the effective version
204
+ effectiveVersions [v .name ] = v .version
205
+ }
206
+ // queue dependants of this to check for reachability
207
+ for _ , m := range versionedGraph [v ] {
208
+ toVisit = append (toVisit , edge {from : v , to : m })
209
+ }
210
+ }
211
+
212
+ for _ , lhs := range lhss {
213
+ if _ , reachable := reachableModules [lhs .name ]; ! reachable {
214
+ // this is not reachable via required versions, skip it
215
+ continue
216
+ }
217
+ if effectiveVersion , ok := effectiveVersions [lhs .name ]; ok && effectiveVersion != lhs .version {
218
+ // this is not the effective version in our graph, skip it
139
219
continue
140
- } else if contains (depGraph .MainModules , words [0 ]) {
141
- if ! contains (depGraph .DirectDepList , words [1 ]) {
142
- depGraph .DirectDepList = append (depGraph .DirectDepList , words [1 ])
220
+ }
221
+ // fmt.Println(lhs.name, "via", reachableModules[lhs.name])
222
+
223
+ for _ , rhs := range versionedGraph [lhs ] {
224
+ // we don't want to add the same dep again
225
+ if ! contains (graph [lhs .name ], rhs .name ) {
226
+ graph [lhs .name ] = append (graph [lhs .name ], rhs .name )
143
227
}
144
- } else if ! contains (depGraph .MainModules , words [0 ]) {
145
- if ! contains (depGraph .TransDepList , words [1 ]) {
146
- depGraph .TransDepList = append (depGraph .TransDepList , words [1 ])
228
+
229
+ // if the LHS is a mainModule
230
+ // then RHS is a direct dep else transitive dep
231
+ if contains (depGraph .MainModules , lhs .name ) && contains (depGraph .MainModules , rhs .name ) {
232
+ continue
233
+ } else if contains (depGraph .MainModules , lhs .name ) {
234
+ if ! contains (depGraph .DirectDepList , rhs .name ) {
235
+ // fmt.Println(rhs.name, "via", lhs)
236
+ depGraph .DirectDepList = append (depGraph .DirectDepList , rhs .name )
237
+ }
238
+ } else if ! contains (depGraph .MainModules , lhs .name ) {
239
+ if ! contains (depGraph .TransDepList , rhs .name ) {
240
+ // fmt.Println(rhs.name, "via", lhs)
241
+ depGraph .TransDepList = append (depGraph .TransDepList , rhs .name )
242
+ }
147
243
}
148
244
}
149
245
}
0 commit comments