├── .bazelci ├── postsubmit.yml ├── presubmit.yml └── tests.yml ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── BUILD ├── CODEOWNERS ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE ├── WORKSPACE.bzlmod ├── admin └── refresh_spdx │ ├── README.md │ └── add_licenses.py ├── deps.bzl ├── distro ├── BUILD └── check_build.sh ├── doc_build ├── BUILD └── merge.py ├── docs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── _config.yml ├── _includes │ ├── head-custom.html │ └── head.html ├── favicon.ico ├── index.md └── latest.md ├── examples ├── BUILD ├── README.md ├── manifest │ ├── BUILD │ ├── android_mock.bzl │ ├── license_display.sh │ ├── main.sh │ ├── main_golden.txt │ └── manifest.bzl ├── my_org │ └── licenses │ │ └── BUILD ├── policy_checker │ ├── BUILD │ ├── license_policy.bzl │ ├── license_policy_check.bzl │ └── license_policy_provider.bzl ├── sboms │ └── BUILD ├── src │ ├── BUILD │ ├── server.cc │ └── server_licenses_test.py └── vndor │ ├── README.md │ ├── acme │ ├── ACME_LICENSE │ ├── BUILD │ └── coyote.cc │ ├── constant_gen │ ├── BUILD │ ├── LICENSE │ ├── LICENSE.on_output │ ├── constant_generator.py │ ├── defs.bzl │ └── verify_licenses_test.py │ └── libhhgttg │ ├── BUILD │ ├── LICENSE │ └── answer.cc ├── licenses ├── generic │ └── BUILD └── spdx │ └── BUILD ├── rules ├── BUILD ├── check_licenses_shim.bzl ├── compliance.bzl ├── current_module_package_info.bzl ├── filtered_rule_kinds.bzl ├── gather_licenses_info.bzl ├── gather_metadata.bzl ├── license.bzl ├── license_impl.bzl ├── license_kind.bzl ├── licenses_core.bzl ├── package_info.bzl ├── private │ ├── BUILD │ └── gathering_providers.bzl ├── providers.bzl ├── sbom.bzl └── user_filtered_rule_kinds.bzl ├── rules_gathering ├── BUILD ├── README.md ├── gather_metadata.bzl ├── gathering_providers.bzl ├── generate_sbom.bzl └── trace.bzl ├── sample_reports ├── BUILD └── licenses_used.bzl ├── tests ├── BUILD ├── Bar.java ├── Hello.java ├── LICENSE ├── LICENSE.extra ├── apps │ ├── BUILD │ ├── an_app.cc │ └── an_app_licenses_test.py ├── bar.cc ├── current_module_package_info │ ├── BUILD.bazel │ └── starlark_tests.bzl ├── hello.cc ├── hello_licenses_test.py ├── legacy │ ├── BUILD │ └── file_under_notice.cc ├── license_test_utils.py └── thrdparty │ ├── BUILD │ ├── LICENSE │ └── new_style_lib.cc ├── tools ├── BUILD ├── checker_demo.py ├── diff_test.sh ├── sbom.py ├── test_helpers.bzl ├── write_sbom.py └── write_workspace_sbom.py └── version.bzl /.bazelci/postsubmit.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - tests.yml 3 | -------------------------------------------------------------------------------- /.bazelci/presubmit.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - tests.yml 3 | -------------------------------------------------------------------------------- /.bazelci/tests.yml: -------------------------------------------------------------------------------- 1 | 2 | default_tests: &default_tests 3 | test_targets: 4 | - "//tests/..." 5 | - "//examples/policy_checker/..." 6 | - "//examples/sboms/..." 7 | 8 | # 9 | # Bazel releases 10 | # 11 | lts: <s 12 | bazel: latest 13 | 14 | rolling: &rolling 15 | bazel: rolling 16 | 17 | 18 | # 19 | # Commmon features by platform 20 | # 21 | ubuntu2004: &ubuntu 22 | platform: ubuntu2004 23 | <<: *default_tests 24 | build_targets: 25 | - "//distro:distro" 26 | - "//distro:relnotes" 27 | 28 | macos: &macos 29 | platform: macos 30 | <<: *default_tests 31 | 32 | windows: &windows 33 | platform: windows 34 | test_targets: 35 | - "//tests/..." 36 | - "//examples/policy_checker/..." 37 | - "//examples/sboms/..." 38 | 39 | 40 | # The cross product of bazel releases X platforms 41 | # 42 | tasks: 43 | lts_ubuntu: 44 | name: lts_ubuntu 45 | <<: *ubuntu 46 | <<: *lts 47 | rolling_ubuntu: 48 | name: rolling_ubuntu 49 | <<: *ubuntu 50 | <<: *rolling 51 | lts_macos: 52 | name: lts_macos 53 | <<: *macos 54 | <<: *lts 55 | rolling_macos: 56 | name: rolling_macos 57 | <<: *macos 58 | # It seems there is no rolling Bazel for macos. 59 | bazel: last_green 60 | lts_windows: 61 | name: lts_windows 62 | <<: *windows 63 | <<: *lts 64 | rolling_windows: 65 | name: rolling_windows 66 | <<: *windows 67 | <<: *rolling 68 | # 69 | # Smoke test with bzlmod 70 | # 71 | bzlmod_rolling_ubuntu: 72 | name: bzlmod_rolling_ubuntu 73 | <<: *ubuntu 74 | <<: *rolling 75 | build_flags: 76 | - "--enable_bzlmod" 77 | build_targets: 78 | - "//distro:distro" 79 | - "//distro:relnotes" 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Tests and examples for any new features. 7 | - [ ] Appropriate changes to README are included in PR 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | admin/refresh_spdx/licenses.json 2 | bazel-bin 3 | bazel-out 4 | bazel-rules_license 5 | bazel-testlogs 6 | MODULE.bazel.lock 7 | -------------------------------------------------------------------------------- /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//:version.bzl", "version") 16 | load("@rules_license//rules:current_module_package_info.bzl", "current_module_package_info") 17 | load("@rules_license//rules:license.bzl", "license") 18 | load("@rules_license//rules:package_info.bzl", "package_info") 19 | 20 | package( 21 | default_applicable_licenses = [":license", ":package_info"], 22 | default_visibility = ["//visibility:public"], 23 | ) 24 | 25 | licenses(["notice"]) 26 | 27 | license( 28 | name = "license", 29 | license_kinds = [ 30 | "@rules_license//licenses/spdx:Apache-2.0", 31 | ], 32 | license_text = "LICENSE", 33 | ) 34 | 35 | current_module_package_info( 36 | name = "package_info", 37 | ) 38 | 39 | exports_files( 40 | ["LICENSE", "WORKSPACE"], 41 | visibility = ["//visibility:public"], 42 | ) 43 | 44 | exports_files( 45 | glob([ 46 | "*.bzl", 47 | ]), 48 | visibility = ["//visibility:public"], 49 | ) 50 | 51 | filegroup( 52 | name = "standard_package", 53 | srcs = glob([ 54 | "*.bzl", 55 | "*.md", 56 | ]) + [ 57 | "BUILD", 58 | "LICENSE", 59 | "MODULE.bazel", 60 | "WORKSPACE.bzlmod", 61 | ], 62 | visibility = ["//distro:__pkg__"], 63 | ) 64 | 65 | filegroup( 66 | name = "docs_deps", 67 | srcs = [ 68 | ":standard_package", 69 | "//rules:standard_package", 70 | "//rules_gathering:standard_package", 71 | ], 72 | visibility = ["//visibility:public"], 73 | ) 74 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @aiuto 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "rules_license", 3 | version = "1.0.0", # Keep in sync with version.bzl 4 | compatibility_level = 1, 5 | ) 6 | 7 | # NOTE: rules_license must not depend on any other repositories if you are 8 | # just using basic rules under //rules/... and //licenses/... 9 | 10 | # TODO(aiuto): Create an extension to enable the rules under //tools/... 11 | # That will require rules_python, which we do not want to force on people who 12 | # do not need //tools. 13 | 14 | # Only for development 15 | bazel_dep(name = "bazel_skylib", version = "1.7.1", dev_dependency = True) 16 | bazel_dep(name = "rules_pkg", version = "1.0.1", dev_dependency = True) 17 | bazel_dep(name = "rules_python", version = "0.35.0", dev_dependency = True) 18 | bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True) 19 | bazel_dep(name = "stardoc", version = "0.6.2", dev_dependency = True) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rules_license 2 | 3 | CI: [![Build status](https://badge.buildkite.com/e12f23186aa579f1e20fcb612a22cd799239c3134bc38e1aff.svg)](https://buildkite.com/bazel/rules-license) 4 | 5 | This repository contains a set of rules and tools for 6 | - declaring metadata about packages, such as 7 | - the licenses the package is available under 8 | - the canonical package name and version 9 | - copyright information 10 | - ... and more TBD in the future 11 | - gathering license declarations into artifacts to ship with code 12 | - applying organization specific compliance constraints against the 13 | set of packages used by a target. 14 | - producing SBOMs for built artifacts. 15 | 16 | WARNING: The code here is still in active initial development and will churn a lot. 17 | 18 | ## Contact 19 | 20 | If you want to follow along: 21 | - Mailing list: [bazel-ssc@bazel.build](https://groups.google.com/a/bazel.build/g/bazel-ssc) 22 | - Monthly eng meeting: [calendar link](MjAyMjA4MjJUMTYwMDAwWiBjXzUzcHBwZzFudWthZXRmb3E5NzhxaXViNmxzQGc&tmsrc=c_53pppg1nukaetfoq978qiub6ls%40group.calendar.google.com&scp=ALL) 23 | - [Latest docs](https://bazelbuild.github.io/rules_license/latest.html) 24 | 25 | ## Roadmap 26 | 27 | *Last update: October 22, 2023* 28 | 29 | ### Q4 2023 30 | 31 | - Reference implementation for "packages used" tool 32 | - produce JSON output usable for SBOM generation or other compliance reporting. 33 | - Reference implementation for an SPDX SBOMM generator 34 | - Support for reading bzlmod lock file 35 | - Support for reading maven lock file 36 | - "How To" guides 37 | - produce a license audit 38 | - produce an SBOM 39 | 40 | ### Q1 2024 41 | 42 | - Add support for other package manager lock file formats 43 | - ? Python 44 | - Golang 45 | - NodeJS 46 | - More SPDX SBOM fields 47 | - support for including vendor SBOMs 48 | - 49 | 50 | ### Beyond 51 | 52 | - Performance improvements 53 | - Sub-SBOMs for tools 54 | 55 | 56 | - TBD 57 | 58 | ## Background reading: 59 | 60 | 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. 61 | - [License Checking with Bazel](https://docs.google.com/document/d/1uwBuhAoBNrw8tmFs-NxlssI6VRolidGYdYqagLqHWt8/edit#). 62 | - [OSS Licenses and Bazel Dependency Management](https://docs.google.com/document/d/1oY53dQ0pOPEbEvIvQ3TvHcFKClkimlF9AtN89EPiVJU/edit#) 63 | - [Adding OSS license declarations to Bazel](https://docs.google.com/document/d/1XszGbpMYNHk_FGRxKJ9IXW10KxMPdQpF5wWbZFpA4C8/edit#heading=h.5mcn15i0e1ch) 64 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 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 | workspace(name = "rules_license") 16 | 17 | # rules_license has no dependencies for basic license and package_info 18 | # declarations. 19 | # 20 | # If you want to use any of the reporting or SBOM tools, and you are using a 21 | # WORKSPACE file instead of bzlmod, then you should explicitly depend on 22 | # rules_python in your WORKSPACE. 23 | 24 | ### INTERNAL ONLY - lines after this are not included in the release packaging. 25 | 26 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 27 | 28 | http_archive( 29 | name = "bazel_skylib", 30 | sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", 31 | urls = [ 32 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", 33 | "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", 34 | ], 35 | ) 36 | 37 | http_archive( 38 | name = "rules_python", 39 | sha256 = "be04b635c7be4604be1ef20542e9870af3c49778ce841ee2d92fcb42f9d9516a", 40 | strip_prefix = "rules_python-0.35.0", 41 | url = "https://github.com/bazelbuild/rules_python/releases/download/0.35.0/rules_python-0.35.0.tar.gz", 42 | ) 43 | 44 | http_archive( 45 | name = "rules_pkg", 46 | urls = [ 47 | "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/1.0.1/rules_pkg-1.0.1.tar.gz", 48 | "https://github.com/bazelbuild/rules_pkg/releases/download/1.0.1/rules_pkg-1.0.1.tar.gz", 49 | ], 50 | sha256 = "d20c951960ed77cb7b341c2a59488534e494d5ad1d30c4818c736d57772a9fef", 51 | ) 52 | 53 | http_archive( 54 | name = "bazel_stardoc", 55 | sha256 = "c9794dcc8026a30ff67cf7cf91ebe245ca294b20b071845d12c192afe243ad72", 56 | urls = [ 57 | "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz", 58 | "https://github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz", 59 | ], 60 | ) 61 | 62 | load("@rules_python//python:repositories.bzl", "py_repositories") 63 | 64 | py_repositories() 65 | 66 | load("@bazel_stardoc//:setup.bzl", "stardoc_repositories") 67 | 68 | stardoc_repositories() 69 | -------------------------------------------------------------------------------- /WORKSPACE.bzlmod: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 2 | load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 3 | 4 | # This is needed for tools 5 | maybe( 6 | http_archive, 7 | name = "rules_python", 8 | sha256 = "b593d13bb43c94ce94b483c2858e53a9b811f6f10e1e0eedc61073bd90e58d9c", 9 | strip_prefix = "rules_python-0.12.0", 10 | url = "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.12.0.tar.gz", 11 | ) 12 | -------------------------------------------------------------------------------- /admin/refresh_spdx/README.md: -------------------------------------------------------------------------------- 1 | # Tools to refresh SPDX license list 2 | 3 | ## Update //licenses/spdx 4 | 5 | This pulls in the authoritative list of licenses from the SPDX project 6 | and updates our copy of it to include any new license identifiers. 7 | 8 | It does not attempt to classify them in any way. 9 | 10 | NOTE: You must run this from //admin/refresh_spdx 11 | 12 | 1. Fetch the license list from the SPDX project. 13 | 14 | ```bash 15 | wget https://github.com/spdx/license-list-data/raw/master/json/licenses.json 16 | ``` 17 | 18 | 1. Run the refresh tool 19 | python add_licenses.py 20 | 21 | -------------------------------------------------------------------------------- /admin/refresh_spdx/add_licenses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Refresh the BUILD file of SPDX license_kinds with new ones from licenses.json. 3 | 4 | Usage: 5 | wget https://raw.githubusercontent.com/spdx/license-list-data/main/json/licenses.json 6 | LC_ALL="en_US.UTF-8" admin/refresh_spdx/add_licenses.py 7 | git diff 8 | git commit 9 | """ 10 | 11 | import json 12 | import os 13 | from string import Template 14 | import re 15 | import sys 16 | 17 | def load_json(path: str): 18 | """Loads a JSON file and returns the data. 19 | 20 | Args: 21 | path: (str) a file path. 22 | Returns: 23 | (dict) the parsed data. 24 | """ 25 | with open(path, 'r') as fp: 26 | ret = json.load(fp) 27 | return ret 28 | 29 | 30 | def parse_build_file(text: str): 31 | """Parse a BUILD file of license declaration. 32 | 33 | Parses a build file and returns all the text that 34 | is not licenese_kind declarations and a map of 35 | license kind names to the text which declared them 36 | 37 | license_kind( 38 | name = "0BSD", 39 | conditions = [], 40 | url = "https://spdx.org/licenses/0BSD.html", 41 | ) 42 | 43 | Returns: 44 | preamble: str 45 | targets: map 46 | """ 47 | 48 | target_start = re.compile(r'^([a-zA-Z0-9_]+)\(') # ) to fix autoindent 49 | name_match = re.compile(r'^ *name *= *"([^"]*)"') 50 | 51 | targets = {} 52 | preamble = [] 53 | collecting_rule = None 54 | 55 | for line in text.split('\n'): 56 | if collecting_rule: 57 | collecting_rule.append(line) 58 | if line.startswith(')'): 59 | assert cur_name 60 | targets[cur_name] = '\n'.join(collecting_rule) 61 | # print(cur_name, "=====", targets[cur_name]) 62 | collecting_rule = None 63 | cur_name = None 64 | else: 65 | m = name_match.match(line) 66 | if m: 67 | cur_name = m.group(1) 68 | continue 69 | 70 | # not collecting a rule 71 | m = target_start.match(line) 72 | if m: 73 | cur_rule = m.group(1) 74 | if cur_rule == 'license_kind': 75 | collecting_rule = [line] 76 | continue 77 | 78 | if line or preamble[-1]: 79 | preamble.append(line) 80 | 81 | return '\n'.join(preamble), targets 82 | 83 | 84 | # Sample JSON formate license declaration. 85 | """ 86 | { 87 | "licenseListVersion": "3.8-57-gca4f142", 88 | "licenses": [ 89 | { 90 | "reference": "./0BSD.html", 91 | "isDeprecatedLicenseId": false, 92 | "detailsUrl": "http://spdx.org/licenses/0BSD.json", 93 | "referenceNumber": "232", 94 | "name": "BSD Zero Clause License", 95 | "licenseId": "0BSD", 96 | "seeAlso": [ 97 | "http://landley.net/toybox/license.html" 98 | ], 99 | "isOsiApproved": true 100 | }, 101 | """ 102 | 103 | 104 | def insert_new(already_declared, licenses): 105 | template = Template(( 106 | 'license_kind(\n' 107 | ' name = "${licenseId}",\n' 108 | ' conditions = [],\n' 109 | ' url = "https://spdx.org/licenses/${licenseId}.html",\n' 110 | ')')) 111 | 112 | for license in licenses: 113 | id = license['licenseId'] 114 | old = already_declared.get(id) 115 | if not old: 116 | print('Adding:', id) 117 | already_declared[id] = template.substitute(license) 118 | 119 | 120 | def update_spdx_build(): 121 | """Update //licenses/spdx/BUILD with new license kinds.""" 122 | 123 | license_json = load_json('licenses.json') 124 | 125 | # Find workspace root 126 | build_path = 'licenses/spdx/BUILD' 127 | found_spdx_build = False 128 | for i in range(5): # Simple regression failure limiter 129 | if os.access('WORKSPACE', os.R_OK) and os.access(build_path, os.R_OK): 130 | found_spdx_build = True 131 | break 132 | os.chdir('..') 133 | if not found_spdx_build: 134 | print('Could not find', build_path) 135 | return 1 136 | 137 | with open(build_path, 'r') as fp: 138 | current_file_content = fp.read() 139 | 140 | preamble, kinds = parse_build_file(current_file_content) 141 | insert_new(kinds, license_json['licenses']) 142 | with open(build_path, 'w') as fp: 143 | fp.write(preamble) 144 | for license in sorted(kinds.keys(), key=lambda x: x.lower()): 145 | fp.write('\n') 146 | fp.write(kinds[license]) 147 | fp.write('\n') 148 | 149 | 150 | def main(): 151 | lc_all = os.environ.get('LC_ALL') 152 | if lc_all != 'en_US.UTF-8': 153 | print('Your environment settings will reorder the file badly.') 154 | print('Please rerun as:') 155 | print(' LC_ALL="en_US.UTF-8"', ' '.join(sys.argv)) 156 | sys.exit(1) 157 | 158 | update_spdx_build() 159 | 160 | 161 | if __name__ == '__main__': 162 | main() 163 | -------------------------------------------------------------------------------- /deps.bzl: -------------------------------------------------------------------------------- 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 | """Workspace dependencies for rules_license/rules.""" 16 | 17 | def rules_license_dependencies(): 18 | pass 19 | -------------------------------------------------------------------------------- /distro/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("//:version.bzl", "version") 16 | load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") 17 | load("@rules_pkg//pkg/releasing:defs.bzl", "print_rel_notes") 18 | 19 | package( 20 | default_visibility = ["//visibility:public"], 21 | default_applicable_licenses = ["//:license"], 22 | ) 23 | 24 | licenses(["notice"]) 25 | 26 | alias( 27 | name = "distro", 28 | actual = "rules_license-%s" % version, 29 | ) 30 | 31 | # Build the artifact to put on the github release page. 32 | pkg_tar( 33 | name = "rules_license-%s" % version, 34 | srcs = [ 35 | ":small_workspace", 36 | "//:standard_package", 37 | "//licenses/generic:standard_package", 38 | "//licenses/spdx:standard_package", 39 | "//rules:standard_package", 40 | "//rules_gathering:standard_package", 41 | "//rules/private:standard_package", 42 | "//sample_reports:standard_package", 43 | "//tools:standard_package", 44 | ], 45 | extension = "tar.gz", 46 | # It is all source code, so make it read-only. 47 | mode = "0444", 48 | # Make it owned by root so it does not have the uid of the CI robot. 49 | owner = "0.0", 50 | package_dir = ".", 51 | strip_prefix = ".", 52 | tags = [ 53 | "no_windows", 54 | ], 55 | ) 56 | 57 | genrule( 58 | name = "small_workspace", 59 | srcs = ["//:WORKSPACE"], 60 | outs = ["WORKSPACE"], 61 | cmd = "sed -e '/### INTERNAL ONLY/,$$d' $(location //:WORKSPACE) >$@", 62 | tags = [ 63 | "no_windows", 64 | ], 65 | ) 66 | 67 | print_rel_notes( 68 | name = "relnotes", 69 | outs = ["relnotes.txt"], 70 | mirror_host = "mirror.bazel.build", 71 | org = "bazelbuild", 72 | repo = "rules_license", 73 | version = version, 74 | ) 75 | -------------------------------------------------------------------------------- /distro/check_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ef 2 | # 3 | # This is a temporary hack to verify the distribution archive is sound. 4 | # 5 | # The intent is to create a rule which does this and add to 6 | # rules_pkg. Comments within it are mostly for future me 7 | # while writing that. 8 | 9 | TARBALL=$(bazel build //distro:distro 2>&1 | grep 'rules_license-.*\.tar\.gz' | sed -e 's/ //g') 10 | REPO_NAME='rules_license' 11 | 12 | # This part can be standard from the rule 13 | 14 | 15 | TARNAME=$(basename "$TARBALL") 16 | 17 | TMP=$(mktemp -d) 18 | trap '/bin/rm -rf "$TMP"; exit 0' 0 1 2 3 15 19 | 20 | cp "$TARBALL" "$TMP" 21 | 22 | cd "$TMP" 23 | cat >WORKSPACE <BUILD <LICENSE 52 | 53 | # Then a list of commands to run. This can be a template 54 | # too so we can substitute the path to bazel. 55 | bazel build ... 56 | bazel build @rules_license//rules/... 57 | bazel build @rules_license//licenses/... 58 | bazel query @rules_license//licenses/generic/... 59 | bazel query ... 60 | -------------------------------------------------------------------------------- /doc_build/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 | """Generate the reference documentation. 15 | 16 | How to: 17 | bazel build //doc_build:reference 18 | cp bazel-bin/doc_build/reference.md docs/latest.md 19 | git commit -m 'update docs' docs/latest.md 20 | """ 21 | 22 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 23 | load("@stardoc//stardoc:stardoc.bzl", "stardoc") 24 | load("@rules_python//python:defs.bzl", "py_library") 25 | load("//:version.bzl", "version") 26 | 27 | package(default_package_metadata = ["//:license", "//:package_info"]) 28 | 29 | filegroup( 30 | name = "standard_package", 31 | srcs = [ 32 | "BUILD", 33 | ] + glob([ 34 | "*.bzl", 35 | "*.py", 36 | ]), 37 | visibility = ["//distro:__pkg__"], 38 | ) 39 | 40 | exports_files( 41 | glob([ 42 | "*.bzl", 43 | ]), 44 | visibility = [ 45 | "//distro:__pkg__", 46 | ], 47 | ) 48 | 49 | # pairs of rule name and the source file to get it from 50 | # Must put macro wrapped rules after their wrapper 51 | # buildifier: leave-alone, do not sort 52 | ORDER = [ 53 | ("license", "//rules:license.bzl"), 54 | ("_license", "//rules:license.bzl"), 55 | ("license_kind", "//rules:license_kind.bzl"), 56 | ("_license_kind", "//rules:license_kind.bzl"), 57 | ("package_info", "//rules:package_info.bzl"), 58 | ("_package_info", "//rules:package_info.bzl"), 59 | ("LicenseInfo", "//rules:providers.bzl"), 60 | ("LicenseKindInfo", "//rules:providers.bzl"), 61 | ("PackageInfo", "//rules:providers.bzl"), 62 | ("gather_metadata_info", "//rules_gathering:gather_metadata.bzl"), 63 | ("gather_metadata_info_and_write", "//rules_gathering:gather_metadata.bzl"), 64 | ("trace", "//rules_gathering:trace.bzl"), 65 | ("current_module_package_info", "//rules:current_module_package_info.bzl"), 66 | ] 67 | 68 | genrule( 69 | name = "reference", 70 | srcs = ["%s.md" % rule for rule, _ in ORDER], 71 | outs = ["reference.md"], 72 | cmd = "$(location :merge) $(SRCS) >$@", 73 | tools = [":merge"], 74 | ) 75 | 76 | [ 77 | stardoc( 78 | name = "%s_gen" % rule, 79 | out = "%s.md" % rule, 80 | input = src, 81 | symbol_names = [ 82 | rule, 83 | ], 84 | deps = [":lib_of_everything"], 85 | ) 86 | for rule, src in ORDER 87 | if src 88 | ] 89 | 90 | # gather all rules that should be documented 91 | bzl_library( 92 | name = "lib_of_everything", 93 | srcs = [ 94 | "//:version.bzl", 95 | "//rules:standard_package", 96 | "//rules_gathering:standard_package", 97 | ], 98 | visibility = ["//visibility:public"], 99 | ) 100 | 101 | # This is experimental. We are waiting for stardoc to get the features which 102 | # are done in merge. 103 | py_binary( 104 | name = "merge", 105 | srcs = ["merge.py"], 106 | python_version = "PY3", 107 | srcs_version = "PY3", 108 | visibility = ["//visibility:private"], 109 | ) 110 | -------------------------------------------------------------------------------- /doc_build/merge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2022 The Bazel Authors. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | """merge stardoc output into a single page. 16 | 17 | - concatenates files 18 | - corrects things that stardoc botches 19 | """ 20 | 21 | import re 22 | import sys 23 | import typing 24 | 25 | 26 | # I think stardoc changed the format of the id strings. Sigh. 27 | ID_RE = re.compile(r'') 28 | ID_OLD_RE = re.compile(r'') 29 | WRAPS_RE = re.compile(r'@wraps\((.*)\)') 30 | SINCE_RE = re.compile(r'@since\(([^)]*)\)') 31 | CENTER_RE = re.compile(r'

([^<]*)

') 32 | 33 | 34 | def merge_file(file: str, out, wrapper_map:typing.Dict[str, str]) -> None: 35 | with open(file, 'r') as inp: 36 | content = inp.read() 37 | m = ID_RE.search(content) 38 | if not m: 39 | m = ID_OLD_RE.search(content) 40 | this_rule = m.group(1) if m else None 41 | m = WRAPS_RE.search(content) 42 | if m: 43 | # I wrap something, so don't emit me. 44 | wrapper_map[m.group(1)] = this_rule 45 | return 46 | # If something wraps me, rewrite myself with the wrapper name. 47 | if this_rule in wrapper_map: 48 | content = content.replace(this_rule, wrapper_map[this_rule]) 49 | merge_text(content, out) 50 | 51 | 52 | def merge_text(text: str, out) -> None: 53 | """Merge a block of text into an output stream. 54 | 55 | Args: 56 | text: block of text produced by Starroc. 57 | out: an output file stream. 58 | """ 59 | for line in text.split('\n'): 60 | line = SINCE_RE.sub(r'
Since \1
', line) 61 | 62 | if line.startswith('| :'): 63 | line = fix_stardoc_table_align(line) 64 | # Compensate for https://github.com/bazelbuild/stardoc/issues/118. 65 | # Convert escaped HTML
  • back to raw text 66 | line = line.replace('<li>', '
  • ') 67 | line = CENTER_RE.sub(r'\1', line) 68 | _ = out.write(line) 69 | _ = out.write('\n') 70 | 71 | 72 | def fix_stardoc_table_align(line: str) -> str: 73 | """Change centered descriptions to left justified.""" 74 | if line.startswith('| :-------------: | :-------------: '): 75 | return '| :------------ | :--------------- | :---------: | :---------: | :----------- |' 76 | return line 77 | 78 | 79 | def main(argv: typing.Sequence[str]) -> None: 80 | wrapper_map = {} 81 | for file in argv[1:]: 82 | merge_file(file, sys.stdout, wrapper_map) 83 | 84 | 85 | if __name__ == '__main__': 86 | main(sys.argv) 87 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to Tony Aiuto (aiuto@google.com), the 73 | Project Steward(s) for bazelbuild/rules_license. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out to the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement (CLA). You (or your employer) retain the copyright to your 10 | contribution; this simply gives us permission to use and redistribute your 11 | contributions as part of the project. Head over to 12 | to see your current agreements on file or 13 | to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult 23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 24 | information on using pull requests. 25 | 26 | All issues that modify the license rules require a github issue first. We 27 | will use that issue for public discussion of the merits and potential 28 | implementation. 29 | 30 | ## Community Guidelines 31 | 32 | This project follows 33 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 34 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/_includes/head-custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bazelbuild/rules_license/fcc27a07599af4f9a5d43b71758b6b70a808d86d/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # bazelbuild/rules_license 2 | 3 | Bazel rules for defining and using package licensing and other metadata 4 | 5 | Use bazel-ssc@bazel.build for discussion. 6 | 7 | ## Reference 8 | 9 | * [Latest Snapshot at head](latest.md) 10 | -------------------------------------------------------------------------------- /examples/BUILD: -------------------------------------------------------------------------------- 1 | # rules_license examples 2 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples for rules_license 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 | -------------------------------------------------------------------------------- /examples/manifest/BUILD: -------------------------------------------------------------------------------- 1 | load(":android_mock.bzl", "android_binary", "android_library") 2 | load("@rules_license//tools:test_helpers.bzl", "golden_cmd_test") 3 | 4 | 5 | # These two rules today capture what an android_binary would look like. 6 | # This rule represents the Android specific code that displays licenses 7 | # on the display. Note that it does not depend on anything to get the 8 | # license contents; the implementation of these rules macros handle that 9 | # detail. 10 | android_library( 11 | name = "licenses", 12 | srcs = [ 13 | "license_display.sh", 14 | ], 15 | data = [ 16 | "@rules_license//distro:distro", 17 | ], 18 | ) 19 | 20 | # This captures how the application would be built. The dependencies of this 21 | # rule are crawled to identify third-party licenses in use. The macro definition 22 | # of this rule creates a graph to capture that process of identifying licenses, 23 | # building the licenses target, and finally invoking the "real" android_binary 24 | # rule to build the final output with the injected license content. 25 | android_binary( 26 | name = "main", 27 | srcs = ["main.sh"], 28 | deps = [ 29 | ], 30 | data = [ 31 | ":licenses", 32 | ], 33 | ) 34 | 35 | golden_cmd_test( 36 | name = "main_test", 37 | srcs = [], 38 | cmd = "$(location :main)", 39 | tools = [":main"], 40 | golden = "main_golden.txt", 41 | ) 42 | -------------------------------------------------------------------------------- /examples/manifest/android_mock.bzl: -------------------------------------------------------------------------------- 1 | load("manifest.bzl", "manifest") 2 | 3 | """This is a proof of concept to show how to modify a macro definition to 4 | create a sub-graph allowing for build time injection of license information. We 5 | use Android-inspired rule names since these are a likely candidate for this 6 | sort of injection.""" 7 | 8 | def android_library(name, **kwargs): 9 | # This is an approximation for demo purposes. 10 | 11 | data = kwargs.pop("data", []) 12 | native.filegroup( 13 | name = name, 14 | srcs = data + kwargs.get("srcs", []), 15 | ) 16 | 17 | # Inject the data dependency into the library, preserving any other data it has. 18 | native.sh_library( 19 | name = name + "_w_licenses", 20 | data = data + [name + "_manifest.txt"], 21 | **kwargs 22 | ) 23 | 24 | def android_binary(name, **kwargs): 25 | # Same observation about not being sloppy with mapping deps, but I think the only important attribute 26 | # in android_binary is deps, but need to double-check. 27 | native.filegroup( 28 | name = name + "_no_licenses", 29 | srcs = kwargs.get("data", []), 30 | ) 31 | 32 | mf_name = name + "_manifest" 33 | manifest( 34 | name = mf_name, 35 | deps = [":" + name + "_no_licenses"], 36 | ) 37 | 38 | # This uses the conditions tool to generate an approximation of a compliance report 39 | # to demonstrate how license data can be plumbed and made available at build time. 40 | native.genrule( 41 | name = "gen_" + name + "_manifest", 42 | srcs = [":" + mf_name], 43 | outs = ["licenses_manifest.txt"], 44 | cmd = "cat $(locations :%s) > $@" % mf_name, 45 | ) 46 | 47 | # Swap out the :licenses dep for our new :licenses_w_licenses dep 48 | newdeps = [] 49 | deps = kwargs.get("data", []) 50 | for dep in deps: 51 | if dep == ":licenses": 52 | newdeps.append(":licenses_w_licenses") 53 | else: 54 | newdeps.append(dep) 55 | kwargs["data"] = newdeps 56 | 57 | # Compile the executable with the user's originally supplied name, but with the new content. 58 | native.sh_binary( 59 | name = name, 60 | **kwargs 61 | ) 62 | -------------------------------------------------------------------------------- /examples/manifest/license_display.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function display_licenses { 4 | echo -n "Licenses: " 5 | cat "$0.runfiles/rules_license/examples/manifest/licenses_manifest.txt" 6 | echo 7 | } 8 | -------------------------------------------------------------------------------- /examples/manifest/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #source gbash.sh || exit 4 | 5 | #source "$RUNFILES/google3/tools/build_defs/license/examples/manifest/license_display.sh" 6 | source "$0.runfiles/rules_license/examples/manifest/license_display.sh" 7 | #source module google3/tools/build_defs/license/examples/manifest/license_display.sh 8 | 9 | echo "I am a program that uses open source code." 10 | display_licenses 11 | -------------------------------------------------------------------------------- /examples/manifest/manifest.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2023 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 | """An example using gather_licenses_info as input to another action.""" 15 | 16 | load( 17 | "@rules_license//rules:gather_licenses_info.bzl", 18 | "gather_licenses_info", 19 | ) 20 | load( 21 | "@rules_license//rules_gathering:gathering_providers.bzl", 22 | "TransitiveLicensesInfo", 23 | ) 24 | 25 | def get_licenses_mapping(deps, warn = False): 26 | """Creates list of entries representing all licenses for the deps. 27 | 28 | Args: 29 | 30 | deps: a list of deps which should have TransitiveLicensesInfo providers. 31 | This requires that you have run the gather_licenses_info 32 | aspect over them 33 | 34 | warn: boolean, if true, display output about legacy targets that need 35 | update 36 | 37 | Returns: 38 | {File:package_name} 39 | """ 40 | tls = [] 41 | for dep in deps: 42 | lds = dep[TransitiveLicensesInfo].licenses 43 | tls.append(lds) 44 | 45 | ds = depset(transitive = tls) 46 | 47 | # Ignore any legacy licenses that may be in the report 48 | mappings = {} 49 | for lic in ds.to_list(): 50 | if type(lic.license_text) == "File": 51 | mappings[lic.license_text] = lic.package_name 52 | elif warn: 53 | print("Legacy license %s not included, rule needs updating" % lic.license_text) 54 | return mappings 55 | 56 | 57 | def _manifest_impl(ctx): 58 | # Gather all licenses and make it available as deps for downstream rules 59 | # Additionally write the list of license filenames to a file that can 60 | # also be used as an input to downstream rules. 61 | licenses_file = ctx.actions.declare_file(ctx.attr.out.name) 62 | mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses) 63 | ctx.actions.write( 64 | output = licenses_file, 65 | content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]), 66 | ) 67 | return [DefaultInfo(files = depset(mappings.keys()))] 68 | 69 | _manifest = rule( 70 | implementation = _manifest_impl, 71 | doc = """Internal tmplementation method for manifest().""", 72 | attrs = { 73 | "deps": attr.label_list( 74 | doc = """List of targets to collect license files for.""", 75 | aspects = [gather_licenses_info], 76 | ), 77 | "out": attr.output( 78 | doc = """Output file.""", 79 | mandatory = True, 80 | ), 81 | "warn_on_legacy_licenses": attr.bool(default = False), 82 | }, 83 | ) 84 | 85 | def manifest(name, deps, out = None, **kwargs): 86 | if not out: 87 | out = name + ".manifest" 88 | _manifest(name = name, deps = deps, out = out, **kwargs) 89 | 90 | -------------------------------------------------------------------------------- /examples/my_org/licenses/BUILD: -------------------------------------------------------------------------------- 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 | # Example license kind definitions. 15 | 16 | # We expect that all license_kind rules used by an organization exist in a 17 | # central place in their source repository. 18 | # 19 | # - This allows centralized audit and, with suport from the SCM, an assurance 20 | # that individual developers are not adding new license kinds to the code 21 | # base without authorization. 22 | # - When third party packages are used, they might come with license rules 23 | # pointing to well known license_kinds from @rules_license/licenses. 24 | # At import time, users can mechanically transform those target paths from 25 | # @rules_license to their own license_kind repository. 26 | # - The conditions for each license_kind may be customized for the organzation's 27 | # needs, and not match the conditions used by the canonical versions from 28 | # @rules_license. 29 | # - In rare cases, a third_party project will define their own license_kinds. 30 | # There is no reasonable automatic handling of that. Organizations will have 31 | # to hand inspect the license text to see if it matches the conditions, and 32 | # then will have to hand import it to their private license_kind repository. 33 | 34 | load("@rules_license//rules:license_kind.bzl", "license_kind") 35 | 36 | package(default_visibility = ["//examples:__subpackages__"]) 37 | 38 | # license_kind rules generally appear in a central location per workspace. They 39 | # are intermingled with normal target build rules 40 | license_kind( 41 | name = "generic_notice", 42 | conditions = [ 43 | "notice", 44 | ], 45 | ) 46 | 47 | license_kind( 48 | name = "generic_restricted", 49 | conditions = [ 50 | "restricted", 51 | ], 52 | ) 53 | 54 | license_kind( 55 | name = "unencumbered", 56 | conditions = [], # none 57 | ) 58 | 59 | license_kind( 60 | name = "lgpl_like", 61 | conditions = [ 62 | "restricted_if_statically_linked", 63 | ], 64 | ) 65 | 66 | license_kind( 67 | name = "acme_corp_paid", 68 | conditions = [ 69 | "allowlist:acme_corp_paid", 70 | ], 71 | ) 72 | -------------------------------------------------------------------------------- /examples/policy_checker/BUILD: -------------------------------------------------------------------------------- 1 | # Example of automated license policy definitions. 2 | 3 | load("@rules_license//examples/policy_checker:license_policy.bzl", "license_policy") 4 | load("@rules_license//examples/policy_checker:license_policy_check.bzl", "license_policy_check") 5 | 6 | package(default_package_metadata = ["//:license", "//:package_info"]) 7 | 8 | # license_policy rules generally appear in a central location per workspace. That 9 | # should be access controlled by the policy team. 10 | 11 | # A production service can use licenses with most conditions 12 | license_policy( 13 | name = "production_service", 14 | conditions = [ 15 | "notice", 16 | "restricted_if_statically_linked", 17 | ], 18 | ) 19 | 20 | # A mobile application usually can not allow end-user replacable libraries. 21 | # So LGPL code (which is restricted_if_statically_linked) can not be used. 22 | license_policy( 23 | name = "mobile_application", 24 | conditions = [ 25 | "notice", 26 | ], 27 | ) 28 | 29 | license_policy( 30 | name = "special_allowlisted_app", 31 | # There could be a allowlist of targets here. 32 | conditions = [ 33 | "notice", 34 | "allowlist:acme_corp_paid", 35 | ], 36 | ) 37 | 38 | # Now we might build checks of critical applications against policies 39 | # 40 | # Questions to consider? 41 | # - Your organization migth want to fold these kinds of checks into 42 | # wrapper macros around the rules which generate services and apps 43 | # - You might want to distribute checks to rules alongside the products 44 | # - Or, you might want to consolidate them in a single place where your 45 | # compliance team owns them, as this example does 46 | 47 | license_policy_check( 48 | name = "check_server", 49 | policy = ":production_service", 50 | targets = ["//examples/src:my_server"], 51 | ) 52 | 53 | 54 | # This is marked manual, so bazel test ... does not fail. Try it yourself with 55 | # bazel build :check_violating_server 56 | license_policy_check( 57 | name = "check_violating_server", 58 | policy = ":production_service", 59 | tags = [ 60 | "manual", 61 | ], 62 | targets = ["//examples/src:my_violating_server"], 63 | ) 64 | -------------------------------------------------------------------------------- /examples/policy_checker/license_policy.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 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 | 15 | """license_policy rule. 16 | 17 | A license_policy names a set of conditions allowed in the union of all 18 | license_kinds use by a target. The name of the rule is typically an 19 | application type (e.g. production_server, mobile_application, ...) 20 | 21 | """ 22 | 23 | load( 24 | "@rules_license//examples/policy_checker:license_policy_provider.bzl", 25 | "LicensePolicyInfo" 26 | ) 27 | 28 | def _license_policy_impl(ctx): 29 | provider = LicensePolicyInfo( 30 | name = ctx.attr.name, 31 | label = "@%s//%s:%s" % ( 32 | ctx.label.workspace_name, 33 | ctx.label.package, 34 | ctx.label.name, 35 | ), 36 | conditions = ctx.attr.conditions, 37 | ) 38 | return [provider] 39 | 40 | _license_policy = rule( 41 | implementation = _license_policy_impl, 42 | attrs = { 43 | "conditions": attr.string_list( 44 | doc = "Conditions to be met when using software under this license." + 45 | " Conditions are defined by the organization using this license.", 46 | mandatory = True, 47 | ), 48 | }, 49 | ) 50 | 51 | def license_policy(name, conditions): 52 | _license_policy( 53 | name = name, 54 | conditions = conditions, 55 | applicable_licenses = [], 56 | ) 57 | -------------------------------------------------------------------------------- /examples/policy_checker/license_policy_check.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 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 | 15 | """License compliance checking at analysis time.""" 16 | 17 | load( 18 | "@rules_license//examples/policy_checker:license_policy_provider.bzl", 19 | "LicensePolicyInfo", 20 | ) 21 | load( 22 | "@rules_license//rules:gather_licenses_info.bzl", 23 | "gather_licenses_info", 24 | ) 25 | load("@rules_license//rules:providers.bzl", "LicenseInfo") 26 | load("@rules_license//rules_gathering:gathering_providers.bzl", "TransitiveLicensesInfo") 27 | 28 | # This is a crude example of the kind of license reporting which can be done. 29 | def _license_policy_check_impl(ctx): 30 | policy = ctx.attr.policy[LicensePolicyInfo] 31 | allowed_conditions = policy.conditions 32 | 33 | for target in ctx.attr.targets: 34 | if TransitiveLicensesInfo in target: 35 | for license in target[TransitiveLicensesInfo].licenses.to_list(): 36 | for kind in license.license_kinds: 37 | for condition in kind.conditions: 38 | if condition not in allowed_conditions: 39 | fail("Condition %s violates policy %s of %s" % ( 40 | condition, 41 | policy.label, 42 | target.label, 43 | )) 44 | 45 | for target in ctx.attr.targets: 46 | if LicenseInfo in target: 47 | for license in target[LicenseInfo].licenses.to_list(): 48 | for kind in license.license_kinds: 49 | for condition in kind.conditions: 50 | if condition not in allowed_conditions: 51 | fail("Condition %s violates policy %s of %s" % ( 52 | condition, 53 | policy.label, 54 | target.label, 55 | )) 56 | return [DefaultInfo()] 57 | 58 | _license_policy_check = rule( 59 | implementation = _license_policy_check_impl, 60 | doc = """Internal implementation method for license_policy_check().""", 61 | attrs = { 62 | "policy": attr.label( 63 | doc = """Policy definition.""", 64 | mandatory = True, 65 | providers = [LicensePolicyInfo], 66 | ), 67 | "targets": attr.label_list( 68 | doc = """Target to collect LicenseInfo for.""", 69 | aspects = [gather_licenses_info], 70 | mandatory = True, 71 | ), 72 | }, 73 | ) 74 | 75 | def license_policy_check(name, targets, policy, **kwargs): 76 | """Checks a list of targets against a policy. 77 | 78 | Args: 79 | name: The target. 80 | targets: A list of targets to test for compliance with a policy 81 | policy: A rule providing LicensePolicyInfo. 82 | **kwargs: other args. 83 | 84 | Usage: 85 | 86 | license_policy_check( 87 | name = "license_info", 88 | targets = [":my_app"], 89 | policy = "//my_org/compliance/policies:mobile_application", 90 | ) 91 | """ 92 | _license_policy_check(name = name, targets = targets, policy = policy, **kwargs) 93 | -------------------------------------------------------------------------------- /examples/policy_checker/license_policy_provider.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 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 | 15 | """LicensePolicyProvider.""" 16 | 17 | LicensePolicyInfo = provider( 18 | doc = """Declares a policy name and the license conditions allowable under it.""", 19 | fields = { 20 | "conditions": "List of conditions to be met when using this software.", 21 | "label": "The full path to the license policy definition.", 22 | "name": "License policy name", 23 | }, 24 | ) 25 | -------------------------------------------------------------------------------- /examples/sboms/BUILD: -------------------------------------------------------------------------------- 1 | # Demonstrate the generate_sbom rule 2 | 3 | load("@rules_license//rules_gathering:generate_sbom.bzl", "generate_sbom") 4 | 5 | # There are not a lot of targets in this rule set to build a SBOM from 6 | # so we will (in a very self-referential way) generate one for the tool 7 | # which generates the SBOMs 8 | # See the output in bazel-bin/examples/sboms/write_sbom.txt 9 | generate_sbom( 10 | name = "write_sbom", 11 | out = "write_sbom.txt", 12 | deps = ["//tools:write_sbom"], 13 | ) 14 | -------------------------------------------------------------------------------- /examples/src/BUILD: -------------------------------------------------------------------------------- 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 | # Examples of applications and interactions with licenses 15 | 16 | load("@rules_license//examples/vndor/constant_gen:defs.bzl", "constant_gen") 17 | load("@rules_license//rules:compliance.bzl", "check_license") 18 | load("@rules_license//sample_reports:licenses_used.bzl", "licenses_used") 19 | 20 | package( 21 | default_package_metadata = ["//:license", "//:package_info"], 22 | default_visibility = ["//examples:__subpackages__"], 23 | ) 24 | 25 | cc_binary( 26 | name = "my_server", 27 | srcs = ["server.cc"], 28 | deps = [":message"], 29 | ) 30 | 31 | # Sample 32 | constant_gen( 33 | name = "message", 34 | text = "Hello, world.", 35 | var = "server_message", 36 | ) 37 | 38 | # TODO(aiuto): Turn this strictly into a compliance test. 39 | check_license( 40 | name = "check_server", 41 | check_conditions = False, 42 | license_texts = "server_licenses.txt", 43 | report = "server_report.txt", 44 | deps = [ 45 | ":my_server", 46 | ], 47 | ) 48 | 49 | # 50 | # Verify the licenses are what we expect. The golden output shows that 51 | # :my_server only uses the unencumbered license type. 52 | 53 | licenses_used( 54 | name = "server_licenses", 55 | out = "server_licenses.json", 56 | deps = [":my_server"], 57 | ) 58 | 59 | py_test( 60 | name = "server_licenses_test", 61 | srcs = ["server_licenses_test.py"], 62 | data = [":server_licenses.json"], 63 | python_version = "PY3", 64 | deps = [ 65 | "@rules_license//tests:license_test_utils", 66 | ], 67 | ) 68 | 69 | # This server uses something under a restricted license 70 | cc_binary( 71 | name = "my_violating_server", 72 | srcs = ["server.cc"], 73 | deps = [ 74 | ":message", 75 | "@rules_license//examples/vndor/acme", 76 | "@rules_license//examples/vndor/libhhgttg", 77 | ], 78 | ) 79 | -------------------------------------------------------------------------------- /examples/src/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/src/server_licenses_test.py: -------------------------------------------------------------------------------- 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 | """Tests for license/examples/src.""" 15 | 16 | import os 17 | 18 | import unittest 19 | from tests import license_test_utils 20 | 21 | 22 | class ServerLicensesTest(unittest.TestCase): 23 | 24 | def test_has_expected_licenses(self): 25 | package_base = license_test_utils.LICENSE_PACKAGE_BASE 26 | licenses_info = license_test_utils.load_licenses_info( 27 | os.path.join(os.path.dirname(__file__), "server_licenses.json")) 28 | licenses_info = license_test_utils.filter_dependencies( 29 | licenses_info, 30 | target_filter=lambda targ: targ.startswith(package_base), 31 | licenses_filter=lambda lic: lic.startswith(package_base)) 32 | 33 | expected = { 34 | "/examples/src:message_src_": [ 35 | "/examples/vndor/constant_gen:license_for_emitted_code" 36 | ], 37 | "/examples/src:message": [ 38 | "/examples/vndor/constant_gen:license_for_emitted_code" 39 | ], 40 | } 41 | license_test_utils.check_licenses_of_dependencies( 42 | self, licenses_info, expected) 43 | 44 | 45 | if __name__ == "__main__": 46 | unittest.main() 47 | -------------------------------------------------------------------------------- /examples/vndor/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 | These examples to not try to address the concerns of organizations 5 | that wish to use license software without first examining the license 6 | and preserving their own copy and audit trail. 7 | -------------------------------------------------------------------------------- /examples/vndor/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 | -------------------------------------------------------------------------------- /examples/vndor/acme/BUILD: -------------------------------------------------------------------------------- 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 | # A package with a commercial license. 15 | 16 | load("@rules_license//rules:license.bzl", "license") 17 | 18 | package( 19 | default_applicable_licenses = [":license"], 20 | default_visibility = ["//examples:__subpackages__"], 21 | ) 22 | 23 | # The default license for an entire package is typically named "license". 24 | license( 25 | name = "license", 26 | license_kinds = [ 27 | "@rules_license//examples/my_org/licenses:acme_corp_paid", 28 | ], 29 | license_text = "ACME_LICENSE", 30 | ) 31 | 32 | cc_library( 33 | name = "acme", 34 | srcs = ["coyote.cc"], 35 | ) 36 | -------------------------------------------------------------------------------- /examples/vndor/acme/coyote.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 | bool caught_road_runner() { 15 | return false; 16 | } 17 | -------------------------------------------------------------------------------- /examples/vndor/constant_gen/BUILD: -------------------------------------------------------------------------------- 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 | # An example of a code generator with a distinct license for the generated code. 15 | 16 | load("@rules_license//rules:license.bzl", "license") 17 | load("@rules_license//sample_reports:licenses_used.bzl", "licenses_used") 18 | load(":defs.bzl", "constant_gen") 19 | 20 | package( 21 | default_applicable_licenses = [":license"], 22 | default_visibility = ["//examples:__subpackages__"], 23 | ) 24 | 25 | # The default license for an entire package is typically named "license". 26 | license( 27 | name = "license", 28 | package_name = "Trivial Code Generator", 29 | license_kinds = [ 30 | "@rules_license//examples/my_org/licenses:generic_restricted", 31 | ], 32 | license_text = "LICENSE", 33 | ) 34 | 35 | license( 36 | name = "license_for_emitted_code", 37 | package_name = "Trivial Code Generator Output", 38 | license_kinds = [ 39 | "@rules_license//examples/my_org/licenses:unencumbered", 40 | ], 41 | license_text = "LICENSE.on_output", 42 | ) 43 | 44 | # The generator itself will be licensed under :license 45 | py_binary( 46 | name = "constant_generator", 47 | srcs = ["constant_generator.py"], 48 | python_version = "PY3", 49 | ) 50 | 51 | # Sample: This target will be licensed under :license_for_emitted_code 52 | constant_gen( 53 | name = "libhello", 54 | text = "Hello, world.", 55 | var = "hello_world", 56 | ) 57 | 58 | # Verify the licenses are what we expect 59 | licenses_used( 60 | name = "generator_licenses", 61 | out = "generator_licenses.json", 62 | deps = [":constant_generator"], 63 | ) 64 | 65 | licenses_used( 66 | name = "generated_code_licenses", 67 | # Note: using default output file name 68 | deps = [":libhello"], 69 | ) 70 | 71 | py_test( 72 | name = "verify_licenses_test", 73 | srcs = ["verify_licenses_test.py"], 74 | data = [ 75 | ":generator_licenses.json", 76 | ":generated_code_licenses.json", 77 | ], 78 | python_version = "PY3", 79 | deps = [ 80 | "//tests:license_test_utils", 81 | ], 82 | ) 83 | -------------------------------------------------------------------------------- /examples/vndor/constant_gen/LICENSE: -------------------------------------------------------------------------------- 1 | This code is provided under a license which contains some restrictions. 2 | -------------------------------------------------------------------------------- /examples/vndor/constant_gen/LICENSE.on_output: -------------------------------------------------------------------------------- 1 | The generated output from constant_gen has no license encumberances. 2 | -------------------------------------------------------------------------------- /examples/vndor/constant_gen/constant_generator.py: -------------------------------------------------------------------------------- 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 | """A trivial tool to turn a string into a C++ constant. 15 | 16 | This is not meant to be useful. It is only to provide an example of a tool that 17 | generates code. 18 | """ 19 | 20 | import sys 21 | 22 | 23 | def main(argv): 24 | if len(argv) != 4: 25 | raise Exception('usage: constant_generator out_file var_name text') 26 | with open(argv[1], 'w') as out: 27 | out.write('const char* %s = "%s";\n' % (argv[2], argv[3])) 28 | 29 | 30 | if __name__ == '__main__': 31 | main(sys.argv) 32 | -------------------------------------------------------------------------------- /examples/vndor/constant_gen/defs.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 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 | """A trivial rule to turn a string into a C++ constant.""" 15 | 16 | def _constant_gen_impl(ctx): 17 | # Turn text into a C++ constant. 18 | outputs = [ctx.outputs.src_out] 19 | ctx.actions.run( 20 | mnemonic = "GenerateConstant", 21 | progress_message = "Generating %s" % ctx.attr.var, 22 | outputs = outputs, 23 | executable = ctx.executable._generator, 24 | arguments = [ctx.outputs.src_out.path, ctx.attr.var, ctx.attr.text], 25 | ) 26 | return [DefaultInfo(files = depset(outputs))] 27 | 28 | _constant_gen = rule( 29 | implementation = _constant_gen_impl, 30 | attrs = { 31 | "src_out": attr.output(mandatory = True), 32 | "text": attr.string(mandatory = True), 33 | "var": attr.string(mandatory = False), 34 | "_generator": attr.label( 35 | default = Label("@rules_license//examples/vndor/constant_gen:constant_generator"), 36 | executable = True, 37 | allow_files = True, 38 | cfg = "exec", 39 | ), 40 | }, 41 | ) 42 | 43 | def constant_gen(name, text, var): 44 | # Generate the code 45 | _constant_gen( 46 | name = name + "_src_", 47 | src_out = name + "_src_.cc", 48 | text = text, 49 | var = var, 50 | applicable_licenses = ["@rules_license//examples/vndor/constant_gen:license_for_emitted_code"], 51 | ) 52 | 53 | # And turn it into a library we can link against 54 | native.cc_library( 55 | name = name, 56 | srcs = [name + "_src_"], 57 | applicable_licenses = ["@rules_license//examples/vndor/constant_gen:license_for_emitted_code"], 58 | ) 59 | -------------------------------------------------------------------------------- /examples/vndor/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/vndor/constant_gen:constant_generator": [ 17 | "examples/vndor/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/vndor/constant_gen:libhello": [ 29 | "/constant_gen:license_for_emitted_code", 30 | ], 31 | "examples/vndor/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 | -------------------------------------------------------------------------------- /examples/vndor/libhhgttg/BUILD: -------------------------------------------------------------------------------- 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 | # A package with all code under a single license. This is the most common case 15 | # we expect to see. 16 | 17 | load("@rules_license//rules:license.bzl", "license") 18 | 19 | # Using a package wide default ensure that all targets are associated with the 20 | # license. 21 | package( 22 | default_applicable_licenses = [":license"], 23 | default_visibility = ["//examples:__subpackages__"], 24 | ) 25 | 26 | # The default license for an entire package is typically named "license". 27 | license( 28 | name = "license", 29 | license_kinds = [ 30 | "@rules_license//examples/my_org/licenses:generic_notice", 31 | ], 32 | license_text = "LICENSE", 33 | ) 34 | 35 | cc_library( 36 | name = "libhhgttg", 37 | srcs = ["answer.cc"], 38 | ) 39 | -------------------------------------------------------------------------------- /examples/vndor/libhhgttg/LICENSE: -------------------------------------------------------------------------------- 1 | You can do whatever you want with this software. Just incude this license 2 | with your distribution. 3 | -------------------------------------------------------------------------------- /examples/vndor/libhhgttg/answer.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 | 15 | int answer = 42; 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | load("@rules_license//rules_gathering:trace.bzl", "trace") 19 | 20 | package( 21 | default_applicable_licenses = ["//:license"], 22 | default_visibility = ["//visibility:public"], 23 | ) 24 | 25 | licenses(["notice"]) 26 | 27 | # This target controls the value of the traced target used during dependency collection. 28 | # This value should always be the empty string! 29 | # Specify this value with a flag, like --@rules_license//rules:trace_target=//target/to:trace 30 | trace( 31 | name = "trace_target", 32 | build_setting_default = "", # TRACE-TARGET-SHOULD-BE-EMPTY 33 | visibility = ["//visibility:public"], 34 | ) 35 | 36 | filegroup( 37 | name = "standard_package", 38 | srcs = glob([ 39 | "**/BUILD", 40 | "**/*.bzl", 41 | ]), 42 | ) 43 | 44 | # Do not create a bzl_library(). That would create a dependency loop back 45 | # to bazel-skylib. We export the .bzl files to the documentation maker. 46 | exports_files( 47 | glob([ 48 | "*.bzl", 49 | ]), 50 | visibility = ["//doc_build:__pkg__"], 51 | ) 52 | -------------------------------------------------------------------------------- /rules/check_licenses_shim.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 | """This module provides a custom Starlark rule used to create wrappers for targets that 15 | can have blaze build --check_licenses executed against them.""" 16 | 17 | def _shim_rule_impl(ctx): 18 | # This rule doesn't need to return anything. It only exists to propagate the dependency supplied 19 | # by the label_flag 20 | return [] 21 | 22 | shim_rule = rule( 23 | doc = """This rule exists to configure a dependent target via label. An instantiation of this 24 | rule is then used as a dependency for the legacy_check_target rule, which can be built with --check_licenses 25 | to get the effect of running --check_licenses on an arbitrary target which may or may not have a distribs 26 | attribute""", 27 | implementation = _shim_rule_impl, 28 | # The definition of this attribute creates a dependency relationship on the manually provided label. 29 | attrs = {"target": attr.label(default = ":check_licenses_target")}, 30 | ) 31 | -------------------------------------------------------------------------------- /rules/compliance.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 | """License compliance checking.""" 15 | 16 | load( 17 | "@rules_license//rules:gather_licenses_info.bzl", 18 | "gather_licenses_info", 19 | "gather_licenses_info_and_write", 20 | "write_licenses_info", 21 | ) 22 | load( 23 | "@rules_license//rules_gathering:gathering_providers.bzl", 24 | "TransitiveLicensesInfo", 25 | ) 26 | 27 | # Forward licenses used until users migrate. Delete at 0.0.7 or 0.1.0. 28 | load( 29 | "@rules_license//sample_reports:licenses_used.bzl", 30 | _licenses_used = "licenses_used", 31 | ) 32 | 33 | licenses_used = _licenses_used 34 | 35 | # This rule is proof of concept, and may not represent the final 36 | # form of a rule for compliance validation. 37 | def _check_license_impl(ctx): 38 | # Gather all licenses and write information to one place 39 | 40 | licenses_file = ctx.actions.declare_file("_%s_licenses_info.json" % ctx.label.name) 41 | write_licenses_info(ctx, ctx.attr.deps, licenses_file) 42 | 43 | license_files = [] 44 | if ctx.outputs.license_texts: 45 | license_files = get_licenses_mapping(ctx.attr.deps).keys() 46 | 47 | # Now run the checker on it 48 | inputs = [licenses_file] 49 | outputs = [ctx.outputs.report] 50 | args = ctx.actions.args() 51 | args.add("--licenses_info", licenses_file.path) 52 | args.add("--report", ctx.outputs.report.path) 53 | if ctx.attr.check_conditions: 54 | args.add("--check_conditions") 55 | if ctx.outputs.copyright_notices: 56 | args.add("--copyright_notices", ctx.outputs.copyright_notices.path) 57 | outputs.append(ctx.outputs.copyright_notices) 58 | if ctx.outputs.license_texts: 59 | args.add("--license_texts", ctx.outputs.license_texts.path) 60 | outputs.append(ctx.outputs.license_texts) 61 | inputs.extend(license_files) 62 | ctx.actions.run( 63 | mnemonic = "CheckLicenses", 64 | progress_message = "Checking license compliance for %s" % ctx.label, 65 | inputs = inputs, 66 | outputs = outputs, 67 | executable = ctx.executable._checker, 68 | arguments = [args], 69 | ) 70 | return [ 71 | DefaultInfo(files = depset(outputs)), 72 | OutputGroupInfo(licenses_file = depset([licenses_file])), 73 | ] 74 | 75 | _check_license = rule( 76 | implementation = _check_license_impl, 77 | attrs = { 78 | "deps": attr.label_list( 79 | aspects = [gather_licenses_info], 80 | ), 81 | "check_conditions": attr.bool(default = True, mandatory = False), 82 | "copyright_notices": attr.output(mandatory = False), 83 | "license_texts": attr.output(mandatory = False), 84 | "report": attr.output(mandatory = True), 85 | "_checker": attr.label( 86 | default = Label("@rules_license//tools:checker_demo"), 87 | executable = True, 88 | allow_files = True, 89 | cfg = "exec", 90 | ), 91 | }, 92 | ) 93 | 94 | # TODO(b/152546336): Update the check to take a pointer to a condition list. 95 | def check_license(**kwargs): 96 | _check_license(**kwargs) 97 | 98 | def _manifest_impl(ctx): 99 | # Gather all licenses and make it available as deps for downstream rules 100 | # Additionally write the list of license filenames to a file that can 101 | # also be used as an input to downstream rules. 102 | licenses_file = ctx.actions.declare_file(ctx.attr.out.name) 103 | mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses) 104 | ctx.actions.write( 105 | output = licenses_file, 106 | content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]), 107 | ) 108 | return [DefaultInfo(files = depset(mappings.keys()))] 109 | 110 | _manifest = rule( 111 | implementation = _manifest_impl, 112 | doc = """Internal tmplementation method for manifest().""", 113 | attrs = { 114 | "deps": attr.label_list( 115 | doc = """List of targets to collect license files for.""", 116 | aspects = [gather_licenses_info], 117 | ), 118 | "out": attr.output( 119 | doc = """Output file.""", 120 | mandatory = True, 121 | ), 122 | "warn_on_legacy_licenses": attr.bool(default = False), 123 | }, 124 | ) 125 | 126 | def manifest(name, deps, out = None, **kwargs): 127 | if not out: 128 | out = name + ".manifest" 129 | 130 | _manifest(name = name, deps = deps, out = out, **kwargs) 131 | 132 | def get_licenses_mapping(deps, warn = False): 133 | """Creates list of entries representing all licenses for the deps. 134 | 135 | Args: 136 | 137 | deps: a list of deps which should have TransitiveLicensesInfo providers. 138 | This requires that you have run the gather_licenses_info 139 | aspect over them 140 | 141 | warn: boolean, if true, display output about legacy targets that need 142 | update 143 | 144 | Returns: 145 | {File:package_name} 146 | """ 147 | tls = [] 148 | for dep in deps: 149 | lds = dep[TransitiveLicensesInfo].licenses 150 | tls.append(lds) 151 | 152 | ds = depset(transitive = tls) 153 | 154 | # Ignore any legacy licenses that may be in the report 155 | mappings = {} 156 | for lic in ds.to_list(): 157 | if type(lic.license_text) == "File": 158 | mappings[lic.license_text] = lic.package_name 159 | elif warn: 160 | print("Legacy license %s not included, rule needs updating" % lic.license_text) 161 | 162 | return mappings 163 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rules/filtered_rule_kinds.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 | 15 | """Filtered rule kinds for aspect inspection. 16 | The format of this dictionary is: 17 | 18 | rule_name: [attr, attr, ...] 19 | 20 | Only filters for rules that are part of the Bazel distribution should be added 21 | to this file. Other filters should be added in user_filtered_rule_kinds.bzl 22 | 23 | Attributes are either the explicit list of attributes to filter, or '_*' which 24 | would ignore all attributes prefixed with a _. 25 | """ 26 | 27 | # Rule kinds with attributes the aspect currently needs to ignore 28 | aspect_filters = { 29 | "*": ["linter"], 30 | "_constant_gen": ["_generator"], 31 | "cc_binary": ["_*"], 32 | "cc_embed_data": ["_*"], 33 | "cc_grpc_library": ["_*"], 34 | "cc_library": ["_*"], 35 | "cc_toolchain_alias": ["_cc_toolchain"], 36 | "genrule": ["tools", "exec_tools", "toolchains"], 37 | "genyacc": ["_*"], 38 | "go_binary": ["_*"], 39 | "go_library": ["_*"], 40 | "go_wrap_cc": ["_*"], 41 | "java_binary": ["_*", "plugins", "exported_plugins"], 42 | "java_library": ["plugins", "exported_plugins"], 43 | "java_wrap_cc": ["_cc_toolchain", "swig_top"], 44 | "py_binary": ["_*"], 45 | "py_extension": ["_cc_toolchain"], 46 | "sh_binary": ["_bash_binary"], 47 | } 48 | -------------------------------------------------------------------------------- /rules/gather_licenses_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 and macros for collecting LicenseInfo providers.""" 15 | 16 | load( 17 | "@rules_license//rules:licenses_core.bzl", 18 | "gather_metadata_info_common", 19 | "should_traverse", 20 | ) 21 | load( 22 | "@rules_license//rules_gathering:gathering_providers.bzl", 23 | "TransitiveLicensesInfo", 24 | ) 25 | load("@rules_license//rules_gathering:trace.bzl", "TraceInfo") 26 | 27 | def _strip_null_repo(label): 28 | """Removes the null repo name (e.g. @//) from a string. 29 | 30 | The is to make str(label) compatible between bazel 5.x and 6.x 31 | """ 32 | s = str(label) 33 | if s.startswith('@//'): 34 | return s[1:] 35 | elif s.startswith('@@//'): 36 | return s[2:] 37 | return s 38 | 39 | def _gather_licenses_info_impl(target, ctx): 40 | return gather_metadata_info_common(target, ctx, TransitiveLicensesInfo, [], should_traverse) 41 | 42 | gather_licenses_info = aspect( 43 | doc = """Collects LicenseInfo providers into a single TransitiveLicensesInfo provider.""", 44 | implementation = _gather_licenses_info_impl, 45 | attr_aspects = ["*"], 46 | attrs = { 47 | "_trace": attr.label(default = "@rules_license//rules:trace_target"), 48 | }, 49 | provides = [TransitiveLicensesInfo], 50 | apply_to_generating_rules = True, 51 | ) 52 | 53 | def _write_licenses_info_impl(target, ctx): 54 | """Write transitive license info into a JSON file 55 | 56 | Args: 57 | target: The target of the aspect. 58 | ctx: The aspect evaluation context. 59 | 60 | Returns: 61 | OutputGroupInfo 62 | """ 63 | 64 | if not TransitiveLicensesInfo in target: 65 | return [OutputGroupInfo(licenses = depset())] 66 | info = target[TransitiveLicensesInfo] 67 | outs = [] 68 | 69 | # If the result doesn't contain licenses, we simply return the provider 70 | if not hasattr(info, "target_under_license"): 71 | return [OutputGroupInfo(licenses = depset())] 72 | 73 | # Write the output file for the target 74 | name = "%s_licenses_info.json" % ctx.label.name 75 | lic_info, _ = licenses_info_to_json(info) 76 | content = "[\n%s\n]\n" % ",\n".join(lic_info) 77 | out = ctx.actions.declare_file(name) 78 | ctx.actions.write( 79 | output = out, 80 | content = content, 81 | ) 82 | outs.append(out) 83 | 84 | if ctx.attr._trace[TraceInfo].trace: 85 | trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name) 86 | ctx.actions.write(output = trace, content = "\n".join(info.traces)) 87 | outs.append(trace) 88 | 89 | return [OutputGroupInfo(licenses = depset(outs))] 90 | 91 | gather_licenses_info_and_write = aspect( 92 | doc = """Collects TransitiveLicensesInfo providers and writes JSON representation to a file. 93 | 94 | Usage: 95 | blaze build //some:target \ 96 | --aspects=@rules_license//rules:gather_licenses_info.bzl%gather_licenses_info_and_write 97 | --output_groups=licenses 98 | """, 99 | implementation = _write_licenses_info_impl, 100 | attr_aspects = ["*"], 101 | attrs = { 102 | "_trace": attr.label(default = "@rules_license//rules:trace_target"), 103 | }, 104 | provides = [OutputGroupInfo], 105 | requires = [gather_licenses_info], 106 | apply_to_generating_rules = True, 107 | ) 108 | 109 | def write_licenses_info(ctx, deps, json_out): 110 | """Writes TransitiveLicensesInfo providers for a set of targets as JSON. 111 | 112 | TODO(aiuto): Document JSON schema. But it is under development, so the current 113 | best place to look is at tests/hello_licenses.golden. 114 | 115 | Usage: 116 | write_licenses_info must be called from a rule implementation, where the 117 | rule has run the gather_licenses_info aspect on its deps to 118 | collect the transitive closure of LicenseInfo providers into a 119 | LicenseInfo provider. 120 | 121 | foo = rule( 122 | implementation = _foo_impl, 123 | attrs = { 124 | "deps": attr.label_list(aspects = [gather_licenses_info]) 125 | } 126 | ) 127 | 128 | def _foo_impl(ctx): 129 | ... 130 | json_file = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name) 131 | license_files = write_licenses_info(ctx, ctx.attr.deps, json_file) 132 | 133 | // process the json file and the license_files referenced by it 134 | ctx.actions.run( 135 | inputs = [json_file] + license_files 136 | executable = ... 137 | ) 138 | 139 | Args: 140 | ctx: context of the caller 141 | deps: a list of deps which should have TransitiveLicensesInfo providers. 142 | This requires that you have run the gather_licenses_info 143 | aspect over them 144 | json_out: output handle to write the JSON info 145 | 146 | Returns: 147 | A list of License File objects for each of the license paths referenced in the json. 148 | """ 149 | licenses_json = [] 150 | licenses_files = [] 151 | for dep in deps: 152 | if TransitiveLicensesInfo in dep: 153 | transitive_licenses_info = dep[TransitiveLicensesInfo] 154 | lic_info, _ = licenses_info_to_json(transitive_licenses_info) 155 | licenses_json.extend(lic_info) 156 | for info in transitive_licenses_info.licenses.to_list(): 157 | if info.license_text: 158 | licenses_files.append(info.license_text) 159 | 160 | ctx.actions.write( 161 | output = json_out, 162 | content = "[\n%s\n]\n" % ",\n".join(licenses_json), 163 | ) 164 | return licenses_files 165 | 166 | def licenses_info_to_json(licenses_info): 167 | """Render a single LicenseInfo provider to JSON 168 | 169 | Args: 170 | licenses_info: A LicenseInfo. 171 | 172 | Returns: 173 | [(str)] list of LicenseInfo values rendered as JSON. 174 | [(File)] list of Files containing license texts. 175 | """ 176 | 177 | main_template = """ {{ 178 | "top_level_target": "{top_level_target}", 179 | "dependencies": [{dependencies} 180 | ], 181 | "licenses": [{licenses} 182 | ]\n }}""" 183 | 184 | dep_template = """ 185 | {{ 186 | "target_under_license": "{target_under_license}", 187 | "licenses": [ 188 | {licenses} 189 | ] 190 | }}""" 191 | 192 | # TODO(aiuto): 'rule' is a duplicate of 'label' until old users are transitioned 193 | license_template = """ 194 | {{ 195 | "label": "{label}", 196 | "rule": "{label}", 197 | "license_kinds": [{kinds} 198 | ], 199 | "copyright_notice": "{copyright_notice}", 200 | "package_name": "{package_name}", 201 | "package_url": "{package_url}", 202 | "package_version": "{package_version}", 203 | "license_text": "{license_text}", 204 | "used_by": [ 205 | {used_by} 206 | ] 207 | }}""" 208 | 209 | kind_template = """ 210 | {{ 211 | "target": "{kind_path}", 212 | "name": "{kind_name}", 213 | "long_name": "{kind_long_name}", 214 | "conditions": {kind_conditions} 215 | }}""" 216 | 217 | # Build reverse map of license to user 218 | used_by = {} 219 | for dep in licenses_info.deps.to_list(): 220 | # Undo the concatenation applied when stored in the provider. 221 | dep_licenses = dep.licenses.split(",") 222 | for license in dep_licenses: 223 | if license not in used_by: 224 | used_by[license] = [] 225 | used_by[license].append(_strip_null_repo(dep.target_under_license)) 226 | 227 | all_licenses = [] 228 | all_license_text_files = [] 229 | for license in sorted(licenses_info.licenses.to_list(), key = lambda x: x.label): 230 | kinds = [] 231 | for kind in sorted(license.license_kinds, key = lambda x: x.name): 232 | if hasattr(kind, "long_name"): 233 | long_name = kind.long_name 234 | else: 235 | long_name = "" 236 | kinds.append(kind_template.format( 237 | kind_name = kind.name, 238 | kind_long_name = long_name, 239 | kind_path = kind.label, 240 | kind_conditions = kind.conditions, 241 | )) 242 | 243 | if license.license_text: 244 | # Special handling for synthetic LicenseInfo 245 | text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path) 246 | all_licenses.append(license_template.format( 247 | copyright_notice = license.copyright_notice, 248 | kinds = ",".join(kinds), 249 | license_text = text_path, 250 | package_name = license.package_name, 251 | package_url = license.package_url, 252 | package_version = license.package_version, 253 | label = _strip_null_repo(license.label), 254 | used_by = ",\n ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])), 255 | )) 256 | # Additionally return all File references so that other rules invoking 257 | # this method can load license text file contents from external repos 258 | # using runfiles 259 | all_license_text_files.append(license.license_text) 260 | all_deps = [] 261 | for dep in sorted(licenses_info.deps.to_list(), key = lambda x: x.target_under_license): 262 | # Undo the concatenation applied when stored in the provider. 263 | dep_licenses = dep.licenses.split(",") 264 | all_deps.append(dep_template.format( 265 | target_under_license = _strip_null_repo(dep.target_under_license), 266 | licenses = ",\n ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])), 267 | )) 268 | 269 | return [main_template.format( 270 | top_level_target = _strip_null_repo(licenses_info.target_under_license), 271 | dependencies = ",".join(all_deps), 272 | licenses = ",".join(all_licenses), 273 | )], all_license_text_files 274 | -------------------------------------------------------------------------------- /rules/gather_metadata.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2023 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 | """Forwarder for gather_metadata_info. 15 | 16 | To be deleted before version 0.1.0 17 | """ 18 | load( 19 | "@rules_license//rules_gathering:gather_metadata.bzl", 20 | _gather_metadata_info = "gather_metadata_info", 21 | _gather_metadata_info_and_write = "gather_metadata_info_and_write", 22 | ) 23 | 24 | gather_metadata_info = _gather_metadata_info 25 | gather_metadata_info_and_write = _gather_metadata_info_and_write 26 | -------------------------------------------------------------------------------- /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:providers.bzl", 20 | "LicenseKindInfo", 21 | ) 22 | load( 23 | "@rules_license//rules:license_impl.bzl", 24 | "license_rule_impl", 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 | -------------------------------------------------------------------------------- /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( 19 | "@rules_license//rules:providers.bzl", 20 | "LicenseInfo", 21 | "LicenseKindInfo", 22 | ) 23 | 24 | # Debugging verbosity 25 | _VERBOSITY = 0 26 | 27 | def _debug(loglevel, msg): 28 | if _VERBOSITY > loglevel: 29 | print(msg) # buildifier: disable=print 30 | 31 | # 32 | # license() 33 | # 34 | 35 | def license_rule_impl(ctx): 36 | provider = LicenseInfo( 37 | license_kinds = tuple([k[LicenseKindInfo] for k in ctx.attr.license_kinds]), 38 | copyright_notice = ctx.attr.copyright_notice, 39 | package_name = ctx.attr.package_name or ctx.label.package, 40 | package_url = ctx.attr.package_url, 41 | package_version = ctx.attr.package_version, 42 | license_text = ctx.file.license_text, 43 | label = ctx.label, 44 | ) 45 | _debug(0, provider) 46 | return [provider] 47 | -------------------------------------------------------------------------------- /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("@rules_license//rules:providers.bzl", "LicenseKindInfo") 17 | 18 | # 19 | # License Kind: The declaration of a well known category of license, for example, 20 | # Apache, MIT, LGPL v2. An organization may also declare its own license kinds 21 | # that it may user privately. 22 | # 23 | 24 | def _license_kind_impl(ctx): 25 | provider = LicenseKindInfo( 26 | name = ctx.attr.name, 27 | label = "@%s//%s:%s" % ( 28 | ctx.label.workspace_name, 29 | ctx.label.package, 30 | ctx.label.name, 31 | ), 32 | long_name = ctx.attr.long_name, 33 | conditions = ctx.attr.conditions, 34 | ) 35 | return [provider] 36 | 37 | _license_kind = rule( 38 | implementation = _license_kind_impl, 39 | attrs = { 40 | "conditions": attr.string_list( 41 | doc = "Conditions to be met when using software under this license." + 42 | " Conditions are defined by the organization using this license.", 43 | mandatory = True, 44 | ), 45 | "canonical_text": attr.label( 46 | doc = "File containing the canonical text for this license. Must be UTF-8 encoded.", 47 | allow_single_file = True, 48 | ), 49 | "long_name": attr.string(doc = "Human readable long name of license."), 50 | "url": attr.string(doc = "URL pointing to canonical license definition"), 51 | }, 52 | ) 53 | 54 | def license_kind(name, **kwargs): 55 | """Wrapper for license_kind. 56 | 57 | @wraps(_license_kind) 58 | """ 59 | if "conditions" not in kwargs: 60 | kwargs["conditions"] = [] 61 | if "long_name" not in kwargs: 62 | kwargs["long_name"] = name 63 | _license_kind( 64 | name = name, 65 | applicable_licenses = [], 66 | **kwargs 67 | ) 68 | -------------------------------------------------------------------------------- /rules/licenses_core.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 LicenseInfo providers.""" 15 | 16 | load("@rules_license//rules:filtered_rule_kinds.bzl", "aspect_filters") 17 | load("@rules_license//rules:providers.bzl", "LicenseInfo") 18 | load("@rules_license//rules:user_filtered_rule_kinds.bzl", "user_aspect_filters") 19 | load( 20 | "@rules_license//rules_gathering:gathering_providers.bzl", 21 | "LicensedTargetInfo", 22 | "TransitiveLicensesInfo", 23 | ) 24 | load("@rules_license//rules_gathering:trace.bzl", "TraceInfo") 25 | 26 | def should_traverse(ctx, attr): 27 | """Checks if the dependent attribute should be traversed. 28 | 29 | Args: 30 | ctx: The aspect evaluation context. 31 | attr: The name of the attribute to be checked. 32 | 33 | Returns: 34 | True iff the attribute should be traversed. 35 | """ 36 | k = ctx.rule.kind 37 | 38 | for filters in [aspect_filters, user_aspect_filters]: 39 | always_ignored = filters.get("*", []) 40 | if k in filters: 41 | attr_matches = filters[k] 42 | if (attr in attr_matches or 43 | "*" in attr_matches or 44 | ("_*" in attr_matches and attr.startswith("_")) or 45 | attr in always_ignored): 46 | return False 47 | 48 | for m in attr_matches: 49 | if attr == m: 50 | return False 51 | 52 | return True 53 | 54 | def _get_transitive_metadata(ctx, trans_licenses, trans_other_metadata, trans_package_info, trans_deps, traces, provider, filter_func): 55 | attrs = [a for a in dir(ctx.rule.attr)] 56 | for name in attrs: 57 | if not filter_func(ctx, name): 58 | continue 59 | a = getattr(ctx.rule.attr, name) 60 | 61 | # Make anything singleton into a list for convenience. 62 | if type(a) != type([]): 63 | a = [a] 64 | for dep in a: 65 | # Ignore anything that isn't a target 66 | if type(dep) != "Target": 67 | continue 68 | 69 | # Targets can also include things like input files that won't have the 70 | # aspect, so we additionally check for the aspect rather than assume 71 | # it's on all targets. Even some regular targets may be synthetic and 72 | # not have the aspect. This provides protection against those outlier 73 | # cases. 74 | if provider in dep: 75 | info = dep[provider] 76 | if info.licenses: 77 | trans_licenses.append(info.licenses) 78 | if info.deps: 79 | trans_deps.append(info.deps) 80 | if info.traces: 81 | for trace in info.traces: 82 | traces.append("(" + ", ".join([str(ctx.label), ctx.rule.kind, name]) + ") -> " + trace) 83 | 84 | # We only need one or the other of these stanzas. 85 | # If we use a polymorphic approach to metadata providers, then 86 | # this works. 87 | if hasattr(info, "other_metadata"): 88 | if info.other_metadata: 89 | trans_other_metadata.append(info.other_metadata) 90 | 91 | # But if we want more precise type safety, we would have a 92 | # trans_* for each type of metadata. That is not user 93 | # extensibile. 94 | if hasattr(info, "package_info"): 95 | if info.package_info: 96 | trans_package_info.append(info.package_info) 97 | 98 | def gather_metadata_info_common(target, ctx, provider_factory, metadata_providers, filter_func): 99 | """Collect license and other metadata info from myself and my deps. 100 | 101 | Any single target might directly depend on a license, or depend on 102 | something that transitively depends on a license, or neither. 103 | This aspect bundles all those into a single provider. At each level, we add 104 | in new direct license deps found and forward up the transitive information 105 | collected so far. 106 | 107 | This is a common abstraction for crawling the dependency graph. It is 108 | parameterized to allow specifying the provider that is populated with 109 | results. It is configurable to select only a subset of providers. It 110 | is also configurable to specify which dependency edges should not 111 | be traced for the purpose of tracing the graph. 112 | 113 | Args: 114 | target: The target of the aspect. 115 | ctx: The aspect evaluation context. 116 | provider_factory: abstracts the provider returned by this aspect 117 | metadata_providers: a list of other providers of interest 118 | filter_func: a function that returns true iff the dep edge should be ignored 119 | 120 | Returns: 121 | provider of parameterized type 122 | """ 123 | 124 | # First we gather my direct license attachments 125 | licenses = [] 126 | other_metadata = [] 127 | package_info = [] 128 | if ctx.rule.kind == "_license": 129 | # Don't try to gather licenses from the license rule itself. We'll just 130 | # blunder into the text file of the license and pick up the default 131 | # attribute of the package, which we don't want. 132 | pass 133 | else: 134 | if hasattr(ctx.rule.attr, "applicable_licenses"): 135 | package_metadata = ctx.rule.attr.applicable_licenses 136 | elif hasattr(ctx.rule.attr, "package_metadata"): 137 | package_metadata = ctx.rule.attr.package_metadata 138 | else: 139 | package_metadata = [] 140 | 141 | for dep in package_metadata: 142 | if LicenseInfo in dep: 143 | lic = dep[LicenseInfo] 144 | licenses.append(lic) 145 | 146 | for m_p in metadata_providers: 147 | if m_p in dep: 148 | other_metadata.append(dep[m_p]) 149 | 150 | # A hack until https://github.com/bazelbuild/rules_license/issues/89 is 151 | # fully resolved. If exec is in the bin_dir path, then the current 152 | # configuration is probably cfg = exec. 153 | if "-exec-" in ctx.bin_dir.path: 154 | return [provider_factory( 155 | target_under_license = target.label, 156 | deps = depset(), 157 | licenses = depset(), 158 | traces = [], 159 | )] 160 | 161 | # Now gather transitive collection of providers from the targets 162 | # this target depends upon. 163 | trans_licenses = [] 164 | trans_other_metadata = [] 165 | trans_package_info = [] 166 | trans_deps = [] 167 | traces = [] 168 | 169 | _get_transitive_metadata(ctx, trans_licenses, trans_other_metadata, trans_package_info, trans_deps, traces, provider_factory, filter_func) 170 | 171 | if not licenses and not trans_licenses: 172 | return [provider_factory( 173 | target_under_license = target.label, 174 | deps = depset(), 175 | licenses = depset(), 176 | traces = [], 177 | )] 178 | 179 | # If this is the target, start the sequence of traces. 180 | if ctx.attr._trace[TraceInfo].trace and ctx.attr._trace[TraceInfo].trace in str(ctx.label): 181 | traces = [ctx.attr._trace[TraceInfo].trace] 182 | 183 | # Trim the number of traces accumulated since the output can be quite large. 184 | # A few representative traces are generally sufficient to identify why a dependency 185 | # is incorrectly incorporated. 186 | if len(traces) > 10: 187 | traces = traces[0:10] 188 | 189 | if licenses: 190 | # At this point we have a target and a list of directly used licenses. 191 | # Bundle those together so we can report the exact targets that cause the 192 | # dependency on each license. Since a list cannot be stored in a 193 | # depset, even inside a provider, the list is concatenated into a 194 | # string and will be unconcatenated in the output phase. 195 | direct_license_uses = [LicensedTargetInfo( 196 | target_under_license = target.label, 197 | licenses = ",".join([str(x.label) for x in licenses]), 198 | )] 199 | else: 200 | direct_license_uses = None 201 | 202 | # This is a bit of a hack for bazel 5.x. We can not pass extra fields to 203 | # the provider constructor, so we need to do something special for each. 204 | # In Bazel 6.x we can use a provider initializer function that would take 205 | # all the args and only use the ones it wants. 206 | if provider_factory == TransitiveLicensesInfo: 207 | return [provider_factory( 208 | target_under_license = target.label, 209 | licenses = depset(tuple(licenses), transitive = trans_licenses), 210 | deps = depset(direct = direct_license_uses, transitive = trans_deps), 211 | traces = traces, 212 | )] 213 | 214 | return [provider_factory( 215 | target_under_license = target.label, 216 | licenses = depset(tuple(licenses), transitive = trans_licenses), 217 | other_metadata = depset(tuple(other_metadata), transitive = trans_other_metadata), 218 | deps = depset(direct = direct_license_uses, transitive = trans_deps), 219 | traces = traces, 220 | )] 221 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rules/private/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2023 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 making license declarations.""" 15 | 16 | package( 17 | default_applicable_licenses = ["//:license"], 18 | default_visibility = ["//visibility:public"], 19 | ) 20 | 21 | licenses(["notice"]) 22 | 23 | filegroup( 24 | name = "standard_package", 25 | srcs = glob(["**"]), 26 | ) 27 | 28 | # Do not create a bzl_library(). That would create a dependency loop back 29 | # to bazel-skylib. We export the .bzl files to the documentation maker. 30 | exports_files( 31 | glob([ 32 | "*.bzl", 33 | ]), 34 | visibility = ["//doc_build:__pkg__"], 35 | ) 36 | -------------------------------------------------------------------------------- /rules/private/gathering_providers.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2023 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 | """Temporary forwarder. 15 | 16 | TODO(2023-07-01): Delete this file. 17 | """ 18 | 19 | load( 20 | "@rules_license//rules_gathering:gathering_providers.bzl", 21 | _private_TransitiveLicensesInfo = "TransitiveLicensesInfo", 22 | ) 23 | 24 | TransitiveLicensesInfo = _private_TransitiveLicensesInfo 25 | -------------------------------------------------------------------------------- /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 | load( 22 | "@rules_license//rules_gathering:gathering_providers.bzl", 23 | _private_TransitiveLicensesInfo = "TransitiveLicensesInfo", 24 | ) 25 | 26 | LicenseKindInfo = provider( 27 | doc = """Provides information about a license_kind instance.""", 28 | fields = { 29 | "conditions": "list(string): List of conditions to be met when using this packages under this license.", 30 | "label": "Label: The full path to the license kind definition.", 31 | "long_name": "string: Human readable license name", 32 | "name": "string: Canonical license name", 33 | }, 34 | ) 35 | 36 | LicenseInfo = provider( 37 | doc = """Provides information about a license instance.""", 38 | fields = { 39 | "copyright_notice": "string: Human readable short copyright notice", 40 | "label": "Label: label of the license rule", 41 | "license_kinds": "list(LicenseKindInfo): License kinds ", 42 | "license_text": "string: The license file path", 43 | # TODO(aiuto): move to PackageInfo 44 | "package_name": "string: Human readable package name", 45 | "package_url": "URL from which this package was downloaded.", 46 | "package_version": "Human readable version string", 47 | }, 48 | ) 49 | 50 | PackageInfo = provider( 51 | doc = """Provides information about a package.""", 52 | fields = { 53 | "type": "string: How to interpret data", 54 | "label": "Label: label of the package_info rule", 55 | "package_name": "string: Human readable package name", 56 | "package_url": "string: URL from which this package was downloaded.", 57 | "package_version": "string: Human readable version string", 58 | "purl": "string: package url matching the purl spec (https://github.com/package-url/purl-spec)", 59 | }, 60 | ) 61 | 62 | # This is more extensible. Because of the provider implementation, having a big 63 | # dict of values rather than named fields is not much more costly. 64 | # Design choice. Replace data with actual providers, such as PackageInfo 65 | ExperimentalMetadataInfo = provider( 66 | doc = """Generic bag of metadata.""", 67 | fields = { 68 | "type": "string: How to interpret data", 69 | "label": "Label: label of the metadata rule", 70 | "data": "String->any: Map of names to values", 71 | }, 72 | ) 73 | 74 | # Deprecated: Use write_licenses_info instead. 75 | TransitiveLicensesInfo = _private_TransitiveLicensesInfo 76 | -------------------------------------------------------------------------------- /rules/sbom.bzl: -------------------------------------------------------------------------------- 1 | ../rules_gathering/generate_sbom.bzl -------------------------------------------------------------------------------- /rules/user_filtered_rule_kinds.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 | """Filtered rule kinds for aspect inspection. 15 | 16 | The format of this dictionary is: 17 | rule_name: [attr, attr, ...] 18 | 19 | Filters for rules that are not part of the Bazel distribution should be added 20 | to this file. 21 | 22 | Attributes are either the explicit list of attributes to filter, or '_*' which 23 | would ignore all attributes prefixed with a _. 24 | """ 25 | 26 | # Rule kinds with attributes the aspect currently needs to ignore 27 | user_aspect_filters = { 28 | } 29 | -------------------------------------------------------------------------------- /rules_gathering/BUILD: -------------------------------------------------------------------------------- 1 | # Copyright 2023 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 making license declarations.""" 15 | 16 | package( 17 | default_applicable_licenses = ["//:license"], 18 | default_visibility = ["//visibility:public"], 19 | ) 20 | 21 | filegroup( 22 | name = "standard_package", 23 | srcs = glob(["**"]), 24 | ) 25 | 26 | # Do not create a bzl_library(). That would create a dependency loop back 27 | # to bazel-skylib. We export the .bzl files to the documentation maker. 28 | exports_files( 29 | glob([ 30 | "*.bzl", 31 | ]), 32 | visibility = ["//doc_build:__pkg__"], 33 | ) 34 | -------------------------------------------------------------------------------- /rules_gathering/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 | `LicenseInfo` or similar providers specified by `default_package_metadata` 5 | and `applicable_licenses`. 6 | 7 | 8 | 9 | # Known issues: 10 | 11 | - [exports_files() is not included](https://github.com/bazelbuild/rules_license/issues/107) 12 | -------------------------------------------------------------------------------- /rules_gathering/gather_metadata.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 LicenseInfo providers.""" 15 | 16 | load( 17 | "@rules_license//rules:licenses_core.bzl", 18 | "gather_metadata_info_common", 19 | "should_traverse", 20 | ) 21 | load( 22 | "@rules_license//rules:providers.bzl", 23 | "ExperimentalMetadataInfo", 24 | "PackageInfo", 25 | ) 26 | load( 27 | "@rules_license//rules_gathering:gathering_providers.bzl", 28 | "TransitiveMetadataInfo", 29 | ) 30 | load("@rules_license//rules_gathering:trace.bzl", "TraceInfo") 31 | 32 | def _strip_null_repo(label): 33 | """Removes the null repo name (e.g. @//) from a string. 34 | 35 | The is to make str(label) compatible between bazel 5.x and 6.x 36 | """ 37 | s = str(label) 38 | if s.startswith('@//'): 39 | return s[1:] 40 | elif s.startswith('@@//'): 41 | return s[2:] 42 | return s 43 | 44 | def _bazel_package(label): 45 | clean_label = _strip_null_repo(label) 46 | return clean_label[0:-(len(label.name) + 1)] 47 | 48 | def _gather_metadata_info_impl(target, ctx): 49 | return gather_metadata_info_common( 50 | target, 51 | ctx, 52 | TransitiveMetadataInfo, 53 | [ExperimentalMetadataInfo, PackageInfo], 54 | should_traverse) 55 | 56 | gather_metadata_info = aspect( 57 | doc = """Collects LicenseInfo providers into a single TransitiveMetadataInfo provider.""", 58 | implementation = _gather_metadata_info_impl, 59 | attr_aspects = ["*"], 60 | attrs = { 61 | "_trace": attr.label(default = "@rules_license//rules:trace_target"), 62 | }, 63 | provides = [TransitiveMetadataInfo], 64 | apply_to_generating_rules = True, 65 | ) 66 | 67 | def _write_metadata_info_impl(target, ctx): 68 | """Write transitive license info into a JSON file 69 | 70 | Args: 71 | target: The target of the aspect. 72 | ctx: The aspect evaluation context. 73 | 74 | Returns: 75 | OutputGroupInfo 76 | """ 77 | 78 | if not TransitiveMetadataInfo in target: 79 | return [OutputGroupInfo(licenses = depset())] 80 | info = target[TransitiveMetadataInfo] 81 | outs = [] 82 | 83 | # If the result doesn't contain licenses, we simply return the provider 84 | if not hasattr(info, "target_under_license"): 85 | return [OutputGroupInfo(licenses = depset())] 86 | 87 | # Write the output file for the target 88 | name = "%s_metadata_info.json" % ctx.label.name 89 | content = "[\n%s\n]\n" % ",\n".join(metadata_info_to_json(info)) 90 | out = ctx.actions.declare_file(name) 91 | ctx.actions.write( 92 | output = out, 93 | content = content, 94 | ) 95 | outs.append(out) 96 | 97 | if ctx.attr._trace[TraceInfo].trace: 98 | trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name) 99 | ctx.actions.write(output = trace, content = "\n".join(info.traces)) 100 | outs.append(trace) 101 | 102 | return [OutputGroupInfo(licenses = depset(outs))] 103 | 104 | gather_metadata_info_and_write = aspect( 105 | doc = """Collects TransitiveMetadataInfo providers and writes JSON representation to a file. 106 | 107 | Usage: 108 | bazel build //some:target \ 109 | --aspects=@rules_license//rules_gathering:gather_metadata.bzl%gather_metadata_info_and_write 110 | --output_groups=licenses 111 | """, 112 | implementation = _write_metadata_info_impl, 113 | attr_aspects = ["*"], 114 | attrs = { 115 | "_trace": attr.label(default = "@rules_license//rules:trace_target"), 116 | }, 117 | provides = [OutputGroupInfo], 118 | requires = [gather_metadata_info], 119 | apply_to_generating_rules = True, 120 | ) 121 | 122 | def write_metadata_info(ctx, deps, json_out): 123 | """Writes TransitiveMetadataInfo providers for a set of targets as JSON. 124 | 125 | TODO(aiuto): Document JSON schema. But it is under development, so the current 126 | best place to look is at tests/hello_licenses.golden. 127 | 128 | Usage: 129 | write_metadata_info must be called from a rule implementation, where the 130 | rule has run the gather_metadata_info aspect on its deps to 131 | collect the transitive closure of LicenseInfo providers into a 132 | LicenseInfo provider. 133 | 134 | foo = rule( 135 | implementation = _foo_impl, 136 | attrs = { 137 | "deps": attr.label_list(aspects = [gather_metadata_info]) 138 | } 139 | ) 140 | 141 | def _foo_impl(ctx): 142 | ... 143 | out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name) 144 | write_metadata_info(ctx, ctx.attr.deps, metadata_file) 145 | 146 | Args: 147 | ctx: context of the caller 148 | deps: a list of deps which should have TransitiveMetadataInfo providers. 149 | This requires that you have run the gather_metadata_info 150 | aspect over them 151 | json_out: output handle to write the JSON info 152 | """ 153 | licenses = [] 154 | for dep in deps: 155 | if TransitiveMetadataInfo in dep: 156 | licenses.extend(metadata_info_to_json(dep[TransitiveMetadataInfo])) 157 | ctx.actions.write( 158 | output = json_out, 159 | content = "[\n%s\n]\n" % ",\n".join(licenses), 160 | ) 161 | 162 | def metadata_info_to_json(metadata_info): 163 | """Render a single LicenseInfo provider to JSON 164 | 165 | Args: 166 | metadata_info: A LicenseInfo. 167 | 168 | Returns: 169 | [(str)] list of LicenseInfo values rendered as JSON. 170 | """ 171 | 172 | main_template = """ {{ 173 | "top_level_target": "{top_level_target}", 174 | "dependencies": [{dependencies} 175 | ], 176 | "licenses": [{licenses} 177 | ], 178 | "packages": [{packages} 179 | ]\n }}""" 180 | 181 | dep_template = """ 182 | {{ 183 | "target_under_license": "{target_under_license}", 184 | "licenses": [ 185 | {licenses} 186 | ] 187 | }}""" 188 | 189 | license_template = """ 190 | {{ 191 | "label": "{label}", 192 | "bazel_package": "{bazel_package}", 193 | "license_kinds": [{kinds} 194 | ], 195 | "copyright_notice": "{copyright_notice}", 196 | "package_name": "{package_name}", 197 | "package_url": "{package_url}", 198 | "package_version": "{package_version}", 199 | "license_text": "{license_text}", 200 | "used_by": [ 201 | {used_by} 202 | ] 203 | }}""" 204 | 205 | kind_template = """ 206 | {{ 207 | "target": "{kind_path}", 208 | "name": "{kind_name}", 209 | "conditions": {kind_conditions} 210 | }}""" 211 | 212 | package_info_template = """ 213 | {{ 214 | "target": "{label}", 215 | "bazel_package": "{bazel_package}", 216 | "package_name": "{package_name}", 217 | "package_url": "{package_url}", 218 | "package_version": "{package_version}", 219 | "purl": "{purl}" 220 | }}""" 221 | 222 | # Build reverse map of license to user 223 | used_by = {} 224 | for dep in metadata_info.deps.to_list(): 225 | # Undo the concatenation applied when stored in the provider. 226 | dep_licenses = dep.licenses.split(",") 227 | for license in dep_licenses: 228 | if license not in used_by: 229 | used_by[license] = [] 230 | used_by[license].append(_strip_null_repo(dep.target_under_license)) 231 | 232 | all_licenses = [] 233 | for license in sorted(metadata_info.licenses.to_list(), key = lambda x: x.label): 234 | kinds = [] 235 | for kind in sorted(license.license_kinds, key = lambda x: x.name): 236 | kinds.append(kind_template.format( 237 | kind_name = kind.name, 238 | kind_path = kind.label, 239 | kind_conditions = kind.conditions, 240 | )) 241 | 242 | if license.license_text: 243 | # Special handling for synthetic LicenseInfo 244 | text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path) 245 | all_licenses.append(license_template.format( 246 | copyright_notice = license.copyright_notice, 247 | kinds = ",".join(kinds), 248 | license_text = text_path, 249 | package_name = license.package_name, 250 | package_url = license.package_url, 251 | package_version = license.package_version, 252 | label = _strip_null_repo(license.label), 253 | bazel_package = _bazel_package(license.label), 254 | used_by = ",\n ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])), 255 | )) 256 | 257 | all_deps = [] 258 | for dep in sorted(metadata_info.deps.to_list(), key = lambda x: x.target_under_license): 259 | # Undo the concatenation applied when stored in the provider. 260 | dep_licenses = dep.licenses.split(",") 261 | all_deps.append(dep_template.format( 262 | target_under_license = _strip_null_repo(dep.target_under_license), 263 | licenses = ",\n ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])), 264 | )) 265 | 266 | all_packages = [] 267 | # We would use this if we had distinct depsets for every provider type. 268 | #for package in sorted(metadata_info.package_info.to_list(), key = lambda x: x.label): 269 | # all_packages.append(package_info_template.format( 270 | # label = _strip_null_repo(package.label), 271 | # package_name = package.package_name, 272 | # package_url = package.package_url, 273 | # package_version = package.package_version, 274 | # )) 275 | 276 | for mi in sorted(metadata_info.other_metadata.to_list(), key = lambda x: x.label): 277 | # Maybe use a map of provider class to formatter. A generic dict->json function 278 | # in starlark would help 279 | 280 | # This format is for using distinct providers. I like the compile time safety. 281 | if mi.type == "package_info": 282 | all_packages.append(package_info_template.format( 283 | label = _strip_null_repo(mi.label), 284 | bazel_package = _bazel_package(mi.label), 285 | package_name = mi.package_name, 286 | package_url = mi.package_url, 287 | package_version = mi.package_version, 288 | purl = mi.purl, 289 | )) 290 | # experimental: Support the ExperimentalMetadataInfo bag of data 291 | # WARNING: Do not depend on this. It will change without notice. 292 | if mi.type == "package_info_alt": 293 | all_packages.append(package_info_template.format( 294 | label = _strip_null_repo(mi.label), 295 | bazel_package = _bazel_package(mi.label), 296 | # data is just a bag, so we need to use get() or "" 297 | package_name = mi.data.get("package_name") or "", 298 | package_url = mi.data.get("package_url") or "", 299 | package_version = mi.data.get("package_version") or "", 300 | purl = mi.data.get("purl") or "", 301 | )) 302 | 303 | return [main_template.format( 304 | top_level_target = _strip_null_repo(metadata_info.target_under_license), 305 | dependencies = ",".join(all_deps), 306 | licenses = ",".join(all_licenses), 307 | packages = ",".join(all_packages), 308 | )] 309 | -------------------------------------------------------------------------------- /rules_gathering/gathering_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 | """Providers for transitively gathering all license and package_info targets. 15 | 16 | Warning: This is private to the aspect that walks the tree. The API is subject 17 | to change at any release. 18 | """ 19 | 20 | LicensedTargetInfo = provider( 21 | doc = """Lists the licenses directly used by a single target.""", 22 | fields = { 23 | "target_under_license": "Label: The target label", 24 | "licenses": "list(label of a license rule)", 25 | }, 26 | ) 27 | 28 | def licenses_info(): 29 | return provider( 30 | doc = """The transitive set of licenses used by a target.""", 31 | fields = { 32 | "target_under_license": "Label: The top level target label.", 33 | "deps": "depset(LicensedTargetInfo): The transitive list of dependencies that have licenses.", 34 | "licenses": "depset(LicenseInfo)", 35 | "traces": "list(string) - diagnostic for tracing a dependency relationship to a target.", 36 | }, 37 | ) 38 | 39 | # This provider is used by the aspect that is used by manifest() rules. 40 | TransitiveLicensesInfo = licenses_info() 41 | 42 | TransitiveMetadataInfo = provider( 43 | doc = """The transitive set of licenses used by a target.""", 44 | fields = { 45 | "top_level_target": "Label: The top level target label we are examining.", 46 | "other_metadata": "depset(ExperimentalMetatdataInfo)", 47 | "licenses": "depset(LicenseInfo)", 48 | "package_info": "depset(PackageInfo)", 49 | 50 | "target_under_license": "Label: A target which will be associated with some licenses.", 51 | "deps": "depset(LicensedTargetInfo): The transitive list of dependencies that have licenses.", 52 | "traces": "list(string) - diagnostic for tracing a dependency relationship to a target.", 53 | }, 54 | ) 55 | -------------------------------------------------------------------------------- /rules_gathering/generate_sbom.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 | """SBOM generation""" 15 | 16 | load( 17 | "@rules_license//rules_gathering:gather_metadata.bzl", 18 | "gather_metadata_info", 19 | "gather_metadata_info_and_write", 20 | "write_metadata_info", 21 | ) 22 | load( 23 | "@rules_license//rules_gathering:gathering_providers.bzl", 24 | "TransitiveLicensesInfo", 25 | ) 26 | 27 | # This rule is proof of concept, and may not represent the final 28 | # form of a rule for compliance validation. 29 | def _generate_sbom_impl(ctx): 30 | # Gather all licenses and write information to one place 31 | 32 | licenses_file = ctx.actions.declare_file("_%s_licenses_info.json" % ctx.label.name) 33 | write_metadata_info(ctx, ctx.attr.deps, licenses_file) 34 | 35 | # Now turn the big blob of data into something consumable. 36 | inputs = [licenses_file] 37 | outputs = [ctx.outputs.out] 38 | args = ctx.actions.args() 39 | args.add("--licenses_info", licenses_file.path) 40 | args.add("--out", ctx.outputs.out.path) 41 | ctx.actions.run( 42 | mnemonic = "CreateSBOM", 43 | progress_message = "Creating SBOM for %s" % ctx.label, 44 | inputs = inputs, 45 | outputs = outputs, 46 | executable = ctx.executable._sbom_generator, 47 | arguments = [args], 48 | ) 49 | return [ 50 | DefaultInfo(files = depset(outputs)), 51 | OutputGroupInfo(licenses_file = depset([licenses_file])), 52 | ] 53 | 54 | _generate_sbom = rule( 55 | implementation = _generate_sbom_impl, 56 | attrs = { 57 | "deps": attr.label_list( 58 | aspects = [gather_metadata_info], 59 | ), 60 | "out": attr.output(mandatory = True), 61 | "_sbom_generator": attr.label( 62 | default = Label("@rules_license//tools:write_sbom"), 63 | executable = True, 64 | allow_files = True, 65 | cfg = "exec", 66 | ), 67 | }, 68 | ) 69 | 70 | def generate_sbom(**kwargs): 71 | _generate_sbom(**kwargs) 72 | 73 | def _manifest_impl(ctx): 74 | # Gather all licenses and make it available as deps for downstream rules 75 | # Additionally write the list of license filenames to a file that can 76 | # also be used as an input to downstream rules. 77 | licenses_file = ctx.actions.declare_file(ctx.attr.out.name) 78 | mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses) 79 | ctx.actions.write( 80 | output = licenses_file, 81 | content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]), 82 | ) 83 | return [DefaultInfo(files = depset(mappings.keys()))] 84 | 85 | _manifest = rule( 86 | implementation = _manifest_impl, 87 | doc = """Internal tmplementation method for manifest().""", 88 | attrs = { 89 | "deps": attr.label_list( 90 | doc = """List of targets to collect license files for.""", 91 | aspects = [gather_metadata_info], 92 | ), 93 | "out": attr.output( 94 | doc = """Output file.""", 95 | mandatory = True, 96 | ), 97 | "warn_on_legacy_licenses": attr.bool(default = False), 98 | }, 99 | ) 100 | 101 | def manifest(name, deps, out = None, **kwargs): 102 | if not out: 103 | out = name + ".manifest" 104 | 105 | _manifest(name = name, deps = deps, out = out, **kwargs) 106 | 107 | def get_licenses_mapping(deps, warn = False): 108 | """Creates list of entries representing all licenses for the deps. 109 | 110 | Args: 111 | 112 | deps: a list of deps which should have TransitiveLicensesInfo providers. 113 | This requires that you have run the gather_licenses_info 114 | aspect over them 115 | 116 | warn: boolean, if true, display output about legacy targets that need 117 | update 118 | 119 | Returns: 120 | {File:package_name} 121 | """ 122 | tls = [] 123 | for dep in deps: 124 | lds = dep[TransitiveLicensesInfo].licenses 125 | tls.append(lds) 126 | 127 | ds = depset(transitive = tls) 128 | 129 | # Ignore any legacy licenses that may be in the report 130 | mappings = {} 131 | for lic in ds.to_list(): 132 | if type(lic.license_text) == "File": 133 | mappings[lic.license_text] = lic.package_name 134 | elif warn: 135 | # buildifier: disable=print 136 | print("Legacy license %s not included, rule needs updating" % lic.license_text) 137 | 138 | return mappings 139 | -------------------------------------------------------------------------------- /rules_gathering/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 | -------------------------------------------------------------------------------- /sample_reports/BUILD: -------------------------------------------------------------------------------- 1 | # BUILD file defining reference implementations for reporting tools 2 | # 3 | # Copyright 2023 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 | 23 | licenses(["notice"]) 24 | 25 | filegroup( 26 | name = "standard_package", 27 | srcs = glob(["**"]), 28 | ) 29 | 30 | # Do not create a bzl_library(). That would create a dependency loop back 31 | # to bazel-skylib. We export the .bzl files to the documentation maker. 32 | exports_files( 33 | glob([ 34 | "*.bzl", 35 | ]), 36 | visibility = ["//doc_build:__pkg__"], 37 | ) 38 | -------------------------------------------------------------------------------- /sample_reports/licenses_used.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 | """License compliance checking.""" 15 | 16 | load( 17 | "@rules_license//rules:gather_licenses_info.bzl", 18 | "gather_licenses_info", 19 | "write_licenses_info", 20 | ) 21 | 22 | def _licenses_used_impl(ctx): 23 | # Gather all licenses and make it available as JSON 24 | write_licenses_info(ctx, ctx.attr.deps, ctx.outputs.out) 25 | return [DefaultInfo(files = depset([ctx.outputs.out]))] 26 | 27 | _licenses_used = rule( 28 | implementation = _licenses_used_impl, 29 | doc = """Internal tmplementation method for licenses_used().""", 30 | attrs = { 31 | "deps": attr.label_list( 32 | doc = """List of targets to collect LicenseInfo for.""", 33 | aspects = [gather_licenses_info], 34 | ), 35 | "out": attr.output( 36 | doc = """Output file.""", 37 | mandatory = True, 38 | ), 39 | }, 40 | ) 41 | 42 | def licenses_used(name, deps, out = None, **kwargs): 43 | """Collects LicensedInfo providers for a set of targets and writes as JSON. 44 | 45 | The output is a single JSON array, with an entry for each license used. 46 | See gather_licenses_info.bzl:write_licenses_info() for a description of the schema. 47 | 48 | Args: 49 | name: The target. 50 | deps: A list of targets to get LicenseInfo for. The output is the union of 51 | the result, not a list of information for each dependency. 52 | out: The output file name. Default: .json. 53 | **kwargs: Other args 54 | 55 | Usage: 56 | 57 | licenses_used( 58 | name = "license_info", 59 | deps = [":my_app"], 60 | out = "license_info.json", 61 | ) 62 | """ 63 | if not out: 64 | out = name + ".json" 65 | _licenses_used(name = name, deps = deps, out = out, **kwargs) 66 | -------------------------------------------------------------------------------- /tests/BUILD: -------------------------------------------------------------------------------- 1 | # Test cases for license rules. 2 | 3 | load("@rules_license//rules:compliance.bzl", "check_license") 4 | load("@rules_license//rules:license.bzl", "license") 5 | load("@rules_license//rules:license_kind.bzl", "license_kind") 6 | load("@rules_license//sample_reports:licenses_used.bzl", "licenses_used") 7 | 8 | package( 9 | default_applicable_licenses = [":license"], 10 | default_visibility = [ 11 | "//examples:__subpackages__", 12 | "//tests:__subpackages__", 13 | ], 14 | ) 15 | 16 | # license_kind rules generally appear in a central location per workspace. They 17 | # are intermingled with normal target build rules 18 | license_kind( 19 | name = "generic_notice_license", 20 | conditions = [ 21 | "notice", 22 | ], 23 | ) 24 | 25 | license_kind( 26 | name = "generic_restricted_license", 27 | conditions = [ 28 | "restricted", 29 | ], 30 | ) 31 | 32 | # The default license for an entire package is typically named "license". 33 | license( 34 | name = "license", 35 | package_name = "A test case package", 36 | # Note the UTF-8 encoded copyright symbol. 37 | copyright_notice = "Copyright © 2019 Uncle Toasty", 38 | license_kinds = [":generic_notice_license"], 39 | # Note. This need not be precise. If a downloader creates the license 40 | # clause for you, then it should use the absolute download URL. 41 | package_url = "http://github.com/bazelbuild/rules_license", 42 | package_version = "0.0.4", 43 | ) 44 | 45 | license( 46 | name = "license_for_extra_feature", 47 | package_name = "A test case package", 48 | license_kinds = [":generic_restricted_license"], 49 | license_text = "LICENSE.extra", 50 | ) 51 | 52 | license( 53 | name = "license_without_file", 54 | package_name = "A test case package", 55 | license_kinds = ["//licenses/spdx:Apache-2.0"], 56 | license_text = None, 57 | ) 58 | 59 | cc_binary( 60 | name = "hello", 61 | srcs = ["hello.cc"], 62 | deps = [ 63 | ":c_bar", 64 | ], 65 | ) 66 | 67 | cc_library( 68 | name = "c_bar", 69 | srcs = [ 70 | "bar.cc", 71 | ], 72 | applicable_licenses = [ 73 | ":license", 74 | ":license_for_extra_feature", 75 | ], 76 | deps = [ 77 | "@rules_license//tests/legacy:another_library_with_legacy_license_clause", 78 | "@rules_license//tests/legacy:library_with_legacy_license_clause", 79 | ], 80 | ) 81 | 82 | java_binary( 83 | name = "hello_java", 84 | srcs = ["Hello.java"], 85 | # Add an addition license to this target, beyond what my deps have. 86 | applicable_licenses = [ 87 | ":license_for_extra_feature", 88 | ], 89 | javacopts = ["-Xep:DefaultPackage:OFF"], 90 | main_class = "Hello", 91 | deps = [ 92 | ":j_bar", 93 | ], 94 | ) 95 | 96 | java_library( 97 | name = "j_bar", 98 | srcs = ["Bar.java"], 99 | applicable_licenses = [ 100 | ":license_without_file", 101 | ], 102 | javacopts = ["-Xep:DefaultPackage:OFF"], 103 | ) 104 | 105 | check_license( 106 | name = "check_cc_app", 107 | check_conditions = False, 108 | copyright_notices = "hello_cc_copyrights.txt", 109 | license_texts = "hello_cc_licenses.txt", 110 | report = "hello_cc_report", 111 | deps = [ 112 | ":hello", 113 | ], 114 | ) 115 | 116 | licenses_used( 117 | name = "hello_licenses", 118 | out = "hello_licenses.json", 119 | deps = [":hello"], 120 | ) 121 | 122 | py_test( 123 | name = "hello_licenses_test", 124 | srcs = ["hello_licenses_test.py"], 125 | data = [ 126 | ":hello_cc_copyrights.txt", 127 | ":hello_licenses.json", 128 | ], 129 | python_version = "PY3", 130 | deps = [ 131 | ":license_test_utils", 132 | ], 133 | ) 134 | 135 | py_library( 136 | name = "license_test_utils", 137 | srcs = ["license_test_utils.py"], 138 | srcs_version = "PY3", 139 | ) 140 | 141 | check_license( 142 | name = "check_java_app", 143 | check_conditions = False, 144 | copyright_notices = "hello_java_copyrights.txt", 145 | license_texts = "hello_java_licenses.txt", 146 | report = "hello_java_report", 147 | deps = [ 148 | ":hello_java", 149 | ], 150 | ) 151 | 152 | license( 153 | name = "license_with_generated_text", 154 | license_kinds = [":generic_notice_license"], 155 | license_text = ":created_license", 156 | ) 157 | 158 | genrule( 159 | name = "created_license", 160 | outs = ["something.text"], 161 | cmd = "echo hello >$@", 162 | ) 163 | -------------------------------------------------------------------------------- /tests/Bar.java: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | 15 | /** 16 | * A java class. 17 | */ 18 | 19 | package rules_license.tests; 20 | 21 | final class Bar { 22 | 23 | static final int PI = 3; 24 | 25 | private Bar() { 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tests/Hello.java: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | 15 | /** 16 | * A java main. 17 | */ 18 | 19 | package rules_license.tests; 20 | 21 | final class Hello { 22 | 23 | private Hello() { 24 | } 25 | 26 | int whatIsPi() { 27 | return Bar.PI; 28 | } 29 | 30 | public static void main(String[] args) { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2019 Uncle Toasty 2 | 3 | Example of a notice style license. 4 | 5 | You may use this software in any way as long as you include the copyright 6 | notice above. 7 | -------------------------------------------------------------------------------- /tests/LICENSE.extra: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 The authors of this software 2 | 3 | A license for an extra feature to a package. 4 | For example, you might have most of the package with a notice license, 5 | but an additional feature is added under a restricted license. 6 | -------------------------------------------------------------------------------- /tests/apps/BUILD: -------------------------------------------------------------------------------- 1 | # Test cases for license rules: Sample app 2 | 3 | load("@rules_license//sample_reports:licenses_used.bzl", "licenses_used") 4 | load("@rules_python//python:defs.bzl", "py_test") 5 | 6 | package(default_visibility = ["//examples:__subpackages__"]) 7 | 8 | # Note that the app explicitly depends only on a library and some legacy 9 | # style licensed code. 10 | cc_binary( 11 | name = "an_app", 12 | srcs = ["an_app.cc"], 13 | deps = [ 14 | ":level4", 15 | # "@rules_license//rules/tests/legacy:another_library_with_legacy_license_clause", 16 | # "@rules_license//rules/tests/legacy:library_with_legacy_license_clause", 17 | ], 18 | ) 19 | 20 | # pointless chain of libraries to show transitive rule gathering, culminating 21 | # in a diamond dependency on a library under license. 22 | # Note that the lowest level depends on some third party code 23 | [ 24 | genrule( 25 | name = "level_%d_src" % level, 26 | outs = ["level_%d.cc" % level], 27 | # Note to reviewers: This should use string format, but format 28 | # is broken when 29 | cmd = """cat >$@ < 31 | extern void {lower}(); 32 | void lib_level_{level}() {{ 33 | std::cout << "This is level {level}" << std::endl; 34 | {lower}(); 35 | }} 36 | END 37 | """.format( 38 | level = level, 39 | lower = "lib_level_%d" % (level - 1) if level > 0 else "new_lib_func", 40 | ), 41 | ) 42 | for level in range(5) 43 | ] 44 | 45 | [ 46 | cc_library( 47 | name = "level%d" % level, 48 | srcs = [":level_%d.cc" % level], 49 | deps = [ 50 | (":level%d" % (level - 1) if level > 0 else "@rules_license//tests/thrdparty:new_style_lib"), 51 | ], 52 | ) 53 | for level in range(5) 54 | ] 55 | 56 | licenses_used( 57 | name = "an_app_licenses", 58 | out = "an_app_licenses.json", 59 | deps = [":an_app"], 60 | ) 61 | 62 | # Examining the golden file shows that we depend on both kinds of license. 63 | py_test( 64 | name = "an_app_licenses_test", 65 | srcs = ["an_app_licenses_test.py"], 66 | data = [":an_app_licenses.json"], 67 | python_version = "PY3", 68 | deps = [ 69 | "@rules_license//tests:license_test_utils", 70 | ], 71 | ) 72 | -------------------------------------------------------------------------------- /tests/apps/an_app.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 | extern void lib_level_4(); 19 | 20 | int main(int argc, char* argv[]) { 21 | std::cout << "main" << std::endl; 22 | lib_level_4(); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tests/apps/an_app_licenses_test.py: -------------------------------------------------------------------------------- 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 | 15 | import os 16 | 17 | import unittest 18 | from tests import license_test_utils 19 | 20 | 21 | class AnAppLicensesTest(unittest.TestCase): 22 | 23 | def test_has_expected_licenses(self): 24 | package_base = license_test_utils.LICENSE_PACKAGE_BASE 25 | licenses_info = license_test_utils.load_licenses_info( 26 | os.path.join(os.path.dirname(__file__), "an_app_licenses.json")) 27 | licenses_info = license_test_utils.filter_dependencies( 28 | licenses_info, 29 | target_filter=lambda targ: targ.startswith(package_base), 30 | licenses_filter=lambda lic: lic.startswith(package_base)) 31 | 32 | expected = { 33 | "/tests/thrdparty:new_style_lib": [ 34 | "/tests/thrdparty:license", 35 | ], 36 | } 37 | license_test_utils.check_licenses_of_dependencies( 38 | self, licenses_info, expected) 39 | 40 | 41 | if __name__ == "__main__": 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/bar.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | 15 | 16 | 17 | int bar() { 18 | return 42; 19 | } 20 | -------------------------------------------------------------------------------- /tests/current_module_package_info/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load(":starlark_tests.bzl", "starlark_tests") 2 | 3 | starlark_tests( 4 | name = "starlark_tests" 5 | ) -------------------------------------------------------------------------------- /tests/current_module_package_info/starlark_tests.bzl: -------------------------------------------------------------------------------- 1 | load("//rules:current_module_package_info.bzl", "current_module_package_info") 2 | load("//rules:providers.bzl", "PackageInfo") 3 | load("//:version.bzl", "version") 4 | load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") 5 | load("@rules_testing//lib:truth.bzl", "subjects") 6 | 7 | def _test_current_module_package_info(name): 8 | current_module_package_info( 9 | name = name + "_subject", 10 | ) 11 | analysis_test( 12 | name = name, 13 | impl = _test_current_module_package_info_impl, 14 | target = name + "_subject", 15 | provider_subject_factories = [_package_info_subject_factory], 16 | ) 17 | 18 | def _test_current_module_package_info_impl(env, target): 19 | env.expect.that_target(target).has_provider(PackageInfo) 20 | subject = env.expect.that_target(target).provider(PackageInfo) 21 | subject.package_name().equals("rules_license") 22 | subject.package_url().equals("https://bcr.bazel.build/modules/rules_license/{}/source.json".format(version)) 23 | subject.package_version().equals(version) 24 | subject.purl().equals("pkg:bazel/rules_license@{}".format(version)) 25 | 26 | def _test_current_module_package_info_custom_registry(name): 27 | current_module_package_info( 28 | name = name + "_subject", 29 | registry = "https://example.com/registry/", 30 | ) 31 | analysis_test( 32 | name = name, 33 | impl = _test_current_module_package_info_custom_registry_impl, 34 | target = name + "_subject", 35 | provider_subject_factories = [_package_info_subject_factory], 36 | ) 37 | 38 | def _test_current_module_package_info_custom_registry_impl(env, target): 39 | env.expect.that_target(target).has_provider(PackageInfo) 40 | subject = env.expect.that_target(target).provider(PackageInfo) 41 | subject.package_name().equals("rules_license") 42 | subject.package_url().equals("https://example.com/registry/modules/rules_license/{}/source.json".format(version)) 43 | subject.package_version().equals(version) 44 | subject.purl().equals("pkg:bazel/rules_license@{}?repository_url=https://example.com/registry".format(version)) 45 | 46 | _package_info_subject_factory = struct( 47 | type = PackageInfo, 48 | name = "PackageInfo", 49 | factory = lambda actual, *, meta: subjects.struct( 50 | actual, 51 | meta = meta, 52 | attrs = { 53 | "package_name": subjects.str, 54 | "package_url": subjects.str, 55 | "package_version": subjects.str, 56 | "purl": subjects.str, 57 | }, 58 | ), 59 | ) 60 | 61 | def starlark_tests(name): 62 | test_suite( 63 | name = name, 64 | tests = [ 65 | _test_current_module_package_info, 66 | _test_current_module_package_info_custom_registry, 67 | ], 68 | ) -------------------------------------------------------------------------------- /tests/hello.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | 15 | 16 | #include 17 | 18 | int main(int argc, char* argv[]) { 19 | std::cout << "Hello foo." << std::endl; 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/hello_licenses_test.py: -------------------------------------------------------------------------------- 1 | """Tests for google3.tools.build_defs.license.tests.hello_licenses.""" 2 | 3 | import codecs 4 | import os 5 | 6 | import unittest 7 | from tests import license_test_utils 8 | 9 | 10 | class HelloLicensesTest(unittest.TestCase): 11 | 12 | def test_has_expected_licenses(self): 13 | licenses_info = license_test_utils.load_licenses_info( 14 | os.path.join(os.path.dirname(__file__), "hello_licenses.json")) 15 | 16 | expected = { 17 | "/tests:hello": [ 18 | "/tests:license", 19 | ], 20 | "/tests:c_bar": [ 21 | "/tests:license", 22 | "/tests:license_for_extra_feature", 23 | ], 24 | } 25 | license_test_utils.check_licenses_of_dependencies( 26 | self, licenses_info, expected) 27 | 28 | def test_has_expected_copyrights(self): 29 | copyrights_file = os.path.join(os.path.dirname(__file__), 30 | "hello_cc_copyrights.txt") 31 | with codecs.open(copyrights_file, encoding="utf-8") as inp: 32 | copyrights = inp.read().split('\n') 33 | self.assertIn( 34 | "package(A test case package/0.0.4), copyright(Copyright © 2019 Uncle Toasty)", 35 | copyrights) 36 | self.assertIn( 37 | "package(A test case package), copyright()", 38 | copyrights) 39 | 40 | 41 | if __name__ == "__main__": 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/legacy/BUILD: -------------------------------------------------------------------------------- 1 | # Example of an unmigrated package. 2 | 3 | package(default_visibility = [ 4 | "//tests:__subpackages__", 5 | ]) 6 | 7 | licenses(["unencumbered"]) 8 | 9 | cc_library( 10 | name = "library_with_legacy_license_clause", 11 | srcs = ["file_under_notice.cc"], 12 | ) 13 | 14 | cc_library( 15 | name = "another_library_with_legacy_license_clause", 16 | srcs = ["file_under_notice.cc"], 17 | ) 18 | -------------------------------------------------------------------------------- /tests/legacy/file_under_notice.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 | int file_under_notice = 1; 15 | -------------------------------------------------------------------------------- /tests/license_test_utils.py: -------------------------------------------------------------------------------- 1 | """Utilities for writing tests of license rules.""" 2 | 3 | import codecs 4 | import json 5 | 6 | # This is extracted out to make it easier to keep test equivalence between 7 | # the OSS version and Google. 8 | LICENSE_PACKAGE_BASE = "/" 9 | 10 | 11 | def load_licenses_info(info_path): 12 | """Loads the licenses_info() JSON format.""" 13 | with codecs.open(info_path, encoding="utf-8") as licenses_file: 14 | return json.loads(licenses_file.read()) 15 | 16 | 17 | def filter_dependencies(licenses_info, target_filter=None, 18 | licenses_filter=None): 19 | """Filters licenses_info to only include dependencies of interest. 20 | 21 | Args: 22 | licenses_info: (dict) licenses info. 23 | target_filter: (function): function which returns true if we should include 24 | the target. 25 | licenses_filter: (function): function which returns true if we should 26 | include the license. 27 | Returns: 28 | (dict) a valid licenses_info dict. 29 | """ 30 | top_target = licenses_info[0] 31 | new_top_target = dict(top_target) 32 | new_deps = [] 33 | for dep in top_target["dependencies"]: 34 | target_name = dep["target_under_license"] 35 | if target_filter and not target_filter(target_name): 36 | continue 37 | licenses = dep["licenses"] 38 | if licenses_filter: 39 | licenses = [lic for lic in licenses if licenses_filter(lic)] 40 | new_deps.append({ 41 | "target_under_license": target_name, 42 | "licenses": licenses}) 43 | new_top_target["dependencies"] = new_deps 44 | return [new_top_target] 45 | 46 | 47 | def check_licenses_of_dependencies(test_case, licenses_info, expected, 48 | path_prefix=LICENSE_PACKAGE_BASE): 49 | """Checks that licenses_info contains an expected set of licenses. 50 | 51 | Args: 52 | test_case: (TestCase) the test. 53 | licenses_info: (dict) licenses info. 54 | expected: (dict) map of target name suffixes to the licenses they are under. 55 | path_prefix: (str) prefix to prepend to targets and licenses in expected. 56 | This turns the relative target names to absolute ones. 57 | """ 58 | 59 | # Turn the list of deps into a dict by target for easier comparison. 60 | deps_to_licenses = { 61 | x["target_under_license"].lstrip("@"): set(l.strip("@") for l in x["licenses"]) 62 | for x in licenses_info[0]["dependencies"]} 63 | 64 | target_names = ",".join(deps_to_licenses.keys()) 65 | # This is n**2, but N is typically < 3 or we are doing this wrong. 66 | for want_target, want_licenses in expected.items(): 67 | found_target = False 68 | for got_target, got_licenses in deps_to_licenses.items(): 69 | if got_target.endswith(want_target): 70 | found_target = True 71 | test_case.assertEqual(len(want_licenses), len(got_licenses)) 72 | found_license = False 73 | for want_l in want_licenses: 74 | for got_l in got_licenses: 75 | if got_l.endswith(want_l): 76 | found_license = True 77 | break 78 | test_case.assertTrue( 79 | found_license, 80 | msg="license (%s) not a suffix in %s" % (want_l, got_licenses)) 81 | break 82 | test_case.assertTrue( 83 | found_target, 84 | msg="target (%s) not a suffix in [%s]" % (want_target, target_names)) 85 | -------------------------------------------------------------------------------- /tests/thrdparty/BUILD: -------------------------------------------------------------------------------- 1 | # A sample library using new license rules. 2 | 3 | load("@rules_license//rules:license.bzl", "license") 4 | 5 | package( 6 | default_applicable_licenses = [":license"], 7 | default_visibility = [ 8 | "//examples:__subpackages__", 9 | "//tests:__subpackages__", 10 | ], 11 | ) 12 | 13 | # The default license for an entire package is typically named "license". 14 | license( 15 | name = "license", 16 | package_name = "migrated package", 17 | license_kinds = ["//licenses/generic:restricted"], 18 | license_text = "LICENSE", 19 | ) 20 | 21 | cc_library( 22 | name = "new_style_lib", 23 | srcs = [ 24 | "new_style_lib.cc", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /tests/thrdparty/LICENSE: -------------------------------------------------------------------------------- 1 | I am restricted in some way. 2 | -------------------------------------------------------------------------------- /tests/thrdparty/new_style_lib.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 | 15 | #include 16 | #include 17 | 18 | void new_lib_func() { 19 | std::cout << "This is restricted code" << std::endl; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /tools/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 declaration and compliance checking tools.""" 16 | 17 | load("@rules_python//python:defs.bzl", "py_binary", "py_library") 18 | 19 | package( 20 | default_applicable_licenses = ["//:license", "//:package_info"], 21 | default_visibility = ["//visibility:public"], 22 | ) 23 | 24 | licenses(["notice"]) 25 | 26 | filegroup( 27 | name = "standard_package", 28 | srcs = glob(["**"]), 29 | visibility = ["//distro:__pkg__"], 30 | ) 31 | 32 | exports_files(["diff_test.sh"]) 33 | 34 | py_binary( 35 | name = "checker_demo", 36 | srcs = ["checker_demo.py"], 37 | python_version = "PY3", 38 | visibility = ["//visibility:public"], 39 | ) 40 | 41 | py_library( 42 | name = "sbom_lib", 43 | srcs = ["sbom.py"], 44 | visibility = ["//visibility:public"], 45 | ) 46 | 47 | py_binary( 48 | name = "write_sbom", 49 | srcs = ["write_sbom.py"], 50 | deps = [":sbom_lib"], 51 | python_version = "PY3", 52 | visibility = ["//visibility:public"], 53 | ) 54 | 55 | py_binary( 56 | name = "write_workspace_sbom", 57 | srcs = ["write_workspace_sbom.py"], 58 | deps = [":sbom_lib"], 59 | python_version = "PY3", 60 | visibility = ["//visibility:public"], 61 | ) 62 | -------------------------------------------------------------------------------- /tools/checker_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2020 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Proof of concept license checker. 17 | 18 | This is only a demonstration. It will be replaced with other tools. 19 | """ 20 | 21 | import argparse 22 | import codecs 23 | import json 24 | 25 | # Conditions allowed for all applications 26 | _ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumbered']) 27 | 28 | 29 | def _load_license_data(licenses_info): 30 | with codecs.open(licenses_info, encoding='utf-8') as licenses_file: 31 | return json.loads(licenses_file.read()) 32 | 33 | 34 | def _do_report(out, licenses): 35 | """Produce a report showing the set of licenses being used. 36 | 37 | Args: 38 | out: file object to write to 39 | licenses: list of LicenseInfo objects 40 | 41 | Returns: 42 | 0 for no restricted licenses. 43 | """ 44 | 45 | for lic in licenses: 46 | print("lic:", lic) 47 | rule = lic['rule'] 48 | for kind in lic['license_kinds']: 49 | out.write('= %s\n kind: %s\n' % (rule, kind['target'])) 50 | out.write(' conditions: %s\n' % kind['conditions']) 51 | 52 | 53 | def _check_conditions(out, licenses, allowed_conditions): 54 | """Check that the application does not use any disallowed licenses. 55 | 56 | Args: 57 | out: file object to write to 58 | licenses: list of LicenseInfo objects 59 | allowed_conditions: list of allowed condition names 60 | 61 | Returns: 62 | 0 for no licenses from outside allowed_conditions. 63 | """ 64 | err = 0 65 | for lic in licenses: # using strange name lic because license is built-in 66 | rule = lic['rule'] 67 | for kind in lic['license_kinds']: 68 | disallowed = [] 69 | for condition in kind['conditions']: 70 | if condition not in allowed_conditions: 71 | disallowed.append(condition) 72 | if disallowed: 73 | out.write('ERROR: %s\n' % rule) 74 | out.write(' kind: %s\n' % kind['target']) 75 | out.write(' conditions: %s\n' % kind['conditions']) 76 | out.write(' disallowed condition: %s\n' % ','.join(disallowed)) 77 | err += 1 78 | return err 79 | 80 | 81 | def _do_copyright_notices(out, licenses): 82 | for l in licenses: 83 | name = l.get('package_name') or '' 84 | if l.get('package_version'): 85 | name = name + "/" + l['package_version'] 86 | # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. 87 | out.write('package(%s), copyright(%s)\n' % (name, l['copyright_notice'])) 88 | 89 | 90 | def _do_licenses(out, licenses): 91 | for lic in licenses: 92 | path = lic['license_text'] 93 | with codecs.open(path, encoding='utf-8') as license_file: 94 | out.write('= %s\n' % path) 95 | out.write(license_file.read()) 96 | 97 | 98 | def main(): 99 | parser = argparse.ArgumentParser( 100 | description='Demonstraton license compliance checker') 101 | 102 | parser.add_argument('--licenses_info', 103 | help='path to JSON file containing all license data') 104 | parser.add_argument('--report', default='report', help='Summary report') 105 | parser.add_argument('--copyright_notices', 106 | help='output file of all copyright notices') 107 | parser.add_argument('--license_texts', help='output file of all license files') 108 | parser.add_argument('--check_conditions', action='store_true', 109 | help='check that the dep only includes allowed license conditions') 110 | args = parser.parse_args() 111 | 112 | license_data = _load_license_data(args.licenses_info) 113 | target = license_data[0] # we assume only one target for the demo 114 | 115 | top_level_target = target['top_level_target'] 116 | dependencies = target['dependencies'] 117 | licenses = target['licenses'] 118 | 119 | err = 0 120 | with codecs.open(args.report, mode='w', encoding='utf-8') as rpt: 121 | _do_report(rpt, licenses) 122 | if args.check_conditions: 123 | # TODO(aiuto): Read conditions from a file of allowed conditions for 124 | # a specified application deployment environment. 125 | err = _check_conditions(rpt, licenses, _ALWAYS_ALLOWED_CONDITIONS) 126 | if args.copyright_notices: 127 | with codecs.open( 128 | args.copyright_notices, mode='w', encoding='utf-8') as out: 129 | _do_copyright_notices(out, licenses) 130 | if args.license_texts: 131 | with codecs.open(args.license_texts, mode='w', encoding='utf-8') as out: 132 | _do_licenses(out, licenses) 133 | return err 134 | 135 | 136 | if __name__ == '__main__': 137 | main() 138 | -------------------------------------------------------------------------------- /tools/diff_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # Diff two files and PASS if they are equal, FAIL otherwise. 17 | # Usage: 18 | # diff_test.sh path_to_expected path_to_got 19 | 20 | # Find input files 21 | declare -r EXPECTED="$1" 22 | declare -r GOT="$2" 23 | 24 | diff_out=$(mktemp /tmp/diff_test.XXXXXXXXX) 25 | diff -cB "$EXPECTED" "$GOT" >"$diff_out" 26 | err=0 27 | if [[ -s "$diff_out" ]] ; then 28 | cat "$diff_out" 29 | err=1 30 | echo 'To update:' 31 | echo ' cp bazel-bin/'"$GOT" "$EXPECTED" 32 | echo FAIL 33 | else 34 | echo PASS 35 | fi 36 | exit $err 37 | -------------------------------------------------------------------------------- /tools/sbom.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import getpass 3 | import json 4 | 5 | 6 | class SBOMWriter: 7 | def __init__(self, tool, out): 8 | self.out = out 9 | self.tool = tool 10 | 11 | def write_header(self, package): 12 | header = [ 13 | 'SPDXVersion: SPDX-2.2', 14 | 'DataLicense: CC0-1.0', 15 | 'SPDXID: SPDXRef-DOCUMENT', 16 | 'DocumentName: %s' % package, 17 | # TBD 18 | # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3 19 | 'Creator: Person: %s' % getpass.getuser(), 20 | 'Creator: Tool: %s' % self.tool, 21 | datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'), 22 | '', 23 | '##### Package: %s' % package, 24 | ] 25 | self.out.write('\n'.join(header)) 26 | 27 | def write_packages(self, packages): 28 | for p in packages: 29 | name = p.get('package_name') or '' 30 | self.out.write('\n') 31 | self.out.write('SPDXID: "%s"\n' % name) 32 | self.out.write(' name: "%s"\n' % name) 33 | 34 | if p.get('package_version'): 35 | self.out.write(' versionInfo: "%s"\n' % p['package_version']) 36 | 37 | # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. 38 | cn = p.get('copyright_notice') 39 | if cn: 40 | self.out.write(' copyrightText: "%s"\n' % cn) 41 | 42 | kinds = p.get('license_kinds') 43 | if kinds: 44 | self.out.write(' licenseDeclared: "%s"\n' % 45 | ','.join([k['name'] for k in kinds])) 46 | 47 | url = p.get('package_url') 48 | if url: 49 | self.out.write(' downloadLocation: %s\n' % url) 50 | 51 | purl = p.get('purl') 52 | if purl: 53 | self.out.write(' externalRef: PACKAGE-MANAGER purl %s\n' % purl) 54 | -------------------------------------------------------------------------------- /tools/test_helpers.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2020 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 | 15 | """Compare rule output to a golden version.""" 16 | 17 | def golden_test( 18 | name, 19 | golden, 20 | subject): 21 | """Check that output from a rule matches the expected output. 22 | 23 | Args: 24 | name: test name 25 | golden: expected content of subect 26 | subject: build target 27 | """ 28 | native.sh_test( 29 | name = name, 30 | size = "medium", 31 | srcs = ["@rules_license//tools:diff_test.sh"], 32 | args = [ 33 | "$(location %s)" % golden, 34 | "$(location %s)" % subject, 35 | ], 36 | data = [ 37 | subject, 38 | golden, 39 | ], 40 | ) 41 | 42 | def golden_cmd_test( 43 | name, 44 | cmd, 45 | golden, # Required 46 | toolchains = [], 47 | tools = None, 48 | srcs = [], # Optional 49 | **kwargs): # Rest 50 | """Compares cmd output to golden output, passes if they are identical. 51 | 52 | Args: 53 | name: Name of the build rule. 54 | cmd: The command to run to generate output. 55 | golden: The golden file to be compared. 56 | toolchains: List of toolchains needed to run the command, passed to genrule. 57 | tools: List of tools needed to run the command, passed to genrule. 58 | srcs: List of sources needed as input to the command, passed to genrule. 59 | **kwargs: Any additional parameters for the generated golden_test. 60 | """ 61 | actual = name + ".output" 62 | 63 | native.genrule( 64 | name = name + "_output", 65 | srcs = srcs, 66 | outs = [actual], 67 | cmd = cmd + " > '$@'", # Redirect to collect output 68 | toolchains = toolchains, 69 | tools = tools, 70 | testonly = True, 71 | ) 72 | 73 | golden_test( 74 | name = name, 75 | subject = actual, 76 | golden = golden, 77 | **kwargs 78 | ) 79 | -------------------------------------------------------------------------------- /tools/write_sbom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2020 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Proof of concept license checker. 17 | 18 | This is only a demonstration. It will be replaced with other tools. 19 | """ 20 | 21 | import argparse 22 | import codecs 23 | import json 24 | from tools import sbom 25 | 26 | TOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom' 27 | 28 | def _load_package_data(package_info): 29 | with codecs.open(package_info, encoding='utf-8') as inp: 30 | return json.loads(inp.read()) 31 | 32 | def main(): 33 | parser = argparse.ArgumentParser( 34 | description='Demonstraton license compliance checker') 35 | 36 | parser.add_argument('--licenses_info', 37 | help='path to JSON file containing all license data') 38 | parser.add_argument('--out', default='sbom.out', help='SBOM output') 39 | args = parser.parse_args() 40 | 41 | license_data = _load_package_data(args.licenses_info) 42 | target = license_data[0] # we assume only one target for the demo 43 | 44 | top_level_target = target['top_level_target'] 45 | dependencies = target['dependencies'] 46 | # It's not really packages, but this is close proxy for now 47 | licenses = target['licenses'] 48 | package_infos = target['packages'] 49 | 50 | # These are similar dicts, so merge them by package. This is not 51 | # strictly true, as different licenese can appear in the same 52 | # package, but it is good enough for demonstrating the sbom. 53 | 54 | all = {x['bazel_package']: x for x in licenses} 55 | for pi in package_infos: 56 | p = all.get(pi['bazel_package']) 57 | if p: 58 | p.update(pi) 59 | else: 60 | all[pi['bazel_package']] = pi 61 | 62 | with codecs.open(args.out, mode='w', encoding='utf-8') as out: 63 | sbom_writer = sbom.SBOMWriter(TOOL, out) 64 | sbom_writer.write_header(package=top_level_target) 65 | sbom_writer.write_packages(packages=all.values()) 66 | 67 | 68 | if __name__ == '__main__': 69 | main() 70 | -------------------------------------------------------------------------------- /tools/write_workspace_sbom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2020 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Proof of a WORKSPACE SBOM generator. 17 | 18 | This is only a demonstration. It will be replaced with other tools. 19 | """ 20 | 21 | import argparse 22 | import codecs 23 | import json 24 | from tools import sbom 25 | import subprocess 26 | import os 27 | 28 | TOOL = 'https//github.com/bazelbuild/rules_license/tools:write_workspace_sbom' 29 | 30 | def main(): 31 | parser = argparse.ArgumentParser( 32 | description='Demonstraton license compliance checker') 33 | 34 | parser.add_argument('--out', default='sbom.out', help='SBOM output') 35 | args = parser.parse_args() 36 | 37 | if "BUILD_WORKING_DIRECTORY" in os.environ: 38 | os.chdir(os.environ["BUILD_WORKING_DIRECTORY"]) 39 | 40 | external_query_process = subprocess.run( 41 | ['bazel', 'query', '--output', 'streamed_jsonproto', '//external:*'], 42 | stdout=subprocess.PIPE, 43 | ) 44 | sbom_packages = [] 45 | for dep_string in external_query_process.stdout.decode('utf-8').splitlines(): 46 | dep = json.loads(dep_string) 47 | if dep["type"] != "RULE": 48 | continue 49 | 50 | rule = dep["rule"] 51 | if rule["ruleClass"] == "http_archive": 52 | sbom_package = {} 53 | sbom_packages.append(sbom_package) 54 | 55 | if "attribute" not in rule: 56 | continue 57 | 58 | attributes = {attribute["name"]: attribute for attribute in rule["attribute"]} 59 | 60 | if "name" in attributes: 61 | sbom_package["package_name"] = attributes["name"]["stringValue"] 62 | 63 | if "url" in attributes: 64 | sbom_package["package_url"] = attributes["url"]["stringValue"] 65 | elif "urls" in attributes: 66 | urls = attributes["urls"]["stringListValue"] 67 | if urls and len(urls) > 0: 68 | sbom_package["package_url"] = attributes["urls"]["stringListValue"][0] 69 | 70 | with codecs.open(args.out, mode='w', encoding='utf-8') as out: 71 | sbom_writer = sbom.SBOMWriter(TOOL, out) 72 | sbom_writer.write_header(package="Bazel's Workspace SBOM") 73 | sbom_writer.write_packages(packages=sbom_packages) 74 | 75 | if __name__ == '__main__': 76 | main() 77 | -------------------------------------------------------------------------------- /version.bzl: -------------------------------------------------------------------------------- 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 | """The version of rules_license.""" 15 | 16 | version = "1.0.0" 17 | --------------------------------------------------------------------------------