Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ the qualification scope requires the JDK installation as well.
| `targets` | *(required)* | Bazel targets whose transitive dependencies are included in the SBOM. |
| `component_name` | rule `name` | Name of the root component written into the SBOM; defaults to the rule name if omitted. |
| `component_version` | `None` | Version string for the root component; auto-detected from the module graph when omitted. |
| `component_license` | `None` | SPDX license expression for the root component; SCORE components without explicit license metadata default to `Apache-2.0`. |
| `module_lockfiles` | `[]` | One or more `MODULE.bazel.lock` files used to extract dependency versions and SHA-256 checksums; C++ projects need only the workspace lockfile (`:MODULE.bazel.lock`), Rust projects should also pass `@score_crates//:MODULE.bazel.lock` to cover crate versions and checksums. |
| `python_lockfiles` | `[]` | One or more pip-compile lockfiles (`requirements.txt.lock`) used to add pinned PyPI packages, SHA-256 hashes, and license expressions from DASH. Packages that DASH cannot verify retain `NOASSERTION`; descriptions are not enriched yet and remain `Missing`. |
| `auto_python_cache` | `True` | Generates Python package metadata from `python_lockfiles`; set to `False` to disable it. |
Expand Down
3 changes: 3 additions & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def sbom(
producer_url = "https://projects.eclipse.org/projects/automotive.score",
component_name = None,
component_version = None,
component_license = None,
sbom_authors = None,
generation_context = None,
sbom_tools = None,
Expand Down Expand Up @@ -98,6 +99,7 @@ def sbom(
PURL namespace falls back to ``eclipse-score``.
component_name: Main component name (defaults to rule name)
component_version: Component version string
component_license: SPDX license for the main component

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2ebe5d0.

namespace: SBOM namespace URI (defaults to https://eclipse.dev/score)
exclude_patterns: Repo patterns to exclude (e.g., build tools)
**kwargs: Additional arguments passed to the underlying rule
Expand Down Expand Up @@ -164,6 +166,7 @@ def sbom(
producer_url = producer_url,
component_name = component_name if component_name else name,
component_version = component_version if component_version else "",
component_license = component_license if component_license else "",
sbom_authors = sbom_authors if sbom_authors else [],
generation_context = generation_context if generation_context else "",
sbom_tools = sbom_tools if sbom_tools else [],
Expand Down
17 changes: 14 additions & 3 deletions internal/generator/spdx_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def generate_spdx(
component_name = config.get("component_name", "unknown")
component_version = config.get("component_version", "")
producer_name = config.get("producer_name", "Eclipse Foundation")
component_license = _default_score_license(
component_name, config.get("component_license", "")
)
Comment on lines +49 to +51

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2ebe5d0; Bazel tests pass.


doc_uuid = uuid.uuid4()

Expand All @@ -62,8 +65,8 @@ def generate_spdx(
"supplier": f"Organization: {producer_name}",
"primaryPackagePurpose": "APPLICATION",
"filesAnalyzed": False,
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"licenseConcluded": component_license or "NOASSERTION",
"licenseDeclared": component_license or "NOASSERTION",
"copyrightText": "NOASSERTION",
}
packages.append(root_package)
Expand Down Expand Up @@ -131,7 +134,7 @@ def _create_spdx_package(
name = component.get("name", "unknown")
version = component.get("version", "unknown")
purl = component.get("purl", "")
license_id = _normalize_spdx_license(component.get("license", ""))
license_id = _default_score_license(name, component.get("license", ""))
description = component.get("description", "")
supplier = component.get("supplier", "")
comp_type = component.get("type", "library")
Expand Down Expand Up @@ -185,6 +188,14 @@ def _create_spdx_package(
return package, spdx_id


def _default_score_license(name: str, license_id: str) -> str:
"""Use the SCORE default license when a score module has no metadata."""
normalized = _normalize_spdx_license(license_id)
if not normalized and name.startswith("score_"):
return "Apache-2.0"
return normalized


def _map_type_to_purpose(comp_type: str) -> str:
"""Map component type to SPDX 2.3 primary package purpose."""
type_mapping = {
Expand Down
5 changes: 5 additions & 0 deletions internal/rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def _sbom_impl(ctx):
"producer_url": ctx.attr.producer_url,
"component_name": ctx.attr.component_name if ctx.attr.component_name else ctx.attr.name,
"component_version": ctx.attr.component_version,
"component_license": ctx.attr.component_license,
"namespace": ctx.attr.namespace,
"sbom_authors": ctx.attr.sbom_authors,
"generation_context": ctx.attr.generation_context,
Expand Down Expand Up @@ -258,6 +259,10 @@ sbom_rule = rule(
default = "",
doc = "Component version",
),
"component_license": attr.string(
default = "",
doc = "SPDX license for the main component",
),
"sbom_authors": attr.string_list(
default = [],
doc = "SBOM author(s) (distinct from software producers)",
Expand Down
39 changes: 39 additions & 0 deletions tests/test_spdx_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,45 @@ def test_generate_spdx_with_empty_components(self):
# Only root package
self.assertEqual(len(packages), 1)

def test_score_packages_default_to_apache_license(self):
"""SCORE packages without license metadata default to Apache-2.0."""
config = {**self.config, "component_name": "score_persistency"}
components = [{"name": "score_baselibs", "version": "0.2.13"}]

spdx = generate_spdx(components, config, self.timestamp)

root_package, score_package = spdx["packages"]
self.assertEqual(root_package["licenseDeclared"], "Apache-2.0")
self.assertEqual(score_package["licenseDeclared"], "Apache-2.0")

def test_component_license_overrides_score_default(self):
"""Explicit license metadata remains authoritative for SCORE packages."""
components = [
{
"name": "score_baselibs",
"version": "0.2.13",
"license": "MIT",
}
]

spdx = generate_spdx(components, self.config, self.timestamp)

self.assertEqual(spdx["packages"][1]["licenseDeclared"], "MIT")

def test_root_component_license_overrides_score_default(self):
"""Explicit root license metadata remains authoritative for SCORE packages."""
config = {
**self.config,
"component_name": "score_persistency",
"component_license": "MIT OR Apache-2.0",
}

spdx = generate_spdx([], config, self.timestamp)

root_package = spdx["packages"][0]
self.assertEqual(root_package["licenseDeclared"], "MIT OR Apache-2.0")
self.assertEqual(root_package["licenseConcluded"], "MIT OR Apache-2.0")

def test_generate_spdx_component_purl(self):
"""Test that component PURLs are properly set."""
spdx = generate_spdx(self.components, self.config, self.timestamp)
Expand Down
Loading