61 lines
3.9 KiB
Python
Executable File
61 lines
3.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Preserve complete notices for the actual locked runtime/build crate graph."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
|
|
REVIEWED = {'GPL-3.0-only', 'MIT', 'MIT OR Apache-2.0', 'Apache-2.0 OR MIT',
|
|
'MIT/Apache-2.0', 'Unlicense OR MIT', 'Unlicense/MIT', 'MPL-2.0',
|
|
'Apache-2.0', 'Apache-2.0 OR BSL-1.0',
|
|
'Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT',
|
|
'BSD-2-Clause OR Apache-2.0 OR MIT',
|
|
'Zlib OR Apache-2.0 OR MIT', 'MIT OR Apache-2.0 OR Zlib',
|
|
'(MIT OR Apache-2.0) AND Unicode-3.0'}
|
|
NOTICE_NAME = re.compile(r'^(licen[cs]e|copying|notice|unlicense|copyright)([-.]|$)', re.I)
|
|
p = argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument('destination', type=Path)
|
|
p.add_argument('--revision', required=True)
|
|
a = p.parse_args()
|
|
root = Path(__file__).resolve().parent.parent
|
|
host = next(x[6:] for x in subprocess.check_output(['rustc', '-vV'], text=True).splitlines() if x.startswith('host: '))
|
|
metadata = json.loads(subprocess.check_output(['cargo', 'metadata', '--locked', '--format-version', '1', '--filter-platform', host], cwd=root, text=True))
|
|
# Cargo metadata includes dev-unified/optional edges; use Cargo's actual normal
|
|
# and build tree to select the shipped graph instead of guessing feature edges.
|
|
tree = subprocess.check_output(['cargo', 'tree', '--locked', '-p', 'sized', '--target', host, '--edges', 'normal,build', '--prefix', 'none', '--format', '{p}'], cwd=root, text=True)
|
|
selected = {tuple(re.match(r'^(\S+) v(\S+)', line).groups()) for line in tree.splitlines()}
|
|
packages = sorted([x for x in metadata['packages'] if (x['name'], x['version']) in selected], key=lambda x: (x['name'], x['version']))
|
|
assert len(packages) == len(selected), 'Ambiguous or missing package identity'
|
|
texts, inventory = {}, []
|
|
for package in packages:
|
|
if package['license'] not in REVIEWED:
|
|
raise ValueError(f"Unreviewed licence: {package['name']} {package['license']}")
|
|
directory = Path(package['manifest_path']).parent
|
|
files = sorted(f for f in directory.iterdir() if f.is_file() and NOTICE_NAME.match(f.name))
|
|
if not files:
|
|
raise ValueError(f"Missing licence text: {package['name']}")
|
|
notices = []
|
|
for f in files:
|
|
data = f.read_bytes(); text = data.decode('utf-8'); assert text.strip()
|
|
digest = hashlib.sha256(data).hexdigest()
|
|
texts.setdefault(digest, {'labels': [], 'text': text})['labels'].append(f"{package['name']} {package['version']} / {f.name}")
|
|
notices.append({'file': f.name, 'sha256': digest})
|
|
inventory.append({'name': package['name'], 'version': package['version'], 'license': package['license'], 'notices': notices})
|
|
header = ['Sized - Licences and credits', '', f'Source revision: {a.revision}',
|
|
f'Target: {host}', '', 'Sized and its core are GPL-3.0-only, without warranty.',
|
|
'Matching source, including the pinned core and dependency sources, is available at:',
|
|
'https://gamertan.com/projects/sized/ and the matching Gitea release.',
|
|
'The colored 2.2.0 source remains under MPL-2.0 and is additionally distributed',
|
|
'under GPL-3.0-only as part of this Larger Work under MPL section 3.3.',
|
|
'All original notices are preserved. See docs/RELEASE-LICENSING.md in the source.', '',
|
|
'Runtime and build dependencies (development-only dependencies are excluded):']
|
|
header += [f" {x['name']} {x['version']} - {x['license']}" for x in inventory]
|
|
for x in texts.values():
|
|
header += ['', '='*72, '\n'.join(x['labels']), '='*72, x['text']]
|
|
a.destination.mkdir(parents=True, exist_ok=True)
|
|
(a.destination/'LICENCES.txt').write_text('\n'.join(header)+'\n')
|
|
(a.destination/'DEPENDENCIES.json').write_text(json.dumps({'revision': a.revision, 'target': host, 'packages': inventory}, indent=2)+'\n')
|
|
print(f'Packaged complete notices for {len(inventory)} runtime/build crates ({host}).')
|