Files
Snapintosh/tools/build-icon-pixels.py
T
Acelogic d43df34e40 tools: add the icon-pixels guest pixel test
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.
2026-10-24 19:31:58 -04:00

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()