The A16 showed partial System Settings icons while neither the plug-in nor the kext refused anything, so the GPU ran the icon kernels and some produced wrong or empty pixels; the encoding tests cannot see that. build-icon-pixels.py builds and ad-hoc signs SnapMetal's icon-pixels for arm64e and records its size, SHA-256 and CDHash (and the trust row the guest would need if its AMFI refused an ad-hoc executable). run-icon-pixels.py runs it in the guest from the root console shell: pushes it with deploy-guest-plugin.py's checked chunk transfer, checks that it executes, runs the synthetic CoreImage cases as root and the system icons both in-process (root) and through NSWorkspace as the console user (launchctl asuser + sudo -u), fetches every output file three CRC-checked lines per command, optionally renames the icon caches aside and restarts iconservicesd, iconservicesagent, the Dock and System Settings (multi-user), collects the plug-in's per-process and unified-log diagnostics, and compares. Offline it refuses the steps that would act on this Mac's own processes. compare-icon-pixels.py compares the guest's lines with this Mac's within a tolerance and names the first matching explanation per case: unwritten or partial output, readback or repeat differences, black or transparent output, swapped red/blue, an extra or missing sRGB decode, the missing or foreign input of a multi-input kernel (with its Metal texture index), a broken blend band, otherwise the channels and tiles that are off; for icons empty, black, partial, wrong colour or shape. --census-only compares the dispatched kernels and formats of an intercepted plug-in run. icon-pixels-reference-synth.txt is this Mac's synthetic reference (generated inputs only; M3 Max, 26A428, capabilities answered like the deployed plug-in 378ff525). The icons reference summarises Apple's icons and is not committed. test-icon-pixels.py: the reference reproduces exactly on this Mac; the plug-in device (commits intercepted) dispatches the same kernels and formats with 8 CS sampler units and drops the multi-input ones without; 20 injected faults are each diagnosed and a within-tolerance perturbation passes; the whole console procedure runs over the console stand-in with loss and noise and fetches lines identical to a direct run.
79 lines
3.9 KiB
Python
79 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: MIT
|
|
"""Build and ad-hoc sign the icon-pixels guest tool (arm64e), without running it in the guest.
|
|
|
|
The tool (gpu/adreno-kext/plugin/harness/icon-pixels*.m, SnapMetal) runs the CoreImage kernels
|
|
of the System Settings pane icons on the default Metal device and prints checksummed lines per case; see its header.
|
|
Built like the other guest probes (tools/build-adreno-clear-probe.py): xcrun clang -arch arm64e for macOS 27, ad-hoc
|
|
signature, architecture and signature checked. Writes OUTPUT/icon-pixels and OUTPUT/build.json (size, SHA-256, CDHash,
|
|
compiler, command lines, source SHA-256s), and prints the record. OUTPUT must not exist.
|
|
|
|
python3 tools/build-icon-pixels.py --output build/icon-pixels/tool-YYYYMMDD
|
|
|
|
The CDHash is the trust-cache row the guest needs only if its AMFI refuses an ad-hoc arm64e executable
|
|
(tools/run-icon-pixels.py checks that first and stops with this hint).
|
|
"""
|
|
from pathlib import Path
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import subprocess
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
HARNESS = ROOT / 'gpu/adreno-kext/plugin/harness'
|
|
SOURCES = [HARNESS / 'icon-pixels.m', HARNESS / 'icon-pixels-census.m', HARNESS / 'icon-pixels-icons.m',
|
|
HARNESS / 'icon-pixels.h']
|
|
FRAMEWORKS = ['Foundation', 'Metal', 'CoreImage', 'CoreGraphics', 'IOKit', 'AppKit', 'UniformTypeIdentifiers']
|
|
|
|
|
|
def sha(p):
|
|
return hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument('--output', type=Path, required=True)
|
|
a = ap.parse_args()
|
|
out = a.output.resolve()
|
|
out.mkdir(parents=True, exist_ok=False)
|
|
binary = out / 'icon-pixels'
|
|
sources = {str(p.relative_to(ROOT)): sha(p) for p in SOURCES}
|
|
commands = []
|
|
|
|
def run(name, argv):
|
|
commands.append(argv)
|
|
r = subprocess.run(argv, cwd=ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
(out / f'{name}.log').write_text(r.stdout)
|
|
if r.returncode:
|
|
raise SystemExit(f'{name} failed ({r.returncode}): {out / (name + ".log")}')
|
|
return r.stdout
|
|
compiler = run('compiler', ['xcrun', 'clang', '--version']).splitlines()[0]
|
|
argv = ['xcrun', 'clang', '-arch', 'arm64e', '-mmacosx-version-min=27.0', '-fobjc-arc', '-fobjc-arc-exceptions', '-O2',
|
|
'-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter',
|
|
*[str(p.relative_to(ROOT)) for p in SOURCES if p.suffix == '.m'], '-o', str(binary)]
|
|
for f in FRAMEWORKS:
|
|
argv += ['-framework', f]
|
|
# -x drops local symbols: a smaller binary is fewer console chunks to push.
|
|
run('build', argv + ['-lz', '-Wl,-dead_strip', '-Wl,-x'])
|
|
run('sign', ['codesign', '-f', '-s', '-', str(binary)])
|
|
run('verify-signature', ['codesign', '--verify', '--strict', '--verbose=2', str(binary)])
|
|
arch = run('architecture', ['lipo', '-archs', str(binary)]).strip()
|
|
if arch != 'arm64e':
|
|
raise SystemExit(f'architecture {arch}, not arm64e')
|
|
signing = run('signature', ['codesign', '-dvvv', str(binary)])
|
|
cdhash, = re.findall(r'^CDHash=([0-9a-f]+)$', signing, re.M)
|
|
version = run('version', [str(binary), 'version']).splitlines()[0]
|
|
if sources != {k: sha(ROOT / k) for k in sources}:
|
|
raise SystemExit('a source changed during the build')
|
|
record = dict(status='ICON_PIXELS_BUILT_NOT_RUN_ON_A16', binary=str(binary), bytes=binary.stat().st_size,
|
|
sha256=sha(binary), cdhash=cdhash, architecture=arch, compiler=compiler, host_version_line=version,
|
|
sources_sha256=sources, commands=commands,
|
|
trust_row=dict(name='icon-pixels', cdhash=cdhash, category=2, flags=0))
|
|
(out / 'build.json').write_text(json.dumps(record, indent=1) + '\n')
|
|
print(json.dumps({k: record[k] for k in ('status', 'binary', 'bytes', 'sha256', 'cdhash')}, indent=1))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|