peachsim 0.1.1625 → 0.1.1640
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +15 -2
- package/cli/app-config.ts +18 -6
- package/cli/app-fonts.ts +37 -17
- package/cli/app-stubs.ts +284 -20
- package/cli/bridge-flow-runner.ts +66 -45
- package/cli/cloud-client.ts +60 -40
- package/cli/command-registry.ts +1 -0
- package/cli/commands/control.ts +283 -49
- package/cli/commands/doctor.ts +30 -189
- package/cli/commands/flow.ts +79 -11
- package/cli/commands/inspect/core.ts +10 -5
- package/cli/commands/inspect/settling.ts +14 -2
- package/cli/commands/inspect/shared.ts +9 -4
- package/cli/commands/inspect/wait-ready.ts +12 -0
- package/cli/commands/inspect.ts +91 -50
- package/cli/commands/install-desktop.ts +1 -0
- package/cli/commands/maestro-compare.ts +97 -38
- package/cli/commands/maestro.ts +9 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/record.ts +118 -48
- package/cli/commands/screenshot.ts +23 -1
- package/cli/commands/serve.ts +7 -2
- package/cli/commands/setup.ts +19 -42
- package/cli/commands/swift.ts +14 -3
- package/cli/commands/upload.ts +93 -48
- package/cli/desktop-companion.ts +102 -17
- package/cli/drivers/desktop.ts +14 -1
- package/cli/drivers/playwright-sim-host.ts +103 -45
- package/cli/drivers/playwright.ts +9 -8
- package/cli/drivers/types.ts +5 -0
- package/cli/maestro-accessibility.ts +1 -1
- package/cli/maestro-compare.ts +84 -12
- package/cli/main.ts +3 -2
- package/cli/outbound-endpoints.ts +20 -6
- package/cli/pause-aware-timeout.ts +106 -0
- package/cli/scene-video.ts +89 -0
- package/cli/ws-bridge.ts +37 -19
- package/detox/attempt-events.cjs +66 -5
- package/detox/attempt-events.d.cts +7 -0
- package/detox/expectations.ts +7 -3
- package/detox/index.ts +269 -170
- package/detox/motion-change.cjs +206 -21
- package/detox/motion-change.d.cts +39 -2
- package/detox/navigation-lifecycle.ts +35 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +8 -7
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +13 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +14 -8
- package/dist-lib/bridge-contract.mjs +13 -7
- package/dist-lib/bundle-channel.cjs +11887 -7900
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +6 -3
- package/dist-lib/config.mjs +6 -3
- package/dist-lib/detox/index.cjs +1379 -725
- package/dist-lib/dev-bundle-resolution.cjs +14 -2
- package/dist-lib/expo-swift-bundle.cjs +9341 -5462
- package/dist-lib/expo-swift-metadata.cjs +11 -7
- package/dist-lib/expo-swift-metadata.mjs +11 -7
- package/dist-lib/home-paths.cjs +32 -3
- package/dist-lib/host/bridge-host.cjs +50204 -7258
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +3541 -448
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +2931 -104
- package/dist-lib/native-app-identity-project.cjs +289 -49
- package/dist-lib/native-app-identity-project.mjs +289 -49
- package/dist-lib/native-app-identity.cjs +77 -26
- package/dist-lib/native-app-identity.mjs +76 -26
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +6 -2
- package/dist-lib/react-native-host-modules.mjs +6 -2
- package/dist-lib/scripts/dev-server-scanner.cjs +22 -8
- package/dist-lib/sdk.cjs +27 -11
- package/dist-lib/sdk.mjs +27 -10
- package/dist-lib/skills.cjs +72201 -26208
- package/dist-lib/swift-compile.cjs +1007 -140
- package/dist-lib/swift-compile.mjs +1049 -174
- package/dist-lib/swift-package.cjs +12 -3
- package/dist-lib/swift-package.mjs +12 -3
- package/dist-lib/swift-submit.cjs +13 -5
- package/dist-lib/swift-submit.mjs +11 -5
- package/dist-lib/swift-toolchain.cjs +423 -33
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +8628 -5268
- package/dist-lib/vite.cjs +2859 -12
- package/dist-lib/worklet-executable.cjs +14 -2
- package/dist-lib/worklet-executable.mjs +14 -2
- package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
- package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +2 -1
- package/skills/peach-perf/SKILL.md +11 -5
- package/skills/peach-setup/SKILL.md +16 -19
- package/src/backend-origin.ts +13 -6
- package/src/bridge-constants.ts +11 -0
- package/src/bridge-contract.ts +22 -34
- package/src/config.ts +15 -0
- package/src/desktop-host-delivery.ts +5 -1
- package/src/dev-bundle-resolution.ts +2 -1
- package/src/expo-manifest-body.ts +13 -0
- package/src/expo-swift-host.ts +196 -49
- package/src/expo-swift-ready.ts +78 -0
- package/src/expoKotlinContract.ts +4 -7
- package/src/expoSwiftContract.ts +17 -6
- package/src/home-paths.ts +48 -2
- package/src/host/bridge-host.ts +5 -2
- package/src/host/staged-host-files.ts +9 -0
- package/src/native-app-identity-project.ts +27 -6
- package/src/native-app-identity.ts +91 -4
- package/src/native-firebase-config.ts +209 -13
- package/src/nativeAppIconContract.ts +1 -4
- package/src/output-lock-stdio.ts +36 -0
- package/src/process-group.ts +25 -2
- package/src/react-native-bridge-modules.ts +431 -0
- package/src/react-native-host-modules.ts +8 -1
- package/src/settings-bundle.ts +1 -4
- package/src/swift-binary-modules.ts +2 -2
- package/src/swift-compile.ts +918 -109
- package/src/swift-compiler-digest.ts +14 -0
- package/src/swift-distribution.lock.json +24 -12
- package/src/swift-distribution.ts +103 -4
- package/src/swift-host.ts +180 -55
- package/src/swift-icu.ts +27 -13
- package/src/swift-objc-compile.ts +355 -0
- package/src/swift-objc-sdk.ts +821 -27
- package/src/swift-objc-standin.ts +148 -14
- package/src/swift-package-manifest.ts +60 -7
- package/src/swift-package.ts +15 -1
- package/src/swift-packages.ts +154 -81
- package/src/swift-standin-emit.ts +96 -15
- package/src/swift-standins.ts +530 -116
- package/src/swift-submit.ts +13 -5
- package/src/swift-toolchain.ts +500 -42
- package/src/vite-plugin.ts +12 -1
- package/src/worker-system-chunks.ts +11 -12
- package/src/worklet-executable.ts +19 -1
- package/src/xcode-project.ts +270 -30
package/src/swift-packages.ts
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
// each linked product resolves through swiftpm's own reading of its package
|
|
3
3
|
// (swift-package-manifest.ts) to the source targets that build it; remote
|
|
4
4
|
// packages are fetched at the revision the app's Package.resolved pins, into
|
|
5
|
-
//
|
|
5
|
+
// PEACH_HOME/cache/swift-packages, isolated by the app root. swift and c-family source
|
|
6
6
|
// targets use the common compiler backend. objective-c, assembly and binary
|
|
7
7
|
// targets the host built no stand-in for stay missing with their reason;
|
|
8
8
|
// the targets beside them still compile.
|
|
9
9
|
|
|
10
10
|
import { execFileSync } from 'node:child_process'
|
|
11
|
+
import { createHash } from 'node:crypto'
|
|
11
12
|
import fs from 'node:fs'
|
|
12
13
|
import path from 'node:path'
|
|
14
|
+
import { cacheDir } from './home-paths'
|
|
13
15
|
import {
|
|
14
16
|
swiftImplicitResourcePaths,
|
|
15
17
|
swiftPackageIdentity,
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
import {
|
|
20
22
|
swiftPackageManifest,
|
|
21
23
|
swiftTargetFiles,
|
|
22
|
-
type
|
|
24
|
+
type SwiftManifestSetting,
|
|
23
25
|
type SwiftPackageManifest,
|
|
24
26
|
} from './swift-package-manifest'
|
|
25
27
|
|
|
@@ -43,7 +45,8 @@ export interface SwiftPackageSourceTarget extends SwiftPackageTarget {
|
|
|
43
45
|
packageDir: string
|
|
44
46
|
remote: boolean
|
|
45
47
|
/** the conditions its manifest defines, before a build configuration picks them. */
|
|
46
|
-
packageDefines:
|
|
48
|
+
packageDefines: SwiftManifestSetting[]
|
|
49
|
+
packageLibraries: SwiftManifestSetting[]
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/** why a product or module does not compile, and where it comes from. */
|
|
@@ -74,47 +77,68 @@ export interface SwiftPackageBinary {
|
|
|
74
77
|
dir: string
|
|
75
78
|
/** a linked product's closure reaches it, so the app links it. */
|
|
76
79
|
reached: boolean
|
|
80
|
+
/** a source Objective-C target: public headers relative to dir. */
|
|
81
|
+
publicHeaders?: string
|
|
82
|
+
/** dependency module public headers, relative to dir. */
|
|
83
|
+
headerDependencies?: { module: string; path: string }[]
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
/**
|
|
80
|
-
export
|
|
86
|
+
/** dependency checkouts belong to peach, isolated by the canonical app root. */
|
|
87
|
+
export function remoteSwiftCheckouts(root: string): string {
|
|
88
|
+
const key = createHash('sha256')
|
|
89
|
+
.update(fs.realpathSync(root))
|
|
90
|
+
.digest('hex')
|
|
91
|
+
.slice(0, 32)
|
|
92
|
+
fs.mkdirSync(cacheDir(), { recursive: true })
|
|
93
|
+
return path.join(fs.realpathSync(cacheDir()), 'swift-packages', key, 'checkouts')
|
|
94
|
+
}
|
|
81
95
|
|
|
82
96
|
// sources requiring an objective-c runtime or assembly lowering.
|
|
83
97
|
const UNSUPPORTED_NATIVE = /\.(m|mm|s|S)$/
|
|
84
98
|
|
|
85
99
|
const BINARY = 'is a binary framework'
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
const blocker = (reasons: (string | null)[]): string | null =>
|
|
90
|
-
reasons.find((reason) => reason?.includes(BINARY)) ??
|
|
91
|
-
reasons.find((reason) => reason !== null) ??
|
|
92
|
-
null
|
|
100
|
+
// retain every independent blocker in dependency order. a product can reach
|
|
101
|
+
// several unsupported targets, and fixing one must not reveal a hidden next one.
|
|
102
|
+
const blockers = (reasons: readonly string[][]): string[] => [...new Set(reasons.flat())]
|
|
93
103
|
|
|
94
104
|
interface Pin {
|
|
95
105
|
location: string
|
|
96
106
|
revision: string
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
function readPins(resolved: string): Map<string, Pin> {
|
|
100
|
-
const data: unknown = JSON.parse(fs.readFileSync(resolved, 'utf8'))
|
|
101
|
-
const pins =
|
|
102
|
-
data && typeof data === 'object' && 'pins' in data && Array.isArray(data.pins)
|
|
103
|
-
? data.pins
|
|
104
|
-
: null
|
|
105
|
-
if (!pins)
|
|
106
|
-
throw new Error(`${resolved} has no pins; resolve packages in a current Xcode`)
|
|
109
|
+
function readPins(resolved: readonly string[]): Map<string, Pin> {
|
|
107
110
|
const out = new Map<string, Pin>()
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
|
|
111
|
+
const owners = new Map<string, string>()
|
|
112
|
+
for (const file of resolved) {
|
|
113
|
+
const data: unknown = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
114
|
+
const pins =
|
|
115
|
+
data && typeof data === 'object' && 'pins' in data && Array.isArray(data.pins)
|
|
116
|
+
? data.pins
|
|
117
|
+
: null
|
|
118
|
+
if (!pins) throw new Error(`${file} has no pins; resolve packages in a current Xcode`)
|
|
119
|
+
for (const pin of pins) {
|
|
120
|
+
const identity = pin?.identity
|
|
121
|
+
const location = pin?.location
|
|
122
|
+
const revision = pin?.state?.revision
|
|
123
|
+
if (
|
|
124
|
+
typeof identity === 'string' &&
|
|
125
|
+
typeof location === 'string' &&
|
|
126
|
+
typeof revision === 'string'
|
|
127
|
+
) {
|
|
128
|
+
const previous = out.get(identity)
|
|
129
|
+
const repository = (url: string) => url.replace(/(?:\.git)?\/?$/, '')
|
|
130
|
+
if (
|
|
131
|
+
previous &&
|
|
132
|
+
(previous.revision !== revision ||
|
|
133
|
+
repository(previous.location) !== repository(location))
|
|
134
|
+
)
|
|
135
|
+
throw new Error(
|
|
136
|
+
`conflicting Package.resolved pins for ${identity}: ${owners.get(identity)} pins ${previous.location} at ${previous.revision}; ${file} pins ${location} at ${revision}`,
|
|
137
|
+
)
|
|
138
|
+
out.set(identity, { location, revision })
|
|
139
|
+
owners.set(identity, file)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
118
142
|
}
|
|
119
143
|
return out
|
|
120
144
|
}
|
|
@@ -167,8 +191,8 @@ interface LoadedPackage {
|
|
|
167
191
|
*/
|
|
168
192
|
export function resolveSwiftPackages(options: {
|
|
169
193
|
rootDir: string
|
|
170
|
-
/** the
|
|
171
|
-
resolved: string
|
|
194
|
+
/** the selected workspace or local Package.resolved files. */
|
|
195
|
+
resolved: readonly string[]
|
|
172
196
|
products: readonly SwiftLinkedProduct[]
|
|
173
197
|
/** local package directories the workspace or a project references. */
|
|
174
198
|
localDirs: readonly string[]
|
|
@@ -184,8 +208,8 @@ export function resolveSwiftPackages(options: {
|
|
|
184
208
|
modules: new Map(),
|
|
185
209
|
binaries: [],
|
|
186
210
|
}
|
|
187
|
-
const pins = resolved ? readPins(resolved) : null
|
|
188
|
-
const checkouts =
|
|
211
|
+
const pins = resolved.length > 0 ? readPins(resolved) : null
|
|
212
|
+
const checkouts = remoteSwiftCheckouts(rootDir)
|
|
189
213
|
|
|
190
214
|
const packages = new Map<string, LoadedPackage | string>()
|
|
191
215
|
// a package by local directory or remote url, or why it cannot load
|
|
@@ -205,8 +229,6 @@ export function resolveSwiftPackages(options: {
|
|
|
205
229
|
: 'no Package.resolved pins it; resolve packages in Xcode once'
|
|
206
230
|
} else {
|
|
207
231
|
fs.mkdirSync(checkouts, { recursive: true })
|
|
208
|
-
// fetched sources never show up as changes in the app's repository
|
|
209
|
-
fs.writeFileSync(path.join(path.dirname(checkouts), '.gitignore'), '*\n')
|
|
210
232
|
loaded = {
|
|
211
233
|
source: source.url,
|
|
212
234
|
remote: true,
|
|
@@ -219,35 +241,40 @@ export function resolveSwiftPackages(options: {
|
|
|
219
241
|
}
|
|
220
242
|
|
|
221
243
|
const targets = new Map<string, SwiftPackageSourceTarget>()
|
|
222
|
-
//
|
|
223
|
-
const targetOutcomes = new Map<string, string
|
|
224
|
-
const resolveTarget = (pkg: LoadedPackage, name: string): string
|
|
244
|
+
// every reason each target cannot compile; empty once it can
|
|
245
|
+
const targetOutcomes = new Map<string, string[]>()
|
|
246
|
+
const resolveTarget = (pkg: LoadedPackage, name: string): string[] => {
|
|
225
247
|
const key = `${pkg.manifest.dir}\0${name}`
|
|
226
248
|
if (targetOutcomes.has(key)) return targetOutcomes.get(key)!
|
|
227
249
|
// a dependency cycle resolves as compiling; swiftpm rejects real cycles
|
|
228
|
-
targetOutcomes.set(key,
|
|
229
|
-
const outcome = ((): string
|
|
230
|
-
if (provided.has(name)) return
|
|
250
|
+
targetOutcomes.set(key, [])
|
|
251
|
+
const outcome = ((): string[] => {
|
|
252
|
+
if (provided.has(name)) return []
|
|
231
253
|
const target = pkg.manifest.targets.get(name)
|
|
232
|
-
if (!target)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
if (!target)
|
|
255
|
+
return [`${path.basename(pkg.manifest.dir)} declares no target ${name}`]
|
|
256
|
+
const files =
|
|
257
|
+
target.type === 'regular' || target.type === 'system'
|
|
258
|
+
? swiftTargetFiles(pkg.manifest, target)
|
|
259
|
+
: []
|
|
260
|
+
const gap = options.binaryGaps?.get(name)
|
|
261
|
+
const ownReason =
|
|
262
|
+
target.type === 'binary'
|
|
263
|
+
? `${name} ${BINARY}${gap ? `; ${gap}` : ''}`
|
|
264
|
+
: target.type !== 'regular' && target.type !== 'system'
|
|
265
|
+
? `${name} is a ${target.type} target`
|
|
266
|
+
: files.some((file) => UNSUPPORTED_NATIVE.test(file))
|
|
267
|
+
? `${name} is an Objective-C or assembly target${gap ? `; ${gap}` : ''}`
|
|
268
|
+
: null
|
|
242
269
|
// every dependency resolves, so a failing one never hides the rest
|
|
243
270
|
// of the closure from the compile or from the report
|
|
244
271
|
const reasons = target.dependencies.map((dependency) => {
|
|
245
272
|
if (dependency.kind === 'target') return resolveTarget(pkg, dependency.name)
|
|
246
273
|
const nested = resolveDependencyProduct(pkg, dependency.package, dependency.name)
|
|
247
|
-
return nested
|
|
274
|
+
return nested.map((reason) => `${dependency.name}: ${reason}`)
|
|
248
275
|
})
|
|
249
|
-
const blocked =
|
|
250
|
-
if (blocked) return blocked
|
|
276
|
+
const blocked = blockers([ownReason ? [ownReason] : [], ...reasons])
|
|
277
|
+
if (blocked.length > 0) return blocked
|
|
251
278
|
const relative = files.map((file) => path.relative(target.dir, file))
|
|
252
279
|
targets.set(`${pkg.manifest.dir}\0${name}`, {
|
|
253
280
|
name,
|
|
@@ -257,6 +284,7 @@ export function resolveSwiftPackages(options: {
|
|
|
257
284
|
packageDir: pkg.manifest.dir,
|
|
258
285
|
remote: pkg.remote,
|
|
259
286
|
packageDefines: target.defines,
|
|
287
|
+
packageLibraries: target.libraries,
|
|
260
288
|
dependencies: target.dependencies.flatMap((dependency) => {
|
|
261
289
|
if (dependency.kind === 'target') return [dependency]
|
|
262
290
|
if (provided.has(dependency.name)) return []
|
|
@@ -265,32 +293,35 @@ export function resolveSwiftPackages(options: {
|
|
|
265
293
|
source &&
|
|
266
294
|
packages.get('dir' in source ? path.resolve(source.dir) : source.url)
|
|
267
295
|
return nested && typeof nested !== 'string'
|
|
268
|
-
? (nested.manifest.products.get(dependency.name) ?? []).map(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
296
|
+
? (nested.manifest.products.get(dependency.name) ?? []).map(
|
|
297
|
+
(name): SwiftTargetDependency => ({
|
|
298
|
+
kind: 'target',
|
|
299
|
+
name,
|
|
300
|
+
}),
|
|
301
|
+
)
|
|
272
302
|
: []
|
|
273
303
|
}),
|
|
274
304
|
executable: false,
|
|
305
|
+
...(target.type === 'system' ? { systemLibrary: true } : {}),
|
|
275
306
|
languageMode: target.languageMode,
|
|
276
307
|
mainActorIsolation: target.mainActorIsolation,
|
|
277
308
|
resources:
|
|
278
309
|
target.declaresResources || swiftImplicitResourcePaths(relative).length > 0,
|
|
279
310
|
})
|
|
280
|
-
return
|
|
311
|
+
return []
|
|
281
312
|
})()
|
|
282
313
|
targetOutcomes.set(key, outcome)
|
|
283
314
|
return outcome
|
|
284
315
|
}
|
|
285
316
|
// products resolved anywhere in the closure, by package, with why one
|
|
286
317
|
// cannot compile: a wrapper package's product is often the module name
|
|
287
|
-
const productOutcomes = new Map<string, { pkg: LoadedPackage; reason: string
|
|
288
|
-
const resolveProduct = (pkg: LoadedPackage, product: string): string
|
|
289
|
-
if (provided.has(product)) return
|
|
318
|
+
const productOutcomes = new Map<string, { pkg: LoadedPackage; reason: string[] }>()
|
|
319
|
+
const resolveProduct = (pkg: LoadedPackage, product: string): string[] => {
|
|
320
|
+
if (provided.has(product)) return []
|
|
290
321
|
const roots = pkg.manifest.products.get(product)
|
|
291
322
|
const reason = !roots
|
|
292
|
-
? `${path.basename(pkg.manifest.dir)} has no library ${product}`
|
|
293
|
-
:
|
|
323
|
+
? [`${path.basename(pkg.manifest.dir)} has no library ${product}`]
|
|
324
|
+
: blockers(roots.map((name) => resolveTarget(pkg, name)))
|
|
294
325
|
productOutcomes.set(`${pkg.manifest.dir}\0${product}`, { pkg, reason })
|
|
295
326
|
return reason
|
|
296
327
|
}
|
|
@@ -299,18 +330,18 @@ export function resolveSwiftPackages(options: {
|
|
|
299
330
|
pkg: LoadedPackage,
|
|
300
331
|
packageName: string,
|
|
301
332
|
product: string,
|
|
302
|
-
): string
|
|
303
|
-
if (provided.has(product)) return
|
|
333
|
+
): string[] => {
|
|
334
|
+
if (provided.has(product)) return []
|
|
304
335
|
const dependency = pkg.manifest.packages.get(packageName)
|
|
305
336
|
if (!dependency)
|
|
306
|
-
return `${path.basename(pkg.manifest.dir)} names no package ${packageName}`
|
|
337
|
+
return [`${path.basename(pkg.manifest.dir)} names no package ${packageName}`]
|
|
307
338
|
if ('dir' in dependency) {
|
|
308
339
|
const loaded = load({ kind: 'local', dir: dependency.dir })
|
|
309
|
-
return typeof loaded === 'string' ? loaded : resolveProduct(loaded, product)
|
|
340
|
+
return typeof loaded === 'string' ? [loaded] : resolveProduct(loaded, product)
|
|
310
341
|
}
|
|
311
|
-
if (!options.provided) return
|
|
342
|
+
if (!options.provided) return []
|
|
312
343
|
const loaded = load({ kind: 'remote', url: dependency.url })
|
|
313
|
-
return typeof loaded === 'string' ? loaded : resolveProduct(loaded, product)
|
|
344
|
+
return typeof loaded === 'string' ? [loaded] : resolveProduct(loaded, product)
|
|
314
345
|
}
|
|
315
346
|
|
|
316
347
|
const unnamed = () =>
|
|
@@ -326,11 +357,11 @@ export function resolveSwiftPackages(options: {
|
|
|
326
357
|
: load(source)
|
|
327
358
|
const reason =
|
|
328
359
|
pkg === undefined
|
|
329
|
-
? 'no package the workspace references vends it'
|
|
360
|
+
? ['no package the workspace references vends it']
|
|
330
361
|
: typeof pkg === 'string'
|
|
331
|
-
? pkg
|
|
362
|
+
? [pkg]
|
|
332
363
|
: resolveProduct(pkg, product)
|
|
333
|
-
if (reason)
|
|
364
|
+
if (reason.length > 0)
|
|
334
365
|
result.unavailable.set(product, {
|
|
335
366
|
source:
|
|
336
367
|
source.kind === 'remote'
|
|
@@ -340,7 +371,7 @@ export function resolveSwiftPackages(options: {
|
|
|
340
371
|
: source.kind === 'local'
|
|
341
372
|
? source.dir
|
|
342
373
|
: 'the workspace',
|
|
343
|
-
reason,
|
|
374
|
+
reason: reason.join('; '),
|
|
344
375
|
})
|
|
345
376
|
}
|
|
346
377
|
|
|
@@ -352,24 +383,35 @@ export function resolveSwiftPackages(options: {
|
|
|
352
383
|
if (target.type === 'test' || target.type === 'plugin' || target.type === 'macro')
|
|
353
384
|
continue
|
|
354
385
|
if (target.type === 'executable') continue
|
|
355
|
-
|
|
386
|
+
const objc =
|
|
387
|
+
target.type === 'regular' &&
|
|
388
|
+
swiftTargetFiles(pkg.manifest, target).some((file) => /\.(m|mm)$/.test(file))
|
|
389
|
+
if (target.type === 'binary' || objc)
|
|
356
390
|
result.binaries.push({
|
|
357
391
|
target: name,
|
|
358
392
|
source: pkg.source,
|
|
359
393
|
url: target.url,
|
|
360
394
|
checksum: target.checksum,
|
|
361
|
-
dir: target.dir,
|
|
395
|
+
dir: objc ? pkg.manifest.dir : target.dir,
|
|
362
396
|
reached: false,
|
|
397
|
+
...(objc
|
|
398
|
+
? {
|
|
399
|
+
publicHeaders: path.relative(
|
|
400
|
+
pkg.manifest.dir,
|
|
401
|
+
path.join(target.dir, target.publicHeadersPath),
|
|
402
|
+
),
|
|
403
|
+
}
|
|
404
|
+
: {}),
|
|
363
405
|
})
|
|
364
406
|
const reason = resolveTarget(pkg, name)
|
|
365
|
-
if (reason && !result.modules.has(name))
|
|
366
|
-
result.modules.set(name, { source: pkg.source, reason })
|
|
407
|
+
if (reason.length > 0 && !result.modules.has(name))
|
|
408
|
+
result.modules.set(name, { source: pkg.source, reason: reason.join('; ') })
|
|
367
409
|
}
|
|
368
410
|
}
|
|
369
411
|
for (const [key, { pkg, reason }] of productOutcomes) {
|
|
370
412
|
const product = key.slice(key.indexOf('\0') + 1)
|
|
371
|
-
if (reason && !result.modules.has(product))
|
|
372
|
-
result.modules.set(product, { source: pkg.source, reason })
|
|
413
|
+
if (reason.length > 0 && !result.modules.has(product))
|
|
414
|
+
result.modules.set(product, { source: pkg.source, reason: reason.join('; ') })
|
|
373
415
|
}
|
|
374
416
|
// what compiles is every buildable target in a linked product's closure,
|
|
375
417
|
// decided per target: a product with one target behind a binary still
|
|
@@ -410,6 +452,37 @@ export function resolveSwiftPackages(options: {
|
|
|
410
452
|
if (pkg && typeof pkg !== 'string')
|
|
411
453
|
binary.reached = reached.has(`${pkg.manifest.dir}\0${binary.target}`)
|
|
412
454
|
}
|
|
455
|
+
for (const binary of result.binaries) {
|
|
456
|
+
if (binary.publicHeaders === undefined) continue
|
|
457
|
+
const pkg = packages.get(binary.source)
|
|
458
|
+
if (!pkg || typeof pkg === 'string') continue
|
|
459
|
+
const headers = new Map<string, string>()
|
|
460
|
+
const visited = new Set<string>()
|
|
461
|
+
const visit = (owner: LoadedPackage, name: string) => {
|
|
462
|
+
const key = `${owner.manifest.dir}\0${name}`
|
|
463
|
+
if (visited.has(key)) return
|
|
464
|
+
visited.add(key)
|
|
465
|
+
const target = owner.manifest.targets.get(name)
|
|
466
|
+
if (!target) return
|
|
467
|
+
const dir = path.join(target.dir, target.publicHeadersPath)
|
|
468
|
+
for (const dependency of target.dependencies) {
|
|
469
|
+
if (dependency.kind === 'target') visit(owner, dependency.name)
|
|
470
|
+
else {
|
|
471
|
+
const source = owner.manifest.packages.get(dependency.package)
|
|
472
|
+
const nested =
|
|
473
|
+
source &&
|
|
474
|
+
packages.get('dir' in source ? path.resolve(source.dir) : source.url)
|
|
475
|
+
if (nested && typeof nested !== 'string')
|
|
476
|
+
for (const root of nested.manifest.products.get(dependency.name) ?? [])
|
|
477
|
+
visit(nested, root)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (name !== binary.target && fs.existsSync(dir))
|
|
481
|
+
headers.set(name, path.relative(binary.dir, dir))
|
|
482
|
+
}
|
|
483
|
+
visit(pkg, binary.target)
|
|
484
|
+
binary.headerDependencies = [...headers].map(([module, path]) => ({ module, path }))
|
|
485
|
+
}
|
|
413
486
|
result.targets = [...targets.entries()]
|
|
414
487
|
.filter(([key]) => reached.has(key))
|
|
415
488
|
.map(([, target]) => target)
|
|
@@ -20,6 +20,7 @@ export type GraphSymbol = {
|
|
|
20
20
|
pathComponents: string[]
|
|
21
21
|
declarationFragments?: SymbolFragment[]
|
|
22
22
|
accessLevel: string
|
|
23
|
+
functionSignature?: { returns?: SymbolFragment[] }
|
|
23
24
|
availability?: {
|
|
24
25
|
domain?: string
|
|
25
26
|
isUnconditionallyUnavailable?: boolean
|
|
@@ -43,6 +44,8 @@ export type StandinOptions = {
|
|
|
43
44
|
module: string
|
|
44
45
|
/** where the declarations came from, for the banner: sdk, version, artifact. */
|
|
45
46
|
source: string
|
|
47
|
+
/** source Objective-C targets keep declarations with inert native bodies. */
|
|
48
|
+
inert?: boolean
|
|
46
49
|
/** modules the source imports beside Foundation and UIKit. */
|
|
47
50
|
imports?: string[]
|
|
48
51
|
/** precise symbol id → swift literal for enum cases and option values. */
|
|
@@ -87,7 +90,7 @@ export type StandinResult = {
|
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
/** bump when emitted source changes: the stand-in cache keys on it. */
|
|
90
|
-
export const STANDIN_EMIT_VERSION = '
|
|
93
|
+
export const STANDIN_EMIT_VERSION = '12'
|
|
91
94
|
|
|
92
95
|
/** the modules every stand-in source imports. */
|
|
93
96
|
export const STANDIN_IMPORTS = ['Foundation', 'UIKit']
|
|
@@ -226,6 +229,11 @@ export function emitSwiftStandin(
|
|
|
226
229
|
family.has(parts[0]!) && parts[0] !== 'SwiftUICore' ? parts.join('.') : unqualified
|
|
227
230
|
return { name, module, local: false }
|
|
228
231
|
}
|
|
232
|
+
const nsObject = nameOf('c:objc(cs)NSObject', 'ObjectiveC.NSObject')!.name
|
|
233
|
+
const nsObjectProtocol = nameOf(
|
|
234
|
+
'c:objc(pl)NSObject',
|
|
235
|
+
'ObjectiveC.NSObjectProtocol',
|
|
236
|
+
)!.name
|
|
229
237
|
for (const graph of graphs)
|
|
230
238
|
for (const edge of graph.relationships) {
|
|
231
239
|
const source = byId.get(edge.source)
|
|
@@ -331,6 +339,47 @@ export function emitSwiftStandin(
|
|
|
331
339
|
])
|
|
332
340
|
}
|
|
333
341
|
|
|
342
|
+
// DEGRADED(swift-objc-runtime): source objc targets retain their api with inert native bodies.
|
|
343
|
+
// return values follow the imported declaration, never the app's call sites.
|
|
344
|
+
const inertValue = (type: string): string => {
|
|
345
|
+
const value = type.trim().replace(/!$/, '?')
|
|
346
|
+
if (value.endsWith('?')) return 'nil'
|
|
347
|
+
if (value === 'Void' || value === '()') return '()'
|
|
348
|
+
if (value === 'Bool') return 'false'
|
|
349
|
+
if (
|
|
350
|
+
/^(?:U?Int(?:8|16|32|64)?|Float|Double|CGFloat|TimeInterval|NSTimeInterval)$/.test(
|
|
351
|
+
value,
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
return '0'
|
|
355
|
+
if (value === 'String') return '""'
|
|
356
|
+
if (value.startsWith('[')) return value.includes(':') ? '[:]' : '[]'
|
|
357
|
+
if (value === 'Any' || value === 'AnyObject') return 'NSObject()'
|
|
358
|
+
const typeId = typeByPath.get(value.replace(`${options.module}.`, ''))
|
|
359
|
+
const symbol = typeId && byId.get(typeId)
|
|
360
|
+
if (typeId && symbol && symbol.kind.identifier === 'swift.enum') {
|
|
361
|
+
const first = membersOf(typeId).find(
|
|
362
|
+
(member) => member.kind.identifier === 'swift.enum.case',
|
|
363
|
+
)
|
|
364
|
+
if (first) return `.${leaf(first).replace(/\(.*/, '')}`
|
|
365
|
+
}
|
|
366
|
+
return `${value}()`
|
|
367
|
+
}
|
|
368
|
+
const inertBody = (symbol: GraphSymbol): string => {
|
|
369
|
+
const declaration = text(symbol)
|
|
370
|
+
if (symbol.kind.identifier === 'swift.init') {
|
|
371
|
+
if (/\bconvenience\b/.test(declaration)) return 'self.init()'
|
|
372
|
+
const parent = owner.get(symbol.identifier.precise)
|
|
373
|
+
return parent && superclass.has(parent) ? 'super.init()' : ''
|
|
374
|
+
}
|
|
375
|
+
const type =
|
|
376
|
+
symbol.kind.identifier.includes('property') ||
|
|
377
|
+
symbol.kind.identifier === 'swift.var'
|
|
378
|
+
? /:\s*(.*?)\s*(?:\{|$)/.exec(declaration)?.[1]
|
|
379
|
+
: symbol.functionSignature?.returns?.map((fragment) => fragment.spelling).join('')
|
|
380
|
+
return type ? `return ${inertValue(type)}` : ''
|
|
381
|
+
}
|
|
382
|
+
|
|
334
383
|
const lines: string[] = []
|
|
335
384
|
const owners: string[] = []
|
|
336
385
|
const omit = options.omit ?? new Map<string, string>()
|
|
@@ -426,7 +475,8 @@ export function emitSwiftStandin(
|
|
|
426
475
|
: /\bResult</.test(core)
|
|
427
476
|
? measured?.resultBody
|
|
428
477
|
: undefined
|
|
429
|
-
const body =
|
|
478
|
+
const body =
|
|
479
|
+
overloadBody ?? measured?.body ?? (options.inert ? inertBody(member) : trap(path))
|
|
430
480
|
switch (member.kind.identifier) {
|
|
431
481
|
case 'swift.property':
|
|
432
482
|
case 'swift.type.property':
|
|
@@ -452,7 +502,7 @@ export function emitSwiftStandin(
|
|
|
452
502
|
return
|
|
453
503
|
}
|
|
454
504
|
emit(
|
|
455
|
-
`${head}${computed} { get${effects} { ${body} }${settable && !effects ? ` ${parsed.nonmutating ? 'nonmutating ' : ''}set { ${measured?.set ?? trap(path)} }` : ''} }`,
|
|
505
|
+
`${head}${computed} { get${effects} { ${body} }${settable && !effects ? ` ${parsed.nonmutating ? 'nonmutating ' : ''}set { ${measured?.set ?? (options.inert ? '' : trap(path))} }` : ''} }`,
|
|
456
506
|
)
|
|
457
507
|
return
|
|
458
508
|
}
|
|
@@ -532,7 +582,18 @@ export function emitSwiftStandin(
|
|
|
532
582
|
const inner = `${indent} `
|
|
533
583
|
switch (symbol.kind.identifier) {
|
|
534
584
|
case 'swift.typealias': {
|
|
535
|
-
|
|
585
|
+
// a graph shortens an external alias to its leaf, which can shadow itself.
|
|
586
|
+
const fragments = (symbol.declarationFragments ?? []).map((fragment) =>
|
|
587
|
+
fragment.kind === 'typeIdentifier' &&
|
|
588
|
+
fragment.spelling === leaf(symbol) &&
|
|
589
|
+
fragment.preciseIdentifier?.startsWith('s:s') &&
|
|
590
|
+
fragment.preciseIdentifier !== id
|
|
591
|
+
? { ...fragment, spelling: `Swift.${fragment.spelling}` }
|
|
592
|
+
: fragment,
|
|
593
|
+
)
|
|
594
|
+
const { 1: attrs, 2: rest } = LEADING_ATTRS.exec(
|
|
595
|
+
text({ ...symbol, declarationFragments: fragments }),
|
|
596
|
+
)!
|
|
536
597
|
emit(`${indent}${attrs}public ${rest}`)
|
|
537
598
|
return
|
|
538
599
|
}
|
|
@@ -553,7 +614,8 @@ export function emitSwiftStandin(
|
|
|
553
614
|
...(conforms.get(id) ?? []).filter(
|
|
554
615
|
(name) =>
|
|
555
616
|
!ancestors.has(name) &&
|
|
556
|
-
!(parent && [...KEEP_EXTERNAL.values()].includes(name))
|
|
617
|
+
!(parent && [...KEEP_EXTERNAL.values()].includes(name)) &&
|
|
618
|
+
!(name === nsObjectProtocol && externalRoot(id) === nsObject),
|
|
557
619
|
),
|
|
558
620
|
].filter((name, at, all) => all.indexOf(name) === at)
|
|
559
621
|
const parentSymbol = superId.has(id) ? byId.get(superId.get(id)!) : undefined
|
|
@@ -646,7 +708,7 @@ export function emitSwiftStandin(
|
|
|
646
708
|
externalRoot(id) !== null
|
|
647
709
|
)
|
|
648
710
|
emit(
|
|
649
|
-
`${inner}public required init?(coder: NSCoder) { ${trap(`${path}.init(coder:)`)} }`,
|
|
711
|
+
`${inner}public required init?(coder: NSCoder) { ${options.inert ? 'return nil' : trap(`${path}.init(coder:)`)} }`,
|
|
650
712
|
)
|
|
651
713
|
emit(`${indent}}`)
|
|
652
714
|
return
|
|
@@ -674,7 +736,8 @@ export function emitSwiftStandin(
|
|
|
674
736
|
synthesizedEquality(member, list))
|
|
675
737
|
)
|
|
676
738
|
continue
|
|
677
|
-
const
|
|
739
|
+
const header = values.get(member.identifier.precise)
|
|
740
|
+
const value = header === undefined ? undefined : atWidth(header, raw)
|
|
678
741
|
if (value !== undefined && member.kind.identifier === 'swift.type.property') {
|
|
679
742
|
emit(
|
|
680
743
|
`${inner}public static let ${tick(leaf(member))} = ${shape.name}(rawValue: ${value})`,
|
|
@@ -694,10 +757,13 @@ export function emitSwiftStandin(
|
|
|
694
757
|
if (!shape)
|
|
695
758
|
return void skipped.push({ path, reason: `unparsed enum ${text(symbol)}` })
|
|
696
759
|
const members = membersOf(id)
|
|
697
|
-
const raw = rawType(members)
|
|
698
760
|
const cases = members.filter(
|
|
699
761
|
(member) => member.kind.identifier === 'swift.enum.case',
|
|
700
762
|
)
|
|
763
|
+
// payload cases use a manual RawRepresentable witness, not a raw enum.
|
|
764
|
+
const raw = cases.some((member) => text(member).includes('('))
|
|
765
|
+
? null
|
|
766
|
+
: rawType(members)
|
|
701
767
|
const missing = raw
|
|
702
768
|
? cases.filter((member) => !values.has(member.identifier.precise))
|
|
703
769
|
: []
|
|
@@ -709,14 +775,15 @@ export function emitSwiftStandin(
|
|
|
709
775
|
reason: `no header value for ${missing.map(leaf).join(', ')}`,
|
|
710
776
|
})
|
|
711
777
|
const list = inheritance(id, raw ?? undefined).filter(
|
|
712
|
-
(name) => name !== 'RawRepresentable',
|
|
778
|
+
(name) => !raw || name !== 'RawRepresentable',
|
|
713
779
|
)
|
|
714
780
|
emit(
|
|
715
781
|
`${indent}${shape.attrs}public enum ${shape.name}${shape.generics}${list.length ? `: ${list.join(', ')}` : ''}${shape.where} {`,
|
|
716
782
|
)
|
|
717
783
|
const seenValues = new Map<string, string>()
|
|
718
784
|
for (const member of cases) {
|
|
719
|
-
const
|
|
785
|
+
const header = values.get(member.identifier.precise)
|
|
786
|
+
const value = header === undefined ? undefined : atWidth(header, raw)
|
|
720
787
|
// objective-c aliases a value under a second name; swift spells
|
|
721
788
|
// the alias as a static member of the first case.
|
|
722
789
|
if (value !== undefined && seenValues.has(value)) {
|
|
@@ -814,7 +881,7 @@ export function emitSwiftStandin(
|
|
|
814
881
|
.replace(/^(?:weak |unowned )/, '')
|
|
815
882
|
.replace(/^((?:static )?)let /, '$1var ')
|
|
816
883
|
const witness = `${owner}.${leaf(member)}`
|
|
817
|
-
const body = trap(witness)
|
|
884
|
+
const body = options.inert ? inertBody(member) : trap(witness)
|
|
818
885
|
if (
|
|
819
886
|
member.kind.identifier === 'swift.property' ||
|
|
820
887
|
member.kind.identifier === 'swift.type.property'
|
|
@@ -846,6 +913,17 @@ export function emitSwiftStandin(
|
|
|
846
913
|
const init = members.find((member) => leaf(member) === 'init(rawValue:)')
|
|
847
914
|
return init ? (/rawValue:\s*([^)]+)\)/.exec(text(init))?.[1]?.trim() ?? null) : null
|
|
848
915
|
}
|
|
916
|
+
// header values are clang's for 64-bit iOS. NSUIntegerMax, NSIntegerMax
|
|
917
|
+
// and NSIntegerMin are the platform-width type's own extremes, which on
|
|
918
|
+
// wasm32 are the 32-bit ones.
|
|
919
|
+
const atWidth = (value: string, raw: string | null) =>
|
|
920
|
+
(raw === 'UInt' || raw === 'Swift.UInt') && value === '18446744073709551615'
|
|
921
|
+
? '4294967295'
|
|
922
|
+
: (raw === 'Int' || raw === 'Swift.Int') && value === '9223372036854775807'
|
|
923
|
+
? '2147483647'
|
|
924
|
+
: (raw === 'Int' || raw === 'Swift.Int') && value === '-9223372036854775808'
|
|
925
|
+
? '-2147483648'
|
|
926
|
+
: value
|
|
849
927
|
|
|
850
928
|
const emitGlobal = scoped((symbol: GraphSymbol) => {
|
|
851
929
|
const path = pathOf(symbol)
|
|
@@ -872,7 +950,7 @@ export function emitSwiftStandin(
|
|
|
872
950
|
? measured?.resultBody
|
|
873
951
|
: undefined
|
|
874
952
|
emit(
|
|
875
|
-
`${attrs}public ${spelled} { ${overloadBody ?? measured?.body ?? trap(path)} }`,
|
|
953
|
+
`${attrs}public ${spelled} { ${overloadBody ?? measured?.body ?? (options.inert ? inertBody(symbol) : trap(path))} }`,
|
|
876
954
|
)
|
|
877
955
|
} else if (kind === 'swift.var') {
|
|
878
956
|
const parsed = splitAccessors(text(symbol).replace(/^let /, 'var '))
|
|
@@ -882,7 +960,9 @@ export function emitSwiftStandin(
|
|
|
882
960
|
// as `var` with no setter.
|
|
883
961
|
const measured = behavior.get(path)
|
|
884
962
|
if (measured) used.add(path)
|
|
885
|
-
emit(
|
|
963
|
+
emit(
|
|
964
|
+
`public ${parsed.decl} { ${measured?.body ?? (options.inert ? inertBody(symbol) : trap(path))} }`,
|
|
965
|
+
)
|
|
886
966
|
} else skipped.push({ path, reason: `unhandled top-level ${kind}` })
|
|
887
967
|
})
|
|
888
968
|
|
|
@@ -929,8 +1009,9 @@ export function emitSwiftStandin(
|
|
|
929
1009
|
const imports = [...STANDIN_IMPORTS, ...(options.imports ?? [])]
|
|
930
1010
|
const banner = [
|
|
931
1011
|
`// stand-in for the binary ${options.module} module, generated from ${options.source}.`,
|
|
932
|
-
|
|
933
|
-
|
|
1012
|
+
options.inert
|
|
1013
|
+
? '// generated from public headers; native behavior is inert in peach.'
|
|
1014
|
+
: '// unconfigured bodies trap because the vendor binary cannot run in peach.',
|
|
934
1015
|
'',
|
|
935
1016
|
...imports.map((name) => `import ${name}`),
|
|
936
1017
|
'',
|