7
7
# See https://aboutcode.org for more information about nexB OSS projects.
8
8
#
9
9
10
+ import os
10
11
import re
11
12
12
13
import saneyaml
@@ -31,8 +32,8 @@ class CargoTomlHandler(models.DatafileHandler):
31
32
@classmethod
32
33
def parse (cls , location ):
33
34
package_data = toml .load (location , _dict = dict )
34
-
35
35
core_package_data = package_data .get ('package' , {})
36
+ workspace = package_data .get ('workspace' , {})
36
37
37
38
name = core_package_data .get ('name' )
38
39
version = core_package_data .get ('version' )
@@ -66,6 +67,9 @@ def parse(cls, location):
66
67
repository_homepage_url = name and f'https://crates.io/crates/{ name } '
67
68
repository_download_url = name and version and f'https://crates.io/api/v1/crates/{ name } /{ version } /download'
68
69
api_data_url = name and f'https://crates.io/api/v1/crates/{ name } '
70
+ extra_data = {}
71
+ if workspace :
72
+ extra_data ["workspace" ] = workspace
69
73
70
74
yield models .PackageData (
71
75
datasource_id = cls .datasource_id ,
@@ -82,20 +86,92 @@ def parse(cls, location):
82
86
repository_download_url = repository_download_url ,
83
87
api_data_url = api_data_url ,
84
88
dependencies = dependencies ,
89
+ extra_data = extra_data ,
85
90
)
86
91
87
92
@classmethod
88
93
def assemble (cls , package_data , resource , codebase , package_adder ):
89
94
"""
90
- Assemble Cargo.toml and possible Cargo.lock datafiles
95
+ Assemble Cargo.toml and possible Cargo.lock datafiles. Also
96
+ support cargo workspaces where we have multiple packages from
97
+ a repository and some shared information present at top-level.
91
98
"""
92
- yield from cls .assemble_from_many_datafiles (
93
- datafile_name_patterns = ('Cargo.toml' , 'cargo.toml' , 'Cargo.lock' , 'cargo.lock' ),
94
- directory = resource .parent (codebase ),
95
- codebase = codebase ,
96
- package_adder = package_adder ,
97
- )
99
+ workspace = package_data .extra_data .get ("workspace" , {})
100
+ workspace_members = workspace .get ("members" , [])
101
+ workspace_package_data = workspace .get ("package" , {})
102
+ attributes_to_copy = [
103
+ "license_detections" ,
104
+ "declared_license_expression" ,
105
+ "declared_license_expression_spdx"
106
+ ]
107
+ if "license" in workspace_package_data :
108
+ for attribute in attributes_to_copy :
109
+ workspace_package_data [attribute ] = getattr (package_data , attribute )
110
+
111
+ workspace_root_path = resource .parent (codebase ).path
112
+ if workspace_package_data and workspace_members :
113
+ for workspace_member_path in workspace_members :
114
+ workspace_directory_path = os .path .join (workspace_root_path , workspace_member_path )
115
+ workspace_directory = codebase .get_resource (path = workspace_directory_path )
116
+ if not workspace_directory :
117
+ continue
118
+
119
+ # Update the package data for all members with the
120
+ # workspace package data
121
+ for resource in workspace_directory .children (codebase ):
122
+ if cls .is_datafile (location = resource .location ):
123
+ if not resource .package_data :
124
+ continue
125
+
126
+ updated_package_data = cls .update_resource_package_data (
127
+ package_data = workspace_package_data ,
128
+ old_package_data = resource .package_data .pop (),
129
+ mapping = CARGO_ATTRIBUTE_MAPPING ,
130
+ )
131
+ resource .package_data .append (updated_package_data )
132
+ resource .save (codebase )
133
+
134
+ yield from cls .assemble_from_many_datafiles (
135
+ datafile_name_patterns = ('Cargo.toml' , 'cargo.toml' , 'Cargo.lock' , 'cargo.lock' ),
136
+ directory = workspace_directory ,
137
+ codebase = codebase ,
138
+ package_adder = package_adder ,
139
+ )
140
+ else :
141
+ yield from cls .assemble_from_many_datafiles (
142
+ datafile_name_patterns = ('Cargo.toml' , 'cargo.toml' , 'Cargo.lock' , 'cargo.lock' ),
143
+ directory = resource .parent (codebase ),
144
+ codebase = codebase ,
145
+ package_adder = package_adder ,
146
+ )
98
147
148
+ @classmethod
149
+ def update_resource_package_data (cls , package_data , old_package_data , mapping = None ):
150
+
151
+ for attribute in old_package_data .keys ():
152
+ if attribute in mapping :
153
+ replace_by_attribute = mapping .get (attribute )
154
+ old_package_data [attribute ] = package_data .get (replace_by_attribute )
155
+ elif attribute == "parties" :
156
+ old_package_data [attribute ] = list (get_parties (
157
+ person_names = package_data .get ("authors" ),
158
+ party_role = 'author' ,
159
+ ))
160
+
161
+ return old_package_data
162
+
163
+
164
+ CARGO_ATTRIBUTE_MAPPING = {
165
+ # Fields in PackageData model: Fields in cargo
166
+ "homepage_url" : "homepage" ,
167
+ "vcs_url" : "repository" ,
168
+ "keywords" : "categories" ,
169
+ "extracted_license_statement" : "license" ,
170
+ # These are fields carried over to avoid re-detection of licenses
171
+ "license_detections" : "license_detections" ,
172
+ "declared_license_expression" : "declared_license_expression" ,
173
+ "declared_license_expression_spdx" : "declared_license_expression_spdx" ,
174
+ }
99
175
100
176
class CargoLockHandler (models .DatafileHandler ):
101
177
datasource_id = 'cargo_lock'
@@ -185,19 +261,21 @@ def dependency_mapper(dependencies, scope='dependencies'):
185
261
)
186
262
187
263
188
- def get_parties (person_names , party_role ):
264
+ def get_parties (person_names , party_role , debug = False ):
189
265
"""
190
266
Yields Party of `party_role` given a list of ``person_names`` strings.
191
267
https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field-optional
192
268
"""
269
+ if debug :
270
+ raise Exception (person_names )
193
271
for person_name in person_names :
194
272
name , email = parse_person (person_name )
195
273
yield models .Party (
196
274
type = models .party_person ,
197
275
name = name ,
198
276
role = party_role ,
199
277
email = email ,
200
- )
278
+ ). to_dict ()
201
279
202
280
203
281
person_parser = re .compile (
0 commit comments