initial commit
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inventory a trusted DocView install and its Arch Linux system dependencies.
|
||||
|
||||
This does not download sources, infer a license choice, or certify redistribution.
|
||||
Only use ldd on executables built by this project, never on an input document.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.parse import quote
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXECUTABLES = ('docview', 'docview-pdf-worker', 'docview-archive-worker')
|
||||
MAX_FILES = 10000
|
||||
MAX_COMPONENTS = 256
|
||||
MAX_LICENSE_BYTES = 32 * 1024 * 1024
|
||||
QT_EMBEDDED_NOTICE_METADATA_SHA256 = '8d90e93aa487eddef4b81722a53b89953cbb07b45d02488e8ea0413f300fd723'
|
||||
|
||||
|
||||
def sha256(path):
|
||||
with Path(path).open('rb') as stream:
|
||||
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
||||
|
||||
|
||||
def run(args):
|
||||
environment = {**os.environ, 'LC_ALL': 'C'}
|
||||
for key in ('LD_PRELOAD', 'LD_LIBRARY_PATH', 'LD_AUDIT'):
|
||||
environment.pop(key, None)
|
||||
result = subprocess.run(args, check=True, text=True, capture_output=True, timeout=30,
|
||||
env=environment)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def pacman_fields(text):
|
||||
result, key = {}, None
|
||||
for line in text.splitlines():
|
||||
if line.startswith('%') and line.endswith('%'):
|
||||
key = line[1:-1]
|
||||
result[key] = []
|
||||
elif line and key:
|
||||
result[key].append(line)
|
||||
return result
|
||||
|
||||
|
||||
class PackageDatabase:
|
||||
def __init__(self, root=Path('/var/lib/pacman/local')):
|
||||
self.packages, self.owners = {}, {}
|
||||
for directory in sorted(root.iterdir()):
|
||||
if not directory.is_dir() or not (directory / 'desc').is_file():
|
||||
continue
|
||||
desc = pacman_fields((directory / 'desc').read_text())
|
||||
name = desc['NAME'][0]
|
||||
self.packages[name] = desc
|
||||
for item in pacman_fields((directory / 'files').read_text()).get('FILES', []):
|
||||
if not item.endswith('/'):
|
||||
self.owners['/' + item] = name
|
||||
|
||||
def owner(self, path):
|
||||
value = self.owners.get(str(path)) or self.owners.get(str(path.resolve()))
|
||||
if not value:
|
||||
raise ValueError(f'No installed Arch package owns runtime file: {path}')
|
||||
return value
|
||||
|
||||
|
||||
def ldd_paths(output):
|
||||
paths = set()
|
||||
for line in output.splitlines():
|
||||
if 'not found' in line:
|
||||
raise ValueError('Unresolved ELF dependency: ' + line.strip())
|
||||
value = line.strip()
|
||||
if not value or value.startswith('linux-vdso'):
|
||||
continue
|
||||
match = re.fullmatch(r'(?:\S+ => )?(/.+?) \(0x[0-9a-fA-F]+\)', value)
|
||||
if not match:
|
||||
raise ValueError('Unrecognized ldd dependency: ' + value)
|
||||
paths.add(Path(match[1]))
|
||||
return paths
|
||||
|
||||
|
||||
def elf(path):
|
||||
with path.open('rb') as stream:
|
||||
return stream.read(4) == b'\x7fELF'
|
||||
|
||||
|
||||
def qt_runtime_files(query):
|
||||
"""Finite candidate modules; not a claim that every style/plugin was loaded."""
|
||||
files = {Path(query['QT_INSTALL_LIBEXECS']) / 'QtWebEngineProcess'}
|
||||
qml = Path(query['QT_INSTALL_QML'])
|
||||
for relative in ('QtQml', 'QtQuick/Controls', 'QtQuick/Dialogs', 'QtQuick/Layouts',
|
||||
'QtQuick/Templates', 'QtQuick/Window', 'QtQuick/NativeStyle',
|
||||
'QtQuick/Effects', 'QtWebEngine'):
|
||||
directory = qml / relative
|
||||
if directory.is_dir():
|
||||
files.update(p for p in directory.rglob('*') if p.is_file())
|
||||
files.update(p for p in (qml / 'QtQuick').glob('*') if p.is_file())
|
||||
plugins = Path(query['QT_INSTALL_PLUGINS'])
|
||||
for relative in ('platforms/libqxcb.so', 'platforms/libqwayland-generic.so',
|
||||
'platforms/libqwayland-egl.so'):
|
||||
path = plugins / relative
|
||||
if path.is_file():
|
||||
files.add(path)
|
||||
for filename in ('libqjpeg.so', 'libqgif.so', 'libqico.so', 'libqsvg.so', 'libqwebp.so'):
|
||||
path = plugins / 'imageformats' / filename
|
||||
if path.is_file():
|
||||
files.add(path)
|
||||
for relative in ('xcbglintegrations', 'wayland-graphics-integration-client'):
|
||||
directory = plugins / relative
|
||||
if directory.is_dir():
|
||||
files.update(p for p in directory.rglob('*') if p.is_file())
|
||||
resources = Path(query['QT_INSTALL_DATA']) / 'resources'
|
||||
files.update(p for p in resources.glob('*') if p.is_file() and
|
||||
(p.name.startswith('qtwebengine') or p.name in ('icudtl.dat', 'v8_context_snapshot.bin')))
|
||||
translations = Path(query['QT_INSTALL_TRANSLATIONS'])
|
||||
for name in ('qtbase_ja.qm', 'qtdeclarative_ja.qm'):
|
||||
path = translations / name
|
||||
if not path.is_file():
|
||||
raise ValueError('Required Japanese Qt translation is missing: ' + name)
|
||||
files.add(path)
|
||||
files.update(p for p in (translations / 'qtwebengine_locales').glob('*') if p.is_file())
|
||||
for pattern in ('qtbase_*.qm', 'qtdeclarative_*.qm', 'qtwebengine_*.qm'):
|
||||
files.update(p for p in translations.glob(pattern) if p.is_file())
|
||||
if not files or len(files) > MAX_FILES or not all(p.is_file() for p in files):
|
||||
raise ValueError('Missing or oversized Qt runtime inventory')
|
||||
return files
|
||||
|
||||
|
||||
def license_candidates(name, desc, base=Path('/usr/share/licenses')):
|
||||
result = set()
|
||||
directory = base / name
|
||||
if directory.is_dir():
|
||||
result.update(p for p in directory.rglob('*') if p.is_file())
|
||||
# Include the generic text when Arch uses a shared SPDX license directory.
|
||||
for label in desc.get('LICENSE', []):
|
||||
for token in re.findall(r'[A-Za-z0-9][A-Za-z0-9.+-]*', label):
|
||||
path = base / 'spdx' / (token + '.txt')
|
||||
if path.is_file():
|
||||
result.add(path)
|
||||
return sorted(result)
|
||||
|
||||
|
||||
def license_supplements(name, version, base=ROOT / 'resources/licenses/linux-supplemental'):
|
||||
result = []
|
||||
upstream_version = version.rsplit('-', 1)[0].split(':')[-1]
|
||||
for row in json.loads((base / 'sources.json').read_text()):
|
||||
if row['package'] != name:
|
||||
continue
|
||||
if row['version'] != upstream_version:
|
||||
raise ValueError('Supplemental license version needs review: ' + name)
|
||||
for filename, expected in sorted(row['files'].items()):
|
||||
if Path(filename).name != filename:
|
||||
raise ValueError('Unsafe supplemental license filename')
|
||||
path = base / name / filename
|
||||
if sha256(path) != expected:
|
||||
raise ValueError('Supplemental license digest mismatch: ' + str(path))
|
||||
result.append((path, row))
|
||||
return result
|
||||
|
||||
|
||||
def qt_embedded_notices(qt_version, package_version, system_files,
|
||||
base=ROOT / 'resources/licenses/qt-embedded-notices'):
|
||||
"""Bind the reviewed partial notice set to the exact system DataPack variant."""
|
||||
source = base / 'sources.json'
|
||||
if not source.is_file() or source.stat().st_size > 65536:
|
||||
raise ValueError('Qt embedded notice metadata missing or oversized')
|
||||
metadata_bytes = source.read_bytes()
|
||||
if hashlib.sha256(metadata_bytes).hexdigest() != QT_EMBEDDED_NOTICE_METADATA_SHA256:
|
||||
raise ValueError('Qt embedded notice metadata changed; review required')
|
||||
value = json.loads(metadata_bytes)
|
||||
if (value['schemaVersion'] != 1 or value['qtVersion'] != qt_version or
|
||||
value['packageVersion'] != package_version or value['package'] != 'qt6-webengine' or
|
||||
value['completeChromiumNotices'] is not False or value['runtimeDistribution'] != 'system-not-bundled'):
|
||||
raise ValueError('Qt embedded notice target version or scope needs review')
|
||||
for expected in value['dataPacks']:
|
||||
matches = [row for row in system_files if row['path'] == expected['path']]
|
||||
if (len(matches) != 1 or any(matches[0].get(key) != expected[key] for key in ('package', 'size', 'sha256')) or
|
||||
expected['packageVersion'] != package_version):
|
||||
raise ValueError('Qt embedded notice DataPack inventory mismatch; review required')
|
||||
for row in value['files']:
|
||||
if Path(row['name']).name != row['name']:
|
||||
raise ValueError('Unsafe Qt embedded notice filename')
|
||||
path = base / row['name']
|
||||
if (not path.is_file() or path.stat().st_size != row['size'] or sha256(path) != row['sha256']):
|
||||
raise ValueError('Qt embedded notice text missing or digest/size mismatch')
|
||||
return source, value
|
||||
|
||||
|
||||
def pdfium_supplemental_notices(prefix, locked):
|
||||
relative = Path('share/doc/docview/pdfium/supplemental')
|
||||
source = prefix / relative / 'sources.json'
|
||||
if not source.is_file() or source.stat().st_size > 65536:
|
||||
raise ValueError('PDFium supplemental notice metadata missing or oversized')
|
||||
value = json.loads(source.read_text())
|
||||
if (value.get('schemaVersion') != 1 or value.get('pdfiumVersion') != locked['version'] or
|
||||
value.get('pdfiumUpstreamCommit') != locked['upstreamCommit'] or
|
||||
value.get('pdfiumLibrarySha256') != sha256(prefix / 'lib/libpdfium.so')):
|
||||
raise ValueError('PDFium supplemental notice source or binary needs review')
|
||||
rows = value.get('files')
|
||||
if (not isinstance(rows, list) or len(rows) != 2 or
|
||||
any(not isinstance(r, dict) for r in rows) or {r.get('name') for r in rows} != {
|
||||
'libcxx-LICENSE.txt', 'libcxxabi-LICENSE.txt'}):
|
||||
raise ValueError('Unexpected PDFium supplemental notice set')
|
||||
files = []
|
||||
for row in rows:
|
||||
path = prefix / relative / row['name']
|
||||
if (not path.is_file() or path.stat().st_size > 1024 * 1024 or
|
||||
path.stat().st_size != row['size'] or sha256(path) != row['sha256']):
|
||||
raise ValueError('PDFium supplemental notice digest or size mismatch')
|
||||
files.append({**row, 'path': (relative / row['name']).as_posix()})
|
||||
return {'metadataPath': (relative / 'sources.json').as_posix(), 'metadataSha256': sha256(source),
|
||||
'scope': value['scope'], 'providerRecipeCommit': value['providerRecipeCommit'],
|
||||
'pdfiumLibrarySha256': value['pdfiumLibrarySha256'], 'files': files}
|
||||
|
||||
|
||||
def inventory(prefix, output, qtpaths='/usr/lib/qt6/bin/qtpaths'):
|
||||
prefix, output = prefix.resolve(strict=True), output.resolve()
|
||||
if sys.platform != 'linux' or platform.machine() != 'x86_64':
|
||||
raise ValueError('This inventory targets Arch Linux x86_64 only')
|
||||
os_release = platform.freedesktop_os_release()
|
||||
if os_release.get('ID') != 'arch':
|
||||
raise ValueError('An Arch package database is required; this is not an Ubuntu package')
|
||||
if output.exists() and any(output.iterdir()):
|
||||
raise ValueError('Inventory output directory must be empty')
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
query = dict(line.split(':', 1) for line in run([qtpaths, '--query']).splitlines() if ':' in line)
|
||||
if query.get('QT_VERSION') != '6.11.2':
|
||||
raise ValueError('Expected the verified Qt 6.11.2 runtime')
|
||||
database = PackageDatabase()
|
||||
inputs = [prefix / 'bin' / name for name in EXECUTABLES]
|
||||
before = {str(p.relative_to(prefix)): sha256(p) for p in inputs}
|
||||
bundled = prefix / 'lib/libpdfium.so'
|
||||
expected_pdfium = ROOT / '.deps/pdfium/lib/libpdfium.so'
|
||||
if not bundled.is_file() or sha256(bundled) != sha256(expected_pdfium):
|
||||
raise ValueError('Installed PDFium differs from the pinned local archive')
|
||||
candidates = qt_runtime_files(query)
|
||||
system = {p.resolve(): {'qt-runtime-candidate'} for p in candidates}
|
||||
edges = []
|
||||
for path in inputs + sorted(candidates):
|
||||
if not elf(path):
|
||||
continue
|
||||
label = str(path.relative_to(prefix)) if path.is_relative_to(prefix) else str(path)
|
||||
resolved = []
|
||||
for dependency in sorted(ldd_paths(run(['ldd', str(path)]))):
|
||||
real = dependency.resolve(strict=True)
|
||||
if real.is_relative_to(prefix):
|
||||
if real != bundled:
|
||||
raise ValueError('Unexpected bundled shared library: ' + str(real))
|
||||
resolved.append('bundle:lib/libpdfium.so')
|
||||
else:
|
||||
system.setdefault(real, set()).add('elf-dependency')
|
||||
resolved.append(str(real))
|
||||
edges.append({'elf': label, 'resolved': sorted(resolved)})
|
||||
if len(system) > MAX_FILES:
|
||||
raise ValueError('Runtime inventory exceeds file limit')
|
||||
components, system_files = {}, []
|
||||
# toml++ is compiled into DocView as well as potentially dynamically linked.
|
||||
names = {'tomlplusplus', 'qt6-base', 'qt6-declarative', 'qt6-webengine'}
|
||||
for path, roles in sorted(system.items()):
|
||||
owner = database.owner(path)
|
||||
names.add(owner)
|
||||
system_files.append({'path': str(path), 'package': owner, 'size': path.stat().st_size,
|
||||
'sha256': sha256(path), 'roles': sorted(roles)})
|
||||
if len(names) > MAX_COMPONENTS:
|
||||
raise ValueError('Runtime inventory exceeds package limit')
|
||||
total_license_bytes = 0
|
||||
for name in sorted(names):
|
||||
desc = database.packages[name]
|
||||
version = desc['VERSION'][0]
|
||||
base = desc.get('BASE', [name])[0]
|
||||
licenses = []
|
||||
embedded_notices = None
|
||||
for source in license_candidates(name, desc):
|
||||
if source.stat().st_size > 8 * 1024 * 1024:
|
||||
raise ValueError('License file exceeds limit: ' + str(source))
|
||||
total_license_bytes += source.stat().st_size
|
||||
if total_license_bytes > MAX_LICENSE_BYTES:
|
||||
raise ValueError('License collection exceeds limit')
|
||||
relative = Path('licenses') / name / source.relative_to('/usr/share/licenses')
|
||||
target = output / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, target)
|
||||
licenses.append({'path': relative.as_posix(), 'sourcePath': str(source),
|
||||
'sha256': sha256(target), 'size': target.stat().st_size})
|
||||
for source, provenance in license_supplements(name, version):
|
||||
total_license_bytes += source.stat().st_size
|
||||
if total_license_bytes > MAX_LICENSE_BYTES:
|
||||
raise ValueError('License collection exceeds limit')
|
||||
relative = Path('licenses') / name / 'upstream' / source.name
|
||||
target = output / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, target)
|
||||
licenses.append({'path': relative.as_posix(), 'sourceUrl': provenance['url'],
|
||||
'upstreamVersion': provenance['version'],
|
||||
'downloadSha256': provenance['downloadSha256'],
|
||||
'collectionScope': 'Upstream notice text only; not a complete source collection',
|
||||
'sha256': sha256(target), 'size': target.stat().st_size})
|
||||
if name == 'qt6-webengine':
|
||||
metadata_path, reviewed = qt_embedded_notices(query['QT_VERSION'], version, system_files)
|
||||
folder = Path('licenses') / name / 'embedded-resource-notices'
|
||||
metadata_relative = folder / 'sources.json'
|
||||
(output / folder).mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(metadata_path, output / metadata_relative)
|
||||
if sha256(output / metadata_relative) != QT_EMBEDDED_NOTICE_METADATA_SHA256:
|
||||
raise ValueError('Qt embedded notice metadata changed while copying')
|
||||
total_license_bytes += (output / metadata_relative).stat().st_size
|
||||
for row in reviewed['files']:
|
||||
relative = folder / row['name']
|
||||
target = output / relative
|
||||
shutil.copyfile(metadata_path.parent / row['name'], target)
|
||||
if target.stat().st_size != row['size'] or sha256(target) != row['sha256']:
|
||||
raise ValueError('Qt embedded notice text changed while copying')
|
||||
total_license_bytes += row['size']
|
||||
licenses.append({'path': relative.as_posix(), 'size': row['size'], 'sha256': row['sha256'],
|
||||
'origin': 'reviewed-installed-DataPack-resource-comment',
|
||||
'sourceResources': row['sourceResources'],
|
||||
'collectionScope': reviewed['scope']})
|
||||
if total_license_bytes > MAX_LICENSE_BYTES:
|
||||
raise ValueError('License collection exceeds limit')
|
||||
embedded_notices = {'status': 'collected-reviewed-resource-subset',
|
||||
'metadataPath': metadata_relative.as_posix(),
|
||||
'metadataSha256': QT_EMBEDDED_NOTICE_METADATA_SHA256,
|
||||
'noticeCount': len(reviewed['files']),
|
||||
'sourceResourceCount': sum(len(row['sourceResources']) for row in reviewed['files']),
|
||||
'runtimeDistribution': reviewed['runtimeDistribution'],
|
||||
'completeChromiumNotices': False, 'scope': reviewed['scope']}
|
||||
components[name] = {
|
||||
'version': version, 'architecture': desc.get('ARCH', ['unknown'])[0],
|
||||
'distribution': 'system-not-bundled',
|
||||
'upstreamHome': desc.get('URL', [''])[0],
|
||||
'licenseLabelsFromPackage': desc.get('LICENSE', []),
|
||||
'licenseTexts': licenses,
|
||||
'noticeStatus': 'collected-package-or-upstream-texts-not-a-compliance-verdict' if licenses else 'text-not-found',
|
||||
'source': {'status': 'not-collected', 'packageBase': base, 'packageVersion': version,
|
||||
'recipeRepository': 'https://gitlab.archlinux.org/archlinux/packaging/packages/' + quote(base),
|
||||
'recipeCommit': None,
|
||||
'note': 'This package contains source pointers, not complete corresponding source. Selected exact recipes and patches are recorded separately in the repository source-correspondence evidence.'}}
|
||||
if name == 'tomlplusplus':
|
||||
components[name]['usage'] = ['system runtime library', 'headers compiled into DocView']
|
||||
if embedded_notices:
|
||||
components[name]['embeddedResourceNotices'] = embedded_notices
|
||||
locked = json.loads((ROOT / 'cmake/pdfium.lock.json').read_text())
|
||||
pdfium_supplements = pdfium_supplemental_notices(prefix, locked)
|
||||
pdfium_licenses = []
|
||||
for path in sorted((prefix / 'share/doc/docview/pdfium').rglob('*')):
|
||||
if path.is_file() and path.name != 'sources.json':
|
||||
pdfium_licenses.append({'path': path.relative_to(prefix).as_posix(), 'sha256': sha256(path),
|
||||
'size': path.stat().st_size})
|
||||
if not pdfium_licenses:
|
||||
raise ValueError('PDFium archive license texts missing from install')
|
||||
components['PDFium-independent-worker'] = {
|
||||
'version': locked['version'], 'distribution': 'bundled', 'path': 'lib/libpdfium.so',
|
||||
'sha256': sha256(bundled), 'size': bundled.stat().st_size,
|
||||
'licenseTexts': pdfium_licenses, 'upstreamHome': locked['upstream'],
|
||||
'supplementalNotices': pdfium_supplements,
|
||||
'source': {'status': 'not-collected', 'upstreamCommit': locked['upstreamCommit'],
|
||||
'sourceUrl': locked['upstream'], 'binaryProvider': locked['binaryProvider'],
|
||||
'binaryArchive': locked['linuxX64Archive'], 'archiveSha256': locked['linuxX64Sha256'],
|
||||
'buildOptions': locked['buildOptions'],
|
||||
'note': 'Upstream revision and provider archive are pinned; corresponding source tree and all dependency sources are not included.'}}
|
||||
versions = {}
|
||||
for path in inputs + [bundled]:
|
||||
values = sorted(set(re.findall(r'Name: ((?:GLIBC|GLIBCXX|CXXABI)_[A-Za-z0-9_.]+)',
|
||||
run(['readelf', '--version-info', str(path)]))))
|
||||
versions[path.relative_to(prefix).as_posix()] = values
|
||||
if before != {str(p.relative_to(prefix)): sha256(p) for p in inputs}:
|
||||
raise ValueError('Installed executables changed during inventory')
|
||||
result = {'schemaVersion': 1,
|
||||
'scope': 'Local Arch Linux x86_64 development package; not a clean-OS or redistribution acceptance',
|
||||
'host': {'id': os_release['ID'], 'prettyName': os_release.get('PRETTY_NAME', ''),
|
||||
'architecture': platform.machine(), 'qtVersion': query['QT_VERSION'],
|
||||
'glibcPackage': database.packages['glibc']['VERSION'][0]},
|
||||
'executableSha256': before, 'requiredSymbolVersions': versions,
|
||||
'components': components, 'systemFiles': system_files, 'elfResolution': edges,
|
||||
'systemRuntimeCandidateBytes': sum(row['size'] for row in system_files),
|
||||
'scopeLimits': [
|
||||
'Qt libraries, selected QML/style/plugin candidates, WebEngine helper/resources/locales and ELF closure are system dependencies, not copied runtime binaries.',
|
||||
'Candidate styles/plugins are a bounded inventory, not a trace proving every file was used. Other platform, IME, theme, GPU and font providers may load additional files.',
|
||||
'System fonts and their licenses are not bundled; FontBroker uses locally installed fonts.',
|
||||
'Arch package license labels can list alternatives. This record does not choose a license for DocView or resolve every component condition.',
|
||||
'Qt WebEngine includes Chromium third parties. Seven reviewed notice texts from 13 system DataPack resources are included, but neither these nor the top-level package license are a complete build-specific Chromium attribution collection.',
|
||||
'Complete corresponding dependency sources are not included in this package. Selected source-correspondence evidence is maintained separately; URLs and license texts alone do not establish source fulfillment.',
|
||||
'Ubuntu 24.04, Windows, clean installation and signed distribution remain unverified.']}
|
||||
(output / 'dependency-manifest.json').write_text(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True) + '\n')
|
||||
lines = ['DocView Linux development package: third-party notices', '',
|
||||
'Local Arch Linux x86_64 artifact. Not a complete redistribution approval.',
|
||||
'DocView public license: unspecified. This inventory does not license DocView.',
|
||||
'Only the independent PDFium binary is bundled; the other runtime libraries use the host system.',
|
||||
'License texts below are copied verbatim from installed packages, recorded versioned upstream sources, or reviewed system DataPack resource comments. Package labels may be alternatives.',
|
||||
'Seven notice texts extracted from 13 fixed QtWebEngine DataPack resources are included; the Qt runtime remains system-only and complete Chromium attribution remains uncollected.',
|
||||
'Complete corresponding dependency sources and build materials are NOT included in this package.',
|
||||
'Qt WebEngine Chromium build-specific notices remain to be completed before a release.',
|
||||
'See dependency-manifest.json for exact versions, hashes, source pointers and limitations.', '',
|
||||
'References:', 'https://doc.qt.io/qt-6/qtwebengine-licensing.html',
|
||||
'https://doc.qt.io/qt-6/linux-deployment.html', '']
|
||||
for name, item in sorted(components.items()):
|
||||
lines += [f'{name} {item["version"]} [{item["distribution"]}]',
|
||||
' Upstream: ' + item['upstreamHome'],
|
||||
' Package license labels: ' + '; '.join(item.get('licenseLabelsFromPackage', ['See bundled PDFium notices'])),
|
||||
' Source status: ' + item['source']['status']]
|
||||
lines += [' License text: ' + text['path'] for text in item['licenseTexts']]
|
||||
if not item['licenseTexts']:
|
||||
lines.append(' License text: NOT COLLECTED from the installed package')
|
||||
lines.append('')
|
||||
(output / 'NOTICE.txt').write_text('\n'.join(lines), encoding='utf-8')
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--prefix', type=Path, required=True)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
parser.add_argument('--qtpaths', default='/usr/lib/qt6/bin/qtpaths')
|
||||
args = parser.parse_args()
|
||||
result = inventory(args.prefix, args.output, args.qtpaths)
|
||||
print(f'{len(result["components"])} components; {len(result["systemFiles"])} system runtime candidates; dependency sources not collected')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user