aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJingwen Chen <jingwen@google.com>2023-03-25 07:00:23 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-03-25 07:00:23 +0000
commit0f22c081a0b9df29e574400ebf7003014b04a06d (patch)
tree39314478665e3c080e3df6fe4a97609182f089b5
parent8a99d2b6dff36f04be445f3c5e8f69874da57a04 (diff)
parenteb146bbc492eb4ebea082d3cd0837105d94449ef (diff)
downloadbazelbuild-rules_license-0f22c081a0b9df29e574400ebf7003014b04a06d.tar.gz
Merge remote-tracking branch 'aosp/upstream-main' into mymerge am: 748e62d604 am: dad1665d12 am: eb146bbc49android14-dev
Original change: https://android-review.googlesource.com/c/platform/external/bazelbuild-rules_license/+/2500461 Change-Id: I8761285ae197b03be4c41e46eff8206ed2069548 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.bazelci/postsubmit.yml2
-rw-r--r--.bazelci/tests.yml39
-rw-r--r--BUILD16
-rw-r--r--METADATA17
-rw-r--r--MODULE.bazel16
-rw-r--r--README.md28
-rw-r--r--WORKSPACE19
-rw-r--r--WORKSPACE.bzlmod11
-rw-r--r--distro/BUILD6
-rw-r--r--doc_build/BUILD106
-rwxr-xr-xdoc_build/merge.py86
-rw-r--r--docs/_config.yml1
-rw-r--r--docs/_includes/head-custom.html6
-rw-r--r--docs/_includes/head.html2
-rwxr-xr-xdocs/favicon.icobin0 -> 5430 bytes
-rw-r--r--docs/index.md9
-rwxr-xr-xdocs/latest.md170
-rw-r--r--examples/README.md2
-rw-r--r--examples/manifest/BUILD41
-rw-r--r--examples/manifest/android_mock.bzl61
-rw-r--r--examples/manifest/license_display.sh7
-rwxr-xr-xexamples/manifest/main.sh10
-rw-r--r--examples/manifest/main_golden.txt406
-rw-r--r--examples/my_org/compliance/BUILD31
-rw-r--r--examples/my_org/licenses/BUILD15
-rw-r--r--examples/policy_checker/BUILD63
-rw-r--r--examples/policy_checker/license_policy.bzl (renamed from rules/license_policy.bzl)5
-rw-r--r--examples/policy_checker/license_policy_check.bzl (renamed from rules/license_policy_check.bzl)30
-rw-r--r--examples/policy_checker/license_policy_provider.bzl (renamed from rules/license_policy_provider.bzl)0
-rw-r--r--examples/sboms/BUILD13
-rw-r--r--examples/src/BUILD79
-rw-r--r--examples/src/server.cc4
-rw-r--r--examples/src/server_licenses.golden32
-rw-r--r--examples/src/server_licenses_test.py46
-rw-r--r--examples/src/server_report.golden3
-rw-r--r--examples/vendor/acme/BUILD22
-rw-r--r--examples/vendor/constant_gen/generated_code_licenses.golden17
-rw-r--r--examples/vendor/constant_gen/generator_licenses.golden17
-rw-r--r--examples/vendor/libhhgttg/BUILD25
-rw-r--r--examples/vndor/README.md (renamed from examples/vendor/README.md)0
-rw-r--r--examples/vndor/acme/ACME_LICENSE (renamed from examples/vendor/acme/ACME_LICENSE)0
-rw-r--r--examples/vndor/acme/BUILD35
-rw-r--r--examples/vndor/acme/coyote.cc (renamed from examples/vendor/acme/coyote.cc)3
-rw-r--r--examples/vndor/constant_gen/BUILD (renamed from examples/vendor/constant_gen/BUILD)43
-rw-r--r--examples/vndor/constant_gen/LICENSE (renamed from examples/vendor/constant_gen/LICENSE)0
-rw-r--r--examples/vndor/constant_gen/LICENSE.on_output (renamed from examples/vendor/constant_gen/LICENSE.on_output)0
-rw-r--r--examples/vndor/constant_gen/constant_generator.py (renamed from examples/vendor/constant_gen/constant_generator.py)4
-rw-r--r--examples/vndor/constant_gen/defs.bzl (renamed from examples/vendor/constant_gen/defs.bzl)9
-rw-r--r--examples/vndor/constant_gen/verify_licenses_test.py40
-rw-r--r--examples/vndor/libhhgttg/BUILD38
-rw-r--r--examples/vndor/libhhgttg/LICENSE (renamed from examples/vendor/libhhgttg/LICENSE)0
-rw-r--r--examples/vndor/libhhgttg/answer.cc (renamed from examples/vendor/libhhgttg/answer.cc)2
-rw-r--r--licenses/generic/BUILD20
-rw-r--r--licenses/spdx/BUILD2
-rw-r--r--rules/BUILD22
-rw-r--r--rules/check_licenses_shim.bzl30
-rw-r--r--rules/compliance.bzl101
-rw-r--r--rules/default_license.bzl55
-rw-r--r--rules/filtered_rule_kinds.bzl47
-rw-r--r--rules/gather_licenses_info.bzl270
-rw-r--r--rules/gather_metadata.bzl309
-rw-r--r--rules/license.bzl117
-rw-r--r--rules/license_impl.bzl47
-rw-r--r--rules/license_kind.bzl19
-rw-r--r--rules/licenses_core.bzl224
-rw-r--r--rules/package_info.bzl104
-rw-r--r--rules/private/BUILD35
-rw-r--r--rules/private/gathering_providers.bzl54
-rw-r--r--rules/providers.bzl53
-rw-r--r--rules/sbom.bzl136
-rw-r--r--rules/user_filtered_rule_kinds.bzl28
-rw-r--r--tests/BUILD81
-rw-r--r--tests/apps/BUILD70
-rw-r--r--tests/apps/an_app.cc24
-rw-r--r--tests/apps/an_app_licenses_test.py42
-rwxr-xr-xtests/hello_cc_copyrights.golden1
-rwxr-xr-xtests/hello_java_copyrights.golden2
-rw-r--r--tests/hello_licenses_test.py42
-rw-r--r--tests/legacy/BUILD17
-rw-r--r--tests/legacy/file_under_notice.cc14
-rw-r--r--tests/license_test_utils.py84
-rw-r--r--tests/thrdparty/BUILD26
-rw-r--r--tests/thrdparty/LICENSE1
-rw-r--r--tests/thrdparty/new_style_lib.cc (renamed from examples/src/mobile.cc)9
-rw-r--r--tools/BUILD23
-rw-r--r--tools/checker_demo.py32
-rw-r--r--tools/test_helpers.bzl47
-rw-r--r--tools/write_sbom.py117
88 files changed, 3428 insertions, 514 deletions
diff --git a/.bazelci/postsubmit.yml b/.bazelci/postsubmit.yml
new file mode 100644
index 0000000..1f6e0d4
--- /dev/null
+++ b/.bazelci/postsubmit.yml
@@ -0,0 +1,2 @@
+imports:
+- tests.yml
diff --git a/.bazelci/tests.yml b/.bazelci/tests.yml
index 045e065..68fc0ae 100644
--- a/.bazelci/tests.yml
+++ b/.bazelci/tests.yml
@@ -1,8 +1,8 @@
default_tests: &default_tests
test_targets:
- - "//tests/...
- - "//examples/...
+ - "//tests/..."
+ - "//examples/..."
#
# Bazel releases
@@ -17,13 +17,12 @@ rolling: &rolling
#
# Commmon features by platform
#
-ubuntu1804: &ubuntu
- platform: ubuntu1804
+ubuntu2004: &ubuntu
+ platform: ubuntu2004
<<: *default_tests
build_targets:
- "//distro:distro"
- "//distro:relnotes"
- - "//doc_build:*"
macos: &macos
platform: macos
@@ -31,22 +30,50 @@ macos: &macos
windows: &windows
platform: windows
- <<: *win_tests
+ test_targets:
+ - "//tests/..."
+ - "//examples/..."
+ - "-//examples/manifest/..."
+
# The cross product of bazel releases X platforms
#
tasks:
+ lts_ubuntu:
+ name: lts_ubuntu
+ <<: *ubuntu
+ <<: *lts
rolling_ubuntu:
name: rolling_ubuntu
<<: *ubuntu
<<: *rolling
+ lts_macos:
+ name: lts_macos
+ <<: *macos
+ <<: *lts
rolling_macos:
name: rolling_macos
<<: *macos
# It seems there is no rolling Bazel for macos.
bazel: last_green
+ lts_windows:
+ name: lts_windows
+ <<: *windows
+ <<: *lts
rolling_windows:
name: rolling_windows
<<: *windows
<<: *rolling
+ #
+ # Smoke test with bzlmod
+ #
+ bzlmod_rolling_ubuntu:
+ name: bzlmod_rolling_ubuntu
+ <<: *ubuntu
+ <<: *rolling
+ build_flags:
+ - "--enable_bzlmod"
+ build_targets:
+ - "//distro:distro"
+ - "//distro:relnotes"
diff --git a/BUILD b/BUILD
index a763238..31520c4 100644
--- a/BUILD
+++ b/BUILD
@@ -13,12 +13,16 @@
# limitations under the License.
load("@rules_license//rules:license.bzl", "license")
+load("@rules_license//rules:package_info.bzl", "package_info")
+load("@rules_license//:version.bzl", "version")
package(
- default_applicable_licenses = [":license"],
+ default_applicable_licenses = [":license", ":package_info"],
default_visibility = ["//visibility:public"],
)
+licenses(["notice"])
+
license(
name = "license",
license_kinds = [
@@ -27,8 +31,14 @@ license(
license_text = "LICENSE",
)
+package_info(
+ name = "package_info",
+ package_name = "rules_license",
+ package_version = version,
+)
+
exports_files(
- ["WORKSPACE"],
+ ["LICENSE", "WORKSPACE"],
visibility = ["//visibility:public"],
)
@@ -45,8 +55,10 @@ filegroup(
"*.bzl",
"*.md",
]) + [
+ "MODULE.bazel",
"BUILD",
"LICENSE",
+ "WORKSPACE.bzlmod",
],
visibility = ["//distro:__pkg__"],
)
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..7d83584
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "rules_license"
+description:
+ "Software license related rules and tools for the Bazel build system."
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://github.com/bazelbuild/rules_license"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/bazelbuild/rules_license.git"
+ }
+ version: "bb6f02d8ce7e51587e600671d41e801960d16bf1"
+ last_upgrade_date { year: 2023 month: 3 day: 22 }
+ license_type: NOTICE
+}
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..114446a
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,16 @@
+module(
+ name = "rules_license",
+ version = "0.0.4", # Keep in sync with version.bzl
+ compatibility_level = 1,
+)
+
+# NOTE: rules_license must not depend on any other repositories if you are
+# just using basic rules under //rules/... and //licenses/...
+
+# TODO(aiuto): Create an extension to enable the rules under //tools/...
+# That will require rules_python, which we do not want to force on people who
+# do not need //tools.
+
+# Only for development
+bazel_dep(name = "rules_pkg", version = "0.7.0", dev_dependency = True)
+bazel_dep(name = "stardoc", version = "0.5.3", dev_dependency = True)
diff --git a/README.md b/README.md
index d4ce9e8..1527f74 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,27 @@
# rules_license
+CI: [![Build status](https://badge.buildkite.com/e12f23186aa579f1e20fcb612a22cd799239c3134bc38e1aff.svg)](https://buildkite.com/bazel/rules-license)
+
This repository contains a set of rules and tools for
-- asserting that packages are available under specified OSS licenses
-- gathering those license assertions into artifacts to ship with code
+- declaring metadata about packages, such as
+ - the licenses the package is available under
+ - the canonical package name and version
+ - copyright information
+ - ... and more TBD in the future
+- gathering those license declarations into artifacts to ship with code
- applying organization specific compliance constriants against the
- set of licenses used by a target.
+ set of packages used by a target.
+- (eventually) producing SBOMs for built artifacts.
+
+WARNING: The code here is still in active initial development and will churn a lot.
-See [License Checking with
-Bazel](https://docs.google.com/document/d/1uwBuhAoBNrw8tmFs-NxlssI6VRolidGYdYqagLqHWt8/edit#)
-for more information about the project.
+If you want to follow along:
+- Mailing list: [bazel-ssc@bazel.build](https://groups.google.com/a/bazel.build/g/bazel-ssc)
+- Monthly eng meeting: [calendar link](MjAyMjA4MjJUMTYwMDAwWiBjXzUzcHBwZzFudWthZXRmb3E5NzhxaXViNmxzQGc&tmsrc=c_53pppg1nukaetfoq978qiub6ls%40group.calendar.google.com&scp=ALL)
+- [Latest docs](https://bazelbuild.github.io/rules_license/latest.html)
-WARNING: The code here is still in active initial development and may be under churn.
+Background reading:
+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.
+- [License Checking with Bazel](https://docs.google.com/document/d/1uwBuhAoBNrw8tmFs-NxlssI6VRolidGYdYqagLqHWt8/edit#).
+- [OSS Licenses and Bazel Dependency Management](https://docs.google.com/document/d/1oY53dQ0pOPEbEvIvQ3TvHcFKClkimlF9AtN89EPiVJU/edit#)
+- [Adding OSS license declarations to Bazel](https://docs.google.com/document/d/1XszGbpMYNHk_FGRxKJ9IXW10KxMPdQpF5wWbZFpA4C8/edit#heading=h.5mcn15i0e1ch)
diff --git a/WORKSPACE b/WORKSPACE
index c4b227b..654ea78 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -25,13 +25,26 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_pkg",
- sha256 = "62eeb544ff1ef41d786e329e1536c1d541bb9bcad27ae984d57f18f314018e66",
urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.6.0/rules_pkg-0.6.0.tar.gz",
- "https://github.com/bazelbuild/rules_pkg/releases/download/0.6.0/rules_pkg-0.6.0.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.8.0/rules_pkg-0.8.0.tar.gz",
+ "https://github.com/bazelbuild/rules_pkg/releases/download/0.8.0/rules_pkg-0.8.0.tar.gz",
],
+ sha256 = "eea0f59c28a9241156a47d7a8e32db9122f3d50b505fae0f33de6ce4d9b61834",
)
load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
rules_pkg_dependencies()
+
+http_archive(
+ name = "io_bazel_stardoc",
+ sha256 = "3fd8fec4ddec3c670bd810904e2e33170bedfe12f90adf943508184be458c8bb",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.5.3/stardoc-0.5.3.tar.gz",
+ "https://github.com/bazelbuild/stardoc/releases/download/0.5.3/stardoc-0.5.3.tar.gz",
+ ],
+)
+
+load("@io_bazel_stardoc//:setup.bzl", "stardoc_repositories")
+
+stardoc_repositories()
diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod
new file mode 100644
index 0000000..62e4ed8
--- /dev/null
+++ b/WORKSPACE.bzlmod
@@ -0,0 +1,11 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+# This is needed for tools
+maybe(
+ http_archive,
+ name = "rules_python",
+ sha256 = "b593d13bb43c94ce94b483c2858e53a9b811f6f10e1e0eedc61073bd90e58d9c",
+ strip_prefix = "rules_python-0.12.0",
+ url = "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.12.0.tar.gz",
+)
diff --git a/distro/BUILD b/distro/BUILD
index a35a118..693d1ac 100644
--- a/distro/BUILD
+++ b/distro/BUILD
@@ -16,12 +16,13 @@ load("//:version.bzl", "version")
load("@rules_pkg//pkg:pkg.bzl", "pkg_tar")
load("@rules_pkg//pkg/releasing:defs.bzl", "print_rel_notes")
-
package(
- default_visibility = ["//visibility:private"],
+ default_visibility = ["//visibility:public"],
default_applicable_licenses = ["//:license"],
)
+licenses(["notice"])
+
alias(
name = "distro",
actual = "rules_license-%s" % version,
@@ -36,6 +37,7 @@ pkg_tar(
"//licenses/generic:standard_package",
"//licenses/spdx:standard_package",
"//rules:standard_package",
+ "//rules/private:standard_package",
"//tools:standard_package",
],
extension = "tar.gz",
diff --git a/doc_build/BUILD b/doc_build/BUILD
new file mode 100644
index 0000000..c50e2d4
--- /dev/null
+++ b/doc_build/BUILD
@@ -0,0 +1,106 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Generate the reference documentation.
+
+How to:
+ bazel build //doc_build:reference
+ cp bazel-bin/doc_build/reference.md docs/latest.md
+ git commit -m 'update docs' docs/latest.md
+"""
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
+load("@rules_python//python:defs.bzl", "py_library")
+load("//:version.bzl", "version")
+
+package(default_package_metadata = ["//:license", "//:package_info"])
+
+filegroup(
+ name = "standard_package",
+ srcs = [
+ "BUILD",
+ ] + glob([
+ "*.bzl",
+ "*.py",
+ ]),
+ visibility = ["//distro:__pkg__"],
+)
+
+exports_files(
+ glob([
+ "*.bzl",
+ ]),
+ visibility = [
+ "//distro:__pkg__",
+ ],
+)
+
+# pairs of rule name and the source file to get it from
+# Must put macro wrapped rules after their wrapper
+# buildifier: leave-alone, do not sort
+ORDER = [
+ ("license", "//rules:license.bzl"),
+ ("_license", "//rules:license.bzl"),
+ ("license_kind", "//rules:license_kind.bzl"),
+ ("_license_kind", "//rules:license_kind.bzl"),
+ ("package_info", "//rules:package_info.bzl"),
+ ("_package_info", "//rules:package_info.bzl"),
+ ("LicenseInfo", "//rules:providers.bzl"),
+ ("LicenseKindInfo", "//rules:providers.bzl"),
+ ("PackageInfo", "//rules:providers.bzl"),
+]
+
+genrule(
+ name = "reference",
+ srcs = ["%s.md" % rule for rule, _ in ORDER],
+ outs = ["reference.md"],
+ cmd = "$(location :merge) $(SRCS) >$@",
+ tools = [":merge"],
+)
+
+[
+ stardoc(
+ name = "%s_gen" % rule,
+ out = "%s.md" % rule,
+ input = src,
+ symbol_names = [
+ rule,
+ ],
+ deps = [":lib_of_everything"],
+ )
+ for rule, src in ORDER
+ if src
+]
+
+# gather all rules that should be documented
+bzl_library(
+ name = "lib_of_everything",
+ srcs = [
+ "//:version.bzl",
+ "//rules:standard_package",
+ "//rules/private:standard_package",
+ # "@bazel_skylib//lib:paths",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+# This is experimental. We are waiting for stardoc to get the features which
+# are done in merge.
+py_binary(
+ name = "merge",
+ srcs = ["merge.py"],
+ python_version = "PY3",
+ srcs_version = "PY3",
+ visibility = ["//visibility:private"],
+)
diff --git a/doc_build/merge.py b/doc_build/merge.py
new file mode 100755
index 0000000..7212f78
--- /dev/null
+++ b/doc_build/merge.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""merge stardoc output into a single page.
+
+- concatenates files
+- corrects things that stardoc botches
+"""
+
+import re
+import sys
+import typing
+
+
+# I think stardoc changed the format of the id strings. Sigh.
+ID_RE = re.compile(r'<a id="(.*)">')
+ID_OLD_RE = re.compile(r'<a id="#(.*)">')
+WRAPS_RE = re.compile(r'@wraps\((.*)\)')
+SINCE_RE = re.compile(r'@since\(([^)]*)\)')
+CENTER_RE = re.compile(r'<p align="center">([^<]*)</p>')
+
+
+def merge_file(file: str, out, wrapper_map:typing.Dict[str, str]) -> None:
+ with open(file, 'r') as inp:
+ content = inp.read()
+ m = ID_RE.search(content)
+ if not m:
+ m = ID_OLD_RE.search(content)
+ this_rule = m.group(1) if m else None
+ m = WRAPS_RE.search(content)
+ if m:
+ # I wrap something, so don't emit me.
+ wrapper_map[m.group(1)] = this_rule
+ return
+ # If something wraps me, rewrite myself with the wrapper name.
+ if this_rule in wrapper_map:
+ content = content.replace(this_rule, wrapper_map[this_rule])
+ merge_text(content, out)
+
+
+def merge_text(text: str, out) -> None:
+ """Merge a block of text into an output stream.
+
+ Args:
+ text: block of text produced by Starroc.
+ out: an output file stream.
+ """
+ for line in text.split('\n'):
+ line = SINCE_RE.sub(r'<div class="since"><i>Since \1</i></div>', line)
+
+ if line.startswith('| :'):
+ line = fix_stardoc_table_align(line)
+ # Compensate for https://github.com/bazelbuild/stardoc/issues/118.
+ # Convert escaped HTML <li> back to raw text
+ line = line.replace('&lt;li&gt;', '<li>')
+ line = CENTER_RE.sub(r'\1', line)
+ _ = out.write(line)
+ _ = out.write('\n')
+
+
+def fix_stardoc_table_align(line: str) -> str:
+ """Change centered descriptions to left justified."""
+ if line.startswith('| :-------------: | :-------------: '):
+ return '| :------------ | :--------------- | :---------: | :---------: | :----------- |'
+ return line
+
+
+def main(argv: typing.Sequence[str]) -> None:
+ wrapper_map = {}
+ for file in argv[1:]:
+ merge_file(file, sys.stdout, wrapper_map)
+
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..c419263
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman \ No newline at end of file
diff --git a/docs/_includes/head-custom.html b/docs/_includes/head-custom.html
new file mode 100644
index 0000000..f1e4106
--- /dev/null
+++ b/docs/_includes/head-custom.html
@@ -0,0 +1,6 @@
+<!-- _includes/head-custom.html -->
+<meta charset="utf-8">
+<link rel="shortcut icon" type="image/x-icon" href="/rules_license/favicon.ico">
+<!-- Setup Google Analytics below -->
+
+<!-- end custom head snippets -->
diff --git a/docs/_includes/head.html b/docs/_includes/head.html
new file mode 100644
index 0000000..52e6032
--- /dev/null
+++ b/docs/_includes/head.html
@@ -0,0 +1,2 @@
+<!-- _inclues/head.html -->
+<meta charset="utf-8">
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100755
index 0000000..507f14f
--- /dev/null
+++ b/docs/favicon.ico
Binary files differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..a2f7596
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,9 @@
+# bazelbuild/rules_license
+
+Bazel rules for defining and using package licensing and other metadata
+
+Use bazel-ssc@bazel.build for discussion.
+
+## Reference
+
+* [Latest Snapshot at head](latest.md)
diff --git a/docs/latest.md b/docs/latest.md
new file mode 100755
index 0000000..a7c373f
--- /dev/null
+++ b/docs/latest.md
@@ -0,0 +1,170 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Rules for declaring the compliance licenses used by a package.
+
+
+
+<a id="license"></a>
+
+## license
+
+<pre>
+license(<a href="#license-name">name</a>, <a href="#license-copyright_notice">copyright_notice</a>, <a href="#license-license_kinds">license_kinds</a>, <a href="#license-license_text">license_text</a>, <a href="#license-namespace">namespace</a>, <a href="#license-package_name">package_name</a>, <a href="#license-package_url">package_url</a>,
+ <a href="#license-package_version">package_version</a>)
+</pre>
+
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="license-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="license-copyright_notice"></a>copyright_notice | Copyright notice. | String | optional | <code>""</code> |
+| <a id="license-license_kinds"></a>license_kinds | License kind(s) of this license. If multiple license kinds are listed in the LICENSE file, and they all apply, then all should be listed here. If the user can choose a single one of many, then only list one here. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+| <a id="license-license_text"></a>license_text | The license file. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>LICENSE</code> |
+| <a id="license-namespace"></a>namespace | A human readable name used to organize licenses into categories. This is used in google3 to differentiate third party licenses used for compliance versus internal licenses used by SLAsan for internal teams' SLAs. | String | optional | <code>""</code> |
+| <a id="license-package_name"></a>package_name | A human readable name identifying this package. This may be used to produce an index of OSS packages used by an applicatation. | String | optional | <code>""</code> |
+| <a id="license-package_url"></a>package_url | The URL this instance of the package was download from. This may be used to produce an index of OSS packages used by an applicatation. | String | optional | <code>""</code> |
+| <a id="license-package_version"></a>package_version | A human readable version string identifying this package. This may be used to produce an index of OSS packages used by an applicatation. It should be a value that increases over time, rather than a commit hash. | String | optional | <code>""</code> |
+
+
+
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Proof of concept. License restriction.
+
+<a id="license_kind"></a>
+
+## license_kind
+
+<pre>
+license_kind(<a href="#license_kind-name">name</a>, <a href="#license_kind-canonical_text">canonical_text</a>, <a href="#license_kind-conditions">conditions</a>, <a href="#license_kind-long_name">long_name</a>, <a href="#license_kind-url">url</a>)
+</pre>
+
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="license_kind-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="license_kind-canonical_text"></a>canonical_text | File containing the canonical text for this license. Must be UTF-8 encoded. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="license_kind-conditions"></a>conditions | Conditions to be met when using software under this license. Conditions are defined by the organization using this license. | List of strings | required | |
+| <a id="license_kind-long_name"></a>long_name | Human readable long name of license. | String | optional | <code>""</code> |
+| <a id="license_kind-url"></a>url | URL pointing to canonical license definition | String | optional | <code>""</code> |
+
+
+
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Rules for declaring metadata about a package.
+
+<a id="package_info"></a>
+
+## package_info
+
+<pre>
+package_info(<a href="#package_info-name">name</a>, <a href="#package_info-package_name">package_name</a>, <a href="#package_info-package_url">package_url</a>, <a href="#package_info-package_version">package_version</a>)
+</pre>
+
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="package_info-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="package_info-package_name"></a>package_name | A human readable name identifying this package. This may be used to produce an index of OSS packages used by an applicatation. | String | optional | <code>""</code> |
+| <a id="package_info-package_url"></a>package_url | The URL this instance of the package was download from. This may be used to produce an index of OSS packages used by an applicatation. | String | optional | <code>""</code> |
+| <a id="package_info-package_version"></a>package_version | A human readable version string identifying this package. This may be used to produce an index of OSS packages used by an applicatation. It should be a value that increases over time, rather than a commit hash. | String | optional | <code>""</code> |
+
+
+
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Providers for license rules.
+
+<a id="LicenseInfo"></a>
+
+## LicenseInfo
+
+<pre>
+LicenseInfo(<a href="#LicenseInfo-copyright_notice">copyright_notice</a>, <a href="#LicenseInfo-label">label</a>, <a href="#LicenseInfo-license_kinds">license_kinds</a>, <a href="#LicenseInfo-license_text">license_text</a>, <a href="#LicenseInfo-namespace">namespace</a>, <a href="#LicenseInfo-package_name">package_name</a>,
+ <a href="#LicenseInfo-package_url">package_url</a>, <a href="#LicenseInfo-package_version">package_version</a>)
+</pre>
+
+Provides information about a license instance.
+
+**FIELDS**
+
+
+| Name | Description |
+| :------------- | :------------- |
+| <a id="LicenseInfo-copyright_notice"></a>copyright_notice | string: Human readable short copyright notice |
+| <a id="LicenseInfo-label"></a>label | Label: label of the license rule |
+| <a id="LicenseInfo-license_kinds"></a>license_kinds | list(LicenseKindInfo): License kinds |
+| <a id="LicenseInfo-license_text"></a>license_text | string: The license file path |
+| <a id="LicenseInfo-namespace"></a>namespace | string: namespace of the license rule |
+| <a id="LicenseInfo-package_name"></a>package_name | string: Human readable package name |
+| <a id="LicenseInfo-package_url"></a>package_url | URL from which this package was downloaded. |
+| <a id="LicenseInfo-package_version"></a>package_version | Human readable version string |
+
+
+
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Providers for license rules.
+
+<a id="LicenseKindInfo"></a>
+
+## LicenseKindInfo
+
+<pre>
+LicenseKindInfo(<a href="#LicenseKindInfo-conditions">conditions</a>, <a href="#LicenseKindInfo-label">label</a>, <a href="#LicenseKindInfo-long_name">long_name</a>, <a href="#LicenseKindInfo-name">name</a>)
+</pre>
+
+Provides information about a license_kind instance.
+
+**FIELDS**
+
+
+| Name | Description |
+| :------------- | :------------- |
+| <a id="LicenseKindInfo-conditions"></a>conditions | list(string): List of conditions to be met when using this packages under this license. |
+| <a id="LicenseKindInfo-label"></a>label | Label: The full path to the license kind definition. |
+| <a id="LicenseKindInfo-long_name"></a>long_name | string: Human readable license name |
+| <a id="LicenseKindInfo-name"></a>name | string: Canonical license name |
+
+
+
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Providers for license rules.
+
+<a id="PackageInfo"></a>
+
+## PackageInfo
+
+<pre>
+PackageInfo(<a href="#PackageInfo-type">type</a>, <a href="#PackageInfo-label">label</a>, <a href="#PackageInfo-package_name">package_name</a>, <a href="#PackageInfo-package_url">package_url</a>, <a href="#PackageInfo-package_version">package_version</a>)
+</pre>
+
+Provides information about a package.
+
+**FIELDS**
+
+
+| Name | Description |
+| :------------- | :------------- |
+| <a id="PackageInfo-type"></a>type | string: How to interpret data |
+| <a id="PackageInfo-label"></a>label | Label: label of the package_info rule |
+| <a id="PackageInfo-package_name"></a>package_name | string: Human readable package name |
+| <a id="PackageInfo-package_url"></a>package_url | string: URL from which this package was downloaded. |
+| <a id="PackageInfo-package_version"></a>package_version | string: Human readable version string |
+
+
+
diff --git a/examples/README.md b/examples/README.md
index 0afb5ca..fede271 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -9,7 +9,7 @@ Terminology
- SCM: source code management system. These examples assume that
an organization has a SCM that can enforce ownership restrictions on
specific folder trees. Targets are divided into BUILD files that are
- reviewed by engineers vs. those that are reviewed by an organization's
+ reviewed by engineers vs. those that are reviewed by an organizations
compliance team.
## Overview
diff --git a/examples/manifest/BUILD b/examples/manifest/BUILD
new file mode 100644
index 0000000..d308a59
--- /dev/null
+++ b/examples/manifest/BUILD
@@ -0,0 +1,41 @@
+load(":android_mock.bzl", "android_binary", "android_library")
+load("@rules_license//tools:test_helpers.bzl", "golden_cmd_test")
+
+
+# These two rules today capture what an android_binary would look like.
+# This rule represents the Android specific code that displays licenses
+# on the display. Note that it does not depend on anything to get the
+# license contents; the implementation of these rules macros handle that
+# detail.
+android_library(
+ name = "licenses",
+ srcs = [
+ "license_display.sh",
+ ],
+ data = [
+ "@rules_license//distro:distro",
+ ],
+)
+
+# This captures how the application would be built. The dependencies of this
+# rule are crawled to identify third-party licenses in use. The macro definition
+# of this rule creates a graph to capture that process of identifying licenses,
+# building the licenses target, and finally invoking the "real" android_binary
+# rule to build the final output with the injected license content.
+android_binary(
+ name = "main",
+ srcs = ["main.sh"],
+ deps = [
+ ],
+ data = [
+ ":licenses",
+ ],
+)
+
+golden_cmd_test(
+ name = "main_test",
+ srcs = [],
+ cmd = "$(location :main)",
+ tools = [":main"],
+ golden = "main_golden.txt",
+)
diff --git a/examples/manifest/android_mock.bzl b/examples/manifest/android_mock.bzl
new file mode 100644
index 0000000..0dee3c9
--- /dev/null
+++ b/examples/manifest/android_mock.bzl
@@ -0,0 +1,61 @@
+load("@rules_license//rules:compliance.bzl", "manifest")
+
+"""This is a proof of concept to show how to modify a macro definition to
+create a sub-graph allowing for build time injection of license information. We
+use Android-inspired rule names since these are a likely candidate for this
+sort of injection."""
+
+def android_library(name, **kwargs):
+ # This is an approximation for demo purposes.
+
+ data = kwargs.pop("data", [])
+ native.filegroup(
+ name = name,
+ srcs = data + kwargs.get("srcs", []),
+ )
+
+ # Inject the data dependency into the library, preserving any other data it has.
+ native.sh_library(
+ name = name + "_w_licenses",
+ data = data + [name + "_manifest.txt"],
+ **kwargs
+ )
+
+def android_binary(name, **kwargs):
+ # Same observation about not being sloppy with mapping deps, but I think the only important attribute
+ # in android_binary is deps, but need to double-check.
+ native.filegroup(
+ name = name + "_no_licenses",
+ srcs = kwargs.get("data", []),
+ )
+
+ mf_name = name + "_manifest"
+ manifest(
+ name = mf_name,
+ deps = [":" + name + "_no_licenses"],
+ )
+
+ # This uses the conditions tool to generate an approximation of a compliance report
+ # to demonstrate how license data can be plumbed and made available at build time.
+ native.genrule(
+ name = "gen_" + name + "_manifest",
+ srcs = [":" + mf_name],
+ outs = ["licenses_manifest.txt"],
+ cmd = "cat $(locations :%s) > $@" % mf_name,
+ )
+
+ # Swap out the :licenses dep for our new :licenses_w_licenses dep
+ newdeps = []
+ deps = kwargs.get("data", [])
+ for dep in deps:
+ if dep == ":licenses":
+ newdeps.append(":licenses_w_licenses")
+ else:
+ newdeps.append(dep)
+ kwargs["data"] = newdeps
+
+ # Compile the executable with the user's originally supplied name, but with the new content.
+ native.sh_binary(
+ name = name,
+ **kwargs
+ )
diff --git a/examples/manifest/license_display.sh b/examples/manifest/license_display.sh
new file mode 100644
index 0000000..f96b3ba
--- /dev/null
+++ b/examples/manifest/license_display.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+function display_licenses {
+ echo -n "Licenses: "
+ cat "$0.runfiles/rules_license/examples/manifest/licenses_manifest.txt"
+ echo
+}
diff --git a/examples/manifest/main.sh b/examples/manifest/main.sh
new file mode 100755
index 0000000..4f80a5c
--- /dev/null
+++ b/examples/manifest/main.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+#source gbash.sh || exit
+
+#source "$RUNFILES/google3/tools/build_defs/license/examples/manifest/license_display.sh"
+source "$0.runfiles/rules_license/examples/manifest/license_display.sh"
+#source module google3/tools/build_defs/license/examples/manifest/license_display.sh
+
+echo "I am a program that uses open source code."
+display_licenses
diff --git a/examples/manifest/main_golden.txt b/examples/manifest/main_golden.txt
new file mode 100644
index 0000000..02a32de
--- /dev/null
+++ b/examples/manifest/main_golden.txt
@@ -0,0 +1,406 @@
+I am a program that uses open source code.
+Licenses:
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/examples/my_org/compliance/BUILD b/examples/my_org/compliance/BUILD
deleted file mode 100644
index 074b21e..0000000
--- a/examples/my_org/compliance/BUILD
+++ /dev/null
@@ -1,31 +0,0 @@
-# Example license policy definitions.
-
-load("@rules_license//rules:license_policy.bzl", "license_policy")
-
-package(default_visibility = ["//examples:__subpackages__"])
-
-# license_policy rules generally appear in a central location per workspace. They
-# are intermingled with normal target build rules
-license_policy(
- name = "production_service",
- conditions = [
- "notice",
- "restricted_if_statically_linked",
- ],
-)
-
-license_policy(
- name = "mobile_application",
- conditions = [
- "notice",
- ],
-)
-
-license_policy(
- name = "special_whitelisted_app",
- # There could be a whitelist of targets here.
- conditions = [
- "notice",
- "whitelist:acme_corp_paid",
- ],
-)
diff --git a/examples/my_org/licenses/BUILD b/examples/my_org/licenses/BUILD
index d9d5c25..c200d37 100644
--- a/examples/my_org/licenses/BUILD
+++ b/examples/my_org/licenses/BUILD
@@ -1,3 +1,16 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
# Example license kind definitions.
# We expect that all license_kind rules used by an organization exist in a
@@ -53,6 +66,6 @@ license_kind(
license_kind(
name = "acme_corp_paid",
conditions = [
- "whitelist:acme_corp_paid",
+ "allowlist:acme_corp_paid",
],
)
diff --git a/examples/policy_checker/BUILD b/examples/policy_checker/BUILD
new file mode 100644
index 0000000..49f77aa
--- /dev/null
+++ b/examples/policy_checker/BUILD
@@ -0,0 +1,63 @@
+# Example of automated license policy definitions.
+
+load("@rules_license//examples/policy_checker:license_policy.bzl", "license_policy")
+load("@rules_license//examples/policy_checker:license_policy_check.bzl", "license_policy_check")
+
+package(default_package_metadata = ["//:license", "//:package_info"])
+
+# license_policy rules generally appear in a central location per workspace. That
+# should be access controlled by the policy team.
+
+# A production service can use licenses with most conditions
+license_policy(
+ name = "production_service",
+ conditions = [
+ "notice",
+ "restricted_if_statically_linked",
+ ],
+)
+
+# A mobile application usually can not allow end-user replacable libraries.
+# So LGPL code (which is restricted_if_statically_linked) can not be used.
+license_policy(
+ name = "mobile_application",
+ conditions = [
+ "notice",
+ ],
+)
+
+license_policy(
+ name = "special_allowlisted_app",
+ # There could be a allowlist of targets here.
+ conditions = [
+ "notice",
+ "allowlist:acme_corp_paid",
+ ],
+)
+
+# Now we might build checks of critical applications against policies
+#
+# Questions to consider?
+# - Your organization migth want to fold these kinds of checks into
+# wrapper macros around the rules which generate services and apps
+# - You might want to distribute checks to rules alongside the products
+# - Or, you might want to consolidate them in a single place where your
+# compliance team owns them, as this example does
+
+license_policy_check(
+ name = "check_server",
+ policy = ":production_service",
+ target = "//examples/src:my_server",
+)
+
+
+# This is marked manual, so bazel test ... does not fail. Try it yourself with
+# bazel build :check_violating_server
+license_policy_check(
+ name = "check_violating_server",
+ policy = ":production_service",
+ tags = [
+ "manual",
+ ],
+ target = "//examples/src:my_violating_server",
+)
diff --git a/rules/license_policy.bzl b/examples/policy_checker/license_policy.bzl
index 0539301..51d6776 100644
--- a/rules/license_policy.bzl
+++ b/examples/policy_checker/license_policy.bzl
@@ -20,7 +20,10 @@ application type (e.g. production_server, mobile_application, ...)
"""
-load("@rules_license//rules:license_policy_provider.bzl", "LicensePolicyInfo")
+load(
+ "@rules_license//examples/policy_checker:license_policy_provider.bzl",
+ "LicensePolicyInfo"
+)
def _license_policy_impl(ctx):
provider = LicensePolicyInfo(
diff --git a/rules/license_policy_check.bzl b/examples/policy_checker/license_policy_check.bzl
index be46913..bb35eee 100644
--- a/rules/license_policy_check.bzl
+++ b/examples/policy_checker/license_policy_check.bzl
@@ -15,23 +15,33 @@
"""License compliance checking at analysis time."""
load(
- "@rules_license//rules:gather_licenses_info.bzl",
- "gather_licenses_info",
-)
-load(
- "@rules_license//rules:license_policy_provider.bzl",
+ "@rules_license//examples/policy_checker:license_policy_provider.bzl",
"LicensePolicyInfo",
)
load(
- "@rules_license//rules:providers.bzl",
- "LicensesInfo",
+ "@rules_license//rules:gather_licenses_info.bzl",
+ "gather_licenses_info",
)
+load("@rules_license//rules:providers.bzl", "LicenseInfo")
+load("@rules_license//rules/private:gathering_providers.bzl", "TransitiveLicensesInfo")
+# This is a crude example of the kind of thing which can be done.
def _license_policy_check_impl(ctx):
policy = ctx.attr.policy[LicensePolicyInfo]
allowed_conditions = policy.conditions
- if LicensesInfo in ctx.attr.target:
- for license in ctx.attr.target[LicensesInfo].licenses.to_list():
+ if TransitiveLicensesInfo in ctx.attr.target:
+ for license in ctx.attr.target[TransitiveLicensesInfo].licenses.to_list():
+ for kind in license.license_kinds:
+ # print(kind.conditions)
+ for condition in kind.conditions:
+ if condition not in allowed_conditions:
+ fail("Condition %s violates policy %s" % (
+ condition,
+ policy.label,
+ ))
+
+ if LicenseInfo in ctx.attr.target:
+ for license in ctx.attr.target[LicenseInfo].licenses.to_list():
for kind in license.license_kinds:
# print(kind.conditions)
for condition in kind.conditions:
@@ -44,7 +54,7 @@ def _license_policy_check_impl(ctx):
_license_policy_check = rule(
implementation = _license_policy_check_impl,
- doc = """Internal tmplementation method for license_policy_check().""",
+ doc = """Internal implementation method for license_policy_check().""",
attrs = {
"policy": attr.label(
doc = """Policy definition.""",
diff --git a/rules/license_policy_provider.bzl b/examples/policy_checker/license_policy_provider.bzl
index caecce8..caecce8 100644
--- a/rules/license_policy_provider.bzl
+++ b/examples/policy_checker/license_policy_provider.bzl
diff --git a/examples/sboms/BUILD b/examples/sboms/BUILD
new file mode 100644
index 0000000..0c31a04
--- /dev/null
+++ b/examples/sboms/BUILD
@@ -0,0 +1,13 @@
+# Demonstrate the generate_sbom rule
+
+load("@rules_license//rules:sbom.bzl", "generate_sbom")
+
+# There are not a lot of targets in this rule set to build a SBOM from
+# so we will (in a very self-referential way) generate one for the tool
+# which generates the SBOMs
+# See the output in bazel-bin/examples/sboms/write_sbom.txt
+generate_sbom(
+ name = "write_sbom_sbom",
+ out = "write_sbom.txt",
+ deps = ["//tools:write_sbom"],
+)
diff --git a/examples/src/BUILD b/examples/src/BUILD
index 29b6803..cd5e985 100644
--- a/examples/src/BUILD
+++ b/examples/src/BUILD
@@ -1,17 +1,30 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
# Examples of applications and interactions with licenses
-load("@rules_license//examples/vendor/constant_gen:defs.bzl", "constant_gen")
-load("@rules_license//rules:compliance.bzl", "licenses_used")
-load("@rules_license//rules:license_policy_check.bzl", "license_policy_check")
-load("@rules_license//tools:test_helpers.bzl", "golden_test")
+load("@rules_license//rules:compliance.bzl", "check_license", "licenses_used")
+load("@rules_license//examples/vndor/constant_gen:defs.bzl", "constant_gen")
+
+package(
+ default_package_metadata = ["//:license", "//:package_info"],
+ default_visibility = ["//examples:__subpackages__"],
+)
cc_binary(
name = "my_server",
srcs = ["server.cc"],
- deps = [
- ":message",
- "@rules_license//examples/vendor/libhhgttg",
- ],
+ deps = [":message"],
)
# Sample
@@ -21,32 +34,17 @@ constant_gen(
var = "server_message",
)
-license_policy_check(
+# TODO(aiuto): Turn this strictly into a compliance test.
+check_license(
name = "check_server",
- policy = "@rules_license//examples/my_org/compliance:production_service",
- target = ":my_server",
-)
-
-cc_binary(
- name = "my_violating_server",
- srcs = ["server.cc"],
+ check_conditions = False,
+ license_texts = "server_licenses.txt",
+ report = "server_report.txt",
deps = [
- ":message",
- "@rules_license//examples/vendor/acme",
- "@rules_license//examples/vendor/libhhgttg",
+ ":my_server",
],
)
-license_policy_check(
- name = "check_violating_server",
- policy = "@rules_license//examples/my_org/compliance:production_service",
- tags = [
- "manual",
- "notap",
- ],
- target = ":my_violating_server",
-)
-
#
# Verify the licenses are what we expect. The golden output shows that
# :my_server only uses the unencumbered license type.
@@ -57,8 +55,23 @@ licenses_used(
deps = [":my_server"],
)
-golden_test(
- name = "verify_server_licenses_test",
- golden = "server_licenses.golden",
- subject = ":server_licenses.json",
+py_test(
+ name = "server_licenses_test",
+ srcs = ["server_licenses_test.py"],
+ data = [":server_licenses.json"],
+ python_version = "PY3",
+ deps = [
+ "@rules_license//tests:license_test_utils",
+ ],
+)
+
+# This server uses something under a restricted license
+cc_binary(
+ name = "my_violating_server",
+ srcs = ["server.cc"],
+ deps = [
+ ":message",
+ "@rules_license//examples/vndor/acme",
+ "@rules_license//examples/vndor/libhhgttg",
+ ],
)
diff --git a/examples/src/server.cc b/examples/src/server.cc
index 8f7990e..8229fc1 100644
--- a/examples/src/server.cc
+++ b/examples/src/server.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,8 +11,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
#include <iostream>
+#include <ostream>
extern const char* server_message;
diff --git a/examples/src/server_licenses.golden b/examples/src/server_licenses.golden
deleted file mode 100644
index 57df1b3..0000000
--- a/examples/src/server_licenses.golden
+++ /dev/null
@@ -1,32 +0,0 @@
-[
- {
- "rule": "//examples/vendor/constant_gen:license_for_emitted_code",
- "license_kinds": [
- {
- "target": "@//examples/my_org/licenses:unencumbered",
- "name": "unencumbered",
- "conditions": []
- }
- ],
- "copyright_notice": "",
- "package_name": "Trivial Code Generator Output",
- "package_url": null,
- "package_version": null,
- "license_text": "examples/vendor/constant_gen/LICENSE"
- },
- {
- "rule": "//examples/vendor/libhhgttg:license",
- "license_kinds": [
- {
- "target": "@//examples/my_org/licenses:generic_notice",
- "name": "generic_notice",
- "conditions": ["notice"]
- }
- ],
- "copyright_notice": "",
- "package_name": "",
- "package_url": null,
- "package_version": null,
- "license_text": "examples/vendor/libhhgttg/LICENSE"
- }
-]
diff --git a/examples/src/server_licenses_test.py b/examples/src/server_licenses_test.py
new file mode 100644
index 0000000..66a20a5
--- /dev/null
+++ b/examples/src/server_licenses_test.py
@@ -0,0 +1,46 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for license/examples/src."""
+
+import os
+
+import unittest
+from tests import license_test_utils
+
+
+class ServerLicensesTest(unittest.TestCase):
+
+ def test_has_expected_licenses(self):
+ package_base = license_test_utils.LICENSE_PACKAGE_BASE
+ licenses_info = license_test_utils.load_licenses_info(
+ os.path.join(os.path.dirname(__file__), "server_licenses.json"))
+ licenses_info = license_test_utils.filter_dependencies(
+ licenses_info,
+ target_filter=lambda targ: targ.startswith(package_base),
+ licenses_filter=lambda lic: lic.startswith(package_base))
+
+ expected = {
+ "/examples/src:message_src_": [
+ "/examples/vndor/constant_gen:license_for_emitted_code"
+ ],
+ "/examples/src:message": [
+ "/examples/vndor/constant_gen:license_for_emitted_code"
+ ],
+ }
+ license_test_utils.check_licenses_of_dependencies(
+ self, licenses_info, expected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/src/server_report.golden b/examples/src/server_report.golden
deleted file mode 100644
index 6d322b1..0000000
--- a/examples/src/server_report.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-= @rules_license//examples/vendor/constant_gen:license_for_emitted_code
- kind: @@rules_license//examples/my_org/licenses:unencumbered
- conditions: []
diff --git a/examples/vendor/acme/BUILD b/examples/vendor/acme/BUILD
deleted file mode 100644
index 488af62..0000000
--- a/examples/vendor/acme/BUILD
+++ /dev/null
@@ -1,22 +0,0 @@
-# A package with a commercial license.
-
-load("@rules_license//rules:license.bzl", "license")
-
-package(
- default_applicable_licenses = [":license"],
- default_visibility = ["//visibility:public"],
-)
-
-# The default license for an entire package is typically named "license".
-license(
- name = "license",
- license_kinds = [
- "@rules_license//examples/my_org/licenses:acme_corp_paid",
- ],
- license_text = "ACME_LICENSE",
-)
-
-cc_library(
- name = "acme",
- srcs = ["coyote.cc"],
-)
diff --git a/examples/vendor/constant_gen/generated_code_licenses.golden b/examples/vendor/constant_gen/generated_code_licenses.golden
deleted file mode 100644
index 6aef78a..0000000
--- a/examples/vendor/constant_gen/generated_code_licenses.golden
+++ /dev/null
@@ -1,17 +0,0 @@
-[
- {
- "rule": "//examples/vendor/constant_gen:license_for_emitted_code",
- "license_kinds": [
- {
- "target": "@//examples/my_org/licenses:unencumbered",
- "name": "unencumbered",
- "conditions": []
- }
- ],
- "copyright_notice": "",
- "package_name": "Trivial Code Generator Output",
- "package_url": null,
- "package_version": null,
- "license_text": "examples/vendor/constant_gen/LICENSE"
- }
-]
diff --git a/examples/vendor/constant_gen/generator_licenses.golden b/examples/vendor/constant_gen/generator_licenses.golden
deleted file mode 100644
index f6fa349..0000000
--- a/examples/vendor/constant_gen/generator_licenses.golden
+++ /dev/null
@@ -1,17 +0,0 @@
-[
- {
- "rule": "//examples/vendor/constant_gen:license",
- "license_kinds": [
- {
- "target": "@//examples/my_org/licenses:generic_restricted",
- "name": "generic_restricted",
- "conditions": ["restricted"]
- }
- ],
- "copyright_notice": "",
- "package_name": "Trivial Code Generator",
- "package_url": "http://github.com/tgc-fake/tgc.tgz",
- "package_version": "3.14",
- "license_text": "examples/vendor/constant_gen/LICENSE"
- }
-]
diff --git a/examples/vendor/libhhgttg/BUILD b/examples/vendor/libhhgttg/BUILD
deleted file mode 100644
index c5da389..0000000
--- a/examples/vendor/libhhgttg/BUILD
+++ /dev/null
@@ -1,25 +0,0 @@
-# A package with all code under a single license. This is the most common case
-# we expect to see.
-
-load("@rules_license//rules:license.bzl", "license")
-
-# Using a package wide default ensure that all targets are associated with the
-# license.
-package(
- default_applicable_licenses = [":license"],
- default_visibility = ["//visibility:public"],
-)
-
-# The default license for an entire package is typically named "license".
-license(
- name = "license",
- license_kinds = [
- "@rules_license//examples/my_org/licenses:generic_notice",
- ],
- license_text = "LICENSE",
-)
-
-cc_library(
- name = "libhhgttg",
- srcs = ["answer.cc"],
-)
diff --git a/examples/vendor/README.md b/examples/vndor/README.md
index 273dea6..273dea6 100644
--- a/examples/vendor/README.md
+++ b/examples/vndor/README.md
diff --git a/examples/vendor/acme/ACME_LICENSE b/examples/vndor/acme/ACME_LICENSE
index 53a5daf..53a5daf 100644
--- a/examples/vendor/acme/ACME_LICENSE
+++ b/examples/vndor/acme/ACME_LICENSE
diff --git a/examples/vndor/acme/BUILD b/examples/vndor/acme/BUILD
new file mode 100644
index 0000000..09da19d
--- /dev/null
+++ b/examples/vndor/acme/BUILD
@@ -0,0 +1,35 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# A package with a commercial license.
+
+load("@rules_license//rules:license.bzl", "license")
+
+package(
+ default_applicable_licenses = [":license"],
+ default_visibility = ["//examples:__subpackages__"],
+)
+
+# The default license for an entire package is typically named "license".
+license(
+ name = "license",
+ license_kinds = [
+ "@rules_license//examples/my_org/licenses:acme_corp_paid",
+ ],
+ license_text = "ACME_LICENSE",
+)
+
+cc_library(
+ name = "acme",
+ srcs = ["coyote.cc"],
+)
diff --git a/examples/vendor/acme/coyote.cc b/examples/vndor/acme/coyote.cc
index e1e8083..d637855 100644
--- a/examples/vendor/acme/coyote.cc
+++ b/examples/vndor/acme/coyote.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,7 +11,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
bool caught_road_runner() {
return false;
}
diff --git a/examples/vendor/constant_gen/BUILD b/examples/vndor/constant_gen/BUILD
index a81885c..5f2ff43 100644
--- a/examples/vendor/constant_gen/BUILD
+++ b/examples/vndor/constant_gen/BUILD
@@ -1,34 +1,44 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
# An example of a code generator with a distinct license for the generated code.
load("@rules_license//rules:compliance.bzl", "licenses_used")
load("@rules_license//rules:license.bzl", "license")
-load("@rules_license//tools:test_helpers.bzl", "golden_test")
load(":defs.bzl", "constant_gen")
package(
default_applicable_licenses = [":license"],
- default_visibility = ["//visibility:public"],
+ default_visibility = ["//examples:__subpackages__"],
)
# The default license for an entire package is typically named "license".
license(
name = "license",
+ package_name = "Trivial Code Generator",
license_kinds = [
"@rules_license//examples/my_org/licenses:generic_restricted",
],
license_text = "LICENSE",
- package_name = "Trivial Code Generator",
- package_url = "http://github.com/tgc-fake/tgc.tgz",
- package_version = "3.14",
)
license(
name = "license_for_emitted_code",
package_name = "Trivial Code Generator Output",
- license = "LICENSE.on_output",
license_kinds = [
"@rules_license//examples/my_org/licenses:unencumbered",
],
+ license_text = "LICENSE.on_output",
)
# The generator itself will be licensed under :license
@@ -52,20 +62,21 @@ licenses_used(
deps = [":constant_generator"],
)
-golden_test(
- name = "verify_generator_licenses",
- golden = "generator_licenses.golden",
- subject = ":generator_licenses.json",
-)
-
licenses_used(
name = "generated_code_licenses",
# Note: using default output file name
deps = [":libhello"],
)
-golden_test(
- name = "verify_generated_code_licenses",
- golden = "generated_code_licenses.golden",
- subject = ":generated_code_licenses.json",
+py_test(
+ name = "verify_licenses_test",
+ srcs = ["verify_licenses_test.py"],
+ data = [
+ ":generator_licenses.json",
+ ":generated_code_licenses.json",
+ ],
+ python_version = "PY3",
+ deps = [
+ "//tests:license_test_utils",
+ ],
)
diff --git a/examples/vendor/constant_gen/LICENSE b/examples/vndor/constant_gen/LICENSE
index 861da0d..861da0d 100644
--- a/examples/vendor/constant_gen/LICENSE
+++ b/examples/vndor/constant_gen/LICENSE
diff --git a/examples/vendor/constant_gen/LICENSE.on_output b/examples/vndor/constant_gen/LICENSE.on_output
index b699638..b699638 100644
--- a/examples/vendor/constant_gen/LICENSE.on_output
+++ b/examples/vndor/constant_gen/LICENSE.on_output
diff --git a/examples/vendor/constant_gen/constant_generator.py b/examples/vndor/constant_gen/constant_generator.py
index 6bd92b2..432b6be 100644
--- a/examples/vendor/constant_gen/constant_generator.py
+++ b/examples/vndor/constant_gen/constant_generator.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,8 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
-# Lint as: python3
"""A trivial tool to turn a string into a C++ constant.
This is not meant to be useful. It is only to provide an example of a tool that
diff --git a/examples/vendor/constant_gen/defs.bzl b/examples/vndor/constant_gen/defs.bzl
index e54fcee..518437c 100644
--- a/examples/vendor/constant_gen/defs.bzl
+++ b/examples/vndor/constant_gen/defs.bzl
@@ -1,5 +1,3 @@
-"""A trivial rule to turn a string into a C++ constant."""
-
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""A trivial rule to turn a string into a C++ constant."""
def _constant_gen_impl(ctx):
# Turn text into a C++ constant.
@@ -33,7 +32,7 @@ _constant_gen = rule(
"text": attr.string(mandatory = True),
"var": attr.string(mandatory = False),
"_generator": attr.label(
- default = Label("@rules_license//examples/vendor/constant_gen:constant_generator"),
+ default = Label("@rules_license//examples/vndor/constant_gen:constant_generator"),
executable = True,
allow_files = True,
cfg = "exec",
@@ -48,12 +47,12 @@ def constant_gen(name, text, var):
src_out = name + "_src_.cc",
text = text,
var = var,
- applicable_licenses = ["@rules_license//examples/vendor/constant_gen:license_for_emitted_code"],
+ applicable_licenses = ["@rules_license//examples/vndor/constant_gen:license_for_emitted_code"],
)
# And turn it into a library we can link against
native.cc_library(
name = name,
srcs = [name + "_src_"],
- applicable_licenses = ["@rules_license//examples/vendor/constant_gen:license_for_emitted_code"],
+ applicable_licenses = ["@rules_license//examples/vndor/constant_gen:license_for_emitted_code"],
)
diff --git a/examples/vndor/constant_gen/verify_licenses_test.py b/examples/vndor/constant_gen/verify_licenses_test.py
new file mode 100644
index 0000000..917e600
--- /dev/null
+++ b/examples/vndor/constant_gen/verify_licenses_test.py
@@ -0,0 +1,40 @@
+"""Test that we see the expected licenses for some generated code."""
+
+import codecs
+import os
+
+import unittest
+from tests import license_test_utils
+
+class VerifyLicensesTest(unittest.TestCase):
+
+ def test_generater_license(self):
+ licenses_info = license_test_utils.load_licenses_info(
+ os.path.join(os.path.dirname(__file__), "generator_licenses.json"))
+
+ expected = {
+ "examples/vndor/constant_gen:constant_generator": [
+ "examples/vndor/constant_gen:license"
+ ],
+ }
+ license_test_utils.check_licenses_of_dependencies(
+ self, licenses_info, expected)
+
+ def test_generated_code_license(self):
+ licenses_info = license_test_utils.load_licenses_info(
+ os.path.join(os.path.dirname(__file__), "generated_code_licenses.json"))
+
+ expected = {
+ "examples/vndor/constant_gen:libhello": [
+ "/constant_gen:license_for_emitted_code",
+ ],
+ "examples/vndor/constant_gen:libhello_src_": [
+ "/constant_gen:license_for_emitted_code",
+ ],
+ }
+ license_test_utils.check_licenses_of_dependencies(
+ self, licenses_info, expected)
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/examples/vndor/libhhgttg/BUILD b/examples/vndor/libhhgttg/BUILD
new file mode 100644
index 0000000..b9e3991
--- /dev/null
+++ b/examples/vndor/libhhgttg/BUILD
@@ -0,0 +1,38 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# A package with all code under a single license. This is the most common case
+# we expect to see.
+
+load("@rules_license//rules:license.bzl", "license")
+
+# Using a package wide default ensure that all targets are associated with the
+# license.
+package(
+ default_applicable_licenses = [":license"],
+ default_visibility = ["//examples:__subpackages__"],
+)
+
+# The default license for an entire package is typically named "license".
+license(
+ name = "license",
+ license_kinds = [
+ "@rules_license//examples/my_org/licenses:generic_notice",
+ ],
+ license_text = "LICENSE",
+)
+
+cc_library(
+ name = "libhhgttg",
+ srcs = ["answer.cc"],
+)
diff --git a/examples/vendor/libhhgttg/LICENSE b/examples/vndor/libhhgttg/LICENSE
index 660e329..660e329 100644
--- a/examples/vendor/libhhgttg/LICENSE
+++ b/examples/vndor/libhhgttg/LICENSE
diff --git a/examples/vendor/libhhgttg/answer.cc b/examples/vndor/libhhgttg/answer.cc
index 8b78f90..440bc62 100644
--- a/examples/vendor/libhhgttg/answer.cc
+++ b/examples/vndor/libhhgttg/answer.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/licenses/generic/BUILD b/licenses/generic/BUILD
index ba95d52..def2334 100644
--- a/licenses/generic/BUILD
+++ b/licenses/generic/BUILD
@@ -37,16 +37,18 @@
# of the well known licenses in @rules_license//licenses/spdx:*
load("@rules_license//rules:license_kind.bzl", "license_kind")
-filegroup(
- name = "standard_package",
- srcs = ["BUILD"],
-)
-
package(
default_applicable_licenses = ["//:license"],
default_visibility = ["//visibility:public"],
)
+licenses(["notice"])
+
+filegroup(
+ name = "standard_package",
+ srcs = ["BUILD"],
+)
+
# "none" should be used for packages which are distributed with no license of
# any kind. You can use this no-op license as a positive indication that the
# code's license terms were reviewed, so that linters will not flag it later as
@@ -84,6 +86,14 @@ license_kind(
],
)
+# See: https://opensource.google/docs/thirdparty/licenses/#restricted
+license_kind(
+ name = "restricted",
+ conditions = [
+ "restricted",
+ ],
+)
+
# See: https://opensource.google/docs/thirdparty/licenses/#ByExceptionOnly
license_kind(
name = "by_exception_only",
diff --git a/licenses/spdx/BUILD b/licenses/spdx/BUILD
index 2ab4f35..feb0580 100644
--- a/licenses/spdx/BUILD
+++ b/licenses/spdx/BUILD
@@ -59,6 +59,8 @@ package(
default_visibility = ["//visibility:public"],
)
+licenses(["notice"])
+
filegroup(
name = "standard_package",
srcs = ["BUILD"],
diff --git a/rules/BUILD b/rules/BUILD
index f0fd218..83e8c14 100644
--- a/rules/BUILD
+++ b/rules/BUILD
@@ -15,12 +15,34 @@
# limitations under the License.
"""Rules for making license declarations."""
+load("@rules_license//rules:licenses_core.bzl", "trace")
+
package(
default_applicable_licenses = ["//:license"],
default_visibility = ["//visibility:public"],
)
+licenses(["notice"])
+
+# This target controls the value of the traced target used during dependency collection.
+# This value should always be the empty string!
+# Specify this value with a flag, like --@rules_license//rules:trace_target=//target/to:trace
+trace(
+ name = "trace_target",
+ build_setting_default = "", # TRACE-TARGET-SHOULD-BE-EMPTY
+ visibility = ["//visibility:public"],
+)
+
filegroup(
name = "standard_package",
srcs = glob(["**"]),
)
+
+# Do not create a bzl_library(). That would create a dependency loop back
+# to bazel-skylib. We export the .bzl files to the documentation maker.
+exports_files(
+ glob([
+ "*.bzl",
+ ]),
+ visibility = ["//doc_build:__pkg__"],
+)
diff --git a/rules/check_licenses_shim.bzl b/rules/check_licenses_shim.bzl
new file mode 100644
index 0000000..3dcfe2c
--- /dev/null
+++ b/rules/check_licenses_shim.bzl
@@ -0,0 +1,30 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""This module provides a custom Starlark rule used to create wrappers for targets that
+can have blaze build --check_licenses executed against them."""
+
+def _shim_rule_impl(ctx):
+ # This rule doesn't need to return anything. It only exists to propagate the dependency supplied
+ # by the label_flag
+ return []
+
+shim_rule = rule(
+ doc = """This rule exists to configure a dependent target via label. An instantiation of this
+ rule is then used as a dependency for the legacy_check_target rule, which can be built with --check_licenses
+ to get the effect of running --check_licenses on an arbitrary target which may or may not have a distribs
+ attribute""",
+ implementation = _shim_rule_impl,
+ # The definition of this attribute creates a dependency relationship on the manually provided label.
+ attrs = {"target": attr.label(default = ":check_licenses_target")},
+)
diff --git a/rules/compliance.bzl b/rules/compliance.bzl
index a30de9e..2fb04ab 100644
--- a/rules/compliance.bzl
+++ b/rules/compliance.bzl
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,40 +11,30 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
-"""Proof of concept. License compliance checking."""
+"""License compliance checking."""
load(
"@rules_license//rules:gather_licenses_info.bzl",
"gather_licenses_info",
+ "gather_licenses_info_and_write",
"write_licenses_info",
)
load(
- "@rules_license//rules:providers.bzl",
- "LicensesInfo",
+ "@rules_license//rules/private:gathering_providers.bzl",
+ "TransitiveLicensesInfo",
)
-# Debugging verbosity
-_VERBOSITY = 0
-
-def _debug(loglevel, msg):
- if _VERBOSITY > loglevel:
- print(msg) # buildifier: disable=print
-
+# This rule is proof of concept, and may not represent the final
+# form of a rule for compliance validation.
def _check_license_impl(ctx):
# Gather all licenses and write information to one place
- _debug(0, "Check license: %s" % ctx.label)
-
licenses_file = ctx.actions.declare_file("_%s_licenses_info.json" % ctx.label.name)
write_licenses_info(ctx, ctx.attr.deps, licenses_file)
license_files = []
if ctx.outputs.license_texts:
- for dep in ctx.attr.deps:
- if LicensesInfo in dep:
- for license in dep[LicensesInfo].licenses.to_list():
- license_files.append(license.license_text)
+ license_files = get_licenses_mapping(ctx.attr.deps).keys()
# Now run the checker on it
inputs = [licenses_file]
@@ -75,11 +65,11 @@ def _check_license_impl(ctx):
_check_license = rule(
implementation = _check_license_impl,
attrs = {
- "check_conditions": attr.bool(default = True, mandatory = False),
- "copyright_notices": attr.output(mandatory = False),
"deps": attr.label_list(
aspects = [gather_licenses_info],
),
+ "check_conditions": attr.bool(default = True, mandatory = False),
+ "copyright_notices": attr.output(mandatory = False),
"license_texts": attr.output(mandatory = False),
"report": attr.output(mandatory = True),
"_checker": attr.label(
@@ -91,11 +81,46 @@ _check_license = rule(
},
)
+# TODO(b/152546336): Update the check to take a pointer to a condition list.
def check_license(**kwargs):
_check_license(**kwargs)
+def _manifest_impl(ctx):
+ # Gather all licenses and make it available as deps for downstream rules
+ # Additionally write the list of license filenames to a file that can
+ # also be used as an input to downstream rules.
+ licenses_file = ctx.actions.declare_file(ctx.attr.out.name)
+ mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses)
+ ctx.actions.write(
+ output = licenses_file,
+ content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]),
+ )
+ return [DefaultInfo(files = depset(mappings.keys()))]
+
+_manifest = rule(
+ implementation = _manifest_impl,
+ doc = """Internal tmplementation method for manifest().""",
+ attrs = {
+ "deps": attr.label_list(
+ doc = """List of targets to collect license files for.""",
+ aspects = [gather_licenses_info],
+ ),
+ "out": attr.output(
+ doc = """Output file.""",
+ mandatory = True,
+ ),
+ "warn_on_legacy_licenses": attr.bool(default = False),
+ },
+)
+
+def manifest(name, deps, out = None, **kwargs):
+ if not out:
+ out = name + ".manifest"
+
+ _manifest(name = name, deps = deps, out = out, **kwargs)
+
def _licenses_used_impl(ctx):
- """Gather all licenses and make it available as JSON."""
+ # Gather all licenses and make it available as JSON
write_licenses_info(ctx, ctx.attr.deps, ctx.outputs.out)
return [DefaultInfo(files = depset([ctx.outputs.out]))]
@@ -105,7 +130,7 @@ _licenses_used = rule(
attrs = {
"deps": attr.label_list(
doc = """List of targets to collect LicenseInfo for.""",
- aspects = [gather_licenses_info],
+ aspects = [gather_licenses_info_and_write],
),
"out": attr.output(
doc = """Output file.""",
@@ -114,6 +139,38 @@ _licenses_used = rule(
},
)
+def get_licenses_mapping(deps, warn = False):
+ """Creates list of entries representing all licenses for the deps.
+
+ Args:
+
+ deps: a list of deps which should have TransitiveLicensesInfo providers.
+ This requires that you have run the gather_licenses_info
+ aspect over them
+
+ warn: boolean, if true, display output about legacy targets that need
+ update
+
+ Returns:
+ {File:package_name}
+ """
+ tls = []
+ for dep in deps:
+ lds = dep[TransitiveLicensesInfo].licenses
+ tls.append(lds)
+
+ ds = depset(transitive = tls)
+
+ # Ignore any legacy licenses that may be in the report
+ mappings = {}
+ for lic in ds.to_list():
+ if type(lic.license_text) == "File":
+ mappings[lic.license_text] = lic.package_name
+ elif warn:
+ print("Legacy license %s not included, rule needs updating" % lic.license_text)
+
+ return mappings
+
def licenses_used(name, deps, out = None, **kwargs):
"""Collects LicensedInfo providers for a set of targets and writes as JSON.
diff --git a/rules/default_license.bzl b/rules/default_license.bzl
deleted file mode 100644
index 57a7147..0000000
--- a/rules/default_license.bzl
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Proof of concept. License restriction."""
-
-load(
- "@rules_license//rules:providers.bzl",
- "LicenseInfo",
- "LicensesInfo",
-)
-
-# An experiment to provide license defaults via a rule. This is far from
-# working and should not be considered part of the current design.
-#
-
-def _default_licenses_impl(ctx):
- licenses = []
- for dep in ctx.attr.deps:
- if LicenseInfo in dep:
- licenses.append(dep[LicenseInfo])
- return [LicensesInfo(licenses = licenses)]
-
-_default_licenses = rule(
- implementation = _default_licenses_impl,
- attrs = {
- "conditions": attr.string_list(
- doc = "TBD",
- ),
- "deps": attr.label_list(
- mandatory = True,
- doc = "Licenses",
- providers = [LicenseInfo],
- cfg = "exec",
- ),
- },
-)
-
-# buildifier: disable=unnamed-macro
-def default_licenses(licenses, conditions = None):
- _default_licenses(
- name = "__default_licenses",
- deps = ["%s_license" % license for license in licenses],
- conditions = conditions,
- )
diff --git a/rules/filtered_rule_kinds.bzl b/rules/filtered_rule_kinds.bzl
new file mode 100644
index 0000000..1d6f01c
--- /dev/null
+++ b/rules/filtered_rule_kinds.bzl
@@ -0,0 +1,47 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Filtered rule kinds for aspect inspection.
+The format of this dictionary is:
+
+ rule_name: [attr, attr, ...]
+
+Only filters for rules that are part of the Bazel distribution should be added
+to this file. Other filters should be added in user_filtered_rule_kinds.bzl
+
+Attributes are either the explicit list of attributes to filter, or '_*' which
+would ignore all attributes prefixed with a _.
+"""
+
+# Rule kinds with attributes the aspect currently needs to ignore
+aspect_filters = {
+ "*": ["linter"],
+ "_constant_gen": ["_generator"],
+ "cc_binary": ["_*"],
+ "cc_embed_data": ["_*"],
+ "cc_grpc_library": ["_*"],
+ "cc_library": ["_*"],
+ "cc_toolchain_alias": ["_cc_toolchain"],
+ "genrule": ["tools", "exec_tools", "toolchains"],
+ "genyacc": ["_*"],
+ "go_binary": ["_*"],
+ "go_library": ["_*"],
+ "go_wrap_cc": ["_*"],
+ "java_binary": ["_*", "plugins", "exported_plugins"],
+ "java_library": ["plugins", "exported_plugins"],
+ "java_wrap_cc": ["_cc_toolchain", "swig_top"],
+ "py_binary": ["_*"],
+ "py_extension": ["_cc_toolchain"],
+ "sh_binary": ["_bash_binary"],
+}
diff --git a/rules/gather_licenses_info.bzl b/rules/gather_licenses_info.bzl
index bd8c210..9dd1cbc 100644
--- a/rules/gather_licenses_info.bzl
+++ b/rules/gather_licenses_info.bzl
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,66 +11,115 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
"""Rules and macros for collecting LicenseInfo providers."""
load(
- "@rules_license//rules:providers.bzl",
- "LicenseInfo",
- "LicensesInfo",
+ "@rules_license//rules:licenses_core.bzl",
+ "TraceInfo",
+ "gather_metadata_info_common",
+ "should_traverse",
+)
+load(
+ "@rules_license//rules/private:gathering_providers.bzl",
+ "TransitiveLicensesInfo",
)
-# Debugging verbosity
-_VERBOSITY = 0
+# Definition for compliance namespace, used for filtering licenses
+# based on the namespace to which they belong.
+NAMESPACES = ["compliance"]
-def _debug(loglevel, msg):
- if _VERBOSITY > loglevel:
- print(msg) # buildifier: disable=print
+def _strip_null_repo(label):
+ """Removes the null repo name (e.g. @//) from a string.
-def _get_transitive_licenses(deps, licenses, trans):
- for dep in deps:
- if LicenseInfo in dep:
- license = dep[LicenseInfo]
- _debug(1, " depends on license: %s" % license.rule)
- licenses.append(license)
- if LicensesInfo in dep:
- license_list = dep[LicensesInfo].licenses
- if license_list:
- _debug(1, " transitively depends on: %s" % licenses)
- trans.append(license_list)
+ The is to make str(label) compatible between bazel 5.x and 6.x
+ """
+ s = str(label)
+ if s.startswith('@//'):
+ return s[1:]
+ elif s.startswith('@@//'):
+ return s[2:]
+ return s
def _gather_licenses_info_impl(target, ctx):
- licenses = []
- trans = []
- if hasattr(ctx.rule.attr, "applicable_licenses"):
- _get_transitive_licenses(ctx.rule.attr.applicable_licenses, licenses, trans)
- if hasattr(ctx.rule.attr, "deps"):
- _get_transitive_licenses(ctx.rule.attr.deps, licenses, trans)
- if hasattr(ctx.rule.attr, "srcs"):
- _get_transitive_licenses(ctx.rule.attr.srcs, licenses, trans)
- return [LicensesInfo(licenses = depset(tuple(licenses), transitive = trans))]
+ return gather_metadata_info_common(target, ctx, TransitiveLicensesInfo, NAMESPACES, [], should_traverse)
gather_licenses_info = aspect(
- doc = """Collects LicenseInfo providers into a single LicensesInfo provider.""",
+ doc = """Collects LicenseInfo providers into a single TransitiveLicensesInfo provider.""",
implementation = _gather_licenses_info_impl,
- attr_aspects = ["applicable_licenses", "deps", "srcs"],
+ attr_aspects = ["*"],
+ attrs = {
+ "_trace": attr.label(default = "@rules_license//rules:trace_target"),
+ },
+ provides = [TransitiveLicensesInfo],
apply_to_generating_rules = True,
)
-def _quotes_or_null(s):
- if not s:
- return "null"
- return '"%s"' % s
+def _write_licenses_info_impl(target, ctx):
+ """Write transitive license info into a JSON file
+
+ Args:
+ target: The target of the aspect.
+ ctx: The aspect evaluation context.
+
+ Returns:
+ OutputGroupInfo
+ """
+
+ if not TransitiveLicensesInfo in target:
+ return [OutputGroupInfo(licenses = depset())]
+ info = target[TransitiveLicensesInfo]
+ outs = []
+
+ # If the result doesn't contain licenses, we simply return the provider
+ if not hasattr(info, "target_under_license"):
+ return [OutputGroupInfo(licenses = depset())]
+
+ # Write the output file for the target
+ name = "%s_licenses_info.json" % ctx.label.name
+ content = "[\n%s\n]\n" % ",\n".join(licenses_info_to_json(info))
+ out = ctx.actions.declare_file(name)
+ ctx.actions.write(
+ output = out,
+ content = content,
+ )
+ outs.append(out)
+
+ if ctx.attr._trace[TraceInfo].trace:
+ trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name)
+ ctx.actions.write(output = trace, content = "\n".join(info.traces))
+ outs.append(trace)
+
+ return [OutputGroupInfo(licenses = depset(outs))]
+
+gather_licenses_info_and_write = aspect(
+ doc = """Collects TransitiveLicensesInfo providers and writes JSON representation to a file.
+
+ Usage:
+ blaze build //some:target \
+ --aspects=@rules_license//rules:gather_licenses_info.bzl%gather_licenses_info_and_write
+ --output_groups=licenses
+ """,
+ implementation = _write_licenses_info_impl,
+ attr_aspects = ["*"],
+ attrs = {
+ "_trace": attr.label(default = "@rules_license//rules:trace_target"),
+ },
+ provides = [OutputGroupInfo],
+ requires = [gather_licenses_info],
+ apply_to_generating_rules = True,
+)
def write_licenses_info(ctx, deps, json_out):
- """Writes LicensesInfo providers for a set of targets as JSON.
+ """Writes TransitiveLicensesInfo providers for a set of targets as JSON.
- TODO(aiuto): Document JSON schema.
+ TODO(aiuto): Document JSON schema. But it is under development, so the current
+ best place to look is at tests/hello_licenses.golden.
Usage:
write_licenses_info must be called from a rule implementation, where the
- rule has run the gather_licenses_info aspect on its deps to collect the
- transitive closure of LicenseInfo providers into a LicenseInfo provider.
+ rule has run the gather_licenses_info aspect on its deps to
+ collect the transitive closure of LicenseInfo providers into a
+ LicenseInfo provider.
foo = rule(
implementation = _foo_impl,
@@ -86,51 +135,116 @@ def write_licenses_info(ctx, deps, json_out):
Args:
ctx: context of the caller
- deps: a list of deps which should have LicensesInfo providers.
+ deps: a list of deps which should have TransitiveLicensesInfo providers.
This requires that you have run the gather_licenses_info
aspect over them
json_out: output handle to write the JSON info
"""
-
- rule_template = """ {{
- "rule": "{rule}",
- "license_kinds": [{kinds}
- ],
- "copyright_notice": "{copyright_notice}",
- "package_name": "{package_name}",
- "package_url": {package_url},
- "package_version": {package_version},
- "license_text": "{license_text}"\n }}"""
-
- kind_template = """
- {{
- "target": "{kind_path}",
- "name": "{kind_name}",
- "conditions": {kind_conditions}
- }}"""
-
licenses = []
for dep in deps:
- if LicensesInfo in dep:
- for license in dep[LicensesInfo].licenses.to_list():
- _debug(0, " Requires license: %s" % license)
- kinds = []
- for kind in license.license_kinds:
- kinds.append(kind_template.format(
- kind_name = kind.name,
- kind_path = kind.label,
- kind_conditions = kind.conditions,
- ))
- licenses.append(rule_template.format(
- rule = license.rule,
- copyright_notice = license.copyright_notice,
- package_name = license.package_name,
- package_url = _quotes_or_null(license.package_url),
- package_version = _quotes_or_null(license.package_version),
- license_text = license.license_text.path,
- kinds = ",\n".join(kinds),
- ))
+ if TransitiveLicensesInfo in dep:
+ licenses.extend(licenses_info_to_json(dep[TransitiveLicensesInfo]))
ctx.actions.write(
output = json_out,
content = "[\n%s\n]\n" % ",\n".join(licenses),
)
+
+def licenses_info_to_json(licenses_info):
+ """Render a single LicenseInfo provider to JSON
+
+ Args:
+ licenses_info: A LicenseInfo.
+
+ Returns:
+ [(str)] list of LicenseInfo values rendered as JSON.
+ """
+
+ main_template = """ {{
+ "top_level_target": "{top_level_target}",
+ "dependencies": [{dependencies}
+ ],
+ "licenses": [{licenses}
+ ]\n }}"""
+
+ dep_template = """
+ {{
+ "target_under_license": "{target_under_license}",
+ "licenses": [
+ {licenses}
+ ]
+ }}"""
+
+ # TODO(aiuto): 'rule' is a duplicate of 'label' until old users are transitioned
+ license_template = """
+ {{
+ "label": "{label}",
+ "rule": "{label}",
+ "license_kinds": [{kinds}
+ ],
+ "copyright_notice": "{copyright_notice}",
+ "package_name": "{package_name}",
+ "package_url": "{package_url}",
+ "package_version": "{package_version}",
+ "license_text": "{license_text}",
+ "used_by": [
+ {used_by}
+ ]
+ }}"""
+
+ kind_template = """
+ {{
+ "target": "{kind_path}",
+ "name": "{kind_name}",
+ "conditions": {kind_conditions}
+ }}"""
+
+ # Build reverse map of license to user
+ used_by = {}
+ for dep in licenses_info.deps.to_list():
+ # Undo the concatenation applied when stored in the provider.
+ dep_licenses = dep.licenses.split(",")
+ for license in dep_licenses:
+ if license not in used_by:
+ used_by[license] = []
+ used_by[license].append(_strip_null_repo(dep.target_under_license))
+
+ all_licenses = []
+ for license in sorted(licenses_info.licenses.to_list(), key = lambda x: x.label):
+ kinds = []
+ for kind in sorted(license.license_kinds, key = lambda x: x.name):
+ kinds.append(kind_template.format(
+ kind_name = kind.name,
+ kind_path = kind.label,
+ kind_conditions = kind.conditions,
+ ))
+
+ if license.license_text:
+ # Special handling for synthetic LicenseInfo
+ text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path)
+ all_licenses.append(license_template.format(
+ copyright_notice = license.copyright_notice,
+ kinds = ",".join(kinds),
+ license_text = text_path,
+ package_name = license.package_name,
+ package_url = license.package_url,
+ package_version = license.package_version,
+ label = _strip_null_repo(license.label),
+ used_by = ",\n ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])),
+ ))
+
+ all_deps = []
+ for dep in sorted(licenses_info.deps.to_list(), key = lambda x: x.target_under_license):
+ licenses_used = []
+
+ # Undo the concatenation applied when stored in the provider.
+ dep_licenses = dep.licenses.split(",")
+ all_deps.append(dep_template.format(
+ target_under_license = _strip_null_repo(dep.target_under_license),
+ licenses = ",\n ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])),
+ ))
+
+ return [main_template.format(
+ top_level_target = _strip_null_repo(licenses_info.target_under_license),
+ dependencies = ",".join(all_deps),
+ licenses = ",".join(all_licenses),
+ )]
diff --git a/rules/gather_metadata.bzl b/rules/gather_metadata.bzl
new file mode 100644
index 0000000..162ea97
--- /dev/null
+++ b/rules/gather_metadata.bzl
@@ -0,0 +1,309 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Rules and macros for collecting LicenseInfo providers."""
+
+load(
+ "@rules_license//rules:licenses_core.bzl",
+ "TraceInfo",
+ "gather_metadata_info_common",
+ "should_traverse",
+)
+load(
+ "@rules_license//rules:providers.bzl",
+ "ExperimentalMetadataInfo",
+ "PackageInfo",
+)
+load(
+ "@rules_license//rules/private:gathering_providers.bzl",
+ "TransitiveMetadataInfo",
+)
+
+# Definition for compliance namespace, used for filtering licenses
+# based on the namespace to which they belong.
+NAMESPACES = ["compliance"]
+
+def _strip_null_repo(label):
+ """Removes the null repo name (e.g. @//) from a string.
+
+ The is to make str(label) compatible between bazel 5.x and 6.x
+ """
+ s = str(label)
+ if s.startswith('@//'):
+ return s[1:]
+ elif s.startswith('@@//'):
+ return s[2:]
+ return s
+
+def _bazel_package(label):
+ clean_label = _strip_null_repo(label)
+ return clean_label[0:-(len(label.name) + 1)]
+
+def _gather_metadata_info_impl(target, ctx):
+ return gather_metadata_info_common(
+ target,
+ ctx,
+ TransitiveMetadataInfo,
+ NAMESPACES,
+ [ExperimentalMetadataInfo, PackageInfo],
+ should_traverse)
+
+gather_metadata_info = aspect(
+ doc = """Collects LicenseInfo providers into a single TransitiveMetadataInfo provider.""",
+ implementation = _gather_metadata_info_impl,
+ attr_aspects = ["*"],
+ attrs = {
+ "_trace": attr.label(default = "@rules_license//rules:trace_target"),
+ },
+ provides = [TransitiveMetadataInfo],
+ apply_to_generating_rules = True,
+)
+
+def _write_metadata_info_impl(target, ctx):
+ """Write transitive license info into a JSON file
+
+ Args:
+ target: The target of the aspect.
+ ctx: The aspect evaluation context.
+
+ Returns:
+ OutputGroupInfo
+ """
+
+ if not TransitiveMetadataInfo in target:
+ return [OutputGroupInfo(licenses = depset())]
+ info = target[TransitiveMetadataInfo]
+ outs = []
+
+ # If the result doesn't contain licenses, we simply return the provider
+ if not hasattr(info, "target_under_license"):
+ return [OutputGroupInfo(licenses = depset())]
+
+ # Write the output file for the target
+ name = "%s_metadata_info.json" % ctx.label.name
+ content = "[\n%s\n]\n" % ",\n".join(metadata_info_to_json(info))
+ out = ctx.actions.declare_file(name)
+ ctx.actions.write(
+ output = out,
+ content = content,
+ )
+ outs.append(out)
+
+ if ctx.attr._trace[TraceInfo].trace:
+ trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name)
+ ctx.actions.write(output = trace, content = "\n".join(info.traces))
+ outs.append(trace)
+
+ return [OutputGroupInfo(licenses = depset(outs))]
+
+gather_metadata_info_and_write = aspect(
+ doc = """Collects TransitiveMetadataInfo providers and writes JSON representation to a file.
+
+ Usage:
+ bazel build //some:target \
+ --aspects=@rules_license//rules:gather_metadata_info.bzl%gather_metadata_info_and_write
+ --output_groups=licenses
+ """,
+ implementation = _write_metadata_info_impl,
+ attr_aspects = ["*"],
+ attrs = {
+ "_trace": attr.label(default = "@rules_license//rules:trace_target"),
+ },
+ provides = [OutputGroupInfo],
+ requires = [gather_metadata_info],
+ apply_to_generating_rules = True,
+)
+
+def write_metadata_info(ctx, deps, json_out):
+ """Writes TransitiveMetadataInfo providers for a set of targets as JSON.
+
+ TODO(aiuto): Document JSON schema. But it is under development, so the current
+ best place to look is at tests/hello_licenses.golden.
+
+ Usage:
+ write_metadata_info must be called from a rule implementation, where the
+ rule has run the gather_metadata_info aspect on its deps to
+ collect the transitive closure of LicenseInfo providers into a
+ LicenseInfo provider.
+
+ foo = rule(
+ implementation = _foo_impl,
+ attrs = {
+ "deps": attr.label_list(aspects = [gather_metadata_info])
+ }
+ )
+
+ def _foo_impl(ctx):
+ ...
+ out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name)
+ write_metadata_info(ctx, ctx.attr.deps, metadata_file)
+
+ Args:
+ ctx: context of the caller
+ deps: a list of deps which should have TransitiveMetadataInfo providers.
+ This requires that you have run the gather_metadata_info
+ aspect over them
+ json_out: output handle to write the JSON info
+ """
+ licenses = []
+ for dep in deps:
+ if TransitiveMetadataInfo in dep:
+ licenses.extend(metadata_info_to_json(dep[TransitiveMetadataInfo]))
+ ctx.actions.write(
+ output = json_out,
+ content = "[\n%s\n]\n" % ",\n".join(licenses),
+ )
+
+def metadata_info_to_json(metadata_info):
+ """Render a single LicenseInfo provider to JSON
+
+ Args:
+ metadata_info: A LicenseInfo.
+
+ Returns:
+ [(str)] list of LicenseInfo values rendered as JSON.
+ """
+
+ main_template = """ {{
+ "top_level_target": "{top_level_target}",
+ "dependencies": [{dependencies}
+ ],
+ "licenses": [{licenses}
+ ],
+ "packages": [{packages}
+ ]\n }}"""
+
+ dep_template = """
+ {{
+ "target_under_license": "{target_under_license}",
+ "licenses": [
+ {licenses}
+ ]
+ }}"""
+
+ license_template = """
+ {{
+ "label": "{label}",
+ "bazel_package": "{bazel_package}",
+ "license_kinds": [{kinds}
+ ],
+ "copyright_notice": "{copyright_notice}",
+ "package_name": "{package_name}",
+ "package_url": "{package_url}",
+ "package_version": "{package_version}",
+ "license_text": "{license_text}",
+ "used_by": [
+ {used_by}
+ ]
+ }}"""
+
+ kind_template = """
+ {{
+ "target": "{kind_path}",
+ "name": "{kind_name}",
+ "conditions": {kind_conditions}
+ }}"""
+
+ package_info_template = """
+ {{
+ "target": "{label}",
+ "bazel_package": "{bazel_package}",
+ "package_name": "{package_name}",
+ "package_url": "{package_url}",
+ "package_version": "{package_version}"
+ }}"""
+
+ # Build reverse map of license to user
+ used_by = {}
+ for dep in metadata_info.deps.to_list():
+ # Undo the concatenation applied when stored in the provider.
+ dep_licenses = dep.licenses.split(",")
+ for license in dep_licenses:
+ if license not in used_by:
+ used_by[license] = []
+ used_by[license].append(_strip_null_repo(dep.target_under_license))
+
+ all_licenses = []
+ for license in sorted(metadata_info.licenses.to_list(), key = lambda x: x.label):
+ kinds = []
+ for kind in sorted(license.license_kinds, key = lambda x: x.name):
+ kinds.append(kind_template.format(
+ kind_name = kind.name,
+ kind_path = kind.label,
+ kind_conditions = kind.conditions,
+ ))
+
+ if license.license_text:
+ # Special handling for synthetic LicenseInfo
+ text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path)
+ all_licenses.append(license_template.format(
+ copyright_notice = license.copyright_notice,
+ kinds = ",".join(kinds),
+ license_text = text_path,
+ package_name = license.package_name,
+ package_url = license.package_url,
+ package_version = license.package_version,
+ label = _strip_null_repo(license.label),
+ bazel_package = _bazel_package(license.label),
+ used_by = ",\n ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])),
+ ))
+
+ all_deps = []
+ for dep in sorted(metadata_info.deps.to_list(), key = lambda x: x.target_under_license):
+ # Undo the concatenation applied when stored in the provider.
+ dep_licenses = dep.licenses.split(",")
+ all_deps.append(dep_template.format(
+ target_under_license = _strip_null_repo(dep.target_under_license),
+ licenses = ",\n ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])),
+ ))
+
+ all_packages = []
+ # We would use this if we had distinct depsets for every provider type.
+ #for package in sorted(metadata_info.package_info.to_list(), key = lambda x: x.label):
+ # all_packages.append(package_info_template.format(
+ # label = _strip_null_repo(package.label),
+ # package_name = package.package_name,
+ # package_url = package.package_url,
+ # package_version = package.package_version,
+ # ))
+
+ for mi in sorted(metadata_info.other_metadata.to_list(), key = lambda x: x.label):
+ # Maybe use a map of provider class to formatter. A generic dict->json function
+ # in starlark would help
+
+ # This format is for using distinct providers. I like the compile time safety.
+ if mi.type == "package_info":
+ all_packages.append(package_info_template.format(
+ label = _strip_null_repo(mi.label),
+ bazel_package = _bazel_package(mi.label),
+ package_name = mi.package_name,
+ package_url = mi.package_url,
+ package_version = mi.package_version,
+ ))
+ # experimental: Support the ExperimentalMetadataInfo bag of data
+ if mi.type == "package_info_alt":
+ all_packages.append(package_info_template.format(
+ label = _strip_null_repo(mi.label),
+ bazel_package = _bazel_package(mi.label),
+ # data is just a bag, so we need to use get() or ""
+ package_name = mi.data.get("package_name") or "",
+ package_url = mi.data.get("package_url") or "",
+ package_version = mi.data.get("package_version") or "",
+ ))
+
+ return [main_template.format(
+ top_level_target = _strip_null_repo(metadata_info.target_under_license),
+ dependencies = ",".join(all_deps),
+ licenses = ",".join(all_licenses),
+ packages = ",".join(all_packages),
+ )]
diff --git a/rules/license.bzl b/rules/license.bzl
index f726be1..032599d 100644
--- a/rules/license.bzl
+++ b/rules/license.bzl
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,54 +11,43 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Rules for declaring the compliance licenses used by a package.
-"""Rules for declaring the licenses used by a package."""
+"""
load(
"@rules_license//rules:providers.bzl",
- "LicenseInfo",
"LicenseKindInfo",
)
+load(
+ "@rules_license//rules:license_impl.bzl",
+ "license_rule_impl",
+)
-# Debugging verbosity
-_VERBOSITY = 0
-
-def _debug(loglevel, msg):
- if _VERBOSITY > loglevel:
- print(msg) # buildifier: disable=print
-
-#
-# license()
-#
-
-def _license_impl(ctx):
- provider = LicenseInfo(
- license_kinds = tuple([k[LicenseKindInfo] for k in ctx.attr.license_kinds]),
- copyright_notice = ctx.attr.copyright_notice,
- package_name = ctx.attr.package_name,
- package_url = ctx.attr.package_url,
- package_version = ctx.attr.package_version,
- license_text = ctx.file.license_text,
- rule = ctx.label,
- )
- _debug(0, provider)
- return [provider]
+# Enable this if your organization requires the license text to be a file
+# checked into source control instead of, possibly, another rule.
+_require_license_text_is_a_file = False
+# This rule must be named "_license" for backwards compatability with older
+# or Bazel that checked that name explicitly. See
+# https://github.com/bazelbuild/bazel/commit/bbc221f60bc8c9177470529d85c3e47a5d9aaf21
+# TODO(after bazel 7.0 release): Feel free to rename the rule and move.
_license = rule(
- implementation = _license_impl,
+ implementation = license_rule_impl,
attrs = {
- "copyright_notice": attr.string(
- doc = "Copyright notice.",
- ),
"license_kinds": attr.label_list(
- mandatory = True,
+ mandatory = False,
doc = "License kind(s) of this license. If multiple license kinds are" +
" listed in the LICENSE file, and they all apply, then all" +
" should be listed here. If the user can choose a single one" +
" of many, then only list one here.",
providers = [LicenseKindInfo],
+ # This should be the null configuration, not the exec.
cfg = "exec",
),
+ "copyright_notice": attr.string(
+ doc = "Copyright notice.",
+ ),
"license_text": attr.label(
allow_single_file = True,
default = "LICENSE",
@@ -80,51 +69,71 @@ _license = rule(
" by an applicatation. It should be a value that" +
" increases over time, rather than a commit hash."
),
+ "namespace": attr.string(
+ doc = "A human readable name used to organize licenses into categories." +
+ " This is used in google3 to differentiate third party licenses used" +
+ " for compliance versus internal licenses used by SLAsan for internal" +
+ " teams' SLAs.",
+ ),
},
)
# buildifier: disable=function-docstring-args
-def license(name,
- copyright_notice = None,
- license_kinds = None,
- license_text = None,
- package_name = None,
- package_url = None,
- package_version = None,
- tags = None,
- **kwargs):
+def license(
+ name,
+ license_text = "LICENSE",
+ license_kind = None,
+ license_kinds = None,
+ copyright_notice = None,
+ package_name = None,
+ package_url = None,
+ package_version = None,
+ namespace = "compliance",
+ tags = [],
+ visibility = ["//visibility:public"]):
"""Wrapper for license rule.
+ @wraps(_license)
+
Args:
name: str target name.
+ license_text: str Filename of the license file
+ license_kind: label a single license_kind. Only one of license_kind or license_kinds may
+ be specified
license_kinds: list(label) list of license_kind targets.
- license_kind: label a single license_kind. Only one of license_kind or
- license_kinds may be specified
copyright_notice: str Copyright notice associated with this package.
package_name: str A human readable name identifying this package. This
may be used to produce an index of OSS packages used by
- an applicatation.
- package_url: The URL this instance was downloaded from.
- package_version: The version number of this package. This should be a
- value that increases over time, rather than a commit
- hash.
- kwargs: Other things may be specified, but they are explicitly ignored.
+ an application.
+ package_url: str The canonical URL this package was downloaded from.
+ package_version: str The version corresponding the the URL.
+ namespace: str Undocumened. Internal.
+ tags: list(str) tags applied to the rule
+ visibility: list(label) visibility spec.
"""
- single_kind = kwargs.pop("license_kind", default = None)
- if single_kind:
+ if license_kind:
if license_kinds:
fail("Can not use both license_kind and license_kinds")
- license_kinds = [single_kind]
- tags = tags or []
+ license_kinds = [license_kind]
+
+ if _require_license_text_is_a_file:
+ # Make sure the file exists as named in the rule. A glob expression that
+ # expands to the name of the file is not acceptable.
+ srcs = native.glob([license_text])
+ if len(srcs) != 1 or srcs[0] != license_text:
+ fail("Specified license file doesn't exist: %s" % license_text)
+
_license(
name = name,
license_kinds = license_kinds,
- license_text = license_text or "LICENSE",
+ license_text = license_text,
copyright_notice = copyright_notice,
package_name = package_name,
package_url = package_url,
package_version = package_version,
+ namespace = namespace,
applicable_licenses = [],
+ visibility = visibility,
tags = tags,
- visibility = ["//visibility:public"],
+ testonly = 0,
)
diff --git a/rules/license_impl.bzl b/rules/license_impl.bzl
new file mode 100644
index 0000000..03477c6
--- /dev/null
+++ b/rules/license_impl.bzl
@@ -0,0 +1,47 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Rules for declaring the licenses used by a package.
+
+"""
+
+load(
+ "@rules_license//rules:providers.bzl",
+ "LicenseInfo",
+ "LicenseKindInfo",
+)
+
+# Debugging verbosity
+_VERBOSITY = 0
+
+def _debug(loglevel, msg):
+ if _VERBOSITY > loglevel:
+ print(msg) # buildifier: disable=print
+
+#
+# license()
+#
+
+def license_rule_impl(ctx):
+ provider = LicenseInfo(
+ license_kinds = tuple([k[LicenseKindInfo] for k in ctx.attr.license_kinds]),
+ copyright_notice = ctx.attr.copyright_notice,
+ package_name = ctx.attr.package_name or ctx.build_file_path.rstrip("/BUILD"),
+ package_url = ctx.attr.package_url,
+ package_version = ctx.attr.package_version,
+ license_text = ctx.file.license_text,
+ label = ctx.label,
+ namespace = ctx.attr.namespace,
+ )
+ _debug(0, provider)
+ return [provider]
diff --git a/rules/license_kind.bzl b/rules/license_kind.bzl
index 47b7639..7e6c024 100644
--- a/rules/license_kind.bzl
+++ b/rules/license_kind.bzl
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
"""Proof of concept. License restriction."""
load("@rules_license//rules:providers.bzl", "LicenseKindInfo")
@@ -30,6 +29,7 @@ def _license_kind_impl(ctx):
ctx.label.package,
ctx.label.name,
),
+ long_name = ctx.attr.long_name,
conditions = ctx.attr.conditions,
)
return [provider]
@@ -37,22 +37,29 @@ def _license_kind_impl(ctx):
_license_kind = rule(
implementation = _license_kind_impl,
attrs = {
- "canonical_text": attr.label(
- doc = "File containing the canonical text for this license. Must be UTF-8 encoded.",
- allow_single_file = True,
- ),
"conditions": attr.string_list(
doc = "Conditions to be met when using software under this license." +
" Conditions are defined by the organization using this license.",
mandatory = True,
),
+ "canonical_text": attr.label(
+ doc = "File containing the canonical text for this license. Must be UTF-8 encoded.",
+ allow_single_file = True,
+ ),
+ "long_name": attr.string(doc = "Human readable long name of license."),
"url": attr.string(doc = "URL pointing to canonical license definition"),
},
)
def license_kind(name, **kwargs):
+ """Wrapper for license_kind.
+
+ @wraps(_license_kind)
+ """
if "conditions" not in kwargs:
kwargs["conditions"] = []
+ if "long_name" not in kwargs:
+ kwargs["long_name"] = name
_license_kind(
name = name,
applicable_licenses = [],
diff --git a/rules/licenses_core.bzl b/rules/licenses_core.bzl
new file mode 100644
index 0000000..9bb37cb
--- /dev/null
+++ b/rules/licenses_core.bzl
@@ -0,0 +1,224 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Rules and macros for collecting LicenseInfo providers."""
+
+load("@rules_license//rules:filtered_rule_kinds.bzl", "aspect_filters")
+load("@rules_license//rules:user_filtered_rule_kinds.bzl", "user_aspect_filters")
+load(
+ "@rules_license//rules:providers.bzl",
+ "LicenseInfo",
+)
+load(
+ "@rules_license//rules/private:gathering_providers.bzl",
+ "LicensedTargetInfo",
+ "TransitiveLicensesInfo",
+)
+
+
+TraceInfo = provider(
+ doc = """Provides a target (as a string) to assist in debugging dependency issues.""",
+ fields = {
+ "trace": "String: a target to trace dependency edges to.",
+ },
+)
+
+def _trace_impl(ctx):
+ return TraceInfo(trace = ctx.build_setting_value)
+
+trace = rule(
+ doc = """Used to allow the specification of a target to trace while collecting license dependencies.""",
+ implementation = _trace_impl,
+ build_setting = config.string(flag = True),
+)
+
+def should_traverse(ctx, attr):
+ """Checks if the dependent attribute should be traversed.
+
+ Args:
+ ctx: The aspect evaluation context.
+ attr: The name of the attribute to be checked.
+
+ Returns:
+ True iff the attribute should be traversed.
+ """
+ k = ctx.rule.kind
+
+ for filters in [aspect_filters, user_aspect_filters]:
+ always_ignored = filters.get("*", [])
+ if k in filters:
+ attr_matches = filters[k]
+ if (attr in attr_matches or
+ "*" in attr_matches or
+ ("_*" in attr_matches and attr.startswith("_")) or
+ attr in always_ignored):
+ return False
+
+ for m in attr_matches:
+ if attr == m:
+ return False
+
+ return True
+
+def _get_transitive_metadata(ctx, trans_licenses, trans_other_metadata, trans_package_info, trans_deps, traces, provider, filter_func):
+ attrs = [a for a in dir(ctx.rule.attr)]
+ for name in attrs:
+ if not filter_func(ctx, name):
+ continue
+ a = getattr(ctx.rule.attr, name)
+
+ # Make anything singleton into a list for convenience.
+ if type(a) != type([]):
+ a = [a]
+ for dep in a:
+ # Ignore anything that isn't a target
+ if type(dep) != "Target":
+ continue
+
+ # Targets can also include things like input files that won't have the
+ # aspect, so we additionally check for the aspect rather than assume
+ # it's on all targets. Even some regular targets may be synthetic and
+ # not have the aspect. This provides protection against those outlier
+ # cases.
+ if provider in dep:
+ info = dep[provider]
+ if info.licenses:
+ trans_licenses.append(info.licenses)
+ if info.deps:
+ trans_deps.append(info.deps)
+ if info.traces:
+ for trace in info.traces:
+ traces.append("(" + ", ".join([str(ctx.label), ctx.rule.kind, name]) + ") -> " + trace)
+
+ # We only need one or the other of these stanzas.
+ # If we use a polymorphic approach to metadata providers, then
+ # this works.
+ if hasattr(info, "other_metadata"):
+ if info.other_metadata:
+ trans_other_metadata.append(info.other_metadata)
+ # But if we want more precise type safety, we would have a
+ # trans_* for each type of metadata. That is not user
+ # extensibile.
+ if hasattr(info, "package_info"):
+ if info.package_info:
+ trans_package_info.append(info.package_info)
+
+def gather_metadata_info_common(target, ctx, provider_factory, namespaces, metadata_providers, filter_func):
+ """Collect license and other metadata info from myself and my deps.
+
+ Any single target might directly depend on a license, or depend on
+ something that transitively depends on a license, or neither.
+ This aspect bundles all those into a single provider. At each level, we add
+ in new direct license deps found and forward up the transitive information
+ collected so far.
+
+ This is a common abstraction for crawling the dependency graph. It is parameterized
+ to allow specifying the provider that is populated with results. It is
+ configurable to select only licenses matching a certain namespace. It is also
+ configurable to specify which dependency edges should not be traced for the
+ purpose of tracing the graph.
+
+ Args:
+ target: The target of the aspect.
+ ctx: The aspect evaluation context.
+ provider_factory: abstracts the provider returned by this aspect
+ namespaces: a list of namespaces licenses must match to be included
+ metadata_providers: a list of other providers of interest
+ filter_func: a function that returns true iff the dep edge should be ignored
+
+ Returns:
+ provider of parameterized type
+ """
+
+ # First we gather my direct license attachments
+ licenses = []
+ other_metadata = []
+ package_info = []
+ if ctx.rule.kind == "_license":
+ # Don't try to gather licenses from the license rule itself. We'll just
+ # blunder into the text file of the license and pick up the default
+ # attribute of the package, which we don't want.
+ pass
+ else:
+ if hasattr(ctx.rule.attr, "applicable_licenses"):
+ for dep in ctx.rule.attr.applicable_licenses:
+ if LicenseInfo in dep:
+ lic = dep[LicenseInfo]
+
+ # This check shouldn't be necessary since any license created
+ # by the official code will have this set. However, one of the
+ # tests has its own implementation of license that had to be fixed
+ # so this is just a conservative safety check.
+ if hasattr(lic, "namespace"):
+ if lic.namespace in namespaces:
+ licenses.append(lic)
+ else:
+ fail("should have a namespace")
+ for m_p in metadata_providers:
+ if m_p in dep:
+ other_metadata.append(dep[m_p])
+
+ # Now gather transitive collection of providers from the targets
+ # this target depends upon.
+ trans_licenses = []
+ trans_other_metadata = []
+ trans_package_info = []
+ trans_deps = []
+ traces = []
+ _get_transitive_metadata(ctx, trans_licenses, trans_other_metadata, trans_package_info, trans_deps, traces, provider_factory, filter_func)
+
+ if not licenses and not trans_licenses:
+ return [provider_factory(deps = depset(), licenses = depset(), traces = [])]
+
+ # If this is the target, start the sequence of traces.
+ if ctx.attr._trace[TraceInfo].trace and ctx.attr._trace[TraceInfo].trace in str(ctx.label):
+ traces = [ctx.attr._trace[TraceInfo].trace]
+
+ # Trim the number of traces accumulated since the output can be quite large.
+ # A few representative traces are generally sufficient to identify why a dependency
+ # is incorrectly incorporated.
+ if len(traces) > 10:
+ traces = traces[0:10]
+
+ if licenses:
+ # At this point we have a target and a list of directly used licenses.
+ # Bundle those together so we can report the exact targets that cause the
+ # dependency on each license. Since a list cannot be stored in a
+ # depset, even inside a provider, the list is concatenated into a
+ # string and will be unconcatenated in the output phase.
+ direct_license_uses = [LicensedTargetInfo(
+ target_under_license = target.label,
+ licenses = ",".join([str(x.label) for x in licenses]),
+ )]
+ else:
+ direct_license_uses = None
+
+ # This is a bit of a hack for bazel 5.x. We can not pass extra fields to
+ # the provider constructor, so we need to do something special for each.
+ # In Bazel 6.x we can use a provider initializer function that would take
+ # all the args and only use the ones it wants.
+ if provider_factory == TransitiveLicensesInfo:
+ return [provider_factory(
+ target_under_license = target.label,
+ licenses = depset(tuple(licenses), transitive = trans_licenses),
+ deps = depset(direct = direct_license_uses, transitive = trans_deps),
+ traces = traces,
+ )]
+
+ return [provider_factory(
+ target_under_license = target.label,
+ licenses = depset(tuple(licenses), transitive = trans_licenses),
+ other_metadata = depset(tuple(other_metadata), transitive = trans_other_metadata),
+ deps = depset(direct = direct_license_uses, transitive = trans_deps),
+ traces = traces,
+ )]
diff --git a/rules/package_info.bzl b/rules/package_info.bzl
new file mode 100644
index 0000000..c79545f
--- /dev/null
+++ b/rules/package_info.bzl
@@ -0,0 +1,104 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Rules for declaring metadata about a package."""
+
+load(
+ "@rules_license//rules:providers.bzl",
+ "ExperimentalMetadataInfo",
+ "PackageInfo",
+)
+
+#
+# package_info()
+#
+
+def _package_info_impl(ctx):
+ provider = PackageInfo(
+ # Metadata providers must include a type discriminator. We don't need it
+ # to collect the providers, but we do need it to write the JSON. We
+ # key on the type field to look up the correct block of code to pull
+ # data out and format it. We can't to the lookup on the provider class.
+ type = "package_info",
+ label = ctx.label,
+ package_name = ctx.attr.package_name or ctx.build_file_path.rstrip("/BUILD"),
+ package_url = ctx.attr.package_url,
+ package_version = ctx.attr.package_version,
+ )
+ # Experimental alternate design, using a generic 'data' back to hold things
+ generic_provider = ExperimentalMetadataInfo(
+ type = "package_info_alt",
+ label = ctx.label,
+ data = {
+ "package_name": ctx.attr.package_name or ctx.build_file_path.rstrip("/BUILD"),
+ "package_url": ctx.attr.package_url,
+ "package_version": ctx.attr.package_version
+ }
+ )
+ return [provider, generic_provider]
+
+_package_info = rule(
+ implementation = _package_info_impl,
+ attrs = {
+ "package_name": attr.string(
+ doc = "A human readable name identifying this package." +
+ " This may be used to produce an index of OSS packages used by" +
+ " an applicatation.",
+ ),
+ "package_url": attr.string(
+ doc = "The URL this instance of the package was download from." +
+ " This may be used to produce an index of OSS packages used by" +
+ " an applicatation.",
+ ),
+ "package_version": attr.string(
+ doc = "A human readable version string identifying this package." +
+ " This may be used to produce an index of OSS packages used" +
+ " by an applicatation. It should be a value that" +
+ " increases over time, rather than a commit hash."
+ ),
+ },
+)
+
+# buildifier: disable=function-docstring-args
+def package_info(
+ name,
+ package_name = None,
+ package_url = None,
+ package_version = None,
+ **kwargs):
+ """Wrapper for package_info rule.
+
+ @wraps(_package_info)
+
+ Args:
+ name: str target name.
+ package_name: str A human readable name identifying this package. This
+ may be used to produce an index of OSS packages used by
+ an application.
+ package_url: str The canoncial URL this package distribution was retrieved from.
+ Note that, because of local mirroring, that might not be the
+ physical URL it was retrieved from.
+ package_version: str A human readable name identifying version of this package.
+ kwargs: other args. Most are ignored.
+ """
+ visibility = kwargs.get("visibility") or ["//visibility:public"]
+ _package_info(
+ name = name,
+ package_name = package_name,
+ package_url = package_url,
+ package_version = package_version,
+ applicable_licenses = [],
+ visibility = visibility,
+ tags = [],
+ testonly = 0,
+ )
diff --git a/rules/private/BUILD b/rules/private/BUILD
new file mode 100644
index 0000000..452e623
--- /dev/null
+++ b/rules/private/BUILD
@@ -0,0 +1,35 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Rules for making license declarations."""
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])
+
+filegroup(
+ name = "standard_package",
+ srcs = glob(["**"]),
+)
+
+# Do not create a bzl_library(). That would create a dependency loop back
+# to bazel-skylib. We export the .bzl files to the documentation maker.
+exports_files(
+ glob([
+ "*.bzl",
+ ]),
+ visibility = ["//doc_build:__pkg__"],
+)
diff --git a/rules/private/gathering_providers.bzl b/rules/private/gathering_providers.bzl
new file mode 100644
index 0000000..1c3740f
--- /dev/null
+++ b/rules/private/gathering_providers.bzl
@@ -0,0 +1,54 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Providers for transitively gathering all license and package_info targets.
+
+Warning: This is private to the aspect that walks the tree. The API is subject
+to change at any release.
+"""
+
+LicensedTargetInfo = provider(
+ doc = """Lists the licenses directly used by a single target.""",
+ fields = {
+ "target_under_license": "Label: The target label",
+ "licenses": "list(label of a license rule)",
+ },
+)
+
+def licenses_info():
+ return provider(
+ doc = """The transitive set of licenses used by a target.""",
+ fields = {
+ "target_under_license": "Label: The top level target label.",
+ "deps": "depset(LicensedTargetInfo): The transitive list of dependencies that have licenses.",
+ "licenses": "depset(LicenseInfo)",
+ "traces": "list(string) - diagnostic for tracing a dependency relationship to a target.",
+ },
+ )
+
+# This provider is used by the aspect that is used by manifest() rules.
+TransitiveLicensesInfo = licenses_info()
+
+TransitiveMetadataInfo = provider(
+ doc = """The transitive set of licenses used by a target.""",
+ fields = {
+ "top_level_target": "Label: The top level target label we are examining.",
+ "other_metadata": "depset(ExperimentalMetatdataInfo)",
+ "licenses": "depset(LicenseInfo)",
+ "package_info": "depset(PackageInfo)",
+
+ "target_under_license": "Label: A target which will be associated with some licenses.",
+ "deps": "depset(LicensedTargetInfo): The transitive list of dependencies that have licenses.",
+ "traces": "list(string) - diagnostic for tracing a dependency relationship to a target.",
+ },
+)
diff --git a/rules/providers.bzl b/rules/providers.bzl
index 9e830ce..33a7fb5 100644
--- a/rules/providers.bzl
+++ b/rules/providers.bzl
@@ -1,4 +1,4 @@
-# Copyright 2020 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,34 +11,57 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Basic providers for license rules.
-"""Providers for license rules."""
+This file should only contain the basic providers needed to create
+license and package_info declarations. Providers needed to gather
+them are declared in other places.
+"""
LicenseKindInfo = provider(
- doc = """Provides information about a license kind.""",
+ doc = """Provides information about a license_kind instance.""",
fields = {
- "conditions": "List of conditions to be met when using this software.",
- "label": "The full path to the license kind definition.",
- "name": "License Name",
+ "conditions": "list(string): List of conditions to be met when using this packages under this license.",
+ "label": "Label: The full path to the license kind definition.",
+ "long_name": "string: Human readable license name",
+ "name": "string: Canonical license name",
},
)
LicenseInfo = provider(
- doc = """Provides information about an instance of a license.""",
+ doc = """Provides information about a license instance.""",
fields = {
- "copyright_notice": "Human readable short copyright notice",
- "license_kinds": "License kinds",
- "license_text": "License file",
- "package_name": "Human readable package name",
+ "copyright_notice": "string: Human readable short copyright notice",
+ "label": "Label: label of the license rule",
+ "license_kinds": "list(LicenseKindInfo): License kinds ",
+ "license_text": "string: The license file path",
+ "namespace": "string: namespace of the license rule",
+ # TODO(aiuto): move to PackageInfo
+ "package_name": "string: Human readable package name",
"package_url": "URL from which this package was downloaded.",
"package_version": "Human readable version string",
- "rule": "From whence this came",
},
)
-LicensesInfo = provider(
- doc = """The set of license instances used in a target.""",
+PackageInfo = provider(
+ doc = """Provides information about a package.""",
fields = {
- "licenses": "list(LicenseInfo).",
+ "type": "string: How to interpret data",
+ "label": "Label: label of the package_info rule",
+ "package_name": "string: Human readable package name",
+ "package_url": "string: URL from which this package was downloaded.",
+ "package_version": "string: Human readable version string",
},
)
+
+# This is more extensible. Because of the provider implementation, having a big
+# dict of values rather than named fields is not much more costly.
+# Design choice. Replace data with actual providers, such as PackageInfo
+ExperimentalMetadataInfo = provider(
+ doc = """Generic bag of metadata.""",
+ fields = {
+ "type": "string: How to interpret data",
+ "label": "Label: label of the metadata rule",
+ "data": "String->any: Map of names to values",
+ }
+)
diff --git a/rules/sbom.bzl b/rules/sbom.bzl
new file mode 100644
index 0000000..73c1861
--- /dev/null
+++ b/rules/sbom.bzl
@@ -0,0 +1,136 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""SBOM generation"""
+
+load(
+ "@rules_license//rules:gather_metadata.bzl",
+ "gather_metadata_info",
+ "gather_metadata_info_and_write",
+ "write_metadata_info",
+)
+load(
+ "@rules_license//rules/private:gathering_providers.bzl",
+ "TransitiveLicensesInfo",
+)
+
+# This rule is proof of concept, and may not represent the final
+# form of a rule for compliance validation.
+def _generate_sbom_impl(ctx):
+ # Gather all licenses and write information to one place
+
+ licenses_file = ctx.actions.declare_file("_%s_licenses_info.json" % ctx.label.name)
+ write_metadata_info(ctx, ctx.attr.deps, licenses_file)
+
+ # Now turn the big blob of data into something consumable.
+ inputs = [licenses_file]
+ outputs = [ctx.outputs.out]
+ args = ctx.actions.args()
+ args.add("--licenses_info", licenses_file.path)
+ args.add("--out", ctx.outputs.out.path)
+ ctx.actions.run(
+ mnemonic = "CreateSBOM",
+ progress_message = "Creating SBOM for %s" % ctx.label,
+ inputs = inputs,
+ outputs = outputs,
+ executable = ctx.executable._sbom_generator,
+ arguments = [args],
+ )
+ outputs.append(licenses_file) # also make the json file available.
+ return [DefaultInfo(files = depset(outputs))]
+
+_generate_sbom = rule(
+ implementation = _generate_sbom_impl,
+ attrs = {
+ "deps": attr.label_list(
+ aspects = [gather_metadata_info],
+ ),
+ "out": attr.output(mandatory = True),
+ "_sbom_generator": attr.label(
+ default = Label("@rules_license//tools:write_sbom"),
+ executable = True,
+ allow_files = True,
+ cfg = "exec",
+ ),
+ },
+)
+
+def generate_sbom(**kwargs):
+ _generate_sbom(**kwargs)
+
+def _manifest_impl(ctx):
+ # Gather all licenses and make it available as deps for downstream rules
+ # Additionally write the list of license filenames to a file that can
+ # also be used as an input to downstream rules.
+ licenses_file = ctx.actions.declare_file(ctx.attr.out.name)
+ mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses)
+ ctx.actions.write(
+ output = licenses_file,
+ content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]),
+ )
+ return [DefaultInfo(files = depset(mappings.keys()))]
+
+_manifest = rule(
+ implementation = _manifest_impl,
+ doc = """Internal tmplementation method for manifest().""",
+ attrs = {
+ "deps": attr.label_list(
+ doc = """List of targets to collect license files for.""",
+ aspects = [gather_metadata_info],
+ ),
+ "out": attr.output(
+ doc = """Output file.""",
+ mandatory = True,
+ ),
+ "warn_on_legacy_licenses": attr.bool(default = False),
+ },
+)
+
+def manifest(name, deps, out = None, **kwargs):
+ if not out:
+ out = name + ".manifest"
+
+ _manifest(name = name, deps = deps, out = out, **kwargs)
+
+def get_licenses_mapping(deps, warn = False):
+ """Creates list of entries representing all licenses for the deps.
+
+ Args:
+
+ deps: a list of deps which should have TransitiveLicensesInfo providers.
+ This requires that you have run the gather_licenses_info
+ aspect over them
+
+ warn: boolean, if true, display output about legacy targets that need
+ update
+
+ Returns:
+ {File:package_name}
+ """
+ tls = []
+ for dep in deps:
+ lds = dep[TransitiveLicensesInfo].licenses
+ tls.append(lds)
+
+ ds = depset(transitive = tls)
+
+ # Ignore any legacy licenses that may be in the report
+ mappings = {}
+ for lic in ds.to_list():
+ if type(lic.license_text) == "File":
+ mappings[lic.license_text] = lic.package_name
+ elif warn:
+ # buildifier: disable=print
+ print("Legacy license %s not included, rule needs updating" % lic.license_text)
+
+ return mappings
diff --git a/rules/user_filtered_rule_kinds.bzl b/rules/user_filtered_rule_kinds.bzl
new file mode 100644
index 0000000..a099794
--- /dev/null
+++ b/rules/user_filtered_rule_kinds.bzl
@@ -0,0 +1,28 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Filtered rule kinds for aspect inspection.
+
+The format of this dictionary is:
+ rule_name: [attr, attr, ...]
+
+Filters for rules that are not part of the Bazel distribution should be added
+to this file.
+
+Attributes are either the explicit list of attributes to filter, or '_*' which
+would ignore all attributes prefixed with a _.
+"""
+
+# Rule kinds with attributes the aspect currently needs to ignore
+user_aspect_filters = {
+}
diff --git a/tests/BUILD b/tests/BUILD
index f817cc4..6ceee9a 100644
--- a/tests/BUILD
+++ b/tests/BUILD
@@ -1,11 +1,16 @@
-"""Test cases for license rules."""
+# Test cases for license rules.
-load("@rules_license//rules:compliance.bzl", "check_license")
+load("@rules_license//rules:compliance.bzl", "check_license", "licenses_used")
load("@rules_license//rules:license.bzl", "license")
load("@rules_license//rules:license_kind.bzl", "license_kind")
-load("@rules_license//tools:test_helpers.bzl", "golden_test")
-package(default_applicable_licenses = [":license"])
+package(
+ default_applicable_licenses = [":license"],
+ default_visibility = [
+ "//examples:__subpackages__",
+ "//tests:__subpackages__",
+ ],
+)
# license_kind rules generally appear in a central location per workspace. They
# are intermingled with normal target build rules
@@ -39,8 +44,16 @@ license(
license(
name = "license_for_extra_feature",
package_name = "A test case package",
- license = "LICENSE.extra",
license_kinds = [":generic_restricted_license"],
+ license_text = "LICENSE.extra",
+)
+
+# This license is not in the "compliance" namespace and
+# therefore should not show up in the report verified by
+# :verify_cc_app_test
+license(
+ name = "internal_non_compliance_license",
+ namespace = "test_namespace",
)
cc_binary(
@@ -53,7 +66,18 @@ cc_binary(
cc_library(
name = "c_bar",
- srcs = ["bar.cc"],
+ srcs = [
+ "bar.cc",
+ ],
+ applicable_licenses = [
+ ":license",
+ ":license_for_extra_feature",
+ ":internal_non_compliance_license",
+ ],
+ deps = [
+ "@rules_license//tests/legacy:another_library_with_legacy_license_clause",
+ "@rules_license//tests/legacy:library_with_legacy_license_clause",
+ ],
)
java_binary(
@@ -63,6 +87,7 @@ java_binary(
applicable_licenses = [
":license_for_extra_feature",
],
+ javacopts = ["-Xep:DefaultPackage:OFF"],
main_class = "Hello",
deps = [
":j_bar",
@@ -72,6 +97,7 @@ java_binary(
java_library(
name = "j_bar",
srcs = ["Bar.java"],
+ javacopts = ["-Xep:DefaultPackage:OFF"],
)
check_license(
@@ -85,6 +111,31 @@ check_license(
],
)
+licenses_used(
+ name = "hello_licenses",
+ out = "hello_licenses.json",
+ deps = [":hello"],
+)
+
+py_test(
+ name = "hello_licenses_test",
+ srcs = ["hello_licenses_test.py"],
+ data = [
+ ":hello_licenses.json",
+ ":hello_cc_copyrights.txt",
+ ],
+ python_version = "PY3",
+ deps = [
+ ":license_test_utils",
+ ],
+)
+
+py_library(
+ name = "license_test_utils",
+ srcs = ["license_test_utils.py"],
+ srcs_version = "PY3",
+)
+
check_license(
name = "check_java_app",
check_conditions = False,
@@ -96,14 +147,16 @@ check_license(
],
)
-golden_test(
- name = "verify_cc_app_test",
- golden = "hello_cc_copyrights.golden",
- subject = ":hello_cc_copyrights.txt",
+
+license(
+ name = "license_with_generated_text",
+ license_text = ":created_license",
+ license_kinds = [":generic_notice_license"],
)
-golden_test(
- name = "verify_java_app_test",
- golden = "hello_java_copyrights.golden",
- subject = ":hello_java_copyrights.txt",
+genrule(
+ name = "created_license",
+ outs = ["something.text"],
+ cmd = "echo hello >$@",
)
+
diff --git a/tests/apps/BUILD b/tests/apps/BUILD
new file mode 100644
index 0000000..2c0778f
--- /dev/null
+++ b/tests/apps/BUILD
@@ -0,0 +1,70 @@
+# Test cases for license rules: Sample app
+
+load("@rules_license//rules:compliance.bzl", "licenses_used")
+
+package(default_visibility = ["//examples:__subpackages__"])
+
+# Note that the app explicitly depends only on a library and some legacy
+# style licensed code.
+cc_binary(
+ name = "an_app",
+ srcs = ["an_app.cc"],
+ deps = [
+ ":level4",
+ # "@rules_license//rules/tests/legacy:another_library_with_legacy_license_clause",
+ # "@rules_license//rules/tests/legacy:library_with_legacy_license_clause",
+ ],
+)
+
+# pointless chain of libraries to show transitive rule gathering, culminating
+# in a diamond dependency on a library under license.
+# Note that the lowest level depends on some third party code
+[
+ genrule(
+ name = "level_%d_src" % level,
+ outs = ["level_%d.cc" % level],
+ # Note to reviewers: This should use string format, but format
+ # is broken when
+ cmd = """cat >$@ <<END
+ #include <iostream>
+ extern void {lower}();
+ void lib_level_{level}() {{
+ std::cout << "This is level {level}" << std::endl;
+ {lower}();
+ }}
+END
+ """.format(
+ level = level,
+ lower = "lib_level_%d" % (level - 1) if level > 0 else "new_lib_func",
+ ),
+ )
+ for level in range(5)
+]
+
+[
+ cc_library(
+ name = "level%d" % level,
+ srcs = [":level_%d.cc" % level],
+ deps = [
+ (":level%d" % (level - 1) if level > 0 else "@rules_license//tests/thrdparty:new_style_lib"),
+ ],
+ )
+ for level in range(5)
+]
+
+licenses_used(
+ name = "an_app_licenses",
+ out = "an_app_licenses.json",
+ deps = [":an_app"],
+)
+
+# Examining the golden file shows that we depend on both kinds of license.
+py_test(
+ name = "an_app_licenses_test",
+ srcs = ["an_app_licenses_test.py"],
+ data = [":an_app_licenses.json"],
+ python_version = "PY3",
+ deps = [
+ "@rules_license//tests:license_test_utils",
+ ],
+)
diff --git a/tests/apps/an_app.cc b/tests/apps/an_app.cc
new file mode 100644
index 0000000..60f6fc5
--- /dev/null
+++ b/tests/apps/an_app.cc
@@ -0,0 +1,24 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <iostream>
+#include <ostream>
+
+extern const char* server_message;
+extern void lib_level_4();
+
+int main(int argc, char* argv[]) {
+ std::cout << "main" << std::endl;
+ lib_level_4();
+ return 0;
+}
diff --git a/tests/apps/an_app_licenses_test.py b/tests/apps/an_app_licenses_test.py
new file mode 100644
index 0000000..0dbf8fb
--- /dev/null
+++ b/tests/apps/an_app_licenses_test.py
@@ -0,0 +1,42 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+import unittest
+from tests import license_test_utils
+
+
+class AnAppLicensesTest(unittest.TestCase):
+
+ def test_has_expected_licenses(self):
+ package_base = license_test_utils.LICENSE_PACKAGE_BASE
+ licenses_info = license_test_utils.load_licenses_info(
+ os.path.join(os.path.dirname(__file__), "an_app_licenses.json"))
+ licenses_info = license_test_utils.filter_dependencies(
+ licenses_info,
+ target_filter=lambda targ: targ.startswith(package_base),
+ licenses_filter=lambda lic: lic.startswith(package_base))
+
+ expected = {
+ "/tests/thrdparty:new_style_lib": [
+ "/tests/thrdparty:license",
+ ],
+ }
+ license_test_utils.check_licenses_of_dependencies(
+ self, licenses_info, expected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/hello_cc_copyrights.golden b/tests/hello_cc_copyrights.golden
deleted file mode 100755
index ac28bab..0000000
--- a/tests/hello_cc_copyrights.golden
+++ /dev/null
@@ -1 +0,0 @@
-package(A test case package/0.0.4), copyright(Copyright © 2019 Uncle Toasty)
diff --git a/tests/hello_java_copyrights.golden b/tests/hello_java_copyrights.golden
deleted file mode 100755
index 0ba7362..0000000
--- a/tests/hello_java_copyrights.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-package(A test case package/0.0.4), copyright(Copyright © 2019 Uncle Toasty)
-package(A test case package), copyright()
diff --git a/tests/hello_licenses_test.py b/tests/hello_licenses_test.py
new file mode 100644
index 0000000..de62c7f
--- /dev/null
+++ b/tests/hello_licenses_test.py
@@ -0,0 +1,42 @@
+"""Tests for google3.tools.build_defs.license.tests.hello_licenses."""
+
+import codecs
+import os
+
+import unittest
+from tests import license_test_utils
+
+
+class HelloLicensesTest(unittest.TestCase):
+
+ def test_has_expected_licenses(self):
+ licenses_info = license_test_utils.load_licenses_info(
+ os.path.join(os.path.dirname(__file__), "hello_licenses.json"))
+
+ expected = {
+ "/tests:hello": [
+ "/tests:license",
+ ],
+ "/tests:c_bar": [
+ "/tests:license",
+ "/tests:license_for_extra_feature",
+ ],
+ }
+ license_test_utils.check_licenses_of_dependencies(
+ self, licenses_info, expected)
+
+ def test_has_expected_copyrights(self):
+ copyrights_file = os.path.join(os.path.dirname(__file__),
+ "hello_cc_copyrights.txt")
+ with codecs.open(copyrights_file, encoding="utf-8") as inp:
+ copyrights = inp.read().split('\n')
+ self.assertIn(
+ "package(A test case package/0.0.4), copyright(Copyright © 2019 Uncle Toasty)",
+ copyrights)
+ self.assertIn(
+ "package(A test case package), copyright()",
+ copyrights)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/legacy/BUILD b/tests/legacy/BUILD
new file mode 100644
index 0000000..e065e22
--- /dev/null
+++ b/tests/legacy/BUILD
@@ -0,0 +1,17 @@
+# Example of an unmigrated package.
+
+package(default_visibility = [
+ "//tests:__subpackages__",
+])
+
+licenses(["unencumbered"])
+
+cc_library(
+ name = "library_with_legacy_license_clause",
+ srcs = ["file_under_notice.cc"],
+)
+
+cc_library(
+ name = "another_library_with_legacy_license_clause",
+ srcs = ["file_under_notice.cc"],
+)
diff --git a/tests/legacy/file_under_notice.cc b/tests/legacy/file_under_notice.cc
new file mode 100644
index 0000000..2fa8bfc
--- /dev/null
+++ b/tests/legacy/file_under_notice.cc
@@ -0,0 +1,14 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+int file_under_notice = 1;
diff --git a/tests/license_test_utils.py b/tests/license_test_utils.py
new file mode 100644
index 0000000..cc29e59
--- /dev/null
+++ b/tests/license_test_utils.py
@@ -0,0 +1,84 @@
+"""Utilities for writing tests of license rules."""
+
+import codecs
+import json
+
+# This is extracted out to make it easier to keep test equivalence between
+# the OSS version and Google.
+LICENSE_PACKAGE_BASE = "/"
+
+
+def load_licenses_info(info_path):
+ """Loads the licenses_info() JSON format."""
+ with codecs.open(info_path, encoding="utf-8") as licenses_file:
+ return json.loads(licenses_file.read())
+
+
+def filter_dependencies(licenses_info, target_filter=None,
+ licenses_filter=None):
+ """Filters licenses_info to only include dependencies of interest.
+
+ Args:
+ licenses_info: (dict) licenses info.
+ target_filter: (function): function which returns true if we should include
+ the target.
+ licenses_filter: (function): function which returns true if we should
+ include the license.
+ Returns:
+ (dict) a valid licenses_info dict.
+ """
+ top_target = licenses_info[0]
+ new_top_target = dict(top_target)
+ new_deps = []
+ for dep in top_target["dependencies"]:
+ target_name = dep["target_under_license"]
+ if target_filter and not target_filter(target_name):
+ continue
+ licenses = dep["licenses"]
+ if licenses_filter:
+ licenses = [lic for lic in licenses if licenses_filter(lic)]
+ new_deps.append({
+ "target_under_license": target_name,
+ "licenses": licenses})
+ new_top_target["dependencies"] = new_deps
+ return [new_top_target]
+
+
+def check_licenses_of_dependencies(test_case, licenses_info, expected,
+ path_prefix=LICENSE_PACKAGE_BASE):
+ """Checks that licenses_info contains an expected set of licenses.
+
+ Args:
+ test_case: (TestCase) the test.
+ licenses_info: (dict) licenses info.
+ expected: (dict) map of target name suffixes to the licenses they are under.
+ path_prefix: (str) prefix to prepend to targets and licenses in expected.
+ This turns the relative target names to absolute ones.
+ """
+
+ # Turn the list of deps into a dict by target for easier comparison.
+ deps_to_licenses = {
+ x["target_under_license"].lstrip("@"): set(l.strip("@") for l in x["licenses"])
+ for x in licenses_info[0]["dependencies"]}
+
+ target_names = ",".join(deps_to_licenses.keys())
+ # This is n**2, but N is typically < 3 or we are doing this wrong.
+ for want_target, want_licenses in expected.items():
+ found_target = False
+ for got_target, got_licenses in deps_to_licenses.items():
+ if got_target.endswith(want_target):
+ found_target = True
+ test_case.assertEqual(len(want_licenses), len(got_licenses))
+ found_license = False
+ for want_l in want_licenses:
+ for got_l in got_licenses:
+ if got_l.endswith(want_l):
+ found_license = True
+ break
+ test_case.assertTrue(
+ found_license,
+ msg="license (%s) not a suffix in %s" % (want_l, got_licenses))
+ break
+ test_case.assertTrue(
+ found_target,
+ msg="target (%s) not a suffix in [%s]" % (want_target, target_names))
diff --git a/tests/thrdparty/BUILD b/tests/thrdparty/BUILD
new file mode 100644
index 0000000..2fa9752
--- /dev/null
+++ b/tests/thrdparty/BUILD
@@ -0,0 +1,26 @@
+# A sample library using new license rules.
+
+load("@rules_license//rules:license.bzl", "license")
+
+package(
+ default_applicable_licenses = [":license"],
+ default_visibility = [
+ "//examples:__subpackages__",
+ "//tests:__subpackages__",
+ ],
+)
+
+# The default license for an entire package is typically named "license".
+license(
+ name = "license",
+ package_name = "migrated package",
+ license_kinds = ["//licenses/generic:restricted"],
+ license_text = "LICENSE",
+)
+
+cc_library(
+ name = "new_style_lib",
+ srcs = [
+ "new_style_lib.cc",
+ ],
+)
diff --git a/tests/thrdparty/LICENSE b/tests/thrdparty/LICENSE
new file mode 100644
index 0000000..e5bcd1f
--- /dev/null
+++ b/tests/thrdparty/LICENSE
@@ -0,0 +1 @@
+I am restricted in some way.
diff --git a/examples/src/mobile.cc b/tests/thrdparty/new_style_lib.cc
index d15090f..0c739b9 100644
--- a/examples/src/mobile.cc
+++ b/tests/thrdparty/new_style_lib.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 Google LLC
+// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-
#include <iostream>
+#include <ostream>
-int main(int argc, char* argv[]) {
- std::cout << "Hello world" << std::endl;
+void new_lib_func() {
+ std::cout << "This is restricted code" << std::endl;
}
+
diff --git a/tools/BUILD b/tools/BUILD
index 03bbac1..2b56a34 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -15,10 +15,20 @@
"""License declaration and compliance checking tools."""
package(
- default_applicable_licenses = ["//:license"],
+ default_applicable_licenses = ["//:license", "//:package_info"],
default_visibility = ["//visibility:public"],
)
+licenses(["notice"])
+
+filegroup(
+ name = "standard_package",
+ srcs = glob(["**"]),
+ visibility = ["//distro:__pkg__"],
+)
+
+exports_files(["diff_test.sh"])
+
py_binary(
name = "checker_demo",
srcs = ["checker_demo.py"],
@@ -26,10 +36,9 @@ py_binary(
visibility = ["//visibility:public"],
)
-exports_files(["diff_test.sh"])
-
-filegroup(
- name = "standard_package",
- srcs = glob(["**"]),
- visibility = ["//distro:__pkg__"],
+py_binary(
+ name = "write_sbom",
+ srcs = ["write_sbom.py"],
+ python_version = "PY3",
+ visibility = ["//visibility:public"],
)
diff --git a/tools/checker_demo.py b/tools/checker_demo.py
index 73042aa..6cdf07f 100644
--- a/tools/checker_demo.py
+++ b/tools/checker_demo.py
@@ -23,14 +23,19 @@ import codecs
import json
# Conditions allowed for all applications
-_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumberd'])
+_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumbered'])
-def _get_licenses(licenses_info):
+def _load_license_data(licenses_info):
with codecs.open(licenses_info, encoding='utf-8') as licenses_file:
return json.loads(licenses_file.read())
+def unique_licenses(licenses):
+ for target in licenses:
+ for lic in target.get('licenses') or []:
+ yield lic
+
def _do_report(out, licenses):
"""Produce a report showing the set of licenses being used.
@@ -41,11 +46,14 @@ def _do_report(out, licenses):
Returns:
0 for no restricted licenses.
"""
- for lic in licenses: # using strange name lic because license is built-in
- rule = lic['rule']
- for kind in lic['license_kinds']:
- out.write('= %s\n kind: %s\n' % (rule, kind['target']))
- out.write(' conditions: %s\n' % kind['conditions'])
+
+ for target in unique_licenses(licenses):
+ for lic in target.get('licenses') or []:
+ print("lic:", lic)
+ rule = lic['rule']
+ for kind in lic['license_kinds']:
+ out.write('= %s\n kind: %s\n' % (rule, kind['target']))
+ out.write(' conditions: %s\n' % kind['conditions'])
def _check_conditions(out, licenses, allowed_conditions):
@@ -86,7 +94,7 @@ def _do_copyright_notices(out, licenses):
def _do_licenses(out, licenses):
- for lic in licenses:
+ for lic in unique_licenses(licenses):
path = lic['license_text']
with codecs.open(path, encoding='utf-8') as license_file:
out.write('= %s\n' % path)
@@ -107,7 +115,13 @@ def main():
help='check that the dep only includes allowed license conditions')
args = parser.parse_args()
- licenses = _get_licenses(args.licenses_info)
+ license_data = _load_license_data(args.licenses_info)
+ target = license_data[0] # we assume only one target for the demo
+
+ top_level_target = target['top_level_target']
+ dependencies = target['dependencies']
+ licenses = target['licenses']
+
err = 0
with codecs.open(args.report, mode='w', encoding='utf-8') as rpt:
_do_report(rpt, licenses)
diff --git a/tools/test_helpers.bzl b/tools/test_helpers.bzl
index 30f1980..3ffb9b7 100644
--- a/tools/test_helpers.bzl
+++ b/tools/test_helpers.bzl
@@ -38,3 +38,50 @@ def golden_test(
golden,
],
)
+
+def golden_cmd_test(
+ name,
+ cmd,
+ golden, # Required
+ toolchains = [],
+ tools = None,
+ exec_tools = None,
+ srcs = [], # Optional
+ **kwargs): # Rest
+ """Compares cmd output to golden output, passes if they are identical.
+
+ Args:
+ name: Name of the build rule.
+ cmd: The command to run to generate output.
+ golden: The golden file to be compared.
+ toolchains: List of toolchains needed to run the command, passed to genrule.
+ tools: List of tools needed to run the command, passed to genrule.
+ exec_tools: List of tools needed to run the command, passed to genrule.
+ srcs: List of sources needed as input to the command, passed to genrule.
+ **kwargs: Any additional parameters for the generated golden_test.
+ """
+ actual = name + ".output"
+
+ # There are some cases where tools are provided and exec_tools are provided.
+ # Specifying both in the same genrule, confuses the host vs exec rules,
+ # which prevents python3 from execution.
+ if tools and exec_tools:
+ fail("Only set one: tools or exec_tools. " +
+ "Setting both confuses python execution mode (host vs exec).")
+ native.genrule(
+ name = name + "_output",
+ srcs = srcs,
+ outs = [actual],
+ cmd = cmd + " > '$@'", # Redirect to collect output
+ toolchains = toolchains,
+ tools = tools,
+ exec_tools = exec_tools,
+ testonly = True,
+ )
+
+ golden_test(
+ name = name,
+ subject = actual,
+ golden = golden,
+ **kwargs
+ )
diff --git a/tools/write_sbom.py b/tools/write_sbom.py
new file mode 100644
index 0000000..18286ab
--- /dev/null
+++ b/tools/write_sbom.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Proof of concept license checker.
+
+This is only a demonstration. It will be replaced with other tools.
+"""
+
+import argparse
+import codecs
+import datetime
+import json
+import os
+
+
+TOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom'
+
+def _load_package_data(package_info):
+ with codecs.open(package_info, encoding='utf-8') as inp:
+ return json.loads(inp.read())
+
+def _write_sbom_header(out, package):
+ header = [
+ 'SPDXVersion: SPDX-2.2',
+ 'DataLicense: CC0-1.0',
+ 'SPDXID: SPDXRef-DOCUMENT',
+ 'DocumentName: %s' % package,
+ # TBD
+ # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3
+ 'Creator: Person: %s' % os.getlogin(),
+ 'Creator: Tool: %s' % TOOL,
+ datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'),
+ '',
+ '##### Package: %s' % package,
+ ]
+ out.write('\n'.join(header))
+
+
+
+def _write_sbom(out, packages):
+ """Produce a basic SBOM
+
+ Args:
+ out: file object to write to
+ packages: package metadata. A big blob of JSON.
+ """
+ for p in packages:
+ name = p.get('package_name') or '<unknown>'
+ out.write('\n')
+ out.write('SPDXID: "%s"\n' % name)
+ out.write(' name: "%s"\n' % name)
+ if p.get('package_version'):
+ out.write(' versionInfo: "%s"\n' % p['package_version'])
+ # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one.
+ cn = p.get('copyright_notice')
+ if cn:
+ out.write(' copyrightText: "%s"\n' % cn)
+ kinds = p.get('license_kinds')
+ if kinds:
+ out.write(' licenseDeclared: "%s"\n' %
+ ','.join([k['name'] for k in kinds]))
+ url = p.get('package_url')
+ if url:
+ out.write(' downloadLocation: %s\n' % url)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Demonstraton license compliance checker')
+
+ parser.add_argument('--licenses_info',
+ help='path to JSON file containing all license data')
+ parser.add_argument('--out', default='sbom.out', help='SBOM output')
+ args = parser.parse_args()
+
+ license_data = _load_package_data(args.licenses_info)
+ target = license_data[0] # we assume only one target for the demo
+
+ top_level_target = target['top_level_target']
+ dependencies = target['dependencies']
+ # It's not really packages, but this is close proxy for now
+ licenses = target['licenses']
+ package_infos = target['packages']
+
+ # These are similar dicts, so merge them by package. This is not
+ # strictly true, as different licenese can appear in the same
+ # package, but it is good enough for demonstrating the sbom.
+
+ all = {x['bazel_package']: x for x in licenses}
+ for pi in package_infos:
+ p = all.get(pi['bazel_package'])
+ if p:
+ p.update(pi)
+ else:
+ all[pi['bazel_package']] = pi
+
+ err = 0
+ with codecs.open(args.out, mode='w', encoding='utf-8') as out:
+ _write_sbom_header(out, package=top_level_target)
+ _write_sbom(out, all.values())
+ return err
+
+
+if __name__ == '__main__':
+ main()