Skip to content

Commit 36f1593

Browse files
Add support for parsing go mod graph dumps
Signed-off-by: Arbaz Khan <arbazkhan971@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 058f439 commit 36f1593

12 files changed

Lines changed: 285 additions & 1 deletion

File tree

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The following organizations or individuals have contributed to ScanCode:
88
- Akanksha Garg @akugarg
99
- Alex Blekhman @a-tinsmith
1010
- Alexander Gschrei @agschrei
11+
- Arbaz Khan @arbazkhan971
1112
- Armijn Hemmel @armijnhemel
1213
- Armin Tänzer @armintaenzertng
1314
- Arnaud Jeansen @ajeans

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Changelog
44
Next release
55
--------------
66

7+
- Add support for parsing ``go mod graph`` dumps as ``go-mod-graph.deplock``
8+
and ``go.mod.graph`` package datafiles.
9+
https://github.com/aboutcode-org/scancode-toolkit/issues/4423
10+
711
- Fix the optional ``licenses`` extra dependency typo to install
812
``licensedcode-data``.
913
https://github.com/aboutcode-org/scancode-toolkit/pull/5056

docs/source/reference/scancode-supported-packages.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,13 @@ parsers in scancode-toolkit during documentation builds.
491491
- ``go_mod``
492492
- Go
493493
- https://go.dev/ref/mod
494+
* - Go module requirement graph
495+
- ``*/go-mod-graph.deplock``, ``*/go.mod.graph``
496+
- ``golang``
497+
- ``linux``, ``win``, ``mac``
498+
- ``go_mod_graph``
499+
- Go
500+
- https://go.dev/ref/mod#go-mod-graph
494501
* - Go module cheksums file
495502
- ``*/go.sum``
496503
- ``golang``

src/packagedcode/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
godeps.GodepsHandler,
103103
golang.GoModHandler,
104104
golang.GoSumHandler,
105+
golang.GoModGraphHandler,
105106

106107
haxe.HaxelibJsonHandler,
107108

src/packagedcode/go_mod.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,68 @@ def parse_gosum(location):
253253
gosums.append(dep)
254254

255255
return gosums
256+
257+
258+
def split_module_version(token):
259+
"""
260+
Return a GoModule parsed from a ``go mod graph`` token.
261+
262+
Each token is a module path, optionally followed by ``@`` and a version.
263+
The main module is typically printed without a version.
264+
265+
For example::
266+
267+
>>> m = split_module_version('example.com/my/thing')
268+
>>> assert m.namespace == 'example.com/my'
269+
>>> assert m.name == 'thing'
270+
>>> assert m.version is None
271+
>>> assert m.module == 'example.com/my/thing'
272+
273+
>>> m = split_module_version('github.com/davecgh/go-spew@v1.1.1')
274+
>>> assert m.namespace == 'github.com/davecgh'
275+
>>> assert m.name == 'go-spew'
276+
>>> assert m.version == 'v1.1.1'
277+
>>> assert m.module == 'github.com/davecgh/go-spew'
278+
"""
279+
if '@' in token:
280+
path, version = token.rsplit('@', 1)
281+
else:
282+
path, version = token, None
283+
namespace, _, name = path.rpartition('/')
284+
return GoModule(
285+
namespace=namespace or None,
286+
name=name,
287+
version=version,
288+
module=path,
289+
)
290+
291+
292+
def parse_gograph(location):
293+
"""
294+
Return a list of (requiring, required) GoModule pairs from a ``go mod graph``
295+
dump at ``location``.
296+
297+
See https://go.dev/ref/mod#go-mod-graph
298+
299+
Each line is two space-separated module versions: the requiring module,
300+
then the required module.
301+
302+
For example::
303+
304+
example.com/main example.com/m1@v1.0.0
305+
example.com/m1@v1.0.0 example.com/m2@v1.1.0
306+
"""
307+
edges = []
308+
with io.open(location, encoding='utf-8', closefd=True) as data:
309+
for raw_line in data:
310+
line = raw_line.strip()
311+
if not line or line.startswith('#'):
312+
continue
313+
parts = line.split()
314+
if len(parts) != 2:
315+
continue
316+
edges.append((
317+
split_module_version(parts[0]),
318+
split_module_version(parts[1]),
319+
))
320+
return edges

src/packagedcode/golang.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ def assemble(cls, package_data, resource, codebase, package_adder):
3333
Always use go.mod first then go.sum
3434
"""
3535
yield from cls.assemble_from_many_datafiles(
36-
datafile_name_patterns=('go.mod', 'go.sum',),
36+
datafile_name_patterns=(
37+
'go.mod',
38+
'go.sum',
39+
'go-mod-graph.deplock',
40+
'go.mod.graph',
41+
),
3742
directory=resource.parent(codebase),
3843
codebase=codebase,
3944
package_adder=package_adder,
@@ -134,3 +139,75 @@ def parse(cls, location, package_only=False):
134139
primary_language=cls.default_primary_language,
135140
)
136141
yield models.PackageData.from_data(package_data, package_only)
142+
143+
144+
class GoModGraphHandler(BaseGoModuleHandler):
145+
datasource_id = 'go_mod_graph'
146+
path_patterns = ('*/go-mod-graph.deplock', '*/go.mod.graph')
147+
default_package_type = 'golang'
148+
default_primary_language = 'Go'
149+
description = 'Go module requirement graph from go mod graph'
150+
documentation_url = 'https://go.dev/ref/mod#go-mod-graph'
151+
152+
@classmethod
153+
def parse(cls, location, package_only=False):
154+
"""
155+
Parse a ``go mod graph`` dump.
156+
157+
Direct dependencies are modules required by the main module (the first
158+
requiring module in the dump). Other required modules are transitive.
159+
Versions in the graph are exact selected versions.
160+
"""
161+
edges = go_mod.parse_gograph(location)
162+
if not edges:
163+
return
164+
165+
main = edges[0][0]
166+
direct_purls = {
167+
dst.purl(include_version=True)
168+
for src, dst in edges
169+
if src.module == main.module
170+
}
171+
172+
dependencies = []
173+
seen = set()
174+
for _src, dst in edges:
175+
purl = dst.purl(include_version=True)
176+
if purl in seen:
177+
continue
178+
seen.add(purl)
179+
dependencies.append(
180+
models.DependentPackage(
181+
purl=purl,
182+
extracted_requirement=dst.version,
183+
scope='require',
184+
is_runtime=True,
185+
is_optional=False,
186+
is_pinned=True,
187+
is_direct=purl in direct_purls,
188+
)
189+
)
190+
191+
namespace = main.namespace
192+
name = main.name
193+
homepage_url = None
194+
vcs_url = None
195+
repository_homepage_url = None
196+
if namespace and name:
197+
homepage_url = f'https://pkg.go.dev/{namespace}/{name}'
198+
vcs_url = f'https://{namespace}/{name}.git'
199+
repository_homepage_url = homepage_url
200+
201+
package_data = dict(
202+
datasource_id=cls.datasource_id,
203+
type=cls.default_package_type,
204+
name=name,
205+
namespace=namespace,
206+
version=main.version,
207+
vcs_url=vcs_url,
208+
homepage_url=homepage_url,
209+
repository_homepage_url=repository_homepage_url,
210+
dependencies=dependencies,
211+
primary_language=cls.default_primary_language,
212+
)
213+
yield models.PackageData.from_data(package_data, package_only)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
example.com/my/thing example.com/other/thing@v1.0.2
2+
example.com/my/thing example.com/new/thing@v2.3.4
3+
example.com/other/thing@v1.0.2 golang.org/x/text@v0.3.0
4+
example.com/new/thing@v2.3.4 github.com/davecgh/go-spew@v1.1.1
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
[
2+
{
3+
"type": "golang",
4+
"namespace": "example.com/my",
5+
"name": "thing",
6+
"version": null,
7+
"qualifiers": {},
8+
"subpath": null,
9+
"primary_language": "Go",
10+
"description": null,
11+
"release_date": null,
12+
"parties": [],
13+
"keywords": [],
14+
"homepage_url": "https://pkg.go.dev/example.com/my/thing",
15+
"download_url": null,
16+
"size": null,
17+
"sha1": null,
18+
"md5": null,
19+
"sha256": null,
20+
"sha512": null,
21+
"bug_tracking_url": null,
22+
"code_view_url": null,
23+
"vcs_url": "https://example.com/my/thing.git",
24+
"copyright": null,
25+
"holder": null,
26+
"declared_license_expression": null,
27+
"declared_license_expression_spdx": null,
28+
"license_detections": [],
29+
"other_license_expression": null,
30+
"other_license_expression_spdx": null,
31+
"other_license_detections": [],
32+
"extracted_license_statement": null,
33+
"notice_text": null,
34+
"source_packages": [],
35+
"file_references": [],
36+
"is_private": false,
37+
"is_virtual": false,
38+
"extra_data": {},
39+
"dependencies": [
40+
{
41+
"purl": "pkg:golang/example.com/other/thing@v1.0.2",
42+
"extracted_requirement": "v1.0.2",
43+
"scope": "require",
44+
"is_runtime": true,
45+
"is_optional": false,
46+
"is_pinned": true,
47+
"is_direct": true,
48+
"resolved_package": {},
49+
"extra_data": {}
50+
},
51+
{
52+
"purl": "pkg:golang/example.com/new/thing@v2.3.4",
53+
"extracted_requirement": "v2.3.4",
54+
"scope": "require",
55+
"is_runtime": true,
56+
"is_optional": false,
57+
"is_pinned": true,
58+
"is_direct": true,
59+
"resolved_package": {},
60+
"extra_data": {}
61+
},
62+
{
63+
"purl": "pkg:golang/golang.org/x/text@v0.3.0",
64+
"extracted_requirement": "v0.3.0",
65+
"scope": "require",
66+
"is_runtime": true,
67+
"is_optional": false,
68+
"is_pinned": true,
69+
"is_direct": false,
70+
"resolved_package": {},
71+
"extra_data": {}
72+
},
73+
{
74+
"purl": "pkg:golang/github.com/davecgh/go-spew@v1.1.1",
75+
"extracted_requirement": "v1.1.1",
76+
"scope": "require",
77+
"is_runtime": true,
78+
"is_optional": false,
79+
"is_pinned": true,
80+
"is_direct": false,
81+
"resolved_package": {},
82+
"extra_data": {}
83+
}
84+
],
85+
"repository_homepage_url": "https://pkg.go.dev/example.com/my/thing",
86+
"repository_download_url": null,
87+
"api_data_url": null,
88+
"datasource_id": "go_mod_graph",
89+
"purl": "pkg:golang/example.com/my/thing"
90+
}
91+
]

tests/packagedcode/data/plugin/help.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ Package type: golang
433433
description: Go modules file
434434
path_patterns: '*/go.mod'
435435
--------------------------------------------
436+
Package type: golang
437+
datasource_id: go_mod_graph
438+
documentation URL: https://go.dev/ref/mod#go-mod-graph
439+
primary language: Go
440+
description: Go module requirement graph from go mod graph
441+
path_patterns: '*/go-mod-graph.deplock', '*/go.mod.graph'
442+
--------------------------------------------
436443
Package type: golang
437444
datasource_id: go_sum
438445
documentation URL: https://go.dev/ref/mod#go-sum-files

tests/packagedcode/data/plugin/plugins_list_linux.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,13 @@ Package type: golang
454454
description: Go modules file
455455
path_patterns: '*/go.mod'
456456
--------------------------------------------
457+
Package type: golang
458+
datasource_id: go_mod_graph
459+
documentation URL: https://go.dev/ref/mod#go-mod-graph
460+
primary language: Go
461+
description: Go module requirement graph from go mod graph
462+
path_patterns: '*/go-mod-graph.deplock', '*/go.mod.graph'
463+
--------------------------------------------
457464
Package type: golang
458465
datasource_id: go_sum
459466
documentation URL: https://go.dev/ref/mod#go-sum-files

0 commit comments

Comments
 (0)