#!/usr/bin/env python3 """Collect license notices for the locked ARM Rust workspace and vendored C code.""" import argparse import hashlib import json from pathlib import Path import subprocess project = Path(__file__).resolve().parents[1] parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('output', type=Path) args = parser.parse_args() metadata = json.loads(subprocess.check_output([ 'cargo', 'metadata', '--locked', '--offline', '--format-version', '1', '--filter-platform', 'aarch64-unknown-linux-musl', ], cwd=project)) resolved = {node['id'] for node in metadata['resolve']['nodes']} notices = ['FDS/OS Rust workspace third-party notices\n', 'Includes build dependencies and vendored native-code license files.\n', 'This inventory does not imply every dependency is linked into every executable.\n'] count = 0 for package in sorted(metadata['packages'], key=lambda p: (p['name'], p['version'])): if not package['source'] or package['id'] not in resolved: continue root = Path(package['manifest_path']).parent files = {path for path in root.rglob('*') if path.is_file() and path.name.upper().startswith(('LICENSE', 'COPYING', 'NOTICE'))} if package['license_file']: files.add(root / package['license_file']) if not files: parser.error(f'No license notice found for {package["name"]} {package["version"]}') notices.append(f'\n=== {package["name"]} {package["version"]} ===\n' f'Declared license: {package["license"] or "see license file"}\n' f'Source: {package["source"]}\n') for path in sorted(files): if path.is_symlink() or not path.resolve().is_relative_to(root.resolve()): parser.error(f'License notice leaves crate source: {package["name"]}') data = path.read_bytes() notices.append(f'\n--- {path.relative_to(root)} ---\n' f'SHA-256: {hashlib.sha256(data).hexdigest()}\n\n') notices.append(data.decode('utf-8') + '\n') count += 1 with args.output.open('x') as stream: stream.write(''.join(notices)) print(f'PASS: license notices for {count} resolved third-party crates: {args.output}')