Recognize macOS extraction helper staging and verify release resources
This commit is contained in:
1 parent
f1028e2689
commit
d41c5b5e98
4 files changed
+44
-4
No files matched your search
@@ -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" })
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in new issue
Block a user