Skip to content

Commit 818dc22

Browse files
Add support for dependencies.md like output (#332)
1 parent cbdf979 commit 818dc22

File tree

8 files changed

+451
-16
lines changed

8 files changed

+451
-16
lines changed

.github/workflows/report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
run: |
5454
echo -e "# Summary\n" >> $GITHUB_STEP_SUMMARY
5555
poetry run nox -s project:report -- -- --format markdown >> $GITHUB_STEP_SUMMARY
56+
poetry run nox -s dependency:licenses >> $GITHUB_STEP_SUMMARY
5657
echo -e "\n\n# Coverage\n" >> $GITHUB_STEP_SUMMARY
5758
poetry run coverage report -- --format markdown >> $GITHUB_STEP_SUMMARY
5859
poetry run tbx lint pretty-print >> $GITHUB_STEP_SUMMARY

doc/changes/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## ✨ Added
44

55
* added tbx task for markdown formating of .lint.json
6+
* Added a Nox task for dependencies packages and their licenses with Markdown output
67

78
## 🐞 Fixed
89
* Fixed an issue in the CI workflow that caused it to be executed twice on the initial push of a PR if the PR branch was on the repo itself.
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import tempfile
5+
from collections import defaultdict
6+
from dataclasses import dataclass
7+
from inspect import cleandoc
8+
from json import loads
9+
from pathlib import Path
10+
11+
import nox
12+
import tomlkit
13+
from nox import Session
14+
15+
16+
@dataclass(frozen=True)
17+
class Package:
18+
name: str
19+
package_link: str
20+
version: str
21+
license: str
22+
license_link: str
23+
24+
25+
def _dependencies(toml_str: str) -> dict[str, list]:
26+
toml = tomlkit.loads(toml_str)
27+
poetry = toml.get("tool", {}).get("poetry", {})
28+
dependencies: dict[str, list] = {}
29+
30+
packages = poetry.get("dependencies", {})
31+
if packages:
32+
dependencies["project"] = []
33+
for package in packages:
34+
dependencies["project"].append(package)
35+
36+
packages = poetry.get("dev", {}).get("dependencies", {})
37+
if packages:
38+
dependencies["dev"] = []
39+
for package in packages:
40+
dependencies["dev"].append(package)
41+
42+
groups = poetry.get("group", {})
43+
for group in groups:
44+
packages = groups.get(group, {}).get("dependencies")
45+
if packages and not dependencies.get(group, {}):
46+
dependencies[group] = []
47+
for package in packages:
48+
dependencies[group].append(package)
49+
return dependencies
50+
51+
52+
def _normalize(_license: str) -> str:
53+
def is_multi_license(l):
54+
return ";" in l
55+
56+
def select_most_restrictive(licenses: list) -> str:
57+
_max = 0
58+
lic = "Unknown"
59+
_mapping = {
60+
"Unknown": -1,
61+
"Unlicensed": 0,
62+
"BSD": 1,
63+
"MIT": 2,
64+
"MPLv2": 3,
65+
"LGPLv2": 4,
66+
"GPLv2": 5,
67+
"GPLv3": 6,
68+
}
69+
for l in licenses:
70+
if l in _mapping:
71+
if _mapping[l] > _mapping[lic]:
72+
lic = l
73+
else:
74+
return "<br>".join(licenses)
75+
return lic
76+
77+
mapping = {
78+
"BSD License": "BSD",
79+
"MIT License": "MIT",
80+
"The Unlicensed (Unlicensed)": "Unlicensed",
81+
"Mozilla Public License 2.0 (MPL 2.0)": "MPLv2",
82+
"GNU General Public License (GPL)": "GPL",
83+
"GNU Lesser General Public License v2 (LGPLv2)": "LGPLv2",
84+
"GNU General Public License v2 (GPLv2)": "GPLv2",
85+
"GNU General Public License v2 or later (GPLv2+)": "GPLv2+",
86+
"GNU General Public License v3 (GPLv3)": "GPLv3",
87+
"Apache Software License": "Apache",
88+
}
89+
90+
if is_multi_license(_license):
91+
items = []
92+
for item in _license.split(";"):
93+
item = str(item).strip()
94+
if item in mapping:
95+
items.append(mapping[item])
96+
else:
97+
items.append(item)
98+
return select_most_restrictive(items)
99+
100+
if _license not in mapping:
101+
return _license
102+
103+
return mapping[_license]
104+
105+
106+
def _packages_from_json(json: str) -> list[Package]:
107+
packages = loads(json)
108+
packages_list = []
109+
mapping = {
110+
"GPLv1": "https://www.gnu.org/licenses/old-licenses/gpl-1.0.html",
111+
"GPLv2": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
112+
"LGPLv2": "https://www.gnu.org/licenses/old-licenses/lgpl-2.0.html",
113+
"GPLv3": "https://www.gnu.org/licenses/gpl-3.0.html",
114+
"LGPLv3": "https://www.gnu.org/licenses/lgpl-3.0.html",
115+
"Apache": "https://www.apache.org/licenses/LICENSE-2.0",
116+
"MIT": "https://mit-license.org/",
117+
"BSD": "https://opensource.org/license/bsd-3-clause",
118+
}
119+
for package in packages:
120+
package_license = _normalize(package["License"])
121+
packages_list.append(
122+
Package(
123+
name=package["Name"],
124+
package_link="" if package["URL"] == "UNKNOWN" else package["URL"],
125+
version=package["Version"],
126+
license=package_license,
127+
license_link=(
128+
"" if package_license not in mapping else mapping[package_license]
129+
),
130+
)
131+
)
132+
return packages_list
133+
134+
135+
def _licenses() -> list[Package]:
136+
with tempfile.NamedTemporaryFile() as file:
137+
subprocess.run(
138+
[
139+
"poetry",
140+
"run",
141+
"pip-licenses",
142+
"--format=json",
143+
"--output-file=" + file.name,
144+
"--with-system",
145+
"--with-urls",
146+
],
147+
capture_output=True,
148+
)
149+
result = _packages_from_json(file.read().decode())
150+
return result
151+
152+
153+
def _packages_to_markdown(
154+
dependencies: dict[str, list], packages: list[Package]
155+
) -> str:
156+
def heading():
157+
text = "# Dependecies\n"
158+
return text
159+
160+
def dependency(group: str, group_packages: list, packages: list[Package]) -> str:
161+
def _header(_group: str):
162+
_group = "".join([word.capitalize() for word in _group.strip().split()])
163+
text = f"## {_group} Dependencies\n"
164+
text += "|Package|version|Licence|\n"
165+
text += "|---|---|---|\n"
166+
return text
167+
168+
def _rows(_group_packages: list, _packages: list[Package]) -> str:
169+
def _normalize_package_name(name: str) -> str:
170+
_name = name.lower()
171+
while "_" in _name:
172+
_name = _name.replace("_", "-")
173+
return _name
174+
175+
text = ""
176+
for package in _group_packages:
177+
consistent = filter(
178+
lambda elem: (_normalize_package_name(elem.name) == package),
179+
_packages,
180+
)
181+
for content in consistent:
182+
if content.package_link:
183+
text += f"|[{content.name}]({content.package_link})"
184+
else:
185+
text += f"|{content.name}"
186+
text += f"|{content.version}"
187+
if content.license_link:
188+
text += f"|[{content.license}]({content.license_link})|\n"
189+
else:
190+
text += f"|{content.license}|\n"
191+
text += "\n"
192+
return text
193+
194+
_template = cleandoc(
195+
"""
196+
{header}{rows}
197+
"""
198+
)
199+
return _template.format(
200+
header=_header(group), rows=_rows(group_packages, packages)
201+
)
202+
203+
template = cleandoc(
204+
"""
205+
{heading}{rows}
206+
"""
207+
)
208+
209+
rows = ""
210+
for group in dependencies:
211+
rows += dependency(group, dependencies[group], packages)
212+
return template.format(heading=heading(), rows=rows)
213+
214+
215+
@nox.session(name="dependency:licenses", python=False)
216+
def dependency_licenses(session: Session) -> None:
217+
"""returns the packages and their licenses"""
218+
toml = Path("pyproject.toml")
219+
dependencies = _dependencies(toml.read_text())
220+
package_infos = _licenses()
221+
print(_packages_to_markdown(dependencies=dependencies, packages=package_infos))

exasol/toolbox/nox/tasks.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,10 @@ def check(session: Session) -> None:
7878
from exasol.toolbox.nox._artifacts import (
7979
check_artifacts
8080
)
81+
82+
from exasol.toolbox.nox._dependencies import (
83+
dependency_licenses,
84+
)
85+
8186
# isort: on
8287
# fmt: on

exasol/toolbox/templates/github/workflows/report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
run: |
5454
echo -e "# Summary\n" >> $GITHUB_STEP_SUMMARY
5555
poetry run nox -s project:report -- -- --format markdown >> $GITHUB_STEP_SUMMARY
56+
poetry run nox -s dependency:licenses >> $GITHUB_STEP_SUMMARY
5657
echo -e "\n\n# Coverage\n" >> $GITHUB_STEP_SUMMARY
5758
poetry run coverage report -- --format markdown >> $GITHUB_STEP_SUMMARY
5859
poetry run tbx security pretty-print >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)