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.
Files changed (169) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +15 -2
  3. package/cli/app-config.ts +18 -6
  4. package/cli/app-fonts.ts +37 -17
  5. package/cli/app-stubs.ts +284 -20
  6. package/cli/bridge-flow-runner.ts +66 -45
  7. package/cli/cloud-client.ts +60 -40
  8. package/cli/command-registry.ts +1 -0
  9. package/cli/commands/control.ts +283 -49
  10. package/cli/commands/doctor.ts +30 -189
  11. package/cli/commands/flow.ts +79 -11
  12. package/cli/commands/inspect/core.ts +10 -5
  13. package/cli/commands/inspect/settling.ts +14 -2
  14. package/cli/commands/inspect/shared.ts +9 -4
  15. package/cli/commands/inspect/wait-ready.ts +12 -0
  16. package/cli/commands/inspect.ts +91 -50
  17. package/cli/commands/install-desktop.ts +1 -0
  18. package/cli/commands/maestro-compare.ts +97 -38
  19. package/cli/commands/maestro.ts +9 -1
  20. package/cli/commands/preview.ts +1 -1
  21. package/cli/commands/record.ts +118 -48
  22. package/cli/commands/screenshot.ts +23 -1
  23. package/cli/commands/serve.ts +7 -2
  24. package/cli/commands/setup.ts +19 -42
  25. package/cli/commands/swift.ts +14 -3
  26. package/cli/commands/upload.ts +93 -48
  27. package/cli/desktop-companion.ts +102 -17
  28. package/cli/drivers/desktop.ts +14 -1
  29. package/cli/drivers/playwright-sim-host.ts +103 -45
  30. package/cli/drivers/playwright.ts +9 -8
  31. package/cli/drivers/types.ts +5 -0
  32. package/cli/maestro-accessibility.ts +1 -1
  33. package/cli/maestro-compare.ts +84 -12
  34. package/cli/main.ts +3 -2
  35. package/cli/outbound-endpoints.ts +20 -6
  36. package/cli/pause-aware-timeout.ts +106 -0
  37. package/cli/scene-video.ts +89 -0
  38. package/cli/ws-bridge.ts +37 -19
  39. package/detox/attempt-events.cjs +66 -5
  40. package/detox/attempt-events.d.cts +7 -0
  41. package/detox/expectations.ts +7 -3
  42. package/detox/index.ts +269 -170
  43. package/detox/motion-change.cjs +206 -21
  44. package/detox/motion-change.d.cts +39 -2
  45. package/detox/navigation-lifecycle.ts +35 -3
  46. package/dist-lib/agent-daemon-client.cjs +1 -1
  47. package/dist-lib/agent-events.cjs +1 -1
  48. package/dist-lib/attached-projects.cjs +1 -1
  49. package/dist-lib/auth/shared-session.cjs +1 -1
  50. package/dist-lib/backend-origin.cjs +8 -7
  51. package/dist-lib/beta.cjs +1 -1
  52. package/dist-lib/beta.mjs +1 -1
  53. package/dist-lib/bridge-constants.cjs +13 -1
  54. package/dist-lib/bridge-contract-input.cjs +1 -1
  55. package/dist-lib/bridge-contract-input.mjs +1 -1
  56. package/dist-lib/bridge-contract.cjs +14 -8
  57. package/dist-lib/bridge-contract.mjs +13 -7
  58. package/dist-lib/bundle-channel.cjs +11887 -7900
  59. package/dist-lib/capture-contract.cjs +1 -1
  60. package/dist-lib/capture-contract.mjs +1 -1
  61. package/dist-lib/cloud-contract.cjs +1 -1
  62. package/dist-lib/cloud-contract.mjs +1 -1
  63. package/dist-lib/cloud-test-args.cjs +1 -1
  64. package/dist-lib/cloud-test-args.mjs +1 -1
  65. package/dist-lib/cloud.cjs +1 -1
  66. package/dist-lib/cloud.mjs +1 -1
  67. package/dist-lib/config.cjs +6 -3
  68. package/dist-lib/config.mjs +6 -3
  69. package/dist-lib/detox/index.cjs +1379 -725
  70. package/dist-lib/dev-bundle-resolution.cjs +14 -2
  71. package/dist-lib/expo-swift-bundle.cjs +9341 -5462
  72. package/dist-lib/expo-swift-metadata.cjs +11 -7
  73. package/dist-lib/expo-swift-metadata.mjs +11 -7
  74. package/dist-lib/home-paths.cjs +32 -3
  75. package/dist-lib/host/bridge-host.cjs +50204 -7258
  76. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  77. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  78. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  79. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  80. package/dist-lib/host/websocket-proxy.cjs +1 -1
  81. package/dist-lib/index.cjs +3541 -448
  82. package/dist-lib/jump-to-source-babel.cjs +1 -1
  83. package/dist-lib/jump-to-source-native.cjs +1 -1
  84. package/dist-lib/kotlin-host.cjs +1 -1
  85. package/dist-lib/menu.cjs +1 -1
  86. package/dist-lib/menu.mjs +1 -1
  87. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  88. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  89. package/dist-lib/metro-production-bundle.cjs +1 -1
  90. package/dist-lib/metro-production-bundle.mjs +1 -1
  91. package/dist-lib/metro.cjs +2931 -104
  92. package/dist-lib/native-app-identity-project.cjs +289 -49
  93. package/dist-lib/native-app-identity-project.mjs +289 -49
  94. package/dist-lib/native-app-identity.cjs +77 -26
  95. package/dist-lib/native-app-identity.mjs +76 -26
  96. package/dist-lib/native-firebase-metadata.cjs +1 -1
  97. package/dist-lib/native-firebase-metadata.mjs +1 -1
  98. package/dist-lib/plist.cjs +1 -1
  99. package/dist-lib/plist.mjs +1 -1
  100. package/dist-lib/profiles.cjs +1 -1
  101. package/dist-lib/public-brand.cjs +1 -1
  102. package/dist-lib/react-native-host-modules.cjs +6 -2
  103. package/dist-lib/react-native-host-modules.mjs +6 -2
  104. package/dist-lib/scripts/dev-server-scanner.cjs +22 -8
  105. package/dist-lib/sdk.cjs +27 -11
  106. package/dist-lib/sdk.mjs +27 -10
  107. package/dist-lib/skills.cjs +72201 -26208
  108. package/dist-lib/swift-compile.cjs +1007 -140
  109. package/dist-lib/swift-compile.mjs +1049 -174
  110. package/dist-lib/swift-package.cjs +12 -3
  111. package/dist-lib/swift-package.mjs +12 -3
  112. package/dist-lib/swift-submit.cjs +13 -5
  113. package/dist-lib/swift-submit.mjs +11 -5
  114. package/dist-lib/swift-toolchain.cjs +423 -33
  115. package/dist-lib/swift.cjs +1 -1
  116. package/dist-lib/vite-swift.cjs +8628 -5268
  117. package/dist-lib/vite.cjs +2859 -12
  118. package/dist-lib/worklet-executable.cjs +14 -2
  119. package/dist-lib/worklet-executable.mjs +14 -2
  120. package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
  121. package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
  122. package/package.json +1 -1
  123. package/scripts/dev-server-scanner.ts +2 -1
  124. package/skills/peach-perf/SKILL.md +11 -5
  125. package/skills/peach-setup/SKILL.md +16 -19
  126. package/src/backend-origin.ts +13 -6
  127. package/src/bridge-constants.ts +11 -0
  128. package/src/bridge-contract.ts +22 -34
  129. package/src/config.ts +15 -0
  130. package/src/desktop-host-delivery.ts +5 -1
  131. package/src/dev-bundle-resolution.ts +2 -1
  132. package/src/expo-manifest-body.ts +13 -0
  133. package/src/expo-swift-host.ts +196 -49
  134. package/src/expo-swift-ready.ts +78 -0
  135. package/src/expoKotlinContract.ts +4 -7
  136. package/src/expoSwiftContract.ts +17 -6
  137. package/src/home-paths.ts +48 -2
  138. package/src/host/bridge-host.ts +5 -2
  139. package/src/host/staged-host-files.ts +9 -0
  140. package/src/native-app-identity-project.ts +27 -6
  141. package/src/native-app-identity.ts +91 -4
  142. package/src/native-firebase-config.ts +209 -13
  143. package/src/nativeAppIconContract.ts +1 -4
  144. package/src/output-lock-stdio.ts +36 -0
  145. package/src/process-group.ts +25 -2
  146. package/src/react-native-bridge-modules.ts +431 -0
  147. package/src/react-native-host-modules.ts +8 -1
  148. package/src/settings-bundle.ts +1 -4
  149. package/src/swift-binary-modules.ts +2 -2
  150. package/src/swift-compile.ts +918 -109
  151. package/src/swift-compiler-digest.ts +14 -0
  152. package/src/swift-distribution.lock.json +24 -12
  153. package/src/swift-distribution.ts +103 -4
  154. package/src/swift-host.ts +180 -55
  155. package/src/swift-icu.ts +27 -13
  156. package/src/swift-objc-compile.ts +355 -0
  157. package/src/swift-objc-sdk.ts +821 -27
  158. package/src/swift-objc-standin.ts +148 -14
  159. package/src/swift-package-manifest.ts +60 -7
  160. package/src/swift-package.ts +15 -1
  161. package/src/swift-packages.ts +154 -81
  162. package/src/swift-standin-emit.ts +96 -15
  163. package/src/swift-standins.ts +530 -116
  164. package/src/swift-submit.ts +13 -5
  165. package/src/swift-toolchain.ts +500 -42
  166. package/src/vite-plugin.ts +12 -1
  167. package/src/worker-system-chunks.ts +11 -12
  168. package/src/worklet-executable.ts +19 -1
  169. package/src/xcode-project.ts +270 -30
@@ -13,11 +13,12 @@
13
13
  // headers import; the stand-in's own typecheck then leaves out whatever peach
14
14
  // cannot spell.
15
15
 
16
- import { spawnSync } from 'node:child_process'
16
+ import { execFileSync, spawnSync } from 'node:child_process'
17
17
  import { createHash } from 'node:crypto'
18
18
  import fs from 'node:fs'
19
19
  import path from 'node:path'
20
20
  import { IOS_SDK_HEADERS, IOS_SDK_OVERLAYS, IOS_SDK_TARGET } from './swift-objc-sdk'
21
+ import { swiftEnvironment } from './swift-toolchain'
21
22
  import type { SymbolGraph } from './swift-standin-emit'
22
23
 
23
24
  /** a module map and the headers it reaches: a framework's, or a library
@@ -26,6 +27,7 @@ export interface ObjcSurface {
26
27
  module: string
27
28
  modulemap: string
28
29
  headers: string
30
+ dependencies?: { module: string; headers: string }[]
29
31
  }
30
32
 
31
33
  export interface ObjcView {
@@ -36,6 +38,100 @@ export interface ObjcView {
36
38
  imports: string[]
37
39
  }
38
40
 
41
+ /** bump when importer behavior changes the Swift view independently of SDK headers. */
42
+ export const OBJC_IMPORT_VERSION = '3'
43
+
44
+ export const NATIVE_GRAPH_SDK_VERSION = '27.1'
45
+
46
+ const NATIVE_GRAPH_INPUTS = {
47
+ sdk: NATIVE_GRAPH_SDK_VERSION,
48
+ settings: 'e68503e5e534e9194af5efe8c636ca8b824e2d8237c5f38ec84aa4d155bc8418',
49
+ build: 'ff785237ba22f1d8f347a5c4f4c68400a387c6d935ef3a443545a72faef57e70',
50
+ compiler: 'Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)',
51
+ target: IOS_SDK_TARGET,
52
+ }
53
+ export const NATIVE_GRAPH_ID = createHash('sha256')
54
+ .update(JSON.stringify(NATIVE_GRAPH_INPUTS))
55
+ .digest('hex')
56
+
57
+ /** a mixed framework's swift overlay and underlying clang module load together. */
58
+ export function nativeInterfaceView(
59
+ surface: ObjcSurface,
60
+ frameworks: readonly string[],
61
+ work: string,
62
+ ): ObjcView {
63
+ if (process.platform !== 'darwin')
64
+ throw new Error('mixed framework declarations require the pinned native iOS SDK')
65
+ const xcrun = (args: string[]) =>
66
+ execFileSync('xcrun', args, {
67
+ encoding: 'utf8',
68
+ stdio: ['ignore', 'pipe', 'pipe'],
69
+ }).trim()
70
+ const sdk = xcrun(['--sdk', 'iphonesimulator', '--show-sdk-path'])
71
+ const compiler = xcrun(['--find', 'swiftc'])
72
+ const version = execFileSync(compiler, ['--version'], { encoding: 'utf8' })
73
+ .trim()
74
+ .split('\n')[0]
75
+ const digest = (file: string) =>
76
+ createHash('sha256')
77
+ .update(fs.readFileSync(path.join(sdk, file)))
78
+ .digest('hex')
79
+ const actual = {
80
+ sdk: xcrun(['--sdk', 'iphonesimulator', '--show-sdk-version']),
81
+ compiler: version,
82
+ settings: digest('SDKSettings.json'),
83
+ build: digest('System/Library/CoreServices/SystemVersion.plist'),
84
+ }
85
+ const mismatches = Object.entries(actual)
86
+ .filter(([key, value]) => Reflect.get(NATIVE_GRAPH_INPUTS, key) !== value)
87
+ .map(
88
+ ([key, value]) =>
89
+ `${key}: expected ${Reflect.get(NATIVE_GRAPH_INPUTS, key)}, got ${value}`,
90
+ )
91
+ if (mismatches.length)
92
+ throw new Error(
93
+ `native declaration SDK or compiler differs from ${NATIVE_GRAPH_ID}: ${mismatches.join('; ')}`,
94
+ )
95
+ const toolchainBin = path.dirname(compiler)
96
+ const search = ['-sdk', sdk, ...frameworks.flatMap((dir) => ['-F', dir])]
97
+ const out = path.join(work, 'native-graphs')
98
+ fs.mkdirSync(out, { recursive: true })
99
+ const run = spawnSync(
100
+ path.join(toolchainBin, 'swift-symbolgraph-extract'),
101
+ [
102
+ '-module-name',
103
+ surface.module,
104
+ '-target',
105
+ IOS_SDK_TARGET,
106
+ ...search,
107
+ '-minimum-access-level',
108
+ 'public',
109
+ '-skip-synthesized-members',
110
+ '-omit-extension-block-symbols',
111
+ '-output-dir',
112
+ out,
113
+ ],
114
+ { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 },
115
+ )
116
+ if (run.error) throw run.error
117
+ if (run.status !== 0)
118
+ throw new Error(
119
+ `native interface extraction ${run.signal ?? `exit ${run.status}`}: ${run.stderr.trim().slice(-4000)}`,
120
+ )
121
+ const graphs = fs
122
+ .readdirSync(out)
123
+ .filter((file) => file.endsWith('.symbols.json'))
124
+ .sort()
125
+ .map((file): SymbolGraph => JSON.parse(fs.readFileSync(path.join(out, file), 'utf8')))
126
+ if (graphs.length === 0)
127
+ throw new Error('native interface extraction returned no graphs')
128
+ return {
129
+ graphs,
130
+ values: enumValues(graphs, surface, true, toolchainBin, search, work),
131
+ imports: [],
132
+ }
133
+ }
134
+
39
135
  // the opaque declarations a failed import asks for, by name.
40
136
  type Missing = Map<string, 'class' | 'protocol' | 'struct' | 'macro'>
41
137
 
@@ -62,7 +158,7 @@ export const IOS_SDK_ID = createHash('sha256')
62
158
 
63
159
  // the sdk, written once per content under the cache directory so the module
64
160
  // cache keyed by its paths stays warm across stand-ins.
65
- function iosSdk(cacheDir: string): string {
161
+ export function materializeObjcSdk(cacheDir: string): string {
66
162
  const sdk = path.join(cacheDir, 'ios-sdk', IOS_SDK_ID)
67
163
  if (fs.existsSync(sdk)) return sdk
68
164
  fs.mkdirSync(path.dirname(sdk), { recursive: true })
@@ -115,7 +211,7 @@ export function objcView(
115
211
  throw new Error(
116
212
  `this peach's Swift has no iOS simulator standard library at ${stdlib}`,
117
213
  )
118
- const sdk = iosSdk(cacheDir)
214
+ const sdk = materializeObjcSdk(cacheDir)
119
215
  // a framework's module map sits in Modules/ beside Headers/; a library
120
216
  // slice keeps it among its headers.
121
217
  const framework = path.basename(path.dirname(surface.modulemap)) === 'Modules'
@@ -123,16 +219,16 @@ export function objcView(
123
219
  const include = path.join(work, 'include')
124
220
  const headersDir = framework
125
221
  ? path.join(frameworks, `${surface.module}.framework`, 'Headers')
126
- : include
222
+ : path.join(include, surface.module)
127
223
  const originals = new Map(
128
224
  headerFiles(surface.headers).map((file) => [
129
- file,
225
+ path.join(headersDir, file),
130
226
  fs.readFileSync(path.join(surface.headers, file), 'utf8'),
131
227
  ]),
132
228
  )
133
229
  for (const [file, text] of originals) {
134
- fs.mkdirSync(path.dirname(path.join(headersDir, file)), { recursive: true })
135
- fs.writeFileSync(path.join(headersDir, file), text)
230
+ fs.mkdirSync(path.dirname(file), { recursive: true })
231
+ fs.writeFileSync(file, text)
136
232
  }
137
233
  if (framework) {
138
234
  fs.mkdirSync(path.join(frameworks, `${surface.module}.framework`, 'Modules'), {
@@ -143,7 +239,28 @@ export function objcView(
143
239
  path.join(frameworks, `${surface.module}.framework`, 'Modules', 'module.modulemap'),
144
240
  )
145
241
  } else {
146
- fs.copyFileSync(surface.modulemap, path.join(include, 'module.modulemap'))
242
+ fs.copyFileSync(surface.modulemap, path.join(headersDir, 'module.modulemap'))
243
+ }
244
+ // dependency headers take the same learned declarations but never become
245
+ // the surface's own imports.
246
+ const dependencyOriginals = new Map<string, string>()
247
+ for (const dependency of surface.dependencies ?? []) {
248
+ const dir = path.join(include, dependency.module)
249
+ fs.mkdirSync(dir, { recursive: true })
250
+ for (const file of headerFiles(dependency.headers)) {
251
+ const destination = path.join(dir, file)
252
+ const text = fs.readFileSync(path.join(dependency.headers, file), 'utf8')
253
+ dependencyOriginals.set(destination, text)
254
+ fs.mkdirSync(path.dirname(destination), { recursive: true })
255
+ fs.writeFileSync(destination, text)
256
+ }
257
+ const supplied = path.join(dependency.headers, 'module.modulemap')
258
+ fs.writeFileSync(
259
+ path.join(dir, 'module.modulemap'),
260
+ fs.existsSync(supplied)
261
+ ? fs.readFileSync(supplied, 'utf8')
262
+ : `module ${dependency.module} { umbrella "." export * }\n`,
263
+ )
147
264
  }
148
265
  const stubs = path.join(work, 'stubs')
149
266
  const stubIncludes = path.join(work, 'stub-include')
@@ -152,12 +269,25 @@ export function objcView(
152
269
  const search = [
153
270
  '-sdk',
154
271
  sdk,
272
+ // keep compiler support headers without importing host platform modules.
273
+ '-resource-dir',
274
+ path.join(sdk, 'usr/lib/swift'),
275
+ '-I',
276
+ path.resolve(toolchainBin, '../lib/swift/clang/include'),
277
+ '-I',
278
+ path.resolve(toolchainBin, '../lib/swift/shims'),
155
279
  '-I',
156
280
  stdlib,
157
281
  '-F',
158
282
  frameworks,
159
283
  '-I',
160
284
  include,
285
+ '-I',
286
+ headersDir,
287
+ ...(surface.dependencies ?? []).flatMap((dependency) => [
288
+ '-I',
289
+ path.join(include, dependency.module),
290
+ ]),
161
291
  '-F',
162
292
  stubs,
163
293
  '-I',
@@ -215,8 +345,8 @@ export function objcView(
215
345
  path.join(root, 'Modules', 'module.modulemap'),
216
346
  'framework module PeachMissing {\n umbrella header "PeachMissing.h"\n export *\n}\n',
217
347
  )
218
- for (const [file, text] of originals)
219
- fs.writeFileSync(path.join(headersDir, file), `${MISSING_IMPORT}${text}`)
348
+ for (const [file, text] of [...originals, ...dependencyOriginals])
349
+ fs.writeFileSync(file, `${MISSING_IMPORT}${text}`)
220
350
  }
221
351
  // the identifier a diagnostic points at, read from the header as compiled.
222
352
  const tokenAt = (file: string, line: number, column: number) => {
@@ -254,11 +384,11 @@ export function objcView(
254
384
  '-output-dir',
255
385
  out,
256
386
  ],
257
- { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 },
387
+ { encoding: 'utf8', env: swiftEnvironment(), maxBuffer: 256 * 1024 * 1024 },
258
388
  )
259
389
  if (run.error) throw run.error
260
390
  const errors = [
261
- ...run.stderr.matchAll(/^(?:(\S+?):(\d+):(\d+): )?(?:fatal )?error: (.*)$/gm),
391
+ ...run.stderr.matchAll(/^(?:(.+?):(\d+):(\d+): )?(?:fatal )?error: (.*)$/gm),
262
392
  ]
263
393
  if (errors.length === 0 && run.status === 0) break
264
394
  let progress = false
@@ -300,7 +430,11 @@ export function objcView(
300
430
  }
301
431
  if (!progress || round === ROUNDS)
302
432
  throw new Error(
303
- `its headers do not import as Swift: ${[...new Set(errors.map((match) => match[4]!))].slice(0, 3).join('; ')}`,
433
+ `its headers do not import as Swift: ${
434
+ errors.length > 0
435
+ ? [...new Set(errors.map((match) => match[4]!))].slice(0, 10).join('; ')
436
+ : `swift-symbolgraph-extract ${run.signal ?? `exit ${run.status}`}: ${(run.stderr.trim() || run.stdout.trim() || 'no diagnostic').slice(0, 4000)}`
437
+ }`,
304
438
  )
305
439
  if (missing.size > 0) declareMissing()
306
440
  }
@@ -383,7 +517,7 @@ function enumValues(
383
517
  '-ast-dump=json',
384
518
  probe,
385
519
  ],
386
- { encoding: 'utf8', maxBuffer: 1024 * 1024 * 1024 },
520
+ { encoding: 'utf8', env: swiftEnvironment(), maxBuffer: 1024 * 1024 * 1024 },
387
521
  )
388
522
  if (run.status !== 0)
389
523
  throw new Error(
@@ -8,10 +8,10 @@
8
8
  import { execFileSync } from 'node:child_process'
9
9
  import { createHash } from 'node:crypto'
10
10
  import fs from 'node:fs'
11
- import os from 'node:os'
12
11
  import path from 'node:path'
12
+ import { cacheDir as peachCacheDir, swiftPackageCacheOptions } from './home-paths'
13
13
  import lock from './swift-distribution.lock.json' with { type: 'json' }
14
- import { swiftToolchainBin } from './swift-toolchain'
14
+ import { swiftEnvironment, swiftToolchainBin } from './swift-toolchain'
15
15
  import type { SwiftTargetDependency } from './swift-package'
16
16
 
17
17
  const TARGET_TYPES = [
@@ -27,9 +27,9 @@ export type SwiftManifestTargetType = (typeof TARGET_TYPES)[number]
27
27
  const isTargetType = (value: string | undefined): value is SwiftManifestTargetType =>
28
28
  TARGET_TYPES.some((type) => type === value)
29
29
 
30
- /** a swift condition a target defines on ios, and the swiftpm build
30
+ /** a named target setting active on ios, and the swiftpm build
31
31
  * configuration it needs (null applies in both). */
32
- export interface SwiftManifestDefine {
32
+ export interface SwiftManifestSetting {
33
33
  name: string
34
34
  configuration: 'debug' | 'release' | null
35
35
  }
@@ -47,9 +47,17 @@ export interface SwiftManifestTarget {
47
47
  dependencies: SwiftTargetDependency[]
48
48
  languageMode: number
49
49
  mainActorIsolation: boolean
50
- defines: SwiftManifestDefine[]
50
+ defines: SwiftManifestSetting[]
51
+ libraries: SwiftManifestSetting[]
52
+ nativeSettings: {
53
+ tool: 'c' | 'cxx'
54
+ flags: string[]
55
+ configuration: 'debug' | 'release' | null
56
+ }[]
51
57
  /** true when the target declares `resources:`. */
52
58
  declaresResources: boolean
59
+ /** public header directory relative to the target, as SwiftPM declares it. */
60
+ publicHeadersPath: string
53
61
  /** a binary target's archive url; a local one's artifact is `dir`. */
54
62
  url: string | null
55
63
  /** the declared SHA-256 for a remote binary archive. */
@@ -80,7 +88,7 @@ function swiftCommand(): { file: string; args: string[] } {
80
88
  : { file: path.join(swiftToolchainBin(lock.release), 'swift'), args: [] }
81
89
  }
82
90
 
83
- const cacheDir = path.join(os.tmpdir(), 'peach-swift-manifests')
91
+ const cacheDir = path.join(peachCacheDir(), 'swift-manifests')
84
92
  const evaluated = new Map<string, SwiftPackageManifest>()
85
93
 
86
94
  function dump(dir: string): { key: string; raw: Record<string, Json> } {
@@ -108,6 +116,7 @@ function dump(dir: string): { key: string; raw: Record<string, Json> } {
108
116
  [
109
117
  ...command.args,
110
118
  'package',
119
+ ...swiftPackageCacheOptions(),
111
120
  '--package-path',
112
121
  dir,
113
122
  // swiftpm's scratch files never land in the app's own package
@@ -117,6 +126,7 @@ function dump(dir: string): { key: string; raw: Record<string, Json> } {
117
126
  ],
118
127
  {
119
128
  encoding: 'utf8',
129
+ env: swiftEnvironment(),
120
130
  stdio: ['ignore', 'pipe', 'pipe'],
121
131
  maxBuffer: 64 * 1024 * 1024,
122
132
  },
@@ -249,10 +259,42 @@ export function swiftPackageManifest(dir: string): SwiftPackageManifest {
249
259
  }
250
260
  let languageMode = packageMode
251
261
  let mainActorIsolation = false
252
- const defines: SwiftManifestDefine[] = []
262
+ const defines: SwiftManifestSetting[] = []
263
+ const libraries: SwiftManifestSetting[] = []
264
+ const nativeSettings: SwiftManifestTarget['nativeSettings'] = []
265
+ for (const [tool, standard] of [
266
+ ['c', string(raw.cLanguageStandard)],
267
+ ['cxx', string(raw.cxxLanguageStandard)],
268
+ ] satisfies ['c' | 'cxx', string | undefined][]) {
269
+ if (standard)
270
+ nativeSettings.push({ tool, flags: [`-std=${standard}`], configuration: null })
271
+ }
253
272
  for (const setting of array(target.settings)) {
254
273
  const { kind: rawKind, tool, condition } = record(setting)
255
274
  const settingKind = record(rawKind)
275
+ if ((tool === 'c' || tool === 'cxx') && linksOnIOS(condition, traits)) {
276
+ const config = string(record(condition).config)
277
+ const header = string(record(settingKind.headerSearchPath)._0)
278
+ const definition = string(record(settingKind.define)._0)
279
+ const value = string(record(settingKind.define)._1)
280
+ const unsafe = record(settingKind.unsafeFlags)._0
281
+ const flags = header
282
+ ? ['-I', path.resolve(targetDir, header)]
283
+ : definition
284
+ ? [`-D${definition}${value === undefined ? '' : `=${value}`}`]
285
+ : Array.isArray(unsafe)
286
+ ? unsafe.flatMap((flag) => string(flag) ?? [])
287
+ : null
288
+ if (!flags)
289
+ throw new Error(
290
+ `${name}: unsupported ${tool} compiler setting ${JSON.stringify(rawKind)}`,
291
+ )
292
+ nativeSettings.push({
293
+ tool,
294
+ flags,
295
+ configuration: config === 'debug' || config === 'release' ? config : null,
296
+ })
297
+ }
256
298
  const mode = string(record(settingKind.swiftLanguageMode)._0)
257
299
  if (mode)
258
300
  languageMode = Math.max(5, Number(/^(\d+)/.exec(mode)?.[1] ?? languageMode))
@@ -271,6 +313,14 @@ export function swiftPackageManifest(dir: string): SwiftPackageManifest {
271
313
  configuration: config === 'debug' || config === 'release' ? config : null,
272
314
  })
273
315
  }
316
+ const library = string(record(settingKind.linkedLibrary)._0)
317
+ if (library && string(tool) === 'linker' && linksOnIOS(condition, traits)) {
318
+ const config = string(record(condition).config)
319
+ libraries.push({
320
+ name: library,
321
+ configuration: config === 'debug' || config === 'release' ? config : null,
322
+ })
323
+ }
274
324
  }
275
325
  targets.set(name, {
276
326
  name,
@@ -285,7 +335,10 @@ export function swiftPackageManifest(dir: string): SwiftPackageManifest {
285
335
  languageMode,
286
336
  mainActorIsolation,
287
337
  defines,
338
+ libraries,
339
+ nativeSettings,
288
340
  declaresResources: array(target.resources).length > 0,
341
+ publicHeadersPath: string(target.publicHeadersPath) ?? 'include',
289
342
  url: string(target.url) ?? null,
290
343
  checksum: string(target.checksum) ?? null,
291
344
  })
@@ -8,6 +8,8 @@ export interface SwiftPackageTarget {
8
8
  path: string
9
9
  /** true for `.executableTarget`: the one that links the wasm artifact. */
10
10
  executable: boolean
11
+ /** true for a header-only `.systemLibrary` supplied by the target SDK. */
12
+ systemLibrary?: boolean
11
13
  /** the target's declared `.swiftLanguageMode(.vN)`; 5 when it declares none. */
12
14
  languageMode: number
13
15
  /** true when the target declares `.defaultIsolation(MainActor.self)`. */
@@ -16,6 +18,8 @@ export interface SwiftPackageTarget {
16
18
  cxxInterop?: boolean
17
19
  /** compilation conditions the target declares unconditionally with `.define`. */
18
20
  defines?: string[]
21
+ /** libraries the target declares unconditionally with `.linkedLibrary`. */
22
+ linkedLibraries?: string[]
19
23
  /** true when declared or implicit resources give the target `Bundle.module`. */
20
24
  resources: boolean
21
25
  /** declared dependencies on other targets in the same package. */
@@ -202,7 +206,11 @@ export function swiftManifestTargets(
202
206
  ): SwiftPackageTarget[] {
203
207
  const implicitResources = swiftImplicitResourcePaths(resourcePaths)
204
208
  const targets: SwiftPackageTarget[] = []
205
- const declarations = manifestCalls(manifest, ['executableTarget', 'target'])
209
+ const declarations = manifestCalls(manifest, [
210
+ 'executableTarget',
211
+ 'target',
212
+ 'systemLibrary',
213
+ ])
206
214
  const targetNames = new Set(
207
215
  declarations.flatMap(({ flat }) => {
208
216
  const name = /\bname\s*:\s*"([^"]+)"/.exec(flat)?.[1]
@@ -230,6 +238,9 @@ export function swiftManifestTargets(
230
238
  const defines = [
231
239
  ...call.matchAll(/\.define\(\s*"([A-Za-z_][A-Za-z0-9_]*)"\s*\)/g),
232
240
  ].map((match) => match[1]!)
241
+ const linkedLibraries = [...call.matchAll(/\.linkedLibrary\(\s*"([^"]+)"\s*\)/g)].map(
242
+ (match) => match[1]!,
243
+ )
233
244
  const excluded = [
234
245
  ...(/\bexclude\s*:\s*\[([^\]]*)\]/.exec(flat)?.[1] ?? '').matchAll(/"([^"]+)"/g),
235
246
  ].map((match) => match[1].replace(/^\.\//, '').replace(/\/+$/, ''))
@@ -270,10 +281,12 @@ export function swiftManifestTargets(
270
281
  name,
271
282
  path: targetPath,
272
283
  executable,
284
+ ...(kind === 'systemLibrary' ? { systemLibrary: true } : {}),
273
285
  languageMode,
274
286
  mainActorIsolation,
275
287
  ...(cxxInterop ? { cxxInterop: true } : {}),
276
288
  ...(defines.length === 0 ? {} : { defines }),
289
+ ...(linkedLibraries.length === 0 ? {} : { linkedLibraries }),
277
290
  resources,
278
291
  ...(dependencies.length === 0 ? {} : { dependencies }),
279
292
  ...(excluded.length === 0 ? {} : { exclude: excluded }),
@@ -380,6 +393,7 @@ export function swiftPackagePartition(
380
393
  targets[0].languageMode === 5 &&
381
394
  !targets[0].mainActorIsolation &&
382
395
  !targets[0].defines?.length &&
396
+ !targets[0].linkedLibraries?.length &&
383
397
  !targets[0].cxxInterop &&
384
398
  !swiftManifestCxxLanguageStandard(manifest)
385
399
  ? []