aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/BUILD23
-rw-r--r--tools/checker_demo.py32
-rw-r--r--tools/test_helpers.bzl47
-rw-r--r--tools/write_sbom.py117
4 files changed, 203 insertions, 16 deletions
diff --git a/tools/BUILD b/tools/BUILD
index 03bbac1..2b56a34 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -15,10 +15,20 @@
"""License declaration and compliance checking tools."""
package(
- default_applicable_licenses = ["//:license"],
+ default_applicable_licenses = ["//:license", "//:package_info"],
default_visibility = ["//visibility:public"],
)
+licenses(["notice"])
+
+filegroup(
+ name = "standard_package",
+ srcs = glob(["**"]),
+ visibility = ["//distro:__pkg__"],
+)
+
+exports_files(["diff_test.sh"])
+
py_binary(
name = "checker_demo",
srcs = ["checker_demo.py"],
@@ -26,10 +36,9 @@ py_binary(
visibility = ["//visibility:public"],
)
-exports_files(["diff_test.sh"])
-
-filegroup(
- name = "standard_package",
- srcs = glob(["**"]),
- visibility = ["//distro:__pkg__"],
+py_binary(
+ name = "write_sbom",
+ srcs = ["write_sbom.py"],
+ python_version = "PY3",
+ visibility = ["//visibility:public"],
)
diff --git a/tools/checker_demo.py b/tools/checker_demo.py
index 73042aa..6cdf07f 100644
--- a/tools/checker_demo.py
+++ b/tools/checker_demo.py
@@ -23,14 +23,19 @@ import codecs
import json
# Conditions allowed for all applications
-_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumberd'])
+_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumbered'])
-def _get_licenses(licenses_info):
+def _load_license_data(licenses_info):
with codecs.open(licenses_info, encoding='utf-8') as licenses_file:
return json.loads(licenses_file.read())
+def unique_licenses(licenses):
+ for target in licenses:
+ for lic in target.get('licenses') or []:
+ yield lic
+
def _do_report(out, licenses):
"""Produce a report showing the set of licenses being used.
@@ -41,11 +46,14 @@ def _do_report(out, licenses):
Returns:
0 for no restricted licenses.
"""
- for lic in licenses: # using strange name lic because license is built-in
- rule = lic['rule']
- for kind in lic['license_kinds']:
- out.write('= %s\n kind: %s\n' % (rule, kind['target']))
- out.write(' conditions: %s\n' % kind['conditions'])
+
+ for target in unique_licenses(licenses):
+ for lic in target.get('licenses') or []:
+ print("lic:", lic)
+ rule = lic['rule']
+ for kind in lic['license_kinds']:
+ out.write('= %s\n kind: %s\n' % (rule, kind['target']))
+ out.write(' conditions: %s\n' % kind['conditions'])
def _check_conditions(out, licenses, allowed_conditions):
@@ -86,7 +94,7 @@ def _do_copyright_notices(out, licenses):
def _do_licenses(out, licenses):
- for lic in licenses:
+ for lic in unique_licenses(licenses):
path = lic['license_text']
with codecs.open(path, encoding='utf-8') as license_file:
out.write('= %s\n' % path)
@@ -107,7 +115,13 @@ def main():
help='check that the dep only includes allowed license conditions')
args = parser.parse_args()
- licenses = _get_licenses(args.licenses_info)
+ license_data = _load_license_data(args.licenses_info)
+ target = license_data[0] # we assume only one target for the demo
+
+ top_level_target = target['top_level_target']
+ dependencies = target['dependencies']
+ licenses = target['licenses']
+
err = 0
with codecs.open(args.report, mode='w', encoding='utf-8') as rpt:
_do_report(rpt, licenses)
diff --git a/tools/test_helpers.bzl b/tools/test_helpers.bzl
index 30f1980..3ffb9b7 100644
--- a/tools/test_helpers.bzl
+++ b/tools/test_helpers.bzl
@@ -38,3 +38,50 @@ def golden_test(
golden,
],
)
+
+def golden_cmd_test(
+ name,
+ cmd,
+ golden, # Required
+ toolchains = [],
+ tools = None,
+ exec_tools = None,
+ srcs = [], # Optional
+ **kwargs): # Rest
+ """Compares cmd output to golden output, passes if they are identical.
+
+ Args:
+ name: Name of the build rule.
+ cmd: The command to run to generate output.
+ golden: The golden file to be compared.
+ toolchains: List of toolchains needed to run the command, passed to genrule.
+ tools: List of tools needed to run the command, passed to genrule.
+ exec_tools: List of tools needed to run the command, passed to genrule.
+ srcs: List of sources needed as input to the command, passed to genrule.
+ **kwargs: Any additional parameters for the generated golden_test.
+ """
+ actual = name + ".output"
+
+ # There are some cases where tools are provided and exec_tools are provided.
+ # Specifying both in the same genrule, confuses the host vs exec rules,
+ # which prevents python3 from execution.
+ if tools and exec_tools:
+ fail("Only set one: tools or exec_tools. " +
+ "Setting both confuses python execution mode (host vs exec).")
+ native.genrule(
+ name = name + "_output",
+ srcs = srcs,
+ outs = [actual],
+ cmd = cmd + " > '$@'", # Redirect to collect output
+ toolchains = toolchains,
+ tools = tools,
+ exec_tools = exec_tools,
+ testonly = True,
+ )
+
+ golden_test(
+ name = name,
+ subject = actual,
+ golden = golden,
+ **kwargs
+ )
diff --git a/tools/write_sbom.py b/tools/write_sbom.py
new file mode 100644
index 0000000..18286ab
--- /dev/null
+++ b/tools/write_sbom.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Proof of concept license checker.
+
+This is only a demonstration. It will be replaced with other tools.
+"""
+
+import argparse
+import codecs
+import datetime
+import json
+import os
+
+
+TOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom'
+
+def _load_package_data(package_info):
+ with codecs.open(package_info, encoding='utf-8') as inp:
+ return json.loads(inp.read())
+
+def _write_sbom_header(out, package):
+ header = [
+ 'SPDXVersion: SPDX-2.2',
+ 'DataLicense: CC0-1.0',
+ 'SPDXID: SPDXRef-DOCUMENT',
+ 'DocumentName: %s' % package,
+ # TBD
+ # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3
+ 'Creator: Person: %s' % os.getlogin(),
+ 'Creator: Tool: %s' % TOOL,
+ datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'),
+ '',
+ '##### Package: %s' % package,
+ ]
+ out.write('\n'.join(header))
+
+
+
+def _write_sbom(out, packages):
+ """Produce a basic SBOM
+
+ Args:
+ out: file object to write to
+ packages: package metadata. A big blob of JSON.
+ """
+ for p in packages:
+ name = p.get('package_name') or '<unknown>'
+ out.write('\n')
+ out.write('SPDXID: "%s"\n' % name)
+ out.write(' name: "%s"\n' % name)
+ if p.get('package_version'):
+ out.write(' versionInfo: "%s"\n' % p['package_version'])
+ # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one.
+ cn = p.get('copyright_notice')
+ if cn:
+ out.write(' copyrightText: "%s"\n' % cn)
+ kinds = p.get('license_kinds')
+ if kinds:
+ out.write(' licenseDeclared: "%s"\n' %
+ ','.join([k['name'] for k in kinds]))
+ url = p.get('package_url')
+ if url:
+ out.write(' downloadLocation: %s\n' % url)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Demonstraton license compliance checker')
+
+ parser.add_argument('--licenses_info',
+ help='path to JSON file containing all license data')
+ parser.add_argument('--out', default='sbom.out', help='SBOM output')
+ args = parser.parse_args()
+
+ license_data = _load_package_data(args.licenses_info)
+ target = license_data[0] # we assume only one target for the demo
+
+ top_level_target = target['top_level_target']
+ dependencies = target['dependencies']
+ # It's not really packages, but this is close proxy for now
+ licenses = target['licenses']
+ package_infos = target['packages']
+
+ # These are similar dicts, so merge them by package. This is not
+ # strictly true, as different licenese can appear in the same
+ # package, but it is good enough for demonstrating the sbom.
+
+ all = {x['bazel_package']: x for x in licenses}
+ for pi in package_infos:
+ p = all.get(pi['bazel_package'])
+ if p:
+ p.update(pi)
+ else:
+ all[pi['bazel_package']] = pi
+
+ err = 0
+ with codecs.open(args.out, mode='w', encoding='utf-8') as out:
+ _write_sbom_header(out, package=top_level_target)
+ _write_sbom(out, all.values())
+ return err
+
+
+if __name__ == '__main__':
+ main()