@@ -47,12 +47,76 @@ def logger_debug(*args):
47
47
return logger .debug (" " .join (isinstance (a , str ) and a or repr (a ) for a in args ))
48
48
49
49
50
+ class SwiftShowDependenciesDepLockHandler (models .DatafileHandler ):
51
+ datasource_id = "swift_package_show_dependencies"
52
+ path_patterns = ("*/swift-show-dependencies.deplock" ,)
53
+ default_package_type = "swift"
54
+ default_primary_language = "Swift"
55
+ description = "Swift dependency graph created by DepLock"
56
+ documentation_url = "https://forums.swift.org/t/swiftpm-show-dependencies-without-fetching-dependencies/51154"
57
+
58
+ @classmethod
59
+ def _parse (cls , swift_dependency_relation , package_only = False ):
60
+
61
+ if TRACE :
62
+ logger_debug (
63
+ f"SwiftShowDependenciesDepLockHandler: deplock: package: { swift_dependency_relation } "
64
+ )
65
+
66
+ dependencies = get_flatten_dependencies (
67
+ dependency_tree = swift_dependency_relation .get ("dependencies" )
68
+ )
69
+
70
+ package_data = dict (
71
+ datasource_id = cls .datasource_id ,
72
+ type = cls .default_package_type ,
73
+ primary_language = cls .default_primary_language ,
74
+ # ``namespace`` is derived from repo URL and same is not available in dependency graph
75
+ # See related issue: https://github.com/nexB/scancode-toolkit/issues/3793
76
+ name = swift_dependency_relation .get ("name" ),
77
+ dependencies = dependencies ,
78
+ )
79
+
80
+ return models .PackageData .from_data (package_data , package_only )
81
+
82
+ @classmethod
83
+ def parse (cls , location , package_only = False ):
84
+ with io .open (location , encoding = "utf-8" ) as loc :
85
+ swift_dependency_relation = json .load (loc )
86
+
87
+ yield cls ._parse (swift_dependency_relation , package_only )
88
+
89
+ @classmethod
90
+ def assemble (
91
+ cls , package_data , resource , codebase , package_adder = models .add_to_package
92
+ ):
93
+ siblings = resource .siblings (codebase )
94
+ swift_manifest_resource = [
95
+ r
96
+ for r in siblings
97
+ if r .name in ("Package.swift.json" , "Package.swift.deplock" )
98
+ ]
99
+
100
+ # Skip the assembly if the Swift manifest is present.
101
+ # SwiftManifestJsonHandler's assembly will take care of the
102
+ # dependencies from swift-show-dependencies.deplock file.
103
+ if swift_manifest_resource :
104
+ return []
105
+
106
+ yield from super (SwiftShowDependenciesDepLockHandler , cls ).assemble (
107
+ package_data = package_data ,
108
+ resource = resource ,
109
+ codebase = codebase ,
110
+ package_adder = package_adder ,
111
+ )
112
+
113
+
50
114
class SwiftManifestJsonHandler (models .DatafileHandler ):
51
115
datasource_id = "swift_package_manifest_json"
52
- path_patterns = ("*/Package.swift.json" ,)
116
+ path_patterns = ("*/Package.swift.json" , "*/Package.swift.deplock" )
53
117
default_package_type = "swift"
54
118
default_primary_language = "Swift"
55
- description = "JSON dump of Package.swift created with ``swift package dump-package > Package.swift.json``"
119
+ description = "JSON dump of Package.swift created by DepLock or with ``swift package dump-package > Package.swift.json``"
56
120
documentation_url = "https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html"
57
121
58
122
@classmethod
@@ -98,41 +162,64 @@ def assemble(
98
162
top-level package with resolved dependencies.
99
163
"""
100
164
siblings = resource .siblings (codebase )
165
+ processed_dependencies = []
101
166
swift_resolved_package_resource = [
102
167
r for r in siblings if r .name == "Package.resolved"
103
168
]
104
- dependencies_from_manifest = package_data .dependencies
105
169
106
- processed_dependencies = []
107
- if swift_resolved_package_resource :
108
- swift_resolved_package_resource = swift_resolved_package_resource [0 ]
109
- swift_resolved_package_data = swift_resolved_package_resource .package_data
170
+ swift_show_dependencies_resources = [
171
+ r for r in siblings if r .name == "swift-show-dependencies.deplock"
172
+ ]
110
173
111
- for package in swift_resolved_package_data :
112
- version = package .get ("version" )
113
- name = package .get ("name" )
174
+ if swift_show_dependencies_resources :
175
+ swift_show_dependencies_resource = swift_show_dependencies_resources [0 ]
176
+ swift_show_dependencies_package_data = (
177
+ swift_show_dependencies_resource .package_data
178
+ )
179
+
180
+ # Dependencies from `swift-show-dependencies.deplock` supersede dependencies from other datafiles.
181
+ processed_dependencies = swift_show_dependencies_package_data [0 ][
182
+ "dependencies"
183
+ ]
184
+ processed_dependencies = [
185
+ models .DependentPackage .from_dict (i ) for i in processed_dependencies
186
+ ]
187
+
188
+ # Use dependencies from `Package.resolved` when `swift-show-dependencies.deplock` is not present.
189
+ else :
190
+ dependencies_from_manifest = package_data .dependencies
114
191
115
- purl = PackageURL (
116
- type = cls .default_package_type , name = name , version = version
192
+ if swift_resolved_package_resource :
193
+ swift_resolved_package_resource = swift_resolved_package_resource [0 ]
194
+ swift_resolved_package_data = (
195
+ swift_resolved_package_resource .package_data
117
196
)
118
- processed_dependencies .append (
119
- models .DependentPackage (
120
- purl = purl .to_string (),
121
- scope = "dependencies" ,
122
- is_runtime = True ,
123
- is_optional = False ,
124
- is_resolved = True ,
125
- extracted_requirement = version ,
197
+
198
+ for package in swift_resolved_package_data :
199
+ version = package .get ("version" )
200
+ name = package .get ("name" )
201
+
202
+ purl = PackageURL (
203
+ type = cls .default_package_type , name = name , version = version
204
+ )
205
+ processed_dependencies .append (
206
+ models .DependentPackage (
207
+ purl = purl .to_string (),
208
+ scope = "dependencies" ,
209
+ is_runtime = True ,
210
+ is_optional = False ,
211
+ is_resolved = True ,
212
+ extracted_requirement = version ,
213
+ )
126
214
)
127
- )
128
215
129
- for dependency in dependencies_from_manifest [:]:
130
- dependency_purl = PackageURL .from_string (dependency .purl )
216
+ for dependency in dependencies_from_manifest [:]:
217
+ dependency_purl = PackageURL .from_string (dependency .purl )
131
218
132
- if dependency_purl .name == name :
133
- dependencies_from_manifest .remove (dependency )
219
+ if dependency_purl .name == name :
220
+ dependencies_from_manifest .remove (dependency )
134
221
135
- processed_dependencies .extend (dependencies_from_manifest )
222
+ processed_dependencies .extend (dependencies_from_manifest )
136
223
137
224
datafile_path = resource .path
138
225
if package_data .purl :
@@ -141,7 +228,12 @@ def assemble(
141
228
datafile_path = datafile_path ,
142
229
)
143
230
144
- if swift_resolved_package_resource :
231
+ if swift_show_dependencies_resources :
232
+ package .datafile_paths .append (swift_show_dependencies_resource .path )
233
+ package .datasource_ids .append (
234
+ SwiftShowDependenciesDepLockHandler .datasource_id
235
+ )
236
+ elif swift_resolved_package_resource :
145
237
package .datafile_paths .append (swift_resolved_package_resource .path )
146
238
package .datasource_ids .append (SwiftPackageResolvedHandler .datasource_id )
147
239
@@ -196,7 +288,9 @@ def assemble(
196
288
):
197
289
siblings = resource .siblings (codebase )
198
290
swift_manifest_resource = [
199
- r for r in siblings if r .name == "Package.swift.json"
291
+ r
292
+ for r in siblings
293
+ if r .name in ("Package.swift.json" , "Package.swift.deplock" )
200
294
]
201
295
202
296
# Skip the assembly if the ``Package.swift.json`` manifest is present.
@@ -328,3 +422,109 @@ def get_namespace_and_name(url):
328
422
canonical_name = hostname + path
329
423
330
424
return canonical_name .rsplit ("/" , 1 )
425
+
426
+
427
+ def get_flatten_dependencies (dependency_tree ):
428
+ """
429
+ Get the list of dependencies from the dependency graph where each
430
+ element is a DependentPackage containing its 1st order dependencies.
431
+ """
432
+ dependencies = []
433
+ transitive_dependencies = []
434
+
435
+ # process direct dependency
436
+ for dependency in dependency_tree :
437
+ transitives = dependency .get ("dependencies" , [])
438
+ transitive_dependencies .append (transitives )
439
+ parent_child_dep = get_dependent_package_from_subtree (
440
+ dependency = dependency ,
441
+ is_top_level_dependency = True ,
442
+ )
443
+ dependencies .append (parent_child_dep )
444
+
445
+ # process all transitive dependencies
446
+ while transitive_dependencies :
447
+ transitive_dependency_tree = transitive_dependencies .pop (0 )
448
+ if not transitive_dependency_tree :
449
+ continue
450
+
451
+ for transitive in transitive_dependency_tree :
452
+ dependencies_of_transitive_dependency = transitive .get ("dependencies" , [])
453
+ # add nested dependencies in transitive_dependencies queue for processing
454
+ transitive_dependencies .append (dependencies_of_transitive_dependency )
455
+
456
+ parent_child_dep = get_dependent_package_from_subtree (
457
+ dependency = transitive ,
458
+ is_top_level_dependency = False ,
459
+ )
460
+
461
+ dependencies .append (parent_child_dep )
462
+
463
+ return dependencies
464
+
465
+
466
+ def get_dependent_package_from_subtree (dependency , is_top_level_dependency ):
467
+ """
468
+ Get the DependentPackage for a ``dependency`` subtree along with its 1st
469
+ order dependencies. Set `is_direct` to True if the subtree is a direct
470
+ dependency for the top-level package.
471
+ """
472
+ dependencies_of_parent = []
473
+ repository_url = dependency .get ("url" )
474
+ version = dependency .get ("version" )
475
+ transitives = dependency .get ("dependencies" , [])
476
+ namespace , name = get_namespace_and_name (repository_url )
477
+ purl = PackageURL (
478
+ type = "swift" ,
479
+ namespace = namespace ,
480
+ name = name ,
481
+ version = version ,
482
+ )
483
+
484
+ for transitive in transitives :
485
+ transitive_repository_url = transitive .get ("url" )
486
+ transitive_version = transitive .get ("version" )
487
+ transitive_namespace , transitive_name = get_namespace_and_name (
488
+ transitive_repository_url
489
+ )
490
+ transitive_purl = PackageURL (
491
+ type = "swift" ,
492
+ namespace = transitive_namespace ,
493
+ name = transitive_name ,
494
+ version = transitive_version ,
495
+ )
496
+
497
+ child_dependency = models .DependentPackage (
498
+ purl = transitive_purl .to_string (),
499
+ scope = "dependencies" ,
500
+ extracted_requirement = transitive_version ,
501
+ is_runtime = False ,
502
+ is_optional = False ,
503
+ is_resolved = True ,
504
+ is_direct = True ,
505
+ ).to_dict ()
506
+
507
+ dependencies_of_parent .append (child_dependency )
508
+
509
+ parent_package_data_mapping = dict (
510
+ datasource_id = SwiftShowDependenciesDepLockHandler .datasource_id ,
511
+ type = SwiftShowDependenciesDepLockHandler .default_package_type ,
512
+ primary_language = SwiftShowDependenciesDepLockHandler .default_primary_language ,
513
+ namespace = namespace ,
514
+ name = name ,
515
+ version = version ,
516
+ dependencies = dependencies_of_parent ,
517
+ is_virtual = True ,
518
+ )
519
+ parent_dependency = models .PackageData .from_data (parent_package_data_mapping )
520
+
521
+ return models .DependentPackage (
522
+ purl = purl .to_string (),
523
+ scope = "dependencies" ,
524
+ extracted_requirement = version ,
525
+ is_runtime = False ,
526
+ is_optional = False ,
527
+ is_resolved = True ,
528
+ is_direct = is_top_level_dependency ,
529
+ resolved_package = parent_dependency ,
530
+ )
0 commit comments