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-standins.ts
CHANGED
|
@@ -12,19 +12,30 @@
|
|
|
12
12
|
import { execFileSync, spawnSync } from 'node:child_process'
|
|
13
13
|
import { createHash } from 'node:crypto'
|
|
14
14
|
import fs from 'node:fs'
|
|
15
|
-
import os from 'node:os'
|
|
16
15
|
import path from 'node:path'
|
|
17
16
|
import { getCliVersion } from './cli-version'
|
|
17
|
+
import { cacheDir as peachCacheDir, swiftPackageCacheOptions } from './home-paths'
|
|
18
|
+
import { isRecord } from './is-record'
|
|
19
|
+
import { outputLockStdio } from './output-lock-stdio'
|
|
18
20
|
import { parsePlistSource } from './plist'
|
|
19
21
|
import {
|
|
20
22
|
swiftDistributionAt,
|
|
23
|
+
swiftImportNames,
|
|
21
24
|
swiftMacroPlugins,
|
|
22
25
|
swiftModuleDigest,
|
|
23
26
|
swiftModuleSdk,
|
|
24
27
|
swiftPeachModules,
|
|
25
28
|
swiftSdkPaths,
|
|
26
29
|
} from './swift-compile'
|
|
27
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
IOS_SDK_ID,
|
|
32
|
+
NATIVE_GRAPH_ID,
|
|
33
|
+
OBJC_IMPORT_VERSION,
|
|
34
|
+
nativeInterfaceView,
|
|
35
|
+
objcView,
|
|
36
|
+
type ObjcSurface,
|
|
37
|
+
} from './swift-objc-standin'
|
|
38
|
+
import { swiftTargetModuleName } from './swift-package'
|
|
28
39
|
import {
|
|
29
40
|
STANDIN_EMIT_VERSION,
|
|
30
41
|
STANDIN_IMPORTS,
|
|
@@ -33,11 +44,13 @@ import {
|
|
|
33
44
|
type SwiftStandinBehaviors,
|
|
34
45
|
type StandinBehavior,
|
|
35
46
|
} from './swift-standin-emit'
|
|
47
|
+
import { swiftEnvironment, swiftSdkOptions } from './swift-toolchain'
|
|
36
48
|
import type { SwiftPackageBinary } from './swift-packages'
|
|
37
49
|
|
|
38
50
|
const TRIPLE = 'wasm32-unknown-wasip1'
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
const WASI_EMULATION = ['_WASI_EMULATED_SIGNAL', '_WASI_EMULATED_MMAN']
|
|
52
|
+
// every stand-in artifact and temporary workspace belongs to peach.
|
|
53
|
+
const cacheDir = path.join(peachCacheDir(), 'swift-standins')
|
|
41
54
|
// each binary stand-in is a target under its module's directory here.
|
|
42
55
|
export const SWIFT_STANDINS_DIR = '__standins__'
|
|
43
56
|
|
|
@@ -48,6 +61,8 @@ export interface SwiftStandin {
|
|
|
48
61
|
target: string
|
|
49
62
|
/** the target's files, relative to its directory. */
|
|
50
63
|
files: Record<string, string>
|
|
64
|
+
/** other generated modules named by this target's public headers. */
|
|
65
|
+
dependencies?: string[]
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
export interface SwiftStandins {
|
|
@@ -61,12 +76,20 @@ interface Toolchain {
|
|
|
61
76
|
toolchainBin: string
|
|
62
77
|
/** the toolchain release, read once where the toolchain is resolved. */
|
|
63
78
|
release: string
|
|
79
|
+
/** modules generated earlier in this dependency closure. */
|
|
80
|
+
standinModules?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface NativeFrameworkInputs {
|
|
84
|
+
dirs: string[]
|
|
85
|
+
digest: string
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
function exec(file: string, args: string[], cwd?: string): string {
|
|
67
89
|
return execFileSync(file, args, {
|
|
68
90
|
cwd,
|
|
69
91
|
encoding: 'utf8',
|
|
92
|
+
env: swiftEnvironment(),
|
|
70
93
|
maxBuffer: 256 * 1024 * 1024,
|
|
71
94
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
72
95
|
})
|
|
@@ -94,14 +117,20 @@ const digests = new Map<string, { stamp: string; digest: string }>()
|
|
|
94
117
|
|
|
95
118
|
function treeDigest(root: string): string {
|
|
96
119
|
const names: string[] = []
|
|
120
|
+
const ancestors = new Set<string>()
|
|
97
121
|
const walk = (dir: string) => {
|
|
122
|
+
const resolved = fs.realpathSync(dir)
|
|
123
|
+
if (ancestors.has(resolved)) throw new Error(`cyclic header directory: ${dir}`)
|
|
124
|
+
ancestors.add(resolved)
|
|
98
125
|
for (const entry of fs
|
|
99
126
|
.readdirSync(dir, { withFileTypes: true })
|
|
100
127
|
.sort((left, right) => (left.name < right.name ? -1 : 1))) {
|
|
101
128
|
const full = path.join(dir, entry.name)
|
|
102
|
-
|
|
103
|
-
|
|
129
|
+
const stat = entry.isSymbolicLink() ? fs.statSync(full) : entry
|
|
130
|
+
if (stat.isDirectory()) walk(full)
|
|
131
|
+
else if (stat.isFile()) names.push(path.relative(root, full))
|
|
104
132
|
}
|
|
133
|
+
ancestors.delete(resolved)
|
|
105
134
|
}
|
|
106
135
|
walk(root)
|
|
107
136
|
const stamp = names
|
|
@@ -217,8 +246,9 @@ const files = (dir: string, test: (name: string) => boolean): string[] =>
|
|
|
217
246
|
/** the module surfaces an iOS simulator slice describes. */
|
|
218
247
|
type Surface =
|
|
219
248
|
| { kind: 'swift'; module: string; interface: string }
|
|
249
|
+
| (ObjcSurface & { kind: 'mixed'; interface: string })
|
|
220
250
|
| { kind: 'c'; module: string; modulemap: string; headers: string }
|
|
221
|
-
| { kind: 'objc'
|
|
251
|
+
| (ObjcSurface & { kind: 'objc' })
|
|
222
252
|
|
|
223
253
|
const OBJC = /^\s*(?:@interface|@protocol|@class|@import|#import)\b/m
|
|
224
254
|
|
|
@@ -257,15 +287,27 @@ function surfaces(xcframeworkDir: string): Surface[] {
|
|
|
257
287
|
)
|
|
258
288
|
const chosen =
|
|
259
289
|
interfaces.find((name) => name.startsWith('arm64-')) ?? interfaces[0] ?? null
|
|
290
|
+
const modulemap = path.join(framework, 'Modules', 'module.modulemap')
|
|
260
291
|
if (chosen) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
292
|
+
const file = path.join(swiftmodule, chosen)
|
|
293
|
+
const underlying = [
|
|
294
|
+
...fs
|
|
295
|
+
.readFileSync(file, 'utf8')
|
|
296
|
+
.matchAll(/^(?:@_exported[ \t]+)?import[ \t]+(\w+)[ \t]*$/gm),
|
|
297
|
+
].some((match) => match[1] === module)
|
|
298
|
+
if (underlying) {
|
|
299
|
+
if (!fs.existsSync(modulemap))
|
|
300
|
+
throw new Error(`${module} imports its underlying module without a modulemap`)
|
|
301
|
+
found.push({
|
|
302
|
+
kind: 'mixed',
|
|
303
|
+
module,
|
|
304
|
+
interface: file,
|
|
305
|
+
modulemap,
|
|
306
|
+
headers: path.join(framework, 'Headers'),
|
|
307
|
+
})
|
|
308
|
+
} else found.push({ kind: 'swift', module, interface: file })
|
|
266
309
|
continue
|
|
267
310
|
}
|
|
268
|
-
const modulemap = path.join(framework, 'Modules', 'module.modulemap')
|
|
269
311
|
if (fs.existsSync(modulemap))
|
|
270
312
|
found.push(headerSurface(module, modulemap, path.join(framework, 'Headers')))
|
|
271
313
|
}
|
|
@@ -281,6 +323,14 @@ function surfaces(xcframeworkDir: string): Surface[] {
|
|
|
281
323
|
return found
|
|
282
324
|
}
|
|
283
325
|
|
|
326
|
+
export function standinsNeedNativeSdk(binaries: readonly SwiftPackageBinary[]): boolean {
|
|
327
|
+
return binaries.some(
|
|
328
|
+
(binary) =>
|
|
329
|
+
binary.publicHeaders === undefined &&
|
|
330
|
+
surfaces(xcframework(binary).dir).some((surface) => surface.kind === 'mixed'),
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
284
334
|
// the peach modules an interface imports, compiled for wasm: a distribution
|
|
285
335
|
// ships them; a checkout builds the ones named into its own build directory,
|
|
286
336
|
// with the flags scripts/build.sh uses, so the two share one build.
|
|
@@ -305,10 +355,12 @@ function peachModuleDirs(toolchain: Toolchain, imports: string[]): string[] {
|
|
|
305
355
|
path.join(toolchainBin, 'swift'),
|
|
306
356
|
[
|
|
307
357
|
'build',
|
|
358
|
+
...swiftPackageCacheOptions(),
|
|
308
359
|
'--build-system',
|
|
309
360
|
'native',
|
|
310
361
|
'-j',
|
|
311
362
|
'4',
|
|
363
|
+
...swiftSdkOptions(),
|
|
312
364
|
'--swift-sdk',
|
|
313
365
|
swiftModuleSdk(moduleRoot).wasmSdkId,
|
|
314
366
|
'-c',
|
|
@@ -359,11 +411,14 @@ export function swiftInterfaceForWasm(text: string): string {
|
|
|
359
411
|
? rooted.replace(/^(?=import[ \t])/m, 'import Foundation\n')
|
|
360
412
|
: rooted
|
|
361
413
|
return foundation
|
|
362
|
-
.replace(
|
|
363
|
-
flags
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
414
|
+
.replace(
|
|
415
|
+
/^(\/\/ swift-module-flags:.*)$/m,
|
|
416
|
+
(flags) =>
|
|
417
|
+
flags
|
|
418
|
+
.replace(/-target \S+/, `-target ${TRIPLE}`)
|
|
419
|
+
.replace(/\s-enable-objc-interop\b/, '')
|
|
420
|
+
.replace(/\s-target-variant \S+/, '') +
|
|
421
|
+
` ${WASI_EMULATION.map((name) => `-Xcc -D${name}`).join(' ')}`,
|
|
367
422
|
)
|
|
368
423
|
.replace(
|
|
369
424
|
/@(?:objc(?:\s*\([^()]*\))?|objcMembers|nonobjc|IBOutlet|IBAction|IBInspectable|IBDesignable|_objcRuntimeName\([^()]*\))(?![\w(])/g,
|
|
@@ -377,17 +432,14 @@ function swiftStandin(
|
|
|
377
432
|
toolchain: Toolchain,
|
|
378
433
|
work: string,
|
|
379
434
|
behavior: ReadonlyMap<string, StandinBehavior>,
|
|
435
|
+
siblings: ReadonlySet<string>,
|
|
380
436
|
): SwiftStandin {
|
|
381
437
|
const text = swiftInterfaceForWasm(fs.readFileSync(surface.interface, 'utf8'))
|
|
382
438
|
const search = path.join(work, 'interfaces')
|
|
383
439
|
const moduleDir = path.join(search, `${surface.module}.swiftmodule`)
|
|
384
440
|
fs.mkdirSync(moduleDir, { recursive: true })
|
|
385
441
|
fs.writeFileSync(path.join(moduleDir, `${TRIPLE}.swiftinterface`), text)
|
|
386
|
-
const imports =
|
|
387
|
-
...text.matchAll(
|
|
388
|
-
/^(?:@_exported[ \t]+)?import[ \t]+(?:(?:struct|class|enum|protocol|typealias|func|var|let)[ \t]+)?(\w+)/gm,
|
|
389
|
-
),
|
|
390
|
-
].map((match) => match[1]!)
|
|
442
|
+
const imports = swiftImportNames(text)
|
|
391
443
|
const wasm = wasmFlags(toolchain, imports)
|
|
392
444
|
const out = path.join(work, 'graphs')
|
|
393
445
|
fs.mkdirSync(out, { recursive: true })
|
|
@@ -418,6 +470,37 @@ function swiftStandin(
|
|
|
418
470
|
toolchain,
|
|
419
471
|
work,
|
|
420
472
|
behavior,
|
|
473
|
+
[...new Set(imports)].filter((module) => siblings.has(module)),
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function mixedStandin(
|
|
478
|
+
surface: Extract<Surface, { kind: 'mixed' }>,
|
|
479
|
+
binary: SwiftPackageBinary,
|
|
480
|
+
toolchain: Toolchain,
|
|
481
|
+
work: string,
|
|
482
|
+
behavior: ReadonlyMap<string, StandinBehavior>,
|
|
483
|
+
frameworkDirs: readonly string[],
|
|
484
|
+
siblings: ReadonlySet<string>,
|
|
485
|
+
): SwiftStandin {
|
|
486
|
+
const view = nativeInterfaceView(surface, frameworkDirs, work)
|
|
487
|
+
const provided = new Set(swiftPeachModules(toolchain.moduleRoot))
|
|
488
|
+
const imports = [
|
|
489
|
+
...new Set(swiftImportNames(fs.readFileSync(surface.interface, 'utf8'))),
|
|
490
|
+
].filter(
|
|
491
|
+
(module) =>
|
|
492
|
+
module !== surface.module && (provided.has(module) || siblings.has(module)),
|
|
493
|
+
)
|
|
494
|
+
return graphStandin(
|
|
495
|
+
surface.module,
|
|
496
|
+
view.graphs,
|
|
497
|
+
view.values,
|
|
498
|
+
wasmFlags(toolchain, imports),
|
|
499
|
+
binary,
|
|
500
|
+
toolchain,
|
|
501
|
+
work,
|
|
502
|
+
behavior,
|
|
503
|
+
imports,
|
|
421
504
|
)
|
|
422
505
|
}
|
|
423
506
|
|
|
@@ -434,7 +517,10 @@ function wasmFlags(toolchain: Toolchain, imports: string[]): string[] {
|
|
|
434
517
|
sysroot,
|
|
435
518
|
'-resource-dir',
|
|
436
519
|
resources,
|
|
520
|
+
// the sdk toolset's emulation defines, which raw swiftc does not read
|
|
521
|
+
...WASI_EMULATION.flatMap((name) => ['-Xcc', `-D${name}`]),
|
|
437
522
|
...peachModuleDirs(toolchain, imports).flatMap((dir) => ['-I', dir]),
|
|
523
|
+
...(toolchain.standinModules ? ['-I', toolchain.standinModules] : []),
|
|
438
524
|
]
|
|
439
525
|
}
|
|
440
526
|
|
|
@@ -462,6 +548,9 @@ function objcStandin(
|
|
|
462
548
|
toolchain,
|
|
463
549
|
work,
|
|
464
550
|
behavior,
|
|
551
|
+
binary.headerDependencies?.map((dependency) =>
|
|
552
|
+
swiftTargetModuleName(dependency.module),
|
|
553
|
+
),
|
|
465
554
|
)
|
|
466
555
|
}
|
|
467
556
|
|
|
@@ -477,6 +566,7 @@ function graphStandin(
|
|
|
477
566
|
toolchain: Toolchain,
|
|
478
567
|
work: string,
|
|
479
568
|
behavior: ReadonlyMap<string, StandinBehavior>,
|
|
569
|
+
imports: string[] = [],
|
|
480
570
|
): SwiftStandin {
|
|
481
571
|
const paths = new Set(
|
|
482
572
|
graphs.flatMap((graph) =>
|
|
@@ -500,6 +590,9 @@ function graphStandin(
|
|
|
500
590
|
behavior,
|
|
501
591
|
omit,
|
|
502
592
|
plain,
|
|
593
|
+
inert: binary.publicHeaders !== undefined,
|
|
594
|
+
imports,
|
|
595
|
+
family: imports,
|
|
503
596
|
})
|
|
504
597
|
for (const entry of skipped) {
|
|
505
598
|
if (behavior.has(entry.path))
|
|
@@ -510,23 +603,28 @@ function graphStandin(
|
|
|
510
603
|
fs.writeFileSync(file, source)
|
|
511
604
|
const check = spawnSync(
|
|
512
605
|
path.join(toolchain.toolchainBin, 'swiftc'),
|
|
606
|
+
// emitting the module runs the diagnostics a typecheck skips, such as
|
|
607
|
+
// literal overflow, which the app's build of the stand-in reaches
|
|
513
608
|
[
|
|
514
|
-
'-
|
|
609
|
+
'-emit-module',
|
|
515
610
|
'-parse-as-library',
|
|
516
611
|
'-swift-version',
|
|
517
612
|
'5',
|
|
518
613
|
'-module-name',
|
|
519
614
|
module,
|
|
520
615
|
...wasm,
|
|
616
|
+
'-emit-module-path',
|
|
617
|
+
path.join(work, `${module}.swiftmodule`),
|
|
521
618
|
file,
|
|
522
619
|
],
|
|
523
|
-
{ encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 },
|
|
620
|
+
{ encoding: 'utf8', env: swiftEnvironment(), maxBuffer: 256 * 1024 * 1024 },
|
|
524
621
|
)
|
|
525
622
|
if (check.status === 0)
|
|
526
623
|
return {
|
|
527
624
|
module,
|
|
528
625
|
target: binary.target,
|
|
529
626
|
files: { [`${module}.swift`]: source },
|
|
627
|
+
...(imports.length ? { dependencies: imports } : {}),
|
|
530
628
|
}
|
|
531
629
|
const before = omit.size + plain.size
|
|
532
630
|
const elsewhere: string[] = []
|
|
@@ -901,6 +999,7 @@ export function standinLibrary(
|
|
|
901
999
|
binary: SwiftPackageBinary,
|
|
902
1000
|
searchRoot: string,
|
|
903
1001
|
): string | null {
|
|
1002
|
+
if (binary.publicHeaders !== undefined) return null
|
|
904
1003
|
const dir = xcframework(binary).dir
|
|
905
1004
|
const library = simulatorLibraryName(dir)
|
|
906
1005
|
const supplied = path.join(dir, RUST_TARGET)
|
|
@@ -922,14 +1021,18 @@ export function standinLibrary(
|
|
|
922
1021
|
[
|
|
923
1022
|
'build',
|
|
924
1023
|
'--release',
|
|
1024
|
+
'--locked',
|
|
925
1025
|
'--target',
|
|
926
1026
|
RUST_TARGET,
|
|
927
1027
|
'--message-format=json-render-diagnostics',
|
|
928
1028
|
'--manifest-path',
|
|
929
1029
|
manifest,
|
|
1030
|
+
'--target-dir',
|
|
1031
|
+
path.join(cacheDir, 'cargo', key(manifest)),
|
|
930
1032
|
],
|
|
931
1033
|
{
|
|
932
1034
|
encoding: 'utf8',
|
|
1035
|
+
env: { ...process.env, CARGO_HOME: path.join(cacheDir, 'cargo-home') },
|
|
933
1036
|
maxBuffer: 256 * 1024 * 1024,
|
|
934
1037
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
935
1038
|
},
|
|
@@ -969,30 +1072,91 @@ export function standinLibrary(
|
|
|
969
1072
|
throw new Error(`cargo built ${crate} for ${RUST_TARGET} but reported no ${library}`)
|
|
970
1073
|
}
|
|
971
1074
|
|
|
972
|
-
// source preflight
|
|
973
|
-
//
|
|
974
|
-
function
|
|
975
|
-
|
|
1075
|
+
// source preflight admits the complete closure once, including ignored local
|
|
1076
|
+
// frameworks. target generation and prerequisite modules stay in that one job.
|
|
1077
|
+
function sourceStandins(
|
|
1078
|
+
binaries: readonly SwiftPackageBinary[],
|
|
976
1079
|
toolchain: Toolchain,
|
|
977
1080
|
provided: ReadonlySet<string>,
|
|
978
1081
|
behavior: SwiftStandinBehaviors,
|
|
979
|
-
):
|
|
1082
|
+
): SwiftStandins {
|
|
980
1083
|
const repo = path.resolve(toolchain.moduleRoot, '../..')
|
|
981
|
-
|
|
1084
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
1085
|
+
const generation = standinGenerationInputs(binaries, toolchain, behavior)
|
|
1086
|
+
const cached = path.join(
|
|
1087
|
+
cacheDir,
|
|
1088
|
+
'closures',
|
|
1089
|
+
`${key(generation, JSON.stringify([...provided].sort()))}.json`,
|
|
1090
|
+
)
|
|
1091
|
+
const targets = new Set(binaries.map((binary) => binary.target))
|
|
1092
|
+
if (fs.existsSync(cached))
|
|
1093
|
+
return readStandinResults(
|
|
1094
|
+
JSON.parse(fs.readFileSync(cached, 'utf8')),
|
|
1095
|
+
generation,
|
|
1096
|
+
targets,
|
|
1097
|
+
)
|
|
1098
|
+
const work = fs.mkdtempSync(path.join(cacheDir, 'request-'))
|
|
982
1099
|
try {
|
|
983
|
-
const artifact =
|
|
984
|
-
|
|
985
|
-
const
|
|
1100
|
+
const artifact = path.join(work, 'artifacts')
|
|
1101
|
+
fs.mkdirSync(artifact)
|
|
1102
|
+
const local: SwiftPackageBinary[] = []
|
|
1103
|
+
const gaps = new Map<string, string>()
|
|
1104
|
+
const headers = new Map<string, string>()
|
|
1105
|
+
for (const [index, binary] of binaries.entries()) {
|
|
1106
|
+
try {
|
|
1107
|
+
if (binary.publicHeaders !== undefined) {
|
|
1108
|
+
const copyHeader = (relative: string) => {
|
|
1109
|
+
const source = fs.realpathSync(path.join(binary.dir, relative))
|
|
1110
|
+
let destination = headers.get(source)
|
|
1111
|
+
if (destination === undefined) {
|
|
1112
|
+
destination = `headers/${headers.size}`
|
|
1113
|
+
fs.cpSync(source, path.join(artifact, destination), {
|
|
1114
|
+
recursive: true,
|
|
1115
|
+
dereference: true,
|
|
1116
|
+
})
|
|
1117
|
+
headers.set(source, destination)
|
|
1118
|
+
}
|
|
1119
|
+
return destination
|
|
1120
|
+
}
|
|
1121
|
+
local.push({
|
|
1122
|
+
...binary,
|
|
1123
|
+
dir: artifact,
|
|
1124
|
+
url: null,
|
|
1125
|
+
checksum: null,
|
|
1126
|
+
publicHeaders: copyHeader(binary.publicHeaders),
|
|
1127
|
+
headerDependencies: (binary.headerDependencies ?? []).map((dependency) => ({
|
|
1128
|
+
module: dependency.module,
|
|
1129
|
+
path: copyHeader(dependency.path),
|
|
1130
|
+
})),
|
|
1131
|
+
})
|
|
1132
|
+
} else {
|
|
1133
|
+
const resolved = xcframework(binary)
|
|
1134
|
+
const dir = path.join(artifact, String(index), path.basename(resolved.dir))
|
|
1135
|
+
fs.cpSync(resolved.dir, dir, { recursive: true, dereference: true })
|
|
1136
|
+
local.push({ ...binary, dir, url: null, checksum: null })
|
|
1137
|
+
}
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
gaps.set(binary.target, error instanceof Error ? error.message : String(error))
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (local.length === 0) return { standins: [], gaps }
|
|
1143
|
+
const preparedGeneration = standinGenerationInputs(local, toolchain, behavior)
|
|
1144
|
+
if (generation !== standinGenerationInputs(binaries, toolchain, behavior))
|
|
1145
|
+
throw new Error('Swift stand-in inputs changed during preparation')
|
|
986
1146
|
const request = path.join(work, 'request.json')
|
|
987
1147
|
const delivery = path.join(work, 'delivery')
|
|
988
1148
|
fs.writeFileSync(
|
|
989
1149
|
request,
|
|
990
1150
|
JSON.stringify({
|
|
991
|
-
|
|
1151
|
+
binaries: local.map((binary) => ({
|
|
1152
|
+
...binary,
|
|
1153
|
+
dir: path.relative(artifact, binary.dir) || '.',
|
|
1154
|
+
})),
|
|
992
1155
|
provided: [...provided],
|
|
993
1156
|
behavior,
|
|
994
|
-
generation,
|
|
1157
|
+
generation: preparedGeneration,
|
|
995
1158
|
release: toolchain.release,
|
|
1159
|
+
native: standinsNeedNativeSdk(local),
|
|
996
1160
|
}),
|
|
997
1161
|
)
|
|
998
1162
|
const build = spawnSync(
|
|
@@ -1002,56 +1166,104 @@ function sourceBinaryStandins(
|
|
|
1002
1166
|
'--request',
|
|
1003
1167
|
request,
|
|
1004
1168
|
'--artifact',
|
|
1005
|
-
artifact
|
|
1169
|
+
artifact,
|
|
1006
1170
|
'--delivery-dir',
|
|
1007
1171
|
delivery,
|
|
1008
1172
|
],
|
|
1009
|
-
{
|
|
1173
|
+
{
|
|
1174
|
+
cwd: repo,
|
|
1175
|
+
encoding: 'utf8',
|
|
1176
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
1177
|
+
stdio: outputLockStdio(['ignore', 'pipe', 'pipe']),
|
|
1178
|
+
},
|
|
1010
1179
|
)
|
|
1011
1180
|
if (build.stdout) process.stdout.write(build.stdout)
|
|
1012
1181
|
if (build.stderr) process.stderr.write(build.stderr)
|
|
1013
1182
|
if (build.error || build.status !== 0)
|
|
1014
1183
|
throw new Error(
|
|
1015
|
-
`source stand-in build for ${binary.target} failed (${build.error?.message ?? build.signal ?? `exit ${build.status}`}): ${build.stderr?.trim().slice(-4000) || build.stdout?.trim().slice(-4000) || 'builder returned no diagnostic'}`,
|
|
1184
|
+
`source stand-in build for ${local.map((binary) => binary.target).join(', ')} failed (${build.error?.message ?? build.signal ?? `exit ${build.status}`}): ${build.stderr?.trim().slice(-4000) || build.stdout?.trim().slice(-4000) || 'builder returned no diagnostic'}`,
|
|
1016
1185
|
)
|
|
1017
1186
|
const output = path.join(delivery, 'standins.json')
|
|
1018
1187
|
if (!fs.existsSync(output))
|
|
1019
|
-
throw new Error(
|
|
1020
|
-
`source stand-in builder returned no standins.json for ${binary.target}`,
|
|
1021
|
-
)
|
|
1188
|
+
throw new Error('source stand-in builder returned no standins.json')
|
|
1022
1189
|
const receipt: unknown = JSON.parse(fs.readFileSync(output, 'utf8'))
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
receipt.generation !== generation ||
|
|
1028
|
-
!('result' in receipt)
|
|
1029
|
-
)
|
|
1030
|
-
throw new Error('Swift stand-in builder source differs from the caller')
|
|
1031
|
-
const result = receipt.result
|
|
1032
|
-
if (typeof result === 'string') throw new Error(result)
|
|
1033
|
-
if (
|
|
1034
|
-
!Array.isArray(result) ||
|
|
1035
|
-
!result.every(
|
|
1036
|
-
(item) =>
|
|
1037
|
-
item &&
|
|
1038
|
-
typeof item === 'object' &&
|
|
1039
|
-
typeof item.module === 'string' &&
|
|
1040
|
-
typeof item.target === 'string' &&
|
|
1041
|
-
item.target === binary.target &&
|
|
1042
|
-
item.files &&
|
|
1043
|
-
typeof item.files === 'object' &&
|
|
1044
|
-
!Array.isArray(item.files) &&
|
|
1045
|
-
Object.values(item.files).every((text) => typeof text === 'string'),
|
|
1046
|
-
)
|
|
1190
|
+
const result = readStandinResults(
|
|
1191
|
+
receipt,
|
|
1192
|
+
preparedGeneration,
|
|
1193
|
+
new Set(local.map((binary) => binary.target)),
|
|
1047
1194
|
)
|
|
1048
|
-
|
|
1195
|
+
if (generation !== standinGenerationInputs(binaries, toolchain, behavior))
|
|
1196
|
+
throw new Error('Swift stand-in inputs changed during generation')
|
|
1197
|
+
for (const [target, reason] of gaps) result.gaps.set(target, reason)
|
|
1198
|
+
// failures retry against the per-target generation cache on the builder.
|
|
1199
|
+
// only a complete successful closure skips the next admission entirely.
|
|
1200
|
+
if (result.gaps.size === 0 && isRecord(receipt)) {
|
|
1201
|
+
fs.mkdirSync(path.dirname(cached), { recursive: true })
|
|
1202
|
+
const pending = `${cached}.${process.pid}.tmp`
|
|
1203
|
+
fs.writeFileSync(pending, JSON.stringify({ ...receipt, generation }))
|
|
1204
|
+
fs.renameSync(pending, cached)
|
|
1205
|
+
}
|
|
1049
1206
|
return result
|
|
1050
1207
|
} finally {
|
|
1051
1208
|
fs.rmSync(work, { recursive: true, force: true })
|
|
1052
1209
|
}
|
|
1053
1210
|
}
|
|
1054
1211
|
|
|
1212
|
+
function readStandinResults(
|
|
1213
|
+
receipt: unknown,
|
|
1214
|
+
generation: string,
|
|
1215
|
+
targets: ReadonlySet<string>,
|
|
1216
|
+
): SwiftStandins {
|
|
1217
|
+
if (!isRecord(receipt) || receipt.generation !== generation)
|
|
1218
|
+
throw new Error('Swift stand-in builder source differs from the caller')
|
|
1219
|
+
if (!Array.isArray(receipt.results) || receipt.results.length !== targets.size)
|
|
1220
|
+
throw new Error('invalid Swift stand-in builder results')
|
|
1221
|
+
const standins: SwiftStandin[] = []
|
|
1222
|
+
const gaps = new Map<string, string>()
|
|
1223
|
+
const seen = new Set<string>()
|
|
1224
|
+
for (const entry of receipt.results) {
|
|
1225
|
+
if (
|
|
1226
|
+
!isRecord(entry) ||
|
|
1227
|
+
typeof entry.target !== 'string' ||
|
|
1228
|
+
!targets.has(entry.target) ||
|
|
1229
|
+
seen.has(entry.target)
|
|
1230
|
+
)
|
|
1231
|
+
throw new Error('invalid Swift stand-in builder target')
|
|
1232
|
+
seen.add(entry.target)
|
|
1233
|
+
if (typeof entry.result === 'string') {
|
|
1234
|
+
gaps.set(entry.target, entry.result)
|
|
1235
|
+
continue
|
|
1236
|
+
}
|
|
1237
|
+
if (!Array.isArray(entry.result))
|
|
1238
|
+
throw new Error('invalid Swift stand-in builder result')
|
|
1239
|
+
for (const item of entry.result) {
|
|
1240
|
+
if (
|
|
1241
|
+
!isRecord(item) ||
|
|
1242
|
+
typeof item.module !== 'string' ||
|
|
1243
|
+
item.target !== entry.target ||
|
|
1244
|
+
!isRecord(item.files) ||
|
|
1245
|
+
!Object.values(item.files).every((text) => typeof text === 'string') ||
|
|
1246
|
+
(item.dependencies !== undefined &&
|
|
1247
|
+
(!Array.isArray(item.dependencies) ||
|
|
1248
|
+
!item.dependencies.every((name) => typeof name === 'string')))
|
|
1249
|
+
)
|
|
1250
|
+
throw new Error('invalid Swift stand-in builder result')
|
|
1251
|
+
const files: Record<string, string> = {}
|
|
1252
|
+
for (const [name, text] of Object.entries(item.files)) {
|
|
1253
|
+
if (typeof text !== 'string') throw new Error('invalid Swift stand-in file')
|
|
1254
|
+
files[name] = text
|
|
1255
|
+
}
|
|
1256
|
+
standins.push({
|
|
1257
|
+
module: item.module,
|
|
1258
|
+
target: entry.target,
|
|
1259
|
+
files,
|
|
1260
|
+
...(item.dependencies === undefined ? {} : { dependencies: item.dependencies }),
|
|
1261
|
+
})
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return { standins, gaps }
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1055
1267
|
const built = new Map<string, SwiftStandin[] | string>()
|
|
1056
1268
|
|
|
1057
1269
|
/** what stand-ins are generated from besides the surfaces' files: the
|
|
@@ -1066,20 +1278,23 @@ export function standinGenerationInputs(
|
|
|
1066
1278
|
const { moduleRoot, release } = toolchain
|
|
1067
1279
|
const sdk = swiftModuleSdk(moduleRoot)
|
|
1068
1280
|
const parts = [
|
|
1069
|
-
'standin/
|
|
1281
|
+
'standin/v9',
|
|
1070
1282
|
release,
|
|
1071
1283
|
sdk.wasmSdkId,
|
|
1072
1284
|
sdk.wasmSdkChecksum,
|
|
1073
1285
|
swiftModuleDigest(moduleRoot).toString('hex'),
|
|
1074
1286
|
`emit/${STANDIN_EMIT_VERSION}`,
|
|
1075
1287
|
`ios-sdk/${IOS_SDK_ID}`,
|
|
1288
|
+
`objc-import/${OBJC_IMPORT_VERSION}`,
|
|
1076
1289
|
// a released cli can change the emitter without anyone bumping the constant
|
|
1077
1290
|
`cli/${getCliVersion()}`,
|
|
1078
1291
|
]
|
|
1079
1292
|
const modules = new Set(
|
|
1080
1293
|
binaries.flatMap((binary) => {
|
|
1081
1294
|
try {
|
|
1082
|
-
return
|
|
1295
|
+
return binary.publicHeaders !== undefined
|
|
1296
|
+
? [swiftTargetModuleName(binary.target)]
|
|
1297
|
+
: surfaces(xcframework(binary).dir).map((surface) => surface.module)
|
|
1083
1298
|
} catch {
|
|
1084
1299
|
return []
|
|
1085
1300
|
}
|
|
@@ -1100,13 +1315,36 @@ export function standinGenerationInputs(
|
|
|
1100
1315
|
),
|
|
1101
1316
|
)
|
|
1102
1317
|
for (const binary of binaries) {
|
|
1318
|
+
if (
|
|
1319
|
+
binary.publicHeaders !== undefined &&
|
|
1320
|
+
swiftTargetModuleName(binary.target) !== binary.target
|
|
1321
|
+
)
|
|
1322
|
+
parts.push(`module/${swiftTargetModuleName(binary.target)}`)
|
|
1103
1323
|
try {
|
|
1324
|
+
if (
|
|
1325
|
+
binary.publicHeaders === undefined &&
|
|
1326
|
+
surfaces(xcframework(binary).dir).some((surface) => surface.kind === 'swift')
|
|
1327
|
+
)
|
|
1328
|
+
parts.push(`interface-emulation/${WASI_EMULATION.join(',')}`)
|
|
1329
|
+
if (
|
|
1330
|
+
binary.publicHeaders === undefined &&
|
|
1331
|
+
surfaces(xcframework(binary).dir).some((surface) => surface.kind === 'mixed')
|
|
1332
|
+
)
|
|
1333
|
+
parts.push(`native-graph/${NATIVE_GRAPH_ID}`)
|
|
1104
1334
|
parts.push(
|
|
1105
1335
|
binary.target,
|
|
1106
1336
|
binary.source,
|
|
1107
1337
|
binary.url ?? '',
|
|
1108
1338
|
binary.checksum ?? '',
|
|
1109
|
-
|
|
1339
|
+
binary.publicHeaders !== undefined
|
|
1340
|
+
? [
|
|
1341
|
+
treeDigest(path.join(binary.dir, binary.publicHeaders)),
|
|
1342
|
+
...(binary.headerDependencies ?? []).map(
|
|
1343
|
+
(dependency) =>
|
|
1344
|
+
`${swiftTargetModuleName(dependency.module)}\0${treeDigest(path.join(binary.dir, dependency.path))}`,
|
|
1345
|
+
),
|
|
1346
|
+
].join('\0')
|
|
1347
|
+
: xcframework(binary).digest,
|
|
1110
1348
|
)
|
|
1111
1349
|
} catch {
|
|
1112
1350
|
// unreadable here fails again below, where it becomes a gap
|
|
@@ -1132,6 +1370,8 @@ function binaryStandins(
|
|
|
1132
1370
|
toolchain: Toolchain,
|
|
1133
1371
|
provided: ReadonlySet<string>,
|
|
1134
1372
|
behavior: SwiftStandinBehaviors,
|
|
1373
|
+
frameworks: NativeFrameworkInputs,
|
|
1374
|
+
siblings: ReadonlySet<string>,
|
|
1135
1375
|
): SwiftStandin[] | string {
|
|
1136
1376
|
if (provided.has(binary.target)) return []
|
|
1137
1377
|
// a local artifact a script downloads or builds is absent from a fresh
|
|
@@ -1139,17 +1379,56 @@ function binaryStandins(
|
|
|
1139
1379
|
// calls, so peach names the path and asks for the artifact
|
|
1140
1380
|
if (!binary.url && !fs.existsSync(binary.dir))
|
|
1141
1381
|
return `${path.relative(process.cwd(), binary.dir) || binary.dir} is not on disk, and Xcode fails without it too; run the script that downloads or builds it, then run again`
|
|
1382
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
1383
|
+
const work = fs.mkdtempSync(path.join(cacheDir, 'work-'))
|
|
1142
1384
|
let all: Surface[]
|
|
1143
1385
|
let digest: string
|
|
1144
1386
|
try {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1387
|
+
if (binary.publicHeaders !== undefined) {
|
|
1388
|
+
const headers = path.join(binary.dir, binary.publicHeaders)
|
|
1389
|
+
const module = swiftTargetModuleName(binary.target)
|
|
1390
|
+
const modulemap = path.join(work, 'module.modulemap')
|
|
1391
|
+
const supplied = path.join(headers, 'module.modulemap')
|
|
1392
|
+
const roots = files(headers, (name) => name.endsWith('.h'))
|
|
1393
|
+
if (roots.length === 0)
|
|
1394
|
+
throw new Error('its public header directory has no headers')
|
|
1395
|
+
fs.writeFileSync(
|
|
1396
|
+
modulemap,
|
|
1397
|
+
fs.existsSync(supplied)
|
|
1398
|
+
? fs.readFileSync(supplied, 'utf8')
|
|
1399
|
+
: `module ${module} {\n${roots.map((file) => ` header "${file}"`).join('\n')}\n export *\n}\n`,
|
|
1400
|
+
)
|
|
1401
|
+
all = [
|
|
1402
|
+
{
|
|
1403
|
+
kind: 'objc',
|
|
1404
|
+
module,
|
|
1405
|
+
modulemap,
|
|
1406
|
+
headers,
|
|
1407
|
+
dependencies: binary.headerDependencies?.map((dependency) => ({
|
|
1408
|
+
module: swiftTargetModuleName(dependency.module),
|
|
1409
|
+
headers: path.join(binary.dir, dependency.path),
|
|
1410
|
+
})),
|
|
1411
|
+
},
|
|
1412
|
+
]
|
|
1413
|
+
digest = [
|
|
1414
|
+
treeDigest(headers),
|
|
1415
|
+
...(binary.headerDependencies ?? []).map(
|
|
1416
|
+
(dependency) =>
|
|
1417
|
+
`${swiftTargetModuleName(dependency.module)}\0${treeDigest(path.join(binary.dir, dependency.path))}`,
|
|
1418
|
+
),
|
|
1419
|
+
].join('\0')
|
|
1420
|
+
} else {
|
|
1421
|
+
const artifact = xcframework(binary)
|
|
1422
|
+
all = surfaces(artifact.dir)
|
|
1423
|
+
digest = artifact.digest
|
|
1424
|
+
}
|
|
1148
1425
|
} catch (error) {
|
|
1426
|
+
fs.rmSync(work, { recursive: true, force: true })
|
|
1149
1427
|
return error instanceof Error ? error.message : String(error)
|
|
1150
1428
|
}
|
|
1151
1429
|
const found = all.filter((surface) => !provided.has(surface.module))
|
|
1152
1430
|
if (found.length === 0) {
|
|
1431
|
+
fs.rmSync(work, { recursive: true, force: true })
|
|
1153
1432
|
if (all.length > 0) return []
|
|
1154
1433
|
return 'its iOS simulator slice has no Swift interface or C modulemap'
|
|
1155
1434
|
}
|
|
@@ -1159,11 +1438,14 @@ function binaryStandins(
|
|
|
1159
1438
|
standinGenerationInputs([binary], toolchain, behavior),
|
|
1160
1439
|
)
|
|
1161
1440
|
hash.update(digest)
|
|
1441
|
+
hash.update([...siblings].sort().join('\0'))
|
|
1442
|
+
if (found.some((surface) => surface.kind === 'mixed')) hash.update(frameworks.digest)
|
|
1162
1443
|
for (const surface of found) {
|
|
1163
1444
|
hash.update(`${surface.kind}\0${surface.module}\0`)
|
|
1164
1445
|
for (const file of surface.kind === 'swift'
|
|
1165
1446
|
? [surface.interface]
|
|
1166
1447
|
: [
|
|
1448
|
+
...(surface.kind === 'mixed' ? [surface.interface] : []),
|
|
1167
1449
|
surface.modulemap,
|
|
1168
1450
|
...files(surface.headers, (name) => name.endsWith('.h')).map((file) =>
|
|
1169
1451
|
path.join(surface.headers, file),
|
|
@@ -1173,54 +1455,60 @@ function binaryStandins(
|
|
|
1173
1455
|
}
|
|
1174
1456
|
const id = hash.digest('hex').slice(0, 32)
|
|
1175
1457
|
const memo = built.get(id)
|
|
1176
|
-
if (memo)
|
|
1458
|
+
if (memo) {
|
|
1459
|
+
fs.rmSync(work, { recursive: true, force: true })
|
|
1460
|
+
return memo
|
|
1461
|
+
}
|
|
1177
1462
|
const cached = path.join(cacheDir, 'standins', `${id}.json`)
|
|
1178
1463
|
if (fs.existsSync(cached)) {
|
|
1179
1464
|
const value: SwiftStandin[] = JSON.parse(fs.readFileSync(cached, 'utf8'))
|
|
1180
1465
|
built.set(id, value)
|
|
1466
|
+
fs.rmSync(work, { recursive: true, force: true })
|
|
1181
1467
|
return value
|
|
1182
1468
|
}
|
|
1183
|
-
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'peach-standin-'))
|
|
1184
1469
|
let result: SwiftStandin[] | string
|
|
1185
1470
|
try {
|
|
1186
|
-
result =
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
surface,
|
|
1212
|
-
binary,
|
|
1213
|
-
toolchain,
|
|
1214
|
-
dir,
|
|
1215
|
-
new Map(Object.entries(configured.get(surface.module) ?? {})),
|
|
1216
|
-
)
|
|
1217
|
-
: cStandin(surface, binary, toolchain, dir)
|
|
1218
|
-
} catch (error) {
|
|
1219
|
-
throw new Error(
|
|
1220
|
-
`no stand-in for ${surface.module}: ${error instanceof Error && !('stderr' in error) ? error.message : failure(error)}`,
|
|
1471
|
+
result = found.map((surface) => {
|
|
1472
|
+
const dir = path.join(work, surface.module)
|
|
1473
|
+
try {
|
|
1474
|
+
if (surface.kind === 'c' && configured.has(surface.module))
|
|
1475
|
+
throw new Error(
|
|
1476
|
+
"Configured behavior requires the framework's Swift declarations",
|
|
1477
|
+
)
|
|
1478
|
+
return surface.kind === 'mixed'
|
|
1479
|
+
? mixedStandin(
|
|
1480
|
+
surface,
|
|
1481
|
+
binary,
|
|
1482
|
+
toolchain,
|
|
1483
|
+
dir,
|
|
1484
|
+
new Map(Object.entries(configured.get(surface.module) ?? {})),
|
|
1485
|
+
frameworks.dirs,
|
|
1486
|
+
siblings,
|
|
1487
|
+
)
|
|
1488
|
+
: surface.kind === 'swift'
|
|
1489
|
+
? swiftStandin(
|
|
1490
|
+
surface,
|
|
1491
|
+
binary,
|
|
1492
|
+
toolchain,
|
|
1493
|
+
dir,
|
|
1494
|
+
new Map(Object.entries(configured.get(surface.module) ?? {})),
|
|
1495
|
+
siblings,
|
|
1221
1496
|
)
|
|
1222
|
-
|
|
1223
|
-
|
|
1497
|
+
: surface.kind === 'objc'
|
|
1498
|
+
? objcStandin(
|
|
1499
|
+
surface,
|
|
1500
|
+
binary,
|
|
1501
|
+
toolchain,
|
|
1502
|
+
dir,
|
|
1503
|
+
new Map(Object.entries(configured.get(surface.module) ?? {})),
|
|
1504
|
+
)
|
|
1505
|
+
: cStandin(surface, binary, toolchain, dir)
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
throw new Error(
|
|
1508
|
+
`no stand-in for ${surface.module}: ${error instanceof Error && !('stderr' in error) ? error.message : failure(error)}`,
|
|
1509
|
+
)
|
|
1510
|
+
}
|
|
1511
|
+
})
|
|
1224
1512
|
if (typeof result === 'string') throw new Error(result)
|
|
1225
1513
|
fs.mkdirSync(path.dirname(cached), { recursive: true })
|
|
1226
1514
|
// publish a complete entry so concurrent workspaces never read partial json.
|
|
@@ -1256,12 +1544,138 @@ export function swiftStandins(
|
|
|
1256
1544
|
provided: ReadonlySet<string> = new Set(),
|
|
1257
1545
|
behavior: SwiftStandinBehaviors = {},
|
|
1258
1546
|
): SwiftStandins {
|
|
1547
|
+
const requested = binaries.filter((binary) => !provided.has(binary.target))
|
|
1548
|
+
if (requested.length === 0) return { standins: [], gaps: new Map() }
|
|
1549
|
+
if (
|
|
1550
|
+
process.platform === 'darwin' &&
|
|
1551
|
+
process.env.TM_EXEC_BUILD_PLATFORM !== 'mac' &&
|
|
1552
|
+
process.env.TM_SESSION &&
|
|
1553
|
+
process.env.CI !== 'true' &&
|
|
1554
|
+
toolchain.moduleRoot === path.resolve(import.meta.dirname, '../../sootsim-swift') &&
|
|
1555
|
+
!swiftDistributionAt(toolchain.moduleRoot)
|
|
1556
|
+
)
|
|
1557
|
+
return sourceStandins(requested, toolchain, provided, behavior)
|
|
1259
1558
|
const standins: SwiftStandin[] = []
|
|
1260
1559
|
const gaps = new Map<string, string>()
|
|
1560
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
1561
|
+
const modules = fs.mkdtempSync(path.join(cacheDir, 'modules-'))
|
|
1562
|
+
const frameworkDirs = new Set<string>()
|
|
1563
|
+
const frameworkInputs: string[] = []
|
|
1564
|
+
const byModule = new Map(
|
|
1565
|
+
binaries.map((binary) => [swiftTargetModuleName(binary.target), binary]),
|
|
1566
|
+
)
|
|
1567
|
+
// the modules each binary's swift interfaces import
|
|
1568
|
+
const interfaceImports = new Map<SwiftPackageBinary, Set<string>>()
|
|
1261
1569
|
for (const binary of binaries) {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1570
|
+
if (binary.publicHeaders !== undefined) continue
|
|
1571
|
+
try {
|
|
1572
|
+
const artifact = xcframework(binary)
|
|
1573
|
+
frameworkInputs.push(`${binary.target}\0${artifact.digest}`)
|
|
1574
|
+
const imports = new Set<string>()
|
|
1575
|
+
interfaceImports.set(binary, imports)
|
|
1576
|
+
for (const surface of surfaces(artifact.dir)) {
|
|
1577
|
+
byModule.set(surface.module, binary)
|
|
1578
|
+
if (surface.kind === 'swift' || surface.kind === 'mixed')
|
|
1579
|
+
for (const module of swiftImportNames(
|
|
1580
|
+
fs.readFileSync(surface.interface, 'utf8'),
|
|
1581
|
+
))
|
|
1582
|
+
imports.add(module)
|
|
1583
|
+
if (surface.kind === 'c') continue
|
|
1584
|
+
frameworkDirs.add(
|
|
1585
|
+
surface.kind === 'swift'
|
|
1586
|
+
? path.resolve(surface.interface, '../../../..')
|
|
1587
|
+
: path.resolve(surface.modulemap, '../../..'),
|
|
1588
|
+
)
|
|
1589
|
+
}
|
|
1590
|
+
} catch {
|
|
1591
|
+
// the target reports its artifact gap when visited below.
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
const generatedToolchain = { ...toolchain, standinModules: modules }
|
|
1595
|
+
const frameworks: NativeFrameworkInputs = {
|
|
1596
|
+
dirs: [...frameworkDirs].sort(),
|
|
1597
|
+
digest: createHash('sha256').update(frameworkInputs.sort().join('\0')).digest('hex'),
|
|
1598
|
+
}
|
|
1599
|
+
// another binary's module a swift interface imports: its stand-in is built
|
|
1600
|
+
// first and imported, as the vendor framework imports the other framework
|
|
1601
|
+
const siblings = new Map(
|
|
1602
|
+
binaries.map((binary) => [
|
|
1603
|
+
binary,
|
|
1604
|
+
new Set(
|
|
1605
|
+
[...(interfaceImports.get(binary) ?? [])].filter((module) => {
|
|
1606
|
+
const owner = byModule.get(module)
|
|
1607
|
+
return owner !== undefined && owner !== binary && !provided.has(module)
|
|
1608
|
+
}),
|
|
1609
|
+
),
|
|
1610
|
+
]),
|
|
1611
|
+
)
|
|
1612
|
+
const prerequisites = new Set(
|
|
1613
|
+
binaries.flatMap((binary) => [
|
|
1614
|
+
...(binary.headerDependencies ?? []).map((dependency) =>
|
|
1615
|
+
swiftTargetModuleName(dependency.module),
|
|
1616
|
+
),
|
|
1617
|
+
...siblings.get(binary)!,
|
|
1618
|
+
]),
|
|
1619
|
+
)
|
|
1620
|
+
const visited = new Set<string>()
|
|
1621
|
+
const visit = (binary: SwiftPackageBinary) => {
|
|
1622
|
+
if (visited.has(binary.target)) return
|
|
1623
|
+
visited.add(binary.target)
|
|
1624
|
+
for (const module of [
|
|
1625
|
+
...(binary.headerDependencies ?? []).map((dependency) =>
|
|
1626
|
+
swiftTargetModuleName(dependency.module),
|
|
1627
|
+
),
|
|
1628
|
+
...siblings.get(binary)!,
|
|
1629
|
+
]) {
|
|
1630
|
+
const target = byModule.get(module)
|
|
1631
|
+
if (target) visit(target)
|
|
1632
|
+
}
|
|
1633
|
+
const result = binaryStandins(
|
|
1634
|
+
binary,
|
|
1635
|
+
generatedToolchain,
|
|
1636
|
+
provided,
|
|
1637
|
+
behavior,
|
|
1638
|
+
frameworks,
|
|
1639
|
+
siblings.get(binary)!,
|
|
1640
|
+
)
|
|
1641
|
+
if (typeof result === 'string') {
|
|
1642
|
+
gaps.set(binary.target, result)
|
|
1643
|
+
return
|
|
1644
|
+
}
|
|
1645
|
+
for (const standin of result) {
|
|
1646
|
+
// source objc dependents typecheck against the same generated declarations
|
|
1647
|
+
// that the app links, including on cache hits.
|
|
1648
|
+
if (prerequisites.has(standin.module)) {
|
|
1649
|
+
const source = standin.files[`${standin.module}.swift`]
|
|
1650
|
+
if (source !== undefined) {
|
|
1651
|
+
const file = path.join(modules, `${standin.module}.swift`)
|
|
1652
|
+
fs.writeFileSync(file, source)
|
|
1653
|
+
try {
|
|
1654
|
+
exec(path.join(toolchain.toolchainBin, 'swiftc'), [
|
|
1655
|
+
'-emit-module',
|
|
1656
|
+
'-parse-as-library',
|
|
1657
|
+
'-swift-version',
|
|
1658
|
+
'5',
|
|
1659
|
+
'-module-name',
|
|
1660
|
+
standin.module,
|
|
1661
|
+
...wasmFlags(generatedToolchain, standin.dependencies ?? []),
|
|
1662
|
+
'-emit-module-path',
|
|
1663
|
+
path.join(modules, `${standin.module}.swiftmodule`),
|
|
1664
|
+
file,
|
|
1665
|
+
])
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
gaps.set(binary.target, failure(error))
|
|
1668
|
+
return
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
standins.push(standin)
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
try {
|
|
1676
|
+
for (const binary of binaries) visit(binary)
|
|
1677
|
+
return { standins, gaps }
|
|
1678
|
+
} finally {
|
|
1679
|
+
fs.rmSync(modules, { recursive: true, force: true })
|
|
1265
1680
|
}
|
|
1266
|
-
return { standins, gaps }
|
|
1267
1681
|
}
|