Recognize macOS extraction helper staging and verify release resources

This commit is contained in:
karl committed 2026-10-13 16:01:07 +02:00
1 parent f1028e2689
commit d41c5b5e98
4 files changed
+44 -4

No files matched your search

+1 -1
View File
@@ -29,7 +29,7 @@ fn main() {
); );
} }
if std::env::var("PROFILE").as_deref() == Ok("release") if std::env::var("PROFILE").as_deref() == Ok("release")
&& matches!(target.as_str(), "windows" | "linux") && matches!(target.as_str(), "windows" | "linux" | "macos")
{ {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../.."); let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../..");
let status = std::process::Command::new(if cfg!(windows) { "python" } else { "python3" }) let status = std::process::Command::new(if cfg!(windows) { "python" } else { "python3" })
+13
View File
@@ -215,3 +215,16 @@ Windows throughput, cold filesystem caches or steady-state search latency.
No persistent parser/cache optimization is justified by this small observation. No persistent parser/cache optimization is justified by this small observation.
Raw ignored report: `.searchi/qa/document-throughput-013.json`, SHA-256 Raw ignored report: `.searchi/qa/document-throughput-013.json`, SHA-256
`b86df0bf2ca0953317033b35b4642e6e0fadb93e50c499a7a28b1ae82512acd1`. `b86df0bf2ca0953317033b35b4642e6e0fadb93e50c499a7a28b1ae82512acd1`.
## macOS staging preparation
The helper staging tool now detects Darwin as macOS, recognizes native thin
64-bit little-endian Mach-O executables for Intel/Apple Silicon, and preserves
executable mode. Desktop release verification now covers macOS too. Header
layout and constants follow Apple's [loader header](https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h)
and [CPU definitions](https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/mach/machine.h).
Six staging tests passed normally and under optimized Python, including mocked
Darwin defaults and synthetic header rejection. The actual staged Windows
helper still verifies. This is preparation only: native macOS build, loader
closure, resource-limit behavior, signing and application execution remain open.
Universal/fat extraction-helper binaries are not supported by this staging tool.
+10 -3
View File
@@ -53,10 +53,17 @@ def identity(path):
machine = struct.unpack_from('<H', signature, 4)[0] if signature[:4] == b'PE\0\0' else 0 machine = struct.unpack_from('<H', signature, 4)[0] if signature[:4] == b'PE\0\0' else 0
arch = {0x8664: 'x86_64', 0xAA64: 'aarch64'}.get(machine) arch = {0x8664: 'x86_64', 0xAA64: 'aarch64'}.get(machine)
platform = 'windows' platform = 'windows'
elif header[:6] == b'\x7fELF\x02\x01': elif len(header) == 64 and header[:6] == b'\x7fELF\x02\x01':
machine = struct.unpack_from('<H', header, 18)[0] machine = struct.unpack_from('<H', header, 18)[0]
arch = {62: 'x86_64', 183: 'aarch64'}.get(machine) arch = {62: 'x86_64', 183: 'aarch64'}.get(machine)
platform = 'linux' platform = 'linux'
elif len(header) >= 32 and header[:4] == b'\xcf\xfa\xed\xfe':
# Apple's mach_header_64: thin little-endian native executable.
cpu, _, filetype, commands, command_bytes = struct.unpack_from('<IIIII', header, 4)
if filetype != 2 or not 0 < commands <= 8192 or not commands * 8 <= command_bytes <= path.stat().st_size - 32:
raise ValueError('Invalid extraction Mach-O executable header')
arch = {0x01000007: 'x86_64', 0x0100000c: 'aarch64'}.get(cpu)
platform = 'macos'
else: else:
raise ValueError('Unsupported extraction executable format') raise ValueError('Unsupported extraction executable format')
if arch is None: if arch is None:
@@ -106,7 +113,7 @@ def stage(binary, provenance, destination, root=ROOT):
try: try:
if target == name and hashlib.sha256(data).hexdigest() != record['sha256']: if target == name and hashlib.sha256(data).hexdigest() != record['sha256']:
raise ValueError('Extraction binary changed while staging') raise ValueError('Extraction binary changed while staging')
if target == name and platform == 'linux': if target == name and platform != 'windows':
temporary.chmod(0o755) temporary.chmod(0o755)
os.replace(temporary, destination / target) os.replace(temporary, destination / target)
finally: finally:
@@ -119,7 +126,7 @@ def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--destination', type=Path, default=ROOT / 'apps/desktop/src-tauri/resources') parser.add_argument('--destination', type=Path, default=ROOT / 'apps/desktop/src-tauri/resources')
parser.add_argument('--verify-only', action='store_true') parser.add_argument('--verify-only', action='store_true')
parser.add_argument('--platform', choices=['windows', 'linux'], default='windows' if os.name == 'nt' else 'linux') parser.add_argument('--platform', choices=['windows', 'linux', 'macos'], default='windows' if os.name == 'nt' else ('macos' if host_platform.system() == 'Darwin' else 'linux'))
parser.add_argument('--architecture', choices=['x86_64', 'aarch64'], default='aarch64' if host_platform.machine().lower() in {'aarch64', 'arm64'} else 'x86_64') parser.add_argument('--architecture', choices=['x86_64', 'aarch64'], default='aarch64' if host_platform.machine().lower() in {'aarch64', 'arm64'} else 'x86_64')
args = parser.parse_args() args = parser.parse_args()
if args.verify_only: if args.verify_only:
+20
View File
@@ -56,6 +56,26 @@ class ExtractionStageTests(unittest.TestCase):
worker.stage(binary, before, resources, root) worker.stage(binary, before, resources, root)
self.assertEqual((resources / 'searchi-extract-worker.exe').read_bytes(), binary.read_bytes()) self.assertEqual((resources / 'searchi-extract-worker.exe').read_bytes(), binary.read_bytes())
def test_macos_thin_headers_and_staging(self):
import struct
for cpu, arch in [(0x01000007, 'x86_64'), (0x0100000c, 'aarch64')]:
with self.subTest(architecture=arch), tempfile.TemporaryDirectory() as directory:
root, binary, resources = self.fixture(directory)
data = struct.pack('<8I', 0xfeedfacf, cpu, 0, 2, 1, 8, 0, 0) + struct.pack('<2I', 0, 8)
binary.write_bytes(data)
worker.stage(binary, worker.fingerprint(root), resources, root)
records = worker.verify(resources, root, 'macos', arch)
self.assertIn('searchi-extract-worker', records)
for invalid in [data[:31], data[:12] + struct.pack('<I', 6) + data[16:], data[:4] + struct.pack('<I', 7) + data[8:], data[:20] + struct.pack('<I', 9999) + data[24:]]:
binary.write_bytes(invalid)
with self.assertRaises(ValueError):
worker.identity(binary)
def test_darwin_default_uses_macos_and_arm64(self):
with patch.object(worker.os, 'name', 'posix'), patch.object(worker.host_platform, 'system', return_value='Darwin'), patch.object(worker.host_platform, 'machine', return_value='arm64'), patch.object(worker, 'verify') as verify, patch('sys.argv', ['stage_extraction_worker.py', '--verify-only']):
worker.main()
self.assertEqual(verify.call_args.args[2:], ('macos', 'aarch64'))
def test_failed_build_never_stages_existing_binary(self): def test_failed_build_never_stages_existing_binary(self):
with patch.object(worker.subprocess, 'run', side_effect=RuntimeError('cargo failed')), patch.object(worker, 'stage') as stage, patch('sys.argv', ['stage_extraction_worker.py']): with patch.object(worker.subprocess, 'run', side_effect=RuntimeError('cargo failed')), patch.object(worker, 'stage') as stage, patch('sys.argv', ['stage_extraction_worker.py']):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):