oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1"""Writes native/LICENSE.deepfilternet-crates: the notices of every Rust crate
2linked into libdf (scripts/build-deepfilter.sh), for the Mac app to carry.
3
4Run after build-deepfilter.sh, which leaves the pinned source and toolchain
5behind, and again whenever its commit changes:
6
7 python3 native/scripts/deepfilter-notices.py
8
9The crate set is what the static library links for the Mac, the only app
10that links it: deep_filter with its C API, followed over normal dependencies only. Build
11scripts and proc-macros run at compile time and are not in the binary, so
12they and whatever only they depend on are left out. Where a crate offers a
13choice of licences it is used under MIT, as DeepFilterNet itself is (see
14LICENSE.deepfilternet); otherwise under the most permissive it offers. Each
15crate's own licence file is reproduced, since that is where its copyright
16line is, with any NOTICE file it ships.
17"""
18from pathlib import Path
19import json
20import os
21import re
22import subprocess
23import sys
24
25native = Path(__file__).resolve().parent.parent
26src = native / 'build/deepfilter/src'
27out = native / 'LICENSE.deepfilternet-crates'
28toolchain = re.search(r'^toolchain=(\S+)', (native / 'scripts/build-deepfilter.sh').read_text(), re.M).group(1)
29target = 'aarch64-apple-darwin'
30
31if not (src / 'Cargo.toml').exists():
32 sys.exit('deepfilter-notices: run scripts/build-deepfilter.sh first')
33env = dict(os.environ, PATH=str(Path.home() / '.cargo/bin') + os.pathsep + os.environ['PATH'])
34
35
36def crates(target):
37 """name version -> (source directory, licence expression). cargo tree and
38 not cargo metadata: metadata resolves features across DeepFilterNet's
39 whole workspace, and so names crates (hdf5 among them) that only the
40 other members use; tree resolves them as `cargo build -p deep_filter`
41 does."""
42 listing = subprocess.check_output(
43 ['cargo', f'+{toolchain}', 'tree', '--locked', '-p', 'deep_filter', '--features', 'capi',
44 '-e', 'normal,no-proc-macro', '--target', target, '--prefix', 'none', '--no-dedupe',
45 '-f', '{p}|{l}'], cwd=src, env=env, text=True)
46 registry = sorted((Path.home() / '.cargo/registry/src').glob('*'))
47 found = {}
48 for line in sorted(set(listing.splitlines())):
49 package, licence = line.split('|', 1)
50 m = re.match(r'(\S+) v(\S+)(?: \((.+)\))?', package)
51 name, version, source = m.group(1), m.group(2), m.group(3)
52 if source and not source.startswith(('registry', 'proxy')) and Path(source).is_dir():
53 root = Path(source)
54 else:
55 root = next((r / f'{name}-{version}' for r in registry if (r / f'{name}-{version}').is_dir()), None)
56 if root is None:
57 sys.exit(f'deepfilter-notices: no source for {name} {version}')
58 found[f'{name} {version}'] = {'name': name, 'root': root, 'license': licence}
59 return found
60
61
62MIT = """Permission is hereby granted, free of charge, to any person obtaining a copy
63of this software and associated documentation files (the "Software"), to deal
64in the Software without restriction, including without limitation the rights
65to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
66copies of the Software, and to permit persons to whom the Software is
67furnished to do so, subject to the following conditions:
68
69The above copyright notice and this permission notice shall be included in all
70copies or substantial portions of the Software.
71
72THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
73IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
74FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
75AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
76LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
77OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
78SOFTWARE."""
79
80# Most permissive first. A crate offering MIT is used under MIT.
81PREFERENCE = ['MIT', '0BSD', 'Unlicense', 'CC0-1.0', 'Zlib', 'BSD-2-Clause', 'BSD-3-Clause',
82 'Apache-2.0']
83FILES = {
84 'MIT': ['LICENSE-MIT', 'LICENSE-MIT.md', 'LICENSE.MIT', 'LICENSE', 'LICENSE.md', 'COPYING', 'LICENSE.txt'],
85 'Apache-2.0': ['LICENSE-APACHE', 'LICENSE-APACHE.md', 'LICENSE.Apache-2.0', 'LICENSE', 'LICENSE.md', 'LICENSE.txt'],
86 'Unlicense': ['UNLICENSE', 'LICENSE-UNLICENSE'],
87 'Zlib': ['LICENSE-ZLIB', 'LICENSE-ZLIB.md', 'LICENSE'],
88 'BSD-2-Clause': ['LICENSE-BSD', 'LICENSE'],
89 'BSD-3-Clause': ['LICENSE', 'LICENSE.md', 'COPYING'],
90 'CC0-1.0': ['LICENSE', 'LICENSE-CC0'],
91 '0BSD': ['LICENSE-0BSD', 'LICENSE-0BSD.md', 'LICENSE'],
92}
93
94
95def choose(expression):
96 offered = set(re.findall(r'[A-Za-z0-9.\-]+', expression.replace('/', ' OR '))) - {'OR', 'AND', 'WITH'}
97 required = re.findall(r'AND\s+([A-Za-z0-9.\-]+)', expression)
98 chosen = next((l for l in PREFERENCE if l in offered), None)
99 return chosen, required
100
101
102mac = crates(target)
103
104sections, missing = [], []
105for key in sorted(mac, key=str.lower):
106 package = mac[key]
107 if package['name'] == 'deep_filter':
108 continue # LICENSE.deepfilternet
109 root = package['root']
110 expression = package.get('license') or ''
111 chosen, required = choose(expression)
112 texts = []
113 for licence in [chosen, *required]:
114 for name in FILES.get(licence, []) + ['LICENSE']:
115 path = root / name
116 if path.is_file():
117 texts.append(path.read_text(errors='replace').strip())
118 break
119 else:
120 if licence and licence != 'MIT':
121 missing.append(f'{key} ({licence})')
122 for notice in sorted(root.glob('NOTICE*')):
123 texts.append(notice.read_text(errors='replace').strip())
124 if not texts and chosen == 'MIT':
125 authors = re.search(r'^authors\s*=\s*\[(.*?)\]', (root / 'Cargo.toml').read_text(), re.M | re.S)
126 holders = ', '.join(re.findall(r'"([^"]+)"', authors.group(1))) if authors else f'the {package["name"]} authors'
127 texts.append('(The crate is published under MIT without a licence file; the standard\n'
128 'MIT text follows, naming the authors its Cargo.toml gives.)\n\n'
129 f'Copyright (c) {holders}\n\n' + MIT)
130 elif not texts:
131 texts.append(f'(The crate ships no licence file; it is published under {expression}.)')
132 heading = f'{key} — used under {chosen or expression}' + (f' AND {" AND ".join(required)}' if required else '')
133 sections.append(heading + '\n' + '-' * len(heading) + '\n\n' + '\n\n'.join(dict.fromkeys(texts)))
134
135intro = f'''Rust crates in DeepFilterNet's libDF, as linked into Calls for Mac
136(scripts/build-deepfilter.sh; generated by scripts/deepfilter-notices.py
137from the pinned DeepFilterNet commit with Rust {toolchain}).
138
139Where a crate is offered under a choice of licences it is used under the
140one named in its heading. DeepFilterNet's own notice is LICENSE.deepfilternet.
141'''
142out.write_text(intro + '\n\n' + '\n\n\n'.join(sections) + '\n')
143print(f'Wrote {out} ({len(sections)} crates)')
144if missing:
145 print('No licence file found for: ' + ', '.join(missing), file=sys.stderr)
146