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
2 changes: 2 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Separate Bazel module - must be built from its own directory.
examples/dummy_project
24 changes: 24 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,27 @@ jobs:
cache-save: ${{ github.ref == 'refs/heads/main' }}
- name: Run unit tests
run: bazel test --test_output=errors //tests/...
integration_test:
name: Integration test
runs-on: ubuntu-24.04
# A cold run builds a Rust toolchain and crate_universe from source before
# it gets to the SBOM itself, which alone takes a few minutes.
timeout-minutes: 30
defaults:
run:
# examples/dummy_project is its own Bazel module - see its MODULE.bazel.
working-directory: examples/dummy_project
steps:
- name: Checkout repository
uses: actions/checkout@v4.2.2
- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.18.0
with:
bazelisk-cache: true
disk-cache: integration_test
repository-cache: true
cache-save: ${{ github.ref == 'refs/heads/main' }}
# Builds the C++ and Rust binaries, generates their SBOM through the
# public sbom() macro and asserts the result in //:sbom_test.
- name: Build the dummy project and check its SBOM
run: bazel test --test_output=errors //...
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,42 @@ Only transitive dependencies of the declared build targets are included. Build-t

## How design is tested

To run tests
Two layers: unit tests over the generator internals, and an end-to-end
integration test that builds a real project through the public `sbom()` macro.

```bash
# From sbom-tool/ — run all SBOM tests
# From sbom-tool/ — unit tests, against captured fixtures
bazel test //tests/...

# From sbom-tool/examples/dummy_project/ — end-to-end, against a real build
bazel test //...
```

Sbom was also tested by external tool
https://sbomgenerator.com/tools/validator

### End-to-end integration test

`examples/dummy_project` is a small C++ and Rust project that consumes these
rules the way a downstream repository does: it declares `bazel_dep(name =
"score_sbom")` (resolved through `local_path_override` to the checkout), calls
`sbom()` on a `cc_binary` and a `rust_binary`, and asserts the resulting SPDX
and CycloneDX documents in `sbom_test.py`.

It is a **separate Bazel module**, listed in the repository root `.bazelignore`,
so it must be built from its own directory and never leaks its dependencies
(`nlohmann_json`, `rules_rust`, `score_crates`) into consumers of `@score_sbom`.
Its `MODULE.bazel.lock` is tracked in git for the same reason the root one is,
plus one of its own: the `sbom()` target consumes the lockfile as a build input,
so a checkout without it fails to build.

`auto_cdxgen` and `auto_crates_cache` are off, so the test needs no npm and no
calls to dash-license-scan or crates.io during SBOM generation. A cold build
still fetches bzlmod modules and toolchains from the configured registries like
any other Bazel build. The test covers the aspect, the rule and the module
extension — the parts no fixture can cover — rather than full license
enrichment.

### Tests description

| Test file | Bazel target | What it covers |
Expand All @@ -209,6 +236,7 @@ https://sbomgenerator.com/tools/validator
| `test_generate_cpp_metadata_cache.py` | `test_generate_cpp_metadata_cache` | `convert_cdxgen_to_cache()`: version, license (id/name/expression/AND), supplier (name/publisher fallback), PURL, URL from externalReferences, description |
| `test_spdx_to_github_snapshot.py` | `test_spdx_to_github_snapshot` | `convert_spdx_to_snapshot()`: top-level fields; direct vs. indirect classification; package filtering; manifest naming; `pkg:generic/` PURL support |
| `test_real_sbom_integration.py` | `test_real_sbom_integration` | End-to-end generation from captured `reference_integration` fixtures (baselibs, kyron, orchestrator): SPDX and CycloneDX validity; package counts; crate license enrichment and checksums; module version enrichment from the lockfile; `LicenseRef-*` declarations; bom-ref uniqueness; every SPDXID resolving to a real node |
| `examples/dummy_project/sbom_test.py` | `//:sbom_test` (in `examples/dummy_project`) | The SBOM of an actual Bazel build: SPDX 2.3 and CycloneDX 1.6 headers, root component identity, one C++ (`nlohmann_json`) and one Rust (`serde`) dependency proving both toolchain paths were traversed, license fields on every package, no dangling SPDXID or bom-ref, and the two formats agreeing on the component set |

## Development

Expand Down Expand Up @@ -248,6 +276,7 @@ Every pull request runs:
| Workflow | Job | What it does |
|---|---|---|
| `tests.yml` | `unit_tests` | `bazel test //tests/...` |
| `tests.yml` | `integration_test` | `bazel test //...` in `examples/dummy_project` — builds the example and checks its SBOM |
| `format.yml` | `formatting-check` | `//tools:format.check` (`ruff format`, buildifier, yamlfmt) via `eclipse-score/cicd-workflows` |
| `copyright.yml` | `copyright-check` | `//tools:copyright-check` via `eclipse-score/cicd-workflows` |
| `bzlmod-lock.yml` | `bzlmod-lock` | `bazel mod tidy` + `bazel mod deps --lockfile_mode=error` via `eclipse-score/cicd-workflows` |
Expand Down
7 changes: 7 additions & 0 deletions examples/dummy_project/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
common --registry=https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/
common --registry=https://bcr.bazel.build

build --java_language_version=17
build --tool_java_language_version=17
build --java_runtime_version=remotejdk_17
build --tool_java_runtime_version=remotejdk_17
1 change: 1 addition & 0 deletions examples/dummy_project/.bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8.7.0
57 changes: 57 additions & 0 deletions examples/dummy_project/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

load("@rules_python//python:defs.bzl", "py_test")
load("@score_sbom//:defs.bzl", "sbom")

package(default_visibility = ["//visibility:public"])

exports_files(["MODULE.bazel.lock"])

# End-to-end smoke test for the SBOM rules: builds one SBOM covering both the
# C++ and the Rust dummy binaries, in SPDX 2.3 and CycloneDX 1.6.
#
# Outputs (in bazel-bin/):
# dummy_sbom.spdx.json
# dummy_sbom.cdx.json
sbom(
name = "dummy_sbom",
# auto_cdxgen needs npm + @cyclonedx/cdxgen on the build machine, and
# auto_crates_cache reaches out to dash-license-scan and crates.io at build
# time. Both are left off so the smoke test needs no extra tooling and no
# network access beyond the module and toolchain downloads any cold Bazel
# build performs; a real project should enable them to get full C++ and
# Rust license data.
auto_cdxgen = False,
auto_crates_cache = False,
component_name = "score_sbom_dummy_project",
component_version = "0.1.0",
generation_context = "build",
module_lockfiles = [":MODULE.bazel.lock"],
output_formats = [
"spdx",
"cyclonedx",
],
targets = [
"//cpp:app",
"//rust:app",
],
)

# Asserts that the SBOMs built above are structurally valid and cover both the
# C++ and the Rust dependency chains.
py_test(
name = "sbom_test",
srcs = ["sbom_test.py"],
data = [":dummy_sbom"],
)
67 changes: 67 additions & 0 deletions examples/dummy_project/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# Nested workspace holding the dummy project that the SBOM smoke test builds
# against. It is deliberately a separate Bazel module so that its dependencies
# (nlohmann_json, rules_rust, score_crates) never leak into the dependency graph
# of consumers of @score_sbom. It is listed in the repository root .bazelignore.

module(
name = "score_sbom_dummy_project",
version = "0.1.0",
)

# --- The tool under test ------------------------------------------------------

bazel_dep(name = "score_sbom", version = "0.0.1")
local_path_override(
module_name = "score_sbom",
path = "../..",
)

# --- C++ dummy code -----------------------------------------------------------

bazel_dep(name = "rules_cc", version = "0.2.9")
bazel_dep(name = "nlohmann_json", version = "3.12.0")

# --- SBOM validation test -----------------------------------------------------

bazel_dep(name = "rules_python", version = "1.5.1")

# --- Rust dummy code ----------------------------------------------------------
#
# rules_rust is taken from the eclipse-score registry, and every crate comes from
# @score_crates. This workspace deliberately does not declare a crate_universe of
# its own, so no crate is ever pulled straight from crates.io.

bazel_dep(name = "rules_rust", version = "0.68.2-score")
bazel_dep(name = "score_crates", version = "0.0.9")

rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2021",
versions = ["1.86.0"],
)
use_repo(rust, "rust_toolchains")

register_toolchains("@rust_toolchains//:all")

# --- SBOM metadata ------------------------------------------------------------
#
# score_sbom is pulled in via local_path_override, so its version cannot be read
# from the module graph; track_module makes the extension read it from the
# module's own MODULE.bazel instead.

sbom_ext = use_extension("@score_sbom//:extensions.bzl", "sbom_metadata")
sbom_ext.track_module(name = "score_sbom")
use_repo(sbom_ext, "sbom_metadata")
Loading
Loading