mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-10 06:58:59 +02:00
DebugBridgeTouch.m and its header both promise the code is DEBUG-only and
never shipped:
"Uses these private UIKit selectors (DEBUG-only; never shipped to App Store)"
"DEBUG-only — never link in Release."
Nothing enforced it. The only guard was `#if TARGET_OS_IOS`, so a Release build
for iOS compiled the entire implementation in, private API and all.
Measured on a real app (an iOS Release build, `nm -j` on the app binary):
DebugBridge symbols 15
IOHIDEventCreateDigitizer 2
AXSSetAutomationEnabled 1 symbol, 2 strings
IOKit.framework 4 strings
including +[DebugBridgeTouch sendTapAtPoint:inWindow:] and
_OBJC_CLASS_$_DebugBridgeTouch. That is a Guideline 2.5.1 private-API exposure
in a shippable binary, and it fails Package.swift's own stated CI invariant:
nm -j build/Release/<binary> | grep -q DebugBridge && exit 1
WHY THE EXISTING GUARD DOES NOT COVER THIS
Package.swift documents the protection as `.when(configuration: .debug)` on the
consuming target's dependency. That works for SwiftPM consumers. It cannot be
expressed by an app that integrates DebugBridge as a local package inside an
.xcodeproj: Xcode's Filters column under Frameworks, Libraries, and Embedded
Content offers platform conditions only — iOS, macOS, visionOS — never build
configuration. So for xcodeproj consumers the documented guard silently does
nothing, which is precisely the case that was measured.
The Swift targets were already safe: all four .swift files are `#if DEBUG`
guarded and Package.swift defines DEBUG for them via swiftSettings. Only the
Objective-C target, the one that actually links private API, was unguarded.
THE FIX
1. DebugBridgeTouch.m.template now branches `#if !defined(DEBUG)` first and
emits nothing at all in Release, falling through to the existing iOS and
non-iOS branches only in Debug.
2. Package.swift.template declares DEBUG explicitly for the ObjC target:
cSettings: [.define("DEBUG", .when(configuration: .debug))]
The two Swift targets already did this. Relying on SwiftPM's implicit DEBUG
for C-family targets is not worth betting a private-API exposure on.
VERIFIED, by compiling the generated file for iOS both ways:
xcrun -sdk iphoneos clang -c DebugBridgeTouch.m -arch arm64 ...
Release (no -DDEBUG) 0 DebugBridge symbols, 0 private-API symbols, 448 B
Debug (-DDEBUG=1) 7 DebugBridge symbols, 6 private-API symbols, 13104 B
The harness is unchanged in Debug. Release now emits an empty translation unit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
3.6 KiB
Plaintext
83 lines
3.6 KiB
Plaintext
// swift-tools-version:5.9
|
|
// AUTO-GENERATED from gstack/ios-qa/templates/Package.swift.template
|
|
//
|
|
// Drop-in SPM package definition for the DebugBridge stack. Three targets:
|
|
//
|
|
// - DebugBridgeCore Swift, cross-platform (Foundation + Network).
|
|
// Hosts the StateServer + bridge protocols.
|
|
// - DebugBridgeTouch Objective-C, iOS-only. KIF-derived in-process touch
|
|
// synthesis (UITouch + IOHIDEvent + iOS 18
|
|
// _UIHitTestContext for SwiftUI Buttons).
|
|
// - DebugBridgeUI Swift, iOS-only. ScreenshotBridge, ElementsBridge,
|
|
// MutationBridge implementations. Depends on the other
|
|
// two.
|
|
//
|
|
// Release-build guards, in order of reliability:
|
|
//
|
|
// 1. SOURCE. Every file is `#if DEBUG` guarded, including DebugBridgeTouch.m,
|
|
// which holds regardless of how the package is integrated. This is the one
|
|
// that actually protects you.
|
|
// 2. `.when(configuration: .debug)` on the consuming target's dependency. This
|
|
// works for SwiftPM consumers ONLY. An app integrating this as a local
|
|
// package inside an .xcodeproj CANNOT express it — Xcode's Filters column in
|
|
// Frameworks/Libraries offers platform conditions, never configuration — so
|
|
// do not rely on it alone.
|
|
//
|
|
// Guard 1 was added 2026-08-15 after a measured failure: DebugBridgeTouch.m was
|
|
// guarded only by `#if TARGET_OS_IOS`, so a Release iOS build of a real app linked
|
|
// it, and `nm -j` returned 15 DebugBridge symbols plus IOHIDEventCreateDigitizer,
|
|
// AXSSetAutomationEnabled and IOKit.framework strings. Guideline 2.5.1 exposure in
|
|
// a shippable binary, with guard 2 present and doing nothing.
|
|
//
|
|
// CI invariant: `swift build -c release` + `nm -j build/Release/<binary>
|
|
// | grep -q DebugBridge && exit 1`.
|
|
|
|
import PackageDescription
|
|
|
|
let package = Package(
|
|
name: "DebugBridge",
|
|
platforms: [.iOS(.v16), .macOS(.v13)],
|
|
products: [
|
|
.library(name: "DebugBridgeCore", targets: ["DebugBridgeCore"]),
|
|
.library(name: "DebugBridgeUI", targets: ["DebugBridgeUI"]),
|
|
.library(name: "DebugBridgeTouch", targets: ["DebugBridgeTouch"]),
|
|
],
|
|
targets: [
|
|
.target(
|
|
name: "DebugBridgeCore",
|
|
dependencies: [],
|
|
path: "Sources/DebugBridgeCore",
|
|
swiftSettings: [
|
|
.define("DEBUG", .when(configuration: .debug)),
|
|
]
|
|
),
|
|
.target(
|
|
name: "DebugBridgeTouch",
|
|
dependencies: [],
|
|
path: "Sources/DebugBridgeTouch",
|
|
publicHeadersPath: "include",
|
|
cSettings: [
|
|
// Explicit, because the source guard depends on it. SwiftPM's
|
|
// implicit DEBUG for C-family targets is not something to bet a
|
|
// private-API exposure on — the two Swift targets already declare
|
|
// it, and this target is the one that actually links private API.
|
|
.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).
|
|
// UIKit links normally.
|
|
.linkedFramework("UIKit", .when(platforms: [.iOS])),
|
|
]
|
|
),
|
|
.target(
|
|
name: "DebugBridgeUI",
|
|
dependencies: ["DebugBridgeCore", "DebugBridgeTouch"],
|
|
path: "Sources/DebugBridgeUI",
|
|
swiftSettings: [
|
|
.define("DEBUG", .when(configuration: .debug)),
|
|
]
|
|
),
|
|
]
|
|
)
|