mirror of https://github.com/garrytan/gstack.git
Merge 2e19cf7ee5 into 3bef43bc5a
This commit is contained in:
commit
0970d6b90f
|
|
@ -31,10 +31,5 @@ let package = Package(
|
|||
],
|
||||
path: "Sources/GenAccessors"
|
||||
),
|
||||
.testTarget(
|
||||
name: "GenAccessorsTests",
|
||||
dependencies: ["GenAccessors"],
|
||||
path: "Tests/GenAccessorsTests"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,12 +22,16 @@ public final class DebugBridgeManager {
|
|||
// 2. Boot the StateServer.
|
||||
StateServer.shared.start()
|
||||
|
||||
// 3. The consuming app installs DebugOverlayWindow separately. See
|
||||
// the example in DebugBridgeWiring.swift.template:
|
||||
// 3. The consuming app installs the UI bridges + overlay separately,
|
||||
// from DebugBridgeUI, via:
|
||||
//
|
||||
// #if canImport(UIKit)
|
||||
// DebugOverlayWindow.shared.install(recording: recording)
|
||||
// DebugBridgeUIWiring.installAll()
|
||||
// #endif
|
||||
//
|
||||
// See Bridges.swift.template (`DebugBridgeUIWiring.installAll()`),
|
||||
// which wires ElementsBridgeImpl / ScreenshotBridgeImpl /
|
||||
// MutationBridgeImpl and installs the overlay window.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#import "DebugBridgeTouch.h"
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#if TARGET_OS_IOS && defined(DEBUG)
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
|
@ -286,11 +286,13 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
|
|||
|
||||
@end
|
||||
|
||||
#else // !TARGET_OS_IOS
|
||||
#else // !(TARGET_OS_IOS && DEBUG) — iOS Release, macOS, or Catalyst
|
||||
|
||||
// macOS / Catalyst / other non-iOS host build: no-op stub so the module
|
||||
// resolves cleanly without UIKit or IOKit. The Swift cross-platform tests
|
||||
// don't exercise touch synthesis; that's iOS-only by definition.
|
||||
// iOS Release / macOS / Catalyst / other non-iOS host build: no-op stub so
|
||||
// the module resolves cleanly without UIKit or IOKit, AND so the private
|
||||
// touch-synthesis SPIs never compile into a shippable (Release) binary. The
|
||||
// Swift cross-platform tests don't exercise touch synthesis; that's
|
||||
// iOS-Debug-only by definition.
|
||||
@implementation DebugBridgeTouch
|
||||
+ (BOOL)sendTapAtPoint:(CGPoint)point inWindow:(UIWindow *)window {
|
||||
(void)point; (void)window;
|
||||
|
|
@ -298,4 +300,4 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
|
|||
}
|
||||
@end
|
||||
|
||||
#endif // TARGET_OS_IOS
|
||||
#endif // TARGET_OS_IOS && defined(DEBUG)
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
// AUTO-GENERATED from gstack/ios-qa/templates/DebugBridgeWiring.swift.template
|
||||
//
|
||||
// Wiring snippet for the app's @main entry. Users paste this into their
|
||||
// App.swift inside the `init()` of the SwiftUI App struct, gated by
|
||||
// #if DEBUG. The wiring is intentionally tiny; everything heavy lives in
|
||||
// the DebugBridge target.
|
||||
|
||||
#if DEBUG
|
||||
import DebugBridge
|
||||
|
||||
@MainActor
|
||||
func startGstackDebugBridge(appState: AppState) {
|
||||
// Read --recording flag from launch arguments
|
||||
let recording = ProcessInfo.processInfo.arguments.contains("--gstack-recording")
|
||||
|
||||
// Install accessibility + screenshot + mutation bridges before starting
|
||||
// the server so the first authenticated request can use them.
|
||||
ElementsBridge.resolver = { AccessibilityScanner.snapshot() }
|
||||
ScreenshotBridge.resolver = { SnapshotCapture.capturePNG() }
|
||||
MutationBridge.resolver = { op, payload in
|
||||
MutationDispatcher.shared.run(op: op, payload: payload)
|
||||
}
|
||||
|
||||
DebugBridgeManager.shared.start(appState: appState, recording: recording)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Example usage in the app's @main entry (paste this into App.swift):
|
||||
//
|
||||
// @main
|
||||
// struct MyApp: App {
|
||||
// @State private var appState = MyAppState()
|
||||
//
|
||||
// init() {
|
||||
// #if DEBUG
|
||||
// startGstackDebugBridge(appState: appState)
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
// var body: some Scene {
|
||||
// WindowGroup { ContentView() }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// 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:
|
||||
|
|
@ -18,7 +19,6 @@
|
|||
// CI invariant: `swift build -c release` + `nm -j build/Release/<binary>
|
||||
// | grep -q DebugBridge && exit 1`.
|
||||
|
||||
// swift-tools-version:5.9
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
|
|
@ -43,6 +43,15 @@ let package = Package(
|
|||
dependencies: [],
|
||||
path: "Sources/DebugBridgeTouch",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
// DEBUG gate for the Obj-C translation unit. swiftSettings do
|
||||
// NOT propagate to .m files, so without this the private
|
||||
// UITouch/UIEvent/IOKit SPIs in DebugBridgeTouch.m would
|
||||
// compile into Release builds and trip Apple's static API
|
||||
// scanner (App Store Guideline 2.1). Pairs with the
|
||||
// `#if TARGET_OS_IOS && defined(DEBUG)` gate in the .m file.
|
||||
.define("DEBUG", to: "1", .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).
|
||||
|
|
@ -58,10 +67,5 @@ let package = Package(
|
|||
.define("DEBUG", .when(configuration: .debug)),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "DebugBridgeCoreTests",
|
||||
dependencies: ["DebugBridgeCore"],
|
||||
path: "Tests/DebugBridgeCoreTests"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ let package = Package(
|
|||
dependencies: [],
|
||||
path: "Sources/DebugBridgeTouch",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
// DEBUG gate for the Obj-C translation unit. swiftSettings do
|
||||
// NOT propagate to .m files, so without this the private
|
||||
// UITouch/UIEvent/IOKit SPIs in DebugBridgeTouch.m would
|
||||
// compile into Release builds and trip Apple's static API
|
||||
// scanner (App Store Guideline 2.1). Pairs with the
|
||||
// `#if TARGET_OS_IOS && defined(DEBUG)` gate in the .m file.
|
||||
.define("DEBUG", to: "1", .when(configuration: .debug)),
|
||||
],
|
||||
linkerSettings: [
|
||||
.linkedFramework("UIKit", .when(platforms: [.iOS])),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -22,12 +22,16 @@ public final class DebugBridgeManager {
|
|||
// 2. Boot the StateServer.
|
||||
StateServer.shared.start()
|
||||
|
||||
// 3. The consuming app installs DebugOverlayWindow separately. See
|
||||
// the example in DebugBridgeWiring.swift.template:
|
||||
// 3. The consuming app installs the UI bridges + overlay separately,
|
||||
// from DebugBridgeUI, via:
|
||||
//
|
||||
// #if canImport(UIKit)
|
||||
// DebugOverlayWindow.shared.install(recording: recording)
|
||||
// DebugBridgeUIWiring.installAll()
|
||||
// #endif
|
||||
//
|
||||
// See Bridges.swift.template (`DebugBridgeUIWiring.installAll()`),
|
||||
// which wires ElementsBridgeImpl / ScreenshotBridgeImpl /
|
||||
// MutationBridgeImpl and installs the overlay window.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#import "DebugBridgeTouch.h"
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#if TARGET_OS_IOS && defined(DEBUG)
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
|
@ -286,11 +286,13 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
|
|||
|
||||
@end
|
||||
|
||||
#else // !TARGET_OS_IOS
|
||||
#else // !(TARGET_OS_IOS && DEBUG) — iOS Release, macOS, or Catalyst
|
||||
|
||||
// macOS / Catalyst / other non-iOS host build: no-op stub so the module
|
||||
// resolves cleanly without UIKit or IOKit. The Swift cross-platform tests
|
||||
// don't exercise touch synthesis; that's iOS-only by definition.
|
||||
// iOS Release / macOS / Catalyst / other non-iOS host build: no-op stub so
|
||||
// the module resolves cleanly without UIKit or IOKit, AND so the private
|
||||
// touch-synthesis SPIs never compile into a shippable (Release) binary. The
|
||||
// Swift cross-platform tests don't exercise touch synthesis; that's
|
||||
// iOS-Debug-only by definition.
|
||||
@implementation DebugBridgeTouch
|
||||
+ (BOOL)sendTapAtPoint:(CGPoint)point inWindow:(UIWindow *)window {
|
||||
(void)point; (void)window;
|
||||
|
|
@ -298,4 +300,4 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
|
|||
}
|
||||
@end
|
||||
|
||||
#endif // TARGET_OS_IOS
|
||||
#endif // TARGET_OS_IOS && defined(DEBUG)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,38 @@ describe('template ↔ fixture parity', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// Static guards for the iOS-Release private-SPI leak. The real failure only
|
||||
// manifests in an iOS Release build (TARGET_OS_IOS true, DEBUG undefined),
|
||||
// which the macOS `swift build` host cannot produce — DebugBridgeTouch links
|
||||
// UIKit. So a host-side nm-scan of Touch always reads clean and proves
|
||||
// nothing. These cheap text assertions lock the two gates that keep the
|
||||
// private UITouch/UIEvent/IOKit SPIs out of App Store binaries (the exact
|
||||
// failure that forced a DebugBridge removal in a downstream app's PR #342).
|
||||
describe('iOS-Release private-SPI leak guard', () => {
|
||||
test('DebugBridgeTouch.m.template gates the iOS impl behind DEBUG', () => {
|
||||
const m = readFileSync(join(TEMPLATES_PATH, 'DebugBridgeTouch.m.template'), 'utf-8');
|
||||
// The SPI-bearing block must require BOTH iOS AND DEBUG. A bare
|
||||
// `#if TARGET_OS_IOS` compiles the private SPIs into iOS Release.
|
||||
expect(m).toContain('#if TARGET_OS_IOS && defined(DEBUG)');
|
||||
expect(m).not.toMatch(/#if\s+TARGET_OS_IOS\s*$/m);
|
||||
});
|
||||
|
||||
test('Package.swift.template DEBUG-gates the Obj-C touch target via cSettings', () => {
|
||||
const pkg = readFileSync(join(TEMPLATES_PATH, 'Package.swift.template'), 'utf-8');
|
||||
// swiftSettings do NOT reach .m translation units — the Obj-C target
|
||||
// needs its own cSettings DEBUG define as belt-and-suspenders.
|
||||
const touchTarget = pkg.match(/\.target\(\s*\n\s*name:\s*"DebugBridgeTouch"[\s\S]*?\n\s{8}\)/);
|
||||
expect(touchTarget).not.toBeNull();
|
||||
expect(touchTarget![0]).toMatch(/cSettings:\s*\[[\s\S]*?\.define\(\s*"DEBUG",\s*to:\s*"1",\s*\.when\(configuration:\s*\.debug\)\)/);
|
||||
});
|
||||
|
||||
test('tools-version directive is the first line of Package.swift.template', () => {
|
||||
const pkg = readFileSync(join(TEMPLATES_PATH, 'Package.swift.template'), 'utf-8');
|
||||
// Swift 6.0+ rejects a tools-version directive that is not on line 1.
|
||||
expect(pkg.split('\n')[0]).toMatch(/^\/\/ swift-tools-version:/);
|
||||
});
|
||||
});
|
||||
|
||||
function hasSwift(): boolean {
|
||||
const r = spawnSync('swift', ['--version'], { stdio: 'pipe' });
|
||||
return r.status === 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue