├── metadata ├── purl │ ├── private │ │ ├── BUILD │ │ ├── tables_test.bzl │ │ └── tables.bzl │ ├── BUILD │ ├── percent_encoding.bzl │ ├── string.bzl │ ├── purl.bzl │ └── percent_encoding_test.bzl ├── README.md ├── MODULE.bazel ├── licenses │ ├── providers │ │ ├── BUILD │ │ └── license_kind_info.bzl │ ├── BUILD │ ├── rules │ │ ├── BUILD │ │ ├── license_kind.bzl │ │ └── license.bzl │ └── defs.bzl ├── rules │ ├── BUILD │ └── package_metadata.bzl ├── providers │ ├── BUILD │ ├── package_metadata_toolchain_info.bzl │ ├── package_metadata_override_info.bzl │ ├── package_metadata_info.bzl │ └── package_attribute_info.bzl ├── BUILD └── defs.bzl ├── examples ├── lib │ ├── private_lib.h │ ├── private_lib.cc │ └── BUILD ├── vendor │ ├── legacy_license │ │ ├── foo.cc │ │ ├── COPYING │ │ └── BUILD │ ├── libhhgttg │ │ ├── answer.cc │ │ ├── LICENSE │ │ └── BUILD │ ├── acme │ │ ├── coyote.cc │ │ ├── ACME_LICENSE │ │ └── BUILD │ ├── constant_gen │ │ ├── LICENSE │ │ ├── LICENSE.on_output │ │ ├── constant_generator.py │ │ ├── verify_licenses_test.py │ │ ├── defs.bzl │ │ └── BUILD │ └── README.md ├── sbom │ ├── BUILD │ └── sbom.bzl ├── sample_reports │ ├── BUILD │ └── licenses_used.bzl ├── README.md ├── BUILD ├── server │ ├── server.cc │ ├── server_sbom.golden │ └── BUILD ├── MODULE.bazel └── my_org │ └── licenses │ └── BUILD ├── renovate.json ├── docs ├── README.md ├── BUILD ├── .gitignore ├── metadata │ ├── BUILD │ ├── purl │ │ ├── BUILD │ │ └── purl.md │ ├── licenses │ │ ├── BUILD │ │ ├── providers │ │ │ ├── BUILD │ │ │ └── license_kind_info.md │ │ ├── rules │ │ │ ├── BUILD │ │ │ ├── license.md │ │ │ └── license_kind.md │ │ └── defs.md │ ├── rules │ │ ├── BUILD │ │ └── package_metadata.md │ ├── providers │ │ ├── BUILD │ │ ├── package_metadata_toolchain_info.md │ │ ├── package_metadata_info.md │ │ ├── package_metadata_override_info.md │ │ └── package_attribute_info.md │ ├── defs.md │ └── README.md ├── supply_chain_tools │ └── gather_metadata │ │ ├── BUILD │ │ └── gather_metadata.md └── MODULE.bazel ├── lib └── supplychain-go │ ├── internal │ └── sbom │ │ ├── genconfig.go │ │ └── BUILD.bazel │ ├── .gitignore │ ├── label │ ├── BUILD │ ├── simple.go │ └── label.go │ ├── package_attribute.go │ ├── go.mod │ ├── MODULE.bazel │ ├── cmd │ └── spdx │ │ ├── BUILD.bazel │ │ └── spdx.go │ ├── BUILD │ ├── go.sum │ ├── package_metadata.go │ └── package_metadata_test.go ├── .bcr ├── metadata │ ├── source.template.json │ ├── presubmit.yml │ └── metadata.template.json ├── lib │ └── supplychain-go │ │ ├── source.template.json │ │ ├── presubmit.yml │ │ └── metadata.template.json ├── config.yml └── README.md ├── .gitignore ├── tools ├── gather_metadata │ ├── README.md │ ├── providers.bzl │ ├── BUILD │ ├── trace.bzl │ ├── rule_filters.bzl │ ├── core.bzl │ └── gather_metadata.bzl ├── sbom │ ├── providers.bzl │ ├── BUILD │ ├── spdx.bzl │ └── sbom.bzl ├── MODULE.bazel └── BUILD ├── rules_license ├── MODULE.bazel ├── README.md ├── rules │ ├── BUILD │ ├── current_module_package_info.bzl │ ├── license_kind.bzl │ ├── providers.bzl │ ├── license_impl.bzl │ ├── package_info.bzl │ └── license.bzl ├── BUILD └── licenses │ └── generic │ └── BUILD ├── .github └── workflows │ ├── ci.sh │ ├── release_prep.sh │ ├── ci.yml │ └── release.yml ├── CONTRIBUTING.md ├── metadata-extensions ├── BUILD └── MODULE.bazel ├── README.md └── maintainers └── tools ├── update_spdx_list └── main.py └── purl_tables └── main.py /metadata/purl/private/BUILD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/lib/private_lib.h: -------------------------------------------------------------------------------- 1 | extern int forty_two(); 2 | -------------------------------------------------------------------------------- /examples/vendor/legacy_license/foo.cc: -------------------------------------------------------------------------------- 1 | int foo = 42; 2 | -------------------------------------------------------------------------------- /examples/vendor/libhhgttg/answer.cc: -------------------------------------------------------------------------------- 1 | int answer = 42; 2 | -------------------------------------------------------------------------------- /examples/vendor/acme/coyote.cc: -------------------------------------------------------------------------------- 1 | bool caught_road_runner() { 2 | return false; 3 | } 4 | -------------------------------------------------------------------------------- /examples/vendor/legacy_license/COPYING: -------------------------------------------------------------------------------- 1 | The license file for //examples/vendor/legacy_license 2 | -------------------------------------------------------------------------------- /examples/vendor/constant_gen/LICENSE: -------------------------------------------------------------------------------- 1 | This code is provided under a license which contains some restrictions. 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "enabled": false 4 | } 5 | -------------------------------------------------------------------------------- /examples/vendor/constant_gen/LICENSE.on_output: -------------------------------------------------------------------------------- 1 | The generated output from constant_gen has no license encumberances. 2 | -------------------------------------------------------------------------------- /examples/sbom/BUILD: -------------------------------------------------------------------------------- 1 | # Examples of metadata reporting tools 2 | # 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | -------------------------------------------------------------------------------- /examples/sample_reports/BUILD: -------------------------------------------------------------------------------- 1 | # Examples of metadata reporting tools 2 | # 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | -------------------------------------------------------------------------------- /examples/vendor/libhhgttg/LICENSE: -------------------------------------------------------------------------------- 1 | You can do whatever you want with this software. Just incude this license 2 | with your distribution. 3 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Supply-chain rules for Bazel 2 | 3 | ## Modules 4 | 5 | - [@package_metadata](./metadata) 6 | - [@supply_chain_tools](./tools) 7 | -------------------------------------------------------------------------------- /examples/lib/private_lib.cc: -------------------------------------------------------------------------------- 1 | // This is purely internal code to my company. 2 | 3 | #include "private_lib.h" 4 | 5 | int forty_two() { return 42; } 6 | -------------------------------------------------------------------------------- /examples/vendor/README.md: -------------------------------------------------------------------------------- 1 | # Third party packges used by your project. 2 | 3 | Note that these are presumed to be vendored in to your source tree. 4 | -------------------------------------------------------------------------------- /metadata/README.md: -------------------------------------------------------------------------------- 1 | # Module `@package_metadata` 2 | 3 | Bazel module for injecting supply-chain metadata into build graphs. 4 | 5 | - [Documentation](../docs/metadata) 6 | -------------------------------------------------------------------------------- /docs/BUILD: -------------------------------------------------------------------------------- 1 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 2 | 3 | package_metadata( 4 | name = "package_metadata", 5 | purl = "pkg:github/bazel-contrib/supply-chain@HEAD#docs", 6 | ) 7 | -------------------------------------------------------------------------------- /lib/supplychain-go/internal/sbom/genconfig.go: -------------------------------------------------------------------------------- 1 | package sbom 2 | 3 | type GenConfig struct { 4 | Deps []DepConfig `json:"deps"` 5 | } 6 | 7 | type DepConfig struct { 8 | Metadata string `json:"metadata"` 9 | } 10 | -------------------------------------------------------------------------------- /.bcr/metadata/source.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrity": "**leave this alone**", 3 | "strip_prefix": "{REPO}-{VERSION}/metadata", 4 | "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz" 5 | } 6 | -------------------------------------------------------------------------------- /examples/vendor/acme/ACME_LICENSE: -------------------------------------------------------------------------------- 1 | Acme Novelty & Software provides a license to this software under terms laid 2 | out in specific contracts. Unless you have purchased a license from us, you may 3 | not use this software for any purpose. 4 | -------------------------------------------------------------------------------- /.bcr/lib/supplychain-go/source.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrity": "**leave this alone**", 3 | "strip_prefix": "{REPO}-{VERSION}/lib/supplychain-go", 4 | "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz" 5 | } 6 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Bazel convenience symlinks. 2 | /bazel-* 3 | 4 | # bzlmod 5 | /MODULE.bazel.lock 6 | 7 | # VSCode 8 | /.vscode 9 | 10 | # JetBrains IDEs- 11 | /.ijwb 12 | /.clwb 13 | /.idea 14 | 15 | # macOS-specific excludes 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.bcr/config.yml: -------------------------------------------------------------------------------- 1 | # A list of Bazel modules we want to publish. 2 | # 3 | # Each module must have a directory in `github.com/bazel-contrib/supply-chain/.bcr` with the configuration to publish the module. 4 | moduleRoots: 5 | - "lib/supplychain-go" 6 | - "metadata" 7 | -------------------------------------------------------------------------------- /lib/supplychain-go/.gitignore: -------------------------------------------------------------------------------- 1 | # Bazel convenience symlinks. 2 | /bazel-* 3 | 4 | # bzlmod 5 | /MODULE.bazel.lock 6 | 7 | # VSCode 8 | /.vscode 9 | 10 | # JetBrains IDEs- 11 | /.ijwb 12 | /.clwb 13 | /.idea 14 | 15 | # macOS-specific excludes 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /metadata/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "package_metadata", 3 | version = "", # Automatically updated by release pipeline. 4 | ) 5 | 6 | # This is a fundamental module that's depended on by virtually every bazel 7 | # module and **MUST NOT** have any dependencies. 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Bazel convenience symlinks. 2 | */bazel-* 3 | 4 | # bzlmod 5 | */MODULE.bazel.lock 6 | 7 | # VSCode 8 | */.vscode 9 | 10 | # JetBrains IDEs- 11 | */.ijwb 12 | */.clwb 13 | */.idea 14 | 15 | # macOS-specific excludes 16 | .DS_Store 17 | 18 | # Editor temporaries 19 | *.swp 20 | -------------------------------------------------------------------------------- /tools/gather_metadata/README.md: -------------------------------------------------------------------------------- 1 | # Rules and aspects to gather gather package_metadata 2 | 3 | This folder contains tools used to walk dependency trees and gather 4 | metadata providers (such as license information) or similar providers 5 | specified by `default_package_metadata` and `applicable_licenses`. 6 | -------------------------------------------------------------------------------- /lib/supplychain-go/internal/sbom/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "sbom", 5 | srcs = ["genconfig.go"], 6 | importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/internal/sbom", 7 | visibility = ["//:__subpackages__"], 8 | ) 9 | -------------------------------------------------------------------------------- /metadata/licenses/providers/BUILD: -------------------------------------------------------------------------------- 1 | exports_files( 2 | [ 3 | "license_kind_info.bzl", 4 | ], 5 | visibility = ["//visibility:public"], 6 | ) 7 | 8 | filegroup( 9 | name = "srcs", 10 | srcs = [ 11 | "license_kind_info.bzl", 12 | ], 13 | visibility = ["//visibility:public"], 14 | ) 15 | -------------------------------------------------------------------------------- /lib/supplychain-go/label/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "label", 5 | srcs = [ 6 | "label.go", 7 | "simple.go", 8 | ], 9 | importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/label", 10 | visibility = ["//visibility:public"], 11 | ) 12 | -------------------------------------------------------------------------------- /.bcr/README.md: -------------------------------------------------------------------------------- 1 | # Bazel Central Registry 2 | 3 | This directory contains configuration files to automatically publish releases 4 | (`"git tags"`) to the [Bazel Central Registry](https://registry.bazel.build). 5 | 6 | See 7 | for authoritative documentation about these files. 8 | -------------------------------------------------------------------------------- /.bcr/lib/supplychain-go/presubmit.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | platform: 3 | - debian10 4 | - ubuntu2004 5 | - macos 6 | - windows 7 | bazel: 8 | - 8.x 9 | - 7.x 10 | tasks: 11 | verify_targets: 12 | name: Verify build targets 13 | platform: ${{ platform }} 14 | bazel: ${{ bazel }} 15 | build_targets: 16 | - '@supply-chain-go//...' 17 | -------------------------------------------------------------------------------- /metadata/rules/BUILD: -------------------------------------------------------------------------------- 1 | exports_files( 2 | [ 3 | "package_metadata.bzl", 4 | ], 5 | visibility = ["//visibility:public"], 6 | ) 7 | 8 | filegroup( 9 | name = "srcs", 10 | srcs = [ 11 | "package_metadata.bzl", 12 | ] + [ 13 | "//providers:srcs", 14 | ], 15 | visibility = ["//visibility:public"], 16 | ) 17 | -------------------------------------------------------------------------------- /.bcr/metadata/presubmit.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | platform: 3 | - debian10 4 | - ubuntu2004 5 | - macos 6 | - windows 7 | bazel: 8 | - 8.x 9 | - 7.x 10 | - 6.x 11 | tasks: 12 | verify_targets: 13 | name: Verify build targets 14 | platform: ${{ platform }} 15 | bazel: ${{ bazel }} 16 | build_targets: 17 | - '@package_metadata//...' 18 | -------------------------------------------------------------------------------- /rules_license/MODULE.bazel: -------------------------------------------------------------------------------- 1 | # This is a modified rules_license. 2 | # It is intended to be used in conjunction with bazel-contrib/supply-chain. 3 | 4 | module( 5 | name = "rules_license", 6 | ) 7 | 8 | bazel_dep(name = "package_metadata", version = "0.0.5") 9 | local_path_override( 10 | module_name = "package_metadata", 11 | path = "../metadata", 12 | ) 13 | -------------------------------------------------------------------------------- /metadata/licenses/BUILD: -------------------------------------------------------------------------------- 1 | exports_files( 2 | [ 3 | "defs.bzl", 4 | ], 5 | visibility = ["//visibility:public"], 6 | ) 7 | 8 | filegroup( 9 | name = "srcs", 10 | srcs = [ 11 | "defs.bzl", 12 | ] + [ 13 | "//licenses/providers:srcs", 14 | "//licenses/rules:srcs", 15 | ], 16 | visibility = ["//visibility:public"], 17 | ) 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o pipefail 4 | 5 | bazel test //... 6 | readonly exit_code="$?" 7 | 8 | case "${exit_code}" in 9 | "4") 10 | # Status code indicates that the build succeeded but there were no tests to 11 | # run. Ignore and exit successfully. 12 | exit "0" 13 | ;; 14 | 15 | *) 16 | exit "${exit_code}" 17 | ;; 18 | esac 19 | -------------------------------------------------------------------------------- /lib/supplychain-go/label/simple.go: -------------------------------------------------------------------------------- 1 | package label 2 | 3 | /* 4 | * Label implementation 5 | */ 6 | var _ Label = (*simple)(nil) 7 | 8 | // simple is a simple implementation of `Label` using a simple string internally. 9 | type simple struct { 10 | Label string 11 | } 12 | 13 | func (s *simple) labelPrivate() {} 14 | 15 | func (s *simple) String() string { 16 | return s.Label 17 | } 18 | -------------------------------------------------------------------------------- /lib/supplychain-go/package_attribute.go: -------------------------------------------------------------------------------- 1 | package supplychain 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // PackageAttributeDescriptor describes an attribute of `PackageMetadata`. 8 | type PackageAttributeDescriptor[T any] struct { 9 | // Kind is the identifier of the attribute. 10 | Kind string 11 | 12 | // Parser is a parser for the attribute. 13 | Parser func(r io.Reader) (*T, error) 14 | } 15 | -------------------------------------------------------------------------------- /tools/sbom/providers.bzl: -------------------------------------------------------------------------------- 1 | """Providers for the sbom rules. 2 | 3 | Warning: This is private to the aspect that walks the tree. The API is subject 4 | to change at any release. 5 | """ 6 | 7 | SbomInfo = provider( 8 | doc = "A provider that contains the configuration for generating an SBOM.", 9 | fields = { 10 | "config": "The configuration file for generating the SBOM.", 11 | } 12 | ) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Releasing 4 | 5 | If you need control over the next release version, for example when making a release candidate for a new major, 6 | then: tag the repo and push the tag, for example 7 | 8 | ```sh 9 | % git fetch 10 | % git tag v1.0.0-rc0 origin/main 11 | % git push origin v1.0.0-rc0 12 | ``` 13 | 14 | Then watch the automation run on GitHub actions which creates the release. 15 | -------------------------------------------------------------------------------- /docs/metadata/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | stardoc( 5 | name = "defs", 6 | out = "defs.generated.md", 7 | input = "@package_metadata//:defs.bzl", 8 | deps = [ 9 | "@package_metadata//:srcs", 10 | ], 11 | ) 12 | 13 | diff_test( 14 | name = "defs_test", 15 | file1 = ":defs", 16 | file2 = "defs.md", 17 | ) 18 | -------------------------------------------------------------------------------- /docs/metadata/purl/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | stardoc( 5 | name = "purl", 6 | out = "purl.generated.md", 7 | input = "@package_metadata//purl:purl.bzl", 8 | deps = [ 9 | "@package_metadata//purl:srcs", 10 | ], 11 | ) 12 | 13 | diff_test( 14 | name = "purl_test", 15 | file1 = ":purl", 16 | file2 = "purl.md", 17 | ) 18 | -------------------------------------------------------------------------------- /metadata/licenses/rules/BUILD: -------------------------------------------------------------------------------- 1 | exports_files( 2 | [ 3 | "license.bzl", 4 | "license_kind.bzl", 5 | ], 6 | visibility = ["//visibility:public"], 7 | ) 8 | 9 | filegroup( 10 | name = "srcs", 11 | srcs = [ 12 | "license.bzl", 13 | "license_kind.bzl", 14 | ] + [ 15 | "//licenses/providers:srcs", 16 | "//providers:srcs", 17 | ], 18 | visibility = ["//visibility:public"], 19 | ) 20 | -------------------------------------------------------------------------------- /docs/metadata/licenses/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | stardoc( 5 | name = "defs", 6 | out = "defs.generated.md", 7 | input = "@package_metadata//licenses:defs.bzl", 8 | deps = [ 9 | "@package_metadata//licenses:srcs", 10 | ], 11 | ) 12 | 13 | diff_test( 14 | name = "defs_test", 15 | file1 = ":defs", 16 | file2 = "defs.md", 17 | ) 18 | -------------------------------------------------------------------------------- /metadata/licenses/defs.bzl: -------------------------------------------------------------------------------- 1 | """Public API of `@package_metadata//licenses`.""" 2 | 3 | load("//licenses/providers:license_kind_info.bzl", _LicenseKindInfo = "LicenseKindInfo") 4 | load("//licenses/rules:license.bzl", _license = "license") 5 | load("//licenses/rules:license_kind.bzl", _license_kind = "license_kind") 6 | 7 | visibility("public") 8 | 9 | # Providers. 10 | LicenseKindInfo = _LicenseKindInfo 11 | 12 | # Rules 13 | license = _license 14 | license_kind = _license_kind 15 | -------------------------------------------------------------------------------- /examples/lib/BUILD: -------------------------------------------------------------------------------- 1 | # Examples of applications and interactions with licenses 2 | 3 | load("@rules_cc//cc:cc_library.bzl", "cc_library") 4 | 5 | package( 6 | default_visibility = ["//visibility:public"], 7 | ) 8 | 9 | cc_library( 10 | name = "private_lib", 11 | srcs = [ 12 | "private_lib.cc", 13 | ], 14 | hdrs = [ 15 | "private_lib.h", 16 | ], 17 | ) 18 | 19 | cc_library( 20 | name = "lib_with_third_party_deps", 21 | deps = [ 22 | "//vendor/acme", 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /metadata/purl/BUILD: -------------------------------------------------------------------------------- 1 | load("//purl:percent_encoding_test.bzl", "percent_encoding_test") 2 | load("//purl/private:tables_test.bzl", "test_cases") 3 | 4 | exports_files( 5 | [ 6 | "purl.bzl", 7 | ], 8 | visibility = ["//visibility:public"], 9 | ) 10 | 11 | filegroup( 12 | name = "srcs", 13 | srcs = [ 14 | "purl.bzl", 15 | ], 16 | visibility = ["//visibility:public"], 17 | ) 18 | 19 | percent_encoding_test( 20 | name = "percent_encoding_test", 21 | cases = test_cases, 22 | ) 23 | -------------------------------------------------------------------------------- /examples/vendor/constant_gen/constant_generator.py: -------------------------------------------------------------------------------- 1 | """A trivial tool to turn a string into a C++ constant. 2 | 3 | This is not meant to be useful. It is only to provide an example of a tool that 4 | generates code. 5 | """ 6 | 7 | import sys 8 | 9 | 10 | def main(argv): 11 | if len(argv) != 4: 12 | raise Exception('usage: constant_generator out_file var_name text') 13 | with open(argv[1], 'w') as out: 14 | out.write('const char* %s = "%s";\n' % (argv[2], argv[3])) 15 | 16 | 17 | if __name__ == '__main__': 18 | main(sys.argv) 19 | -------------------------------------------------------------------------------- /docs/metadata/licenses/providers/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | stardoc( 5 | name = "license_kind_info", 6 | out = "license_kind_info.generated.md", 7 | input = "@package_metadata//licenses/providers:license_kind_info.bzl", 8 | deps = [ 9 | "@package_metadata//licenses/providers:srcs", 10 | ], 11 | ) 12 | 13 | diff_test( 14 | name = "license_kind_info_test", 15 | file1 = ":license_kind_info", 16 | file2 = "license_kind_info.md", 17 | ) 18 | -------------------------------------------------------------------------------- /docs/supply_chain_tools/gather_metadata/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | stardoc( 5 | name = "gather_metadata", 6 | out = "gather_metadata.generated.md", 7 | input = "@supply_chain_tools//gather_metadata:gather_metadata.bzl", 8 | deps = [ 9 | "@supply_chain_tools//gather_metadata", 10 | ], 11 | ) 12 | 13 | diff_test( 14 | name = "gather_metadata_test", 15 | file1 = ":gather_metadata.generated.md", 16 | file2 = "gather_metadata.md", 17 | ) 18 | -------------------------------------------------------------------------------- /lib/supplychain-go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bazel-contrib/supply-chain/lib/supplychain-go 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/package-url/packageurl-go v0.1.3 7 | github.com/spdx/tools-golang v0.5.5 8 | github.com/stretchr/testify v1.11.1 9 | ) 10 | 11 | require ( 12 | github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | sigs.k8s.io/yaml v1.4.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /tools/sbom/BUILD: -------------------------------------------------------------------------------- 1 | """Rules for generating a SBOM out of a target.""" 2 | 3 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 4 | 5 | package( 6 | default_package_metadata = ["//:package_metadata"], 7 | default_visibility = ["//visibility:public"], 8 | ) 9 | 10 | filegroup( 11 | name = "srcs", 12 | srcs = glob(["**"]), 13 | ) 14 | 15 | bzl_library( 16 | name = "sbom", 17 | srcs = [ 18 | ":sbom.bzl", 19 | "//gather_metadata", 20 | "//:core_metadata", 21 | ], 22 | visibility = ["//visibility:public"], 23 | ) 24 | -------------------------------------------------------------------------------- /metadata/providers/BUILD: -------------------------------------------------------------------------------- 1 | exports_files( 2 | [ 3 | "package_attribute_info.bzl", 4 | "package_metadata_info.bzl", 5 | "package_metadata_override_info.bzl", 6 | "package_metadata_toolchain_info.bzl", 7 | ], 8 | visibility = ["//visibility:public"], 9 | ) 10 | 11 | filegroup( 12 | name = "srcs", 13 | srcs = [ 14 | "package_attribute_info.bzl", 15 | "package_metadata_info.bzl", 16 | "package_metadata_override_info.bzl", 17 | "package_metadata_toolchain_info.bzl", 18 | ], 19 | visibility = ["//visibility:public"], 20 | ) 21 | -------------------------------------------------------------------------------- /metadata-extensions/BUILD: -------------------------------------------------------------------------------- 1 | load("@package_metadata//licenses/rules:license.bzl", "license") 2 | load("@package_metadata//purl:purl.bzl", "purl") 3 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 4 | 5 | package_metadata( 6 | name = "package_metadata", 7 | attributes = [ 8 | ":license", 9 | ], 10 | purl = purl.bazel( 11 | module_name(), 12 | module_version(), 13 | ), 14 | visibility = ["//visibility:public"], 15 | ) 16 | 17 | license( 18 | name = "license", 19 | kind = "@package_metadata//licenses/spdx:Apache-2.0", 20 | text = "LICENSE", 21 | ) 22 | -------------------------------------------------------------------------------- /metadata/providers/package_metadata_toolchain_info.bzl: -------------------------------------------------------------------------------- 1 | """Declares provider `PackageMetadataToolchainInfo`.""" 2 | 3 | visibility("public") 4 | 5 | def _init(metadata_overrides = []): 6 | return { 7 | "metadata_overrides": metadata_overrides, 8 | } 9 | 10 | PackageMetadataToolchainInfo, _create = provider( 11 | doc = """ 12 | Toolchain for `package_metadata`. 13 | 14 | > **Fields in this provider are not covered by the stability guarantee.** 15 | """.strip(), 16 | fields = { 17 | "metadata_overrides": """ 18 | A sequence of `PackageMetadataOverrideInfo` providers. 19 | """.strip(), 20 | }, 21 | init = _init, 22 | ) 23 | -------------------------------------------------------------------------------- /docs/metadata/rules/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | [ 5 | [ 6 | stardoc( 7 | name = file, 8 | out = "{}.generated.md".format(file), 9 | input = "@package_metadata//rules:{}.bzl".format(file), 10 | deps = [ 11 | "@package_metadata//rules:srcs", 12 | ], 13 | ), 14 | diff_test( 15 | name = "{}_test".format(file), 16 | file1 = ":{}".format(file), 17 | file2 = "{}.md".format(file), 18 | ), 19 | ] 20 | for file in [ 21 | "package_metadata", 22 | ] 23 | ] 24 | -------------------------------------------------------------------------------- /rules_license/README.md: -------------------------------------------------------------------------------- 1 | # Compatiblity layer to make legacy rules_license declarations work with supply_chain 2 | 3 | 4 | ## Usage 5 | 6 | This module does not appear in the BCR. You must load it with 7 | an archive_override rule. 8 | 9 | ``` 10 | bazel_dep(name = "package_metadata", version = "0.0.6") 11 | bazel_dep(name = "rules_license", version = "1.0.0") 12 | archive_override( 13 | module_name = "rules_license", 14 | # 15 | sha256 = "5bd0cc7594ea528fd28f98d82457f157827d48cc20e07bcfdbb56072f35c8f67", 16 | strip_prefix = "supply-chain-0.0.6/rules_license", 17 | urls = ["https://github.com/bazel-contrib/supply-chain/releases/download/v0.0.6/supply-chain-v0.0.6.tar.gz"], 18 | ) 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples for supply_chain_tools 2 | 3 | This set of files provides an example of how license rules can be used. 4 | 5 | Terminology 6 | - Organization: A company, organization or other entity that wants to use 7 | license rules to enforce their particular compliance needs. These examples 8 | use the work organization throughout. 9 | - SCM: source code management system. These examples assume that 10 | an organization has a SCM that can enforce ownership restrictions on 11 | specific folder trees. Targets are divided into BUILD files that are 12 | reviewed by engineers vs. those that are reviewed by an organizations 13 | compliance team. 14 | 15 | ## Overview 16 | 17 | TODO 18 | -------------------------------------------------------------------------------- /.bcr/metadata/metadata.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://github.com/bazel-contrib/supply-chain", 3 | "maintainers": [ 4 | { 5 | "email": "antonio@engflow.com", 6 | "github": "TheGrizzlyDev", 7 | "name": "Antonio Di Stefano" 8 | }, 9 | { 10 | "email": "fwe@google.com", 11 | "github": "fweikert", 12 | "name": "Florian Weikert" 13 | }, 14 | { 15 | "email": "yannic@engflow.com", 16 | "github": "Yannic", 17 | "name": "Yannic Bonenberger" 18 | } 19 | ], 20 | "repository": ["github:bazel-contrib/supply-chain"], 21 | "versions": [], 22 | "yanked_versions": {} 23 | } 24 | -------------------------------------------------------------------------------- /.bcr/lib/supplychain-go/metadata.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://github.com/bazel-contrib/supply-chain", 3 | "maintainers": [ 4 | { 5 | "email": "antonio@engflow.com", 6 | "github": "TheGrizzlyDev", 7 | "name": "Antonio Di Stefano" 8 | }, 9 | { 10 | "email": "fwe@google.com", 11 | "github": "fweikert", 12 | "name": "Florian Weikert" 13 | }, 14 | { 15 | "email": "yannic@engflow.com", 16 | "github": "Yannic", 17 | "name": "Yannic Bonenberger" 18 | } 19 | ], 20 | "repository": ["github:bazel-contrib/supply-chain"], 21 | "versions": [], 22 | "yanked_versions": {} 23 | } 24 | -------------------------------------------------------------------------------- /examples/BUILD: -------------------------------------------------------------------------------- 1 | load("@package_metadata//licenses/rules:license.bzl", "license") 2 | load("@package_metadata//purl:purl.bzl", "purl") 3 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 4 | 5 | package( 6 | default_package_metadata = [":package_metadata"], 7 | default_visibility = ["//visibility:public"], 8 | ) 9 | 10 | package_metadata( 11 | name = "package_metadata", 12 | attributes = [ 13 | ":license", 14 | ], 15 | purl = purl.bazel( 16 | module_name(), 17 | module_version(), 18 | ), 19 | visibility = ["//visibility:public"], 20 | ) 21 | 22 | license( 23 | name = "license", 24 | kind = "@package_metadata//licenses/spdx:Apache-2.0", 25 | text = "LICENSE", 26 | ) 27 | -------------------------------------------------------------------------------- /examples/vendor/libhhgttg/BUILD: -------------------------------------------------------------------------------- 1 | # A package with all code under a single license. This is the most common case 2 | # we expect to see. 3 | 4 | load("@package_metadata//licenses/rules:license.bzl", "license") 5 | load("@rules_cc//cc:cc_library.bzl", "cc_library") 6 | 7 | # Using a package wide default ensure that all targets are associated with the 8 | # license. 9 | package( 10 | default_applicable_licenses = [":license"], 11 | default_visibility = ["//visibility:public"], 12 | ) 13 | 14 | # The default license for an entire package is typically named "license". 15 | license( 16 | name = "license", 17 | kind = "//my_org/licenses:generic_notice", 18 | text = "LICENSE", 19 | ) 20 | 21 | cc_library( 22 | name = "libhhgttg", 23 | srcs = ["answer.cc"], 24 | ) 25 | -------------------------------------------------------------------------------- /examples/vendor/legacy_license/BUILD: -------------------------------------------------------------------------------- 1 | # A package with all code under a single license. This is the most common case 2 | # we expect to see. 3 | 4 | load("@rules_license//rules:license.bzl", "license") 5 | load("@rules_cc//cc:cc_library.bzl", "cc_library") 6 | 7 | # Using a package wide default ensure that all targets are associated with the 8 | # license. 9 | package( 10 | default_applicable_licenses = [":license"], 11 | default_visibility = ["//visibility:public"], 12 | ) 13 | 14 | # The default license for an entire package is typically named "license". 15 | license( 16 | name = "license", 17 | license_kinds = ["@rules_license//licenses/generic:reciprocal"], 18 | license_text = "COPYING", 19 | ) 20 | 21 | cc_library( 22 | name = "libfoo", 23 | srcs = ["foo.cc"], 24 | ) 25 | -------------------------------------------------------------------------------- /lib/supplychain-go/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "supply-chain-go", 3 | version = "HEAD", # Automatically updated by release pipeline. 4 | ) 5 | 6 | bazel_dep( 7 | name = "package_metadata", 8 | version = "0.0.6", 9 | ) 10 | bazel_dep( 11 | name = "rules_go", 12 | version = "0.58.3", 13 | ) 14 | bazel_dep( 15 | name = "gazelle", 16 | version = "0.46.0", 17 | ) 18 | 19 | go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") 20 | go_deps.from_file(go_mod = "//:go.mod") 21 | use_repo( 22 | go_deps, 23 | "com_github_package_url_packageurl_go", 24 | "com_github_spdx_tools_golang", 25 | "com_github_stretchr_testify", 26 | ) 27 | 28 | local_path_override( 29 | module_name = "package_metadata", 30 | path = "../../metadata", 31 | ) 32 | 33 | -------------------------------------------------------------------------------- /lib/supplychain-go/cmd/spdx/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_library( 4 | name = "spdx_lib", 5 | srcs = ["spdx.go"], 6 | importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/cmd/spdx", 7 | visibility = ["//visibility:private"], 8 | deps = [ 9 | "//:supplychain-go", 10 | "//internal/sbom", 11 | "@com_github_spdx_tools_golang//json", 12 | "@com_github_spdx_tools_golang//spdx", 13 | "@com_github_spdx_tools_golang//spdx/v2/common", 14 | "@com_github_spdx_tools_golang//tagvalue", 15 | "@com_github_spdx_tools_golang//yaml", 16 | ], 17 | ) 18 | 19 | go_binary( 20 | name = "spdx", 21 | embed = [":spdx_lib"], 22 | visibility = ["//visibility:public"], 23 | ) 24 | -------------------------------------------------------------------------------- /tools/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "supply_chain_tools", 3 | version = "0.0.1", 4 | ) 5 | 6 | # Do not update to newer versions until you need a specific new feature. 7 | bazel_dep(name = "package_metadata", version = "0.0.7") 8 | bazel_dep(name = "rules_license", version = "1.0.0") 9 | bazel_dep(name = "bazel_skylib", version = "1.8.2") 10 | bazel_dep(name = "supply-chain-go", version = "0.0.6") 11 | 12 | # For development, we use our sibling projects directly. 13 | local_path_override( 14 | module_name = "package_metadata", 15 | path = "../metadata", 16 | ) 17 | 18 | local_path_override( 19 | module_name = "supply-chain-go", 20 | path = "../lib/supplychain-go", 21 | ) 22 | 23 | local_path_override( 24 | module_name = "rules_license", 25 | path = "../rules_license", 26 | ) 27 | -------------------------------------------------------------------------------- /metadata-extensions/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "package_metadata_extensions", 3 | version = "", # Automatically updated by release pipeline. 4 | ) 5 | 6 | # This module provides extensions to `@package_metadata` for features that 7 | # require newer Bazel versions (i.e., Bazel 8 or newer) and that we want 8 | # to merge into `@package_metadata` once dropping support for older releases 9 | # of Bazel. Thus, this must adhere to the same policy of "no dependencies" as 10 | # `@package_metadata` itself (with the exception that a dependency on 11 | # `@package_metadata` is allowed). 12 | 13 | bazel_dep( 14 | name = "package_metadata", 15 | version = "HEAD", # Automatically updated by release pipeline. 16 | ) 17 | local_path_override( 18 | module_name = "package_metadata", 19 | path = "../metadata", 20 | ) 21 | -------------------------------------------------------------------------------- /lib/supplychain-go/label/label.go: -------------------------------------------------------------------------------- 1 | package label 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | // Label represents the [Label](https://bazel.build/rules/lib/builtins/Label) of a `Bazel` target. 9 | type Label interface { 10 | fmt.Stringer 11 | 12 | // labelPrivate acts as marker to prevent other packages to implement the interface. 13 | // 14 | // See also https://medium.com/@johnsiilver/writing-an-interface-that-only-sub-packages-can-implement-fe36e7511449 15 | labelPrivate() 16 | } 17 | 18 | // Parse parses a `Label`. 19 | func Parse(label string) (Label, error) { 20 | return &simple{label}, nil 21 | } 22 | 23 | // MustParse parses a `Label`. 24 | func MustParse(label string) Label { 25 | l, err := Parse(label) 26 | if err != nil { 27 | log.Fatalf("Error parsing label %q: %v", label, err) 28 | } 29 | return l 30 | } 31 | -------------------------------------------------------------------------------- /docs/metadata/providers/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | [ 5 | [ 6 | stardoc( 7 | name = file, 8 | out = "{}.generated.md".format(file), 9 | input = "@package_metadata//providers:{}.bzl".format(file), 10 | deps = [ 11 | "@package_metadata//providers:srcs", 12 | ], 13 | ), 14 | diff_test( 15 | name = "{}_test".format(file), 16 | file1 = ":{}".format(file), 17 | file2 = "{}.md".format(file), 18 | ), 19 | ] 20 | for file in [ 21 | "package_attribute_info", 22 | "package_metadata_info", 23 | "package_metadata_override_info", 24 | "package_metadata_toolchain_info", 25 | ] 26 | ] 27 | -------------------------------------------------------------------------------- /metadata/purl/private/tables_test.bzl: -------------------------------------------------------------------------------- 1 | # Generated by maintainers/tools/purl_tables/main.py. DO NOT EDIT. 2 | 3 | visibility([ 4 | "//purl/...", 5 | ]) 6 | test_cases = { 7 | "foo": "foo", 8 | "Hello, World!": "Hello%2C%20World%21", 9 | "path: /foo": "path:%20/foo", 10 | "München": "M%C3%BCnchen", 11 | "Köln": "K%C3%B6ln", 12 | "Småland": "Sm%C3%A5land", 13 | "française": "fran%C3%A7aise", 14 | "¡Hola Mundo!": "%C2%A1Hola%20Mundo%21", 15 | "مرحبا بالعالم!": "%D9%85%D8%B1%D8%AD%D8%A8%D8%A7%20%D8%A8%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85%21", 16 | "你好世界!": "%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C%EF%BC%81", 17 | "こんにちは世界!": "%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E4%B8%96%E7%95%8C%EF%BC%81", 18 | "🙎": "%F0%9F%99%8E", 19 | "🙊": "%F0%9F%99%8A", 20 | "🙎🏾‍♀️": "%F0%9F%99%8E%F0%9F%8F%BE%E2%80%8D%E2%99%80%EF%B8%8F", 21 | } 22 | -------------------------------------------------------------------------------- /examples/vendor/acme/BUILD: -------------------------------------------------------------------------------- 1 | # A package with a commercial license. 2 | 3 | load("@package_metadata//:defs.bzl", "package_metadata") 4 | load("@package_metadata//licenses/rules:license.bzl", "license") 5 | load("@rules_cc//cc:cc_library.bzl", "cc_library") 6 | 7 | package( 8 | default_applicable_licenses = [":package_data"], 9 | default_visibility = ["//visibility:public"], 10 | ) 11 | 12 | package_metadata( 13 | name = "package_data", 14 | attributes = [ 15 | ":license", 16 | ], 17 | purl = "pkg:github/acmecorp/coyote@3.1.4", 18 | ) 19 | 20 | # The default license for an entire package is typically named "license". 21 | license( 22 | name = "license", 23 | kind = "//my_org/licenses:acme_corp_paid", 24 | text = "ACME_LICENSE", 25 | ) 26 | 27 | cc_library( 28 | name = "acme", 29 | srcs = ["coyote.cc"], 30 | ) 31 | -------------------------------------------------------------------------------- /examples/server/server.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | 17 | extern const char* server_message; 18 | 19 | int main(int argc, char* argv[]) { 20 | std::cout << server_message << std::endl; 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /examples/server/server_sbom.golden: -------------------------------------------------------------------------------- 1 | Target: @@//server:my_violating_server 2 | Attribute data: vendor/constant_gen/license_for_emitted_code.package-attribute.json 3 | file: vendor/constant_gen/LICENSE_OF_OUTPUT 4 | file: vendor/constant_gen/license_for_emitted_code.package-attribute.json 5 | Attribute data: vendor/constant_gen/license_for_emitted_code.package-attribute.json 6 | file: vendor/constant_gen/LICENSE_OF_OUTPUT 7 | file: vendor/constant_gen/license_for_emitted_code.package-attribute.json 8 | Attribute data: vendor/legacy_license/license.package-attribute.json 9 | file: vendor/legacy_license/COPYING 10 | file: vendor/legacy_license/license.package-attribute.json 11 | Attribute data: vendor/libhhgttg/license.package-attribute.json 12 | file: vendor/libhhgttg/LICENSE 13 | file: vendor/libhhgttg/license.package-attribute.json 14 | -------------------------------------------------------------------------------- /rules_license/rules/BUILD: -------------------------------------------------------------------------------- 1 | # BUILD file defining @rules_license/rules 2 | # 3 | # Copyright 2020 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """Rules for making license declarations.""" 17 | 18 | package( 19 | default_applicable_licenses = ["//:license"], 20 | default_visibility = ["//visibility:public"], 21 | ) 22 | -------------------------------------------------------------------------------- /docs/metadata/licenses/rules/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 3 | 4 | stardoc( 5 | name = "license", 6 | out = "license.generated.md", 7 | input = "@package_metadata//licenses/rules:license.bzl", 8 | deps = [ 9 | "@package_metadata//licenses/rules:srcs", 10 | ], 11 | ) 12 | 13 | diff_test( 14 | name = "license_test", 15 | file1 = ":license", 16 | file2 = "license.md", 17 | ) 18 | 19 | stardoc( 20 | name = "license_kind", 21 | out = "license_kind.generated.md", 22 | input = "@package_metadata//licenses/rules:license_kind.bzl", 23 | deps = [ 24 | "@package_metadata//licenses/rules:srcs", 25 | ], 26 | ) 27 | 28 | diff_test( 29 | name = "license_kind_test", 30 | file1 = ":license_kind", 31 | file2 = "license_kind.md", 32 | ) 33 | -------------------------------------------------------------------------------- /docs/metadata/licenses/rules/license.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares rule `license`. 4 | 5 | 6 | 7 | ## license 8 | 9 |
10 | load("@package_metadata//licenses/rules:license.bzl", "license")
11 | 
12 | license(*, name, kind, text, visibility)
13 | 
14 | 15 | 16 | 17 | **PARAMETERS** 18 | 19 | 20 | | Name | Description | Default Value | 21 | | :------------- | :------------- | :------------- | 22 | | name |

-

| none | 23 | | kind |

-

| none | 24 | | text |

-

| `None` | 25 | | visibility |

-

| `None` | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/metadata/providers/package_metadata_toolchain_info.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares provider `PackageMetadataToolchainInfo`. 4 | 5 | 6 | 7 | ## PackageMetadataToolchainInfo 8 | 9 |
10 | load("@package_metadata//providers:package_metadata_toolchain_info.bzl", "PackageMetadataToolchainInfo")
11 | 
12 | PackageMetadataToolchainInfo(metadata_overrides)
13 | 
14 | 15 | Toolchain for `package_metadata`. 16 | 17 | > **Fields in this provider are not covered by the stability guarantee.** 18 | 19 | **FIELDS** 20 | 21 | | Name | Description | Default Value | 22 | | :------------- | :------------- | :------------- | 23 | | metadata_overrides | A sequence of `PackageMetadataOverrideInfo` providers. | `[]` | 24 | 25 | 26 | -------------------------------------------------------------------------------- /metadata/BUILD: -------------------------------------------------------------------------------- 1 | load("//licenses/rules:license.bzl", "license") 2 | load("//purl:purl.bzl", "purl") 3 | load("//rules:package_metadata.bzl", "package_metadata") 4 | 5 | exports_files( 6 | [ 7 | "defs.bzl", 8 | ], 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | filegroup( 13 | name = "srcs", 14 | srcs = [ 15 | "defs.bzl", 16 | ] + [ 17 | "//licenses:srcs", 18 | "//providers:srcs", 19 | "//purl:srcs", 20 | "//rules:srcs", 21 | ], 22 | visibility = ["//visibility:public"], 23 | ) 24 | 25 | package_metadata( 26 | name = "package_metadata", 27 | attributes = [ 28 | ":license", 29 | ], 30 | purl = purl.bazel( 31 | module_name(), 32 | module_version(), 33 | ), 34 | visibility = ["//visibility:public"], 35 | ) 36 | 37 | license( 38 | name = "license", 39 | kind = "//licenses/spdx:Apache-2.0", 40 | text = "LICENSE", 41 | ) 42 | -------------------------------------------------------------------------------- /metadata/defs.bzl: -------------------------------------------------------------------------------- 1 | """Public API of `@package_metadata`.""" 2 | 3 | load("//providers:package_attribute_info.bzl", _PackageAttributeInfo = "PackageAttributeInfo") 4 | load("//providers:package_metadata_info.bzl", _PackageMetadataInfo = "PackageMetadataInfo") 5 | load("//providers:package_metadata_override_info.bzl", _PackageMetadataOverrideInfo = "PackageMetadataOverrideInfo") 6 | load("//providers:package_metadata_toolchain_info.bzl", _PackageMetadataToolchainInfo = "PackageMetadataToolchainInfo") 7 | load("//purl:purl.bzl", _purl = "purl") 8 | load("//rules:package_metadata.bzl", _package_metadata = "package_metadata") 9 | 10 | visibility("public") 11 | 12 | # Providers. 13 | PackageAttributeInfo = _PackageAttributeInfo 14 | PackageMetadataInfo = _PackageMetadataInfo 15 | PackageMetadataOverrideInfo = _PackageMetadataOverrideInfo 16 | PackageMetadataToolchainInfo = _PackageMetadataToolchainInfo 17 | 18 | # Rules 19 | package_metadata = _package_metadata 20 | 21 | # Utils 22 | purl = _purl 23 | -------------------------------------------------------------------------------- /metadata/purl/percent_encoding.bzl: -------------------------------------------------------------------------------- 1 | """Utils for [purl](https://github.com/package-url/purl-spec)'s `percent encoding`. 2 | 3 | Spec: https://github.com/package-url/purl-spec/blob/main/PURL-SPECIFICATION.rst#character-encoding 4 | """ 5 | 6 | load("//purl:string.bzl", "string") 7 | load("//purl/private:tables.bzl", "encode_byte") 8 | 9 | visibility([ 10 | "//purl/...", 11 | ]) 12 | 13 | def _encode_byte(b): 14 | """Encodes a single byte. 15 | 16 | Args: 17 | c: The byte to encode. 18 | Returns: 19 | The encoded string. 20 | """ 21 | 22 | encoded = encode_byte.get(b, None) 23 | if not encoded: 24 | fail("Cannot encode {} (type={})".format(b, type(b))) 25 | 26 | return encoded 27 | 28 | def percent_encode(value): 29 | """Encodes the provided string. 30 | 31 | Args: 32 | value (string): The string to encode. 33 | Returns: 34 | The encoded string. 35 | """ 36 | 37 | return "".join([_encode_byte(b) for b in string.to_bytes(value)]) 38 | -------------------------------------------------------------------------------- /metadata/licenses/providers/license_kind_info.bzl: -------------------------------------------------------------------------------- 1 | """Declares provider `LicenseKindInfo`.""" 2 | 3 | visibility("public") 4 | 5 | def _init(identifier, name): 6 | return { 7 | "identifier": identifier, 8 | "name": name, 9 | } 10 | 11 | LicenseKindInfo, _create = provider( 12 | doc = """ 13 | Provides information to identify a license. 14 | """.strip(), 15 | fields = { 16 | "identifier": """ 17 | A [string](https://bazel.build/rules/lib/core/string) uniquely identifying the 18 | license (e.g., `Apache-2.0`, `EUPL-1.1`). 19 | 20 | This is typically the [SPDX identifier](https://spdx.org/licenses/) of the 21 | license, but may also be a non-standard value (e.g., in case of a commercial 22 | license). 23 | """.strip(), 24 | "name": """ 25 | A [string](https://bazel.build/rules/lib/core/string) containing the (human 26 | readable) name of the license (e.g., `Apache License 2.0`, `European Union 27 | Public License 1.1`) 28 | """.strip(), 29 | }, 30 | init = _init, 31 | ) 32 | -------------------------------------------------------------------------------- /tools/gather_metadata/providers.bzl: -------------------------------------------------------------------------------- 1 | """Providers for transitively gathering all license and package_info targets. 2 | 3 | Warning: This is private to the aspect that walks the tree. The API is subject 4 | to change at any release. 5 | """ 6 | 7 | TargetWithMetadataInfo = provider( 8 | doc = """A target and the assocated metadata for it.""", 9 | fields = { 10 | "target": "Label: A target which will be associated with some metadata.", 11 | "metadata": "depset(): [list] of my direct collected leaf attribute providers", 12 | }, 13 | ) 14 | 15 | TransitiveMetadataInfo = provider( 16 | doc = """The transitive set of TargetWithMetadataInfo objects.""", 17 | fields = { 18 | "trans": "depset(): transitive collection of TWMI", 19 | "top_level_target": "Label: The top level target label we are examining.", 20 | "traces": "list(string) - diagnostic for tracing a dependency relationship to a target.", 21 | }, 22 | ) 23 | 24 | null_transitive_metadata_info = TransitiveMetadataInfo() 25 | -------------------------------------------------------------------------------- /docs/metadata/licenses/rules/license_kind.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares rule `license_kind`. 4 | 5 | 6 | 7 | ## license_kind 8 | 9 |
10 | load("@package_metadata//licenses/rules:license_kind.bzl", "license_kind")
11 | 
12 | license_kind(*, name, identifier, full_name, visibility)
13 | 
14 | 15 | 16 | 17 | **PARAMETERS** 18 | 19 | 20 | | Name | Description | Default Value | 21 | | :------------- | :------------- | :------------- | 22 | | name |

-

| none | 23 | | identifier |

-

| none | 24 | | full_name |

-

| none | 25 | | visibility |

-

| `None` | 26 | 27 | 28 | -------------------------------------------------------------------------------- /metadata/providers/package_metadata_override_info.bzl: -------------------------------------------------------------------------------- 1 | """Declares provider `PackageMetadataOverrideInfo`.""" 2 | 3 | visibility("public") 4 | 5 | def _init(*, packages, metadata): 6 | return { 7 | "metadata": metadata, 8 | "packages": packages, 9 | } 10 | 11 | PackageMetadataOverrideInfo, _create = provider( 12 | doc = """ 13 | Defines an override for `PackageMetadataInfo` for a set of packages. 14 | 15 | > **Fields in this provider are not covered by the stability guarantee.** 16 | """.strip(), 17 | fields = { 18 | "metadata": """ 19 | The `PackageMetadataInfo` provider to use instead of the provider declared by 20 | package itself. 21 | """.strip(), 22 | "packages": """ 23 | A [PackageSpecificationInfo](https://bazel.build/rules/lib/providers/PackageSpecificationInfo) 24 | provider declaring which packages the override applies to. 25 | 26 | This is typically created by a 27 | [package_group](https://bazel.build/rules/lib/globals/build#package_group) 28 | target. 29 | """.strip(), 30 | }, 31 | init = _init, 32 | ) 33 | -------------------------------------------------------------------------------- /metadata/providers/package_metadata_info.bzl: -------------------------------------------------------------------------------- 1 | """Declares provider `PackageMetadataInfo`.""" 2 | 3 | visibility("public") 4 | 5 | def _init(metadata, files = []): 6 | return { 7 | "files": depset( 8 | direct = [ 9 | metadata, 10 | ], 11 | transitive = files, 12 | ), 13 | "metadata": metadata, 14 | } 15 | 16 | PackageMetadataInfo, _create = provider( 17 | doc = """ 18 | Provider for declaring metadata about a Bazel package. 19 | 20 | > **Fields in this provider are not covered by the stability guarantee.** 21 | """.strip(), 22 | fields = { 23 | "files": """ 24 | A [depset](https://bazel.build/rules/lib/builtins/depset) of 25 | [File](https://bazel.build/rules/lib/builtins/File)s with metadata about the 26 | package, including transitive files from all attributes of the package. 27 | """.strip(), 28 | "metadata": """ 29 | The [File](https://bazel.build/rules/lib/builtins/File) containing metadata 30 | about the package. 31 | """.strip(), 32 | }, 33 | init = _init, 34 | ) 35 | -------------------------------------------------------------------------------- /docs/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "supply-chain-docs", 3 | version = "HEAD", 4 | ) 5 | 6 | 7 | bazel_dep( 8 | name = "bazel_skylib", 9 | version = "1.8.2", 10 | ) 11 | bazel_dep( 12 | name = "stardoc", 13 | version = "0.8.0", 14 | ) 15 | 16 | # 17 | # Modules from this repository. 18 | # 19 | 20 | bazel_dep( 21 | name = "package_metadata", 22 | # Always overridden to use local path. 23 | version = "HEAD", 24 | ) 25 | 26 | # 27 | # Overrides from modules from this repository. 28 | # 29 | 30 | local_path_override( 31 | module_name = "package_metadata", 32 | path = "../metadata", 33 | ) 34 | 35 | bazel_dep( 36 | name = "supply_chain_tools", 37 | # Always overridden to use local path. 38 | version = "HEAD", 39 | ) 40 | local_path_override( 41 | module_name = "supply_chain_tools", 42 | path = "../tools", 43 | ) 44 | 45 | bazel_dep( 46 | name = "supply-chain-go", 47 | # Always overridden to use local path. 48 | version = "HEAD", 49 | ) 50 | local_path_override( 51 | module_name = "supply-chain-go", 52 | path = "../lib/supplychain-go", 53 | ) 54 | -------------------------------------------------------------------------------- /lib/supplychain-go/BUILD: -------------------------------------------------------------------------------- 1 | load("@gazelle//:def.bzl", "gazelle") 2 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 3 | load("@rules_go//go:def.bzl", "go_library", "go_test") 4 | 5 | package_metadata( 6 | name = "package_metadata", 7 | purl = "pkg:bazel/{}@{}".format( 8 | module_name(), 9 | module_version(), 10 | ), 11 | ) 12 | 13 | gazelle(name = "gazelle") 14 | 15 | go_library( 16 | name = "supplychain-go", 17 | srcs = [ 18 | "package_attribute.go", 19 | "package_metadata.go", 20 | ], 21 | importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go", 22 | visibility = ["//visibility:public"], 23 | deps = [ 24 | "//label", 25 | "@com_github_package_url_packageurl_go//:packageurl-go", 26 | ], 27 | ) 28 | 29 | go_test( 30 | name = "supplychain-go_test", 31 | srcs = ["package_metadata_test.go"], 32 | embed = [":supplychain-go"], 33 | deps = [ 34 | "//label", 35 | "@com_github_package_url_packageurl_go//:packageurl-go", 36 | "@com_github_stretchr_testify//assert", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /docs/metadata/licenses/providers/license_kind_info.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares provider `LicenseKindInfo`. 4 | 5 | 6 | 7 | ## LicenseKindInfo 8 | 9 |
10 | load("@package_metadata//licenses/providers:license_kind_info.bzl", "LicenseKindInfo")
11 | 
12 | LicenseKindInfo(identifier, name)
13 | 
14 | 15 | Provides information to identify a license. 16 | 17 | **FIELDS** 18 | 19 | | Name | Description | 20 | | :------------- | :------------- | 21 | | identifier | A [string](https://bazel.build/rules/lib/core/string) uniquely identifying the license (e.g., `Apache-2.0`, `EUPL-1.1`).

This is typically the [SPDX identifier](https://spdx.org/licenses/) of the license, but may also be a non-standard value (e.g., in case of a commercial license). | 22 | | name | A [string](https://bazel.build/rules/lib/core/string) containing the (human readable) name of the license (e.g., `Apache License 2.0`, `European Union Public License 1.1`) | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "supply_chain_examples", 3 | version = "0.0.1", 4 | ) 5 | 6 | # Do not update to newer versions until you need a specific new feature. 7 | bazel_dep(name = "package_metadata", version = "0.0.5") 8 | bazel_dep(name = "rules_license", version = "1.0.0") 9 | bazel_dep(name = "bazel_skylib", version = "1.8.2") 10 | bazel_dep(name = "supply-chain-go", version = "0.0.6") 11 | bazel_dep(name = "supply_chain_tools", version = "0.0.1") 12 | 13 | # Development tools 14 | local_path_override( 15 | module_name = "package_metadata", 16 | path = "../metadata", 17 | ) 18 | 19 | local_path_override( 20 | module_name = "supply-chain-go", 21 | path = "../lib/supplychain-go", 22 | ) 23 | 24 | local_path_override( 25 | module_name = "supply_chain_tools", 26 | path = "../tools", 27 | ) 28 | 29 | local_path_override( 30 | module_name = "rules_license", 31 | path = "../rules_license", 32 | ) 33 | 34 | bazel_dep(name = "rules_python", version = "1.6.3", dev_dependency = True) 35 | bazel_dep(name = "platforms", version = "1.0.0", dev_dependency = True) 36 | bazel_dep(name = "rules_cc", version = "0.2.13", dev_dependency = True) 37 | -------------------------------------------------------------------------------- /docs/metadata/providers/package_metadata_info.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares provider `PackageMetadataInfo`. 4 | 5 | 6 | 7 | ## PackageMetadataInfo 8 | 9 |
10 | load("@package_metadata//providers:package_metadata_info.bzl", "PackageMetadataInfo")
11 | 
12 | PackageMetadataInfo(metadata, files)
13 | 
14 | 15 | Provider for declaring metadata about a Bazel package. 16 | 17 | > **Fields in this provider are not covered by the stability guarantee.** 18 | 19 | **FIELDS** 20 | 21 | | Name | Description | Default Value | 22 | | :------------- | :------------- | :------------- | 23 | | metadata | The [File](https://bazel.build/rules/lib/builtins/File) containing metadata about the package. | none | 24 | | files | A [depset](https://bazel.build/rules/lib/builtins/depset) of [File](https://bazel.build/rules/lib/builtins/File)s with metadata about the package, including transitive files from all attributes of the package. | `[]` | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/metadata/rules/package_metadata.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares rule `package_metadata`. 4 | 5 | 6 | 7 | ## package_metadata 8 | 9 |
10 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata")
11 | 
12 | package_metadata(*, name, purl, attributes, visibility, tags)
13 | 
14 | 15 | 16 | 17 | **PARAMETERS** 18 | 19 | 20 | | Name | Description | Default Value | 21 | | :------------- | :------------- | :------------- | 22 | | name |

-

| none | 23 | | purl |

-

| none | 24 | | attributes |

-

| `[]` | 25 | | visibility |

-

| `None` | 26 | | tags |

-

| `None` | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tools/gather_metadata/BUILD: -------------------------------------------------------------------------------- 1 | """Rules for collecting package_metadata.""" 2 | 3 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 4 | load(":trace.bzl", "trace") 5 | 6 | package( 7 | default_package_metadata = ["//:package_metadata"], 8 | default_visibility = ["//visibility:public"], 9 | ) 10 | 11 | filegroup( 12 | name = "srcs", 13 | srcs = glob(["**"]), 14 | ) 15 | 16 | bzl_library( 17 | name = "gather_metadata", 18 | srcs = [ 19 | ":core.bzl", 20 | ":gather_metadata.bzl", 21 | ":providers.bzl", 22 | ":rule_filters.bzl", 23 | ":trace.bzl", 24 | "//:core_metadata", 25 | ], 26 | visibility = ["//visibility:public"], 27 | ) 28 | 29 | # This target controls the value of the traced target used during dependency collection. 30 | # It is useful for debugging unexpected dependencies. 31 | # This value should always be the empty string! 32 | # Specify this value with a flag, like --@supply_chain_tools//gather_metadata:trace_target=//target/to:trace 33 | trace( 34 | name = "trace_target", 35 | build_setting_default = "", # TRACE-TARGET-SHOULD-BE-EMPTY 36 | visibility = ["//visibility:public"], 37 | ) 38 | -------------------------------------------------------------------------------- /tools/gather_metadata/trace.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Rules and macros for collecting package metdata providers.""" 15 | 16 | TraceInfo = provider( 17 | doc = """Provides a target (as a string) to assist in debugging dependency issues.""", 18 | fields = { 19 | "trace": "String: a target to trace dependency edges to.", 20 | }, 21 | ) 22 | 23 | def _trace_impl(ctx): 24 | return TraceInfo(trace = ctx.build_setting_value) 25 | 26 | trace = rule( 27 | doc = """Used to allow the specification of a target to trace while collecting license dependencies.""", 28 | implementation = _trace_impl, 29 | build_setting = config.string(flag = True), 30 | ) 31 | -------------------------------------------------------------------------------- /tools/gather_metadata/rule_filters.bzl: -------------------------------------------------------------------------------- 1 | """Attribute exclusions for metadata gathering. 2 | 3 | The format of this dictionary is: 4 | 5 | rule_name: [attr, attr, ...] 6 | 7 | Only filters for rules that are part of the Bazel distribution should be added 8 | to this file. Other filters should be added in user_filtered_rule_kinds.bzl 9 | 10 | Attributes are either the explicit list of attributes to filter, or '_*' which 11 | would ignore all attributes prefixed with a _. 12 | """ 13 | 14 | # Rule kinds with attributes the aspect currently needs to ignore 15 | rule_to_excluded_attributes = { 16 | "*": ["linter"], 17 | "cc_binary": ["_*"], 18 | "cc_embed_data": ["_*"], 19 | "cc_grpc_library": ["_*", "current_cc_toolchain"], 20 | "cc_library": ["_*"], 21 | "cc_toolchain_alias": ["*"], 22 | "genrule": ["tools", "exec_tools", "toolchains"], 23 | "genyacc": ["_*"], 24 | "go_binary": ["_*"], 25 | "go_library": ["_*"], 26 | "go_wrap_cc": ["_*"], 27 | "java_binary": ["_*", "plugins", "exported_plugins"], 28 | "java_library": ["plugins", "exported_plugins"], 29 | "java_wrap_cc": ["_cc_toolchain", "swig_top"], 30 | "py_binary": ["_*"], 31 | "py_extension": ["_cc_toolchain"], 32 | "sh_binary": ["_bash_binary"], 33 | "_constant_gen": ["_generator"], 34 | } 35 | -------------------------------------------------------------------------------- /docs/metadata/providers/package_metadata_override_info.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares provider `PackageMetadataOverrideInfo`. 4 | 5 | 6 | 7 | ## PackageMetadataOverrideInfo 8 | 9 |
10 | load("@package_metadata//providers:package_metadata_override_info.bzl", "PackageMetadataOverrideInfo")
11 | 
12 | PackageMetadataOverrideInfo(*, packages, metadata)
13 | 
14 | 15 | Defines an override for `PackageMetadataInfo` for a set of packages. 16 | 17 | > **Fields in this provider are not covered by the stability guarantee.** 18 | 19 | **FIELDS** 20 | 21 | | Name | Description | 22 | | :------------- | :------------- | 23 | | packages | A [PackageSpecificationInfo](https://bazel.build/rules/lib/providers/PackageSpecificationInfo) provider declaring which packages the override applies to.

This is typically created by a [package_group](https://bazel.build/rules/lib/globals/build#package_group) target. | 24 | | metadata | The `PackageMetadataInfo` provider to use instead of the provider declared by package itself. | 25 | 26 | 27 | -------------------------------------------------------------------------------- /metadata/providers/package_attribute_info.bzl: -------------------------------------------------------------------------------- 1 | """Declares provider `PackageAttributeInfo`.""" 2 | 3 | visibility("public") 4 | 5 | def _init(kind, attributes, files = []): 6 | return { 7 | "attributes": attributes, 8 | "files": depset( 9 | direct = [ 10 | attributes, 11 | ], 12 | transitive = files, 13 | ), 14 | "kind": kind, 15 | } 16 | 17 | PackageAttributeInfo, _create = provider( 18 | doc = """ 19 | Provider for declaring metadata about a Bazel package. 20 | 21 | > **Fields in this provider are not covered by the stability guarantee.** 22 | """.strip(), 23 | fields = { 24 | "attributes": """ 25 | The [File](https://bazel.build/rules/lib/builtins/File) containing the 26 | attributes. 27 | 28 | The format of this file depends on the `kind` of attribute. Please consult the 29 | documentation of the attribute. 30 | """.strip(), 31 | "files": """ 32 | A [depset](https://bazel.build/rules/lib/builtins/depset) of 33 | [File](https://bazel.build/rules/lib/builtins/File)s containing information 34 | about this attribute. 35 | """.strip(), 36 | "kind": """ 37 | The identifier of the attribute. 38 | 39 | This should generally be in reverse DNS format (e.g., `com.example.foo`). 40 | """.strip(), 41 | }, 42 | init = _init, 43 | ) 44 | -------------------------------------------------------------------------------- /metadata/purl/string.bzl: -------------------------------------------------------------------------------- 1 | """Utils for working with Starlark [string](https://bazel.build/rules/lib/core/string)s.""" 2 | 3 | def _to_bytes(value): 4 | """Converts a string to a list of bytes. 5 | 6 | Args: 7 | value (string): The string to convert. 8 | 9 | Returns: 10 | A sequence of bytes in Bazel's native encoding. 11 | """ 12 | 13 | forced_utf8_encoded_string = "".join(value.elems()) 14 | 15 | # Bazel reads `BUILD` and `.bzl` files as `ISO-8859-1` (a.k.a., `latin-1`), 16 | # which is always 1 byte wide. This means that `utf-8` multi-byte characters 17 | # will end up being multiple characters in a Starlark string (e.g., `ü`, 18 | # which is unicode `\u00FC`, becomes two invalid `utf-8` bytes 19 | # `[0xC3, 0xBC]`). 20 | # 21 | # In Bazel, Starlark's `hash()` function is implemented using 22 | # `String#hashCode()`, which hashes with this formula: 23 | # [s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#hashCode--) 24 | # (where `s` is the `char[]` internally used by `String`). For one-char 25 | # `Strings` this means that 26 | # `String#hashCode() == (int) String#toCharArray()`. 27 | return [hash(c) for c in forced_utf8_encoded_string.elems()] 28 | 29 | string = struct( 30 | to_bytes = _to_bytes, 31 | ) 32 | -------------------------------------------------------------------------------- /tools/sbom/spdx.bzl: -------------------------------------------------------------------------------- 1 | load("providers.bzl", "SbomInfo") 2 | 3 | def _spdx_impl(ctx): 4 | out_path = ctx.attr.out.name if ctx.attr.out != None else "%s.txt" % ctx.attr.name 5 | out = ctx.actions.declare_file(out_path) 6 | inputs = depset( 7 | [], 8 | transitive = [ 9 | ctx.attr._spdx[DefaultInfo].data_runfiles.files, 10 | ctx.attr.sbom[DefaultInfo].files, 11 | ], 12 | ) 13 | ctx.actions.run( 14 | outputs=[out], 15 | inputs=inputs, 16 | executable=ctx.attr._spdx[DefaultInfo].files_to_run.executable, 17 | arguments=[ 18 | "--config", 19 | ctx.attr.sbom[SbomInfo].config.path, 20 | "--out", 21 | out.path, 22 | "--format", 23 | ctx.attr.format, 24 | ], 25 | ) 26 | return DefaultInfo(files=depset([out])) 27 | 28 | spdx = rule( 29 | _spdx_impl, 30 | attrs = { 31 | "sbom": attr.label(doc = "The sbom target to generate the SPDX SBOM from."), 32 | "format": attr.string(default = "json", values = ["json", "yaml", "tag-value"], doc = "The output format for the SPDX SBOM."), 33 | "out": attr.output(doc = "The output file for the SPDX SBOM."), 34 | "_spdx": attr.label(default = "@supply-chain-go//cmd/spdx", doc = "The spdx tool to use."), 35 | } 36 | ) -------------------------------------------------------------------------------- /rules_license/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Bazel Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | load("@rules_license//rules:current_module_package_info.bzl", "current_module_package_info") 16 | load("@rules_license//rules:license.bzl", "license") 17 | load("@rules_license//rules:package_info.bzl", "package_info") 18 | 19 | package( 20 | default_applicable_licenses = [":license", ":package_info"], 21 | default_visibility = ["//visibility:public"], 22 | ) 23 | 24 | license( 25 | name = "license", 26 | license_kinds = [ 27 | "@rules_license//licenses/spdx:Apache-2.0", 28 | ], 29 | license_text = "LICENSE", 30 | ) 31 | 32 | current_module_package_info( 33 | name = "package_info", 34 | ) 35 | 36 | exports_files( 37 | ["LICENSE"], 38 | visibility = ["//visibility:public"], 39 | ) 40 | -------------------------------------------------------------------------------- /examples/vendor/constant_gen/verify_licenses_test.py: -------------------------------------------------------------------------------- 1 | """Test that we see the expected licenses for some generated code.""" 2 | 3 | import codecs 4 | import os 5 | 6 | import unittest 7 | from tests import license_test_utils 8 | 9 | class VerifyLicensesTest(unittest.TestCase): 10 | 11 | def test_generater_license(self): 12 | licenses_info = license_test_utils.load_licenses_info( 13 | os.path.join(os.path.dirname(__file__), "generator_licenses.json")) 14 | 15 | expected = { 16 | "examples/vendor/constant_gen:constant_generator": [ 17 | "examples/vendor/constant_gen:license" 18 | ], 19 | } 20 | license_test_utils.check_licenses_of_dependencies( 21 | self, licenses_info, expected) 22 | 23 | def test_generated_code_license(self): 24 | licenses_info = license_test_utils.load_licenses_info( 25 | os.path.join(os.path.dirname(__file__), "generated_code_licenses.json")) 26 | 27 | expected = { 28 | "examples/vendor/constant_gen:libhello": [ 29 | "/constant_gen:license_for_emitted_code", 30 | ], 31 | "examples/vendor/constant_gen:libhello_src_": [ 32 | "/constant_gen:license_for_emitted_code", 33 | ], 34 | } 35 | license_test_utils.check_licenses_of_dependencies( 36 | self, licenses_info, expected) 37 | 38 | if __name__ == "__main__": 39 | unittest.main() 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/release_prep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit -o nounset -o pipefail 4 | 5 | # Passed as argument when invoking the script. 6 | TAG="${1}" 7 | 8 | # The prefix is chosen to match what GitHub generates for source archives 9 | # This guarantees that users can easily switch from a released artifact to a source archive 10 | # with minimal differences in their code (e.g. strip_prefix remains the same) 11 | PREFIX="supply-chain-${TAG:1}" 12 | ARCHIVE="supply-chain-$TAG.tar.gz" 13 | 14 | # NB: configuration for 'git archive' is in /.gitattributes 15 | git archive --format=tar --prefix=${PREFIX}/ ${TAG} | gzip > $ARCHIVE 16 | SHA=$(shasum -a 256 $ARCHIVE | awk '{print $1}') 17 | 18 | cat << EOF 19 | ## Using Bzlmod with Bazel 6 or greater 20 | 21 | 1. (Bazel 6 only) Enable with \`common --enable_bzlmod\` in \`.bazelrc\`. 22 | 2. Add to your \`MODULE.bazel\` file: 23 | 24 | \`\`\`starlark 25 | bazel_dep(name = "package_metadata", version = "${TAG:1}") 26 | \`\`\` 27 | 28 | ## Using WORKSPACE 29 | 30 | Paste this snippet into your \`WORKSPACE.bazel\` file: 31 | 32 | \`\`\`starlark 33 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 34 | http_archive( 35 | name = "package_metadata", 36 | sha256 = "${SHA}", 37 | strip_prefix = "${PREFIX}/metadata", 38 | url = "https://github.com/bazel-contrib/supply-chain/releases/download/${TAG}/${ARCHIVE}", 39 | ) 40 | \`\`\` 41 | EOF 42 | -------------------------------------------------------------------------------- /docs/metadata/providers/package_attribute_info.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Declares provider `PackageAttributeInfo`. 4 | 5 | 6 | 7 | ## PackageAttributeInfo 8 | 9 |
10 | load("@package_metadata//providers:package_attribute_info.bzl", "PackageAttributeInfo")
11 | 
12 | PackageAttributeInfo(kind, attributes, files)
13 | 
14 | 15 | Provider for declaring metadata about a Bazel package. 16 | 17 | > **Fields in this provider are not covered by the stability guarantee.** 18 | 19 | **FIELDS** 20 | 21 | | Name | Description | Default Value | 22 | | :------------- | :------------- | :------------- | 23 | | kind | The identifier of the attribute.

This should generally be in reverse DNS format (e.g., `com.example.foo`). | none | 24 | | attributes | The [File](https://bazel.build/rules/lib/builtins/File) containing the attributes.

The format of this file depends on the `kind` of attribute. Please consult the documentation of the attribute. | none | 25 | | files | A [depset](https://bazel.build/rules/lib/builtins/depset) of [File](https://bazel.build/rules/lib/builtins/File)s containing information about this attribute. | `[]` | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tools/sbom/sbom.bzl: -------------------------------------------------------------------------------- 1 | load("providers.bzl", "SbomInfo") 2 | load( 3 | "@supply_chain_tools//gather_metadata:gather_metadata.bzl", 4 | "gather_metadata_info", 5 | ) 6 | load( 7 | "@supply_chain_tools//gather_metadata:providers.bzl", 8 | "TransitiveMetadataInfo", 9 | ) 10 | 11 | def _sbom_impl(ctx): 12 | transitive_metadata_info = ctx.attr.target[TransitiveMetadataInfo] 13 | transitive_inputs = [] 14 | config = { "deps": [] } 15 | for transitive_metadata in transitive_metadata_info.trans.to_list(): 16 | for m in transitive_metadata.metadata.to_list(): 17 | if hasattr(m, "metadata"): 18 | config["deps"].append({ 19 | "metadata": m.metadata.path 20 | }) 21 | transitive_inputs.append(m.files) 22 | 23 | sbom_gen_config = ctx.actions.declare_file("{name}.sbom.config.json".format(name=ctx.attr.name)) 24 | ctx.actions.write(sbom_gen_config, json.encode(config)) 25 | 26 | return [ 27 | DefaultInfo(files=depset( 28 | [sbom_gen_config], 29 | transitive=transitive_inputs 30 | )), 31 | SbomInfo(config=sbom_gen_config), 32 | ] 33 | 34 | def sbom_rule(gathering_aspect): 35 | return rule( 36 | _sbom_impl, 37 | attrs = { 38 | "target": attr.label(aspects = [gathering_aspect], doc="The target for which to generate an SBOM."), 39 | }, 40 | ) 41 | 42 | sbom = sbom_rule(gathering_aspect=gather_metadata_info) 43 | -------------------------------------------------------------------------------- /metadata/purl/purl.bzl: -------------------------------------------------------------------------------- 1 | """Module defining urils for [purl](https://github.com/package-url/purl-spec)s.""" 2 | 3 | visibility("public") 4 | 5 | def _bazel(name, version): 6 | """Defines a `purl` for a Bazel module. 7 | 8 | This is typically used to construct `purl` for `package_metadata` targets in 9 | Bazel modules. 10 | 11 | This is **NOT** supported in `WORKSPACE` mode. 12 | 13 | Example: 14 | 15 | ```starlark 16 | load("@package_metadata//purl:purl.bzl", "purl") 17 | 18 | package_metadata( 19 | name = "package_metadata", 20 | purl = purl.bazel(module_name(), module_version()), 21 | attributes = [ 22 | # ... 23 | ], 24 | visibility = ["//visibility:public"], 25 | ) 26 | ``` 27 | 28 | Args: 29 | name (str): The name of the Bazel module. Typically 30 | [module_name()](https://bazel.build/rules/lib/globals/build#module_name). 31 | version (str): The version of the Bazel module. Typically 32 | [module_version()](https://bazel.build/rules/lib/globals/build#module_version). 33 | May be empty or `None`. 34 | 35 | Returns: 36 | The `purl` for the Bazel module (e.g. `pkg:bazel/foo` or 37 | `pkg:bazel/bar@1.2.3`). 38 | """ 39 | 40 | if not version: 41 | return "pkg:bazel/{}".format(name) 42 | 43 | return "pkg:bazel/{}@{}".format(name, version) 44 | 45 | purl = struct( 46 | bazel = _bazel, 47 | ) 48 | -------------------------------------------------------------------------------- /tools/BUILD: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 2 | load("@package_metadata//licenses/rules:license.bzl", "license") 3 | load("@package_metadata//purl:purl.bzl", "purl") 4 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 5 | 6 | package( 7 | default_package_metadata = [":package_metadata"], 8 | default_visibility = ["//visibility:public"], 9 | ) 10 | 11 | package_metadata( 12 | name = "package_metadata", 13 | attributes = [ 14 | ":license", 15 | ], 16 | purl = purl.bazel( 17 | module_name(), 18 | module_version(), 19 | ), 20 | visibility = ["//visibility:public"], 21 | ) 22 | 23 | # TODO: Investigate why the example/srv targets are not comming up under 24 | # this license. It is as if we get the package_metadata target, but 25 | # then we do not descend to it during aspect traversal. 26 | license( 27 | name = "license", 28 | kind = "@package_metadata//licenses/spdx:Apache-2.0", 29 | text = "LICENSE", 30 | ) 31 | 32 | filegroup( 33 | name = "srcs", 34 | srcs = [ 35 | "//gather_metadata:srcs", 36 | ], 37 | visibility = ["//visibility:public"], 38 | ) 39 | 40 | # This is a convenience warpper to turn the metadata core into a bzl_library 41 | # that can be consumed by others. 42 | bzl_library( 43 | name = "core_metadata", 44 | srcs = [ 45 | "@package_metadata//:srcs", 46 | "@package_metadata//licenses:srcs", 47 | ], 48 | visibility = ["//visibility:public"], 49 | ) 50 | -------------------------------------------------------------------------------- /docs/metadata/purl/purl.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Module defining urils for [purl](https://github.com/package-url/purl-spec)s. 4 | 5 | 6 | 7 | ## purl.bazel 8 | 9 |
10 | load("@package_metadata//purl:purl.bzl", "purl")
11 | 
12 | purl.bazel(name, version)
13 | 
14 | 15 | Defines a `purl` for a Bazel module. 16 | 17 | This is typically used to construct `purl` for `package_metadata` targets in 18 | Bazel modules. 19 | 20 | This is **NOT** supported in `WORKSPACE` mode. 21 | 22 | Example: 23 | 24 | ```starlark 25 | load("@package_metadata//purl:purl.bzl", "purl") 26 | 27 | package_metadata( 28 | name = "package_metadata", 29 | purl = purl.bazel(module_name(), module_version()), 30 | attributes = [ 31 | # ... 32 | ], 33 | visibility = ["//visibility:public"], 34 | ) 35 | ``` 36 | 37 | 38 | **PARAMETERS** 39 | 40 | 41 | | Name | Description | Default Value | 42 | | :------------- | :------------- | :------------- | 43 | | name | The name of the Bazel module. Typically [module_name()](https://bazel.build/rules/lib/globals/build#module_name). | none | 44 | | version | The version of the Bazel module. Typically [module_version()](https://bazel.build/rules/lib/globals/build#module_version). May be empty or `None`. | none | 45 | 46 | **RETURNS** 47 | 48 | The `purl` for the Bazel module (e.g. `pkg:bazel/foo` or 49 | `pkg:bazel/bar@1.2.3`). 50 | 51 | 52 | -------------------------------------------------------------------------------- /metadata/purl/percent_encoding_test.bzl: -------------------------------------------------------------------------------- 1 | """Rule for testing `percent_encode` and `percent_decode`.""" 2 | 3 | load("//purl:percent_encoding.bzl", "percent_encode") 4 | 5 | visibility([ 6 | "//purl/...", 7 | ]) 8 | 9 | _bash_executable = """ 10 | #!/usr/bin/env bash 11 | 12 | echo "All tests passed" 13 | """.strip() 14 | 15 | _bat_executable = """ 16 | echo "Hello World" 17 | """.strip() 18 | 19 | def _percent_encoding_test_impl(ctx): 20 | for decoded, encoded in ctx.attr.cases.items(): 21 | actual_encoded = percent_encode(decoded) 22 | if encoded != actual_encoded: 23 | fail("Error encoding {}: expected {}, got {}".format(decoded, encoded, actual_encoded)) 24 | 25 | # Unix does not care about the file extension, so always use `.bat` so it 26 | # also works on Windows. 27 | executable = ctx.actions.declare_file("{}.bat".format(ctx.attr.name)) 28 | ctx.actions.write( 29 | output = executable, 30 | content = _bash_executable if (ctx.configuration.host_path_separator == ":") else _bat_executable, 31 | is_executable = True, 32 | ) 33 | 34 | return [ 35 | DefaultInfo( 36 | files = depset( 37 | direct = [ 38 | executable, 39 | ], 40 | ), 41 | executable = executable, 42 | ), 43 | ] 44 | 45 | percent_encoding_test = rule( 46 | implementation = _percent_encoding_test_impl, 47 | attrs = { 48 | "cases": attr.string_dict( 49 | mandatory = True, 50 | ), 51 | }, 52 | test = True, 53 | ) 54 | -------------------------------------------------------------------------------- /examples/vendor/constant_gen/defs.bzl: -------------------------------------------------------------------------------- 1 | """A trivial rule to turn a string into a C++ constant.""" 2 | 3 | load("@rules_cc//cc:cc_library.bzl", "cc_library") 4 | 5 | def _constant_gen_impl(ctx): 6 | # Turn text into a C++ constant. 7 | outputs = [ctx.outputs.src_out] 8 | ctx.actions.run( 9 | mnemonic = "GenerateConstant", 10 | progress_message = "Generating %s" % ctx.attr.var, 11 | outputs = outputs, 12 | executable = ctx.executable._generator, 13 | arguments = [ctx.outputs.src_out.path, ctx.attr.var, ctx.attr.text], 14 | ) 15 | return [DefaultInfo(files = depset(outputs))] 16 | 17 | _constant_gen = rule( 18 | implementation = _constant_gen_impl, 19 | attrs = { 20 | "src_out": attr.output(mandatory = True), 21 | "text": attr.string(mandatory = True), 22 | "var": attr.string(mandatory = False), 23 | "_generator": attr.label( 24 | default = Label("//vendor/constant_gen:constant_generator"), 25 | executable = True, 26 | allow_files = True, 27 | cfg = "exec", 28 | ), 29 | }, 30 | ) 31 | 32 | def constant_gen(name, text, var): 33 | # Generate the code 34 | _constant_gen( 35 | name = name + "_src_", 36 | src_out = name + "_src_.cc", 37 | text = text, 38 | var = var, 39 | applicable_licenses = ["//vendor/constant_gen:license_for_emitted_code"], 40 | ) 41 | 42 | # And turn it into a library we can link against 43 | cc_library( 44 | name = name, 45 | srcs = [name + "_src_"], 46 | applicable_licenses = ["//vendor/constant_gen:license_for_emitted_code"], 47 | ) 48 | -------------------------------------------------------------------------------- /metadata/licenses/rules/license_kind.bzl: -------------------------------------------------------------------------------- 1 | """Declares rule `license_kind`.""" 2 | 3 | load("//licenses/providers:license_kind_info.bzl", "LicenseKindInfo") 4 | 5 | visibility("public") 6 | 7 | def _license_kind_impl(ctx): 8 | return [ 9 | LicenseKindInfo( 10 | identifier = ctx.attr.identifier, 11 | name = ctx.attr.full_name, 12 | ), 13 | ] 14 | 15 | _license_kind = rule( 16 | implementation = _license_kind_impl, 17 | attrs = { 18 | "full_name": attr.string( 19 | mandatory = True, 20 | doc = """ 21 | The [PURL](https://github.com/package-url/purl-spec) uniquely identifying this 22 | package. 23 | """.strip(), 24 | ), 25 | "identifier": attr.string( 26 | mandatory = True, 27 | doc = """ 28 | The unique identifier of the license (e.g., `Apache-2.0`, `EUPL-1.1`). 29 | 30 | This is typically the [SPDX identifier](https://spdx.org/licenses/) of the 31 | license, but may also be a non-standard value (e.g., in case of a commercial 32 | license). 33 | """.strip(), 34 | ), 35 | }, 36 | provides = [ 37 | LicenseKindInfo, 38 | ], 39 | doc = """ 40 | Rule for declaring `LicenseKindInfo`. 41 | """.strip(), 42 | ) 43 | 44 | def license_kind( 45 | # Disallow unnamed attributes. 46 | *, 47 | # `_license_kind` attributes. 48 | name, 49 | identifier, 50 | full_name, 51 | # Common attributes (subset since this target is non-configurable). 52 | visibility = None): 53 | _license_kind( 54 | # `_license_kind` attributes. 55 | name = name, 56 | identifier = identifier, 57 | full_name = full_name, 58 | 59 | # Common attributes. 60 | visibility = visibility, 61 | applicable_licenses = [], 62 | ) 63 | -------------------------------------------------------------------------------- /examples/server/BUILD: -------------------------------------------------------------------------------- 1 | # Examples of applications and interactions with licenses 2 | 3 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 4 | load("@rules_cc//cc:cc_binary.bzl", "cc_binary") 5 | load("//sample_reports:licenses_used.bzl", "licenses_used") 6 | load("//sbom:sbom.bzl", "sbom") 7 | load("//vendor/constant_gen:defs.bzl", "constant_gen") 8 | 9 | package( 10 | default_applicable_licenses = ["//:package_metadata"], 11 | default_visibility = ["//visibility:public"], 12 | ) 13 | 14 | # Sample 15 | constant_gen( 16 | name = "message", 17 | text = "Hello, world.", 18 | var = "server_message", 19 | ) 20 | 21 | # This server is clean to ship. 22 | cc_binary( 23 | name = "my_server", 24 | srcs = ["server.cc"], 25 | deps = [":message"], 26 | ) 27 | 28 | # Verify the licenses are what we expect. The output shows that 29 | # :my_server only uses the unencumbered license type. 30 | licenses_used( 31 | name = "server_licenses", 32 | out = "server_licenses.txt", 33 | target = ":my_server", 34 | ) 35 | 36 | # This server uses something under a restricted license. 37 | cc_binary( 38 | name = "my_violating_server", 39 | srcs = ["server.cc"], 40 | deps = [ 41 | ":message", 42 | "//lib:lib_with_third_party_deps", 43 | "//lib:private_lib", 44 | "//vendor/acme", 45 | "//vendor/legacy_license:libfoo", 46 | "//vendor/libhhgttg", 47 | ], 48 | ) 49 | 50 | licenses_used( 51 | name = "violating_server_licenses", 52 | out = "violating_server_licenses.txt", 53 | target = ":my_violating_server", 54 | ) 55 | 56 | sbom( 57 | name = "server_sbom", 58 | out = "server_sbom.txt", 59 | target = ":my_violating_server", 60 | ) 61 | 62 | diff_test( 63 | name = "sbom_diff_test", 64 | file1 = ":server_sbom.txt", 65 | file2 = "server_sbom.golden", 66 | ) 67 | -------------------------------------------------------------------------------- /examples/vendor/constant_gen/BUILD: -------------------------------------------------------------------------------- 1 | # An example of a code generator with a distinct license for the generated code. 2 | 3 | load("@package_metadata//:defs.bzl", "package_metadata") 4 | load("@package_metadata//licenses/rules:license.bzl", "license") 5 | load("@rules_python//python:defs.bzl", "py_binary") 6 | load("//sample_reports:licenses_used.bzl", "licenses_used") 7 | load(":defs.bzl", "constant_gen") 8 | 9 | package( 10 | default_applicable_licenses = [":package_data"], 11 | default_visibility = ["//visibility:public"], 12 | ) 13 | 14 | package_metadata( 15 | name = "package_data", 16 | attributes = [ 17 | ":license_of_the_package", 18 | ], 19 | purl = "pkg:ftp//insecure@fictionalcorp.com/constant_generator-latest.tgz@0.0.1", 20 | ) 21 | 22 | # The default license for an entire package is typically named "license". 23 | license( 24 | name = "license_of_the_package", 25 | kind = "//my_org/licenses:generic_restricted", 26 | text = "LICENSE", 27 | ) 28 | 29 | license( 30 | name = "license_for_emitted_code", 31 | kind = "//my_org/licenses:unencumbered", 32 | text = "LICENSE_OF_OUTPUT", 33 | ) 34 | 35 | # The generator itself will be licensed under :license 36 | py_binary( 37 | name = "constant_generator", 38 | srcs = ["constant_generator.py"], 39 | python_version = "PY3", 40 | ) 41 | 42 | # Sample: This target will be licensed under :license_for_emitted_code 43 | constant_gen( 44 | name = "libhello", 45 | text = "Hello, world.", 46 | var = "hello_world", 47 | ) 48 | 49 | # Verify the licenses are what we expect 50 | licenses_used( 51 | name = "generator_licenses", 52 | out = "generator_licenses.txt", 53 | target = ":constant_generator", 54 | ) 55 | 56 | licenses_used( 57 | name = "generated_code_licenses", 58 | out = "generated_code_licenses.txt", 59 | target = ":libhello", 60 | ) 61 | 62 | # TODO(aiuto): Add a test that shows what we expect 63 | # For now: 64 | # bazel build //vendor/constant_gen:all 65 | # examine bazel-bin/vendor/constant_gen/generator_licenses.json 66 | # examine bazel-bin/vendor/constant_gen/generated_code_licenses.json 67 | -------------------------------------------------------------------------------- /examples/my_org/licenses/BUILD: -------------------------------------------------------------------------------- 1 | # We expect that all license_kind rules used by an organization exist in a 2 | # central place in their source repository. 3 | # 4 | # - This allows centralized audit and, with suport from the SCM, an assurance 5 | # that individual developers are not adding new license kinds to the code 6 | # base without authorization. 7 | # - When third party packages are used, they might come with license rules 8 | # pointing to well known license_kinds from @supply_chain_tools/licenses. 9 | # At import time, users can mechanically transform those target paths from 10 | # @supply_chain_tools to their own license_kind repository. 11 | # - The conditions for each license_kind may be customized for the organzation's 12 | # needs, and not match the conditions used by the canonical versions from 13 | # @supply_chain_tools. 14 | # - In rare cases, a third_party project will define their own license_kinds. 15 | # There is no reasonable automatic handling of that. Organizations will have 16 | # to hand inspect the license text to see if it matches the conditions, and 17 | # then will have to hand import it to their private license_kind repository. 18 | 19 | load("@package_metadata//licenses/rules:license_kind.bzl", "license_kind") 20 | 21 | package(default_visibility = ["//visibility:public"]) 22 | 23 | # license_kind rules generally appear in a central location per workspace. They 24 | # are intermingled with normal target build rules 25 | license_kind( 26 | name = "generic_notice", 27 | full_name = "a sample generic license", 28 | identifier = "generic_license", 29 | # TODO: Bring back conditions on kind. 30 | ) 31 | 32 | license_kind( 33 | name = "generic_restricted", 34 | full_name = "a sample generic license", 35 | identifier = "generic_restricted", 36 | ) 37 | 38 | license_kind( 39 | name = "unencumbered", 40 | full_name = "a sample unencumered license", 41 | identifier = "unencumbered", 42 | ) 43 | 44 | license_kind( 45 | name = "lgpl_like", 46 | full_name = "a sample generic license", 47 | identifier = "LGPL", 48 | ) 49 | 50 | license_kind( 51 | name = "acme_corp_paid", 52 | full_name = "The ACME per seat license.", 53 | identifier = "ACME", 54 | ) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Supply-chain rules for Bazel 2 | 3 | This repository contains Bazel modules for injecting and collecting supply-chain metadata into builds. 4 | 5 | - [Documentation](./docs) 6 | - Modules 7 | - [@package_metadata](./metadata) 8 | - Contact: 9 | - [Slack](https://bazelbuild.slack.com/archives/C04AZC3E729) 10 | - There is a working group which meets weekly on Thursdays at 2:30pm CET / 8:30am EST. [Meet link](https://meet.google.com/qop-eyei-cfh). 11 | - If you would like to participate, reach out on the slack channel for an invitation. 12 | - [Meeting notes](https://docs.google.com/document/d/1WhScaOLERet4Fxi4fa2Lpke2MgJZGvEE4EXeq6yb0LU) 13 | - Mailing list: [bazel-supply-chain-security@bazel.build](https://groups.google.com/a/bazel.build/g/bazel-supply-chain-security) 14 | 15 | 16 | This project is the successor to [rules_license](https://github.com/bazelbuild/rules_license). 17 | 18 | The intended use cases are: 19 | - declaring metadata about packages, such as 20 | - the licenses the package is available under 21 | - the canonical package name and version 22 | - copyright information 23 | - ... and more TBD in the future 24 | - gathering license declarations into artifacts to ship with code 25 | - applying organization specific compliance constriants against the 26 | set of packages used by a target. 27 | - producing SBOMs for built artifacts. 28 | 29 | WARNING: The code here is still in active initial development and will churn a lot. 30 | 31 | 32 | ## Roadmap 33 | 34 | In flux. 35 | 36 | ### Q3 2025 37 | 38 | The immediate concern is feature parity with rules_license and providing a smooth migration path. 39 | 40 | ## Background reading: 41 | 42 | 43 | These is for learning about the problem space, and our approach to solutions. Concrete specifications will always appear in checked in code rather than documents. 44 | - [License Checking with Bazel](https://docs.google.com/document/d/1uwBuhAoBNrw8tmFs-NxlssI6VRolidGYdYqagLqHWt8/edit#). 45 | - [OSS Licenses and Bazel Dependency Management](https://docs.google.com/document/d/1oY53dQ0pOPEbEvIvQ3TvHcFKClkimlF9AtN89EPiVJU/edit#) 46 | - [Adding OSS license declarations to Bazel](https://docs.google.com/document/d/1XszGbpMYNHk_FGRxKJ9IXW10KxMPdQpF5wWbZFpA4C8/edit#heading=h.5mcn15i0e1ch) 47 | -------------------------------------------------------------------------------- /maintainers/tools/update_spdx_list/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Tool for updating the list of SPDX licenses.""" 4 | 5 | import argparse 6 | import json 7 | import os 8 | import sys 9 | 10 | from typing import List 11 | 12 | 13 | def _run(args) -> int: 14 | lc_all = os.environ.get("LC_ALL") 15 | if lc_all != "en_US.UTF-8": 16 | print("Your environment settings will reorder the file badly.") 17 | print("Please rerun as:") 18 | print(' LC_ALL="en_US.UTF-8"', " ".join(sys.argv)) 19 | return 0 20 | 21 | with open(args.input) as f: 22 | licenses = json.load(f) 23 | 24 | with open(args.output, "w") as f: 25 | f.write( 26 | f""" 27 | # Generated by maintainers/tools/update_spdx_list/main.py. DO NOT EDIT. 28 | # 29 | # version: {licenses["licenseListVersion"]} 30 | 31 | load("//licenses/rules:license_kind.bzl", "license_kind") 32 | """.strip() 33 | ) 34 | 35 | for license in sorted(licenses["licenses"], key=lambda l: l["licenseId"]): 36 | identifier = license["licenseId"].replace('"', '\\"') 37 | full_name = license["name"].replace('"', '\\"') 38 | 39 | f.write("\n") 40 | f.write("\n") 41 | f.write( 42 | f""" 43 | license_kind( 44 | name = "{identifier}", 45 | full_name = "{full_name}", 46 | identifier = "{identifier}", 47 | visibility = ["//visibility:public"], 48 | ) 49 | """.strip() 50 | ) 51 | 52 | f.write("\n") 53 | 54 | return 0 55 | 56 | 57 | def main(argv: List[str]) -> int: 58 | """Main program. 59 | 60 | Args: 61 | argv: command-line arguments, such as sys.argv (including the program name 62 | in argv[0]). 63 | 64 | Returns: 65 | Zero on successful program termination, non-zero otherwise. 66 | """ 67 | 68 | parser = argparse.ArgumentParser(description="Update SPDX license list.") 69 | parser.add_argument( 70 | "--input", 71 | required=True, 72 | help="The JSON file containing all SPDX licenses. Download from https://raw.githubusercontent.com/spdx/license-list-data/main/json/licenses.json", 73 | ) 74 | parser.add_argument( 75 | "--output", required=True, help="The BUILD file writing targets to." 76 | ) 77 | 78 | return _run(parser.parse_args(argv[1:])) 79 | 80 | 81 | if __name__ == "__main__": 82 | sys.exit(main(sys.argv)) 83 | -------------------------------------------------------------------------------- /metadata/rules/package_metadata.bzl: -------------------------------------------------------------------------------- 1 | """Declares rule `package_metadata`.""" 2 | 3 | load("//providers:package_attribute_info.bzl", "PackageAttributeInfo") 4 | load("//providers:package_metadata_info.bzl", "PackageMetadataInfo") 5 | 6 | visibility("public") 7 | 8 | def _package_metadata_impl(ctx): 9 | attributes = [a[PackageAttributeInfo] for a in ctx.attr.attributes] 10 | 11 | metadata = ctx.actions.declare_file("{}.package-metadata.json".format(ctx.attr.name)) 12 | 13 | ctx.actions.write( 14 | output = metadata, 15 | content = json.encode({ 16 | "attributes": {a.kind: a.attributes.path for a in attributes}, 17 | "label": str(ctx.label), 18 | "purl": ctx.attr.purl, 19 | }), 20 | ) 21 | 22 | return [ 23 | DefaultInfo( 24 | files = depset( 25 | direct = [ 26 | metadata, 27 | ], 28 | ), 29 | ), 30 | PackageMetadataInfo( 31 | metadata = metadata, 32 | files = [a.files for a in attributes], 33 | ), 34 | ] 35 | 36 | _package_metadata = rule( 37 | implementation = _package_metadata_impl, 38 | attrs = { 39 | "attributes": attr.label_list( 40 | mandatory = False, 41 | doc = """ 42 | A list of `attributes` of the package (e.g., source location, license, ...). 43 | """.strip(), 44 | providers = [ 45 | PackageAttributeInfo, 46 | ], 47 | ), 48 | "purl": attr.string( 49 | mandatory = True, 50 | doc = """ 51 | The [PURL](https://github.com/package-url/purl-spec) uniquely identifying this 52 | package. 53 | """.strip(), 54 | ), 55 | }, 56 | provides = [ 57 | PackageMetadataInfo, 58 | ], 59 | doc = """ 60 | Rule for declaring `PackageMetadataInfo`, typically of a `bzlmod` module. 61 | """.strip(), 62 | ) 63 | 64 | def package_metadata( 65 | # Disallow unnamed attributes. 66 | *, 67 | # `_package_metadata` attributes. 68 | name, 69 | purl, 70 | attributes = [], 71 | # Common attributes (subset since this target is non-configurable). 72 | visibility = None, 73 | tags = None): 74 | _package_metadata( 75 | # `_package_metadata` attributes. 76 | name = name, 77 | purl = purl, 78 | attributes = attributes, 79 | 80 | # Common attributes. 81 | visibility = visibility, 82 | tags = tags, 83 | applicable_licenses = [], 84 | ) 85 | -------------------------------------------------------------------------------- /lib/supplychain-go/cmd/spdx/spdx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | supplychain "github.com/bazel-contrib/supply-chain/lib/supplychain-go" 10 | "github.com/bazel-contrib/supply-chain/lib/supplychain-go/internal/sbom" 11 | spdxJson "github.com/spdx/tools-golang/json" 12 | "github.com/spdx/tools-golang/spdx" 13 | "github.com/spdx/tools-golang/spdx/v2/common" 14 | spdxTV "github.com/spdx/tools-golang/tagvalue" 15 | spdxYaml "github.com/spdx/tools-golang/yaml" 16 | ) 17 | 18 | var ( 19 | outPath = flag.String("out", "", "The path to write the generated SPDX SBOM.") 20 | configPath = flag.String("config", "", "The path to the SBOM generation configuration file.") 21 | format = flag.String("format", "", "The output format of the SPDX SBOM.") 22 | ) 23 | 24 | func main() { 25 | flag.Parse() 26 | var config sbom.GenConfig 27 | 28 | configBytes, err := os.ReadFile(*configPath) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | json.Unmarshal(configBytes, &config) 34 | 35 | out, err := os.OpenFile(*outPath, os.O_WRONLY|os.O_CREATE, 0664) 36 | if err != nil { 37 | panic(err) 38 | } 39 | defer out.Close() 40 | 41 | doc, err := GenerateDocument(config) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | must := func(err error) { 47 | if err != nil { 48 | panic(err) 49 | } 50 | } 51 | 52 | switch *format { 53 | case "json": 54 | must(spdxJson.Write(doc, out)) 55 | case "yaml": 56 | must(spdxYaml.Write(doc, out)) 57 | case "tag-value": 58 | must(spdxTV.Write(doc, out)) 59 | default: 60 | panic(fmt.Sprintf("'%s' is not a supported format", *format)) 61 | } 62 | } 63 | 64 | func GenerateDocument(config sbom.GenConfig) (*spdx.Document, error) { 65 | spdxPackages := make([]*spdx.Package, len(config.Deps)) 66 | 67 | for i, dep := range config.Deps { 68 | pkgMetadata, err := supplychain.ReadPackageMetadataFromFile(dep.Metadata) 69 | if err != nil { 70 | return nil, err 71 | } 72 | spdxPackages[i] = &spdx.Package{ 73 | PackageSPDXIdentifier: common.ElementID(fmt.Sprintf("dep-%d", i)), 74 | PackageExternalReferences: []*spdx.PackageExternalReference{ 75 | { 76 | Category: "PACKAGE-MANAGER", 77 | RefType: "purl", 78 | Locator: pkgMetadata.GetPURL().String(), 79 | }, 80 | }, 81 | PackageName: pkgMetadata.GetPURL().Name, 82 | } 83 | } 84 | 85 | doc := spdx.Document{ 86 | SPDXIdentifier: "DOCUMENT", 87 | SPDXVersion: "SPDX-2.3", 88 | Packages: spdxPackages, 89 | CreationInfo: &spdx.CreationInfo{}, 90 | } 91 | 92 | return &doc, nil 93 | } 94 | -------------------------------------------------------------------------------- /rules_license/rules/current_module_package_info.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Rules for declaring metadata about a package.""" 15 | 16 | load( 17 | "@rules_license//rules:package_info.bzl", 18 | "package_info", 19 | ) 20 | 21 | # 22 | # current_module_package_info() 23 | # 24 | 25 | _DEFAULT_REGISTRY = "https://bcr.bazel.build" 26 | 27 | # buildifier: disable=function-docstring-args 28 | def current_module_package_info( 29 | name, 30 | registry = _DEFAULT_REGISTRY, 31 | visibility = ["//:__subpackages__"], 32 | **kwargs): 33 | """A wrapper around package_info with info for the current Bazel module. 34 | 35 | If `//:package_info` is a target of this macro, it can be registered for the 36 | entire module by adding a `REPO.bazel` file with the following content to 37 | the root of the module: 38 | 39 | ``` 40 | repo(default_package_metadata = ["//:package_info"]) 41 | ``` 42 | 43 | @wraps(package_info) 44 | 45 | Args: 46 | name: str target name. 47 | registry: str the URL of the registry hosting the module. 48 | visibility: list[str] visibility of the target. 49 | kwargs: other args. Most are ignored. 50 | """ 51 | 52 | package_name = native.module_name() 53 | package_version = native.module_version() 54 | 55 | normalized_registry = registry.rstrip("/") 56 | package_url = "{registry}/modules/{name}/{version}/source.json".format( 57 | registry = normalized_registry, 58 | name = package_name, 59 | version = package_version, 60 | ) 61 | purl = "pkg:bazel/{name}@{version}{registry_qualifier}".format( 62 | name = package_name, 63 | version = package_version, 64 | registry_qualifier = "" if normalized_registry == _DEFAULT_REGISTRY else "?repository_url=" + normalized_registry, 65 | ) 66 | 67 | package_info( 68 | name = name, 69 | package_name = package_name, 70 | package_url = package_url, 71 | package_version = package_version, 72 | purl = purl, 73 | visibility = visibility, 74 | **kwargs 75 | ) 76 | -------------------------------------------------------------------------------- /docs/metadata/licenses/defs.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public API of `@package_metadata//licenses`. 4 | 5 | 6 | 7 | ## LicenseKindInfo 8 | 9 |
10 | load("@package_metadata//licenses:defs.bzl", "LicenseKindInfo")
11 | 
12 | LicenseKindInfo(identifier, name)
13 | 
14 | 15 | Provides information to identify a license. 16 | 17 | **FIELDS** 18 | 19 | | Name | Description | 20 | | :------------- | :------------- | 21 | | identifier | A [string](https://bazel.build/rules/lib/core/string) uniquely identifying the license (e.g., `Apache-2.0`, `EUPL-1.1`).

This is typically the [SPDX identifier](https://spdx.org/licenses/) of the license, but may also be a non-standard value (e.g., in case of a commercial license). | 22 | | name | A [string](https://bazel.build/rules/lib/core/string) containing the (human readable) name of the license (e.g., `Apache License 2.0`, `European Union Public License 1.1`) | 23 | 24 | 25 | 26 | 27 | ## license 28 | 29 |
30 | load("@package_metadata//licenses:defs.bzl", "license")
31 | 
32 | license(*, name, kind, text, visibility)
33 | 
34 | 35 | 36 | 37 | **PARAMETERS** 38 | 39 | 40 | | Name | Description | Default Value | 41 | | :------------- | :------------- | :------------- | 42 | | name |

-

| none | 43 | | kind |

-

| none | 44 | | text |

-

| `None` | 45 | | visibility |

-

| `None` | 46 | 47 | 48 | 49 | 50 | ## license_kind 51 | 52 |
53 | load("@package_metadata//licenses:defs.bzl", "license_kind")
54 | 
55 | license_kind(*, name, identifier, full_name, visibility)
56 | 
57 | 58 | 59 | 60 | **PARAMETERS** 61 | 62 | 63 | | Name | Description | Default Value | 64 | | :------------- | :------------- | :------------- | 65 | | name |

-

| none | 66 | | identifier |

-

| none | 67 | | full_name |

-

| none | 68 | | visibility |

-

| `None` | 69 | 70 | 71 | -------------------------------------------------------------------------------- /rules_license/rules/license_kind.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Proof of concept. License restriction.""" 15 | 16 | load("@package_metadata//licenses/providers:license_kind_info.bzl", "LicenseKindInfo") 17 | load("@rules_license//rules:providers.bzl", _legacyLicenseKindInfo = "LicenseKindInfo") 18 | 19 | # 20 | # License Kind: The declaration of a well known category of license, for example, 21 | # Apache, MIT, LGPL v2. An organization may also declare its own license kinds 22 | # that it may user privately. 23 | # 24 | 25 | def _license_kind_impl(ctx): 26 | identifier = "@%s//%s:%s" % ( 27 | # Remove the disambiguator on the repo name so we get the actual 28 | # canonical name for this target. 29 | # TODO: This could be more robust 30 | ctx.label.repo_name.replace("+_repo_rules+", ""), 31 | ctx.label.package, 32 | ctx.label.name, 33 | ) 34 | legacy_provider = _legacyLicenseKindInfo( 35 | name = ctx.attr.name, 36 | label = identifier, 37 | long_name = ctx.attr.long_name, 38 | conditions = ctx.attr.conditions, 39 | ) 40 | provider = LicenseKindInfo( 41 | identifier = identifier, 42 | name = ctx.attr.long_name, 43 | ) 44 | return [legacy_provider, provider] 45 | 46 | _license_kind = rule( 47 | implementation = _license_kind_impl, 48 | attrs = { 49 | "conditions": attr.string_list( 50 | doc = "Conditions to be met when using software under this license." + 51 | " Conditions are defined by the organization using this license.", 52 | mandatory = True, 53 | ), 54 | "canonical_text": attr.label( 55 | doc = "File containing the canonical text for this license. Must be UTF-8 encoded.", 56 | allow_single_file = True, 57 | ), 58 | "long_name": attr.string(doc = "Human readable long name of license."), 59 | "url": attr.string(doc = "URL pointing to canonical license definition"), 60 | }, 61 | ) 62 | 63 | def license_kind(name, **kwargs): 64 | """Wrapper for license_kind. 65 | 66 | @wraps(_license_kind) 67 | """ 68 | if "conditions" not in kwargs: 69 | kwargs["conditions"] = [] 70 | if "long_name" not in kwargs: 71 | kwargs["long_name"] = name 72 | _license_kind( 73 | name = name, 74 | applicable_licenses = [], 75 | **kwargs 76 | ) 77 | -------------------------------------------------------------------------------- /lib/supplychain-go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= 2 | github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 8 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= 10 | github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= 14 | github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= 15 | github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 18 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 19 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 20 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 22 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 23 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 24 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 25 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 30 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 31 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 32 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 33 | -------------------------------------------------------------------------------- /rules_license/rules/providers.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Basic providers for license rules. 15 | 16 | This file should only contain the basic providers needed to create 17 | license and package_info declarations. Providers needed to gather 18 | them are declared in other places. 19 | """ 20 | 21 | LicenseKindInfo = provider( 22 | doc = """Provides information about a license_kind instance.""", 23 | fields = { 24 | "conditions": "list(string): List of conditions to be met when using this packages under this license.", 25 | "label": "Label: The full path to the license kind definition.", 26 | "long_name": "string: Human readable license name", 27 | "name": "string: Canonical license name", 28 | }, 29 | ) 30 | 31 | LicenseInfo = provider( 32 | doc = """Provides information about a license instance.""", 33 | fields = { 34 | "copyright_notice": "string: Human readable short copyright notice", 35 | "label": "Label: label of the license rule", 36 | "license_kinds": "list(LicenseKindInfo): License kinds ", 37 | "license_text": "string: The license file path", 38 | # TODO(aiuto): move to PackageInfo 39 | "package_name": "string: Human readable package name", 40 | "package_url": "URL from which this package was downloaded.", 41 | "package_version": "Human readable version string", 42 | }, 43 | ) 44 | 45 | PackageInfo = provider( 46 | doc = """Provides information about a package.""", 47 | fields = { 48 | "type": "string: How to interpret data", 49 | "label": "Label: label of the package_info rule", 50 | "package_name": "string: Human readable package name", 51 | "package_url": "string: URL from which this package was downloaded.", 52 | "package_version": "string: Human readable version string", 53 | "purl": "string: package url matching the purl spec (https://github.com/package-url/purl-spec)", 54 | }, 55 | ) 56 | 57 | # This is more extensible. Because of the provider implementation, having a big 58 | # dict of values rather than named fields is not much more costly. 59 | # Design choice. Replace data with actual providers, such as PackageInfo 60 | ExperimentalMetadataInfo = provider( 61 | doc = """Generic bag of metadata.""", 62 | fields = { 63 | "type": "string: How to interpret data", 64 | "label": "Label: label of the metadata rule", 65 | "data": "String->any: Map of names to values", 66 | }, 67 | ) 68 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | pull_request: 9 | branches: 10 | - "main" 11 | 12 | merge_group: {} 13 | 14 | workflow_call: {} 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | checks: 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | include: 25 | - name: "Docs" 26 | bazel_version: "8.x" 27 | package: "docs" 28 | runner: "ubuntu-latest" 29 | 30 | - name: "Bazel latest: @package_metadata (Linux x64)" 31 | bazel_version: "rolling" 32 | package: "metadata" 33 | runner: "ubuntu-latest" 34 | 35 | - name: "Bazel 8.x: @package_metadata (Linux x64)" 36 | bazel_version: "8.x" 37 | package: "metadata" 38 | runner: "ubuntu-latest" 39 | 40 | - name: "Bazel 7.x: @package_metadata (Linux x64)" 41 | bazel_version: "7.x" 42 | package: "metadata" 43 | runner: "ubuntu-latest" 44 | 45 | - name: "Bazel latest: @supply-chain-go (Linux x64)" 46 | bazel_version: "9.0.0-pre.20250921.2" 47 | package: "lib/supplychain-go" 48 | runner: "ubuntu-latest" 49 | 50 | - name: "Bazel 8.x: @supply-chain-go (Linux x64)" 51 | bazel_version: "8.x" 52 | package: "lib/supplychain-go" 53 | runner: "ubuntu-latest" 54 | 55 | - name: "Bazel 7.x: @supply-chain-go (Linux x64)" 56 | bazel_version: "7.x" 57 | package: "lib/supplychain-go" 58 | runner: "ubuntu-latest" 59 | 60 | - name: "Bazel 7.x: @tools (Linux x64)" 61 | bazel_version: "7.x" 62 | package: "tools" 63 | runner: "ubuntu-latest" 64 | 65 | - name: "Bazel 8.x: @tools (Linux x64)" 66 | bazel_version: "8.x" 67 | package: "tools" 68 | runner: "ubuntu-latest" 69 | 70 | - name: "Bazel 7.x: @examples (Linux x64)" 71 | bazel_version: "7.x" 72 | package: "examples" 73 | runner: "ubuntu-latest" 74 | 75 | - name: "Bazel 8.x: @examples (Linux x64)" 76 | bazel_version: "8.x" 77 | package: "examples" 78 | runner: "ubuntu-latest" 79 | 80 | runs-on: 81 | - "${{ matrix.runner }}" 82 | 83 | steps: 84 | - uses: "actions/checkout@v6" 85 | 86 | - name: "Run (Bash)" 87 | working-directory: "${{ github.workspace }}/${{ matrix.package }}" 88 | env: 89 | USE_BAZEL_VERSION: "${{ matrix.bazel_version }}" 90 | run: bash "${{ github.workspace }}/.github/workflows/ci.sh" 91 | 92 | status: 93 | runs-on: 94 | - "ubuntu-latest" 95 | 96 | needs: 97 | - "checks" 98 | if: always() 99 | 100 | steps: 101 | - name: "Report status" 102 | env: 103 | RESULT: "${{ needs.checks.result }}" 104 | run: | 105 | echo "Status: ${RESULT}" 106 | if [[ "${RESULT}" != "success" ]]; then 107 | exit 1 108 | fi 109 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Create release" 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | required: true 8 | type: "string" 9 | 10 | permissions: 11 | id-token: write 12 | attestations: write 13 | contents: write 14 | 15 | jobs: 16 | ci: 17 | name: "CI" 18 | uses: "./.github/workflows/ci.yml" 19 | 20 | create_tag: 21 | name: "Create tag" 22 | runs-on: 23 | - "ubuntu-latest" 24 | needs: 25 | - "ci" 26 | 27 | steps: 28 | - uses: actions/checkout@v6 29 | 30 | - name: "Create tag" 31 | id: "tag" 32 | env: 33 | VERSION: "${{ inputs.version }}" 34 | 35 | GIT_AUTHOR_NAME: "${{ github.actor }}" 36 | GIT_AUTHOR_EMAIL: "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" 37 | GIT_COMMITTER_NAME: "${{ github.actor }}" 38 | GIT_COMMITTER_EMAIL: "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" 39 | run: | 40 | # 1. Download `buildozer` 41 | # 42 | # TODO(yannic): Use `RUNNER_TOOL_CACHE` and cache between runs? 43 | curl -o "${RUNNER_TEMP}/buildozer" -L "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildozer-linux-amd64" 44 | chmod +x "${RUNNER_TEMP}/buildozer" 45 | 46 | # 2. Update `MODULE.bazel` and commit. 47 | # 48 | # TODO(yannic): Allow for all repos to go at different cadences. 49 | bzlmod_names=("package_metadata" "package_metadata_extensions" "supply-chain-go" "supply-chain-docs") 50 | bzlmod_directories=("metadata" "metadata-extensions" "lib/supplychain-go" "docs") 51 | 52 | for bzlmod_directory in "${bzlmod_directories[@]}"; do 53 | for bzlmod_name in "${bzlmod_names[@]}"; do 54 | "${RUNNER_TEMP}/buildozer" "set version ${VERSION}" "//${bzlmod_directory}/MODULE.bazel:${bzlmod_name}" || true 55 | done 56 | 57 | git add "${bzlmod_directory}/MODULE.bazel" 58 | done 59 | git commit -m "Release `v${VERSION}`" 60 | 61 | # 3. Push release tag. 62 | git tag "v${VERSION}" "HEAD" 63 | git push origin "v${VERSION}" 64 | 65 | release: 66 | name: "Create GitHub release" 67 | needs: 68 | - "create_tag" 69 | 70 | uses: "bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7.2.3" 71 | with: 72 | # The workflow appends `--disk_cache` here. There seems to be now way to 73 | # opt out, so use `echo || false` to make it a noop. 74 | bazel_test_command: echo "Already executed by step 'CI'" || false 75 | release_files: "supply-chain-*.tar.gz" 76 | tag_name: "v${{ inputs.version }}" 77 | 78 | publish: 79 | name: "Publish to BCR" 80 | needs: 81 | - "release" 82 | 83 | permissions: 84 | attestations: write 85 | contents: write 86 | id-token: write 87 | 88 | uses: "bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.0.0" 89 | with: 90 | tag_name: "v${{ inputs.version }}" 91 | registry_fork: "bazel-contrib/bazel-central-registry" 92 | draft: false 93 | secrets: 94 | publish_token: "${{ secrets.BCR_PUBLISH_TOKEN }}" 95 | -------------------------------------------------------------------------------- /rules_license/rules/license_impl.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Rules for declaring the licenses used by a package. 15 | 16 | """ 17 | 18 | load("@package_metadata//licenses/providers:license_kind_info.bzl", "LicenseKindInfo") 19 | load("@package_metadata//providers:package_attribute_info.bzl", "PackageAttributeInfo") 20 | load( 21 | "@rules_license//rules:providers.bzl", 22 | _legacyLicenseInfo = "LicenseInfo", 23 | _legacyLicenseKindInfo = "LicenseKindInfo", 24 | ) 25 | 26 | # Debugging verbosity 27 | _VERBOSITY = 0 28 | 29 | def _debug(loglevel, msg): 30 | if _VERBOSITY > loglevel: 31 | print(msg) # buildifier: disable=print 32 | 33 | # 34 | # license() 35 | # 36 | 37 | def license_rule_impl(ctx): 38 | legacy_provider = _legacyLicenseInfo( 39 | license_kinds = tuple([k[_legacyLicenseKindInfo] for k in ctx.attr.license_kinds]), 40 | copyright_notice = ctx.attr.copyright_notice, 41 | package_name = ctx.attr.package_name or ctx.label.package, 42 | package_url = ctx.attr.package_url, 43 | package_version = ctx.attr.package_version, 44 | license_text = ctx.file.license_text, 45 | label = ctx.label, 46 | ) 47 | _debug(0, legacy_provider) 48 | 49 | # Also return new style 50 | kinds = [k[LicenseKindInfo] for k in ctx.attr.license_kinds] 51 | if not kinds: 52 | # This should not happen, because the override for license_kind should 53 | # always synthesize a new LicenseKindInfo 54 | kinds = [LicenseKindInfo(identifier = "", name = "")] 55 | 56 | attribute = { 57 | "kind": "bazel-contrib.supply-chain.attribute.license", 58 | "license_kinds": [ 59 | { 60 | "identifier": kind.identifier, 61 | "name": kind.name, 62 | } 63 | for kind in kinds 64 | ], 65 | "label": str(ctx.label), 66 | } 67 | files = [] 68 | 69 | if ctx.attr.license_text: 70 | attribute["text"] = ctx.file.license_text.path 71 | files.append(ctx.attr.license_text[DefaultInfo].files) 72 | 73 | output = ctx.actions.declare_file("{}.package-attribute.json".format(ctx.attr.name)) 74 | ctx.actions.write( 75 | output = output, 76 | content = json.encode(attribute), 77 | ) 78 | 79 | return [ 80 | DefaultInfo( 81 | files = depset( 82 | direct = [ 83 | output, 84 | ], 85 | ), 86 | ), 87 | PackageAttributeInfo( 88 | kind = "build.bazel.rules_license.license", 89 | attributes = output, 90 | files = files, 91 | ), 92 | legacy_provider, 93 | ] 94 | -------------------------------------------------------------------------------- /maintainers/tools/purl_tables/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Tool for updating the tables for PURL.""" 4 | 5 | import argparse 6 | import os 7 | import sys 8 | 9 | from typing import List 10 | from urllib.parse import quote 11 | 12 | 13 | def _percent_encode(s: str) -> str: 14 | """ 15 | Percent-encodes a character according to the [PURL specification](https://github.com/package-url/purl-spec/blob/main/PURL-SPECIFICATION.rst#character-encoding). 16 | """ 17 | 18 | try: 19 | return quote(s, encoding=None).replace("%3A", ":") 20 | except: 21 | return None 22 | 23 | 24 | def _run(args) -> int: 25 | lc_all = os.environ.get("LC_ALL") 26 | if lc_all != "en_US.UTF-8": 27 | print("Your environment settings will reorder the file badly.") 28 | print("Please rerun as:") 29 | print(' LC_ALL="en_US.UTF-8"', " ".join(sys.argv)) 30 | return 0 31 | 32 | with open(args.output, "w") as f: 33 | f.write("""# Generated by maintainers/tools/purl_tables/main.py. DO NOT EDIT. 34 | 35 | visibility([ 36 | "//purl/...", 37 | ]) 38 | 39 | """) 40 | f.write("encode_byte = {\n") 41 | for i in range(0, 256, 1): 42 | f.write(""" %d: "%s",\n""" % (i, _percent_encode(bytes([i])))) 43 | f.write("}\n") 44 | 45 | percent_encoding_tests = [ 46 | "foo", 47 | "Hello, World!", 48 | "path: /foo", 49 | # German 50 | "München", 51 | "Köln", 52 | # Swedish 53 | "Småland", 54 | # French 55 | "française", 56 | # Spanish 57 | "¡Hola Mundo!", 58 | # Arabic 59 | "مرحبا بالعالم!", 60 | # Chinese 61 | "你好世界!", 62 | # Japanese 63 | "こんにちは世界!", 64 | # Emoji, 65 | "🙎", 66 | "🙊", 67 | # Emoji with modifiers. 68 | str( 69 | b"\xf0\x9f\x99\x8e\xf0\x9f\x8f\xbe\xe2\x80\x8d\xe2\x99\x80\xef\xb8\x8f", 70 | "utf8", 71 | ), 72 | ] 73 | 74 | test_cases = {s: _percent_encode(s) for s in percent_encoding_tests} 75 | 76 | if args.output.endswith(".bzl"): 77 | test_data = args.output.replace(".bzl", "_test.bzl") 78 | with open(test_data, "w") as f: 79 | f.write("""# Generated by maintainers/tools/purl_tables/main.py. DO NOT EDIT. 80 | 81 | visibility([ 82 | "//purl/...", 83 | ]) 84 | """) 85 | f.write("test_cases = {\n") 86 | for s in percent_encoding_tests: 87 | f.write(" \"%s\": \"%s\",\n" % (s, _percent_encode(s))) 88 | f.write("}\n") 89 | 90 | return 0 91 | 92 | 93 | def main(argv: List[str]) -> int: 94 | """Main program. 95 | Args: 96 | argv: command-line arguments, such as sys.argv (including the program name 97 | in argv[0]). 98 | Returns: 99 | Zero on successful program termination, non-zero otherwise. 100 | """ 101 | 102 | parser = argparse.ArgumentParser(description="Update tables for PURL.") 103 | parser.add_argument("--output", required=True, help="The .bzl file to write to.") 104 | 105 | return _run(parser.parse_args(argv[1:])) 106 | 107 | 108 | if __name__ == "__main__": 109 | sys.exit(main(sys.argv)) 110 | -------------------------------------------------------------------------------- /metadata/licenses/rules/license.bzl: -------------------------------------------------------------------------------- 1 | """Declares rule `license`.""" 2 | 3 | load("//licenses/providers:license_kind_info.bzl", "LicenseKindInfo") 4 | load("//providers:package_attribute_info.bzl", "PackageAttributeInfo") 5 | 6 | visibility("public") 7 | 8 | def _license_impl(ctx): 9 | kind = ctx.attr.kind[LicenseKindInfo] 10 | attribute = { 11 | "kind": { 12 | "identifier": kind.identifier, 13 | "name": kind.name, 14 | }, 15 | "label": str(ctx.label), 16 | } 17 | files = [] 18 | 19 | if ctx.attr.text: 20 | attribute["text"] = ctx.file.text.path 21 | files.append(ctx.attr.text[DefaultInfo].files) 22 | 23 | output = ctx.actions.declare_file("{}.package-attribute.json".format(ctx.attr.name)) 24 | ctx.actions.write( 25 | output = output, 26 | content = json.encode(attribute), 27 | ) 28 | 29 | return [ 30 | DefaultInfo( 31 | files = depset( 32 | direct = [ 33 | output, 34 | ], 35 | ), 36 | ), 37 | PackageAttributeInfo( 38 | kind = "build.bazel.attribute.license", 39 | attributes = output, 40 | files = files, 41 | ), 42 | ] 43 | 44 | _license = rule( 45 | implementation = _license_impl, 46 | attrs = { 47 | "kind": attr.label( 48 | mandatory = True, 49 | doc = """ 50 | The kind of license this license is classified as. 51 | 52 | This is typically a `license_kind` target. 53 | 54 | See `@package_metadata//licenses/spdx`. 55 | """.strip(), 56 | providers = [ 57 | LicenseKindInfo, 58 | ], 59 | ), 60 | "text": attr.label( 61 | mandatory = False, 62 | allow_single_file = True, 63 | doc = """ 64 | The [File](https://bazel.build/rules/lib/builtins/File) with the text of the 65 | license. 66 | """.strip(), 67 | ), 68 | }, 69 | doc = """ 70 | Rule for declaring the license of a package or target. 71 | 72 | This is typically passed to `package_metadata.attributes`. 73 | 74 | Usage: 75 | 76 | ```starlark 77 | load("@package_metadata//licenses/rules:license.bzl", "license") 78 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 79 | 80 | package_metadata( 81 | name = "package_metadata", 82 | purl = "...", 83 | attributes = [ 84 | ":license", 85 | ], 86 | visibility = ["//visibility:public"], 87 | ) 88 | 89 | license( 90 | name = "license", 91 | kind = "@package_metadata//licenses/spdx:GPL-3.0", 92 | text = "COPYING", 93 | ) 94 | """.strip(), 95 | ) 96 | 97 | def license( 98 | # Disallow unnamed attributes. 99 | *, 100 | # `_license` attributes. 101 | name, 102 | kind, 103 | text = None, 104 | # Common attributes (subset since this target is non-configurable). 105 | visibility = None): 106 | _license( 107 | # `_license` attributes. 108 | name = name, 109 | kind = kind, 110 | text = text, 111 | 112 | # Common attributes. 113 | visibility = visibility, 114 | applicable_licenses = [], 115 | ) 116 | -------------------------------------------------------------------------------- /rules_license/licenses/generic/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Bazel Authors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """license_kind()s for generic license identifiers.""" 16 | 17 | # This is a set of license_kind declarations based on the legacy Bazel license 18 | # identifiers. 19 | # 20 | # Projects using one of these licenses may reference the license_kind targets 21 | # here. For example, their BUILD file might contain: 22 | # 23 | # package(default_applicable_licenses = [":license"]) 24 | # 25 | # license( 26 | # name = "license", 27 | # license_kinds = ["@rules_license//licenses/generic:notice"], 28 | # license_text = "LICENSE", 29 | # ) 30 | # 31 | 32 | # These licenses represent the LicenseType enum originally baked into Bazel. See 33 | # https://github.com/bazelbuild/bazel/blob/fcfca6157b6de903ab942cef2fc460bf8c8da6ed/src/main/java/com/google/devtools/build/lib/packages/License.java#L59 34 | # 35 | # Packages may create license rules that use these license_kinds when the 36 | # package has provided a LICENSE file, but the text in it does not match any 37 | # of the well known licenses in @rules_license//licenses/spdx:* 38 | load("@rules_license//rules:license_kind.bzl", "license_kind") 39 | 40 | package( 41 | default_applicable_licenses = ["//:license"], 42 | default_visibility = ["//visibility:public"], 43 | ) 44 | 45 | licenses(["notice"]) 46 | 47 | filegroup( 48 | name = "standard_package", 49 | srcs = ["BUILD"], 50 | ) 51 | 52 | # "none" should be used for packages which are distributed with no license of 53 | # any kind. You can use this no-op license as a positive indication that the 54 | # code's license terms were reviewed, so that linters will not flag it later as 55 | # unreviewed. 56 | license_kind( 57 | name = "none", 58 | ) 59 | 60 | # See: https://opensource.google/docs/thirdparty/licenses/#unencumbered 61 | license_kind( 62 | name = "unencumbered", 63 | ) 64 | 65 | # See: https://opensource.google/docs/thirdparty/licenses/#notice 66 | license_kind( 67 | name = "notice", 68 | conditions = [ 69 | "notice", 70 | ], 71 | ) 72 | 73 | # See: https://opensource.google/docs/thirdparty/licenses/#permissive 74 | license_kind( 75 | name = "permissive", 76 | conditions = [ 77 | "permissive", 78 | ], 79 | ) 80 | 81 | # See: https://opensource.google/docs/thirdparty/licenses/#reciprocal 82 | license_kind( 83 | name = "reciprocal", 84 | conditions = [ 85 | "reciprocal", 86 | ], 87 | ) 88 | 89 | # See: https://opensource.google/docs/thirdparty/licenses/#restricted 90 | license_kind( 91 | name = "restricted", 92 | conditions = [ 93 | "restricted", 94 | ], 95 | ) 96 | 97 | # See: https://opensource.google/docs/thirdparty/licenses/#ByExceptionOnly 98 | license_kind( 99 | name = "by_exception_only", 100 | conditions = [ 101 | "by_exception_only", 102 | ], 103 | ) 104 | -------------------------------------------------------------------------------- /docs/supply_chain_tools/gather_metadata/gather_metadata.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rules and macros for collecting metadata providers. 4 | 5 | 6 | 7 | ## metadata_info_to_json 8 | 9 |
 10 | load("@supply_chain_tools//gather_metadata:gather_metadata.bzl", "metadata_info_to_json")
 11 | 
 12 | metadata_info_to_json(metadata_info)
 13 | 
14 | 15 | Render a single LicenseInfo provider to JSON 16 | 17 | **PARAMETERS** 18 | 19 | 20 | | Name | Description | Default Value | 21 | | :------------- | :------------- | :------------- | 22 | | metadata_info | A LicenseInfo. | none | 23 | 24 | **RETURNS** 25 | 26 | [(str)] list of LicenseInfo values rendered as JSON. 27 | 28 | 29 | 30 | 31 | ## write_metadata_info 32 | 33 |
 34 | load("@supply_chain_tools//gather_metadata:gather_metadata.bzl", "write_metadata_info")
 35 | 
 36 | write_metadata_info(ctx, deps, json_out)
 37 | 
38 | 39 | Writes TransitiveMetadataInfo providers for a set of targets as JSON. 40 | 41 | TODO(aiuto): Document JSON schema. But it is under development, so the current 42 | best place to look is at tests/hello_licenses.golden. 43 | 44 | Usage: 45 | write_metadata_info must be called from a rule implementation, where the 46 | rule has run the gather_metadata_info aspect on its deps to 47 | collect the transitive closure of LicenseInfo providers into a 48 | LicenseInfo provider. 49 | 50 | foo = rule( 51 | implementation = _foo_impl, 52 | attrs = { 53 | "deps": attr.label_list(aspects = [gather_metadata_info]) 54 | } 55 | ) 56 | 57 | def _foo_impl(ctx): 58 | ... 59 | out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name) 60 | write_metadata_info(ctx, ctx.attr.deps, metadata_file) 61 | 62 | 63 | **PARAMETERS** 64 | 65 | 66 | | Name | Description | Default Value | 67 | | :------------- | :------------- | :------------- | 68 | | ctx | context of the caller | none | 69 | | deps | a list of deps which should have TransitiveMetadataInfo providers. This requires that you have run the gather_metadata_info aspect over them | none | 70 | | json_out | output handle to write the JSON info | none | 71 | 72 | 73 | 74 | 75 | ## gather_metadata_info 76 | 77 |
 78 | load("@supply_chain_tools//gather_metadata:gather_metadata.bzl", "gather_metadata_info")
 79 | 
 80 | gather_metadata_info()
 81 | 
82 | 83 | Collects metadata providers into a single TransitiveMetadataInfo provider. 84 | 85 | **ASPECT ATTRIBUTES** 86 | 87 | 88 | 89 | **ATTRIBUTES** 90 | 91 | 92 | 93 | 94 | 95 | ## gather_metadata_info_and_write 96 | 97 |
 98 | load("@supply_chain_tools//gather_metadata:gather_metadata.bzl", "gather_metadata_info_and_write")
 99 | 
100 | gather_metadata_info_and_write()
101 | 
102 | 103 | Collects TransitiveMetadataInfo providers and writes JSON representation to a file. 104 | 105 | Usage: 106 | bazel build //some:target --aspects=@supply_chain//rules_gathering:gather_metadata.bzl%gather_metadata_info_and_write 107 | --output_groups=licenses 108 | 109 | **ASPECT ATTRIBUTES** 110 | 111 | 112 | 113 | **ATTRIBUTES** 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /lib/supplychain-go/package_metadata.go: -------------------------------------------------------------------------------- 1 | package supplychain 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "maps" 8 | "os" 9 | "slices" 10 | 11 | "github.com/package-url/packageurl-go" 12 | 13 | "github.com/bazel-contrib/supply-chain/lib/supplychain-go/label" 14 | ) 15 | 16 | // PackageMetadata provides metadata about a Bazel package. 17 | type PackageMetadata interface { 18 | // packageMetadataPrivate acts as marker to prevent other packages to implement the interface. 19 | // 20 | // See also https://medium.com/@johnsiilver/writing-an-interface-that-only-sub-packages-can-implement-fe36e7511449 21 | packageMetadataPrivate() 22 | 23 | // GetLabel returns the [Label](https://bazel.build/rules/lib/builtins/Label) of the `package_metadata` target declaring this `PackageMetadata`. 24 | GetLabel() label.Label 25 | 26 | // GetPURL returns the [package-url](https://github.com/package-url/purl-spec/blob/main/PURL-SPECIFICATION.rst) this `PackageMetadata` if for. 27 | GetPURL() packageurl.PackageURL 28 | 29 | // ListAttributeKinds returns a slice of kinds of `PackageAttributeDescriptor`s included in this `PackageMetadata`. 30 | ListAttributeKinds() []string 31 | } 32 | 33 | // ReadPackageMetadata deserializes `PackageMetadata` from the provided reader. 34 | func ReadPackageMetadata(r io.Reader) (PackageMetadata, error) { 35 | var metadata packageMetadata 36 | if err := json.NewDecoder(r).Decode(&metadata); err != nil { 37 | return nil, err 38 | } 39 | 40 | return &metadata, nil 41 | } 42 | 43 | // ReadPackageMetadataFromFile deserializes `PackageMetadata` from a file with the provided path. 44 | func ReadPackageMetadataFromFile(path string) (PackageMetadata, error) { 45 | f, err := os.Open(path) 46 | if err != nil { 47 | return nil, err 48 | } 49 | defer f.Close() 50 | 51 | return ReadPackageMetadata(f) 52 | } 53 | 54 | // WritePackageMetadata serializes `PackageMetadata` to the provided reader. 55 | func WritePackageMetadata(r io.Writer, metadata PackageMetadata) error { 56 | return json.NewEncoder(r).Encode(metadata) 57 | } 58 | 59 | // WritePackageMetadataToFile serializes `PackageMetadata` into a file with the provided path. 60 | func WritePackageMetadataToFile(path string, metadata PackageMetadata) error { 61 | f, err := os.Create(path) 62 | if err != nil { 63 | return err 64 | } 65 | defer f.Close() 66 | 67 | return WritePackageMetadata(f, metadata) 68 | } 69 | 70 | // GetPackageAttribute returns the provided attribute from the `PackageMetadata`. 71 | func GetPackageAttribute[T any](m PackageMetadata, d PackageAttributeDescriptor[T]) (*T, error) { 72 | p := m.(*packageMetadata) 73 | 74 | a, ok := p.Attributes[d.Kind] 75 | if !ok { 76 | return nil, fmt.Errorf("package %q does not have attribute %q", p.PURL, d.Kind) 77 | } 78 | 79 | r, err := os.Open(a) 80 | if err != nil { 81 | return nil, err 82 | } 83 | defer r.Close() 84 | 85 | return d.Parser(r) 86 | } 87 | 88 | type packageMetadata struct { 89 | Label label.Label 90 | PURL packageurl.PackageURL 91 | Attributes map[string]string 92 | } 93 | 94 | /* 95 | * PackageMetadata implementation 96 | */ 97 | var _ PackageMetadata = (*packageMetadata)(nil) 98 | 99 | func (p *packageMetadata) packageMetadataPrivate() { 100 | // Nothing to do. 101 | } 102 | 103 | func (p *packageMetadata) GetLabel() label.Label { 104 | return p.Label 105 | } 106 | 107 | func (p *packageMetadata) GetPURL() packageurl.PackageURL { 108 | return p.PURL 109 | } 110 | 111 | func (p *packageMetadata) ListAttributeKinds() []string { 112 | return slices.Sorted(maps.Keys(p.Attributes)) 113 | } 114 | 115 | /* 116 | * JSON implementation. 117 | */ 118 | var _ json.Marshaler = (*packageMetadata)(nil) 119 | var _ json.Unmarshaler = (*packageMetadata)(nil) 120 | 121 | type rawPackageMetadata struct { 122 | Label string `json:"label"` 123 | PURL string `json:"purl"` 124 | Attributes map[string]string `json:"attributes"` 125 | } 126 | 127 | func (p *packageMetadata) UnmarshalJSON(data []byte) error { 128 | var rawMetadata rawPackageMetadata 129 | if err := json.Unmarshal(data, &rawMetadata); err != nil { 130 | return err 131 | } 132 | 133 | l, err := label.Parse(rawMetadata.Label) 134 | if err != nil { 135 | return err 136 | } 137 | p.Label = l 138 | 139 | purl, err := packageurl.FromString(rawMetadata.PURL) 140 | if err != nil { 141 | return err 142 | } 143 | p.PURL = purl 144 | 145 | p.Attributes = rawMetadata.Attributes 146 | if p.Attributes == nil { 147 | p.Attributes = make(map[string]string) 148 | } 149 | 150 | return nil 151 | } 152 | 153 | func (p *packageMetadata) MarshalJSON() ([]byte, error) { 154 | return json.Marshal(&rawPackageMetadata{ 155 | Label: p.Label.String(), 156 | PURL: p.PURL.String(), 157 | Attributes: p.Attributes, 158 | }) 159 | } 160 | -------------------------------------------------------------------------------- /metadata/purl/private/tables.bzl: -------------------------------------------------------------------------------- 1 | # Generated by maintainers/tools/purl_tables/main.py. DO NOT EDIT. 2 | 3 | visibility([ 4 | "//purl/...", 5 | ]) 6 | 7 | encode_byte = { 8 | 0: "%00", 9 | 1: "%01", 10 | 2: "%02", 11 | 3: "%03", 12 | 4: "%04", 13 | 5: "%05", 14 | 6: "%06", 15 | 7: "%07", 16 | 8: "%08", 17 | 9: "%09", 18 | 10: "%0A", 19 | 11: "%0B", 20 | 12: "%0C", 21 | 13: "%0D", 22 | 14: "%0E", 23 | 15: "%0F", 24 | 16: "%10", 25 | 17: "%11", 26 | 18: "%12", 27 | 19: "%13", 28 | 20: "%14", 29 | 21: "%15", 30 | 22: "%16", 31 | 23: "%17", 32 | 24: "%18", 33 | 25: "%19", 34 | 26: "%1A", 35 | 27: "%1B", 36 | 28: "%1C", 37 | 29: "%1D", 38 | 30: "%1E", 39 | 31: "%1F", 40 | 32: "%20", 41 | 33: "%21", 42 | 34: "%22", 43 | 35: "%23", 44 | 36: "%24", 45 | 37: "%25", 46 | 38: "%26", 47 | 39: "%27", 48 | 40: "%28", 49 | 41: "%29", 50 | 42: "%2A", 51 | 43: "%2B", 52 | 44: "%2C", 53 | 45: "-", 54 | 46: ".", 55 | 47: "/", 56 | 48: "0", 57 | 49: "1", 58 | 50: "2", 59 | 51: "3", 60 | 52: "4", 61 | 53: "5", 62 | 54: "6", 63 | 55: "7", 64 | 56: "8", 65 | 57: "9", 66 | 58: ":", 67 | 59: "%3B", 68 | 60: "%3C", 69 | 61: "%3D", 70 | 62: "%3E", 71 | 63: "%3F", 72 | 64: "%40", 73 | 65: "A", 74 | 66: "B", 75 | 67: "C", 76 | 68: "D", 77 | 69: "E", 78 | 70: "F", 79 | 71: "G", 80 | 72: "H", 81 | 73: "I", 82 | 74: "J", 83 | 75: "K", 84 | 76: "L", 85 | 77: "M", 86 | 78: "N", 87 | 79: "O", 88 | 80: "P", 89 | 81: "Q", 90 | 82: "R", 91 | 83: "S", 92 | 84: "T", 93 | 85: "U", 94 | 86: "V", 95 | 87: "W", 96 | 88: "X", 97 | 89: "Y", 98 | 90: "Z", 99 | 91: "%5B", 100 | 92: "%5C", 101 | 93: "%5D", 102 | 94: "%5E", 103 | 95: "_", 104 | 96: "%60", 105 | 97: "a", 106 | 98: "b", 107 | 99: "c", 108 | 100: "d", 109 | 101: "e", 110 | 102: "f", 111 | 103: "g", 112 | 104: "h", 113 | 105: "i", 114 | 106: "j", 115 | 107: "k", 116 | 108: "l", 117 | 109: "m", 118 | 110: "n", 119 | 111: "o", 120 | 112: "p", 121 | 113: "q", 122 | 114: "r", 123 | 115: "s", 124 | 116: "t", 125 | 117: "u", 126 | 118: "v", 127 | 119: "w", 128 | 120: "x", 129 | 121: "y", 130 | 122: "z", 131 | 123: "%7B", 132 | 124: "%7C", 133 | 125: "%7D", 134 | 126: "~", 135 | 127: "%7F", 136 | 128: "%80", 137 | 129: "%81", 138 | 130: "%82", 139 | 131: "%83", 140 | 132: "%84", 141 | 133: "%85", 142 | 134: "%86", 143 | 135: "%87", 144 | 136: "%88", 145 | 137: "%89", 146 | 138: "%8A", 147 | 139: "%8B", 148 | 140: "%8C", 149 | 141: "%8D", 150 | 142: "%8E", 151 | 143: "%8F", 152 | 144: "%90", 153 | 145: "%91", 154 | 146: "%92", 155 | 147: "%93", 156 | 148: "%94", 157 | 149: "%95", 158 | 150: "%96", 159 | 151: "%97", 160 | 152: "%98", 161 | 153: "%99", 162 | 154: "%9A", 163 | 155: "%9B", 164 | 156: "%9C", 165 | 157: "%9D", 166 | 158: "%9E", 167 | 159: "%9F", 168 | 160: "%A0", 169 | 161: "%A1", 170 | 162: "%A2", 171 | 163: "%A3", 172 | 164: "%A4", 173 | 165: "%A5", 174 | 166: "%A6", 175 | 167: "%A7", 176 | 168: "%A8", 177 | 169: "%A9", 178 | 170: "%AA", 179 | 171: "%AB", 180 | 172: "%AC", 181 | 173: "%AD", 182 | 174: "%AE", 183 | 175: "%AF", 184 | 176: "%B0", 185 | 177: "%B1", 186 | 178: "%B2", 187 | 179: "%B3", 188 | 180: "%B4", 189 | 181: "%B5", 190 | 182: "%B6", 191 | 183: "%B7", 192 | 184: "%B8", 193 | 185: "%B9", 194 | 186: "%BA", 195 | 187: "%BB", 196 | 188: "%BC", 197 | 189: "%BD", 198 | 190: "%BE", 199 | 191: "%BF", 200 | 192: "%C0", 201 | 193: "%C1", 202 | 194: "%C2", 203 | 195: "%C3", 204 | 196: "%C4", 205 | 197: "%C5", 206 | 198: "%C6", 207 | 199: "%C7", 208 | 200: "%C8", 209 | 201: "%C9", 210 | 202: "%CA", 211 | 203: "%CB", 212 | 204: "%CC", 213 | 205: "%CD", 214 | 206: "%CE", 215 | 207: "%CF", 216 | 208: "%D0", 217 | 209: "%D1", 218 | 210: "%D2", 219 | 211: "%D3", 220 | 212: "%D4", 221 | 213: "%D5", 222 | 214: "%D6", 223 | 215: "%D7", 224 | 216: "%D8", 225 | 217: "%D9", 226 | 218: "%DA", 227 | 219: "%DB", 228 | 220: "%DC", 229 | 221: "%DD", 230 | 222: "%DE", 231 | 223: "%DF", 232 | 224: "%E0", 233 | 225: "%E1", 234 | 226: "%E2", 235 | 227: "%E3", 236 | 228: "%E4", 237 | 229: "%E5", 238 | 230: "%E6", 239 | 231: "%E7", 240 | 232: "%E8", 241 | 233: "%E9", 242 | 234: "%EA", 243 | 235: "%EB", 244 | 236: "%EC", 245 | 237: "%ED", 246 | 238: "%EE", 247 | 239: "%EF", 248 | 240: "%F0", 249 | 241: "%F1", 250 | 242: "%F2", 251 | 243: "%F3", 252 | 244: "%F4", 253 | 245: "%F5", 254 | 246: "%F6", 255 | 247: "%F7", 256 | 248: "%F8", 257 | 249: "%F9", 258 | 250: "%FA", 259 | 251: "%FB", 260 | 252: "%FC", 261 | 253: "%FD", 262 | 254: "%FE", 263 | 255: "%FF", 264 | } 265 | -------------------------------------------------------------------------------- /rules_license/rules/package_info.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Rules for declaring metadata about a package.""" 15 | 16 | load( 17 | "@rules_license//rules:providers.bzl", 18 | "ExperimentalMetadataInfo", 19 | "PackageInfo", 20 | ) 21 | 22 | # 23 | # package_info() 24 | # 25 | 26 | def _package_info_impl(ctx): 27 | provider = PackageInfo( 28 | # Metadata providers must include a type discriminator. We don't need it 29 | # to collect the providers, but we do need it to write the JSON. We 30 | # key on the type field to look up the correct block of code to pull 31 | # data out and format it. We can't to the lookup on the provider class. 32 | type = "package_info", 33 | label = ctx.label, 34 | package_name = ctx.attr.package_name or ctx.build_file_path.rstrip("/BUILD"), 35 | package_url = ctx.attr.package_url, 36 | package_version = ctx.attr.package_version, 37 | purl = ctx.attr.purl, 38 | ) 39 | 40 | # Experimental alternate design, using a generic 'data' back to hold things 41 | generic_provider = ExperimentalMetadataInfo( 42 | type = "package_info_alt", 43 | label = ctx.label, 44 | data = { 45 | "package_name": ctx.attr.package_name or ctx.build_file_path.rstrip("/BUILD"), 46 | "package_url": ctx.attr.package_url, 47 | "package_version": ctx.attr.package_version, 48 | "purl": ctx.attr.purl, 49 | }, 50 | ) 51 | return [provider, generic_provider] 52 | 53 | _package_info = rule( 54 | implementation = _package_info_impl, 55 | attrs = { 56 | "package_name": attr.string( 57 | doc = "A human readable name identifying this package." + 58 | " This may be used to produce an index of OSS packages used by" + 59 | " an application.", 60 | ), 61 | "package_url": attr.string( 62 | doc = "The URL this instance of the package was download from." + 63 | " This may be used to produce an index of OSS packages used by" + 64 | " an application.", 65 | ), 66 | "package_version": attr.string( 67 | doc = "A human readable version string identifying this package." + 68 | " This may be used to produce an index of OSS packages used" + 69 | " by an application. It should be a value that" + 70 | " increases over time, rather than a commit hash.", 71 | ), 72 | "purl": attr.string( 73 | doc = "A pURL conforming to the spec outlined in" + 74 | " https://github.com/package-url/purl-spec. This may be used when" + 75 | " generating an SBOM.", 76 | ), 77 | }, 78 | ) 79 | 80 | # buildifier: disable=function-docstring-args 81 | def package_info( 82 | name, 83 | package_name = None, 84 | package_url = None, 85 | package_version = None, 86 | purl = None, 87 | **kwargs): 88 | """Wrapper for package_info rule. 89 | 90 | @wraps(_package_info) 91 | 92 | The purl attribute should be a valid pURL, as defined in the 93 | [pURL spec](https://github.com/package-url/purl-spec). 94 | 95 | Args: 96 | name: str target name. 97 | package_name: str A human readable name identifying this package. This 98 | may be used to produce an index of OSS packages used by 99 | an application. 100 | package_url: str The canoncial URL this package distribution was retrieved from. 101 | Note that, because of local mirroring, that might not be the 102 | physical URL it was retrieved from. 103 | package_version: str A human readable name identifying version of this package. 104 | purl: str The canonical pURL by which this package is known. 105 | kwargs: other args. Most are ignored. 106 | """ 107 | visibility = kwargs.get("visibility") or ["//visibility:public"] 108 | _package_info( 109 | name = name, 110 | package_name = package_name, 111 | package_url = package_url, 112 | package_version = package_version, 113 | purl = purl, 114 | applicable_licenses = [], 115 | visibility = visibility, 116 | tags = [], 117 | testonly = 0, 118 | ) 119 | -------------------------------------------------------------------------------- /lib/supplychain-go/package_metadata_test.go: -------------------------------------------------------------------------------- 1 | package supplychain 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/package-url/packageurl-go" 12 | "github.com/stretchr/testify/assert" 13 | 14 | "github.com/bazel-contrib/supply-chain/lib/supplychain-go/label" 15 | ) 16 | 17 | func TestReadValid(t *testing.T) { 18 | type ExpectedData struct { 19 | Label string 20 | PURL packageurl.PackageURL 21 | AttributeKinds []string 22 | } 23 | 24 | cases := []struct { 25 | Name string 26 | Input string 27 | Expected ExpectedData 28 | }{ 29 | { 30 | Name: "no-version", 31 | Input: ` 32 | { 33 | "purl": "pkg:github/bazel-contrib/supply-chain", 34 | "label": "@package_metadata//foo/bar", 35 | "attributes": {} 36 | } 37 | `, 38 | Expected: ExpectedData{ 39 | Label: "@package_metadata//foo/bar", 40 | PURL: *packageurl.NewPackageURL( 41 | /* type= */ "github", 42 | /* namespace= */ "bazel-contrib", 43 | /* name= */ "supply-chain", 44 | /* version= */ "", 45 | /* qualifiers= */ packageurl.Qualifiers{}, 46 | /* subpath= */ "", 47 | ), 48 | }, 49 | }, 50 | { 51 | Name: "version", 52 | Input: ` 53 | { 54 | "purl": "pkg:github/bazel-contrib/supply-chain@v0.0.1", 55 | "label": "@package_metadata//foo/bar", 56 | "attributes": {} 57 | } 58 | `, 59 | Expected: ExpectedData{ 60 | Label: "@package_metadata//foo/bar", 61 | PURL: *packageurl.NewPackageURL( 62 | /* type= */ "github", 63 | /* namespace= */ "bazel-contrib", 64 | /* name= */ "supply-chain", 65 | /* version= */ "v0.0.1", 66 | /* qualifiers= */ packageurl.Qualifiers{}, 67 | /* subpath= */ "", 68 | ), 69 | }, 70 | }, 71 | { 72 | Name: "single-attribute-kind", 73 | Input: ` 74 | { 75 | "purl": "pkg:github/bazel-contrib/supply-chain", 76 | "label": "@package_metadata//foo/bar", 77 | "attributes": { 78 | "foo": "path/to/foo.attributes.json" 79 | } 80 | } 81 | `, 82 | Expected: ExpectedData{ 83 | Label: "@package_metadata//foo/bar", 84 | PURL: *packageurl.NewPackageURL( 85 | /* type= */ "github", 86 | /* namespace= */ "bazel-contrib", 87 | /* name= */ "supply-chain", 88 | /* version= */ "", 89 | /* qualifiers= */ packageurl.Qualifiers{}, 90 | /* subpath= */ "", 91 | ), 92 | AttributeKinds: []string{ 93 | "foo", 94 | }, 95 | }, 96 | }, 97 | { 98 | Name: "multiple-attribute-kind", 99 | Input: ` 100 | { 101 | "purl": "pkg:github/bazel-contrib/supply-chain", 102 | "label": "@package_metadata//foo/bar", 103 | "attributes": { 104 | "bar": "path/to/bar.attributes.json", 105 | "foo": "path/to/foo.attributes.json", 106 | "baz": "path/to/foo.attributes.json" 107 | } 108 | } 109 | `, 110 | Expected: ExpectedData{ 111 | Label: "@package_metadata//foo/bar", 112 | PURL: *packageurl.NewPackageURL( 113 | /* type= */ "github", 114 | /* namespace= */ "bazel-contrib", 115 | /* name= */ "supply-chain", 116 | /* version= */ "", 117 | /* qualifiers= */ packageurl.Qualifiers{}, 118 | /* subpath= */ "", 119 | ), 120 | AttributeKinds: []string{ 121 | "bar", 122 | "baz", 123 | "foo", 124 | }, 125 | }, 126 | }, 127 | } 128 | 129 | for _, c := range cases { 130 | t.Run( 131 | fmt.Sprintf("Deserialize %s", c.Name), 132 | func(t *testing.T) { 133 | m, err := ReadPackageMetadata(strings.NewReader(c.Input)) 134 | assert.Nil(t, err) 135 | assert.Equal(t, label.MustParse(c.Expected.Label), m.GetLabel()) 136 | assert.Equal(t, c.Expected.PURL, m.GetPURL()) 137 | assert.ElementsMatch(t, c.Expected.AttributeKinds, m.ListAttributeKinds()) 138 | }) 139 | t.Run( 140 | fmt.Sprintf("Serialize %s", c.Name), 141 | func(t *testing.T) { 142 | m, err := ReadPackageMetadata(strings.NewReader(c.Input)) 143 | assert.Nil(t, err) 144 | 145 | b, err := json.Marshal(m) 146 | assert.Nil(t, err) 147 | assert.JSONEq(t, c.Input, string(b)) 148 | }) 149 | } 150 | } 151 | 152 | type FakeAttribute struct { 153 | Name string 154 | } 155 | 156 | func TestGetAttribute(t *testing.T) { 157 | p := &packageMetadata{ 158 | Label: label.MustParse("@package_metadata//foo/bar"), 159 | PURL: *packageurl.NewPackageURL( 160 | /* type= */ "github", 161 | /* namespace= */ "bazel-contrib", 162 | /* name= */ "supply-chain", 163 | /* version= */ "HEAD", 164 | /* qualifiers= */ packageurl.Qualifiers{}, 165 | /* subpath= */ "", 166 | ), 167 | Attributes: map[string]string{ 168 | "fake": os.Args[0], 169 | "other": "/does/not/exist", 170 | }, 171 | } 172 | 173 | a, err := GetPackageAttribute( 174 | p, 175 | PackageAttributeDescriptor[FakeAttribute]{ 176 | Kind: "fake", 177 | Parser: func(r io.Reader) (*FakeAttribute, error) { 178 | return &FakeAttribute{ 179 | Name: "foo", 180 | }, nil 181 | }, 182 | }) 183 | assert.Nil(t, err) 184 | assert.Equal(t, &FakeAttribute{Name: "foo"}, a) 185 | } 186 | -------------------------------------------------------------------------------- /rules_license/rules/license.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Rules for declaring the compliance licenses used by a package. 15 | 16 | """ 17 | 18 | load( 19 | "@rules_license//rules:license_impl.bzl", 20 | "license_rule_impl", 21 | ) 22 | load( 23 | "@rules_license//rules:providers.bzl", 24 | "LicenseKindInfo", 25 | ) 26 | 27 | # Enable this if your organization requires the license text to be a file 28 | # checked into source control instead of, possibly, another rule. 29 | _require_license_text_is_a_file = False 30 | 31 | # This rule must be named "_license" for backwards compatability with older 32 | # or Bazel that checked that name explicitly. See 33 | # https://github.com/bazelbuild/bazel/commit/bbc221f60bc8c9177470529d85c3e47a5d9aaf21 34 | # TODO(after bazel 7.0 release): Feel free to rename the rule and move. 35 | _license = rule( 36 | implementation = license_rule_impl, 37 | attrs = { 38 | "license_kinds": attr.label_list( 39 | mandatory = False, 40 | doc = "License kind(s) of this license. If multiple license kinds are" + 41 | " listed in the LICENSE file, and they all apply, then all" + 42 | " should be listed here. If the user can choose a single one" + 43 | " of many, then only list one here.", 44 | providers = [LicenseKindInfo], 45 | # Bazel 8+ users: In Bazel 8, the targets for package_metadata are 46 | # always in a null configuration. This reflects the fact that they 47 | # are non-configurable and keeps the node for a license in a single 48 | # configuration, no matter what physical platforms you are building 49 | # for. 50 | # TODO: (after bazel 8.0 is mainstream): Remove this line. 51 | cfg = "exec", 52 | ), 53 | "copyright_notice": attr.string( 54 | doc = "Copyright notice.", 55 | ), 56 | "license_text": attr.label( 57 | allow_single_file = True, 58 | doc = "The license file.", 59 | ), 60 | "package_name": attr.string( 61 | doc = "A human readable name identifying this package." + 62 | " This may be used to produce an index of OSS packages used by" + 63 | " an applicatation.", 64 | ), 65 | "package_url": attr.string( 66 | doc = "The URL this instance of the package was download from." + 67 | " This may be used to produce an index of OSS packages used by" + 68 | " an applicatation.", 69 | ), 70 | "package_version": attr.string( 71 | doc = "A human readable version string identifying this package." + 72 | " This may be used to produce an index of OSS packages used" + 73 | " by an applicatation. It should be a value that" + 74 | " increases over time, rather than a commit hash.", 75 | ), 76 | }, 77 | ) 78 | 79 | # buildifier: disable=function-docstring-args 80 | def license( 81 | name, 82 | license_text = "LICENSE", 83 | license_kind = None, 84 | license_kinds = None, 85 | copyright_notice = None, 86 | package_name = None, 87 | package_url = None, 88 | package_version = None, 89 | namespace = None, 90 | tags = [], 91 | visibility = ["//visibility:public"]): 92 | """Wrapper for license rule. 93 | 94 | @wraps(_license) 95 | 96 | Args: 97 | name: str target name. 98 | license_text: str Filename of the license file 99 | license_kind: label a single license_kind. Only one of license_kind or license_kinds may 100 | be specified 101 | license_kinds: list(label) list of license_kind targets. 102 | copyright_notice: str Copyright notice associated with this package. 103 | package_name: str A human readable name identifying this package. This 104 | may be used to produce an index of OSS packages used by 105 | an application. 106 | package_url: str The canonical URL this package was downloaded from. 107 | package_version: str The version corresponding the the URL. 108 | tags: list(str) tags applied to the rule 109 | visibility: list(label) visibility spec. 110 | """ 111 | if license_kind: 112 | if license_kinds: 113 | fail("Can not use both license_kind and license_kinds") 114 | license_kinds = [license_kind] 115 | 116 | if _require_license_text_is_a_file: 117 | # Make sure the file exists as named in the rule. A glob expression that 118 | # expands to the name of the file is not acceptable. 119 | srcs = native.glob([license_text]) 120 | if len(srcs) != 1 or srcs[0] != license_text: 121 | fail("Specified license file doesn't exist: %s" % license_text) 122 | 123 | # TODO(0.0.6 release): Remove this warning and fail hard instead. 124 | if namespace: 125 | # buildifier: disable=print 126 | print("license(namespace=) is deprecated.") 127 | 128 | _license( 129 | name = name, 130 | license_kinds = license_kinds, 131 | license_text = license_text, 132 | copyright_notice = copyright_notice, 133 | package_name = package_name, 134 | package_url = package_url, 135 | package_version = package_version, 136 | applicable_licenses = [], 137 | visibility = visibility, 138 | tags = tags, 139 | testonly = 0, 140 | ) 141 | -------------------------------------------------------------------------------- /docs/metadata/defs.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public API of `@package_metadata`. 4 | 5 | 6 | 7 | ## PackageAttributeInfo 8 | 9 |
 10 | load("@package_metadata//:defs.bzl", "PackageAttributeInfo")
 11 | 
 12 | PackageAttributeInfo(kind, attributes, files)
 13 | 
14 | 15 | Provider for declaring metadata about a Bazel package. 16 | 17 | > **Fields in this provider are not covered by the stability guarantee.** 18 | 19 | **FIELDS** 20 | 21 | | Name | Description | Default Value | 22 | | :------------- | :------------- | :------------- | 23 | | kind | The identifier of the attribute.

This should generally be in reverse DNS format (e.g., `com.example.foo`). | none | 24 | | attributes | The [File](https://bazel.build/rules/lib/builtins/File) containing the attributes.

The format of this file depends on the `kind` of attribute. Please consult the documentation of the attribute. | none | 25 | | files | A [depset](https://bazel.build/rules/lib/builtins/depset) of [File](https://bazel.build/rules/lib/builtins/File)s containing information about this attribute. | `[]` | 26 | 27 | 28 | 29 | 30 | ## PackageMetadataInfo 31 | 32 |
 33 | load("@package_metadata//:defs.bzl", "PackageMetadataInfo")
 34 | 
 35 | PackageMetadataInfo(metadata, files)
 36 | 
37 | 38 | Provider for declaring metadata about a Bazel package. 39 | 40 | > **Fields in this provider are not covered by the stability guarantee.** 41 | 42 | **FIELDS** 43 | 44 | | Name | Description | Default Value | 45 | | :------------- | :------------- | :------------- | 46 | | metadata | The [File](https://bazel.build/rules/lib/builtins/File) containing metadata about the package. | none | 47 | | files | A [depset](https://bazel.build/rules/lib/builtins/depset) of [File](https://bazel.build/rules/lib/builtins/File)s with metadata about the package, including transitive files from all attributes of the package. | `[]` | 48 | 49 | 50 | 51 | 52 | ## PackageMetadataOverrideInfo 53 | 54 |
 55 | load("@package_metadata//:defs.bzl", "PackageMetadataOverrideInfo")
 56 | 
 57 | PackageMetadataOverrideInfo(*, packages, metadata)
 58 | 
59 | 60 | Defines an override for `PackageMetadataInfo` for a set of packages. 61 | 62 | > **Fields in this provider are not covered by the stability guarantee.** 63 | 64 | **FIELDS** 65 | 66 | | Name | Description | 67 | | :------------- | :------------- | 68 | | packages | A [PackageSpecificationInfo](https://bazel.build/rules/lib/providers/PackageSpecificationInfo) provider declaring which packages the override applies to.

This is typically created by a [package_group](https://bazel.build/rules/lib/globals/build#package_group) target. | 69 | | metadata | The `PackageMetadataInfo` provider to use instead of the provider declared by package itself. | 70 | 71 | 72 | 73 | 74 | ## PackageMetadataToolchainInfo 75 | 76 |
 77 | load("@package_metadata//:defs.bzl", "PackageMetadataToolchainInfo")
 78 | 
 79 | PackageMetadataToolchainInfo(metadata_overrides)
 80 | 
81 | 82 | Toolchain for `package_metadata`. 83 | 84 | > **Fields in this provider are not covered by the stability guarantee.** 85 | 86 | **FIELDS** 87 | 88 | | Name | Description | Default Value | 89 | | :------------- | :------------- | :------------- | 90 | | metadata_overrides | A sequence of `PackageMetadataOverrideInfo` providers. | `[]` | 91 | 92 | 93 | 94 | 95 | ## package_metadata 96 | 97 |
 98 | load("@package_metadata//:defs.bzl", "package_metadata")
 99 | 
100 | package_metadata(*, name, purl, attributes, visibility, tags)
101 | 
102 | 103 | 104 | 105 | **PARAMETERS** 106 | 107 | 108 | | Name | Description | Default Value | 109 | | :------------- | :------------- | :------------- | 110 | | name |

-

| none | 111 | | purl |

-

| none | 112 | | attributes |

-

| `[]` | 113 | | visibility |

-

| `None` | 114 | | tags |

-

| `None` | 115 | 116 | 117 | 118 | 119 | ## purl.bazel 120 | 121 |
122 | load("@package_metadata//:defs.bzl", "purl")
123 | 
124 | purl.bazel(name, version)
125 | 
126 | 127 | Defines a `purl` for a Bazel module. 128 | 129 | This is typically used to construct `purl` for `package_metadata` targets in 130 | Bazel modules. 131 | 132 | This is **NOT** supported in `WORKSPACE` mode. 133 | 134 | Example: 135 | 136 | ```starlark 137 | load("@package_metadata//purl:purl.bzl", "purl") 138 | 139 | package_metadata( 140 | name = "package_metadata", 141 | purl = purl.bazel(module_name(), module_version()), 142 | attributes = [ 143 | # ... 144 | ], 145 | visibility = ["//visibility:public"], 146 | ) 147 | ``` 148 | 149 | 150 | **PARAMETERS** 151 | 152 | 153 | | Name | Description | Default Value | 154 | | :------------- | :------------- | :------------- | 155 | | name | The name of the Bazel module. Typically [module_name()](https://bazel.build/rules/lib/globals/build#module_name). | none | 156 | | version | The version of the Bazel module. Typically [module_version()](https://bazel.build/rules/lib/globals/build#module_version). May be empty or `None`. | none | 157 | 158 | **RETURNS** 159 | 160 | The `purl` for the Bazel module (e.g. `pkg:bazel/foo` or 161 | `pkg:bazel/bar@1.2.3`). 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/metadata/README.md: -------------------------------------------------------------------------------- 1 | # Module `@package_metadata` 2 | 3 | General-purpose rules for injecting supply-chain metadata into Bazel projects (e.g., for generating [SBOM](https://www.ntia.gov/page/software-bill-materials)s for shipped software artifacts). 4 | 5 | 6 | ## Stability 7 | 8 | This is a fundamental module of the Bazel ecosystem that most, if not all, other Bazel modules depend on. Thus stability is very important, and we promise to never change the public API. 9 | 10 | > **IMPORTANT**: This module is currently under active development, and not all exported symbols are covered by this stability guarantee. Please refer to the documentation of rules and providers for whether they are currently considered stable. 11 | 12 | 13 | ## Concepts 14 | 15 | Requirements for software supply-chain security measures vary widely depending on the organization building the product or the jurisdiction(s) the software is shipped to. They are also subject to change over time as laws evolve over time and companies find themselves in need to comply to these new requirements from governments around the world. Hence, the rules to inject supply-chain metadata needs to be very customizable. 16 | 17 | The core of this module is built around `package`s and `attribute`s. 18 | 19 | - `package` is used to identify (third-party) software and track its origin (e.g., `npm` module, `maven` artifact, or Rust `crate` its downloaded from). We use [PURL](https://github.com/package-url/purl-spec)s for this. 20 | - `attribute`s are used to declare metadata attached to `package`s (e.g., the License, or who the maintainers are). They are identified by `kind` to distinguish between different types of `attribute`s. 21 | - This provides the primary extension point for organizations to inject the metadata they need. 22 | - While we encourage organizations to adopt "well-known" `attribute`s provided in this module whenever possible, custom `attribute`s are also expected. 23 | 24 | 25 | ## Usage 26 | 27 | The rules and providers in this module have two primary audiences: 28 | 29 | - Authors of modules in the [Bazel Central Registry](https://registry.bazel.build) (or private registries) that want to annotate their module/packages/targets, and 30 | - Organizations that want to consume annotations for compliance checks or for producing provenance information for artifacts. 31 | 32 | ### As module author 33 | 34 | If you are a module author and want to annotate your module, you will need to take the following steps: 35 | 36 | - Add a dependency on `package_metadata` to your `MODULE.bazel` file. 37 | 38 | ```starlark 39 | bazel_dep(name = "package_metadata", version = "") 40 | ``` 41 | 42 | - Create `package_metadata` target(s) for declaring metadata. 43 | 44 | This target is typically in the top-level `BUILD.bazel` file. 45 | 46 | > Modules typically need only a single `package_metadata` target. However, multiple targets can be required in some cases (e.g., when some targets are licensed under a different license). 47 | 48 | ```starlark 49 | load("@package_metadata//purl:purl.bzl", "purl") 50 | load("@package_metadata//rules:package_metadata.bzl", "package_metadata") 51 | 52 | package_metadata( 53 | name = "package_metadata", 54 | purl = purl.bazel(module_name(), module_version()), 55 | attributes = [ 56 | # ... 57 | ], 58 | visibility = ["//visibility:public"], 59 | ) 60 | ``` 61 | 62 | 63 | 64 | - (optional) Add `attributes` to your `package_metadata` target(s). 65 | 66 | `package_metadata` itself only provides information about the identity of a module or package and where it was retrieved from. Additional metadata is provided as `attributes` to the `package_metadata` target (e.g., the license the packages are under, ...). 67 | 68 | > Definition of attributes are currently under development and not ready for wider usage yet. Please avoid adding attributes to OSS modules for now. 69 | 70 | - Annotate all targets with `package_metadata`. 71 | 72 | This step is required for consumers to access the declared metadata. 73 | 74 | There are three options to annotate targets: 75 | 76 | - Add `package_metadata` to all targets individually: 77 | 78 | ```starlark 79 | foo_library( 80 | name = "hello", 81 | package_metadata = [ 82 | "//:package_metadata", 83 | ], 84 | # ... 85 | ) 86 | ``` 87 | 88 | While this allows very fine grained control over the metadata of a target, it's also very tedious to modify all targets in a module. This method should therefore be reserved for targets with different metadata. 89 | 90 | - Add `default_package_metadata` to all packages 91 | 92 | ```starlark 93 | package(default_package_metadata = ["//:package_metadata"]) 94 | ``` 95 | 96 | This provides a simple way to annotate all targets in a package, while preserving the ability to annotate some targets in the package with a different metadata using the method above. 97 | 98 | - Add `default_package_metadata` to `REPO.bazel` 99 | 100 | This method is very similar to adding `default_package_metadata` to all packages, but it requires changing a single file only. 101 | 102 | ```starlark 103 | repo(default_package_metadata = ["//:package_metadata"]) 104 | ``` 105 | 106 | This provides a simple way to annotate all targets in a package, while preserving the ability to annotate some packages or targets in the package with a different metadata using the methods above. 107 | 108 | - Publish your module. 109 | 110 | ### As an organization 111 | 112 | > This is currently under active development. 113 | > 114 | > We will update this page after stabilizing the API. 115 | 116 | 117 | ## API Documentation 118 | 119 | ### Generic 120 | 121 | - [@package_metadata//:defs.bzl](./defs.md) 122 | 123 | #### Providers 124 | 125 | - [@package_metadata//providers:package_attribute_info.bzl](./providers/package_attribute_info.md) 126 | - [@package_metadata//providers:package_metadata_info.bzl](./providers/package_metadata_info.md) 127 | 128 | #### Rules 129 | 130 | - [@package_metadata//rules:package_metadata.bzl](./rules/package_metadata.md) 131 | 132 | #### Utils 133 | 134 | - [@package_metadata//purl:purl.bzl](./purl/purl.md) 135 | 136 | 137 | ### Licenses 138 | 139 | - [@package_metadata//licenses:defs.bzl](./licenses/defs.md) 140 | 141 | #### Providers 142 | 143 | - [@package_metadata//licenses/providers:license_kind_info.bzl](./licenses/providers/license_kind_info.md) 144 | 145 | #### Rules 146 | 147 | - [@package_metadata//licenses/rules:license.bzl](./licenses/rules/license.md) 148 | - [@package_metadata//licenses/rules:license_kind.bzl](./licenses/rules/license_kind.md) 149 | -------------------------------------------------------------------------------- /examples/sbom/sbom.bzl: -------------------------------------------------------------------------------- 1 | """An example of gathering and processing just license information.""" 2 | 3 | load( 4 | "@package_metadata//:defs.bzl", 5 | "PackageAttributeInfo", 6 | "PackageMetadataInfo", 7 | ) 8 | load( 9 | "@supply_chain_tools//gather_metadata:core.bzl", 10 | "gather_metadata_info_common", 11 | "should_traverse", 12 | ) 13 | load( 14 | "@supply_chain_tools//gather_metadata:providers.bzl", 15 | "TransitiveMetadataInfo", 16 | "null_transitive_metadata_info", 17 | ) 18 | 19 | # All top level metadata processing rules will generally wrap gahter with their 20 | # Own aspect to walk the tree. This wrapper is usually not much different than 21 | # the example here. The variation is usually only to provide the set of 22 | # providers we want to collect. This allows for organization specific providers 23 | # to be gathered in the same pass as the canonical ones from suppply_chain. 24 | # 25 | def _gather_metadata_info_impl(target, ctx): 26 | return gather_metadata_info_common( 27 | target = target, 28 | ctx = ctx, 29 | want_providers = [PackageAttributeInfo, PackageMetadataInfo], 30 | provider_factory = TransitiveMetadataInfo, 31 | null_provider_instance = null_transitive_metadata_info, 32 | filter_func = should_traverse, 33 | ) 34 | 35 | gather_metadata_info = aspect( 36 | doc = """Collects metadata providers into a single TransitiveMetadataInfo provider.""", 37 | implementation = _gather_metadata_info_impl, 38 | attr_aspects = ["*"], 39 | attrs = { 40 | "_trace": attr.label(default = "@supply_chain_tools//gather_metadata:trace_target"), 41 | }, 42 | provides = [TransitiveMetadataInfo], 43 | apply_to_generating_rules = True, 44 | ) 45 | 46 | def _handle_provider(metadata_provider, command, inputs, report): 47 | """Handle an individual metadata provider. 48 | 49 | Args: 50 | metadata_provider: A provider instance 51 | command: (in/out) list of command line args we are building 52 | inputs: (in/out) list of files needed for that command line 53 | report: (in/out) list of things we want to say to the user. 54 | This is for illustrating how to use these rules, and 55 | is not needed for the SBOM. 56 | """ 57 | 58 | # We are presuming having metadata means you are a PackageMetadataInfo. 59 | if hasattr(metadata_provider, "metadata"): 60 | command.append("-metadata %s" % metadata_provider.metadata.path) 61 | inputs.extend(metadata_provider.files.to_list()) 62 | if hasattr(metadata_provider, "purl"): 63 | command.append("-purl %s" % metadata_provider.purl) 64 | report.append("purl %s" % metadata_provider.purl) 65 | return 66 | 67 | # If you are gathering your own custom types, having a kind field can 68 | # be used to distingish them if they need different post processing. 69 | kind = metadata_provider.kind if hasattr(metadata_provider, "kind") else "_UNKNOWN_" 70 | print("##-- %s" % kind) 71 | 72 | # print(metadata_provider) 73 | if kind: 74 | # but maybe the kind is in the info file. 75 | command.append("-kind %s" % kind) 76 | if hasattr(metadata_provider, "attributes"): 77 | command.append("-attributes %s" % metadata_provider.attributes.path) 78 | report.append(" Attribute data: %s" % metadata_provider.attributes.short_path) 79 | if hasattr(metadata_provider, "files"): 80 | inputs.extend(metadata_provider.files.to_list()) 81 | for f in metadata_provider.files.to_list(): 82 | report.append(" file: %s" % f.short_path) 83 | 84 | def _handle_trans_collector(t_m_i, command, inputs, report): 85 | """Process a TransitiveMetadataInfo. 86 | 87 | Args: 88 | t_m_i: A provider instance 89 | command: (in/out) list of command line args we are building 90 | inputs: (in/out) list of files needed for that command line 91 | report: (in/out) list of things we want to say to the user. 92 | This is for illustrating how to use these rules, and 93 | is not needed for the SBOM. 94 | """ 95 | if hasattr(t_m_i, "metadata"): 96 | for metadata in t_m_i.metadata.to_list(): 97 | _handle_provider(metadata, command, inputs, report) 98 | 99 | def _sbom_impl(ctx): 100 | # Gather all metadata and make a report from that 101 | 102 | # TODO: Replace this 103 | # The code below just dumps the collected metadata providers in a somewhat 104 | # pretty printed way. In reality, we need to read the files associated with 105 | # each attribute to get the real data. So this should be a rule to pass 106 | # all the files to a helper which generates a formated report. 107 | # That is clearly a job for another day. 108 | 109 | out = [] 110 | if TransitiveMetadataInfo not in ctx.attr.target: 111 | fail("Missing metadata for %s" % ctx.attr.target) 112 | t_m_i = ctx.attr.target[TransitiveMetadataInfo] 113 | print(t_m_i) 114 | 115 | inputs = [] 116 | report = [] 117 | command = ["echo"] 118 | command.append("--output '%s'" % ctx.outputs.out.path) 119 | 120 | report.append("Target: %s" % str(ctx.attr.target.label)) 121 | if hasattr(t_m_i, "target"): 122 | report.append("Gathered target: %s" % str(t_m_i.target)) 123 | command.append("--target '%s'" % str(t_m_i.target)) 124 | if hasattr(t_m_i, "metadata"): 125 | print("TOP HAS DIRECTS") 126 | for direct in t_m_i.directs.to_list(): 127 | _handle_provider(direct, command, inputs, report) 128 | if hasattr(t_m_i, "trans"): 129 | for trans in t_m_i.trans.to_list(): 130 | _handle_trans_collector(trans, command, inputs, report) 131 | 132 | # TBD: Run the SBOM generator here. 133 | print("RUN THE SBOM\n %s\n" % " ".join(command)) 134 | print("Report: \n %s\n" % "\n ".join(report)) 135 | 136 | # This just gives us an output. Next pass the write will happen in the 137 | # action we create 138 | ctx.actions.write(ctx.outputs.out, "\n".join(report) + "\n") 139 | return [DefaultInfo(files = depset([ctx.outputs.out]))] 140 | 141 | _sbom = rule( 142 | implementation = _sbom_impl, 143 | doc = """Internal tmplementation method for sbom().""", 144 | attrs = { 145 | "out": attr.output( 146 | doc = """Output file.""", 147 | mandatory = True, 148 | ), 149 | "target": attr.label( 150 | doc = """Targets to build an SBOM for.""", 151 | aspects = [gather_metadata_info], 152 | ), 153 | }, 154 | ) 155 | 156 | def sbom(name, target, out = None, **kwargs): 157 | """Collects metadata providers for a set of targets and writes a minimal SBOM. 158 | 159 | Args: 160 | name: The target. 161 | target: A target to build an SBOM for. 162 | out: The output file name. Default: .json. 163 | **kwargs: Other args 164 | 165 | Usage: 166 | 167 | sbom( 168 | name = "my_app_sbom", 169 | target = [":my_app"], 170 | out = "my_app_sbom.json", 171 | ) 172 | """ 173 | if not out: 174 | out = name + ".txt" 175 | _sbom(name = name, target = target, out = out, **kwargs) 176 | -------------------------------------------------------------------------------- /examples/sample_reports/licenses_used.bzl: -------------------------------------------------------------------------------- 1 | """Gather licenses used by bazel targets.""" 2 | 3 | load( 4 | "@package_metadata//:defs.bzl", 5 | "PackageAttributeInfo", 6 | "PackageMetadataInfo", 7 | ) 8 | load( 9 | "@supply_chain_tools//gather_metadata:core.bzl", 10 | "gather_metadata_info_common", 11 | "should_traverse", 12 | ) 13 | load( 14 | "@supply_chain_tools//gather_metadata:providers.bzl", 15 | "TransitiveMetadataInfo", 16 | "null_transitive_metadata_info", 17 | ) 18 | 19 | DEBUG_LEVEL = 0 20 | 21 | def update_attribute_to_consumers(attribute_to_consumers, file, target): 22 | if file.path not in attribute_to_consumers: 23 | attribute_to_consumers[file.path] = [] 24 | attribute_to_consumers[file.path].append(str(target)) 25 | 26 | # All top level metadata processing rules will generally wrap gahter with their 27 | # Own aspect to walk the tree. This wrapper is usually not much different than 28 | # the example here. The variation is usually only to provide the set of 29 | # providers we want to collect. This allows for organization specific providers 30 | # to be gathered in the same pass as the canonical ones from suppply_chain. 31 | # 32 | def _gather_metadata_info_impl(target, ctx): 33 | return gather_metadata_info_common( 34 | target, 35 | ctx, 36 | want_providers = [PackageAttributeInfo, PackageMetadataInfo], 37 | provider_factory = TransitiveMetadataInfo, 38 | null_provider_instance = null_transitive_metadata_info, 39 | filter_func = should_traverse, 40 | ) 41 | 42 | gather_metadata_info = aspect( 43 | doc = """Collects metadata providers into a single TransitiveMetadataInfo provider.""", 44 | implementation = _gather_metadata_info_impl, 45 | attr_aspects = ["*"], 46 | provides = [TransitiveMetadataInfo], 47 | apply_to_generating_rules = True, 48 | ) 49 | 50 | def _handle_attribute_provider(metadata_provider, target = None, command = None, inputs = None, report = None, attribute_to_consumers = None): 51 | """Handle an individual metadata provider. 52 | 53 | Args: 54 | metadata_provider: A provider instance 55 | target: target to which this attribute applies 56 | command: (in/out) list of command line args we are building 57 | inputs: (in/out) list of files needed for that command line 58 | report: (in/out) list of things we want to say to the user. 59 | This is for illustrating how to use these rules, and 60 | is not needed for the SBOM. 61 | attribute_to_consumers: Map of attribute providers back to the packages that use them. 62 | """ 63 | 64 | # We are presuming having metadata means you are a PackageMetadataInfo. 65 | if hasattr(metadata_provider, "metadata"): 66 | command.append("-metadata %s" % metadata_provider.metadata.path) 67 | inputs.extend(metadata_provider.files.to_list()) 68 | if hasattr(metadata_provider, "purl"): 69 | command.append("-purl %s" % metadata_provider.purl) 70 | report.append("purl %s" % metadata_provider.purl) 71 | return 72 | 73 | # If you are gathering your own custom types, having a kind field can 74 | # be used to distingish them if they need different post processing. 75 | kind = metadata_provider.kind if hasattr(metadata_provider, "kind") else "_UNKNOWN_" 76 | if DEBUG_LEVEL > 1: 77 | # buildifier: disable=print 78 | print("##-- %s" % kind) 79 | 80 | # buildifier: disable=print 81 | print(metadata_provider) 82 | if kind: 83 | # but maybe the kind is in the info file. 84 | command.append("-kind %s" % kind) 85 | 86 | if hasattr(metadata_provider, "attributes"): 87 | update_attribute_to_consumers(attribute_to_consumers, metadata_provider.attributes, target) 88 | command.append("-attributes %s" % metadata_provider.attributes.path) 89 | report.append(" Attribute data: %s" % metadata_provider.attributes.short_path) 90 | if hasattr(metadata_provider, "files"): 91 | inputs.extend(metadata_provider.files.to_list()) 92 | for f in metadata_provider.files.to_list(): 93 | report.append(" file: %s" % f.path) 94 | 95 | # Check for extras. 96 | # This is for debugging during initial development. There should be 97 | # no extra fields. 98 | for field in sorted(dir(metadata_provider)): 99 | if field in ("attributes", "files", "kind"): 100 | continue 101 | value = getattr(metadata_provider, field) 102 | report.append("%s: %s" % (field, value)) 103 | 104 | def _handle_trans_collector(t_m_i, command, inputs, report, attribute_to_consumers): 105 | """Process a TransitiveMetadataInfo. 106 | 107 | Args: 108 | t_m_i: A provider instance 109 | command: (in/out) list of command line args we are building 110 | inputs: (in/out) list of files needed for that command line 111 | report: (in/out) list of things we want to say to the user. 112 | This is for illustrating how to use these rules, and 113 | is not needed for the SBOM. 114 | attribute_to_consumers: Map of attribute providers back to the 115 | packages that use them. 116 | """ 117 | if hasattr(t_m_i, "metadata"): 118 | report.append("Target %s" % t_m_i.target) 119 | command.append("-target %s" % t_m_i.target) 120 | for metadata in t_m_i.metadata.to_list(): 121 | _handle_attribute_provider( 122 | metadata, 123 | target = t_m_i.target, 124 | command = command, 125 | inputs = inputs, 126 | report = report, 127 | attribute_to_consumers = attribute_to_consumers, 128 | ) 129 | if hasattr(t_m_i, "trans"): 130 | fail("TransititiveMetadataInfo contains both metadata and trans." + str(t_m_i)) 131 | 132 | def _licenses_used_impl(ctx): 133 | # Gather all metadata and make a report from that 134 | 135 | # TODO: Replace this 136 | # The code below just dumps the collected metadata providers in a somewhat 137 | # pretty printed way. In reality, we need to read the files associated with 138 | # each attribute to get the real data. So this should be a rule to pass 139 | # all the files to a helper which generates a formated report. 140 | # That is clearly a job for another day. 141 | 142 | if TransitiveMetadataInfo not in ctx.attr.target: 143 | fail("Missing metadata for %s" % ctx.attr.target) 144 | t_m_i = ctx.attr.target[TransitiveMetadataInfo] 145 | if DEBUG_LEVEL > 0: 146 | # buildifier: disable=print 147 | print(t_m_i) 148 | 149 | inputs = [] 150 | report = [] 151 | attribute_to_consumers = {} 152 | 153 | command = ["echo"] 154 | command.append("--output '%s'" % ctx.outputs.out.path) 155 | 156 | report.append("Top label: %s" % str(ctx.attr.target.label)) 157 | if hasattr(t_m_i, "target"): 158 | report.append("Target: %s" % str(t_m_i.target)) 159 | command.append("--target '%s'" % str(t_m_i.target)) 160 | 161 | # It is possible for the top level target to have metadata, but rare. 162 | if hasattr(t_m_i, "metadata"): 163 | if DEBUG_LEVEL > 0: 164 | # buildifier: disable=print 165 | print("TOP HAS DIRECTS") 166 | for direct in t_m_i.metadata.to_list(): 167 | _handle_attribute_provider( 168 | metadata = direct, 169 | target = t_m_i.target, 170 | command = command, 171 | inputs = inputs, 172 | report = report, 173 | attribute_to_consumers = attribute_to_consumers, 174 | ) 175 | 176 | if hasattr(t_m_i, "trans"): 177 | for trans in t_m_i.trans.to_list(): 178 | _handle_trans_collector(trans, command, inputs, report, attribute_to_consumers) 179 | if DEBUG_LEVEL > 1: 180 | # buildifier: disable=print 181 | print(json.encode_indent(attribute_to_consumers)) 182 | 183 | # For the time being, print a report of what we have. It's not the final 184 | # output. It just helps see what we have. 185 | # buildifier: disable=print 186 | print("Report: \n %s\n" % "\n ".join(report)) 187 | 188 | # TBD: Run the generator here. 189 | # buildifier: disable=print 190 | print("Run the report maker\n %s\n" % " ".join(command)) 191 | 192 | # This just gives us an output. Next pass the write will happen in the 193 | # action we create 194 | ctx.actions.write(ctx.outputs.out, "\n".join(report) + "\n") 195 | return [DefaultInfo(files = depset([ctx.outputs.out]))] 196 | 197 | licenses_used = rule( 198 | implementation = _licenses_used_impl, 199 | doc = """Create a list of the licenses used by a target.""", 200 | attrs = { 201 | "target": attr.label( 202 | doc = """Targets to gather licenses for.""", 203 | aspects = [gather_metadata_info], 204 | ), 205 | "out": attr.output( 206 | doc = """Output file.""", 207 | mandatory = True, 208 | ), 209 | }, 210 | ) 211 | -------------------------------------------------------------------------------- /tools/gather_metadata/core.bzl: -------------------------------------------------------------------------------- 1 | """Rules and macros for collecting package_metadata providers.""" 2 | 3 | load(":providers.bzl", "TargetWithMetadataInfo", "TransitiveMetadataInfo") 4 | load(":rule_filters.bzl", "rule_to_excluded_attributes") 5 | load(":trace.bzl", "TraceInfo") 6 | 7 | DEBUG_LEVEL = 0 8 | 9 | def should_traverse(ctx, attr, user_filters = None): 10 | """Checks if the dependent attribute should be traversed. 11 | 12 | Note for the future: We can vastly inmprove the peformance by 13 | moving this to a Bazel 9 style aspect traversal filter. 14 | 15 | Args: 16 | ctx: The aspect evaluation context. 17 | attr: The name of the attribute to be checked. 18 | user_filters: Additional dictionary of per-rule attribute filters. 19 | 20 | Returns: 21 | True iff the attribute should be traversed. 22 | """ 23 | per_rule_filters = [rule_to_excluded_attributes] 24 | if user_filters: 25 | per_rule_filters.append(user_filters) 26 | 27 | for filters in per_rule_filters: 28 | always_ignored = filters.get("*", []) 29 | if attr in always_ignored: 30 | return False 31 | rule_specific_filter = filters.get(ctx.rule.kind, None) 32 | if rule_specific_filter: 33 | if (attr in rule_specific_filter or 34 | "*" in rule_specific_filter or 35 | ("_*" in rule_specific_filter and attr.startswith("_"))): 36 | return False 37 | return True 38 | 39 | def _get_transitive_metadata( 40 | ctx, 41 | trans_tmi, 42 | provider = None, 43 | null_provider_instance = None, 44 | filter_func = None, 45 | traces = None): 46 | """Gather the collection provider instances of interest from our children. 47 | 48 | This is a helper to pull up the collected metadata info from children so 49 | that we can rebundle into the next level efficiently. It revolves around 50 | a "collection provider" which is the transitive collected data so far. 51 | While this method is intended to be generic, it is only built and tested 52 | with TransitiveMetadataInfo. 53 | 54 | Args: 55 | ctx: the ctx 56 | trans_tmi: (output) list of the depsets in the children 57 | provider: the transitive collection provider. 58 | null_provider_instance: a singleton instance of the empty provider. 59 | filter_func: filter to determine to skip. 60 | traces: debuging traces 61 | """ 62 | attrs = [attr for attr in dir(ctx.rule.attr)] 63 | for name in attrs: 64 | if filter_func and not filter_func(ctx, name): 65 | if DEBUG_LEVEL > 2: 66 | print("Trimming attribute %s of %s" % (name, ctx.rule.kind)) 67 | continue 68 | if DEBUG_LEVEL > 4: 69 | print("CHECKING attribute %s of %s" % (name, ctx.rule.kind)) 70 | 71 | attr_value = getattr(ctx.rule.attr, name) 72 | 73 | # Make scalers into a lists for convenience. 74 | if type(attr_value) != type([]): 75 | attr_value = [attr_value] 76 | 77 | for dep in attr_value: 78 | # Ignore anything that isn't a target 79 | if type(dep) != "Target": 80 | continue 81 | 82 | # Targets can also include things like input files that won't have the 83 | # aspect, so we additionally check for the aspect rather than assume 84 | # it's on all targets. Even some regular targets may be synthetic and 85 | # not have the aspect. This provides protection against those outlier 86 | # cases. 87 | if provider in dep: 88 | info = dep[provider] 89 | if hasattr(info, "traces") and getattr(info, "traces"): 90 | for trace in info.traces: 91 | traces.append("(" + ", ".join([str(ctx.label), ctx.rule.kind, name]) + ") -> " + trace) 92 | if info != null_provider_instance: 93 | trans_tmi.append(info.trans) 94 | 95 | def gather_metadata_info_common( 96 | target, 97 | ctx, 98 | want_providers = None, 99 | provider_factory = None, 100 | null_provider_instance = None, 101 | filter_func = None): 102 | """Collect package metadata info from myself and my deps. 103 | 104 | Any single target might directly depend on a package metadata, or depend on 105 | something that transitively depends on a package metadata, or neither. 106 | This aspect bundles all those into a single provider. At each level, we add 107 | in new direct metadata deps found and forward up the transitive information 108 | collected so far. 109 | 110 | This is a common abstraction for crawling the dependency graph. It is 111 | parameterized to allow specifying the provider that is populated with 112 | results. It is configurable to select only a subset of providers. It 113 | is also configurable to specify which dependency edges should not 114 | be traced for the purpose of tracing the graph. 115 | 116 | Args: 117 | target: The target of the aspect. 118 | ctx: The aspect evaluation context. 119 | want_providers: a list of providers of interest 120 | provider_factory: abstracts the provider returned by this aspect 121 | null_provider_instance: a singleton instance of the empty provider. Reusing a 122 | a singleton across a large graph can save significant memory. 123 | filter_func: a function that returns true IFF the dep edge should be ignored 124 | 125 | Returns: 126 | provider of parameterized type 127 | """ 128 | 129 | # TODO(aiuto): Consider dropping this hack. 130 | # A hack until https://github.com/bazelbuild/rules_license/issues/89 is 131 | # fully resolved. If exec is in the bin_dir path, then the current 132 | # configuration is probably cfg = exec. 133 | if "-exec-" in ctx.bin_dir.path: 134 | return [null_provider_instance or provider_factory()] 135 | 136 | # First we gather my direct metadata providers. 137 | # This captures the pairs if 138 | got_providers = [] 139 | package_info = [] 140 | if DEBUG_LEVEL > 1: 141 | print("==============================================\n %s (%s) \n" % (target.label, ctx.rule.kind)) 142 | if hasattr(ctx.rule.attr, "kind") and ctx.rule.attr.kind == "build.bazel.attribute.license": 143 | # Don't try to gather licenses from the license rule itself. We'll just 144 | # blunder into the text file of the license and pick up the default 145 | # attribute of the package, which we don't want. 146 | pass 147 | else: 148 | if hasattr(ctx.rule.attr, "package_metadata"): 149 | package_metadata = ctx.rule.attr.package_metadata 150 | elif hasattr(ctx.rule.attr, "applicable_licenses"): 151 | package_metadata = ctx.rule.attr.applicable_licenses 152 | else: 153 | package_metadata = [] 154 | for metadata_dependency in package_metadata: 155 | if DEBUG_LEVEL > 1: 156 | print("checking", metadata_dependency.label) 157 | for wanted_provider in want_providers: 158 | if wanted_provider in metadata_dependency: 159 | got_providers.append(metadata_dependency[wanted_provider]) 160 | 161 | if DEBUG_LEVEL > 0 and got_providers: 162 | print(" GOT: ", target.label, got_providers) 163 | 164 | # Now gather transitive collection of providers from the children this 165 | # target depends upon. 166 | trans_tmi = [] 167 | traces = [] 168 | _get_transitive_metadata( 169 | ctx = ctx, 170 | trans_tmi = trans_tmi, 171 | provider = provider_factory, 172 | null_provider_instance = null_provider_instance, 173 | filter_func = filter_func, 174 | traces = traces, 175 | ) 176 | 177 | # If this is the target, start the sequence of traces. 178 | if hasattr(ctx.attr, "_trace"): 179 | if ctx.attr._trace[TraceInfo].trace and ctx.attr._trace[TraceInfo].trace in str(ctx.label): 180 | traces = [ctx.attr._trace[TraceInfo].trace] 181 | 182 | # Trim the number of traces accumulated since the output can be quite large. 183 | # A few representative traces are generally sufficient to identify why a dependency 184 | # is incorrectly incorporated. 185 | if len(traces) > 10: 186 | traces = traces[0:10] 187 | 188 | # State so far: 189 | # got_providers: list (maybe empty) of metadata providers we directly have 190 | # trans_tmi: the list of the collection providers from our children. 191 | 192 | # Efficiently merge them. 193 | 194 | # We can do some tricks to avoid allocating a lot of memory 195 | # in big graphs. For the most part, metadata attachments are near the 196 | # leaves, and sparse higher up. 197 | # 1. If there is no direct metadata (got_providers is None) and there is 198 | # no transitive metadata, return the null instance. This is typical 199 | # for home grown code that only depends on our own code. 200 | # 2. If got_providers is None, and there transitive info. 201 | # If the length of the list is one, just pass up the first element. 202 | # This is common through the whole middle of a build graph. 203 | # 3. If the above fail, construct a new one. 204 | 205 | if DEBUG_LEVEL > 0: 206 | print("%s: got: %d, trans: %d" % (target.label, len(got_providers), len(trans_tmi))) 207 | 208 | if not got_providers and not trans_tmi: 209 | return [null_provider_instance or provider_factory()] 210 | 211 | if not got_providers: 212 | """ 213 | TODO: If there is only one, pass up the entire provider, not the extracted trans 214 | if len(trans_tmi) == 1 and trans_tmi[0]: 215 | # Often, there is only one thing we are passing up. There is no 216 | # reason to allocate another collection provider around that. 217 | return trans_tmi[0] 218 | """ 219 | return [provider_factory(trans = depset(transitive = trans_tmi))] 220 | 221 | # Create a TWMI linking this target to the applicable metadata 222 | me = TargetWithMetadataInfo( 223 | target = target.label, 224 | metadata = depset(got_providers), 225 | ) 226 | if not trans_tmi: 227 | return [provider_factory( 228 | trans = depset(direct = [me]), 229 | )] 230 | return [provider_factory( 231 | trans = depset(direct = [me], transitive = trans_tmi), 232 | )] -------------------------------------------------------------------------------- /tools/gather_metadata/gather_metadata.bzl: -------------------------------------------------------------------------------- 1 | """Rules and macros for collecting metadata providers.""" 2 | 3 | load( 4 | "@package_metadata//:defs.bzl", 5 | "PackageAttributeInfo", 6 | "PackageMetadataInfo", 7 | ) 8 | load( 9 | "@package_metadata//licenses/providers:license_kind_info.bzl", 10 | "LicenseKindInfo", 11 | ) 12 | load( 13 | ":core.bzl", 14 | "gather_metadata_info_common", 15 | "should_traverse", 16 | ) 17 | load(":providers.bzl", "TransitiveMetadataInfo", "null_transitive_metadata_info") 18 | load(":trace.bzl", "TraceInfo") 19 | 20 | def _strip_null_repo(label): 21 | """Removes the null repo name (e.g. @//) from a string. 22 | 23 | The idea is to make str(label) compatible between bazel 5.x and 6.x 24 | """ 25 | s = str(label) 26 | if s.startswith("@//"): 27 | return s[1:] 28 | elif s.startswith("@@//"): 29 | return s[2:] 30 | return s 31 | 32 | def _bazel_package(label): 33 | clean_label = _strip_null_repo(label) 34 | return clean_label[0:-(len(label.name) + 1)] 35 | 36 | def _gather_metadata_info_impl(target, ctx): 37 | return gather_metadata_info_common( 38 | target, 39 | ctx, 40 | want_providers = [PackageAttributeInfo, PackageMetadataInfo, LicenseKindInfo], 41 | provider_factory = TransitiveMetadataInfo, 42 | null_provider_instance = null_transitive_metadata_info, 43 | filter_func = should_traverse, 44 | ) 45 | 46 | gather_metadata_info = aspect( 47 | doc = """Collects metadata providers into a single TransitiveMetadataInfo provider.""", 48 | implementation = _gather_metadata_info_impl, 49 | attr_aspects = ["*"], 50 | attrs = { 51 | "_trace": attr.label(default = "@supply_chain_tools//gather_metadata:trace_target"), 52 | }, 53 | provides = [TransitiveMetadataInfo], 54 | apply_to_generating_rules = True, 55 | ) 56 | 57 | def _write_metadata_info_impl(target, ctx): 58 | """Write transitive license info into a JSON file 59 | 60 | Args: 61 | target: The target of the aspect. 62 | ctx: The aspect evaluation context. 63 | 64 | Returns: 65 | OutputGroupInfo 66 | """ 67 | 68 | if not TransitiveMetadataInfo in target: 69 | return [OutputGroupInfo(licenses = depset())] 70 | info = target[TransitiveMetadataInfo] 71 | outs = [] 72 | 73 | # If the result doesn't contain licenses, we simply return the provider 74 | if not hasattr(info, "target_under_license"): 75 | return [OutputGroupInfo(licenses = depset())] 76 | 77 | # Write the output file for the target 78 | name = "%s_metadata_info.json" % ctx.label.name 79 | content = "[\n%s\n]\n" % ",\n".join(metadata_info_to_json(info)) 80 | out = ctx.actions.declare_file(name) 81 | ctx.actions.write( 82 | output = out, 83 | content = content, 84 | ) 85 | outs.append(out) 86 | 87 | if ctx.attr._trace[TraceInfo].trace: 88 | trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name) 89 | ctx.actions.write(output = trace, content = "\n".join(info.traces)) 90 | outs.append(trace) 91 | 92 | return [OutputGroupInfo(licenses = depset(outs))] 93 | 94 | gather_metadata_info_and_write = aspect( 95 | doc = """Collects TransitiveMetadataInfo providers and writes JSON representation to a file. 96 | 97 | Usage: 98 | bazel build //some:target \ 99 | --aspects=@supply_chain//rules_gathering:gather_metadata.bzl%gather_metadata_info_and_write 100 | --output_groups=licenses 101 | """, 102 | implementation = _write_metadata_info_impl, 103 | attr_aspects = ["*"], 104 | attrs = { 105 | "_trace": attr.label(default = "@supply_chain_tools//gather_metdata:trace_target"), 106 | }, 107 | provides = [OutputGroupInfo], 108 | requires = [gather_metadata_info], 109 | apply_to_generating_rules = True, 110 | ) 111 | 112 | def write_metadata_info(ctx, deps, json_out): 113 | """Writes TransitiveMetadataInfo providers for a set of targets as JSON. 114 | 115 | TODO(aiuto): Document JSON schema. But it is under development, so the current 116 | best place to look is at tests/hello_licenses.golden. 117 | 118 | Usage: 119 | write_metadata_info must be called from a rule implementation, where the 120 | rule has run the gather_metadata_info aspect on its deps to 121 | collect the transitive closure of LicenseInfo providers into a 122 | LicenseInfo provider. 123 | 124 | foo = rule( 125 | implementation = _foo_impl, 126 | attrs = { 127 | "deps": attr.label_list(aspects = [gather_metadata_info]) 128 | } 129 | ) 130 | 131 | def _foo_impl(ctx): 132 | ... 133 | out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name) 134 | write_metadata_info(ctx, ctx.attr.deps, metadata_file) 135 | 136 | Args: 137 | ctx: context of the caller 138 | deps: a list of deps which should have TransitiveMetadataInfo providers. 139 | This requires that you have run the gather_metadata_info 140 | aspect over them 141 | json_out: output handle to write the JSON info 142 | """ 143 | licenses = [] 144 | for dep in deps: 145 | if TransitiveMetadataInfo in dep: 146 | licenses.extend(metadata_info_to_json(dep[TransitiveMetadataInfo])) 147 | ctx.actions.write( 148 | output = json_out, 149 | content = "[\n%s\n]\n" % ",\n".join(licenses), 150 | ) 151 | 152 | def metadata_info_to_json(metadata_info): 153 | """Render a single LicenseInfo provider to JSON 154 | 155 | Args: 156 | metadata_info: A LicenseInfo. 157 | 158 | Returns: 159 | [(str)] list of LicenseInfo values rendered as JSON. 160 | """ 161 | 162 | main_template = """ {{ 163 | "top_level_target": "{top_level_target}", 164 | "dependencies": [{dependencies} 165 | ], 166 | "licenses": [{licenses} 167 | ], 168 | "packages": [{packages} 169 | ]\n }}""" 170 | 171 | dep_template = """ 172 | {{ 173 | "target_under_license": "{target_under_license}", 174 | "licenses": [ 175 | {licenses} 176 | ] 177 | }}""" 178 | 179 | license_template = """ 180 | {{ 181 | "label": "{label}", 182 | "bazel_package": "{bazel_package}", 183 | "license_kinds": [{kinds} 184 | ], 185 | "copyright_notice": "{copyright_notice}", 186 | "package_name": "{package_name}", 187 | "package_url": "{package_url}", 188 | "package_version": "{package_version}", 189 | "license_text": "{license_text}", 190 | "used_by": [ 191 | {used_by} 192 | ] 193 | }}""" 194 | 195 | kind_template = """ 196 | {{ 197 | "target": "{kind_path}", 198 | "name": "{kind_name}", 199 | "conditions": {kind_conditions} 200 | }}""" 201 | 202 | package_info_template = """ 203 | {{ 204 | "target": "{label}", 205 | "bazel_package": "{bazel_package}", 206 | "package_name": "{package_name}", 207 | "package_url": "{package_url}", 208 | "package_version": "{package_version}", 209 | "purl": "{purl}" 210 | }}""" 211 | 212 | # Build reverse map of license to user 213 | used_by = {} 214 | for dep in metadata_info.deps.to_list(): 215 | # Undo the concatenation applied when stored in the provider. 216 | dep_licenses = dep.licenses.split(",") 217 | for license in dep_licenses: 218 | if license not in used_by: 219 | used_by[license] = [] 220 | used_by[license].append(_strip_null_repo(dep.target_under_license)) 221 | 222 | all_licenses = [] 223 | for license in sorted(metadata_info.licenses.to_list(), key = lambda x: x.label): 224 | kinds = [] 225 | for kind in sorted(license.license_kinds, key = lambda x: x.name): 226 | kinds.append(kind_template.format( 227 | kind_name = kind.name, 228 | kind_path = kind.label, 229 | kind_conditions = kind.conditions, 230 | )) 231 | 232 | if license.license_text: 233 | # Special handling for synthetic LicenseInfo 234 | text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path) 235 | all_licenses.append(license_template.format( 236 | copyright_notice = license.copyright_notice, 237 | kinds = ",".join(kinds), 238 | license_text = text_path, 239 | package_name = license.package_name, 240 | package_url = license.package_url, 241 | package_version = license.package_version, 242 | label = _strip_null_repo(license.label), 243 | bazel_package = _bazel_package(license.label), 244 | used_by = ",\n ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])), 245 | )) 246 | 247 | all_deps = [] 248 | for dep in sorted(metadata_info.deps.to_list(), key = lambda x: x.target_under_license): 249 | # Undo the concatenation applied when stored in the provider. 250 | dep_licenses = dep.licenses.split(",") 251 | all_deps.append(dep_template.format( 252 | target_under_license = _strip_null_repo(dep.target_under_license), 253 | licenses = ",\n ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])), 254 | )) 255 | 256 | all_packages = [] 257 | # We would use this if we had distinct depsets for every provider type. 258 | #for package in sorted(metadata_info.package_info.to_list(), key = lambda x: x.label): 259 | # all_packages.append(package_info_template.format( 260 | # label = _strip_null_repo(package.label), 261 | # package_name = package.package_name, 262 | # package_url = package.package_url, 263 | # package_version = package.package_version, 264 | # )) 265 | 266 | for mi in sorted(metadata_info.other_metadata.to_list(), key = lambda x: x.label): 267 | # Maybe use a map of provider class to formatter. A generic dict->json function 268 | # in starlark would help 269 | 270 | # This format is for using distinct providers. I like the compile time safety. 271 | if mi.type == "package_info": 272 | all_packages.append(package_info_template.format( 273 | label = _strip_null_repo(mi.label), 274 | bazel_package = _bazel_package(mi.label), 275 | package_name = mi.package_name, 276 | package_url = mi.package_url, 277 | package_version = mi.package_version, 278 | purl = mi.purl, 279 | )) 280 | 281 | # experimental: Support the ExperimentalMetadataInfo bag of data 282 | # WARNING: Do not depend on this. It will change without notice. 283 | if mi.type == "package_info_alt": 284 | all_packages.append(package_info_template.format( 285 | label = _strip_null_repo(mi.label), 286 | bazel_package = _bazel_package(mi.label), 287 | # data is just a bag, so we need to use get() or "" 288 | package_name = mi.data.get("package_name") or "", 289 | package_url = mi.data.get("package_url") or "", 290 | package_version = mi.data.get("package_version") or "", 291 | purl = mi.data.get("purl") or "", 292 | )) 293 | 294 | return [main_template.format( 295 | top_level_target = _strip_null_repo(metadata_info.target_under_license), 296 | dependencies = ",".join(all_deps), 297 | licenses = ",".join(all_licenses), 298 | packages = ",".join(all_packages), 299 | )] 300 | --------------------------------------------------------------------------------