diff --git a/src/packagedcode/cargo.py b/src/packagedcode/cargo.py index 3a0533d8f9..fa9d590789 100644 --- a/src/packagedcode/cargo.py +++ b/src/packagedcode/cargo.py @@ -59,7 +59,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 @@ -95,12 +101,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..eb5c15cbc2 --- /dev/null +++ b/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml @@ -0,0 +1,38 @@ +[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" + +[features] +default = ["std"] + +# Necessary to detect at runtime whether DIT is available on aarch64. +std = [] + +# Enables tests which depend on the count_instructions crate. +count_instructions_test = [] + +[[bench]] +name = "bench" +harness = false + +[[bench]] +name = "bench_generic" +harness = false + +[[bench]] +name = "bench_classic" +harness = false \ 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..96f1c56fde --- /dev/null +++ b/tests/packagedcode/data/cargo/cargo_toml/single-file-scan/Cargo.toml.expected @@ -0,0 +1,114 @@ +[ + { + "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": null, + "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" + } +] \ No newline at end of file diff --git a/tests/packagedcode/test_cargo.py b/tests/packagedcode/test_cargo.py index b71634aa8a..429b67ce70 100644 --- a/tests/packagedcode/test_cargo.py +++ b/tests/packagedcode/test_cargo.py @@ -83,6 +83,12 @@ 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_loc = self.get_test_loc('cargo/cargo_toml/single-file-scan/Cargo.toml.expected') + packages_data = cargo.CargoTomlHandler.parse(test_file) + self.check_packages_data(packages_data, expected_loc, 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')