Get test_licenses using pkg_info ala liccheck

Fixes #2
This commit is contained in:
Reid 'arrdem' McKenzie 2021-09-19 17:59:18 -06:00
parent 13d7f07b03
commit 223fd5688b
6 changed files with 48 additions and 63 deletions

View file

@ -40,7 +40,7 @@ bazel_skylib_workspace()
git_repository( git_repository(
name = "rules_python", name = "rules_python",
remote = "https://github.com/bazelbuild/rules_python.git", remote = "https://github.com/bazelbuild/rules_python.git",
tag = "0.3.0", tag = "0.4.0",
# commit = "...", # commit = "...",
) )
@ -48,14 +48,20 @@ register_toolchains("//tools/python:python3_toolchain")
# pip package pinnings need to be initialized. # pip package pinnings need to be initialized.
# this generates a bunch of bzl rules so that each pip dep is a bzl target # this generates a bunch of bzl rules so that each pip dep is a bzl target
load("@rules_python//python:pip.bzl", "pip_install") load("@rules_python//python:pip.bzl", "pip_parse")
pip_install( pip_parse(
name = "arrdem_source_pypi", name = "arrdem_source_pypi",
requirements = "//tools/python:requirements.txt", requirements_lock = "//tools/python:requirements.txt",
python_interpreter = "/usr/bin/python3.9", python_interpreter = "/usr/bin/python3.9",
) )
# Load the starlark macro which will define your dependencies.
load("@arrdem_source_pypi//:requirements.bzl", "install_deps")
# Call it to define repos for your requirements.
install_deps()
# git_repository( # git_repository(
# name = "rules_zapp", # name = "rules_zapp",
# remote = "https://github.com/arrdem/rules_zapp.git", # remote = "https://github.com/arrdem/rules_zapp.git",

View file

@ -3,7 +3,7 @@
import argparse import argparse
import sys import sys
from black import patched_main, nullcontext from black import nullcontext, patched_main
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()

View file

@ -1,7 +1,9 @@
load("@rules_python//python:defs.bzl", load("@rules_python//python:defs.bzl",
"py_runtime_pair" "py_runtime_pair",
) )
load("@arrdem_source_pypi//:requirements.bzl", "all_requirements")
package(default_visibility = ["//visibility:public"]) package(default_visibility = ["//visibility:public"])
licenses(["notice"]) licenses(["notice"])
@ -40,8 +42,5 @@ py_pytest(
data = [ data = [
"requirements.txt", "requirements.txt",
], ],
deps = [ deps = all_requirements,
py_requirement("requests"),
py_requirement("requirements-parser"),
]
) )

View file

@ -14,6 +14,9 @@ load("@rules_zapp//zapp:zapp.bzl",
"zapp_binary", "zapp_binary",
) )
load("@bazel_skylib//lib:sets.bzl", "sets")
def py_requirement(*args, **kwargs): def py_requirement(*args, **kwargs):
"""A re-export of requirement()""" """A re-export of requirement()"""
return _py_requirement(*args, **kwargs) return _py_requirement(*args, **kwargs)
@ -39,11 +42,11 @@ def py_pytest(name, srcs, deps, main=None, python_version=None, args=None, **kwa
f = "//tools/python:bzl_pytest_shim.py" f = "//tools/python:bzl_pytest_shim.py"
deps = [ deps = sets.to_list(sets.make([
py_requirement("pytest"), py_requirement("pytest"),
py_requirement("jedi"), py_requirement("jedi"),
py_requirement("pytest-pudb"), py_requirement("pytest-pudb"),
] + deps ] + deps))
srcs = [f] + srcs srcs = [f] + srcs
@ -60,12 +63,14 @@ def py_pytest(name, srcs, deps, main=None, python_version=None, args=None, **kwa
# FIXME (arrdem 2020-09-27): # FIXME (arrdem 2020-09-27):
# This really needs to be a py_image_test. # This really needs to be a py_image_test.
# Not clear how to achieve that. # Not clear how to achieve that.
# py_image( # zapp_binary(
# name = name + ".containerized", # name = name + ".hermetic",
# main = f, # main = f,
# args = args, # args = args,
# srcs = srcs, # srcs = srcs,
# deps = deps, # deps = deps,
# test = True,
# zip_safe = False,
# **kwargs, # **kwargs,
# ) # )

View file

@ -45,7 +45,7 @@ packaging==20.9
parso==0.8.2 parso==0.8.2
pathspec==0.9.0 pathspec==0.9.0
pep517==0.11.0 pep517==0.11.0
piexif==1.1.3 pip==20.3.3
pip-tools==6.2.0 pip-tools==6.2.0
platformdirs==2.3.0 platformdirs==2.3.0
pluggy==0.13.1 pluggy==0.13.1
@ -73,6 +73,7 @@ regex==2021.8.28
requests==2.25.1 requests==2.25.1
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
requirements-parser==0.2.0 requirements-parser==0.2.0
setuptools==51.0.0
six==1.15.0 six==1.15.0
snowballstemmer==2.1.0 snowballstemmer==2.1.0
sortedcontainers==2.3.0 sortedcontainers==2.3.0
@ -99,6 +100,7 @@ urwid==2.1.2
wcwidth==0.2.5 wcwidth==0.2.5
webencodings==0.5.1 webencodings==0.5.1
Werkzeug==2.0.1 Werkzeug==2.0.1
wheel==0.36.2
yamllint==1.26.1 yamllint==1.26.1
yarl==1.6.3 yarl==1.6.3
yaspin==1.5.0 yaspin==1.5.0

View file

@ -4,10 +4,8 @@ Validate 3rdparty library licenses as approved.
import re import re
from pkg_resources import DistInfoDistribution, working_set
import pytest import pytest
import requests
import requirements
from requirements.requirement import Requirement
# Licenses approved as representing non-copyleft and not precluding commercial usage. # Licenses approved as representing non-copyleft and not precluding commercial usage.
@ -63,14 +61,10 @@ APPROVED_PACKAGES = [
] ]
with open("tools/python/requirements.txt") as fd:
PACKAGES = list(requirements.parse(fd))
def bash_license(ln): def bash_license(ln):
while True: while True:
lnn = re.sub( lnn = re.sub(
r"[(),]|( version)|( license)|( ?v(?=\d))|([ -]clause)", "", ln.lower() r"[(),]|( version)|( license)|( ?v(?=\d))|([ -]clause)|(or later)", "", ln.lower()
) )
if ln != lnn: if ln != lnn:
ln = lnn ln = lnn
@ -98,59 +92,38 @@ def test_bash_license(a, b):
assert bash_license(a) == b assert bash_license(a) == b
def licenses(package: Requirement): def licenses(dist: DistInfoDistribution):
"""Get package metadata (the licenses list) from PyPi. """Get dist metadata (the licenses list) from PyPi.
pip and other tools use the local package metadata to introspect licenses which requires that pip and other tools use the local dist metadata to introspect licenses which requires that
packages be installed. Going to PyPi isn't strictly reproducible both because the PyPi database packages be installed. Going to PyPi isn't strictly reproducible both because the PyPi database
could be updated and we could see network failures but there really isn't a good way to solve could be updated and we could see network failures but there really isn't a good way to solve
this problem. this problem.
""" """
lics = [] lics = []
version = next((v for op, v in package.specs if op == "=="), None) name = dist.project_name
print(package.name, version) version = dist.version
print(name, version, type(dist))
# If we don't have a version (eg. forked git dep) assume we've got the same license constraints meta = dist.get_metadata(dist.PKG_INFO).split("\n")
# as the latest upstream release. After all we can't re-license stuff. classifiers = [l.replace("Classifier: ", "", 1) for l in meta if l.startswith("Classifier: ")]
if not version: license = bash_license(next((l for l in meta if l.startswith("License:")), "License: UNKNOWN").replace("License: ", "", 1))
blob = requests.get( lics.extend(l for l in classifiers if l.startswith("License ::"))
f"https://pypi.org/pypi/{package.name}/json",
headers={"Accept": "application/json"},
).json()
if ln := bash_license(blob.get("license")):
lics.append(ln)
else:
try:
version = list(blob.get("releases", {}).keys())[-1]
except IndexError:
pass
# If we have a version, try to pull that release's metadata since it may have more/better. if not lics:
if version: lics.append(license)
blob = requests.get(
f"https://pypi.org/pypi/{package.name}/{version}/json",
headers={"Accept": "application/json"},
).json()
lics.extend(
[
c
for c in blob.get("info", {}).get("classifiers", [])
if c.startswith("License")
]
)
ln = blob.get("info", {}).get("license")
if ln and not lics:
lics.append(bash_license(ln))
return lics return lics
@pytest.mark.parametrize("package", PACKAGES) @pytest.mark.parametrize("dist", (w for w in working_set if w.location.find("arrdem_source_pypi") != -1), ids=lambda dist: dist.project_name)
def test_approved_license(package): def test_approved_license(dist: DistInfoDistribution):
"""Ensure that a given package is either allowed by name or uses an approved license.""" """Ensure that a given package is either allowed by name or uses an approved license."""
_licenses = licenses(package) _licenses = licenses(dist)
assert package.name in APPROVED_PACKAGES or any( print(dist.location)
assert dist.project_name in APPROVED_PACKAGES or any(
lic in APPROVED_LICENSES for lic in _licenses lic in APPROVED_LICENSES for lic in _licenses
), f"{package} was not approved and its license(s) were unknown {_licenses!r}" ), f"{dist.project_name} ({dist.location}) was not approved and its license(s) were unknown {_licenses!r}"