diff --git a/src/packagedcode/cargo.py b/src/packagedcode/cargo.py index 8cd087c2cc..3b2d342d82 100644 --- a/src/packagedcode/cargo.py +++ b/src/packagedcode/cargo.py @@ -67,7 +67,13 @@ def assemble(cls, package_data, resource, codebase, package_adder): package_data.extra_data[attribute] = 'workspace' workspace_package_data[attribute] = getattr(package_data, attribute) - workspace_root_path = resource.parent(codebase).path + workspace_root = resource.parent(codebase) + if not workspace_root: + # If there's no parent (e.g., scanning a single file), use the directory part of the resource path + workspace_root_path = os.path.dirname(resource.path) + else: + workspace_root_path = workspace_root.path + if workspace_package_data and workspace_members: # TODO: support glob patterns found in cargo workspaces @@ -103,12 +109,14 @@ def assemble(cls, package_data, resource, codebase, package_adder): package_adder=package_adder, ) else: - yield from cls.assemble_from_many_datafiles( - datafile_name_patterns=('Cargo.toml', 'cargo.toml', 'Cargo.lock', 'cargo.lock'), - directory=resource.parent(codebase), - codebase=codebase, - package_adder=package_adder, - ) + parent_resource = resource.parent(codebase) + if parent_resource: + yield from cls.assemble_from_many_datafiles( + datafile_name_patterns=('Cargo.toml', 'cargo.toml', 'Cargo.lock', 'cargo.lock'), + directory=parent_resource, + codebase=codebase, + package_adder=package_adder, + ) @classmethod def update_resource_package_data(cls, workspace, workspace_package_data, resource_package_data, mapping=None): diff --git a/src/packagedcode/npm.py b/src/packagedcode/npm.py index 779ff28166..0532d08694 100644 --- a/src/packagedcode/npm.py +++ b/src/packagedcode/npm.py @@ -125,7 +125,7 @@ def assemble(cls, package_data, resource, codebase, package_adder): workspace_root = package_resource.parent(codebase) workspace_root_path = None if workspace_root: - workspace_root_path = package_resource.parent(codebase).path + workspace_root_path = workspace_root.path workspaces = pkg_data.extra_data.get('workspaces') or [] # Also look for pnpm workspaces diff --git a/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml b/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml new file mode 100644 index 0000000000..cbfee53902 --- /dev/null +++ b/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "constant_time_eq" +version = "0.4.2" +edition = "2024" +authors = ["Cesar Eduardo Barros "] +description = "Compares two equal-sized byte strings in constant time." +documentation = "https://docs.rs/constant_time_eq" +repository = "https://github.com/cesarb/constant_time_eq" +readme = "README" +keywords = ["constant_time"] +categories = ["cryptography", "no-std"] +license = "CC0-1.0 OR MIT-0 OR Apache-2.0" +rust-version = "1.85.0" + +[dev-dependencies] +criterion = { version = "0.5.1", features = ["cargo_bench_support", "html_reports"] } +count_instructions = "0.2.0" \ No newline at end of file diff --git a/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml.expected b/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml.expected new file mode 100644 index 0000000000..ff00223cc8 --- /dev/null +++ b/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml.expected @@ -0,0 +1,126 @@ +{ + "packages": [], + "dependencies": [], + "files": [ + { + "path": "Cargo.toml", + "type": "file", + "package_data": [ + { + "type": "cargo", + "namespace": null, + "name": "constant_time_eq", + "version": "0.4.2", + "qualifiers": {}, + "subpath": null, + "primary_language": "Rust", + "description": "Compares two equal-sized byte strings in constant time.", + "release_date": null, + "parties": [ + { + "type": "person", + "role": "author", + "name": "Cesar Eduardo Barros", + "email": "cesarb@cesarb.eti.br", + "url": null + } + ], + "keywords": [ + "constant_time", + "cryptography", + "no-std" + ], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": "https://github.com/cesarb/constant_time_eq", + "copyright": null, + "holder": null, + "declared_license_expression": "cc0-1.0 OR mit-0 OR apache-2.0", + "declared_license_expression_spdx": "CC0-1.0 OR MIT-0 OR Apache-2.0", + "license_detections": [ + { + "license_expression": "cc0-1.0 OR mit-0 OR apache-2.0", + "license_expression_spdx": "CC0-1.0 OR MIT-0 OR Apache-2.0", + "matches": [ + { + "license_expression": "cc0-1.0 OR mit-0 OR apache-2.0", + "license_expression_spdx": "CC0-1.0 OR MIT-0 OR Apache-2.0", + "from_file": "Cargo.toml", + "start_line": 1, + "end_line": 1, + "matcher": "1-spdx-id", + "score": 100.0, + "matched_length": 10, + "match_coverage": 100.0, + "rule_relevance": 100, + "rule_identifier": "spdx-license-identifier-cc0_1_0_or_mit_0_or_apache_2_0-f44a2ec174eb034bd3c662f728664281e507b20d", + "rule_url": null, + "matched_text": "CC0-1.0 OR MIT-0 OR Apache-2.0" + } + ], + "identifier": "cc0_1_0_or_mit_0_or_apache_2_0-3f14dd48-7cd8-cf28-d4e1-3b0174a587ee" + } + ], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": "CC0-1.0 OR MIT-0 OR Apache-2.0", + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "documentation_url": "https://docs.rs/constant_time_eq", + "rust_version": "1.85.0", + "rust_edition": "2024" + }, + "dependencies": [ + { + "purl": "pkg:cargo/criterion", + "extracted_requirement": "0.5.1", + "scope": "dev-dependencies", + "is_runtime": false, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": { + "version": "0.5.1", + "features": [ + "cargo_bench_support", + "html_reports" + ] + } + }, + { + "purl": "pkg:cargo/count_instructions", + "extracted_requirement": "0.2.0", + "scope": "dev-dependencies", + "is_runtime": false, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": "https://crates.io/crates/constant_time_eq", + "repository_download_url": "https://crates.io/api/v1/crates/constant_time_eq/0.4.2/download", + "api_data_url": "https://crates.io/api/v1/crates/constant_time_eq", + "datasource_id": "cargo_toml", + "purl": "pkg:cargo/constant_time_eq@0.4.2" + } + ], + "for_packages": [], + "scan_errors": [] + } + ] +} diff --git a/tests/packagedcode/test_cargo.py b/tests/packagedcode/test_cargo.py index b71634aa8a..e3309a438e 100644 --- a/tests/packagedcode/test_cargo.py +++ b/tests/packagedcode/test_cargo.py @@ -83,6 +83,15 @@ def test_parse_cargo_toml_workspace_with_dependencies_child(self): packages_data = cargo.CargoTomlHandler.parse(test_file) self.check_packages_data(packages_data, expected_loc, regen=REGEN_TEST_FIXTURES) + def test_parse_cargo_toml_single_file_no_crash(self): + test_file = self.get_test_loc('cargo/cargo_toml/single-file-scan/Cargo.toml') + expected_file = self.get_test_loc('cargo/cargo_toml/single-file-scan/Cargo.toml.expected') + result_file = self.get_temp_file('results.json') + run_scan_click(['--package', test_file, '--json', result_file]) + check_json_scan( + expected_file, result_file, remove_uuid=True, regen=REGEN_TEST_FIXTURES + ) + def test_parse_cargo_toml_tauri_workspace_in_version(self): test_file = self.get_test_loc('cargo/cargo_toml/tauri-examples/Cargo.toml') expected_loc = self.get_test_loc('cargo/cargo_toml/tauri-examples/Cargo.toml.expected')