open-webui/scripts/encryption/build.py
Timothy Jaeryang Baek 7dc91de86c refac
2026-10-15 17:08:00 +04:00

136 lines
5.1 KiB
Python

"""Manual artifact release only; never invoked by the application build."""
import json
import os
import subprocess
import tempfile
from functools import cache
from pathlib import Path
from urllib.parse import urlparse
from urllib.request import urlopen
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / 'crypto/encryption'
OUTPUT = ROOT / 'src/lib/encryption/vendor'
def run(*args, **kwargs):
return subprocess.run(args, cwd=SOURCE, check=True, **kwargs)
run('cargo', 'test', '--locked')
run('cargo', 'build', '--locked', '--release', '--target', 'wasm32-unknown-unknown')
run(
'wasm-bindgen',
'--target',
'web',
'--out-name',
'encryption',
'--out-dir',
str(OUTPUT),
str(SOURCE / 'target/wasm32-unknown-unknown/release/encryption.wasm'),
)
# Use Cargo's actual target build graph, excluding tests and unused optional crates.
metadata = json.loads(run('cargo', 'metadata', '--locked', '--format-version', '1', capture_output=True).stdout)
tree = run(
'cargo',
'tree',
'--locked',
'--target',
'wasm32-unknown-unknown',
'--edges',
'normal,build',
'--prefix',
'none',
'--format',
'{p}',
capture_output=True,
).stdout.decode()
used = {tuple(line.split()[:2]) for line in tree.splitlines()}
metadata['packages'] = [p for p in metadata['packages'] if (p['name'], 'v' + p['version']) in used]
assert {(p['name'], 'v' + p['version']) for p in metadata['packages']} == used
@cache
def download(url):
with urlopen(url, timeout=30) as response:
return response.read().decode('utf-8')
texts = {}
notices = [
'Third-Party Licenses',
'===================',
'',
'Scope: Encryption WebAssembly artifact and its build-time dependencies.',
'Packages are listed alphabetically with references to the full license and notice texts below.',
'Identical texts are included once; package-specific copyright notices are preserved.',
'Source code, including MPL-2.0 dependencies, is available at each Source URL below.',
]
for package in sorted(metadata['packages'], key=lambda p: (p['name'], p['version'])):
if package['source'] is None:
continue # The local wrapper is not a third-party dependency.
license = package['license'] or ''
choices = {
'MIT',
'Apache-2.0',
'BSD-2-Clause',
'BSD-3-Clause',
'ISC',
'Unicode-3.0',
'Zlib',
'CC0-1.0',
'Unlicense',
'MPL-2.0',
}
if not any(x.strip(' ()') in choices for x in license.replace('/', ' OR ').split(' OR ')):
raise RuntimeError(f'Review dependency license: {package["name"]} {license}')
directory = Path(package['manifest_path']).parent
documents = {
path.name: path.read_text()
for path in sorted(directory.iterdir())
if path.is_file() and path.name.upper().startswith(('LICENSE', 'COPYING', 'NOTICE'))
}
if package.get('license_file'):
path = directory / package['license_file']
documents[path.name] = path.read_text()
if not documents:
# Some published crates omit the workspace license; use their exact source revision.
if license == 'MPL-2.0':
url = 'https://www.mozilla.org/media/MPL/2.0/index.txt'
expected = 'Mozilla Public License Version 2.0'
else:
repository = urlparse(package['repository'])
if repository.netloc != 'github.com' or license not in ('MIT', 'Apache-2.0'):
raise RuntimeError(f'Missing license notice: {package["name"]}')
repo = '/'.join(repository.path.strip('/').split('/')[:2])
revision = json.loads((directory / '.cargo_vcs_info.json').read_text())['git']['sha1']
url = f'https://raw.githubusercontent.com/{repo}/{revision}/LICENSE'
expected = 'Permission is hereby granted' if license == 'MIT' else 'Apache License'
documents[url] = download(url)
if expected not in documents[url]:
raise RuntimeError(f'Unexpected license notice: {url}')
references = []
for name, text in documents.items():
text = text.strip()
if not text:
raise RuntimeError(f'Empty license notice: {package["name"]}/{name}')
number = texts.setdefault(text, len(texts) + 1)
references.append(f'{name}: [{number}]')
notices.append(
f'\nPackage: {package["name"]}\nVersion: {package["version"]}\nLicense: {license}\n'
f'Source: https://crates.io/api/v1/crates/{package["name"]}/{package["version"]}/download\n'
+ '\n'.join(references)
)
notices.extend(f'\nLicense and notice text [{number}]\n--------------------------------\n{text}' for text, number in texts.items())
(OUTPUT / 'THIRD_PARTY_LICENSES.txt').write_text('\n'.join(notices) + '\n')
with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as temp:
json.dump(metadata, temp)
temp.flush()
env = dict(
os.environ,
CRYPTO_SOURCE_REVISION=subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT).decode().strip(),
)
subprocess.run(
['node', str(ROOT / 'scripts/encryption/artifact.mjs'), '--write', '--metadata', temp.name], check=True, env=env
)