Files
docview/tests/source_archives/collect_qt_sdk_supplement.py
2026-09-21 13:41:40 +09:00

118 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Recover two explicitly declared Patent files omitted by the fixed Qt SDK SBOM."""
import argparse
import hashlib
import json
from pathlib import Path
import re
ROOT = Path(__file__).resolve().parents[2]
BASE = ROOT / 'tests/results/source-archives/qt-sdk-notices-final/report.json'
BASE_SHA = 'cea0a2f80ad50b5519fbad94c1dd2eed55f5ffa34e83d1360ce8da6902a845a0'
INVENTORY_SHA = '43dc4aa3cf0d970fc97a9db2555f0025429e9bd31823cb794fd5526496d1a9e0'
SOURCE = ROOT / '.deps/source-archives/qt-6.11.2-regular-files'
PREFIX = 'qtwebengine/src/3rdparty/chromium/'
PATENTS = {
'libwebm': ('source/LICENSE.TXT', 'source/PATENTS.TXT'),
'libwebp': ('src/COPYING', 'src/PATENTS'),
}
ID_ONLY = {
'mini-chromium': ('third_party/crashpad/crashpad/third_party/mini_chromium/README.crashpad', 'Shipped in Chromium'),
'libdrm': ('third_party/libdrm/README.chromium', 'Shipped'),
}
def sha(path):
with path.open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
output = args.output.resolve()
if not output.is_relative_to(ROOT):
parser.error('Output must be inside the workspace')
assert sha(BASE) == BASE_SHA
base = json.loads(BASE.read_text())
inventory = ROOT / 'tests/results/source-archives/qt-inspection/files.jsonl'
assert sha(inventory) == INVENTORY_SHA
names = {PREFIX + 'tools/licenses/sbom.py'}
for name, files in PATENTS.items():
names.update(PREFIX + 'third_party/' + name + '/' + tail for tail in (*files, 'README.chromium'))
names.update(PREFIX + row[0] for row in ID_ONLY.values())
sources = {}
for line in inventory.open():
row = json.loads(line)
if row['path'] in names:
assert row['path'] not in sources
source = SOURCE / row['path']
assert source.is_file() and not source.is_symlink()
assert source.stat().st_size == row['bytes'] and sha(source) == row['sha256']
sources[row['path']] = row
assert sources.keys() == names
assert sources[PREFIX + 'tools/licenses/sbom.py']['sha256'] == base['generatorSources'][PREFIX + 'tools/licenses/sbom.py']
packages = {p['SPDXID']: p for p in base['packages']}
output.mkdir(parents=True, exist_ok=False)
copied, notices, id_only = [], [], []
def copy(name, kind):
source = sources[name]
target = 'sources/' + name.removeprefix(PREFIX)
path = output / target
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes((SOURCE / name).read_bytes())
assert sha(path) == source['sha256']
copied.append(dict(file=target, source=name, sha256=source['sha256'], bytes=source['bytes'], kind=kind))
return target
def fields(name):
return dict(re.findall(r'^([^:\n]+): (.*)$', (SOURCE / name).read_text(), re.M))
for name, (license_file, patent_file) in PATENTS.items():
directory = PREFIX + 'third_party/' + name + '/'
metadata = directory + 'README.chromium'
data = fields(metadata)
assert data['License'] == 'BSD-3-Clause, Patent' and data['Shipped'] == 'yes'
assert data['License File'] == license_file + ', ' + patent_file
package_id = 'SPDXRef-Package-QtWebEngine-Chromium-WebEngine-' + name
package = packages[package_id]
assert package['comment'].splitlines()[0] == 'Location within source: third_party/' + name
embedded = next(n for n in base['notices'] if package_id in n['packageIds'])
assert embedded['source'] == directory + license_file
assert embedded['sha256'] == sources[directory + license_file]['sha256']
assert not any(n['source'] == directory + patent_file for n in base['notices'])
metadata_file = copy(metadata, 'upstream-package-metadata')
notice_file = copy(directory + patent_file, 'declared-patent-notice')
notices.append(dict(packageId=package_id, revision=data['Revision'], licenseDeclared=data['License'],
metadataFile=metadata_file, noticeFile=notice_file,
existingLicenseSha256=embedded['sha256'], reason='Second declared file omitted by SDK generator Patent branch'))
for name, (relative, shipped_field) in ID_ONLY.items():
metadata = PREFIX + relative
data = fields(metadata)
assert data[shipped_field] == 'no'
package_id = 'SPDXRef-Package-QtWebEngine-Chromium-WebEngine-' + name
package = next(p for p in base['packagesWithoutEmbeddedNoticeText'] if p['SPDXID'] == package_id)
assert data['License'] == package['licenseConcluded']
id_only.append(dict(packageId=package_id, metadataFile=copy(metadata, 'upstream-package-metadata'),
declaredShippedField=shipped_field, declaredShippedValue='no',
licenseId=package['licenseConcluded'], noticeAdded=False,
linkedInSdk='unverified; upstream metadata is not a build-graph proof'))
report = dict(schemaVersion=1, success=True, collectorSha256=sha(Path(__file__)),
baseNoticeReportSha256=BASE_SHA, sourceInventorySha256=INVENTORY_SHA,
qtSourceArchiveSha256=base['qtSourceArchiveSha256'], sdkArchiveSha256=base['sdkArchive']['sha256'],
runtimeComparisons=base['testedRuntimeComparisons'], files=sorted(copied, key=lambda x:x['file']),
sourceChecks=sorted(sources.values(), key=lambda x:x['path']), notices=notices, idOnlyPackages=id_only,
sourceSdkExternalPackageReferencesValid=base['externalPackageReferencesValid'],
completeChromiumNotices=False, completeCorrespondingSources=False, publicReleaseApproved=False,
scope='Two upstream-declared Patent files recovered from the fixed Qt 6.11.2 source archive. '
'Four upstream metadata files retained. Original SBOM is unchanged. '
'Does not resolve the SDK build graph, generator skips or external package-ID inconsistency.')
(output / 'report.json').write_text(json.dumps(report, indent=2, sort_keys=True) + '\n')
print(json.dumps({'success': True, 'additionalNotices': len(notices), 'metadataFiles': len(copied)-len(notices)}))
if __name__ == '__main__':
main()