diff options
54 files changed, 2020 insertions, 0 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..3c52212 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +## Expected Behavior + + +## Actual Behavior + + +## Steps to Reproduce the Problem + +1. +1. +1. + +## Specifications + +- Version: +- Platform:
\ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a5b6657 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Fixes #<issue_number_goes_here> + +> It's a good idea to open an issue first for discussion. + +- [ ] Tests pass +- [ ] Tests and examples for any new features. +- [ ] Appropriate changes to README are included in PR diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..39b95d1 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @aiuto @@ -0,0 +1,202 @@ + + 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/README.md b/README.md new file mode 100644 index 0000000..7cba46e --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# rules_license + +This repository contains a set of rules and tools for +- asserting that packages are available under specified OSS licenses +- gathering those license assertions into artifacts to ship with code +- applying organization specific compliance constriants against the + set of licenses used by a target. + +See [License Checking with +Bazel](https://docs.google.com/document/d/1uwBuhAoBNrw8tmFs-NxlssI6VRolidGYdYqagLqHWt8/edit#) +for more information about this project. + +WARNING: The code here is still in early, active development. + +## Disclaimer + +This is not an officially supported Google product. diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..0e352d6 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1 @@ +workspace(name = "rules_license") diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8529ae6 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,93 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + +Reports should be directed to Tony Aiuto (aiuto@google.com), the +Project Steward(s) for bazelbuild/rules_license. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Google Open Source Strategy team. If for any reason you +are uncomfortable reaching out to the Project Steward, please email +opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..22b241c --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement (CLA). You (or your employer) retain the copyright to your +contribution; this simply gives us permission to use and redistribute your +contributions as part of the project. Head over to +<https://cla.developers.google.com/> to see your current agreements on file or +to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). diff --git a/examples/BUILD b/examples/BUILD new file mode 100644 index 0000000..4d93f01 --- /dev/null +++ b/examples/BUILD @@ -0,0 +1 @@ +# rules_license examples diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..0afb5ca --- /dev/null +++ b/examples/README.md @@ -0,0 +1,16 @@ +# Examples for rules_license + +This set of files provides an example of how license rules can be used. + +Terminology +- Organization: A company, organization or other entity that wants to use + license rules to enforce their particular compliance needs. These examples + use the work organization throughout. +- 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 + compliance team. + +## Overview + diff --git a/examples/my_org/compliance/BUILD b/examples/my_org/compliance/BUILD new file mode 100644 index 0000000..074b21e --- /dev/null +++ b/examples/my_org/compliance/BUILD @@ -0,0 +1,31 @@ +# 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 new file mode 100644 index 0000000..d9d5c25 --- /dev/null +++ b/examples/my_org/licenses/BUILD @@ -0,0 +1,58 @@ +# Example license kind definitions. + +# We expect that all license_kind rules used by an organization exist in a +# central place in their source repository. +# +# - This allows centralized audit and, with suport from the SCM, an assurance +# that individual developers are not adding new license kinds to the code +# base without authorization. +# - When third party packages are used, they might come with license rules +# pointing to well known license_kinds from @rules_license/licenses. +# At import time, users can mechanically transform those target paths from +# @rules_license to their own license_kind repository. +# - The conditions for each license_kind may be customized for the organzation's +# needs, and not match the conditions used by the canonical versions from +# @rules_license. +# - In rare cases, a third_party project will define their own license_kinds. +# There is no reasonable automatic handling of that. Organizations will have +# to hand inspect the license text to see if it matches the conditions, and +# then will have to hand import it to their private license_kind repository. + +load("@rules_license//rules:license_kind.bzl", "license_kind") + +package(default_visibility = ["//examples:__subpackages__"]) + +# license_kind rules generally appear in a central location per workspace. They +# are intermingled with normal target build rules +license_kind( + name = "generic_notice", + conditions = [ + "notice", + ], +) + +license_kind( + name = "generic_restricted", + conditions = [ + "restricted", + ], +) + +license_kind( + name = "unencumbered", + conditions = [], # none +) + +license_kind( + name = "lgpl_like", + conditions = [ + "restricted_if_statically_linked", + ], +) + +license_kind( + name = "acme_corp_paid", + conditions = [ + "whitelist:acme_corp_paid", + ], +) diff --git a/examples/src/BUILD b/examples/src/BUILD new file mode 100644 index 0000000..be415fc --- /dev/null +++ b/examples/src/BUILD @@ -0,0 +1,64 @@ +# Examples of applications and interactions with licenses + +load("@rules_license//examples/vendor/constant_gen:defs.bzl", "constant_gen") +load("@rules_license//rules:compliance.bzl", "check_license", "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", + ], +) + +# Sample +constant_gen( + name = "message", + text = "Hello, world.", + var = "server_message", +) + +license_policy_check( + name = "check_server", + policy = "@rules_license//examples/my_org/compliance:production_service", + target = ":my_server", +) + +cc_binary( + name = "my_violating_server", + srcs = ["server.cc"], + deps = [ + ":message", + "@rules_license//examples/vendor/acme", + "@rules_license//examples/vendor/libhhgttg", + ], +) + +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. + +licenses_used( + name = "server_licenses", + out = "server_licenses.json", + deps = [":my_server"], +) + +golden_test( + name = "verify_server_licenses_test", + golden = "server_licenses.golden", + subject = ":server_licenses.json", +) diff --git a/examples/src/mobile.cc b/examples/src/mobile.cc new file mode 100644 index 0000000..d15090f --- /dev/null +++ b/examples/src/mobile.cc @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 0000000..8f7990e --- /dev/null +++ b/examples/src/server.cc @@ -0,0 +1,22 @@ +// 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> + +extern const char* server_message; + +int main(int argc, char* argv[]) { + std::cout << server_message << std::endl; + return 0; +} diff --git a/examples/src/server_licenses.golden b/examples/src/server_licenses.golden new file mode 100644 index 0000000..a8f388b --- /dev/null +++ b/examples/src/server_licenses.golden @@ -0,0 +1,28 @@ +[ + { + "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", + "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": "", + "license_text": "examples/vendor/libhhgttg/LICENSE" + } +] diff --git a/examples/src/server_report.golden b/examples/src/server_report.golden new file mode 100644 index 0000000..6d322b1 --- /dev/null +++ b/examples/src/server_report.golden @@ -0,0 +1,3 @@ += @rules_license//examples/vendor/constant_gen:license_for_emitted_code + kind: @@rules_license//examples/my_org/licenses:unencumbered + conditions: [] diff --git a/examples/vendor/README.md b/examples/vendor/README.md new file mode 100644 index 0000000..273dea6 --- /dev/null +++ b/examples/vendor/README.md @@ -0,0 +1,6 @@ +# Third party packges used by your project. + +Note that these are presumed to be vendored in to your source tree. +These examples to not try to address the concerns of organizations +that wish to use license software without first examining the license +and preserving their own copy and audit trail. diff --git a/examples/vendor/acme/ACME_LICENSE b/examples/vendor/acme/ACME_LICENSE new file mode 100644 index 0000000..53a5daf --- /dev/null +++ b/examples/vendor/acme/ACME_LICENSE @@ -0,0 +1,3 @@ +Acme Novelty & Software provides a license to this software under terms laid +out in specific contracts. Unless you have purchased a license from us, you may +not use this software for any purpose. diff --git a/examples/vendor/acme/BUILD b/examples/vendor/acme/BUILD new file mode 100644 index 0000000..488af62 --- /dev/null +++ b/examples/vendor/acme/BUILD @@ -0,0 +1,22 @@ +# A package with a commercial license. + +load("@rules_license//rules:license.bzl", "license") + +package( + default_applicable_licenses = [":license"], + default_visibility = ["//visibility:public"], +) + +# The default license for an entire package is typically named "license". +license( + name = "license", + license_kinds = [ + "@rules_license//examples/my_org/licenses:acme_corp_paid", + ], + license_text = "ACME_LICENSE", +) + +cc_library( + name = "acme", + srcs = ["coyote.cc"], +) diff --git a/examples/vendor/acme/coyote.cc b/examples/vendor/acme/coyote.cc new file mode 100644 index 0000000..e1e8083 --- /dev/null +++ b/examples/vendor/acme/coyote.cc @@ -0,0 +1,17 @@ +// 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. + +bool caught_road_runner() { + return false; +} diff --git a/examples/vendor/constant_gen/BUILD b/examples/vendor/constant_gen/BUILD new file mode 100644 index 0000000..e837fd1 --- /dev/null +++ b/examples/vendor/constant_gen/BUILD @@ -0,0 +1,69 @@ +# An example of a code generator with a distinct license for the generated code. + +load(":defs.bzl", "constant_gen") +load("@rules_license//rules:compliance.bzl", "licenses_used") +load("@rules_license//rules:license.bzl", "license") +load("@rules_license//tools:test_helpers.bzl", "golden_test") + +package( + default_applicable_licenses = [":license"], + default_visibility = ["//visibility:public"], +) + +# 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", +) + +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", + ], +) + +# The generator itself will be licensed under :license +py_binary( + name = "constant_generator", + srcs = ["constant_generator.py"], + python_version = "PY3", +) + +# Sample: This target will be licensed under :license_for_emitted_code +constant_gen( + name = "libhello", + text = "Hello, world.", + var = "hello_world", +) + +# Verify the licenses are what we expect +licenses_used( + name = "generator_licenses", + out = "generator_licenses.json", + deps = [":constant_generator"], +) + +golden_test( + name = "verify_generator_licenses", + golden = "generator_licenses.golden", + subject = ":generator_licenses.json", +) + +licenses_used( + name = "generated_code_licenses", + # Note: using default output file name + deps = [":libhello"], +) + +golden_test( + name = "verify_generated_code_licenses", + golden = "generated_code_licenses.golden", + subject = ":generated_code_licenses.json", +) diff --git a/examples/vendor/constant_gen/LICENSE b/examples/vendor/constant_gen/LICENSE new file mode 100644 index 0000000..861da0d --- /dev/null +++ b/examples/vendor/constant_gen/LICENSE @@ -0,0 +1 @@ +This code is provided under a license which contains some restrictions. diff --git a/examples/vendor/constant_gen/LICENSE.on_output b/examples/vendor/constant_gen/LICENSE.on_output new file mode 100644 index 0000000..b699638 --- /dev/null +++ b/examples/vendor/constant_gen/LICENSE.on_output @@ -0,0 +1 @@ +The generated output from constant_gen has no license encumberances. diff --git a/examples/vendor/constant_gen/constant_generator.py b/examples/vendor/constant_gen/constant_generator.py new file mode 100644 index 0000000..6bd92b2 --- /dev/null +++ b/examples/vendor/constant_gen/constant_generator.py @@ -0,0 +1,33 @@ +# 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. + +# 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 +generates code. +""" + +import sys + + +def main(argv): + if len(argv) != 4: + raise Exception('usage: constant_generator out_file var_name text') + with open(argv[1], 'w') as out: + out.write('const char* %s = "%s";\n' % (argv[2], argv[3])) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/examples/vendor/constant_gen/defs.bzl b/examples/vendor/constant_gen/defs.bzl new file mode 100644 index 0000000..f979664 --- /dev/null +++ b/examples/vendor/constant_gen/defs.bzl @@ -0,0 +1,59 @@ +"""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] + ctx.actions.run( + mnemonic = "GenerateConstant", + progress_message = "Generating %s" % ctx.attr.var, + outputs = outputs, + executable = ctx.executable._generator, + arguments = [ctx.outputs.src_out.path, ctx.attr.var, ctx.attr.text], + ) + return [DefaultInfo(files = depset(outputs))] + +_constant_gen = rule( + implementation = _constant_gen_impl, + attrs = { + "src_out": attr.output(mandatory = True), + "text": attr.string(mandatory = True), + "var": attr.string(mandatory = False), + "_generator": attr.label( + default = Label("@rules_license//examples/vendor/constant_gen:constant_generator"), + executable = True, + allow_files = True, + cfg = "host", + ), + }, +) + +def constant_gen(name, text, var): + # Generate the code + _constant_gen( + name = name + "_src_", + src_out = name + "_src_.cc", + text = text, + var = var, + applicable_licenses = ["@rules_license//examples/vendor/constant_gen:license_for_emitted_code"], + ) + + # And turn it into a library we can link against + native.cc_library( + name = name, + srcs = [name + "_src_"], + applicable_licenses = ["@rules_license//examples/vendor/constant_gen:license_for_emitted_code"], + ) diff --git a/examples/vendor/constant_gen/generated_code_licenses.golden b/examples/vendor/constant_gen/generated_code_licenses.golden new file mode 100644 index 0000000..a37723a --- /dev/null +++ b/examples/vendor/constant_gen/generated_code_licenses.golden @@ -0,0 +1,15 @@ +[ + { + "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", + "license_text": "examples/vendor/constant_gen/LICENSE" + } +] diff --git a/examples/vendor/constant_gen/generator_licenses.golden b/examples/vendor/constant_gen/generator_licenses.golden new file mode 100644 index 0000000..164b00b --- /dev/null +++ b/examples/vendor/constant_gen/generator_licenses.golden @@ -0,0 +1,15 @@ +[ + { + "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", + "license_text": "examples/vendor/constant_gen/LICENSE" + } +] diff --git a/examples/vendor/libhhgttg/BUILD b/examples/vendor/libhhgttg/BUILD new file mode 100644 index 0000000..c5da389 --- /dev/null +++ b/examples/vendor/libhhgttg/BUILD @@ -0,0 +1,25 @@ +# A package with all code under a single license. This is the most common case +# we expect to see. + +load("@rules_license//rules:license.bzl", "license") + +# Using a package wide default ensure that all targets are associated with the +# license. +package( + default_applicable_licenses = [":license"], + default_visibility = ["//visibility:public"], +) + +# The default license for an entire package is typically named "license". +license( + name = "license", + license_kinds = [ + "@rules_license//examples/my_org/licenses:generic_notice", + ], + license_text = "LICENSE", +) + +cc_library( + name = "libhhgttg", + srcs = ["answer.cc"], +) diff --git a/examples/vendor/libhhgttg/LICENSE b/examples/vendor/libhhgttg/LICENSE new file mode 100644 index 0000000..660e329 --- /dev/null +++ b/examples/vendor/libhhgttg/LICENSE @@ -0,0 +1,2 @@ +You can do whatever you want with this software. Just incude this license +with your distribution. diff --git a/examples/vendor/libhhgttg/answer.cc b/examples/vendor/libhhgttg/answer.cc new file mode 100644 index 0000000..8b78f90 --- /dev/null +++ b/examples/vendor/libhhgttg/answer.cc @@ -0,0 +1,15 @@ +// 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. + +int answer = 42; diff --git a/rules/BUILD b/rules/BUILD new file mode 100644 index 0000000..33f6ab6 --- /dev/null +++ b/rules/BUILD @@ -0,0 +1,18 @@ +# BUILD file defining @rules_license/rules +# +# 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. +# +# TODO(aiuto): Add dependency on Skylib for bzl_library so that we can later +# generate docs with stardoc. diff --git a/rules/compliance.bzl b/rules/compliance.bzl new file mode 100644 index 0000000..8144003 --- /dev/null +++ b/rules/compliance.bzl @@ -0,0 +1,140 @@ +# 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 compliance checking.""" + +load( + "@rules_license//rules:providers.bzl", + "LicensesInfo", +) +load( + "@rules_license//rules:gather_licenses_info.bzl", + "gather_licenses_info", + "write_licenses_info", +) + +# Debugging verbosity +_VERBOSITY = 0 + +def _debug(loglevel, msg): + if _VERBOSITY > loglevel: + print(msg) # buildifier: disable=print + +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) + + # Now run the checker on it + inputs = [licenses_file] + outputs = [ctx.outputs.report] + args = ctx.actions.args() + args.add("--licenses_info", licenses_file.path) + args.add("--report", ctx.outputs.report.path) + if ctx.attr.check_conditions: + args.add("--check_conditions") + if ctx.outputs.copyright_notices: + args.add("--copyright_notices", ctx.outputs.copyright_notices.path) + outputs.append(ctx.outputs.copyright_notices) + if ctx.outputs.license_texts: + args.add("--license_texts", ctx.outputs.license_texts.path) + outputs.append(ctx.outputs.license_texts) + inputs.extend(license_files) + ctx.actions.run( + mnemonic = "CheckLicenses", + progress_message = "Checking license compliance for %s" % ctx.label, + inputs = inputs, + outputs = outputs, + executable = ctx.executable._checker, + arguments = [args], + ) + outputs.append(licenses_file) # also make the json file available. + return [DefaultInfo(files = depset(outputs))] + +_check_license = rule( + implementation = _check_license_impl, + attrs = { + "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( + default = Label("@rules_license//tools:checker_demo"), + executable = True, + allow_files = True, + cfg = "host", + ), + }, +) + +def check_license(**kwargs): + _check_license(**kwargs) + +def _licenses_used_impl(ctx): + """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 tmplementation method for licenses_used().""", + attrs = { + "deps": attr.label_list( + doc = """List of targets to collect LicenseInfo for.""", + aspects = [gather_licenses_info], + ), + "out": attr.output( + doc = """Output file.""", + mandatory = True, + ), + }, +) + +def licenses_used(name, deps, out = None, **kwargs): + """Collects LicensedInfo providers for a set of targets and writes as JSON. + + The output is a single JSON array, with an entry for each license used. + See gather_licenses_info.bzl:write_licenses_info() for a description of the schema. + + Args: + name: The target. + deps: A list of targets to get LicenseInfo for. The output is the union of + the result, not a list of information for each dependency. + out: The output file name. Default: <name>.json. + **kwargs: Other args + + Usage: + + licenses_used( + name = "license_info", + deps = [":my_app"], + out = "license_info.json", + ) + """ + if not out: + out = name + ".json" + _licenses_used(name = name, deps = deps, out = out, **kwargs) diff --git a/rules/default_license.bzl b/rules/default_license.bzl new file mode 100644 index 0000000..b2deeb1 --- /dev/null +++ b/rules/default_license.bzl @@ -0,0 +1,55 @@ +# 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", + "LicenseKindInfo", + "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 = { + "deps": attr.label_list( + mandatory = True, + doc = "Licenses", + providers = [LicenseInfo], + cfg = "host", + ), + "conditions": attr.string_list( + doc = "", + ), + }, +) + +def default_licenses(licenses, conditions = None): + _default_licenses( + name = "__default_licenses", + deps = ["%s_license" % l for l in licenses], + conditions = conditions, + ) diff --git a/rules/gather_licenses_info.bzl b/rules/gather_licenses_info.bzl new file mode 100644 index 0000000..9d6db25 --- /dev/null +++ b/rules/gather_licenses_info.bzl @@ -0,0 +1,126 @@ +# 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. + +"""Rules and macros for collecting LicenseInfo providers.""" + +load( + "@rules_license//rules:providers.bzl", + "LicenseInfo", + "LicensesInfo", +) + +# Debugging verbosity +_VERBOSITY = 0 + +def _debug(loglevel, msg): + if _VERBOSITY > loglevel: + print(msg) # buildifier: disable=print + +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) + +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))] + +gather_licenses_info = aspect( + doc = """Collects LicenseInfo providers into a single LicensesInfo provider.""", + implementation = _gather_licenses_info_impl, + attr_aspects = ["applicable_licenses", "deps", "srcs"], +) + +def write_licenses_info(ctx, deps, json_out): + """Writes LicensesInfo providers for a set of targets as JSON. + + TODO(aiuto): Document JSON schema. + + 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. + + foo = rule( + implementation = _foo_impl, + attrs = { + "deps": attr.label_list(aspects = [gather_licenses_info]) + } + ) + + def _foo_impl(ctx): + ... + out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name) + write_licenses_info(ctx, ctx.attr.deps, licenses_file) + + Args: + ctx: context of the caller + deps: a list of deps which should have LicensesInfo 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}", + "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, + license_text = license.license_text.path, + kinds = ",\n".join(kinds), + )) + ctx.actions.write( + output = json_out, + content = "[\n%s\n]\n" % ",\n".join(licenses), + ) diff --git a/rules/license.bzl b/rules/license.bzl new file mode 100644 index 0000000..2195619 --- /dev/null +++ b/rules/license.bzl @@ -0,0 +1,102 @@ +# 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. + +"""Rules for declaring the licenses used by a package.""" + +load( + "@rules_license//rules:providers.bzl", + "LicenseInfo", + "LicenseKindInfo", +) + +# Debugging verbosity +_VERBOSITY = 0 + +def _debug(loglevel, msg): + if _VERBOSITY > loglevel: + print(msg) # buildifier: disable=print + +# +# license() +# + +def _license_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, + license_text = ctx.file.license_text, + rule = ctx.label, + ) + _debug(0, provider) + return [provider] + +_license = rule( + implementation = _license_impl, + attrs = { + "license_kinds": attr.label_list( + mandatory = True, + 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 = "host", + ), + "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.", + ), + }, +) + +# buildifier: disable=function-docstring-args +def license(name, license_kinds = None, license_kind = None, copyright_notice = None, package_name = None, tags = None, **kwargs): + """Wrapper for license rule. + + Args: + name: str target name. + 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. + """ + license_text_arg = kwargs.get("license_text") or "LICENSE" + single_kind = kwargs.get("license_kind") + if single_kind: + if license_kinds: + fail("Can not use both license_kind and license_kinds") + license_kinds = [single_kind] + tags = tags or [] + _license( + name = name, + license_kinds = license_kinds, + license_text = license_text_arg, + copyright_notice = copyright_notice, + package_name = package_name, + applicable_licenses = [], + tags = tags, + ) diff --git a/rules/license_kind.bzl b/rules/license_kind.bzl new file mode 100644 index 0000000..78b0594 --- /dev/null +++ b/rules/license_kind.bzl @@ -0,0 +1,60 @@ +# 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", "LicenseKindInfo") + +# +# License Kind: The declaration of a well known category of license, for example, +# Apache, MIT, LGPL v2. An organization may also declare its own license kinds +# that it may user privately. +# + +def _license_kind_impl(ctx): + provider = LicenseKindInfo( + 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_kind = rule( + implementation = _license_kind_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, + ), + "canonical_text": attr.label( + doc = "File containing the canonical text for this license. Must be UTF-8 encoded.", + allow_single_file = True, + ), + "url": attr.string(doc = "URL pointing to canonical license definition"), + }, +) + +def license_kind(name, **kwargs): + if "conditions" not in kwargs: + kwargs["conditions"] = [] + _license_kind( + name = name, + applicable_licenses = [], + **kwargs + ) diff --git a/rules/license_policy.bzl b/rules/license_policy.bzl new file mode 100644 index 0000000..0539301 --- /dev/null +++ b/rules/license_policy.bzl @@ -0,0 +1,53 @@ +# 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 new file mode 100644 index 0000000..bdd8c05 --- /dev/null +++ b/rules/license_policy_check.bzl @@ -0,0 +1,80 @@ +# 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:providers.bzl", + "LicensesInfo", +) +load( + "@rules_license//rules:gather_licenses_info.bzl", + "gather_licenses_info", +) +load( + "@rules_license//rules:license_policy_provider.bzl", + "LicensePolicyInfo", +) + +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 tmplementation method for license_policy_check().""", + attrs = { + "target": attr.label( + doc = """Target to collect LicenseInfo for.""", + aspects = [gather_licenses_info], + mandatory = True, + allow_single_file = True, + ), + "policy": attr.label( + doc = """Policy definition.""", + mandatory = True, + providers = [LicensePolicyInfo], + ), + }, +) + +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/license_policy_provider.bzl b/rules/license_policy_provider.bzl new file mode 100644 index 0000000..c604e69 --- /dev/null +++ b/rules/license_policy_provider.bzl @@ -0,0 +1,24 @@ +# 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. + +"""LicensePolicyProvider.""" + +LicensePolicyInfo = provider( + doc = """Declares a policy name and the license conditions allowable under it.""", + fields = { + "name": "License Policy Name", + "label": "The full path to the license policy definition.", + "conditions": "List of conditions to be met when using this software.", + }, +) diff --git a/rules/providers.bzl b/rules/providers.bzl new file mode 100644 index 0000000..676fc79 --- /dev/null +++ b/rules/providers.bzl @@ -0,0 +1,42 @@ +# 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. + +"""Providers for license rules.""" + +LicenseKindInfo = provider( + doc = """Provides information about a license kind.""", + fields = { + "name": "License Name", + "label": "The full path to the license kind definition.", + "conditions": "List of conditions to be met when using this software.", + }, +) + +LicenseInfo = provider( + doc = """Provides information about an instance of a license.""", + fields = { + "copyright_notice": "Human readable short copyright notice", + "license_kinds": "License kinds", + "license_text": "License file", + "package_name": "Human readable package name", + "rule": "From whence this came", + }, +) + +LicensesInfo = provider( + doc = """The set of license instances used in a target.""", + fields = { + "licenses": "list(LicenseInfo).", + }, +) diff --git a/tests/BUILD b/tests/BUILD new file mode 100644 index 0000000..8bfdb19 --- /dev/null +++ b/tests/BUILD @@ -0,0 +1,105 @@ +# Test cases for license rules. + +load("@rules_license//rules:compliance.bzl", "check_license") +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"]) + +# license_kind rules generally appear in a central location per workspace. They +# are intermingled with normal target build rules +license_kind( + name = "generic_notice_license", + conditions = [ + "notice", + ], +) + +license_kind( + name = "generic_restricted_license", + conditions = [ + "restricted", + ], +) + +# The default license for an entire package is typically named "license". +license( + name = "license", + package_name = "A test case package", + # Note the UTF-8 encoded copyright symbol. + copyright_notice = "Copyright © 2019 Uncle Toasty", + license_kinds = [":generic_notice_license"], +) + +license( + name = "license_for_extra_feature", + package_name = "A test case package", + license = "LICENSE.extra", + license_kinds = [":generic_restricted_license"], +) + +cc_binary( + name = "hello", + srcs = ["hello.cc"], + deps = [ + ":c_bar", + ], +) + +cc_library( + name = "c_bar", + srcs = ["bar.cc"], +) + +java_binary( + name = "hello_java", + srcs = ["Hello.java"], + # Add an addition license to this target, beyond what my deps have. + applicable_licenses = [ + ":license_for_extra_feature", + ], + main_class = "Hello", + deps = [ + ":j_bar", + ], +) + +java_library( + name = "j_bar", + srcs = ["Bar.java"], +) + +check_license( + name = "check_cc_app", + check_conditions = False, + copyright_notices = "hello_cc_copyrights.txt", + license_texts = "hello_cc_licenses.txt", + report = "hello_cc_report", + deps = [ + ":hello", + ], +) + +check_license( + name = "check_java_app", + check_conditions = False, + copyright_notices = "hello_java_copyrights.txt", + license_texts = "hello_java_licenses.txt", + report = "hello_java_report", + deps = [ + ":hello_java", + ], +) + +golden_test( + name = "verify_cc_app_test", + golden = "hello_cc_copyrights.golden", + subject = ":hello_cc_copyrights.txt", +) + +golden_test( + name = "verify_java_app_test", + golden = "hello_java_copyrights.golden", + subject = ":hello_java_copyrights.txt", +) diff --git a/tests/Bar.java b/tests/Bar.java new file mode 100644 index 0000000..fd57c7d --- /dev/null +++ b/tests/Bar.java @@ -0,0 +1,25 @@ +// 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. + +/** + * + */ +final class Bar { + + static final int PI = 3; + + private Bar() { + } + +} diff --git a/tests/Hello.java b/tests/Hello.java new file mode 100644 index 0000000..b4a1037 --- /dev/null +++ b/tests/Hello.java @@ -0,0 +1,26 @@ +// 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. + +/** + * + */ +final class Hello { + + private Hello() { + } + + public static int main(String[] args) { + return Bar.PI; + } +} diff --git a/tests/LICENSE b/tests/LICENSE new file mode 100644 index 0000000..bc6bc98 --- /dev/null +++ b/tests/LICENSE @@ -0,0 +1,6 @@ +Copyright © 2019 Uncle Toasty + +Example of a notice style license. + +You may use this software in any way as long as you include the copyright +notice above. diff --git a/tests/LICENSE.extra b/tests/LICENSE.extra new file mode 100644 index 0000000..fbcdbbb --- /dev/null +++ b/tests/LICENSE.extra @@ -0,0 +1,5 @@ +Copyright (c) 2020 The authors of this software + +A license for an extra feature to a package. +For example, you might have most of the package with a notice license, +but an additional feature is added under a restricted license. diff --git a/tests/bar.cc b/tests/bar.cc new file mode 100644 index 0000000..1cd1214 --- /dev/null +++ b/tests/bar.cc @@ -0,0 +1,19 @@ +// 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. + + + +int bar() { + return 42; +} diff --git a/tests/hello.cc b/tests/hello.cc new file mode 100644 index 0000000..8833791 --- /dev/null +++ b/tests/hello.cc @@ -0,0 +1,21 @@ +// 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 foo." << std::endl; + return 0; +} diff --git a/tests/hello_cc_copyrights.golden b/tests/hello_cc_copyrights.golden new file mode 100755 index 0000000..8dc3985 --- /dev/null +++ b/tests/hello_cc_copyrights.golden @@ -0,0 +1 @@ +package(A test case package), copyright(Copyright © 2019 Uncle Toasty) diff --git a/tests/hello_java_copyrights.golden b/tests/hello_java_copyrights.golden new file mode 100755 index 0000000..9df37e5 --- /dev/null +++ b/tests/hello_java_copyrights.golden @@ -0,0 +1,2 @@ +package(A test case package), copyright(Copyright © 2019 Uncle Toasty) +package(A test case package), copyright() diff --git a/tools/BUILD b/tools/BUILD new file mode 100644 index 0000000..d3a5658 --- /dev/null +++ b/tools/BUILD @@ -0,0 +1,10 @@ +# License declaration and compliance checking rules. + +py_binary( + name = "checker_demo", + srcs = ["checker_demo.py"], + python_version = "PY3", + visibility = ["//visibility:public"], +) + +exports_files(["diff_test.sh"]) diff --git a/tools/checker_demo.py b/tools/checker_demo.py new file mode 100644 index 0000000..0b57cee --- /dev/null +++ b/tools/checker_demo.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Proof of concept license checker. + +This is only a demonstration. It will be replaced with other tools. +""" + +import argparse +import codecs +import json + +# Conditions allowed for all applications +_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumberd']) + + +def _get_licenses(licenses_info): + with codecs.open(licenses_info, encoding='utf-8') as licenses_file: + # TODO(aiuto): Bazel is not parsing the BUILD file as utf-8, so it is + # double encoding characters. We should use the line below, but we have + # to hack up a double utf-8 decode. + # return json.loads(licenses_file.read()) + b = bytearray([ord(c) for c in licenses_file.read()]) + return json.loads(b.decode('utf-8')) + + +def _do_report(out, licenses): + """Produce a report showing the set of licenses being used. + + Args: + out: file object to write to + licenses: list of LicenseInfo objects + + 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']) + + +def _check_conditions(out, licenses, allowed_conditions): + """Check that the application does not use any disallowed licenses. + + Args: + out: file object to write to + licenses: list of LicenseInfo objects + allowed_conditions: list of allowed condition names + + Returns: + 0 for no licenses from outside allowed_conditions. + """ + err = 0 + for lic in licenses: # using strange name lic because license is built-in + rule = lic['rule'] + for kind in lic['license_kinds']: + disallowed = [] + for condition in kind['conditions']: + if condition not in allowed_conditions: + disallowed.append(condition) + if disallowed: + out.write('ERROR: %s\n' % rule) + out.write(' kind: %s\n' % kind['target']) + out.write(' conditions: %s\n' % kind['conditions']) + out.write(' disallowed condition: %s\n' % ','.join(disallowed)) + err += 1 + return err + + +def _do_copyright_notices(out, licenses): + for l in licenses: + # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. + out.write('package(%s), copyright(%s)\n' % (l.get('package_name') or '<unknown>', + l['copyright_notice'])) + + +def _do_licenses(out, licenses): + for lic in licenses: + path = lic['license_text'] + with codecs.open(path, encoding='utf-8') as license_file: + out.write('= %s\n' % path) + out.write(license_file.read()) + + +def main(): + parser = argparse.ArgumentParser( + description='Demonstraton license compliance checker') + + parser.add_argument('--licenses_info', + help='path to JSON file containing all license data') + parser.add_argument('--report', default='report', help='Summary report') + parser.add_argument('--copyright_notices', + help='output file of all copyright notices') + parser.add_argument('--license_texts', help='output file of all license files') + parser.add_argument('--check_conditions', action='store_true', + help='check that the dep only includes allowed license conditions') + args = parser.parse_args() + + licenses = _get_licenses(args.licenses_info) + err = 0 + with codecs.open(args.report, mode='w', encoding='utf-8') as rpt: + _do_report(rpt, licenses) + if args.check_conditions: + # TODO(aiuto): Read conditions from a file of allowed conditions for + # a specified application deployment environment. + err = _check_conditions(rpt, licenses, _ALWAYS_ALLOWED_CONDITIONS) + if args.copyright_notices: + with codecs.open( + args.copyright_notices, mode='w', encoding='utf-8') as out: + _do_copyright_notices(out, licenses) + if args.license_texts: + with codecs.open(args.license_texts, mode='w', encoding='utf-8') as out: + _do_licenses(out, licenses) + return err + + +if __name__ == '__main__': + main() diff --git a/tools/diff_test.sh b/tools/diff_test.sh new file mode 100755 index 0000000..ee06790 --- /dev/null +++ b/tools/diff_test.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# 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. + +# Diff two files and PASS if they are equal, FAIL otherwise. +# Usage: +# diff_test.sh path_to_expected path_to_got + +# Find input files +declare -r EXPECTED="$1" +declare -r GOT="$2" + +diff_out=$(mktemp /tmp/diff_test.XXXXXXXXX) +diff -cB "$EXPECTED" "$GOT" >"$diff_out" +err=0 +if [[ -s "$diff_out" ]] ; then + cat "$diff_out" + err=1 + echo 'To update:' + echo ' cp bazel-bin/'"$GOT" "$EXPECTED" + echo FAIL +else + echo PASS +fi +exit $err diff --git a/tools/test_helpers.bzl b/tools/test_helpers.bzl new file mode 100644 index 0000000..30f1980 --- /dev/null +++ b/tools/test_helpers.bzl @@ -0,0 +1,40 @@ +# 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. + +"""Compare rule output to a golden version.""" + +def golden_test( + name, + golden, + subject): + """Check that output from a rule matches the expected output. + + Args: + name: test name + golden: expected content of subect + subject: build target + """ + native.sh_test( + name = name, + size = "medium", + srcs = ["@rules_license//tools:diff_test.sh"], + args = [ + "$(location %s)" % golden, + "$(location %s)" % subject, + ], + data = [ + subject, + golden, + ], + ) |