Skip to content

Commit 505a51d

Browse files
committed
v0.4.0 Release still testing automatic workflow
1 parent eec0aeb commit 505a51d

File tree

6 files changed

+115
-66
lines changed

6 files changed

+115
-66
lines changed

.github/workflows/self-test.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Toolkit Quality Check
2+
on: [push, pull_request]
3+
4+
jobs:
5+
quality:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v4
9+
- uses: astral-sh/setup-uv@v5
10+
11+
- name: Lint with Ruff
12+
run: uvx ruff check .
13+
14+
- name: Run Unit Tests
15+
# ..-tell uv to run pytest, which will automatically find our tests
16+
run: uv run pytest tests/

.github/workflows/toolkit-ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
lint:
2+
runs-on: ubuntu-latest
3+
steps:
4+
- uses: actions/checkout@v4
5+
- uses: astral-sh/setup-uv@v5
6+
- name: Lint with Ruff
7+
run: uvx ruff check .

compliance_tool.py

Lines changed: 45 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,72 @@
22
# requires-python = ">=3.11"
33
# dependencies = [
44
# "scancode-toolkit",
5-
# "requests",
65
# ]
76
# ///
87

98
import json
109
import subprocess
1110
import sys
12-
import os
13-
from datetime import datetime
11+
from pathlib import Path
1412

15-
# A more robust mapping of license text templates
16-
# In a production org, you'd pull these from https://opensource.org/licenses/
17-
LICENSE_MAP = {
18-
"MIT": "https://opensource.org/licenses/MIT",
19-
"Apache-2.0": "https://opensource.org/licenses/Apache-2.0",
20-
"GPL-3.0": "https://opensource.org/licenses/GPL-3.0",
21-
"AGPL-3.0": "https://opensource.org/licenses/AGPL-3.0",
22-
}
23-
24-
def get_full_license_text(license_id, holder):
13+
def evaluate_compliance(detected_licenses):
2514
"""
26-
In a real scenario, you'd have these files in your 'compliance-toolkit' repo.
27-
For this script, we generate a placeholder header.
15+
Pure logic: Takes a list of licenses and returns (recommendation, commercial_allowed, blockers).
2816
"""
29-
year = datetime.now().year
30-
return f"FULL LICENSE TEXT FOR {license_id}\nCopyright (c) {year} {holder}\n\n[Standard {license_id} terms apply...]"
17+
recommendation = "Apache-2.0"
18+
commercial_allowed = True
19+
blockers = []
3120

32-
def run_scan(target):
33-
print("🚀 Scanning dependencies...")
34-
subprocess.run(["scancode", "-l", "--json-pp", "results.json", target], check=True, capture_output=True)
21+
# Normalize to lowercase for easier matching
22+
normalized = [l.lower() for l in detected_licenses]
3523

36-
def analyze():
37-
with open("results.json") as f:
38-
data = json.load(f)
24+
# Check for Viral/Strong Copyleft (AGPL/GPL)
25+
if any("agpl" in l for l in normalized):
26+
recommendation = "AGPL-3.0"
27+
commercial_allowed = False
28+
elif any("gpl" in l for l in normalized):
29+
recommendation = "GPL-3.0"
30+
commercial_allowed = False
31+
32+
# Collect blockers for commercial release
33+
if not commercial_allowed:
34+
blockers = [l for l in detected_licenses if any(v in l.lower() for v in ["gpl", "agpl"])]
3935

40-
detected = set()
41-
hindering_commercial = []
42-
43-
for file in data.get('files', []):
44-
for entry in file.get('license_expressions', []):
45-
lic = entry.lower()
46-
detected.add(entry)
47-
# Check for commercial blockers
48-
if any(viral in lic for viral in ["gpl", "osl", "cpal"]):
49-
hindering_commercial.append(f"{file['path']} ({entry})")
36+
return recommendation, commercial_allowed, blockers
5037

51-
return detected, hindering_commercial
38+
def run_scancode(target_path):
39+
"""Runs the actual shell command to scan the project."""
40+
output_file = "results.json"
41+
subprocess.run(
42+
["scancode", "-l", "--json-pp", output_file, target_path],
43+
check=True, capture_output=True
44+
)
45+
return output_file
5246

5347
def main():
5448
target = sys.argv[1] if len(sys.argv) > 1 else "."
5549
apply_fix = "--apply" in sys.argv
56-
org_name = "Your Organization Name"
5750

58-
run_scan(target)
59-
detected, blockers = analyze()
51+
# 1. Scan
52+
results_path = run_scancode(target)
53+
54+
# 2. Parse
55+
with open(results_path) as f:
56+
data = json.load(f)
57+
58+
detected = set()
59+
for file in data.get('files', []):
60+
for entry in file.get('license_expressions', []):
61+
detected.add(entry)
6062

61-
print("\n" + "="*50)
62-
print("⚖️ OPEN SOURCE COMPLIANCE ANALYSIS")
63-
print("="*50)
63+
# 3. Evaluate
64+
rec, comm_ok, blockers = evaluate_compliance(list(detected))
6465

65-
# 1. Commercial Viability Check
66+
# 4. Output (simplified for brevity)
67+
print(f"Recommended License: {rec}")
68+
print(f"Commercial Release Possible: {comm_ok}")
6669
if blockers:
67-
print("\n❌ COMMERCIAL RELEASE: NOT RECOMMENDED")
68-
print("The following dependencies have 'Viral' licenses that require your code to be Open Source:")
69-
for b in set(blockers[:5]): # Show first 5
70-
print(f" - {b}")
71-
else:
72-
print("\n✅ COMMERCIAL RELEASE: POSSIBLE")
73-
print("All dependencies are permissive. You can release this commercially.")
74-
75-
# 2. Recommendation Logic
76-
recommendation = "Apache-2.0" # Default
77-
if any("agpl" in l.lower() for l in detected):
78-
recommendation = "AGPL-3.0"
79-
elif any("gpl" in l.lower() for l in detected):
80-
recommendation = "GPL-3.0"
81-
82-
print(f"\n💡 RECOMMENDED OPEN SOURCE LICENSE: {recommendation}")
83-
84-
# 3. Apply the license file
85-
if apply_fix:
86-
text = get_full_license_text(recommendation, org_name)
87-
with open("LICENSE", "w") as f:
88-
f.write(text)
89-
print(f"\n💾 SUCCESS: Created LICENSE file ({recommendation})")
90-
else:
91-
print("\n👉 Run with '--apply' to automatically create the LICENSE file.")
70+
print(f"Blockers: {', '.join(blockers)}")
9271

9372
if __name__ == "__main__":
9473
main()

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[tool.ruff]
2+
line-length = 100
3+
target-version = "py311"
4+
5+
[tool.ruff.lint]
6+
select = ["E", "F", "I"] # Error, Pyflakes, Isort (sorting imports)
7+
ignore = []

tests/test_compliance.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from compliance_tool import evaluate_compliance
2+
3+
def test_permissive_licenses():
4+
# Scenario: Project only uses MIT and BSD
5+
rec, comm_ok, blockers = evaluate_compliance(["MIT", "BSD-3-Clause"])
6+
assert rec == "Apache-2.0"
7+
assert comm_ok is True
8+
assert len(blockers) == 0
9+
10+
def test_agpl_blocker():
11+
# Scenario: Project uses an AGPL library
12+
rec, comm_ok, blockers = evaluate_compliance(["MIT", "AGPL-3.0-only"])
13+
assert rec == "AGPL-3.0"
14+
assert comm_ok is False
15+
assert "AGPL-3.0-only" in blockers
16+
17+
def test_gpl_blocker():
18+
# Scenario: Project uses GPL
19+
rec, comm_ok, blockers = evaluate_compliance(["GPL-2.0-or-later"])
20+
assert rec == "GPL-3.0"
21+
assert comm_ok is False

tests/test_recommender.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pytest
2+
from compliance_tool import determine_recommendation
3+
4+
def test_recommends_agpl_when_present():
5+
detected = ["MIT", "AGPL-3.0-or-later"]
6+
rec, commercial_allowed = determine_recommendation(detected)
7+
assert rec == "AGPL-3.0"
8+
assert commercial_allowed is False
9+
10+
def test_recommends_apache_for_permissive():
11+
detected = ["MIT", "BSD-3-Clause"]
12+
rec, commercial_allowed = determine_recommendation(detected)
13+
assert rec == "Apache-2.0"
14+
assert commercial_allowed is True
15+
16+
def test_hindering_licenses_list():
17+
# Test that GPL correctly identifies as a commercial blocker
18+
blockers = [l for l in ["GPL-3.0", "MIT"] if "gpl" in l.lower()]
19+
assert len(blockers) == 1

0 commit comments

Comments
 (0)