peachsim 0.1.1625 → 0.1.1635

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 (152) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +10 -1
  3. package/cli/app-config.ts +4 -1
  4. package/cli/app-fonts.ts +37 -17
  5. package/cli/app-stubs.ts +224 -9
  6. package/cli/bridge-flow-runner.ts +64 -43
  7. package/cli/command-registry.ts +1 -0
  8. package/cli/commands/control.ts +283 -49
  9. package/cli/commands/doctor.ts +17 -184
  10. package/cli/commands/flow.ts +79 -11
  11. package/cli/commands/inspect/core.ts +10 -5
  12. package/cli/commands/inspect/settling.ts +14 -2
  13. package/cli/commands/inspect/wait-ready.ts +12 -0
  14. package/cli/commands/inspect.ts +26 -3
  15. package/cli/commands/install-desktop.ts +1 -0
  16. package/cli/commands/maestro-compare.ts +97 -38
  17. package/cli/commands/maestro.ts +9 -1
  18. package/cli/commands/record.ts +21 -1
  19. package/cli/commands/screenshot.ts +23 -1
  20. package/cli/commands/serve.ts +7 -2
  21. package/cli/commands/setup.ts +19 -42
  22. package/cli/commands/swift.ts +5 -0
  23. package/cli/commands/upload.ts +46 -7
  24. package/cli/desktop-companion.ts +102 -17
  25. package/cli/drivers/desktop.ts +14 -1
  26. package/cli/drivers/playwright-sim-host.ts +38 -3
  27. package/cli/drivers/playwright.ts +9 -8
  28. package/cli/drivers/types.ts +5 -0
  29. package/cli/maestro-accessibility.ts +1 -1
  30. package/cli/maestro-compare.ts +84 -12
  31. package/cli/main.ts +2 -1
  32. package/cli/outbound-endpoints.ts +13 -1
  33. package/cli/pause-aware-timeout.ts +102 -0
  34. package/cli/scene-video.ts +89 -0
  35. package/cli/ws-bridge.ts +23 -14
  36. package/detox/attempt-events.cjs +4 -0
  37. package/detox/index.ts +167 -142
  38. package/detox/motion-change.cjs +57 -21
  39. package/detox/motion-change.d.cts +7 -2
  40. package/dist-lib/agent-daemon-client.cjs +1 -1
  41. package/dist-lib/agent-events.cjs +1 -1
  42. package/dist-lib/attached-projects.cjs +1 -1
  43. package/dist-lib/auth/shared-session.cjs +1 -1
  44. package/dist-lib/backend-origin.cjs +1 -1
  45. package/dist-lib/beta.cjs +1 -1
  46. package/dist-lib/beta.mjs +1 -1
  47. package/dist-lib/bridge-constants.cjs +13 -1
  48. package/dist-lib/bridge-contract-input.cjs +1 -1
  49. package/dist-lib/bridge-contract-input.mjs +1 -1
  50. package/dist-lib/bridge-contract.cjs +14 -8
  51. package/dist-lib/bridge-contract.mjs +13 -7
  52. package/dist-lib/bundle-channel.cjs +9536 -7604
  53. package/dist-lib/capture-contract.cjs +1 -1
  54. package/dist-lib/capture-contract.mjs +1 -1
  55. package/dist-lib/cloud-contract.cjs +1 -1
  56. package/dist-lib/cloud-contract.mjs +1 -1
  57. package/dist-lib/cloud-test-args.cjs +1 -1
  58. package/dist-lib/cloud-test-args.mjs +1 -1
  59. package/dist-lib/cloud.cjs +1 -1
  60. package/dist-lib/cloud.mjs +1 -1
  61. package/dist-lib/config.cjs +6 -3
  62. package/dist-lib/config.mjs +6 -3
  63. package/dist-lib/detox/index.cjs +895 -664
  64. package/dist-lib/dev-bundle-resolution.cjs +14 -2
  65. package/dist-lib/expo-swift-bundle.cjs +7818 -5916
  66. package/dist-lib/expo-swift-metadata.cjs +3 -1
  67. package/dist-lib/expo-swift-metadata.mjs +3 -1
  68. package/dist-lib/home-paths.cjs +32 -3
  69. package/dist-lib/host/bridge-host.cjs +8340 -6392
  70. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  71. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  72. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  73. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  74. package/dist-lib/host/websocket-proxy.cjs +1 -1
  75. package/dist-lib/index.cjs +812 -339
  76. package/dist-lib/jump-to-source-babel.cjs +1 -1
  77. package/dist-lib/jump-to-source-native.cjs +1 -1
  78. package/dist-lib/kotlin-host.cjs +1 -1
  79. package/dist-lib/menu.cjs +1 -1
  80. package/dist-lib/menu.mjs +1 -1
  81. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  82. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  83. package/dist-lib/metro-production-bundle.cjs +1 -1
  84. package/dist-lib/metro-production-bundle.mjs +1 -1
  85. package/dist-lib/metro.cjs +391 -28
  86. package/dist-lib/native-app-identity-project.cjs +55 -2
  87. package/dist-lib/native-app-identity-project.mjs +55 -2
  88. package/dist-lib/native-app-identity.cjs +53 -1
  89. package/dist-lib/native-app-identity.mjs +52 -1
  90. package/dist-lib/native-firebase-metadata.cjs +1 -1
  91. package/dist-lib/native-firebase-metadata.mjs +1 -1
  92. package/dist-lib/plist.cjs +1 -1
  93. package/dist-lib/plist.mjs +1 -1
  94. package/dist-lib/profiles.cjs +1 -1
  95. package/dist-lib/public-brand.cjs +1 -1
  96. package/dist-lib/react-native-host-modules.cjs +5 -1
  97. package/dist-lib/react-native-host-modules.mjs +5 -1
  98. package/dist-lib/scripts/dev-server-scanner.cjs +15 -3
  99. package/dist-lib/sdk.cjs +27 -11
  100. package/dist-lib/sdk.mjs +27 -10
  101. package/dist-lib/skills.cjs +12276 -8947
  102. package/dist-lib/swift-compile.cjs +924 -118
  103. package/dist-lib/swift-compile.mjs +966 -152
  104. package/dist-lib/swift-package.cjs +12 -3
  105. package/dist-lib/swift-package.mjs +12 -3
  106. package/dist-lib/swift-submit.cjs +7 -1
  107. package/dist-lib/swift-submit.mjs +5 -1
  108. package/dist-lib/swift-toolchain.cjs +133 -18
  109. package/dist-lib/swift.cjs +1 -1
  110. package/dist-lib/vite-swift.cjs +1955 -498
  111. package/dist-lib/vite.cjs +3 -1
  112. package/dist-lib/worklet-executable.cjs +1 -1
  113. package/dist-lib/worklet-executable.mjs +1 -1
  114. package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
  115. package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
  116. package/package.json +1 -1
  117. package/scripts/dev-server-scanner.ts +2 -1
  118. package/skills/peach-perf/SKILL.md +11 -5
  119. package/skills/peach-setup/SKILL.md +16 -19
  120. package/src/bridge-constants.ts +11 -0
  121. package/src/bridge-contract.ts +22 -34
  122. package/src/config.ts +15 -0
  123. package/src/desktop-host-delivery.ts +5 -1
  124. package/src/dev-bundle-resolution.ts +2 -1
  125. package/src/expo-manifest-body.ts +13 -0
  126. package/src/expo-swift-host.ts +191 -47
  127. package/src/expo-swift-ready.ts +78 -0
  128. package/src/expoSwiftContract.ts +13 -0
  129. package/src/home-paths.ts +48 -2
  130. package/src/host/bridge-host.ts +5 -2
  131. package/src/native-app-identity-project.ts +26 -2
  132. package/src/native-app-identity.ts +90 -0
  133. package/src/native-firebase-config.ts +209 -13
  134. package/src/process-group.ts +25 -2
  135. package/src/react-native-bridge-modules.ts +416 -0
  136. package/src/react-native-host-modules.ts +4 -0
  137. package/src/swift-binary-modules.ts +2 -2
  138. package/src/swift-compile.ts +835 -84
  139. package/src/swift-compiler-digest.ts +14 -0
  140. package/src/swift-distribution.lock.json +14 -12
  141. package/src/swift-host.ts +91 -29
  142. package/src/swift-icu.ts +14 -6
  143. package/src/swift-objc-sdk.ts +133 -10
  144. package/src/swift-objc-standin.ts +25 -3
  145. package/src/swift-package-manifest.ts +21 -6
  146. package/src/swift-package.ts +15 -1
  147. package/src/swift-packages.ts +119 -60
  148. package/src/swift-standin-emit.ts +57 -9
  149. package/src/swift-standins.ts +168 -20
  150. package/src/swift-submit.ts +10 -0
  151. package/src/swift-toolchain.ts +149 -23
  152. package/src/vite-plugin.ts +12 -1
@@ -0,0 +1,14 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { readFileSync } from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ // the lowering and manifest code in swift-compile.ts and its design-time lowering
6
+ // decide the emitted output, so their text addresses every swift artifact. bundled
7
+ // builds cannot read these files at runtime, so build-cli injects this digest.
8
+ export function swiftCompilerSourceDigest(srcDir: string): string {
9
+ const hash = createHash('sha256')
10
+ for (const file of ['swift-compile.ts', 'swift-design-time.ts']) {
11
+ hash.update(readFileSync(path.join(srcDir, file)))
12
+ }
13
+ return hash.digest('hex')
14
+ }
@@ -1,10 +1,10 @@
1
1
  {
2
- "id": "1fda1fb984f25758",
3
- "release": "swift-6.4.0-RELEASE",
4
- "wasmSdkId": "swift-6.4.0-RELEASE_wasm-peach-34b989c2",
5
- "wasmSdkUrl": "https://r2.sootsim.com/swift-wasm-sdk/swift-6.4.0-RELEASE_wasm-peach-34b989c2.artifactbundle.tar.gz",
6
- "wasmSdkChecksum": "1598ac9d063e07b20aa6232acb03c7147c9a94abf989d51f1e55688eb4aba854",
7
- "source": "078b4f63d3734871dc1fbea898005e2ef95a73cb",
2
+ "id": "4fdcd218fa180b52",
3
+ "release": "swift-6.4.0-RELEASE-custom-91504a66b3f012ac",
4
+ "wasmSdkId": "swift-6.4.0-RELEASE_wasm-peach-7fd9a101",
5
+ "wasmSdkUrl": "https://r2.sootsim.com/swift-wasm-sdk/swift-6.4.0-RELEASE_wasm-peach-7fd9a101.artifactbundle.tar.gz",
6
+ "wasmSdkChecksum": "72f7fec1d090d142d26148d6cc0cd5b1ce78da70bb97bd577dc1482eaf0299b2",
7
+ "source": "80c26dea3c4bbf0e29458c33a3a0e856b0942564",
8
8
  "targets": [
9
9
  "System",
10
10
  "_LocationEssentials",
@@ -36,12 +36,14 @@
36
36
  "FoundationNetworking",
37
37
  "DeveloperToolsSupport",
38
38
  "SwiftUI",
39
+ "os_workgroup",
39
40
  "Dispatch",
40
41
  "DarwinRecords",
41
42
  "Darwin",
42
43
  "MachO",
43
44
  "ObjectiveC",
44
45
  "PeachObjC",
46
+ "UIUtilities",
45
47
  "UIKitCore",
46
48
  "PeachICU",
47
49
  "PeachCMark",
@@ -179,16 +181,16 @@
179
181
  },
180
182
  "hosts": {
181
183
  "darwin-arm64": {
182
- "sha256": "6a3b956b13bcc76dbf0b55533088251e2ae29badcd5c8d66394a2459e0942a32",
183
- "size": 63180761
184
+ "sha256": "2b6768539f39bbdc2d77447fc91b00e9a7ee2d9ed732ea10e67a7b05c1c854ea",
185
+ "size": 69899802
184
186
  },
185
187
  "linux-x64": {
186
- "sha256": "37f26adff0a90c0647065b3c2e70bfdae05c84832b27add00915d441f2f79359",
187
- "size": 63183751
188
+ "sha256": "2d357dc6c24559b1e4f792c4142de2608e2dd91a2b322c74013af31a9346ad6e",
189
+ "size": 69902826
188
190
  },
189
191
  "linux-arm64": {
190
- "sha256": "e329424d19ede87f4c9257de40f88220cdaff42bd82f1ab47616394ec7f32f35",
191
- "size": 63181548
192
+ "sha256": "fdbdc2b25d102db38da6c00e74c6d198b87537aefe74365ef65793ec6feb3faa",
193
+ "size": 69900830
192
194
  }
193
195
  }
194
196
  }
package/src/swift-host.ts CHANGED
@@ -2,7 +2,6 @@ import { createHash } from 'node:crypto'
2
2
  // the node host for every Swift preview: package inputs, one compile and
3
3
  // immutable artifact addresses. vite and the foreground CLI adapt this host.
4
4
  import fs from 'node:fs'
5
- import os from 'node:os'
6
5
  import path from 'node:path'
7
6
  import {
8
7
  enumerateBundleColors,
@@ -11,8 +10,10 @@ import {
11
10
  enumerateBundleResources,
12
11
  resolveSwiftImageResourceUrls,
13
12
  } from '../../sootsim-swift/src/bundleAssets'
13
+ import { compileStringCatalog } from '../../sootsim-swift/src/stringCatalog'
14
14
  import { authHeaderValue, resolveCliAuth } from '../cli/auth'
15
15
  import { resolveContrastBackendOrigin } from './backend-origin'
16
+ import { cacheDir } from './home-paths'
16
17
  import iosSdk from './ios-sdk-frameworks.json' with { type: 'json' }
17
18
  import { swiftBinaryModules } from './swift-binary-modules'
18
19
  import {
@@ -39,13 +40,13 @@ import {
39
40
  } from './swift-package'
40
41
  import { swiftPackageManifest } from './swift-package-manifest'
41
42
  import {
42
- REMOTE_CHECKOUTS,
43
+ remoteSwiftCheckouts,
43
44
  resolveSwiftPackages,
44
45
  type SwiftPackageBinary,
45
46
  type SwiftPackageGap,
46
47
  type SwiftPackages,
47
48
  } from './swift-packages'
48
- import { readPackageFile } from './swift-source-file'
49
+ import { readPackageFile as readSwiftSourceFile } from './swift-source-file'
49
50
  import { readSwiftStandinBehavior } from './swift-standin-config'
50
51
  import {
51
52
  standinGenerationInputs,
@@ -68,6 +69,7 @@ import {
68
69
  MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES,
69
70
  isSwiftNativeModules,
70
71
  compileSwiftProject,
72
+ DEFAULT_SWIFT_COMPILE_BUILD_MS,
71
73
  httpSwiftCompileSubmit,
72
74
  swiftNativeEntry,
73
75
  type SwiftImageUpload,
@@ -242,6 +244,8 @@ interface XcodeProjectEntry {
242
244
  packages?: {
243
245
  key: string
244
246
  standinKey: string
247
+ /** generation always uses the original closure, before stand-ins replace targets. */
248
+ binaries: SwiftPackageBinary[]
245
249
  packages: SwiftPackages
246
250
  standins: SwiftStandin[]
247
251
  /** why a framework the project vendors got no stand-in, by name. */
@@ -324,16 +328,18 @@ function swiftPackages(
324
328
  // emitter), so a changed input rebuilds instead of reusing stale output.
325
329
  let cached = entry.packages
326
330
  if (cached?.key !== key) {
331
+ const packages = resolveSwiftPackages({
332
+ rootDir: app.rootDir,
333
+ resolved: app.packageResolved,
334
+ products: app.linkedProducts,
335
+ localDirs: app.localPackageDirs,
336
+ provided: entry.provided,
337
+ })
327
338
  cached = {
328
339
  key,
329
340
  standinKey: '',
330
- packages: resolveSwiftPackages({
331
- rootDir: app.rootDir,
332
- resolved: app.packageResolved,
333
- products: app.linkedProducts,
334
- localDirs: app.localPackageDirs,
335
- provided: entry.provided,
336
- }),
341
+ packages,
342
+ binaries: packages.binaries,
337
343
  standins: [],
338
344
  projectGaps: new Map(),
339
345
  }
@@ -360,10 +366,7 @@ function swiftPackages(
360
366
  ]
361
367
  : [],
362
368
  )
363
- const reached = [
364
- ...cached.packages.binaries.filter((binary) => binary.reached),
365
- ...vendored,
366
- ]
369
+ const reached = [...cached.binaries.filter((binary) => binary.reached), ...vendored]
367
370
  if (entry.moduleRoot && entry.provided && reached.length > 0) {
368
371
  const resolved = swiftModuleToolchain(entry.moduleRoot)
369
372
  const toolchain = {
@@ -385,6 +388,8 @@ function swiftPackages(
385
388
  ]),
386
389
  binaryGaps: built.gaps,
387
390
  })
391
+ for (const target of new Set(built.standins.map((standin) => standin.target)))
392
+ console.error(`Peach: target ${target} was replaced with a stand-in`)
388
393
  cached.standins = built.standins
389
394
  cached.projectGaps = new Map(
390
395
  vendored.flatMap(({ target }) => {
@@ -408,14 +413,17 @@ const XCODE_FRAMEWORKS_DIR = '__frameworks__'
408
413
  // remote package checkouts compile here: swiftpm owns `.build` in the
409
414
  // compile's package root as its scratch directory.
410
415
  const XCODE_PACKAGES_DIR = '__packages__'
411
- const CHECKOUTS_PREFIX = `${REMOTE_CHECKOUTS.split(path.sep).join(path.posix.sep)}/`
412
416
 
413
417
  // the compile input key of a root-relative path in an xcode layout.
414
418
  function xcodeInputKey(layout: XcodeLayout, file: string, relative: string): string {
415
419
  const owner = layout.targetDirs.get(file)
416
420
  if (owner) return path.posix.join(owner, relative)
417
- return relative.startsWith(CHECKOUTS_PREFIX)
418
- ? path.posix.join(XCODE_PACKAGES_DIR, relative.slice(CHECKOUTS_PREFIX.length))
421
+ const checkouts = remoteSwiftCheckouts(layout.app.rootDir)
422
+ return file.startsWith(`${checkouts}${path.sep}`)
423
+ ? path.posix.join(
424
+ XCODE_PACKAGES_DIR,
425
+ path.relative(checkouts, file).split(path.sep).join('/'),
426
+ )
419
427
  : relative
420
428
  }
421
429
 
@@ -489,6 +497,7 @@ function xcodeLayout(root: string): XcodeLayout | null {
489
497
  targets.push({
490
498
  name: standin.module,
491
499
  path: path.posix.join(SWIFT_STANDINS_DIR, standin.module),
500
+ dependencies: standin.dependencies?.map((name) => ({ kind: 'target', name })),
492
501
  executable: false,
493
502
  resources: false,
494
503
  exclude: [],
@@ -500,7 +509,7 @@ function xcodeLayout(root: string): XcodeLayout | null {
500
509
  const packageConfiguration = app.configuration === 'Debug' ? 'debug' : 'release'
501
510
  for (const target of packages.targets) {
502
511
  const relative = path.relative(
503
- target.remote ? path.join(root, REMOTE_CHECKOUTS) : root,
512
+ target.remote ? remoteSwiftCheckouts(root) : root,
504
513
  target.dir,
505
514
  )
506
515
  if (
@@ -524,6 +533,13 @@ function xcodeLayout(root: string): XcodeLayout | null {
524
533
  )
525
534
  .map((define) => define.name),
526
535
  ],
536
+ linkedLibraries: target.packageLibraries
537
+ .filter(
538
+ (library) =>
539
+ library.configuration === null ||
540
+ library.configuration === packageConfiguration,
541
+ )
542
+ .map((library) => library.name),
527
543
  })
528
544
  if (target.resources) {
529
545
  resourceBundles.push({
@@ -568,14 +584,19 @@ function xcodeManifest(layout: XcodeLayout): string {
568
584
  ...(target.mainActorIsolation ? ['.defaultIsolation(MainActor.self)'] : []),
569
585
  ...(target.defines ?? []).map((name) => `.define(${JSON.stringify(name)})`),
570
586
  ].join(', ')}]`
587
+ const libraries = (target: SwiftPackageTarget) =>
588
+ target.linkedLibraries?.length
589
+ ? `, linkerSettings: [${target.linkedLibraries.map((name) => `.linkedLibrary(${JSON.stringify(name)})`).join(', ')}]`
590
+ : ''
571
591
  return [
572
592
  '// swift-tools-version: 6.2',
573
593
  'import PackageDescription',
574
594
  'let package = Package(name: "app", targets: [',
575
595
  ` .executableTarget(name: "${layout.app.moduleName}", path: "${XCODE_APP_DIR}", ${settings(layout.app)}),`,
576
- ...layout.targets.map(
577
- (target) =>
578
- ` .target(name: "${target.name}", dependencies: ${JSON.stringify((target.dependencies ?? []).map((dependency) => dependency.name))}, path: "${target.path}", ${target.exclude.length > 0 ? `exclude: ${JSON.stringify(target.exclude)}, ` : ''}${target.resources ? 'resources: [], ' : ''}${settings(target)}),`,
596
+ ...layout.targets.map((target) =>
597
+ target.systemLibrary
598
+ ? ` .systemLibrary(name: "${target.name}", path: "${target.path}"),`
599
+ : ` .target(name: "${target.name}", dependencies: ${JSON.stringify((target.dependencies ?? []).map((dependency) => dependency.name))}, path: "${target.path}", ${target.exclude.length > 0 ? `exclude: ${JSON.stringify(target.exclude)}, ` : ''}${target.resources ? 'resources: [], ' : ''}${settings(target)}${libraries(target)}),`,
579
600
  ),
580
601
  '])',
581
602
  '',
@@ -590,6 +611,17 @@ function packageFiles(root: string): string[] {
590
611
  return xcode ? xcode.files : sourceTreeFiles(canonicalRoot, canonicalRoot)
591
612
  }
592
613
 
614
+ // remote dependencies are enumerated from peach's checkout cache, while app
615
+ // inputs retain their selected-project boundary and all symlink checks.
616
+ function readPackageFile(root: string, file: string, maxBytes: number): Buffer {
617
+ if (xcodeProjects.has(root)) {
618
+ const checkouts = remoteSwiftCheckouts(root)
619
+ if (file.startsWith(`${checkouts}${path.sep}`))
620
+ return readSwiftSourceFile(checkouts, file, maxBytes)
621
+ }
622
+ return readSwiftSourceFile(root, file, maxBytes)
623
+ }
624
+
593
625
  // every input file under `dir`, which must stay inside `canonicalRoot`.
594
626
  function sourceTreeFiles(dir: string, canonicalRoot: string): string[] {
595
627
  const found: string[] = []
@@ -983,6 +1015,13 @@ export function buildSwiftBundleFiles(
983
1015
  const disk = path.join(packagePath, ...file.split('/'))
984
1016
  const bytes = readPackageFile(packagePath, disk, limits.resourceBytes)
985
1017
  addFile(bundlePath === '.' ? base : `${bundlePath}/${base}`, bytes)
1018
+ if (base.endsWith('.xcstrings')) {
1019
+ for (const [name, table] of Object.entries(
1020
+ compileStringCatalog(bytes.toString('utf8'), base.slice(0, -10)),
1021
+ )) {
1022
+ addFile(bundlePath === '.' ? name : `${bundlePath}/${name}`, Buffer.from(table))
1023
+ }
1024
+ }
986
1025
  }
987
1026
  }
988
1027
  return { files, bundles, watched }
@@ -1048,6 +1087,24 @@ export function sourceStamp(
1048
1087
  return hash.digest('hex').slice(0, 16)
1049
1088
  }
1050
1089
 
1090
+ // sourceStamp's inputs read by content and relative to the package, so
1091
+ // another process's build copy of the same sources stamps the same.
1092
+ export function sourceContentStamp(
1093
+ packagePath: string,
1094
+ moduleRootOverride?: string,
1095
+ ): string {
1096
+ const hash = createHash('sha256')
1097
+ hash.update(JSON.stringify(xcodeProjects.get(packagePath)?.behavior ?? {}))
1098
+ const libraries = projects.get(packagePath)?.libraries
1099
+ if (libraries) hash.update(JSON.stringify(libraries))
1100
+ for (const file of [...new Set(packageFiles(packagePath))].sort()) {
1101
+ hash.update(path.relative(packagePath, file))
1102
+ hash.update(fs.readFileSync(file))
1103
+ }
1104
+ hash.update(moduleStamp(packagePath, moduleRootOverride, false))
1105
+ return hash.digest('hex').slice(0, 16)
1106
+ }
1107
+
1051
1108
  // the linked module's part of the source stamp
1052
1109
  function moduleStamp(
1053
1110
  packagePath: string,
@@ -1177,9 +1234,7 @@ function packageSnapshot(project: SwiftProject, entry: string): SwiftPackageSnap
1177
1234
  .relative(project.packagePath, file)
1178
1235
  .split(path.sep)
1179
1236
  .join(path.posix.sep)
1180
- return layout && file.endsWith('.metal')
1181
- ? xcodeInputKey(layout, file, relative)
1182
- : relative
1237
+ return layout ? xcodeInputKey(layout, file, relative) : relative
1183
1238
  }),
1184
1239
  ),
1185
1240
  resources: buildSwiftResourceTable(project, '', files),
@@ -1304,13 +1359,20 @@ async function compile(
1304
1359
  if (process.env.CONTRAST_SWIFT_BUILD_ROOT)
1305
1360
  return process.env.CONTRAST_SWIFT_BUILD_ROOT
1306
1361
  const resolvedPath = path.resolve(packagePath)
1362
+ // units of one app share a root so the module cache stays warm.
1363
+ // peach-expo-swift/<app>/<scope>/<package> shares <app>/<scope>, and
1364
+ // the older <app>/<package> layout shares <app>. the scope is this
1365
+ // process, so two servers do not compile into one tree.
1366
+ const parts = resolvedPath.split(path.sep)
1367
+ const expoAt = parts.lastIndexOf('peach-expo-swift')
1368
+ const scoped = expoAt >= 0 && parts.length >= expoAt + 4
1307
1369
  const buildKeyPath =
1308
- path.basename(path.dirname(path.dirname(resolvedPath))) === 'peach-expo-swift'
1309
- ? path.dirname(resolvedPath)
1370
+ expoAt >= 0 && parts.length >= expoAt + 3
1371
+ ? parts.slice(0, scoped ? expoAt + 3 : expoAt + 2).join(path.sep)
1310
1372
  : resolvedPath
1311
1373
  return path.join(
1312
- os.tmpdir(),
1313
- 'contrast-swift-build',
1374
+ cacheDir(),
1375
+ 'swift-builds',
1314
1376
  createHash('sha256')
1315
1377
  .update(`${moduleRoot}\0${buildKeyPath}`)
1316
1378
  .digest('hex')
@@ -1496,7 +1558,7 @@ async function compileRemote(
1496
1558
  hosted?.authorization ?? (auth ? authHeaderValue(auth) : undefined)
1497
1559
  const signal = AbortSignal.any([
1498
1560
  ...(ownerSignal ? [ownerSignal] : []),
1499
- AbortSignal.timeout(5 * 60_000),
1561
+ AbortSignal.timeout(DEFAULT_SWIFT_COMPILE_BUILD_MS + 60_000),
1500
1562
  ])
1501
1563
  const submit = httpSwiftCompileSubmit({
1502
1564
  endpoint: origin,
package/src/swift-icu.ts CHANGED
@@ -4,6 +4,7 @@ import { existsSync, readFileSync } from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import { promisify } from 'node:util'
6
6
  import { gzip } from 'node:zlib'
7
+ import { swiftSdkOptions } from './swift-toolchain.ts'
7
8
 
8
9
  /**
9
10
  * the icu common data a swift artifact reserves and its host fills
@@ -49,15 +50,21 @@ function memo<T>(
49
50
 
50
51
  /** the raw blob, for a harness that fills an artifact in node. */
51
52
  export function swiftIcuData(toolchainBin: string, sdk: string): Promise<Buffer> {
52
- return memo(raw, `${toolchainBin}\0${sdk}`, () => load(toolchainBin, sdk))
53
+ return memo(raw, JSON.stringify([toolchainBin, sdk, swiftSdkOptions()]), () =>
54
+ load(toolchainBin, sdk),
55
+ )
53
56
  }
54
57
 
55
58
  /** the served form: gzipped and content-addressed. */
56
59
  export function swiftIcuAsset(toolchainBin: string, sdk: string): Promise<SwiftIcuAsset> {
57
- return memo(assets, `${toolchainBin}\0${sdk}`, async () => {
58
- const zipped = await deflate(await swiftIcuData(toolchainBin, sdk))
59
- return { sha256: createHash('sha256').update(zipped).digest('hex'), gzip: zipped }
60
- })
60
+ return memo(
61
+ assets,
62
+ JSON.stringify([toolchainBin, sdk, swiftSdkOptions()]),
63
+ async () => {
64
+ const zipped = await deflate(await swiftIcuData(toolchainBin, sdk))
65
+ return { sha256: createHash('sha256').update(zipped).digest('hex'), gzip: zipped }
66
+ },
67
+ )
61
68
  }
62
69
 
63
70
  async function load(toolchainBin: string, sdk: string): Promise<Buffer> {
@@ -66,12 +73,13 @@ async function load(toolchainBin: string, sdk: string): Promise<Buffer> {
66
73
  // missing piece instead of shelling out to sdk configure.
67
74
  if (!existsSync(swift))
68
75
  throw new Error(`swift-icu: no swift at ${swift} for the ${sdk} SDK's icu data`)
69
- const { stdout: listed } = await run(swift, ['sdk', 'list'])
76
+ const { stdout: listed } = await run(swift, ['sdk', 'list', ...swiftSdkOptions()])
70
77
  if (!listed.split('\n').some((line) => line.trim() === sdk))
71
78
  throw new Error(`swift-icu: ${sdk} is not installed for ${swift}`)
72
79
  const { stdout } = await run(swift, [
73
80
  'sdk',
74
81
  'configure',
82
+ ...swiftSdkOptions(),
75
83
  '--show-configuration',
76
84
  sdk,
77
85
  'wasm32-unknown-wasip1',
@@ -11,6 +11,129 @@ export const IOS_SDK_TARGET = 'arm64-apple-ios16.0-simulator'
11
11
 
12
12
  /** the clang headers and module maps, by path under the sdk root. */
13
13
  export const IOS_SDK_HEADERS: Record<string, string> = {
14
+ // darwin pointer and mach port identities used in public native headers.
15
+ 'usr/include/pthread.h': `#pragma once
16
+ struct _opaque_pthread_t;
17
+ typedef struct _opaque_pthread_t *pthread_t;
18
+ `,
19
+ 'usr/include/mach/mach_types.h': `#pragma once
20
+ #include <stdint.h>
21
+ typedef unsigned int natural_t;
22
+ typedef int integer_t;
23
+ typedef int vm_prot_t;
24
+ typedef integer_t cpu_type_t;
25
+ typedef integer_t cpu_subtype_t;
26
+ typedef natural_t *thread_state_t;
27
+ typedef int thread_state_flavor_t;
28
+ typedef natural_t mach_port_t;
29
+ typedef mach_port_t thread_t;
30
+ typedef thread_t thread_act_t;
31
+ typedef thread_act_t *thread_act_array_t;
32
+ typedef natural_t mach_msg_type_number_t;
33
+ `,
34
+ 'usr/include/mach/mach.h': `#pragma once
35
+ #include <mach/mach_types.h>
36
+ `,
37
+ 'usr/include/TargetConditionals.h': `#pragma once
38
+ #define TARGET_OS_IPHONE 1
39
+ #define TARGET_OS_IOS 1
40
+ #define TARGET_OS_SIMULATOR 1
41
+ #define TARGET_OS_OSX 0
42
+ #define TARGET_OS_MAC 1
43
+ #define TARGET_OS_TV 0
44
+ #define TARGET_OS_WATCH 0
45
+ #define TARGET_OS_VISION 0
46
+ #define TARGET_OS_MACCATALYST 0
47
+ #define TARGET_IPHONE_SIMULATOR 1
48
+ #define TARGET_CPU_ARM64 1
49
+ #define TARGET_CPU_X86_64 0
50
+ #define TARGET_RT_64_BIT 1
51
+ `,
52
+ 'usr/include/stdlib.h': `#pragma once
53
+ #include <stddef.h>
54
+ void exit(int) __attribute__((noreturn));
55
+ void abort(void) __attribute__((noreturn));
56
+ void *malloc(size_t);
57
+ void free(void *);
58
+ `,
59
+ 'usr/include/unistd.h': `#pragma once
60
+ #include <stdlib.h>
61
+ void _exit(int) __attribute__((noreturn));
62
+ `,
63
+ 'usr/include/string.h': `#pragma once
64
+ #include <stddef.h>
65
+ size_t strlen(const char *);
66
+ char *strdup(const char *);
67
+ void *memcpy(void *, const void *, size_t);
68
+ void *memset(void *, int, size_t);
69
+ int strcmp(const char *, const char *);
70
+ `,
71
+ 'usr/include/sys/types.h': `#pragma once
72
+ #include <time.h>
73
+ #include <stddef.h>
74
+ typedef long ssize_t;
75
+ `,
76
+ 'usr/include/time.h': `#pragma once
77
+ typedef long time_t;
78
+ `,
79
+ 'usr/include/dlfcn.h': `#pragma once
80
+ typedef struct dl_info {
81
+ const char *dli_fname;
82
+ void *dli_fbase;
83
+ const char *dli_sname;
84
+ void *dli_saddr;
85
+ } Dl_info;
86
+ `,
87
+ 'usr/include/sys/ucontext.h': `#pragma once
88
+ #include <stdint.h>
89
+ #define _STRUCT_ARM_EXCEPTION_STATE64 struct __darwin_arm_exception_state64
90
+ _STRUCT_ARM_EXCEPTION_STATE64 { uint64_t __far; uint32_t __esr, __exception; };
91
+ #define _STRUCT_ARM_THREAD_STATE64 struct __darwin_arm_thread_state64
92
+ _STRUCT_ARM_THREAD_STATE64 {
93
+ uint64_t __x[29];
94
+ uint64_t __fp, __lr, __sp, __pc;
95
+ uint32_t __cpsr, __pad;
96
+ };
97
+ #define _STRUCT_ARM_NEON_STATE64 struct __darwin_arm_neon_state64
98
+ _STRUCT_ARM_NEON_STATE64 { unsigned __int128 __v[32]; uint32_t __fpsr, __fpcr; };
99
+ #define _STRUCT_MCONTEXT64 struct __darwin_mcontext64
100
+ _STRUCT_MCONTEXT64 {
101
+ _STRUCT_ARM_EXCEPTION_STATE64 __es;
102
+ _STRUCT_ARM_THREAD_STATE64 __ss;
103
+ _STRUCT_ARM_NEON_STATE64 __ns;
104
+ };
105
+ `,
106
+ 'usr/include/mach-o/loader.h': `#pragma once
107
+ #include <stdint.h>
108
+ #include <mach/mach_types.h>
109
+ #define LC_SEGMENT_64 0x19
110
+ struct mach_header_64 {
111
+ uint32_t magic;
112
+ int32_t cputype, cpusubtype;
113
+ uint32_t filetype, ncmds, sizeofcmds, flags, reserved;
114
+ };
115
+ struct segment_command_64 {
116
+ uint32_t cmd, cmdsize;
117
+ char segname[16];
118
+ uint64_t vmaddr, vmsize, fileoff, filesize;
119
+ int32_t maxprot, initprot;
120
+ uint32_t nsects, flags;
121
+ };
122
+ struct section_64 {
123
+ char sectname[16], segname[16];
124
+ uint64_t addr, size;
125
+ uint32_t offset, align, reloff, nreloc, flags, reserved1, reserved2, reserved3;
126
+ };
127
+ `,
128
+ 'usr/include/mach-o/nlist.h': `#pragma once
129
+ #include <stdint.h>
130
+ struct nlist_64 {
131
+ union { uint32_t n_strx; } n_un;
132
+ uint8_t n_type, n_sect;
133
+ uint16_t n_desc;
134
+ uint64_t n_value;
135
+ };
136
+ `,
14
137
  'usr/include/module.modulemap': `module ObjectiveC [system] {
15
138
  header "objc/NSObject.h"
16
139
  export *
@@ -67,6 +190,10 @@ __attribute__((objc_root_class))
67
190
  @end
68
191
  @interface Protocol : NSObject
69
192
  @end
193
+ `,
194
+ 'System/Library/Frameworks/CoreFoundation.framework/Headers/CoreFoundation.h': `#pragma once
195
+ #import <Foundation/Foundation.h>
196
+ typedef const struct __attribute__((objc_bridge(NSString))) __CFString *CFStringRef;
70
197
  `,
71
198
  'System/Library/Frameworks/CoreGraphics.framework/Headers/CoreGraphics.h': `#pragma once
72
199
  #include <stdint.h>
@@ -99,9 +226,15 @@ typedef struct __attribute__((objc_bridge(id))) CGColorSpace *CGColorSpaceRef;
99
226
  #include <stddef.h>
100
227
  #include <stdbool.h>
101
228
  #include <stdarg.h>
229
+ #include <stdlib.h>
230
+ #include <string.h>
231
+ #include <time.h>
232
+ #include <TargetConditionals.h>
102
233
  #import <objc/NSObject.h>
103
234
  #import <CoreGraphics/CoreGraphics.h>
104
235
 
236
+ @class NSException;
237
+ typedef void NSUncaughtExceptionHandler(NSException *exception);
105
238
  #define FOUNDATION_EXPORT extern
106
239
  #define FOUNDATION_EXTERN extern
107
240
  #define FOUNDATION_STATIC_INLINE static inline
@@ -237,16 +370,6 @@ typedef struct __attribute__((objc_bridge(id))) CGColorSpace *CGColorSpaceRef;
237
370
  #define CF_IMPLICIT_BRIDGING_DISABLED _Pragma("clang arc_cf_code_audited end")
238
371
  #define NS_NONATOMIC_IOSONLY nonatomic
239
372
  #define NS_UNAVAILABLE_INIT
240
- #define TARGET_OS_IPHONE 1
241
- #define TARGET_OS_IOS 1
242
- #define TARGET_OS_SIMULATOR 1
243
- #define TARGET_OS_OSX 0
244
- #define TARGET_OS_MAC 1
245
- #define TARGET_OS_TV 0
246
- #define TARGET_OS_WATCH 0
247
- #define TARGET_OS_VISION 0
248
- #define TARGET_OS_MACCATALYST 0
249
- #define TARGET_IPHONE_SIMULATOR 1
250
373
  #define NSIntegerMax LONG_MAX
251
374
  #define NSIntegerMin LONG_MIN
252
375
  #define NSUIntegerMax ULONG_MAX
@@ -26,6 +26,7 @@ export interface ObjcSurface {
26
26
  module: string
27
27
  modulemap: string
28
28
  headers: string
29
+ dependencies?: { module: string; headers: string }[]
29
30
  }
30
31
 
31
32
  export interface ObjcView {
@@ -123,7 +124,7 @@ export function objcView(
123
124
  const include = path.join(work, 'include')
124
125
  const headersDir = framework
125
126
  ? path.join(frameworks, `${surface.module}.framework`, 'Headers')
126
- : include
127
+ : path.join(include, surface.module)
127
128
  const originals = new Map(
128
129
  headerFiles(surface.headers).map((file) => [
129
130
  file,
@@ -143,7 +144,22 @@ export function objcView(
143
144
  path.join(frameworks, `${surface.module}.framework`, 'Modules', 'module.modulemap'),
144
145
  )
145
146
  } else {
146
- fs.copyFileSync(surface.modulemap, path.join(include, 'module.modulemap'))
147
+ fs.copyFileSync(surface.modulemap, path.join(headersDir, 'module.modulemap'))
148
+ }
149
+ for (const dependency of surface.dependencies ?? []) {
150
+ const dir = path.join(include, dependency.module)
151
+ fs.mkdirSync(dir, { recursive: true })
152
+ for (const file of headerFiles(dependency.headers)) {
153
+ fs.mkdirSync(path.dirname(path.join(dir, file)), { recursive: true })
154
+ fs.copyFileSync(path.join(dependency.headers, file), path.join(dir, file))
155
+ }
156
+ const supplied = path.join(dependency.headers, 'module.modulemap')
157
+ fs.writeFileSync(
158
+ path.join(dir, 'module.modulemap'),
159
+ fs.existsSync(supplied)
160
+ ? fs.readFileSync(supplied, 'utf8')
161
+ : `module ${dependency.module} { umbrella "." export * }\n`,
162
+ )
147
163
  }
148
164
  const stubs = path.join(work, 'stubs')
149
165
  const stubIncludes = path.join(work, 'stub-include')
@@ -158,6 +174,12 @@ export function objcView(
158
174
  frameworks,
159
175
  '-I',
160
176
  include,
177
+ '-I',
178
+ headersDir,
179
+ ...(surface.dependencies ?? []).flatMap((dependency) => [
180
+ '-I',
181
+ path.join(include, dependency.module),
182
+ ]),
161
183
  '-F',
162
184
  stubs,
163
185
  '-I',
@@ -300,7 +322,7 @@ export function objcView(
300
322
  }
301
323
  if (!progress || round === ROUNDS)
302
324
  throw new Error(
303
- `its headers do not import as Swift: ${[...new Set(errors.map((match) => match[4]!))].slice(0, 3).join('; ')}`,
325
+ `its headers do not import as Swift: ${[...new Set(errors.map((match) => match[4]!))].slice(0, 10).join('; ')}`,
304
326
  )
305
327
  if (missing.size > 0) declareMissing()
306
328
  }