mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
fix(ios-qa): compile the private-API touch bridge out of Release builds
PR #2264 claimed DebugBridgeTouch.m (KIF-derived in-process touch synthesis using private UIKit/IOKit symbols: _touchesEvent, IOHIDEventCreateDigitizer*, _AXSSetAutomationEnabled) was "compiled out in Release," but the body was gated only by TARGET_OS_IOS, so a Release iOS build carried the private symbols (App Store rejection risk). The safety half of the fix (closed PR #2269) never landed. Gate the body on `#if TARGET_OS_IOS && DEBUG` and add the cSettings DEBUG define to the DebugBridgeTouch target so `#if DEBUG` is true in debug and false in release (mirrors the Core/UI swiftSettings). A free static tripwire pins both halves; the nm/strings symbol proof needs an iOS-SDK build and belongs in the device/periodic tier. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3c93aef0ff
commit
33a55562f7
@@ -20,7 +20,12 @@
|
||||
#import "DebugBridgeTouch.h"
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
// DEBUG gate in addition to TARGET_OS_IOS: the private-API touch synthesis must
|
||||
// compile out of Release builds entirely (App Store rejection risk + no
|
||||
// automation code in shipped binaries). DEBUG is defined for this target only
|
||||
// in the debug configuration (see cSettings in Package.swift), so a Release iOS
|
||||
// build emits an empty translation unit — zero private symbols.
|
||||
#if TARGET_OS_IOS && DEBUG
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@@ -43,6 +43,13 @@ let package = Package(
|
||||
dependencies: [],
|
||||
path: "Sources/DebugBridgeTouch",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
// Defines DEBUG for the ObjC translation unit in debug config
|
||||
// only, so DebugBridgeTouch.m's `#if TARGET_OS_IOS && DEBUG`
|
||||
// body compiles out of Release builds. Mirrors the swiftSettings
|
||||
// DEBUG define on the Core/UI targets.
|
||||
.define("DEBUG", .when(configuration: .debug)),
|
||||
],
|
||||
linkerSettings: [
|
||||
// IOKit is loaded dynamically via dlopen at runtime (it's a
|
||||
// private framework on iOS and can't be linked statically).
|
||||
|
||||
@@ -32,6 +32,9 @@ let package = Package(
|
||||
dependencies: [],
|
||||
path: "Sources/DebugBridgeTouch",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
.define("DEBUG", .when(configuration: .debug)),
|
||||
],
|
||||
linkerSettings: [
|
||||
.linkedFramework("UIKit", .when(platforms: [.iOS])),
|
||||
]
|
||||
|
||||
+6
-1
@@ -20,7 +20,12 @@
|
||||
#import "DebugBridgeTouch.h"
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
// DEBUG gate in addition to TARGET_OS_IOS: the private-API touch synthesis must
|
||||
// compile out of Release builds entirely (App Store rejection risk + no
|
||||
// automation code in shipped binaries). DEBUG is defined for this target only
|
||||
// in the debug configuration (see cSettings in Package.swift), so a Release iOS
|
||||
// build emits an empty translation unit — zero private symbols.
|
||||
#if TARGET_OS_IOS && DEBUG
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Static tripwire (free tier, runs on every PR): the private-API ObjC touch
|
||||
// bridge MUST compile out of Release builds. That safety property has two
|
||||
// load-bearing halves, and this test pins both against regression:
|
||||
//
|
||||
// 1. DebugBridgeTouch.m's body is gated `#if TARGET_OS_IOS && DEBUG` — NOT a
|
||||
// bare `#if TARGET_OS_IOS` (which shipped the private symbols in Release
|
||||
// iOS builds; PR #2264 claimed they were compiled out but the guard was
|
||||
// platform-only).
|
||||
// 2. The DebugBridgeTouch target in Package.swift carries a cSettings
|
||||
// DEBUG define scoped to the debug configuration — without it, `#if DEBUG`
|
||||
// is false even in Debug and the bridge silently breaks in the case it
|
||||
// exists to serve.
|
||||
//
|
||||
// The full proof — an iOS-SDK Release build asserting `nm`/`strings` of the
|
||||
// built binary contain none of _touchesEvent / IOHIDEventCreateDigitizer* /
|
||||
// _AXSSetAutomationEnabled / DebugBridgeTouch — needs an iOS builder and lives
|
||||
// in the device/periodic tier (the macOS `swift build` lane can't build the
|
||||
// Touch target). This tripwire guards the source-level invariant everywhere.
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
|
||||
const TOUCH_SOURCES = [
|
||||
'ios-qa/templates/DebugBridgeTouch.m.template',
|
||||
'test/fixtures/ios-qa/FixtureApp/Sources/DebugBridgeTouch/DebugBridgeTouch.m',
|
||||
];
|
||||
|
||||
const PACKAGE_MANIFESTS = [
|
||||
'ios-qa/templates/Package.swift.template',
|
||||
'test/fixtures/ios-qa/FixtureApp/Package.swift',
|
||||
];
|
||||
|
||||
describe('DebugBridgeTouch Release compile-out guard', () => {
|
||||
for (const rel of TOUCH_SOURCES) {
|
||||
test(`${rel} gates the body on TARGET_OS_IOS && DEBUG`, () => {
|
||||
const src = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||
// The primary body guard must be the DEBUG-qualified form.
|
||||
expect(src).toContain('#if TARGET_OS_IOS && DEBUG');
|
||||
// And must NOT carry a bare platform-only guard as the body gate — that
|
||||
// was the exact regression (private API shipped in Release).
|
||||
expect(src).not.toMatch(/^#if TARGET_OS_IOS$/m);
|
||||
});
|
||||
}
|
||||
|
||||
for (const rel of PACKAGE_MANIFESTS) {
|
||||
test(`${rel} defines DEBUG for the DebugBridgeTouch target in debug config`, () => {
|
||||
const src = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||
// Anchor on the target's unique `path:` (the `name:` string also appears
|
||||
// in the products/.library section). The DEBUG cSettings define sits just
|
||||
// after the path line; take a forward window to the next .target( (or end)
|
||||
// so the assertion is scoped to THIS target, not the whole manifest.
|
||||
const anchor = src.indexOf('path: "Sources/DebugBridgeTouch"');
|
||||
expect(anchor).toBeGreaterThan(-1);
|
||||
const rest = src.slice(anchor);
|
||||
const nextTarget = rest.indexOf('.target(');
|
||||
const block = nextTarget > -1 ? rest.slice(0, nextTarget) : rest;
|
||||
expect(block).toContain('cSettings:');
|
||||
expect(block).toMatch(/\.define\("DEBUG",\s*\.when\(configuration:\s*\.debug\)\)/);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user