aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Neubauer <wcn@google.com>2022-10-24 19:52:53 -0400
committerTony Aiuto <aiuto@google.com>2022-11-01 00:32:59 -0400
commitd8c455ebb46b0f489a4a83367e4833fda5a131db (patch)
tree15432eb15d61d926fadba52d89209698ea4bd324
parent84c8fe9fabbd149dff42f854c07fabbe286f93a8 (diff)
downloadbazelbuild-rules_license-d8c455ebb46b0f489a4a83367e4833fda5a131db.tar.gz
Mega merge from Google
The core of the PR is an expert from Google, but it applies several changes to account for Bazel differences. - deal with bazel 5.x 6.x @// handling - restore package_url and package_version. This is temporary they will move to other providers. PiperOrigin-RevId: 483521567
-rw-r--r--README.md3
-rw-r--r--distro/BUILD2
-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.txt204
-rw-r--r--examples/my_org/compliance/BUILD31
-rw-r--r--examples/my_org/licenses/BUILD13
-rw-r--r--examples/src/BUILD61
-rw-r--r--examples/src/mobile.cc20
-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.golden4
-rw-r--r--examples/vendor/acme/BUILD18
-rw-r--r--examples/vendor/acme/coyote.cc3
-rw-r--r--examples/vendor/constant_gen/BUILD22
-rw-r--r--examples/vendor/constant_gen/constant_generator.py4
-rw-r--r--examples/vendor/constant_gen/defs.bzl14
-rw-r--r--examples/vendor/constant_gen/generated_code_licenses.golden44
-rw-r--r--examples/vendor/constant_gen/generator_licenses.golden37
-rw-r--r--examples/vendor/libhhgttg/BUILD18
-rw-r--r--examples/vendor/libhhgttg/answer.cc2
-rw-r--r--licenses/generic/BUILD8
-rw-r--r--rules/BUILD11
-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.bzl266
-rw-r--r--rules/license.bzl110
-rw-r--r--rules/license_impl.bzl82
-rw-r--r--rules/license_kind.bzl15
-rw-r--r--rules/license_policy.bzl53
-rw-r--r--rules/license_policy_check.bzl80
-rw-r--r--rules/licenses_core.bzl187
-rw-r--r--rules/providers.bzl47
-rw-r--r--rules/user_filtered_rule_kinds.bzl (renamed from rules/license_policy_provider.bzl)24
-rw-r--r--tests/BUILD65
-rw-r--r--tests/apps/BUILD70
-rw-r--r--tests/apps/an_app.cc11
-rw-r--r--tests/apps/an_app_licenses_test.py30
-rwxr-xr-xtests/hello_cc_copyrights.golden2
-rw-r--r--tests/hello_licenses_test.py34
-rw-r--r--tests/license_test_utils.py72
-rw-r--r--tests/thrdparty/BUILD26
-rw-r--r--tests/thrdparty/LICENSE1
-rw-r--r--tests/thrdparty/new_style_lib.cc8
-rw-r--r--tools/checker_demo.py34
-rw-r--r--tools/test_helpers.bzl47
52 files changed, 1650 insertions, 569 deletions
diff --git a/README.md b/README.md
index 10209d1..2cf85fb 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,5 @@
# 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
- declaring metadata about packages, such as
- the licenses the package is available under
diff --git a/distro/BUILD b/distro/BUILD
index 0231f13..a79d54b 100644
--- a/distro/BUILD
+++ b/distro/BUILD
@@ -17,7 +17,7 @@ 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"],
)
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..4469cce
--- /dev/null
+++ b/examples/manifest/main_golden.txt
@@ -0,0 +1,204 @@
+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.
+
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..f17bfa3 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
diff --git a/examples/src/BUILD b/examples/src/BUILD
index 29b6803..ecab5da 100644
--- a/examples/src/BUILD
+++ b/examples/src/BUILD
@@ -1,17 +1,25 @@
+# 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//rules:compliance.bzl", "check_license", "licenses_used")
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")
cc_binary(
name = "my_server",
srcs = ["server.cc"],
- deps = [
- ":message",
- "@rules_license//examples/vendor/libhhgttg",
- ],
+ deps = [":message"],
)
# Sample
@@ -21,32 +29,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 +50,12 @@ 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",
+ ],
)
diff --git a/examples/src/mobile.cc b/examples/src/mobile.cc
deleted file mode 100644
index d15090f..0000000
--- a/examples/src/mobile.cc
+++ /dev/null
@@ -1,20 +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.
-
-
-#include <iostream>
-
-int main(int argc, char* argv[]) {
- std::cout << "Hello world" << std::endl;
-}
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..c7c30da
--- /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/vendor/constant_gen:license_for_emitted_code"
+ ],
+ "/examples/src:message": [
+ "/examples/vendor/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
index 6d322b1..8be07a0 100644
--- a/examples/src/server_report.golden
+++ b/examples/src/server_report.golden
@@ -1,3 +1,3 @@
-= @rules_license//examples/vendor/constant_gen:license_for_emitted_code
- kind: @@rules_license//examples/my_org/licenses:unencumbered
+= //examples/vendor/constant_gen:license_for_emitted_code
+ kind: @//examples/my_org/licenses:unencumbered
conditions: []
diff --git a/examples/vendor/acme/BUILD b/examples/vendor/acme/BUILD
index 488af62..814957d 100644
--- a/examples/vendor/acme/BUILD
+++ b/examples/vendor/acme/BUILD
@@ -1,11 +1,21 @@
+# 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 = ["//visibility:public"],
-)
+package(default_applicable_licenses = [":license"])
# The default license for an entire package is typically named "license".
license(
diff --git a/examples/vendor/acme/coyote.cc b/examples/vendor/acme/coyote.cc
index e1e8083..d637855 100644
--- a/examples/vendor/acme/coyote.cc
+++ b/examples/vendor/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/vendor/constant_gen/BUILD
index a81885c..e70f489 100644
--- a/examples/vendor/constant_gen/BUILD
+++ b/examples/vendor/constant_gen/BUILD
@@ -1,8 +1,21 @@
+# 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("@rules_license//rules:license.bzl", "license")
load(":defs.bzl", "constant_gen")
package(
@@ -13,22 +26,20 @@ package(
# 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
@@ -69,3 +80,4 @@ golden_test(
golden = "generated_code_licenses.golden",
subject = ":generated_code_licenses.json",
)
+
diff --git a/examples/vendor/constant_gen/constant_generator.py b/examples/vendor/constant_gen/constant_generator.py
index 6bd92b2..432b6be 100644
--- a/examples/vendor/constant_gen/constant_generator.py
+++ b/examples/vendor/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/vendor/constant_gen/defs.bzl
index e54fcee..f3eb715 100644
--- a/examples/vendor/constant_gen/defs.bzl
+++ b/examples/vendor/constant_gen/defs.bzl
@@ -1,19 +1,5 @@
"""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");
-# 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.
-
def _constant_gen_impl(ctx):
# Turn text into a C++ constant.
outputs = [ctx.outputs.src_out]
diff --git a/examples/vendor/constant_gen/generated_code_licenses.golden b/examples/vendor/constant_gen/generated_code_licenses.golden
index 6aef78a..c4c8ef1 100644
--- a/examples/vendor/constant_gen/generated_code_licenses.golden
+++ b/examples/vendor/constant_gen/generated_code_licenses.golden
@@ -1,17 +1,41 @@
[
{
- "rule": "//examples/vendor/constant_gen:license_for_emitted_code",
- "license_kinds": [
+ "top_level_target": "//examples/vendor/constant_gen:libhello",
+ "dependencies": [
{
- "target": "@//examples/my_org/licenses:unencumbered",
- "name": "unencumbered",
- "conditions": []
+ "target_under_license": "//examples/vendor/constant_gen:libhello",
+ "licenses": [
+ "//examples/vendor/constant_gen:license_for_emitted_code"
+ ]
+ },
+ {
+ "target_under_license": "//examples/vendor/constant_gen:libhello_src_",
+ "licenses": [
+ "//examples/vendor/constant_gen:license_for_emitted_code"
+ ]
}
],
- "copyright_notice": "",
- "package_name": "Trivial Code Generator Output",
- "package_url": null,
- "package_version": null,
- "license_text": "examples/vendor/constant_gen/LICENSE"
+ "licenses": [
+ {
+ "label": "//examples/vendor/constant_gen:license_for_emitted_code",
+ "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": "",
+ "package_version": "",
+ "license_text": "examples/vendor/constant_gen/LICENSE.on_output",
+ "used_by": [
+ "//examples/vendor/constant_gen:libhello",
+ "//examples/vendor/constant_gen:libhello_src_"
+ ]
+ }
+ ]
}
]
diff --git a/examples/vendor/constant_gen/generator_licenses.golden b/examples/vendor/constant_gen/generator_licenses.golden
index f6fa349..4b2f175 100644
--- a/examples/vendor/constant_gen/generator_licenses.golden
+++ b/examples/vendor/constant_gen/generator_licenses.golden
@@ -1,17 +1,34 @@
[
{
- "rule": "//examples/vendor/constant_gen:license",
- "license_kinds": [
+ "top_level_target": "//examples/vendor/constant_gen:constant_generator",
+ "dependencies": [
{
- "target": "@//examples/my_org/licenses:generic_restricted",
- "name": "generic_restricted",
- "conditions": ["restricted"]
+ "target_under_license": "//examples/vendor/constant_gen:constant_generator",
+ "licenses": [
+ "//examples/vendor/constant_gen:license"
+ ]
}
],
- "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"
+ "licenses": [
+ {
+ "label": "//examples/vendor/constant_gen:license",
+ "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": "",
+ "package_version": "",
+ "license_text": "examples/vendor/constant_gen/LICENSE",
+ "used_by": [
+ "//examples/vendor/constant_gen:constant_generator"
+ ]
+ }
+ ]
}
]
diff --git a/examples/vendor/libhhgttg/BUILD b/examples/vendor/libhhgttg/BUILD
index c5da389..44d2d61 100644
--- a/examples/vendor/libhhgttg/BUILD
+++ b/examples/vendor/libhhgttg/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.
# A package with all code under a single license. This is the most common case
# we expect to see.
@@ -5,10 +18,7 @@ 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"],
-)
+package(default_applicable_licenses = [":license"])
# The default license for an entire package is typically named "license".
license(
diff --git a/examples/vendor/libhhgttg/answer.cc b/examples/vendor/libhhgttg/answer.cc
index 8b78f90..440bc62 100644
--- a/examples/vendor/libhhgttg/answer.cc
+++ b/examples/vendor/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 71880cf..def2334 100644
--- a/licenses/generic/BUILD
+++ b/licenses/generic/BUILD
@@ -86,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/rules/BUILD b/rules/BUILD
index b4dde6f..1d67059 100644
--- a/rules/BUILD
+++ b/rules/BUILD
@@ -15,6 +15,8 @@
# 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"],
@@ -22,6 +24,15 @@ package(
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(["**"]),
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 343759a..1792454 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",
+ "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,21 +81,56 @@ _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]))]
_licenses_used = rule(
implementation = _licenses_used_impl,
- doc = """Internal implementation method for licenses_used().""",
+ doc = """Internal tmplementation method for licenses_used().""",
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 d47a821..d2d9df5 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,113 @@
# 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_licenses_info_common",
+ "should_traverse",
+)
+load(
"@rules_license//rules:providers.bzl",
- "LicenseInfo",
- "LicensesInfo",
+ "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:]
+ 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_licenses_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 +133,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/license.bzl b/rules/license.bzl
index f726be1..183e106 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,47 +11,25 @@
# 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."""
+See: go/license-checking-v2
+"""
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_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]
+load(
+ "@rules_license//rules:license_impl.bzl",
+ "license_rule_impl",
+)
_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" +
@@ -59,6 +37,9 @@ _license = rule(
providers = [LicenseKindInfo],
cfg = "exec",
),
+ "copyright_notice": attr.string(
+ doc = "Copyright notice.",
+ ),
"license_text": attr.label(
allow_single_file = True,
default = "LICENSE",
@@ -80,51 +61,66 @@ _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",
+ visibility = ["//visibility:public"],
+ license_kind = None,
+ license_kinds = None,
+ copyright_notice = None,
+ package_name = None,
+ package_url = None,
+ package_version = None,
+ namespace = "compliance",
+ tags = []):
"""Wrapper for license rule.
Args:
name: str target name.
+ license_text: str Filename of the license file
+ visibility: list(label) visibility spec
+ 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.
+ 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.
+ tags: list(str) tags applied to the rule
"""
- 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]
+
+ # 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..c506953
--- /dev/null
+++ b/rules/license_impl.bzl
@@ -0,0 +1,82 @@
+# 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.
+
+See: go/license-checking-v2
+"""
+
+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]
+
+license_impl = rule(
+ implementation = license_rule_impl,
+ attrs = {
+ "license_kinds": attr.label_list(
+ 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],
+ cfg = "exec",
+ ),
+ "copyright_notice": attr.string(
+ doc = "Copyright notice.",
+ ),
+ "license_text": attr.label(
+ allow_single_file = True,
+ default = "LICENSE",
+ doc = "The license file.",
+ ),
+ "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.",
+ ),
+ "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.",
+ ),
+ },
+)
diff --git a/rules/license_kind.bzl b/rules/license_kind.bzl
index 47b7639..5c8022e 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,15 +37,16 @@ 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"),
},
)
@@ -53,6 +54,8 @@ _license_kind = rule(
def license_kind(name, **kwargs):
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/license_policy.bzl b/rules/license_policy.bzl
deleted file mode 100644
index 0539301..0000000
--- a/rules/license_policy.bzl
+++ /dev/null
@@ -1,53 +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.
-
-"""license_policy rule.
-
-A license_policy names a set of conditions allowed in the union of all
-license_kinds use by a target. The name of the rule is typically an
-application type (e.g. production_server, mobile_application, ...)
-
-"""
-
-load("@rules_license//rules:license_policy_provider.bzl", "LicensePolicyInfo")
-
-def _license_policy_impl(ctx):
- provider = LicensePolicyInfo(
- name = ctx.attr.name,
- label = "@%s//%s:%s" % (
- ctx.label.workspace_name,
- ctx.label.package,
- ctx.label.name,
- ),
- conditions = ctx.attr.conditions,
- )
- return [provider]
-
-_license_policy = rule(
- implementation = _license_policy_impl,
- attrs = {
- "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,
- ),
- },
-)
-
-def license_policy(name, conditions):
- _license_policy(
- name = name,
- conditions = conditions,
- applicable_licenses = [],
- )
diff --git a/rules/license_policy_check.bzl b/rules/license_policy_check.bzl
deleted file mode 100644
index 49ab207..0000000
--- a/rules/license_policy_check.bzl
+++ /dev/null
@@ -1,80 +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.
-
-"""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",
- "LicensePolicyInfo",
-)
-load(
- "@rules_license//rules:providers.bzl",
- "LicensesInfo",
-)
-
-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():
- 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,
- ))
- return [DefaultInfo()]
-
-_license_policy_check = rule(
- implementation = _license_policy_check_impl,
- doc = """Internal implementation method for license_policy_check().""",
- attrs = {
- "policy": attr.label(
- doc = """Policy definition.""",
- mandatory = True,
- providers = [LicensePolicyInfo],
- ),
- "target": attr.label(
- doc = """Target to collect LicenseInfo for.""",
- aspects = [gather_licenses_info],
- mandatory = True,
- allow_single_file = True,
- ),
- },
-)
-
-def license_policy_check(name, target, policy, **kwargs):
- """Checks a target against a policy.
-
- Args:
- name: The target.
- target: A target to test for compliance with a policy
- policy: A rule providing LicensePolicyInfo.
- **kwargs: other args.
-
- Usage:
-
- license_policy_check(
- name = "license_info",
- target = ":my_app",
- policy = "//my_org/compliance/policies:mobile_application",
- )
- """
- _license_policy_check(name = name, target = target, policy = policy, **kwargs)
diff --git a/rules/licenses_core.bzl b/rules/licenses_core.bzl
new file mode 100644
index 0000000..42702bd
--- /dev/null
+++ b/rules/licenses_core.bzl
@@ -0,0 +1,187 @@
+# 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",
+ "LicensedTargetInfo",
+)
+
+
+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_licenses(ctx, trans_licenses, 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)
+
+def gather_licenses_info_common(target, ctx, provider_factory, namespaces, filter_func):
+ """Collect license 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
+ 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 = []
+ 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")
+
+
+ # Now gather transitive collection of providers from the targets
+ # this target depends upon.
+ trans_licenses = []
+ trans_deps = []
+ traces = []
+ _get_transitive_licenses(ctx, trans_licenses, 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
+
+ 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,
+ )]
diff --git a/rules/providers.bzl b/rules/providers.bzl
index 9e830ce..8778fd7 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,51 @@
# 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 license rules."""
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.""",
+LicensedTargetInfo = provider(
+ doc = """Lists the licenses directly used by a single target.""",
fields = {
- "licenses": "list(LicenseInfo).",
+ "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()
diff --git a/rules/license_policy_provider.bzl b/rules/user_filtered_rule_kinds.bzl
index caecce8..a099794 100644
--- a/rules/license_policy_provider.bzl
+++ b/rules/user_filtered_rule_kinds.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,14 +11,18 @@
# 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.
-"""LicensePolicyProvider."""
+The format of this dictionary is:
+ rule_name: [attr, attr, ...]
-LicensePolicyInfo = provider(
- doc = """Declares a policy name and the license conditions allowable under it.""",
- fields = {
- "conditions": "List of conditions to be met when using this software.",
- "label": "The full path to the license policy definition.",
- "name": "License policy name",
- },
-)
+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 256386a..10b8560 100644
--- a/tests/BUILD
+++ b/tests/BUILD
@@ -1,13 +1,17 @@
-"""Test cases for license rules."""
+# Test cases for license rules.
-load("@rules_license//rules:compliance.bzl", "check_license")
+load("@rules_license//tools:test_helpers.bzl", "golden_test")
+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"])
-licenses(["notice"])
+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
@@ -41,8 +45,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(
@@ -55,7 +67,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//rules/tests/legacy:another_library_with_legacy_license_clause",
+ # "@rules_license//rules/tests/legacy:library_with_legacy_license_clause",
+ #],
)
java_binary(
@@ -87,6 +110,28 @@ 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"],
+ 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,
@@ -109,3 +154,7 @@ golden_test(
golden = "hello_java_copyrights.golden",
subject = ":hello_java_copyrights.txt",
)
+
+exports_files([
+ "hello_licenses.golden",
+])
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..410f986
--- /dev/null
+++ b/tests/apps/an_app.cc
@@ -0,0 +1,11 @@
+#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..9899e6c
--- /dev/null
+++ b/tests/apps/an_app_licenses_test.py
@@ -0,0 +1,30 @@
+"""Tests for google3.tools.build_defs.license.tests.apps.an_app_licenses."""
+
+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
index ac28bab..38f67ad 100755
--- a/tests/hello_cc_copyrights.golden
+++ b/tests/hello_cc_copyrights.golden
@@ -1 +1,3 @@
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..465688f
--- /dev/null
+++ b/tests/hello_licenses_test.py
@@ -0,0 +1,34 @@
+"""Tests for google3.tools.build_defs.license.tests.hello_licenses."""
+
+import os
+
+import unittest
+from tests import license_test_utils
+
+
+class HelloLicensesTest(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__), "hello_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:hello": [
+ "/tests:license",
+ ],
+ "/tests:c_bar": [
+ "/tests:license",
+ "/tests:license_for_extra_feature",
+ ],
+ }
+ license_test_utils.check_licenses_of_dependencies(
+ self, licenses_info, expected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/license_test_utils.py b/tests/license_test_utils.py
new file mode 100644
index 0000000..2c5a18a
--- /dev/null
+++ b/tests/license_test_utils.py
@@ -0,0 +1,72 @@
+"""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 names to the licenses they are under. Names
+ must be relative to the licenses package, not absolute.
+ 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.
+ print(licenses_info)
+ deps_to_licenses = {
+ x["target_under_license"].lstrip('@'): set(l.strip('@') for l in x["licenses"])
+ for x in licenses_info[0]["dependencies"]}
+ print(deps_to_licenses)
+
+ for target, licenses in expected.items():
+ got_licenses = set(deps_to_licenses[path_prefix + target])
+ for lic in licenses:
+ test_case.assertIn(path_prefix + lic, got_licenses)
+ # future: Maybe check that deps is not larger than expected.
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/tests/thrdparty/new_style_lib.cc b/tests/thrdparty/new_style_lib.cc
new file mode 100644
index 0000000..545e5b4
--- /dev/null
+++ b/tests/thrdparty/new_style_lib.cc
@@ -0,0 +1,8 @@
+
+#include <iostream>
+#include <ostream>
+
+void new_lib_func() {
+ std::cout << "This is restricted code" << std::endl;
+}
+
diff --git a/tools/checker_demo.py b/tools/checker_demo.py
index 73042aa..1075621 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):
@@ -82,11 +90,12 @@ def _do_copyright_notices(out, licenses):
if l.get('package_version'):
name = name + "/" + l['package_version']
# IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one.
+ print(l)
out.write('package(%s), copyright(%s)\n' % (name, l['copyright_notice']))
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 +116,14 @@ 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']
+ print(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
+ )