buildstream-sbom 1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- buildstream_sbom-1.1/.gitignore +14 -0
- buildstream_sbom-1.1/.gitlab-ci.yml +57 -0
- buildstream_sbom-1.1/CHANGELOG.md +28 -0
- buildstream_sbom-1.1/LICENSES/Apache-2.0.txt +73 -0
- buildstream_sbom-1.1/PKG-INFO +111 -0
- buildstream_sbom-1.1/README.md +103 -0
- buildstream_sbom-1.1/pyproject.toml +22 -0
- buildstream_sbom-1.1/setup.cfg +4 -0
- buildstream_sbom-1.1/src/buildstream_sbom/frontend.py +190 -0
- buildstream_sbom-1.1/src/buildstream_sbom/licensing.py +151 -0
- buildstream_sbom-1.1/src/buildstream_sbom/spdx.py +337 -0
- buildstream_sbom-1.1/src/buildstream_sbom/utils.py +30 -0
- buildstream_sbom-1.1/src/buildstream_sbom.egg-info/PKG-INFO +111 -0
- buildstream_sbom-1.1/src/buildstream_sbom.egg-info/SOURCES.txt +16 -0
- buildstream_sbom-1.1/src/buildstream_sbom.egg-info/dependency_links.txt +1 -0
- buildstream_sbom-1.1/src/buildstream_sbom.egg-info/entry_points.txt +2 -0
- buildstream_sbom-1.1/src/buildstream_sbom.egg-info/requires.txt +1 -0
- buildstream_sbom-1.1/src/buildstream_sbom.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
workflow:
|
|
6
|
+
rules:
|
|
7
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
8
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
9
|
+
- if: $CI_COMMIT_TAG
|
|
10
|
+
|
|
11
|
+
stages:
|
|
12
|
+
- build
|
|
13
|
+
- publish
|
|
14
|
+
|
|
15
|
+
.ruff:
|
|
16
|
+
image:
|
|
17
|
+
name: ghcr.io/astral-sh/ruff:0.12.9-alpine
|
|
18
|
+
stage: build
|
|
19
|
+
interruptible: true
|
|
20
|
+
|
|
21
|
+
Ruff Check:
|
|
22
|
+
extends: .ruff
|
|
23
|
+
script:
|
|
24
|
+
- ruff check --output-format=gitlab > code-quality-report.json
|
|
25
|
+
artifacts:
|
|
26
|
+
reports:
|
|
27
|
+
codequality: code-quality-report.json
|
|
28
|
+
|
|
29
|
+
Ruff Format:
|
|
30
|
+
extends: .ruff
|
|
31
|
+
script:
|
|
32
|
+
- ruff format --diff
|
|
33
|
+
|
|
34
|
+
REUSE Compliance Check:
|
|
35
|
+
stage: build
|
|
36
|
+
image:
|
|
37
|
+
name: fsfe/reuse:latest
|
|
38
|
+
entrypoint: [""]
|
|
39
|
+
script:
|
|
40
|
+
- reuse lint
|
|
41
|
+
|
|
42
|
+
# publish to PyPi for pipelines run on tags
|
|
43
|
+
pypi-release:
|
|
44
|
+
stage: publish
|
|
45
|
+
rules:
|
|
46
|
+
- if: $CI_COMMIT_TAG
|
|
47
|
+
id_tokens:
|
|
48
|
+
PYPI_ID_TOKEN:
|
|
49
|
+
aud: pypi
|
|
50
|
+
image: docker.io/library/python:3.14
|
|
51
|
+
before_script:
|
|
52
|
+
- |
|
|
53
|
+
python3 -mvenv venv
|
|
54
|
+
./venv/bin/pip install twine build
|
|
55
|
+
script:
|
|
56
|
+
- ./venv/bin/python3 -m build .
|
|
57
|
+
- ./venv/bin/twine upload --skip-existing dist/*
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Codethink Limited
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# Changelog
|
|
8
|
+
|
|
9
|
+
## [1.1] - 2026-04-30
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Make use of a source's (optional) provenance field
|
|
14
|
+
- Add license extraction support
|
|
15
|
+
- Add --spdx-comment option
|
|
16
|
+
- Add element name as externalRef
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Don't use the BST environment variable
|
|
21
|
+
- Check creators are in the spdx format
|
|
22
|
+
- Drop special casing of patch sources
|
|
23
|
+
- Set fallback package name to 'UNKNOWN' for sources
|
|
24
|
+
- docker images are currently the only known "unknown"
|
|
25
|
+
|
|
26
|
+
## [1.0] - 2025-10-14
|
|
27
|
+
|
|
28
|
+
Initial release.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
10
|
+
|
|
11
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
12
|
+
|
|
13
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
14
|
+
|
|
15
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
16
|
+
|
|
17
|
+
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
18
|
+
|
|
19
|
+
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
20
|
+
|
|
21
|
+
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
22
|
+
|
|
23
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
24
|
+
|
|
25
|
+
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
26
|
+
|
|
27
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
28
|
+
|
|
29
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
30
|
+
|
|
31
|
+
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
32
|
+
|
|
33
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
34
|
+
|
|
35
|
+
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
36
|
+
|
|
37
|
+
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
38
|
+
|
|
39
|
+
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
40
|
+
|
|
41
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
|
42
|
+
|
|
43
|
+
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
44
|
+
|
|
45
|
+
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
46
|
+
|
|
47
|
+
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
48
|
+
|
|
49
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
50
|
+
|
|
51
|
+
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
52
|
+
|
|
53
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
54
|
+
|
|
55
|
+
END OF TERMS AND CONDITIONS
|
|
56
|
+
|
|
57
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
58
|
+
|
|
59
|
+
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
|
60
|
+
|
|
61
|
+
Copyright [yyyy] [name of copyright owner]
|
|
62
|
+
|
|
63
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
64
|
+
you may not use this file except in compliance with the License.
|
|
65
|
+
You may obtain a copy of the License at
|
|
66
|
+
|
|
67
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
68
|
+
|
|
69
|
+
Unless required by applicable law or agreed to in writing, software
|
|
70
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
71
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
72
|
+
See the License for the specific language governing permissions and
|
|
73
|
+
limitations under the License.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: buildstream-sbom
|
|
3
|
+
Version: 1.1
|
|
4
|
+
Summary: A tool to generate SPDX SBoMs from buildstream projects
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pyyaml
|
|
8
|
+
|
|
9
|
+
<!--
|
|
10
|
+
SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
11
|
+
|
|
12
|
+
SPDX-License-Identifier: Apache-2.0
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
# BuildStream SBOM generator
|
|
16
|
+
|
|
17
|
+
This tool can be used to produce an SBoM (Software Bill of Materials)
|
|
18
|
+
describing a BuildStream element and its dependencies. It currently
|
|
19
|
+
generates [SPDX](https://spdx.dev/) version 2.3.
|
|
20
|
+
|
|
21
|
+
The manifest contains useful information, such as the package name, version,
|
|
22
|
+
source locations and dependencies.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Source provenance data
|
|
26
|
+
|
|
27
|
+
This tool relies on the [Source Provenance API](https://docs.buildstream.build/2.5/buildstream.source.html#generating-sourceinfo-for-provenance-information)
|
|
28
|
+
introduced in BuildStream 2.5. This API is implemented by buildstream-plugins
|
|
29
|
+
version 2.5.0 and buildstream-plugins-community 2.1.0. Please make sure your
|
|
30
|
+
project is using those (or more recent) versions. If your project uses custom
|
|
31
|
+
source plugins, please make sure that they also implement this API.
|
|
32
|
+
|
|
33
|
+
Version guessing is handled by individual source plugins, please check the
|
|
34
|
+
individual plugin documentation for details. Most plugins that implement it
|
|
35
|
+
do something similar to what [DownloadableFileSource](https://docs.buildstream.build/2.5/buildstream.downloadablefilesource.html#core-downloadable-source-builtins)
|
|
36
|
+
does, so it is a good starting point for understanding how this works.
|
|
37
|
+
|
|
38
|
+
The list of currently supported source provenance attributes that can be
|
|
39
|
+
specified and used for a Buildstream project is as follows:
|
|
40
|
+
|
|
41
|
+
| Attribute name | Corresponding SPDX attribute |
|
|
42
|
+
| -------------- | ---------------------------- |
|
|
43
|
+
| concluded-license | [licenseConcluded](https://spdx.github.io/spdx-spec/v2.3/package-information/#713-concluded-license-field) |
|
|
44
|
+
| copyright-text | [copyrightText](https://spdx.github.io/spdx-spec/v2.3/package-information/#717-copyright-text-field) |
|
|
45
|
+
| declared-license | [licenseDeclared](https://spdx.github.io/spdx-spec/v2.3/package-information/#715-declared-license-field) |
|
|
46
|
+
| description | [summary](https://spdx.github.io/spdx-spec/v2.3/package-information/#718-package-summary-description-field) |
|
|
47
|
+
| homepage | [homepage](https://spdx.github.io/spdx-spec/v2.3/package-information/#711-package-home-page-field) |
|
|
48
|
+
| name | [name](https://spdx.github.io/spdx-spec/v2.3/package-information/#71-package-name-field) |
|
|
49
|
+
| originator | [originator](https://spdx.github.io/spdx-spec/v2.3/package-information/#76-package-originator-field) |
|
|
50
|
+
| supplier | [supplier](https://spdx.github.io/spdx-spec/v2.3/package-information/#75-package-supplier-field) |
|
|
51
|
+
|
|
52
|
+
These can be used in projects by use of the `source-provenance-attributes`
|
|
53
|
+
field in the project.conf, this is described in the provenance section of
|
|
54
|
+
the [BuildStream documentation](https://docs.buildstream.build/master/buildstream.source.html#built-in-functionality).
|
|
55
|
+
|
|
56
|
+
### source-provenance-attributes snippet
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
# project.conf
|
|
60
|
+
|
|
61
|
+
source-provenance-attributes:
|
|
62
|
+
concluded-license: The license as determined by the evidence provided by the source project
|
|
63
|
+
copyright-text: Copyright text defined by the source project
|
|
64
|
+
declared-license: The license of the source project as decided by the authors
|
|
65
|
+
description: Description of the source project
|
|
66
|
+
homepage: The URL of the source project's homepage
|
|
67
|
+
name: The name of the source project
|
|
68
|
+
originator: The name of the person or organisation that created the source package originally
|
|
69
|
+
supplier: The name of the person or organisation that provided the source package
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Should any BuildStream plugins implement tracking for source provenance attributes, similar to
|
|
73
|
+
[source tracking](https://docs.buildstream.build/master/using_commands.html#bst-source-track),
|
|
74
|
+
it is recommended for all projects and plugins to use the attribute names exactly as seen above.
|
|
75
|
+
This ensures the source provenance attributes are always generated identically between different
|
|
76
|
+
plugins and makes sure they align with projects' definitions in the same way.
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
To install, clone this repository and install it using `pip` (or preferably a
|
|
81
|
+
tool like [`uv tool`](https://docs.astral.sh/uv/#tools) or
|
|
82
|
+
[`pipx`](https://pipx.pypa.io/stable/) which install it in a virtual
|
|
83
|
+
environment).
|
|
84
|
+
|
|
85
|
+
To use, run `buildstream-sbom` in a buildstream project passing in the name of
|
|
86
|
+
elements like you would pass to `bst`. There are two additional required
|
|
87
|
+
arguments `--spdx-name` and `--spdx-namespace`, to set the SPDX document name
|
|
88
|
+
and document namespace respectively. See the [SPDX specification](https://spdx.github.io/spdx-spec/v2.3/document-creation-information/)
|
|
89
|
+
for details.
|
|
90
|
+
|
|
91
|
+
`buildstream-sbom` also accepts some buildstream options, notably `-o/--option`
|
|
92
|
+
to set buildstream options, `-C/--directory` to set the directory containing
|
|
93
|
+
the buildstream project, and `--deps` to choose whether to include only runtime
|
|
94
|
+
dependencies or all dependencies.
|
|
95
|
+
|
|
96
|
+
On the topic of runtime dependencies, two BuildStream core plugins are treated
|
|
97
|
+
specially: `filter` and `compose`. All build dependencies of elements using
|
|
98
|
+
these plugins are considered runtime dependencies. You can set the `depends-on`
|
|
99
|
+
key in the `sbom` public domain data to a list of build dependencies to have
|
|
100
|
+
`buildstream-sbom` treat these build dependencies as runtime dependencies. This
|
|
101
|
+
is useful for a `script` or `manual` element that copies artifacts from a build
|
|
102
|
+
dependency into its own artifact.
|
|
103
|
+
|
|
104
|
+
You can also include licenses that have been extracted from the element's
|
|
105
|
+
artifacts using `--include-licenses`. This uses license information installed
|
|
106
|
+
with tooling such as [Freedesktop-SDK's install-extra script](https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/master/include/install-extra.yml?ref_type=heads). This option can
|
|
107
|
+
also be used in conjunction with `--artifact-checkout-directory` (`-A`), to
|
|
108
|
+
control where element artifacts are checked out to during processing; useful
|
|
109
|
+
for if you are checking out larger artifacts and want to specify a different
|
|
110
|
+
part of a filesystem with more available space (note that artifacts are removed
|
|
111
|
+
once processed anyway to minimise required storage space).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# BuildStream SBOM generator
|
|
8
|
+
|
|
9
|
+
This tool can be used to produce an SBoM (Software Bill of Materials)
|
|
10
|
+
describing a BuildStream element and its dependencies. It currently
|
|
11
|
+
generates [SPDX](https://spdx.dev/) version 2.3.
|
|
12
|
+
|
|
13
|
+
The manifest contains useful information, such as the package name, version,
|
|
14
|
+
source locations and dependencies.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Source provenance data
|
|
18
|
+
|
|
19
|
+
This tool relies on the [Source Provenance API](https://docs.buildstream.build/2.5/buildstream.source.html#generating-sourceinfo-for-provenance-information)
|
|
20
|
+
introduced in BuildStream 2.5. This API is implemented by buildstream-plugins
|
|
21
|
+
version 2.5.0 and buildstream-plugins-community 2.1.0. Please make sure your
|
|
22
|
+
project is using those (or more recent) versions. If your project uses custom
|
|
23
|
+
source plugins, please make sure that they also implement this API.
|
|
24
|
+
|
|
25
|
+
Version guessing is handled by individual source plugins, please check the
|
|
26
|
+
individual plugin documentation for details. Most plugins that implement it
|
|
27
|
+
do something similar to what [DownloadableFileSource](https://docs.buildstream.build/2.5/buildstream.downloadablefilesource.html#core-downloadable-source-builtins)
|
|
28
|
+
does, so it is a good starting point for understanding how this works.
|
|
29
|
+
|
|
30
|
+
The list of currently supported source provenance attributes that can be
|
|
31
|
+
specified and used for a Buildstream project is as follows:
|
|
32
|
+
|
|
33
|
+
| Attribute name | Corresponding SPDX attribute |
|
|
34
|
+
| -------------- | ---------------------------- |
|
|
35
|
+
| concluded-license | [licenseConcluded](https://spdx.github.io/spdx-spec/v2.3/package-information/#713-concluded-license-field) |
|
|
36
|
+
| copyright-text | [copyrightText](https://spdx.github.io/spdx-spec/v2.3/package-information/#717-copyright-text-field) |
|
|
37
|
+
| declared-license | [licenseDeclared](https://spdx.github.io/spdx-spec/v2.3/package-information/#715-declared-license-field) |
|
|
38
|
+
| description | [summary](https://spdx.github.io/spdx-spec/v2.3/package-information/#718-package-summary-description-field) |
|
|
39
|
+
| homepage | [homepage](https://spdx.github.io/spdx-spec/v2.3/package-information/#711-package-home-page-field) |
|
|
40
|
+
| name | [name](https://spdx.github.io/spdx-spec/v2.3/package-information/#71-package-name-field) |
|
|
41
|
+
| originator | [originator](https://spdx.github.io/spdx-spec/v2.3/package-information/#76-package-originator-field) |
|
|
42
|
+
| supplier | [supplier](https://spdx.github.io/spdx-spec/v2.3/package-information/#75-package-supplier-field) |
|
|
43
|
+
|
|
44
|
+
These can be used in projects by use of the `source-provenance-attributes`
|
|
45
|
+
field in the project.conf, this is described in the provenance section of
|
|
46
|
+
the [BuildStream documentation](https://docs.buildstream.build/master/buildstream.source.html#built-in-functionality).
|
|
47
|
+
|
|
48
|
+
### source-provenance-attributes snippet
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
# project.conf
|
|
52
|
+
|
|
53
|
+
source-provenance-attributes:
|
|
54
|
+
concluded-license: The license as determined by the evidence provided by the source project
|
|
55
|
+
copyright-text: Copyright text defined by the source project
|
|
56
|
+
declared-license: The license of the source project as decided by the authors
|
|
57
|
+
description: Description of the source project
|
|
58
|
+
homepage: The URL of the source project's homepage
|
|
59
|
+
name: The name of the source project
|
|
60
|
+
originator: The name of the person or organisation that created the source package originally
|
|
61
|
+
supplier: The name of the person or organisation that provided the source package
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Should any BuildStream plugins implement tracking for source provenance attributes, similar to
|
|
65
|
+
[source tracking](https://docs.buildstream.build/master/using_commands.html#bst-source-track),
|
|
66
|
+
it is recommended for all projects and plugins to use the attribute names exactly as seen above.
|
|
67
|
+
This ensures the source provenance attributes are always generated identically between different
|
|
68
|
+
plugins and makes sure they align with projects' definitions in the same way.
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
To install, clone this repository and install it using `pip` (or preferably a
|
|
73
|
+
tool like [`uv tool`](https://docs.astral.sh/uv/#tools) or
|
|
74
|
+
[`pipx`](https://pipx.pypa.io/stable/) which install it in a virtual
|
|
75
|
+
environment).
|
|
76
|
+
|
|
77
|
+
To use, run `buildstream-sbom` in a buildstream project passing in the name of
|
|
78
|
+
elements like you would pass to `bst`. There are two additional required
|
|
79
|
+
arguments `--spdx-name` and `--spdx-namespace`, to set the SPDX document name
|
|
80
|
+
and document namespace respectively. See the [SPDX specification](https://spdx.github.io/spdx-spec/v2.3/document-creation-information/)
|
|
81
|
+
for details.
|
|
82
|
+
|
|
83
|
+
`buildstream-sbom` also accepts some buildstream options, notably `-o/--option`
|
|
84
|
+
to set buildstream options, `-C/--directory` to set the directory containing
|
|
85
|
+
the buildstream project, and `--deps` to choose whether to include only runtime
|
|
86
|
+
dependencies or all dependencies.
|
|
87
|
+
|
|
88
|
+
On the topic of runtime dependencies, two BuildStream core plugins are treated
|
|
89
|
+
specially: `filter` and `compose`. All build dependencies of elements using
|
|
90
|
+
these plugins are considered runtime dependencies. You can set the `depends-on`
|
|
91
|
+
key in the `sbom` public domain data to a list of build dependencies to have
|
|
92
|
+
`buildstream-sbom` treat these build dependencies as runtime dependencies. This
|
|
93
|
+
is useful for a `script` or `manual` element that copies artifacts from a build
|
|
94
|
+
dependency into its own artifact.
|
|
95
|
+
|
|
96
|
+
You can also include licenses that have been extracted from the element's
|
|
97
|
+
artifacts using `--include-licenses`. This uses license information installed
|
|
98
|
+
with tooling such as [Freedesktop-SDK's install-extra script](https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/master/include/install-extra.yml?ref_type=heads). This option can
|
|
99
|
+
also be used in conjunction with `--artifact-checkout-directory` (`-A`), to
|
|
100
|
+
control where element artifacts are checked out to during processing; useful
|
|
101
|
+
for if you are checking out larger artifacts and want to specify a different
|
|
102
|
+
part of a filesystem with more available space (note that artifacts are removed
|
|
103
|
+
once processed anyway to minimise required storage space).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
[build-system]
|
|
6
|
+
requires = ["setuptools", "setuptools-scm"]
|
|
7
|
+
build-backend = "setuptools.build_meta"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "buildstream-sbom"
|
|
11
|
+
dynamic = ["version"]
|
|
12
|
+
description = "A tool to generate SPDX SBoMs from buildstream projects"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"pyyaml",
|
|
16
|
+
]
|
|
17
|
+
requires-python = ">=3.9"
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
buildstream-sbom = "buildstream_sbom.frontend:main"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools_scm]
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from .spdx import generate_spdx
|
|
17
|
+
from .utils import get_hosttool, bst_common_args
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def configure():
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
description="Generate an SPDX formatted SBoM manifest from a BuildStream project"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--spdx-name",
|
|
27
|
+
metavar="NAME",
|
|
28
|
+
help="SPDX document name",
|
|
29
|
+
required=True,
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--spdx-namespace",
|
|
33
|
+
metavar="NAMESPACE",
|
|
34
|
+
help="SPDX document namespace",
|
|
35
|
+
required=True,
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--spdx-comment",
|
|
39
|
+
metavar="COMMENT",
|
|
40
|
+
help="SPDX document comment",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--spdx-creator",
|
|
44
|
+
metavar="CREATOR",
|
|
45
|
+
action="append",
|
|
46
|
+
dest="spdx_creators",
|
|
47
|
+
help="SPDX creator, in the format mandated by SPDX. "
|
|
48
|
+
"Can be passed multiple times for multiple creators.",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"-C",
|
|
53
|
+
"--directory",
|
|
54
|
+
metavar="DIRECTORY",
|
|
55
|
+
help="BuildStream project directory",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"-o",
|
|
60
|
+
"--option",
|
|
61
|
+
metavar="OPTION VALUE",
|
|
62
|
+
nargs=2,
|
|
63
|
+
action="append",
|
|
64
|
+
dest="options",
|
|
65
|
+
default=[],
|
|
66
|
+
help="BuildStream project option",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--output",
|
|
71
|
+
metavar="FILE",
|
|
72
|
+
type=argparse.FileType("w"),
|
|
73
|
+
help="Output filename",
|
|
74
|
+
default="-",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
parser.add_argument("--deps", choices=["all", "run"], default="run")
|
|
78
|
+
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--with-licenses",
|
|
81
|
+
action=argparse.BooleanOptionalAction,
|
|
82
|
+
default=False,
|
|
83
|
+
help="Attempt to extract licenses from build artifacts and add them to the SBOM",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# A lot of artifacts can be checked out so it is a good idea to let users choose where these
|
|
87
|
+
# go in case of storage space limitations
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--artifact-checkout-directory",
|
|
90
|
+
"-A",
|
|
91
|
+
default=Path(os.getcwd()),
|
|
92
|
+
help="Directory to use when checking out artifacts for processing; "
|
|
93
|
+
"the artifacts will be removed once they have been processed "
|
|
94
|
+
"(default: %(default)s)",
|
|
95
|
+
type=Path,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"elements",
|
|
100
|
+
metavar="ELEMENT",
|
|
101
|
+
nargs="+",
|
|
102
|
+
help="Elements to generate SBoM for",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return parser.parse_args()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def extract_element_infos(config):
|
|
109
|
+
element_infos = []
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
result = run_bst_show(config)
|
|
113
|
+
except subprocess.CalledProcessError:
|
|
114
|
+
exit(1)
|
|
115
|
+
|
|
116
|
+
documents = yaml.load_all(result.stdout, yaml.BaseLoader)
|
|
117
|
+
try:
|
|
118
|
+
while True:
|
|
119
|
+
name = next(documents)
|
|
120
|
+
kind = next(documents)
|
|
121
|
+
source_infos = next(documents)
|
|
122
|
+
build_deps = next(documents)
|
|
123
|
+
runtime_deps = next(documents)
|
|
124
|
+
config = next(documents)
|
|
125
|
+
public = next(documents)
|
|
126
|
+
vars = next(documents)
|
|
127
|
+
|
|
128
|
+
element_infos.append(
|
|
129
|
+
{
|
|
130
|
+
"name": name,
|
|
131
|
+
"kind": None if kind == "%{kind}" else kind,
|
|
132
|
+
"source-infos": source_infos,
|
|
133
|
+
"build-deps": build_deps,
|
|
134
|
+
"runtime-deps": runtime_deps,
|
|
135
|
+
"config": config,
|
|
136
|
+
"public": public,
|
|
137
|
+
"vars": vars,
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
except StopIteration:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
return element_infos
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def check_creators(config):
|
|
147
|
+
spdx_creators = ["Person", "Organization", "Tool"]
|
|
148
|
+
valid_creator = True
|
|
149
|
+
for c in config.spdx_creators:
|
|
150
|
+
valid_creator &= any(re.split(":", c)[0] in sc for sc in spdx_creators)
|
|
151
|
+
if not valid_creator:
|
|
152
|
+
sys.exit(
|
|
153
|
+
"ERROR: SPDX creator should follow the SPDX format.\n"
|
|
154
|
+
"See: https://spdx.github.io/spdx-spec/v2.3/document-creation-information/#68-creator-field"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def run_bst_show(config):
|
|
159
|
+
# Python documentation recommends using a full path for the executable
|
|
160
|
+
# https://docs.python.org/3/library/subprocess.html#subprocess.Popen
|
|
161
|
+
bst_cmdline = [get_hosttool("bst")]
|
|
162
|
+
|
|
163
|
+
bst_cmdline += bst_common_args(config)
|
|
164
|
+
|
|
165
|
+
bst_cmdline += [
|
|
166
|
+
"show",
|
|
167
|
+
"--format",
|
|
168
|
+
"---\n%{name}\n---\n'%{kind}'\n---\n%{source-info}\n---\n%{build-deps}\n---\n%{runtime-deps}\n---\n%{config}\n---\n%{public}---\n%{vars}",
|
|
169
|
+
]
|
|
170
|
+
bst_cmdline += config.elements
|
|
171
|
+
|
|
172
|
+
return subprocess.run(bst_cmdline, stdout=subprocess.PIPE, text=True, check=True)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def main():
|
|
176
|
+
config = configure()
|
|
177
|
+
|
|
178
|
+
if config.spdx_creators:
|
|
179
|
+
check_creators(config)
|
|
180
|
+
|
|
181
|
+
element_infos = extract_element_infos(config)
|
|
182
|
+
|
|
183
|
+
spdx = generate_spdx(element_infos, config)
|
|
184
|
+
|
|
185
|
+
with config.output:
|
|
186
|
+
json.dump(spdx, config.output, indent=2)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
main()
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Codethink Limited
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
from argparse import Namespace
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .utils import get_hosttool, bst_common_args
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_license_artifacts(
|
|
17
|
+
element: str,
|
|
18
|
+
artifact_checkout_path: Path,
|
|
19
|
+
licenses_path: Path,
|
|
20
|
+
config: Namespace,
|
|
21
|
+
) -> Path | None:
|
|
22
|
+
"""Use Buildstream artifact checkout to get the installed licenses of an element and outputs the licenses, specified by
|
|
23
|
+
`license_artifact_path`, to the chosen output directory.
|
|
24
|
+
|
|
25
|
+
If the artifact checkout failed then the it will cause the program to exit.
|
|
26
|
+
|
|
27
|
+
Returns a Path to the licenses or None if none were found in the artifacts
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# bst artifact checkout
|
|
31
|
+
bst_cmdline = [get_hosttool("bst")]
|
|
32
|
+
|
|
33
|
+
bst_cmdline += bst_common_args(config)
|
|
34
|
+
|
|
35
|
+
bst_cmdline += [
|
|
36
|
+
"artifact",
|
|
37
|
+
"checkout",
|
|
38
|
+
element,
|
|
39
|
+
"--directory", # Note: This is the artifact checkout directory arg and not the same as the bst project directory
|
|
40
|
+
artifact_checkout_path,
|
|
41
|
+
"--deps",
|
|
42
|
+
"none",
|
|
43
|
+
"--no-integrate",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
r = subprocess.run(bst_cmdline)
|
|
47
|
+
|
|
48
|
+
if r.returncode != 0:
|
|
49
|
+
sys.exit("ERROR: Failed to checkout artifacts")
|
|
50
|
+
|
|
51
|
+
if licenses_path.exists():
|
|
52
|
+
return licenses_path
|
|
53
|
+
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def extract_licenses(
|
|
58
|
+
input_path, element_name, artifact_checkout_path
|
|
59
|
+
) -> list[str] | None:
|
|
60
|
+
"""Attempt to extract licenses from `input_path` using Askalono.
|
|
61
|
+
|
|
62
|
+
Unrecognised licenses automatically trigger warning outputs.
|
|
63
|
+
|
|
64
|
+
If the Akalono command fails for any reason it will cause the program to exit.
|
|
65
|
+
|
|
66
|
+
`element_name` and `artifact_checkout_path` are only used for providing error
|
|
67
|
+
context reporting and have no impact on the license extraction.
|
|
68
|
+
"""
|
|
69
|
+
askalono_bin = get_hosttool("askalono")
|
|
70
|
+
|
|
71
|
+
r = subprocess.run(
|
|
72
|
+
[askalono_bin, "--format", "json", "crawl", str(input_path)],
|
|
73
|
+
stdout=subprocess.PIPE,
|
|
74
|
+
stderr=subprocess.PIPE,
|
|
75
|
+
text=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if r.returncode != 0:
|
|
79
|
+
sys.exit(f"ERROR: Askalono command failed.\n{r.stderr}")
|
|
80
|
+
|
|
81
|
+
license_json = r.stdout
|
|
82
|
+
|
|
83
|
+
# Askalono ouptuts multiple licenses as separate objects but not as an array,
|
|
84
|
+
# need to combine them in to an array
|
|
85
|
+
license_json_split = license_json.splitlines()
|
|
86
|
+
|
|
87
|
+
# use set to avoid duplicates
|
|
88
|
+
licenses = set()
|
|
89
|
+
failures = {}
|
|
90
|
+
for license_object_json in license_json_split:
|
|
91
|
+
license_object = json.loads(license_object_json)
|
|
92
|
+
license_result = license_object.get("result", None)
|
|
93
|
+
if license_result is None:
|
|
94
|
+
offending_file = license_object.get("path", "Missing File Name")
|
|
95
|
+
if offending_file is not None:
|
|
96
|
+
offending_file = offending_file.removeprefix(
|
|
97
|
+
str(artifact_checkout_path)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
files_with_error = failures.setdefault(
|
|
101
|
+
license_object.get("error", "Missing Error"), []
|
|
102
|
+
)
|
|
103
|
+
files_with_error.append(offending_file)
|
|
104
|
+
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
licenses.add(license_result["license"]["name"])
|
|
108
|
+
|
|
109
|
+
for error, files in failures.items():
|
|
110
|
+
# python doesn't allow for '\' in f-string parts so need to use format
|
|
111
|
+
print(
|
|
112
|
+
"{0}: {1} found in\n\t{2}\n".format(
|
|
113
|
+
element_name, error, "\n\t".join(files)
|
|
114
|
+
),
|
|
115
|
+
file=sys.stderr,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if len(licenses) == 0:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
return list(licenses)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def retrieve_licenses(
|
|
125
|
+
element_name: str, artifact_license_path: Path, config: Namespace
|
|
126
|
+
) -> list[str] | None:
|
|
127
|
+
"""Retrieve licenses for an element by checking out its artifacts to `artifact_checkout_root` and scanning
|
|
128
|
+
`licenses_artifact_path` for any identifiable licenses.
|
|
129
|
+
|
|
130
|
+
Returns None if none could be found otherwise returns licenses as in a list
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
# path to use for artifact checkout
|
|
134
|
+
with tempfile.TemporaryDirectory(
|
|
135
|
+
dir=config.artifact_checkout_directory
|
|
136
|
+
) as artifact_checkout_path:
|
|
137
|
+
# path to the licenses inside the artifact
|
|
138
|
+
licenses_path = Path(artifact_checkout_path, artifact_license_path)
|
|
139
|
+
|
|
140
|
+
license_data_path = get_license_artifacts(
|
|
141
|
+
element_name, artifact_checkout_path, licenses_path, config
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if license_data_path is None:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
license_data = extract_licenses(
|
|
148
|
+
license_data_path, element_name, artifact_checkout_path
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return license_data
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from buildstream_sbom.utils import bst_common_args
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .licensing import retrieve_licenses
|
|
15
|
+
|
|
16
|
+
from .utils import get_hosttool
|
|
17
|
+
|
|
18
|
+
# buildstream-sbom supported spdx attributes
|
|
19
|
+
# this could be generic for any key in provenance but being
|
|
20
|
+
# explicit about these attributes guarantees valid SPDX
|
|
21
|
+
SOURCE_PROVENANCE_ATTRIBUTES = {
|
|
22
|
+
"concluded-license": "licenseConcluded",
|
|
23
|
+
"copyright-text": "copyrightText",
|
|
24
|
+
"declared-license": "licenseDeclared",
|
|
25
|
+
"description": "summary",
|
|
26
|
+
"homepage": "homepage",
|
|
27
|
+
"name": "name",
|
|
28
|
+
"originator": "originator",
|
|
29
|
+
"supplier": "supplier",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate_spdx(element_infos, config):
|
|
34
|
+
element_infos = {
|
|
35
|
+
element_info["name"]: element_info for element_info in element_infos
|
|
36
|
+
}
|
|
37
|
+
packages = []
|
|
38
|
+
relationships = []
|
|
39
|
+
|
|
40
|
+
elements_done = set()
|
|
41
|
+
elements_to_do = set()
|
|
42
|
+
|
|
43
|
+
for element in config.elements:
|
|
44
|
+
relationships.append(
|
|
45
|
+
{
|
|
46
|
+
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
47
|
+
"relationshipType": "DESCRIBES",
|
|
48
|
+
"relatedSpdxElement": element_spdxid(element),
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
elements_to_do.add(element)
|
|
52
|
+
|
|
53
|
+
while elements_to_do:
|
|
54
|
+
name = elements_to_do.pop()
|
|
55
|
+
if name in elements_done:
|
|
56
|
+
continue
|
|
57
|
+
elements_done.add(name)
|
|
58
|
+
|
|
59
|
+
elem_info = element_infos[name]
|
|
60
|
+
source_infos = elem_info["source-infos"]
|
|
61
|
+
|
|
62
|
+
element_package, source_packages = generate_spdx_for_element(
|
|
63
|
+
name,
|
|
64
|
+
source_infos,
|
|
65
|
+
elem_info,
|
|
66
|
+
config,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
packages.append(element_package)
|
|
70
|
+
packages.extend(source_packages)
|
|
71
|
+
|
|
72
|
+
for source_package in source_packages:
|
|
73
|
+
relationships.append(
|
|
74
|
+
{
|
|
75
|
+
"spdxElementId": element_spdxid(name),
|
|
76
|
+
"relationshipType": "GENERATED_FROM",
|
|
77
|
+
"relatedSpdxElement": source_package["SPDXID"],
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Order here doesn't matter, as we're sorting the relationships at the end
|
|
82
|
+
all_deps = set(elem_info["runtime-deps"])
|
|
83
|
+
|
|
84
|
+
extra_deps = get_extra_dependencies(elem_info, config.deps == "all", config)
|
|
85
|
+
all_deps.update(extra_deps)
|
|
86
|
+
|
|
87
|
+
for dep in all_deps:
|
|
88
|
+
relationships.append(
|
|
89
|
+
{
|
|
90
|
+
"spdxElementId": element_package["SPDXID"],
|
|
91
|
+
"relationshipType": "DEPENDS_ON",
|
|
92
|
+
"relatedSpdxElement": element_spdxid(dep),
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
elements_to_do.add(dep)
|
|
96
|
+
|
|
97
|
+
def relationship_sort_key(relationship):
|
|
98
|
+
return (
|
|
99
|
+
relationship["spdxElementId"],
|
|
100
|
+
relationship["relatedSpdxElement"],
|
|
101
|
+
relationship["relationshipType"],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
spdx = {
|
|
105
|
+
"spdxVersion": "SPDX-2.3",
|
|
106
|
+
"dataLicense": "CC0-1.0",
|
|
107
|
+
"SPDXID": "SPDXRef-DOCUMENT",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
creation_info = spdx["creationInfo"] = {}
|
|
111
|
+
|
|
112
|
+
spdx["name"] = config.spdx_name
|
|
113
|
+
|
|
114
|
+
spdx["documentNamespace"] = config.spdx_namespace
|
|
115
|
+
|
|
116
|
+
if config.spdx_comment:
|
|
117
|
+
spdx["comment"] = config.spdx_comment
|
|
118
|
+
|
|
119
|
+
if config.spdx_creators:
|
|
120
|
+
creation_info["creators"] = config.spdx_creators
|
|
121
|
+
|
|
122
|
+
created_datetime = datetime.now(timezone.utc)
|
|
123
|
+
# This is a specific variant of isoformat, that python's datetime
|
|
124
|
+
# module doesn't support
|
|
125
|
+
# https://spdx.github.io/spdx-spec/v2.3/document-creation-information/#691-description
|
|
126
|
+
creation_info["created"] = created_datetime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
127
|
+
|
|
128
|
+
spdx["packages"] = sorted(packages, key=lambda pkg: pkg["SPDXID"])
|
|
129
|
+
spdx["relationships"] = sorted(relationships, key=relationship_sort_key)
|
|
130
|
+
|
|
131
|
+
return spdx
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def resolve_element_name(name, config):
|
|
135
|
+
# Python documentation recommends using a full path for the executable
|
|
136
|
+
# https://docs.python.org/3/library/subprocess.html#subprocess.Popen
|
|
137
|
+
bst_cmdline = [get_hosttool("bst")]
|
|
138
|
+
|
|
139
|
+
bst_cmdline += bst_common_args(config)
|
|
140
|
+
|
|
141
|
+
bst_cmdline += ["show", "--deps", "none", "--format", "%{name}", name]
|
|
142
|
+
|
|
143
|
+
return subprocess.run(
|
|
144
|
+
bst_cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def get_extra_dependencies(elem_info, all_deps, config):
|
|
149
|
+
"""
|
|
150
|
+
Return extra dependencies (beyond the element runtime dependencies) that need to be
|
|
151
|
+
included in the SPDX. If `all_deps` is true, then all build dependencies are included.
|
|
152
|
+
Otherwise, it uses heuristics are based on the element kind, or hints in the element's
|
|
153
|
+
public data.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
if all_deps:
|
|
157
|
+
return elem_info["build-deps"]
|
|
158
|
+
|
|
159
|
+
# See if we have explicit configuration in the public data
|
|
160
|
+
public_data = elem_info["public"].get("sbom", {})
|
|
161
|
+
if public_data:
|
|
162
|
+
extra_deps = []
|
|
163
|
+
junction, _, _ = elem_info["name"].rpartition(":")
|
|
164
|
+
for dep_name in public_data.get("depends-on", []):
|
|
165
|
+
# need to account for the junction where the element resides
|
|
166
|
+
if junction:
|
|
167
|
+
result = resolve_element_name(f"{junction}:{dep_name}", config)
|
|
168
|
+
if result.returncode == 0:
|
|
169
|
+
resolved_dep_name = result.stdout.strip()
|
|
170
|
+
else:
|
|
171
|
+
sys.exit(
|
|
172
|
+
f"Could not find dependency {dep_name}, referenced "
|
|
173
|
+
f"from public data of element {elem_info['name']}:\n"
|
|
174
|
+
f"{result.stderr.strip()}"
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
resolved_dep_name = dep_name
|
|
178
|
+
|
|
179
|
+
if resolved_dep_name not in elem_info["build-deps"]:
|
|
180
|
+
deps_str = "\n".join(elem_info["build-deps"])
|
|
181
|
+
sys.exit(
|
|
182
|
+
f"Could not find dependency {dep_name}, referenced "
|
|
183
|
+
f"from public data of element {elem_info['name']} "
|
|
184
|
+
f"(resolved to {resolved_dep_name}). "
|
|
185
|
+
f"Dependency must be one of:\n{deps_str}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
extra_deps.append(resolved_dep_name)
|
|
189
|
+
return extra_deps
|
|
190
|
+
|
|
191
|
+
if include_build_deps(elem_info["kind"], elem_info["config"].keys()):
|
|
192
|
+
return elem_info["build-deps"]
|
|
193
|
+
|
|
194
|
+
return []
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def include_build_deps(kind, config_keys):
|
|
198
|
+
"""
|
|
199
|
+
Returns whether build dependencies should be always included even when generating
|
|
200
|
+
an SBoM with runtime dependencies. This currently means the compose and filter
|
|
201
|
+
elements.
|
|
202
|
+
"""
|
|
203
|
+
if kind is None:
|
|
204
|
+
# Try to guess the element kind depending on its configuration
|
|
205
|
+
if config_keys == set(
|
|
206
|
+
["include", "exclude", "include-orphans", "pass-integration"]
|
|
207
|
+
):
|
|
208
|
+
kind = "filter"
|
|
209
|
+
if config_keys == set(["include", "exclude", "include-orphans", "integrate"]):
|
|
210
|
+
kind = "compose"
|
|
211
|
+
|
|
212
|
+
return kind in ("filter", "compose")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def element_licenses(name: str, elem_info, config):
|
|
216
|
+
license_dir: str = elem_info["vars"].get("licensedir", None)
|
|
217
|
+
|
|
218
|
+
if license_dir is not None:
|
|
219
|
+
license_dir = Path(license_dir.lstrip(os.sep))
|
|
220
|
+
|
|
221
|
+
licenses = retrieve_licenses(name, license_dir, config)
|
|
222
|
+
if licenses is None:
|
|
223
|
+
print(f"{name}: Skipping: No licenses found", file=sys.stderr)
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
return " AND ".join(licenses)
|
|
227
|
+
else:
|
|
228
|
+
print(
|
|
229
|
+
f"Could not find license directory for {name}, skipping licenses.",
|
|
230
|
+
file=sys.stderr,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def generate_spdx_for_element(
|
|
237
|
+
name,
|
|
238
|
+
source_infos,
|
|
239
|
+
elem_info,
|
|
240
|
+
config,
|
|
241
|
+
):
|
|
242
|
+
"""
|
|
243
|
+
Generate SPDX package information from the passed-in module, which is an
|
|
244
|
+
entry from a new style manifest (using the source provenance API)
|
|
245
|
+
"""
|
|
246
|
+
package_name = element_package_name(name)
|
|
247
|
+
spdx_name = element_spdxid(name)
|
|
248
|
+
|
|
249
|
+
element_package = {
|
|
250
|
+
"name": package_name,
|
|
251
|
+
"SPDXID": spdx_name,
|
|
252
|
+
"filesAnalyzed": False,
|
|
253
|
+
"downloadLocation": "NOASSERTION",
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
element_ext_refs = []
|
|
257
|
+
bst_element_name = {
|
|
258
|
+
"referenceCategory": "OTHER",
|
|
259
|
+
"referenceType": "bst-element",
|
|
260
|
+
"referenceLocator": f"{name}",
|
|
261
|
+
}
|
|
262
|
+
element_ext_refs.append(bst_element_name)
|
|
263
|
+
|
|
264
|
+
element_package["externalRefs"] = element_ext_refs
|
|
265
|
+
|
|
266
|
+
if config.with_licenses:
|
|
267
|
+
licenses = element_licenses(name, elem_info, config)
|
|
268
|
+
if licenses is not None:
|
|
269
|
+
element_package["licenseDeclared"] = licenses
|
|
270
|
+
|
|
271
|
+
source_packages = []
|
|
272
|
+
|
|
273
|
+
for idx, source in enumerate(source_infos):
|
|
274
|
+
package = {
|
|
275
|
+
"SPDXID": f"{spdx_name}-{idx}",
|
|
276
|
+
"filesAnalyzed": False,
|
|
277
|
+
}
|
|
278
|
+
source_ext_refs = []
|
|
279
|
+
|
|
280
|
+
if "provenance" in source:
|
|
281
|
+
provenance = source["provenance"]
|
|
282
|
+
for attribute, spdx_field in SOURCE_PROVENANCE_ATTRIBUTES.items():
|
|
283
|
+
if attribute in provenance:
|
|
284
|
+
package[spdx_field] = provenance[attribute]
|
|
285
|
+
|
|
286
|
+
package.setdefault("name", source_package_name(source))
|
|
287
|
+
|
|
288
|
+
package["downloadLocation"] = source["url"]
|
|
289
|
+
if "version-guess" in source:
|
|
290
|
+
package["versionInfo"] = source["version-guess"]
|
|
291
|
+
else:
|
|
292
|
+
package["versionInfo"] = source["version"]
|
|
293
|
+
|
|
294
|
+
package["sourceInfo"] = source["kind"]
|
|
295
|
+
|
|
296
|
+
source_ext_refs.append(bst_element_name)
|
|
297
|
+
package["externalRefs"] = source_ext_refs
|
|
298
|
+
|
|
299
|
+
source_packages.append(package)
|
|
300
|
+
|
|
301
|
+
return element_package, source_packages
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def element_spdxid(name):
|
|
305
|
+
# SPDXID can only contain letters, numbers, `.`, and `-`
|
|
306
|
+
not_allowed = "[^a-zA-Z0-9.-]"
|
|
307
|
+
spdx_name = re.sub(not_allowed, "-", name)
|
|
308
|
+
return f"SPDXRef-{spdx_name}"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def element_package_name(name):
|
|
312
|
+
element_name = name.split(":")[-1]
|
|
313
|
+
return os.path.splitext(os.path.basename(element_name))[0]
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def source_package_name(source_info):
|
|
317
|
+
basename = os.path.basename(source_info["url"])
|
|
318
|
+
|
|
319
|
+
if "extra-data" in source_info and "crate-name" in source_info["extra-data"]:
|
|
320
|
+
return source_info["extra-data"]["crate-name"]
|
|
321
|
+
|
|
322
|
+
if source_info["medium"] == "git":
|
|
323
|
+
if basename.endswith(".git"):
|
|
324
|
+
return basename[: -len(".git")]
|
|
325
|
+
return basename
|
|
326
|
+
elif source_info["medium"] in ("remote-file", "archive"):
|
|
327
|
+
stem = re.split(r"\.(tar|tgz|tbz|zip|crate)", basename)[0]
|
|
328
|
+
package = re.split("[-_][0-9]", stem)[0]
|
|
329
|
+
return package
|
|
330
|
+
elif source_info["medium"] == "local":
|
|
331
|
+
return basename
|
|
332
|
+
else:
|
|
333
|
+
print(
|
|
334
|
+
f"Can't extract a package name from source info {source_info}",
|
|
335
|
+
file=sys.stderr,
|
|
336
|
+
)
|
|
337
|
+
return "UNKNOWN"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Codethink Limited
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from argparse import Namespace
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_hosttool(name: str):
|
|
11
|
+
"""Returns the path to the executable of the specified command.
|
|
12
|
+
|
|
13
|
+
Errors if the command could not be found.
|
|
14
|
+
"""
|
|
15
|
+
command = shutil.which(name)
|
|
16
|
+
if command is None:
|
|
17
|
+
sys.exit(f"`{name}` is not installed or could not be found.")
|
|
18
|
+
|
|
19
|
+
return command
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def bst_common_args(config: Namespace) -> list[str]:
|
|
23
|
+
args = []
|
|
24
|
+
if config.directory:
|
|
25
|
+
args += ["--directory", config.directory]
|
|
26
|
+
|
|
27
|
+
for option, value in config.options:
|
|
28
|
+
args += ["--option", option, value]
|
|
29
|
+
|
|
30
|
+
return args
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: buildstream-sbom
|
|
3
|
+
Version: 1.1
|
|
4
|
+
Summary: A tool to generate SPDX SBoMs from buildstream projects
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pyyaml
|
|
8
|
+
|
|
9
|
+
<!--
|
|
10
|
+
SPDX-FileCopyrightText: 2025 Codethink Limited
|
|
11
|
+
|
|
12
|
+
SPDX-License-Identifier: Apache-2.0
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
# BuildStream SBOM generator
|
|
16
|
+
|
|
17
|
+
This tool can be used to produce an SBoM (Software Bill of Materials)
|
|
18
|
+
describing a BuildStream element and its dependencies. It currently
|
|
19
|
+
generates [SPDX](https://spdx.dev/) version 2.3.
|
|
20
|
+
|
|
21
|
+
The manifest contains useful information, such as the package name, version,
|
|
22
|
+
source locations and dependencies.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Source provenance data
|
|
26
|
+
|
|
27
|
+
This tool relies on the [Source Provenance API](https://docs.buildstream.build/2.5/buildstream.source.html#generating-sourceinfo-for-provenance-information)
|
|
28
|
+
introduced in BuildStream 2.5. This API is implemented by buildstream-plugins
|
|
29
|
+
version 2.5.0 and buildstream-plugins-community 2.1.0. Please make sure your
|
|
30
|
+
project is using those (or more recent) versions. If your project uses custom
|
|
31
|
+
source plugins, please make sure that they also implement this API.
|
|
32
|
+
|
|
33
|
+
Version guessing is handled by individual source plugins, please check the
|
|
34
|
+
individual plugin documentation for details. Most plugins that implement it
|
|
35
|
+
do something similar to what [DownloadableFileSource](https://docs.buildstream.build/2.5/buildstream.downloadablefilesource.html#core-downloadable-source-builtins)
|
|
36
|
+
does, so it is a good starting point for understanding how this works.
|
|
37
|
+
|
|
38
|
+
The list of currently supported source provenance attributes that can be
|
|
39
|
+
specified and used for a Buildstream project is as follows:
|
|
40
|
+
|
|
41
|
+
| Attribute name | Corresponding SPDX attribute |
|
|
42
|
+
| -------------- | ---------------------------- |
|
|
43
|
+
| concluded-license | [licenseConcluded](https://spdx.github.io/spdx-spec/v2.3/package-information/#713-concluded-license-field) |
|
|
44
|
+
| copyright-text | [copyrightText](https://spdx.github.io/spdx-spec/v2.3/package-information/#717-copyright-text-field) |
|
|
45
|
+
| declared-license | [licenseDeclared](https://spdx.github.io/spdx-spec/v2.3/package-information/#715-declared-license-field) |
|
|
46
|
+
| description | [summary](https://spdx.github.io/spdx-spec/v2.3/package-information/#718-package-summary-description-field) |
|
|
47
|
+
| homepage | [homepage](https://spdx.github.io/spdx-spec/v2.3/package-information/#711-package-home-page-field) |
|
|
48
|
+
| name | [name](https://spdx.github.io/spdx-spec/v2.3/package-information/#71-package-name-field) |
|
|
49
|
+
| originator | [originator](https://spdx.github.io/spdx-spec/v2.3/package-information/#76-package-originator-field) |
|
|
50
|
+
| supplier | [supplier](https://spdx.github.io/spdx-spec/v2.3/package-information/#75-package-supplier-field) |
|
|
51
|
+
|
|
52
|
+
These can be used in projects by use of the `source-provenance-attributes`
|
|
53
|
+
field in the project.conf, this is described in the provenance section of
|
|
54
|
+
the [BuildStream documentation](https://docs.buildstream.build/master/buildstream.source.html#built-in-functionality).
|
|
55
|
+
|
|
56
|
+
### source-provenance-attributes snippet
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
# project.conf
|
|
60
|
+
|
|
61
|
+
source-provenance-attributes:
|
|
62
|
+
concluded-license: The license as determined by the evidence provided by the source project
|
|
63
|
+
copyright-text: Copyright text defined by the source project
|
|
64
|
+
declared-license: The license of the source project as decided by the authors
|
|
65
|
+
description: Description of the source project
|
|
66
|
+
homepage: The URL of the source project's homepage
|
|
67
|
+
name: The name of the source project
|
|
68
|
+
originator: The name of the person or organisation that created the source package originally
|
|
69
|
+
supplier: The name of the person or organisation that provided the source package
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Should any BuildStream plugins implement tracking for source provenance attributes, similar to
|
|
73
|
+
[source tracking](https://docs.buildstream.build/master/using_commands.html#bst-source-track),
|
|
74
|
+
it is recommended for all projects and plugins to use the attribute names exactly as seen above.
|
|
75
|
+
This ensures the source provenance attributes are always generated identically between different
|
|
76
|
+
plugins and makes sure they align with projects' definitions in the same way.
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
To install, clone this repository and install it using `pip` (or preferably a
|
|
81
|
+
tool like [`uv tool`](https://docs.astral.sh/uv/#tools) or
|
|
82
|
+
[`pipx`](https://pipx.pypa.io/stable/) which install it in a virtual
|
|
83
|
+
environment).
|
|
84
|
+
|
|
85
|
+
To use, run `buildstream-sbom` in a buildstream project passing in the name of
|
|
86
|
+
elements like you would pass to `bst`. There are two additional required
|
|
87
|
+
arguments `--spdx-name` and `--spdx-namespace`, to set the SPDX document name
|
|
88
|
+
and document namespace respectively. See the [SPDX specification](https://spdx.github.io/spdx-spec/v2.3/document-creation-information/)
|
|
89
|
+
for details.
|
|
90
|
+
|
|
91
|
+
`buildstream-sbom` also accepts some buildstream options, notably `-o/--option`
|
|
92
|
+
to set buildstream options, `-C/--directory` to set the directory containing
|
|
93
|
+
the buildstream project, and `--deps` to choose whether to include only runtime
|
|
94
|
+
dependencies or all dependencies.
|
|
95
|
+
|
|
96
|
+
On the topic of runtime dependencies, two BuildStream core plugins are treated
|
|
97
|
+
specially: `filter` and `compose`. All build dependencies of elements using
|
|
98
|
+
these plugins are considered runtime dependencies. You can set the `depends-on`
|
|
99
|
+
key in the `sbom` public domain data to a list of build dependencies to have
|
|
100
|
+
`buildstream-sbom` treat these build dependencies as runtime dependencies. This
|
|
101
|
+
is useful for a `script` or `manual` element that copies artifacts from a build
|
|
102
|
+
dependency into its own artifact.
|
|
103
|
+
|
|
104
|
+
You can also include licenses that have been extracted from the element's
|
|
105
|
+
artifacts using `--include-licenses`. This uses license information installed
|
|
106
|
+
with tooling such as [Freedesktop-SDK's install-extra script](https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/master/include/install-extra.yml?ref_type=heads). This option can
|
|
107
|
+
also be used in conjunction with `--artifact-checkout-directory` (`-A`), to
|
|
108
|
+
control where element artifacts are checked out to during processing; useful
|
|
109
|
+
for if you are checking out larger artifacts and want to specify a different
|
|
110
|
+
part of a filesystem with more available space (note that artifacts are removed
|
|
111
|
+
once processed anyway to minimise required storage space).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
.gitlab-ci.yml
|
|
3
|
+
CHANGELOG.md
|
|
4
|
+
README.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
LICENSES/Apache-2.0.txt
|
|
7
|
+
src/buildstream_sbom/frontend.py
|
|
8
|
+
src/buildstream_sbom/licensing.py
|
|
9
|
+
src/buildstream_sbom/spdx.py
|
|
10
|
+
src/buildstream_sbom/utils.py
|
|
11
|
+
src/buildstream_sbom.egg-info/PKG-INFO
|
|
12
|
+
src/buildstream_sbom.egg-info/SOURCES.txt
|
|
13
|
+
src/buildstream_sbom.egg-info/dependency_links.txt
|
|
14
|
+
src/buildstream_sbom.egg-info/entry_points.txt
|
|
15
|
+
src/buildstream_sbom.egg-info/requires.txt
|
|
16
|
+
src/buildstream_sbom.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyyaml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
buildstream_sbom
|