peachsim 0.1.1635 → 0.1.1641

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 (142) hide show
  1. package/README.md +10 -1
  2. package/cli/app-config.ts +14 -5
  3. package/cli/app-stubs.ts +96 -22
  4. package/cli/bridge-flow-runner.ts +2 -2
  5. package/cli/cloud-client.ts +60 -40
  6. package/cli/cloud-session.ts +16 -0
  7. package/cli/commands/assert.ts +17 -4
  8. package/cli/commands/box.ts +33 -19
  9. package/cli/commands/doctor.ts +37 -20
  10. package/cli/commands/flow.ts +1 -1
  11. package/cli/commands/inspect/actions.ts +3 -2
  12. package/cli/commands/inspect/core.ts +0 -8
  13. package/cli/commands/inspect/shared.ts +9 -4
  14. package/cli/commands/inspect.ts +65 -61
  15. package/cli/commands/platform.ts +2 -1
  16. package/cli/commands/preview.ts +1 -1
  17. package/cli/commands/record.ts +154 -68
  18. package/cli/commands/swift.ts +18 -4
  19. package/cli/commands/three-mode.ts +4 -38
  20. package/cli/commands/upload.ts +55 -49
  21. package/cli/drivers/playwright-sim-host.ts +65 -42
  22. package/cli/flow-live-status.ts +0 -2
  23. package/cli/main.ts +2 -2
  24. package/cli/outbound-endpoints.ts +7 -5
  25. package/cli/pause-aware-timeout.ts +4 -0
  26. package/cli/ws-bridge.ts +19 -16
  27. package/detox/attempt-events.cjs +99 -6
  28. package/detox/attempt-events.d.cts +7 -0
  29. package/detox/expectations.ts +7 -3
  30. package/detox/index.ts +259 -139
  31. package/detox/jest-environment.cjs +5 -1
  32. package/detox/motion-change.cjs +170 -21
  33. package/detox/motion-change.d.cts +32 -0
  34. package/detox/navigation-lifecycle.ts +35 -3
  35. package/dist-lib/agent-daemon-client.cjs +1 -1
  36. package/dist-lib/agent-events.cjs +1 -1
  37. package/dist-lib/attached-projects.cjs +1 -1
  38. package/dist-lib/auth/shared-session.cjs +1 -1
  39. package/dist-lib/backend-origin.cjs +17 -7
  40. package/dist-lib/beta.cjs +1 -1
  41. package/dist-lib/beta.mjs +1 -1
  42. package/dist-lib/bridge-constants.cjs +1 -1
  43. package/dist-lib/bridge-contract-input.cjs +1 -1
  44. package/dist-lib/bridge-contract-input.mjs +1 -1
  45. package/dist-lib/bridge-contract.cjs +1 -1
  46. package/dist-lib/bridge-contract.mjs +1 -1
  47. package/dist-lib/bundle-channel.cjs +5014 -2268
  48. package/dist-lib/capture-contract.cjs +1 -1
  49. package/dist-lib/capture-contract.mjs +1 -1
  50. package/dist-lib/cloud-contract.cjs +1 -1
  51. package/dist-lib/cloud-contract.mjs +1 -1
  52. package/dist-lib/cloud-test-args.cjs +1 -1
  53. package/dist-lib/cloud-test-args.mjs +1 -1
  54. package/dist-lib/cloud.cjs +1 -1
  55. package/dist-lib/cloud.mjs +1 -1
  56. package/dist-lib/config.cjs +1 -1
  57. package/dist-lib/config.mjs +1 -1
  58. package/dist-lib/detox/index.cjs +1036 -166
  59. package/dist-lib/detox/jest-environment.cjs +5 -1
  60. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  61. package/dist-lib/expo-swift-bundle.cjs +4613 -1963
  62. package/dist-lib/expo-swift-metadata.cjs +9 -7
  63. package/dist-lib/expo-swift-metadata.mjs +9 -7
  64. package/dist-lib/home-paths.cjs +1 -1
  65. package/dist-lib/host/bridge-host.cjs +58072 -16400
  66. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  67. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  68. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  69. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  70. package/dist-lib/host/websocket-proxy.cjs +1 -1
  71. package/dist-lib/index.cjs +2790 -143
  72. package/dist-lib/jump-to-source-babel.cjs +1 -1
  73. package/dist-lib/jump-to-source-native.cjs +1 -1
  74. package/dist-lib/kotlin-host.cjs +1 -1
  75. package/dist-lib/menu.cjs +1 -1
  76. package/dist-lib/menu.mjs +1 -1
  77. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  78. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  79. package/dist-lib/metro-production-bundle.cjs +1 -1
  80. package/dist-lib/metro-production-bundle.mjs +1 -1
  81. package/dist-lib/metro.cjs +2579 -95
  82. package/dist-lib/native-app-identity-project.cjs +243 -53
  83. package/dist-lib/native-app-identity-project.mjs +243 -53
  84. package/dist-lib/native-app-identity.cjs +29 -30
  85. package/dist-lib/native-app-identity.mjs +29 -30
  86. package/dist-lib/native-firebase-metadata.cjs +1 -1
  87. package/dist-lib/native-firebase-metadata.mjs +1 -1
  88. package/dist-lib/plist.cjs +1 -1
  89. package/dist-lib/plist.mjs +1 -1
  90. package/dist-lib/profiles.cjs +1 -1
  91. package/dist-lib/public-brand.cjs +1 -1
  92. package/dist-lib/react-native-host-modules.cjs +2 -2
  93. package/dist-lib/react-native-host-modules.mjs +2 -2
  94. package/dist-lib/scripts/dev-server-scanner.cjs +8 -6
  95. package/dist-lib/sdk.cjs +4 -3
  96. package/dist-lib/sdk.mjs +4 -3
  97. package/dist-lib/skills.cjs +66475 -23097
  98. package/dist-lib/swift-compile.cjs +452 -260
  99. package/dist-lib/swift-compile.mjs +451 -252
  100. package/dist-lib/swift-package.cjs +1 -1
  101. package/dist-lib/swift-package.mjs +1 -1
  102. package/dist-lib/swift-submit.cjs +14 -7
  103. package/dist-lib/swift-submit.mjs +14 -7
  104. package/dist-lib/swift-toolchain.cjs +316 -41
  105. package/dist-lib/swift.cjs +1 -1
  106. package/dist-lib/vite-swift.cjs +8188 -5636
  107. package/dist-lib/vite.cjs +2857 -12
  108. package/dist-lib/worklet-executable.cjs +14 -2
  109. package/dist-lib/worklet-executable.mjs +14 -2
  110. package/package.json +1 -1
  111. package/src/backend-origin.ts +21 -6
  112. package/src/expo-swift-host.ts +5 -2
  113. package/src/expoKotlinContract.ts +4 -7
  114. package/src/expoSwiftContract.ts +4 -6
  115. package/src/host/staged-host-files.ts +9 -0
  116. package/src/native-app-identity-project.ts +1 -4
  117. package/src/native-app-identity.ts +1 -4
  118. package/src/nativeAppIconContract.ts +1 -4
  119. package/src/output-lock-stdio.ts +36 -0
  120. package/src/react-native-bridge-modules.ts +106 -78
  121. package/src/react-native-host-modules.ts +4 -1
  122. package/src/settings-bundle.ts +1 -4
  123. package/src/swift-compile.ts +224 -139
  124. package/src/swift-distribution.lock.json +23 -12
  125. package/src/swift-distribution.ts +103 -4
  126. package/src/swift-host.ts +95 -30
  127. package/src/swift-icu.ts +16 -10
  128. package/src/swift-objc-compile.ts +547 -0
  129. package/src/swift-objc-metadata.ts +204 -0
  130. package/src/swift-objc-sdk.ts +883 -19
  131. package/src/swift-objc-standin.ts +126 -14
  132. package/src/swift-package-manifest.ts +42 -1
  133. package/src/swift-packages.ts +35 -21
  134. package/src/swift-root-lock.ts +153 -0
  135. package/src/swift-standin-emit.ts +40 -7
  136. package/src/swift-standins.ts +404 -138
  137. package/src/swift-submit.ts +18 -7
  138. package/src/swift-toolchain.ts +378 -46
  139. package/src/swiftContract.ts +12 -0
  140. package/src/worker-system-chunks.ts +11 -12
  141. package/src/worklet-executable.ts +19 -1
  142. package/src/xcode-project.ts +280 -33
@@ -92,6 +92,7 @@ interface Scope {
92
92
 
93
93
  type Binding =
94
94
  | { kind: 'value'; init: SyntaxNode; scope: Scope }
95
+ | { kind: 'uninitialized' }
95
96
  // `key` is set when the parameter is a destructured member of that argument.
96
97
  | { kind: 'param'; fn: SyntaxNode; index: number; key?: string }
97
98
  | { kind: 'opaque' }
@@ -106,6 +107,9 @@ interface InitCollection {
106
107
  writes: Array<{ name: string; scope: Scope; value?: SyntaxNode }>
107
108
  // bindings written after their declaration; their value is not static.
108
109
  reassigned: Set<Binding>
110
+ // rolldown hoists declarations out of esm initializers. a single plain
111
+ // assignment still identifies the literal; multiple writes remain opaque.
112
+ assigned: Map<Binding, { value: SyntaxNode; scope: Scope } | null>
109
113
  // names the parsed source never declares but writes exactly once with a plain
110
114
  // assignment. a bundler that hoists a module's declarations out of its
111
115
  // initializer leaves only that write in the initializer.
@@ -227,6 +231,8 @@ function enter(node: SyntaxNode, scope: Scope): Scope {
227
231
  const name = identifierName(id)
228
232
  if (name !== undefined && isNode(init))
229
233
  declare(target, name, { kind: 'value', init, scope })
234
+ else if (name !== undefined && init === null)
235
+ declare(target, name, { kind: 'uninitialized' })
230
236
  else declareOpaque(target, patternNames(id))
231
237
  }
232
238
  return scope
@@ -311,12 +317,18 @@ function collectInitData(root: SyntaxNode): InitCollection {
311
317
  immediate: new Map(),
312
318
  writes: [],
313
319
  reassigned: new Set(),
320
+ assigned: new Map(),
314
321
  free: new Map(),
315
322
  }
316
323
  visit(root, newScope(undefined, true), collection)
317
324
  for (const { name, scope, value } of collection.writes) {
318
325
  const binding = lookup(scope, name)
319
- if (binding) collection.reassigned.add(binding)
326
+ if (binding?.kind === 'uninitialized')
327
+ collection.assigned.set(
328
+ binding,
329
+ collection.assigned.has(binding) || !value ? null : { value, scope },
330
+ )
331
+ else if (binding) collection.reassigned.add(binding)
320
332
  else
321
333
  collection.free.set(
322
334
  name,
@@ -415,6 +427,12 @@ function resolveObject(
415
427
  : undefined
416
428
  }
417
429
  if (collection.reassigned.has(binding)) return undefined
430
+ if (binding.kind === 'uninitialized') {
431
+ const assigned = collection.assigned.get(binding)
432
+ return assigned
433
+ ? resolveObject(assigned.value, assigned.scope, collection, depth + 1)
434
+ : undefined
435
+ }
418
436
  if (binding.kind === 'value')
419
437
  return resolveObject(binding.init, binding.scope, collection, depth + 1)
420
438
  if (binding.kind === 'param') {
@@ -1,11 +1,14 @@
1
1
  // reads an xcode project's application target the way xcode builds it: the
2
2
  // swift sources and resources that are members of the target, the local
3
3
  // swift packages its products come from, the remote package products it links,
4
- // and the build settings that change how its swift compiles. never runs xcode
5
- // or evaluates a build phase; the project file is data: project.pbxproj, or
4
+ // and the build settings that change how its swift compiles. project opening
5
+ // prepares declared generated swift sources without running xcode. the project
6
+ // file is data: project.pbxproj, or
6
7
  // xcode 27.2's json project.xcproj, which xcode-json-project.ts reads into
7
8
  // the same object table.
8
9
 
10
+ import { execFileSync } from 'node:child_process'
11
+ import { createHash } from 'node:crypto'
9
12
  import fs from 'node:fs'
10
13
  import path from 'node:path'
11
14
  import plist from 'plist'
@@ -45,6 +48,19 @@ export interface XcodeModuleTarget {
45
48
  mainActorIsolation: boolean
46
49
  /** the compilation conditions the build configuration defines, as `-D` passes them. */
47
50
  defines: string[]
51
+ /** the resolved source iOS minimum, including for native package dependencies. */
52
+ iosDeploymentTarget: string | null
53
+ /** original implementation inputs and clang settings, before any declaration import. */
54
+ objc?: {
55
+ headers: string[]
56
+ prefixHeader: string | null
57
+ bridgingHeader: string | null
58
+ headerSearchPaths: string[]
59
+ frameworkSearchPaths: string[]
60
+ flags: string[]
61
+ cxxFlags: string[]
62
+ fileFlags: Record<string, string[]>
63
+ }
48
64
  }
49
65
 
50
66
  /** an application target, resolved to files on disk with everything it builds. */
@@ -72,8 +88,8 @@ export interface XcodeAppTarget extends XcodeModuleTarget {
72
88
  localPackageDirs: string[]
73
89
  /** prebuilt frameworks and xcframeworks the projects vendor and link, at their path when it resolves. */
74
90
  binaryFrameworks: { name: string; path: string | null }[]
75
- /** the Package.resolved pinning the remote packages' revisions, when resolved. */
76
- packageResolved: string | null
91
+ /** workspace pins, or local package locks when the workspace has no lock. */
92
+ packageResolved: string[]
77
93
  /** every application target in the workspace, for a chooser. */
78
94
  appTargets: string[]
79
95
  /** the build configuration every target's settings are read in. */
@@ -383,15 +399,22 @@ function targetSettings(
383
399
  if (!config) return []
384
400
  const name = str(config.name) ?? configuration
385
401
  const base = str(config.baseConfigurationReference)
402
+ const anchor = str(config.baseConfigurationReferenceAnchor)
403
+ const relative = str(config.baseConfigurationReferenceRelativePath)
386
404
  const own = isDict(config.buildSettings) ? config.buildSettings : {}
387
- const baseFile = base ? project.resolvePath(base) : null
405
+ const directory = anchor ? project.resolvePath(anchor) : null
406
+ const baseFile = base
407
+ ? project.resolvePath(base)
408
+ : directory && relative
409
+ ? path.resolve(directory, relative)
410
+ : null
388
411
  if (
389
412
  options.requireSources !== false &&
390
- base &&
413
+ (base || anchor || relative) &&
391
414
  (!baseFile || !fs.existsSync(baseFile))
392
415
  )
393
416
  throw new Error(
394
- `Unable to open base configuration reference file '${baseFile ?? base}'`,
417
+ `Unable to open base configuration reference file '${baseFile ?? base ?? relative ?? anchor}'`,
395
418
  )
396
419
  const read: string[] = []
397
420
  const fromFile = baseFile ? readXcconfig(baseFile, read) : {}
@@ -498,14 +521,17 @@ function expand(value: string, lookup: (name: string) => string): string {
498
521
  )
499
522
  }
500
523
 
524
+ const preparedSourcePhases = new Map<string, string>()
525
+
501
526
  // the swift sources and resources that are members of a target.
502
- // run script phases never execute here; their declared swift outputs are
503
- // compile inputs xcode generates before compiling, so a missing one names
504
- // the script instead of silently dropping the file.
527
+ // metadata-only reads never execute scripts. opening a project prepares the
528
+ // declared swift outputs with the project's own shell and build settings.
505
529
  function targetMembers(
506
530
  ref: TargetRef,
507
531
  settings: (key: string) => string,
508
- options: { requireSources?: boolean } = {},
532
+ options: { requireSources?: boolean; prepareGeneratedSources?: boolean },
533
+ settingKeys: Set<string>,
534
+ projectInputs: Set<string>,
509
535
  ): {
510
536
  sources: string[]
511
537
  resources: string[]
@@ -517,30 +543,137 @@ function targetMembers(
517
543
  const allSources = new Set<string>()
518
544
  const resources = new Set<string>()
519
545
  const scriptForOutput = new Map<string, string>()
520
- const noteScriptOutput = (raw: string, script: string) => {
521
- const expanded = expand(raw, settings)
522
- if (!expanded.endsWith('.swift')) return
523
- const absolute = path.isAbsolute(expanded)
524
- ? path.normalize(expanded)
525
- : path.resolve(project.projectDir, expanded)
526
- if (!scriptForOutput.has(absolute)) scriptForOutput.set(absolute, script)
527
- }
528
546
  for (const phaseId of list(target.buildPhases)) {
529
547
  const phase = project.object(phaseId)
530
548
  if (phase?.isa !== 'PBXShellScriptBuildPhase') continue
531
549
  const script = str(phase.name) ?? phaseId
532
- for (const raw of list(phase.outputPaths)) noteScriptOutput(raw, script)
533
- for (const listPath of list(phase.outputFileListPaths)) {
534
- const expandedList = expand(listPath, settings)
535
- const absoluteList = path.isAbsolute(expandedList)
536
- ? expandedList
537
- : path.resolve(project.projectDir, expandedList)
538
- if (!fs.existsSync(absoluteList)) continue
539
- for (const line of fs.readFileSync(absoluteList, 'utf8').split('\n')) {
540
- const trimmed = line.trim()
541
- if (trimmed === '' || trimmed.startsWith('#')) continue
542
- noteScriptOutput(trimmed, script)
550
+ const absolute = (raw: string) =>
551
+ path.resolve(project.projectDir, expand(raw, settings))
552
+ const inputs = list(phase.inputPaths).map(absolute)
553
+ const outputs = list(phase.outputPaths).map(absolute)
554
+ const fileLists = new Map<string, { file: string; entries: string[] }[]>()
555
+ for (const kind of ['INPUT', 'OUTPUT']) {
556
+ const lists = list(
557
+ kind === 'INPUT' ? phase.inputFileListPaths : phase.outputFileListPaths,
558
+ ).map((raw) => {
559
+ const file = absolute(raw)
560
+ if (!fs.existsSync(file)) {
561
+ return { file, entries: [] }
562
+ }
563
+ const entries = fs
564
+ .readFileSync(file, 'utf8')
565
+ .split(/\r?\n/)
566
+ .map((line) => line.trim())
567
+ .filter((line) => line !== '' && !line.startsWith('#'))
568
+ .map(absolute)
569
+ return { file, entries }
570
+ })
571
+ fileLists.set(kind, lists)
572
+ }
573
+ const allOutputs = [
574
+ ...outputs,
575
+ ...fileLists.get('OUTPUT')!.flatMap((item) => item.entries),
576
+ ]
577
+ for (const output of allOutputs)
578
+ if (output.endsWith('.swift') && !scriptForOutput.has(output))
579
+ scriptForOutput.set(output, script)
580
+ const inputFiles = [
581
+ ...inputs,
582
+ ...fileLists.get('INPUT')!.flatMap((item) => [item.file, ...item.entries]),
583
+ ...fileLists.get('OUTPUT')!.map((item) => item.file),
584
+ ]
585
+ if (allOutputs.some((file) => file.endsWith('.swift')))
586
+ for (const file of inputFiles) if (fs.existsSync(file)) projectInputs.add(file)
587
+ if (
588
+ options.prepareGeneratedSources &&
589
+ options.requireSources !== false &&
590
+ allOutputs.some((file) => file.endsWith('.swift'))
591
+ ) {
592
+ const phaseDir = path.join(settings('TARGET_TEMP_DIR'), `Script-${phaseId}`)
593
+ for (const lists of fileLists.values())
594
+ for (const { file } of lists)
595
+ if (!fs.existsSync(file))
596
+ throw new Error(
597
+ `${targetName}: Run Script "${script}" missing file list ${file}`,
598
+ )
599
+ const env = {
600
+ ...process.env,
601
+ ...Object.fromEntries(
602
+ [...settingKeys]
603
+ .filter((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
604
+ .map((key) => [key, settings(key)]),
605
+ ),
606
+ }
607
+ const declared = { INPUT: inputs, OUTPUT: outputs }
608
+ for (const kind of ['INPUT', 'OUTPUT'] satisfies (keyof typeof declared)[]) {
609
+ env[`SCRIPT_${kind}_FILE_COUNT`] = String(declared[kind].length)
610
+ declared[kind].forEach((file, index) => {
611
+ env[`SCRIPT_${kind}_FILE_${index}`] = file
612
+ })
613
+ const lists = fileLists.get(kind)!
614
+ env[`SCRIPT_${kind}_FILE_LIST_COUNT`] = String(lists.length)
615
+ lists.forEach((_item, index) => {
616
+ env[`SCRIPT_${kind}_FILE_LIST_${index}`] = path.join(
617
+ phaseDir,
618
+ `${kind}-${index}.xcfilelist`,
619
+ )
620
+ })
621
+ }
622
+ const signature = createHash('sha256').update(
623
+ JSON.stringify([phase, [...settingKeys].map((key) => [key, settings(key)])]),
624
+ )
625
+ for (const file of inputFiles) {
626
+ signature.update(file)
627
+ if (fs.existsSync(file)) {
628
+ const stat = fs.statSync(file)
629
+ signature.update(`${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`)
630
+ } else signature.update('missing')
631
+ }
632
+ const stamp = signature.digest('hex')
633
+ const key = `${project.projectFile}:${targetId}:${phaseId}:${settings('CONFIGURATION')}`
634
+ if (
635
+ preparedSourcePhases.get(key) === stamp &&
636
+ allOutputs.every((file) => fs.existsSync(file))
637
+ )
638
+ continue
639
+ fs.mkdirSync(phaseDir, { recursive: true })
640
+ fs.mkdirSync(settings('BUILT_PRODUCTS_DIR'), { recursive: true })
641
+ fs.mkdirSync(settings('DERIVED_FILE_DIR'), { recursive: true })
642
+ for (const [kind, lists] of fileLists)
643
+ lists.forEach((item, index) => {
644
+ fs.writeFileSync(
645
+ env[`SCRIPT_${kind}_FILE_LIST_${index}`]!,
646
+ `${item.entries.join('\n')}\n`,
647
+ )
648
+ })
649
+ try {
650
+ execFileSync(
651
+ str(phase.shellPath) ?? '/bin/sh',
652
+ ['-c', str(phase.shellScript) ?? ''],
653
+ {
654
+ cwd: project.projectDir,
655
+ env,
656
+ stdio: ['ignore', 'pipe', 'pipe'],
657
+ timeout: 120_000,
658
+ },
659
+ )
660
+ } catch (error) {
661
+ const output =
662
+ error instanceof Error
663
+ ? `${error.message}\n${'stdout' in error ? String(error.stdout) : ''}${'stderr' in error ? String(error.stderr) : ''}`
664
+ : String(error)
665
+ throw new Error(
666
+ `${targetName}: Run Script "${script}" failed preparing generated Swift sources:\n${output}`,
667
+ )
543
668
  }
669
+ const missing = allOutputs.filter(
670
+ (file) => file.endsWith('.swift') && !fs.existsSync(file),
671
+ )
672
+ if (missing.length > 0)
673
+ throw new Error(
674
+ `${targetName}: Run Script "${script}" did not produce ${missing.join(', ')}`,
675
+ )
676
+ preparedSourcePhases.set(key, stamp)
544
677
  }
545
678
  }
546
679
  visitXcodeTargetMembers(
@@ -769,7 +902,7 @@ export function readXcodeAppTarget(
769
902
  input: string,
770
903
  targetName?: string,
771
904
  configurationName?: string,
772
- options: { requireSources?: boolean } = {},
905
+ options: { requireSources?: boolean; prepareGeneratedSources?: boolean } = {},
773
906
  ): XcodeAppTarget {
774
907
  // canonical, so every path returned agrees with the host's realpath checks
775
908
  const xcodeprojOrWorkspace = fs.realpathSync(input)
@@ -881,22 +1014,112 @@ export function readXcodeAppTarget(
881
1014
  SRCROOT: ref.project.projectDir,
882
1015
  SOURCE_ROOT: ref.project.projectDir,
883
1016
  PROJECT_DIR: ref.project.projectDir,
1017
+ SYMROOT: path.join(ref.project.projectDir, '.build/peach-xcode/Build/Products'),
1018
+ BUILD_DIR: '$(SYMROOT)',
1019
+ OBJROOT: path.join(
1020
+ ref.project.projectDir,
1021
+ '.build/peach-xcode/Build/Intermediates.noindex',
1022
+ ),
1023
+ CONFIGURATION_BUILD_DIR:
1024
+ '$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)',
1025
+ BUILT_PRODUCTS_DIR: '$(CONFIGURATION_BUILD_DIR)',
1026
+ TARGET_BUILD_DIR: '$(CONFIGURATION_BUILD_DIR)',
1027
+ PROJECT_TEMP_DIR: '$(OBJROOT)/$(PROJECT_NAME).build',
1028
+ CONFIGURATION_TEMP_DIR:
1029
+ '$(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)',
1030
+ TARGET_TEMP_DIR: '$(CONFIGURATION_TEMP_DIR)/$(TARGET_NAME).build',
1031
+ DERIVED_FILE_DIR: '$(TARGET_TEMP_DIR)/DerivedSources',
1032
+ DERIVED_FILES_DIR: '$(DERIVED_FILE_DIR)',
1033
+ PLATFORM_NAME: 'iphonesimulator',
1034
+ EFFECTIVE_PLATFORM_NAME: '-iphonesimulator',
1035
+ CURRENT_ARCH: 'arm64',
1036
+ ARCHS: 'arm64',
1037
+ ACTION: 'build',
884
1038
  DEVELOPMENT_LANGUAGE: str(ref.project.project.developmentRegion) ?? 'en',
885
1039
  },
886
1040
  options,
887
1041
  )
888
1042
  for (const file of read) xcconfigs.add(file)
889
1043
  const swiftVersion = Number.parseFloat(settings('SWIFT_VERSION') || '5')
1044
+ const members = targetMembers(ref, settings, options, keys, xcconfigs)
1045
+ const implementations = members.allSources.filter((file) =>
1046
+ /\.(c|cc|cpp|cxx|m|mm)$/.test(file),
1047
+ )
1048
+ const absolute = (file: string) => path.resolve(ref.project.projectDir, file)
1049
+ const fileFlags: Record<string, string[]> = {}
1050
+ for (const phaseId of list(ref.target.buildPhases)) {
1051
+ const phase = ref.project.object(phaseId)
1052
+ if (phase?.isa !== 'PBXSourcesBuildPhase') continue
1053
+ for (const id of list(phase.files)) {
1054
+ const file = ref.project.object(id)
1055
+ const resolved = ref.project.resolvePath(str(file?.fileRef) ?? '')
1056
+ if (resolved && implementations.includes(resolved))
1057
+ fileFlags[resolved] = settingList(
1058
+ expand(
1059
+ str(isDict(file?.settings) ? file.settings.COMPILER_FLAGS : undefined) ??
1060
+ '',
1061
+ settings,
1062
+ ),
1063
+ )
1064
+ }
1065
+ }
890
1066
  return {
891
1067
  settings,
892
1068
  keys,
893
1069
  module: {
894
1070
  name,
895
1071
  moduleName: applyOperator(settings('PRODUCT_MODULE_NAME'), 'c99extidentifier'),
896
- ...targetMembers(ref, settings, options),
1072
+ ...members,
897
1073
  languageMode: swiftVersion >= 6 ? 6 : 5,
898
1074
  mainActorIsolation: settings('SWIFT_DEFAULT_ACTOR_ISOLATION') === 'MainActor',
899
1075
  defines: compilationConditions(settings),
1076
+ iosDeploymentTarget: settings('IPHONEOS_DEPLOYMENT_TARGET') || null,
1077
+ ...(implementations.some((file) => /\.mm?$/.test(file))
1078
+ ? {
1079
+ objc: {
1080
+ headers: Object.keys(ref.project.objects).flatMap((id) => {
1081
+ const file = ref.project.resolvePath(id)
1082
+ return file && /\.(h|hh|hpp|hxx|pch)$/.test(file) && fs.existsSync(file)
1083
+ ? [file]
1084
+ : []
1085
+ }),
1086
+ prefixHeader: settings('GCC_PREFIX_HEADER')
1087
+ ? absolute(settings('GCC_PREFIX_HEADER'))
1088
+ : null,
1089
+ bridgingHeader: settings('SWIFT_OBJC_BRIDGING_HEADER')
1090
+ ? absolute(settings('SWIFT_OBJC_BRIDGING_HEADER'))
1091
+ : null,
1092
+ headerSearchPaths: [
1093
+ ...settingList(settings('HEADER_SEARCH_PATHS')),
1094
+ ...settingList(settings('USER_HEADER_SEARCH_PATHS')),
1095
+ ].map(absolute),
1096
+ frameworkSearchPaths: settingList(settings('FRAMEWORK_SEARCH_PATHS')).map(
1097
+ absolute,
1098
+ ),
1099
+ flags: [
1100
+ ...(settings('CLANG_ENABLE_OBJC_ARC') === 'YES' ? ['-fobjc-arc'] : []),
1101
+ ...(settings('CLANG_ENABLE_OBJC_WEAK') === 'YES'
1102
+ ? ['-fobjc-weak']
1103
+ : []),
1104
+ ...(settings('CLANG_ENABLE_MODULES') === 'YES' ? ['-fmodules'] : []),
1105
+ ...(settings('GCC_C_LANGUAGE_STANDARD')
1106
+ ? [`-std=${settings('GCC_C_LANGUAGE_STANDARD')}`]
1107
+ : []),
1108
+ ...settingList(settings('GCC_PREPROCESSOR_DEFINITIONS')).map(
1109
+ (value) => `-D${value}`,
1110
+ ),
1111
+ ...settingList(settings('OTHER_CFLAGS')),
1112
+ ],
1113
+ cxxFlags: [
1114
+ ...(settings('CLANG_CXX_LANGUAGE_STANDARD')
1115
+ ? [`-std=${settings('CLANG_CXX_LANGUAGE_STANDARD')}`]
1116
+ : []),
1117
+ ...settingList(settings('OTHER_CPLUSPLUSFLAGS')),
1118
+ ],
1119
+ fileFlags,
1120
+ },
1121
+ }
1122
+ : {}),
900
1123
  } satisfies XcodeModuleTarget,
901
1124
  }
902
1125
  }
@@ -967,6 +1190,23 @@ export function readXcodeAppTarget(
967
1190
  ...projects.flatMap((project) =>
968
1191
  Object.entries(project.objects).flatMap(([id, entry]) => {
969
1192
  if (!isDict(entry)) return []
1193
+ if (entry.isa === 'PBXFileSystemSynchronizedRootGroup') {
1194
+ const folder = project.resolvePath(id)
1195
+ const pending = folder ? [folder] : []
1196
+ const packages: string[] = []
1197
+ while (pending.length > 0) {
1198
+ const dir = pending.pop()!
1199
+ if (fs.existsSync(path.join(dir, 'Package.swift'))) {
1200
+ packages.push(dir)
1201
+ continue
1202
+ }
1203
+ if (!isDirectory(dir)) continue
1204
+ for (const child of fs.readdirSync(dir, { withFileTypes: true }))
1205
+ if (child.isDirectory() && !child.name.startsWith('.'))
1206
+ pending.push(path.join(dir, child.name))
1207
+ }
1208
+ return packages
1209
+ }
970
1210
  const candidate =
971
1211
  entry.isa === 'XCLocalSwiftPackageReference'
972
1212
  ? path.resolve(project.projectDir, str(entry.relativePath) ?? '')
@@ -1046,6 +1286,13 @@ export function readXcodeAppTarget(
1046
1286
  'Package.resolved',
1047
1287
  )
1048
1288
 
1289
+ const packageResolved = fs.existsSync(resolvedFile)
1290
+ ? [fs.realpathSync(resolvedFile)]
1291
+ : [...new Set(localPackageDirs.map((dir) => path.join(dir, 'Package.resolved')))]
1292
+ .filter((file) => fs.existsSync(file))
1293
+ .map((file) => fs.realpathSync(file))
1294
+ .sort()
1295
+
1049
1296
  // the app is last in its own dependency order
1050
1297
  const used = order.map((ref) => ref.project)
1051
1298
  return {
@@ -1074,7 +1321,7 @@ export function readXcodeAppTarget(
1074
1321
  binaryFrameworks: [...binaryFrameworks]
1075
1322
  .map(([name, file]) => ({ name, path: file }))
1076
1323
  .sort((left, right) => left.name.localeCompare(right.name)),
1077
- packageResolved: fs.existsSync(resolvedFile) ? resolvedFile : null,
1324
+ packageResolved,
1078
1325
  appTargets: appTargetNames,
1079
1326
  configuration,
1080
1327
  settings,