77# See https://aboutcode.org for more information about nexB OSS projects.
88#
99
10+ import os
1011import re
1112
1213import saneyaml
2021"""
2122
2223
23- class CargoTomlHandler (models .DatafileHandler ):
24+ class CargoBaseHandler (models .DatafileHandler ):
25+ @classmethod
26+ def assemble (cls , package_data , resource , codebase , package_adder ):
27+ """
28+ Assemble Cargo.toml and possible Cargo.lock datafiles. Also
29+ support cargo workspaces where we have multiple packages from
30+ a repository and some shared information present at top-level.
31+ """
32+ workspace = package_data .extra_data .get ("workspace" , {})
33+ workspace_members = workspace .get ("members" , [])
34+ workspace_package_data = workspace .get ("package" , {})
35+ attributes_to_copy = [
36+ "license_detections" ,
37+ "declared_license_expression" ,
38+ "declared_license_expression_spdx"
39+ ]
40+ if "license" in workspace_package_data :
41+ for attribute in attributes_to_copy :
42+ workspace_package_data [attribute ] = getattr (package_data , attribute )
43+
44+ workspace_root_path = resource .parent (codebase ).path
45+ if workspace_package_data and workspace_members :
46+ for workspace_member_path in workspace_members :
47+ workspace_directory_path = os .path .join (workspace_root_path , workspace_member_path )
48+ workspace_directory = codebase .get_resource (path = workspace_directory_path )
49+ if not workspace_directory :
50+ continue
51+
52+ # Update the package data for all members with the
53+ # workspace package data
54+ for resource in workspace_directory .children (codebase ):
55+ if cls .is_datafile (location = resource .location ):
56+ if not resource .package_data :
57+ continue
58+
59+ updated_package_data = cls .update_resource_package_data (
60+ package_data = workspace_package_data ,
61+ old_package_data = resource .package_data .pop (),
62+ mapping = CARGO_ATTRIBUTE_MAPPING ,
63+ )
64+ resource .package_data .append (updated_package_data )
65+ resource .save (codebase )
66+
67+ yield from cls .assemble_from_many_datafiles (
68+ datafile_name_patterns = ('Cargo.toml' , 'cargo.toml' , 'Cargo.lock' , 'cargo.lock' ),
69+ directory = workspace_directory ,
70+ codebase = codebase ,
71+ package_adder = package_adder ,
72+ )
73+ else :
74+ yield from cls .assemble_from_many_datafiles (
75+ datafile_name_patterns = ('Cargo.toml' , 'cargo.toml' , 'Cargo.lock' , 'cargo.lock' ),
76+ directory = resource .parent (codebase ),
77+ codebase = codebase ,
78+ package_adder = package_adder ,
79+ )
80+
81+ @classmethod
82+ def update_resource_package_data (cls , package_data , old_package_data , mapping = None ):
83+
84+ for attribute in old_package_data .keys ():
85+ if attribute in mapping :
86+ replace_by_attribute = mapping .get (attribute )
87+ old_package_data [attribute ] = package_data .get (replace_by_attribute )
88+ elif attribute == "parties" :
89+ old_package_data [attribute ] = list (get_parties (
90+ person_names = package_data .get ("authors" ),
91+ party_role = 'author' ,
92+ ))
93+
94+ return old_package_data
95+
96+
97+
98+ class CargoTomlHandler (CargoBaseHandler ):
2499 datasource_id = 'cargo_toml'
25100 path_patterns = ('*/Cargo.toml' , '*/cargo.toml' ,)
26101 default_package_type = 'cargo'
@@ -31,11 +106,16 @@ class CargoTomlHandler(models.DatafileHandler):
31106 @classmethod
32107 def parse (cls , location , package_only = False ):
33108 package_data = toml .load (location , _dict = dict )
34-
35109 core_package_data = package_data .get ('package' , {})
110+ workspace = package_data .get ('workspace' , {})
111+ extra_data = {}
36112
37113 name = core_package_data .get ('name' )
38114 version = core_package_data .get ('version' )
115+ if isinstance (version , dict ) and "workspace" in version :
116+ version = None
117+ extra_data ["version" ] = "workspace"
118+
39119 description = core_package_data .get ('description' ) or ''
40120 description = description .strip ()
41121
@@ -66,6 +146,8 @@ def parse(cls, location, package_only=False):
66146 repository_homepage_url = name and f'https://crates.io/crates/{ name } '
67147 repository_download_url = name and version and f'https://crates.io/api/v1/crates/{ name } /{ version } /download'
68148 api_data_url = name and f'https://crates.io/api/v1/crates/{ name } '
149+ if workspace :
150+ extra_data ["workspace" ] = workspace
69151
70152 package_data = dict (
71153 datasource_id = cls .datasource_id ,
@@ -85,20 +167,21 @@ def parse(cls, location, package_only=False):
85167 )
86168 yield models .PackageData .from_data (package_data , package_only )
87169
88- @classmethod
89- def assemble (cls , package_data , resource , codebase , package_adder ):
90- """
91- Assemble Cargo.toml and possible Cargo.lock datafiles
92- """
93- yield from cls .assemble_from_many_datafiles (
94- datafile_name_patterns = ('Cargo.toml' , 'cargo.toml' , 'Cargo.lock' , 'cargo.lock' ),
95- directory = resource .parent (codebase ),
96- codebase = codebase ,
97- package_adder = package_adder ,
98- )
170+
171+ CARGO_ATTRIBUTE_MAPPING = {
172+ # Fields in PackageData model: Fields in cargo
173+ "homepage_url" : "homepage" ,
174+ "vcs_url" : "repository" ,
175+ "keywords" : "categories" ,
176+ "extracted_license_statement" : "license" ,
177+ # These are fields carried over to avoid re-detection of licenses
178+ "license_detections" : "license_detections" ,
179+ "declared_license_expression" : "declared_license_expression" ,
180+ "declared_license_expression_spdx" : "declared_license_expression_spdx" ,
181+ }
99182
100183
101- class CargoLockHandler (models . DatafileHandler ):
184+ class CargoLockHandler (CargoBaseHandler ):
102185 datasource_id = 'cargo_lock'
103186 path_patterns = ('*/Cargo.lock' , '*/cargo.lock' ,)
104187 default_package_type = 'cargo'
@@ -146,18 +229,6 @@ def parse(cls, location, package_only=False):
146229 )
147230 yield models .PackageData .from_data (package_data , package_only )
148231
149- @classmethod
150- def assemble (cls , package_data , resource , codebase , package_adder ):
151- """
152- Assemble Cargo.toml and possible Cargo.lock datafiles
153- """
154- yield from cls .assemble_from_many_datafiles (
155- datafile_name_patterns = ('Cargo.toml' , 'Cargo.lock' ,),
156- directory = resource .parent (codebase ),
157- codebase = codebase ,
158- package_adder = package_adder ,
159- )
160-
161232
162233def dependency_mapper (dependencies , scope = 'dependencies' ):
163234 """
@@ -199,7 +270,7 @@ def get_parties(person_names, party_role):
199270 name = name ,
200271 role = party_role ,
201272 email = email ,
202- )
273+ ). to_dict ()
203274
204275
205276person_parser = re .compile (
0 commit comments