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/vite-plugin.ts
CHANGED
|
@@ -47,7 +47,10 @@ import {
|
|
|
47
47
|
isReactNativeEnvironmentSetupModule,
|
|
48
48
|
} from '../../peach-engine/src/guest-module-overrides.ts'
|
|
49
49
|
import { readPeachWarmManifest } from '../../peach-engine/src/peach-warm-manifest.ts'
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
assertPeachPortEnvironment,
|
|
52
|
+
resolveSootsimBridgePort,
|
|
53
|
+
} from './bridge-constants.ts'
|
|
51
54
|
import { isRecord } from './is-record.ts'
|
|
52
55
|
import {
|
|
53
56
|
SHELL_BRIDGE_IDENTITY_PATH,
|
|
@@ -1321,6 +1324,7 @@ const viteBridgeProcesses = (globalThis.__sootsimViteBridgeProcesses ??= new Map
|
|
|
1321
1324
|
// installs it explicitly and owns the one shared bridge; sootsim() never
|
|
1322
1325
|
// does, so an isolated dev server cannot bind the shared default port.
|
|
1323
1326
|
export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plugin {
|
|
1327
|
+
assertPeachPortEnvironment(process.env)
|
|
1324
1328
|
if (
|
|
1325
1329
|
options.ownedBridgePort !== undefined &&
|
|
1326
1330
|
(!Number.isInteger(options.ownedBridgePort) ||
|
|
@@ -1652,6 +1656,13 @@ function sootsimConfigPlugin(
|
|
|
1652
1656
|
return nativeLibraryStub ? { id: nativeLibraryStub } : null
|
|
1653
1657
|
},
|
|
1654
1658
|
},
|
|
1659
|
+
{
|
|
1660
|
+
name: 'peach-one-native-backend',
|
|
1661
|
+
resolveId(source: string, importer: string | undefined) {
|
|
1662
|
+
const backend = resolveOneNativeBackend(source, importer)
|
|
1663
|
+
return backend ? { id: backend } : null
|
|
1664
|
+
},
|
|
1665
|
+
},
|
|
1655
1666
|
{
|
|
1656
1667
|
name: 'sootsim-published-browser-internals',
|
|
1657
1668
|
resolveId(source: string, importer: string | undefined) {
|
|
@@ -242,10 +242,16 @@ const linkedChunks = new Set();
|
|
|
242
242
|
function chunkBody(url) {
|
|
243
243
|
let body = chunkBodies.get(url);
|
|
244
244
|
if (!body) {
|
|
245
|
-
body =
|
|
245
|
+
body = (async () => {
|
|
246
|
+
if (deliveredChunkList && (await deliveredChunkList.promise).has(url)) {
|
|
247
|
+
const bytes = await deliveredChunk(url).promise;
|
|
248
|
+
deliveredChunks.delete(url);
|
|
249
|
+
return new TextDecoder().decode(bytes);
|
|
250
|
+
}
|
|
251
|
+
const response = await fetchWorkerChunk(url, { credentials: 'same-origin' });
|
|
246
252
|
if (!response.ok) throw new Error(url + ': HTTP ' + response.status);
|
|
247
253
|
return response.text();
|
|
248
|
-
});
|
|
254
|
+
})();
|
|
249
255
|
body.catch(() => chunkBodies.delete(url));
|
|
250
256
|
chunkBodies.set(url, body);
|
|
251
257
|
}
|
|
@@ -261,16 +267,9 @@ const importModule = specifier => import(specifier);
|
|
|
261
267
|
System.importModule = importModule;
|
|
262
268
|
System.instantiate = async function(url) {
|
|
263
269
|
if (url === workerEntryUrl) return workerEntryRegistration;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
deliveredChunks.delete(url);
|
|
268
|
-
source = new TextDecoder().decode(body);
|
|
269
|
-
} else {
|
|
270
|
-
source = await chunkBody(url);
|
|
271
|
-
chunkBodies.delete(url);
|
|
272
|
-
linkedChunks.add(url);
|
|
273
|
-
}
|
|
270
|
+
const source = await chunkBody(url);
|
|
271
|
+
chunkBodies.delete(url);
|
|
272
|
+
linkedChunks.add(url);
|
|
274
273
|
let registration;
|
|
275
274
|
// minified module chunks assume strict block-scoped function semantics.
|
|
276
275
|
new Function('System', '"use strict";\\n' + source)({
|
|
@@ -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
|
|
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') {
|
package/src/xcode-project.ts
CHANGED
|
@@ -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.
|
|
5
|
-
//
|
|
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
|
-
/**
|
|
76
|
-
packageResolved: string
|
|
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. */
|
|
@@ -498,14 +514,17 @@ function expand(value: string, lookup: (name: string) => string): string {
|
|
|
498
514
|
)
|
|
499
515
|
}
|
|
500
516
|
|
|
517
|
+
const preparedSourcePhases = new Map<string, string>()
|
|
518
|
+
|
|
501
519
|
// the swift sources and resources that are members of a target.
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
// the script instead of silently dropping the file.
|
|
520
|
+
// metadata-only reads never execute scripts. opening a project prepares the
|
|
521
|
+
// declared swift outputs with the project's own shell and build settings.
|
|
505
522
|
function targetMembers(
|
|
506
523
|
ref: TargetRef,
|
|
507
524
|
settings: (key: string) => string,
|
|
508
|
-
options: { requireSources?: boolean
|
|
525
|
+
options: { requireSources?: boolean; prepareGeneratedSources?: boolean },
|
|
526
|
+
settingKeys: Set<string>,
|
|
527
|
+
projectInputs: Set<string>,
|
|
509
528
|
): {
|
|
510
529
|
sources: string[]
|
|
511
530
|
resources: string[]
|
|
@@ -517,30 +536,137 @@ function targetMembers(
|
|
|
517
536
|
const allSources = new Set<string>()
|
|
518
537
|
const resources = new Set<string>()
|
|
519
538
|
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
539
|
for (const phaseId of list(target.buildPhases)) {
|
|
529
540
|
const phase = project.object(phaseId)
|
|
530
541
|
if (phase?.isa !== 'PBXShellScriptBuildPhase') continue
|
|
531
542
|
const script = str(phase.name) ?? phaseId
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
+
const absolute = (raw: string) =>
|
|
544
|
+
path.resolve(project.projectDir, expand(raw, settings))
|
|
545
|
+
const inputs = list(phase.inputPaths).map(absolute)
|
|
546
|
+
const outputs = list(phase.outputPaths).map(absolute)
|
|
547
|
+
const fileLists = new Map<string, { file: string; entries: string[] }[]>()
|
|
548
|
+
for (const kind of ['INPUT', 'OUTPUT']) {
|
|
549
|
+
const lists = list(
|
|
550
|
+
kind === 'INPUT' ? phase.inputFileListPaths : phase.outputFileListPaths,
|
|
551
|
+
).map((raw) => {
|
|
552
|
+
const file = absolute(raw)
|
|
553
|
+
if (!fs.existsSync(file)) {
|
|
554
|
+
return { file, entries: [] }
|
|
555
|
+
}
|
|
556
|
+
const entries = fs
|
|
557
|
+
.readFileSync(file, 'utf8')
|
|
558
|
+
.split(/\r?\n/)
|
|
559
|
+
.map((line) => line.trim())
|
|
560
|
+
.filter((line) => line !== '' && !line.startsWith('#'))
|
|
561
|
+
.map(absolute)
|
|
562
|
+
return { file, entries }
|
|
563
|
+
})
|
|
564
|
+
fileLists.set(kind, lists)
|
|
565
|
+
}
|
|
566
|
+
const allOutputs = [
|
|
567
|
+
...outputs,
|
|
568
|
+
...fileLists.get('OUTPUT')!.flatMap((item) => item.entries),
|
|
569
|
+
]
|
|
570
|
+
for (const output of allOutputs)
|
|
571
|
+
if (output.endsWith('.swift') && !scriptForOutput.has(output))
|
|
572
|
+
scriptForOutput.set(output, script)
|
|
573
|
+
const inputFiles = [
|
|
574
|
+
...inputs,
|
|
575
|
+
...fileLists.get('INPUT')!.flatMap((item) => [item.file, ...item.entries]),
|
|
576
|
+
...fileLists.get('OUTPUT')!.map((item) => item.file),
|
|
577
|
+
]
|
|
578
|
+
if (allOutputs.some((file) => file.endsWith('.swift')))
|
|
579
|
+
for (const file of inputFiles) if (fs.existsSync(file)) projectInputs.add(file)
|
|
580
|
+
if (
|
|
581
|
+
options.prepareGeneratedSources &&
|
|
582
|
+
options.requireSources !== false &&
|
|
583
|
+
allOutputs.some((file) => file.endsWith('.swift'))
|
|
584
|
+
) {
|
|
585
|
+
const phaseDir = path.join(settings('TARGET_TEMP_DIR'), `Script-${phaseId}`)
|
|
586
|
+
for (const lists of fileLists.values())
|
|
587
|
+
for (const { file } of lists)
|
|
588
|
+
if (!fs.existsSync(file))
|
|
589
|
+
throw new Error(
|
|
590
|
+
`${targetName}: Run Script "${script}" missing file list ${file}`,
|
|
591
|
+
)
|
|
592
|
+
const env = {
|
|
593
|
+
...process.env,
|
|
594
|
+
...Object.fromEntries(
|
|
595
|
+
[...settingKeys]
|
|
596
|
+
.filter((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
597
|
+
.map((key) => [key, settings(key)]),
|
|
598
|
+
),
|
|
599
|
+
}
|
|
600
|
+
const declared = { INPUT: inputs, OUTPUT: outputs }
|
|
601
|
+
for (const kind of ['INPUT', 'OUTPUT'] satisfies (keyof typeof declared)[]) {
|
|
602
|
+
env[`SCRIPT_${kind}_FILE_COUNT`] = String(declared[kind].length)
|
|
603
|
+
declared[kind].forEach((file, index) => {
|
|
604
|
+
env[`SCRIPT_${kind}_FILE_${index}`] = file
|
|
605
|
+
})
|
|
606
|
+
const lists = fileLists.get(kind)!
|
|
607
|
+
env[`SCRIPT_${kind}_FILE_LIST_COUNT`] = String(lists.length)
|
|
608
|
+
lists.forEach((_item, index) => {
|
|
609
|
+
env[`SCRIPT_${kind}_FILE_LIST_${index}`] = path.join(
|
|
610
|
+
phaseDir,
|
|
611
|
+
`${kind}-${index}.xcfilelist`,
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
}
|
|
615
|
+
const signature = createHash('sha256').update(
|
|
616
|
+
JSON.stringify([phase, [...settingKeys].map((key) => [key, settings(key)])]),
|
|
617
|
+
)
|
|
618
|
+
for (const file of inputFiles) {
|
|
619
|
+
signature.update(file)
|
|
620
|
+
if (fs.existsSync(file)) {
|
|
621
|
+
const stat = fs.statSync(file)
|
|
622
|
+
signature.update(`${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`)
|
|
623
|
+
} else signature.update('missing')
|
|
624
|
+
}
|
|
625
|
+
const stamp = signature.digest('hex')
|
|
626
|
+
const key = `${project.projectFile}:${targetId}:${phaseId}:${settings('CONFIGURATION')}`
|
|
627
|
+
if (
|
|
628
|
+
preparedSourcePhases.get(key) === stamp &&
|
|
629
|
+
allOutputs.every((file) => fs.existsSync(file))
|
|
630
|
+
)
|
|
631
|
+
continue
|
|
632
|
+
fs.mkdirSync(phaseDir, { recursive: true })
|
|
633
|
+
fs.mkdirSync(settings('BUILT_PRODUCTS_DIR'), { recursive: true })
|
|
634
|
+
fs.mkdirSync(settings('DERIVED_FILE_DIR'), { recursive: true })
|
|
635
|
+
for (const [kind, lists] of fileLists)
|
|
636
|
+
lists.forEach((item, index) => {
|
|
637
|
+
fs.writeFileSync(
|
|
638
|
+
env[`SCRIPT_${kind}_FILE_LIST_${index}`]!,
|
|
639
|
+
`${item.entries.join('\n')}\n`,
|
|
640
|
+
)
|
|
641
|
+
})
|
|
642
|
+
try {
|
|
643
|
+
execFileSync(
|
|
644
|
+
str(phase.shellPath) ?? '/bin/sh',
|
|
645
|
+
['-c', str(phase.shellScript) ?? ''],
|
|
646
|
+
{
|
|
647
|
+
cwd: project.projectDir,
|
|
648
|
+
env,
|
|
649
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
650
|
+
timeout: 120_000,
|
|
651
|
+
},
|
|
652
|
+
)
|
|
653
|
+
} catch (error) {
|
|
654
|
+
const output =
|
|
655
|
+
error instanceof Error
|
|
656
|
+
? `${error.message}\n${'stdout' in error ? String(error.stdout) : ''}${'stderr' in error ? String(error.stderr) : ''}`
|
|
657
|
+
: String(error)
|
|
658
|
+
throw new Error(
|
|
659
|
+
`${targetName}: Run Script "${script}" failed preparing generated Swift sources:\n${output}`,
|
|
660
|
+
)
|
|
543
661
|
}
|
|
662
|
+
const missing = allOutputs.filter(
|
|
663
|
+
(file) => file.endsWith('.swift') && !fs.existsSync(file),
|
|
664
|
+
)
|
|
665
|
+
if (missing.length > 0)
|
|
666
|
+
throw new Error(
|
|
667
|
+
`${targetName}: Run Script "${script}" did not produce ${missing.join(', ')}`,
|
|
668
|
+
)
|
|
669
|
+
preparedSourcePhases.set(key, stamp)
|
|
544
670
|
}
|
|
545
671
|
}
|
|
546
672
|
visitXcodeTargetMembers(
|
|
@@ -769,7 +895,7 @@ export function readXcodeAppTarget(
|
|
|
769
895
|
input: string,
|
|
770
896
|
targetName?: string,
|
|
771
897
|
configurationName?: string,
|
|
772
|
-
options: { requireSources?: boolean } = {},
|
|
898
|
+
options: { requireSources?: boolean; prepareGeneratedSources?: boolean } = {},
|
|
773
899
|
): XcodeAppTarget {
|
|
774
900
|
// canonical, so every path returned agrees with the host's realpath checks
|
|
775
901
|
const xcodeprojOrWorkspace = fs.realpathSync(input)
|
|
@@ -881,22 +1007,112 @@ export function readXcodeAppTarget(
|
|
|
881
1007
|
SRCROOT: ref.project.projectDir,
|
|
882
1008
|
SOURCE_ROOT: ref.project.projectDir,
|
|
883
1009
|
PROJECT_DIR: ref.project.projectDir,
|
|
1010
|
+
SYMROOT: path.join(ref.project.projectDir, '.build/peach-xcode/Build/Products'),
|
|
1011
|
+
BUILD_DIR: '$(SYMROOT)',
|
|
1012
|
+
OBJROOT: path.join(
|
|
1013
|
+
ref.project.projectDir,
|
|
1014
|
+
'.build/peach-xcode/Build/Intermediates.noindex',
|
|
1015
|
+
),
|
|
1016
|
+
CONFIGURATION_BUILD_DIR:
|
|
1017
|
+
'$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)',
|
|
1018
|
+
BUILT_PRODUCTS_DIR: '$(CONFIGURATION_BUILD_DIR)',
|
|
1019
|
+
TARGET_BUILD_DIR: '$(CONFIGURATION_BUILD_DIR)',
|
|
1020
|
+
PROJECT_TEMP_DIR: '$(OBJROOT)/$(PROJECT_NAME).build',
|
|
1021
|
+
CONFIGURATION_TEMP_DIR:
|
|
1022
|
+
'$(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)',
|
|
1023
|
+
TARGET_TEMP_DIR: '$(CONFIGURATION_TEMP_DIR)/$(TARGET_NAME).build',
|
|
1024
|
+
DERIVED_FILE_DIR: '$(TARGET_TEMP_DIR)/DerivedSources',
|
|
1025
|
+
DERIVED_FILES_DIR: '$(DERIVED_FILE_DIR)',
|
|
1026
|
+
PLATFORM_NAME: 'iphonesimulator',
|
|
1027
|
+
EFFECTIVE_PLATFORM_NAME: '-iphonesimulator',
|
|
1028
|
+
CURRENT_ARCH: 'arm64',
|
|
1029
|
+
ARCHS: 'arm64',
|
|
1030
|
+
ACTION: 'build',
|
|
884
1031
|
DEVELOPMENT_LANGUAGE: str(ref.project.project.developmentRegion) ?? 'en',
|
|
885
1032
|
},
|
|
886
1033
|
options,
|
|
887
1034
|
)
|
|
888
1035
|
for (const file of read) xcconfigs.add(file)
|
|
889
1036
|
const swiftVersion = Number.parseFloat(settings('SWIFT_VERSION') || '5')
|
|
1037
|
+
const members = targetMembers(ref, settings, options, keys, xcconfigs)
|
|
1038
|
+
const implementations = members.allSources.filter((file) =>
|
|
1039
|
+
/\.(c|cc|cpp|cxx|m|mm)$/.test(file),
|
|
1040
|
+
)
|
|
1041
|
+
const absolute = (file: string) => path.resolve(ref.project.projectDir, file)
|
|
1042
|
+
const fileFlags: Record<string, string[]> = {}
|
|
1043
|
+
for (const phaseId of list(ref.target.buildPhases)) {
|
|
1044
|
+
const phase = ref.project.object(phaseId)
|
|
1045
|
+
if (phase?.isa !== 'PBXSourcesBuildPhase') continue
|
|
1046
|
+
for (const id of list(phase.files)) {
|
|
1047
|
+
const file = ref.project.object(id)
|
|
1048
|
+
const resolved = ref.project.resolvePath(str(file?.fileRef) ?? '')
|
|
1049
|
+
if (resolved && implementations.includes(resolved))
|
|
1050
|
+
fileFlags[resolved] = settingList(
|
|
1051
|
+
expand(
|
|
1052
|
+
str(isDict(file?.settings) ? file.settings.COMPILER_FLAGS : undefined) ??
|
|
1053
|
+
'',
|
|
1054
|
+
settings,
|
|
1055
|
+
),
|
|
1056
|
+
)
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
890
1059
|
return {
|
|
891
1060
|
settings,
|
|
892
1061
|
keys,
|
|
893
1062
|
module: {
|
|
894
1063
|
name,
|
|
895
1064
|
moduleName: applyOperator(settings('PRODUCT_MODULE_NAME'), 'c99extidentifier'),
|
|
896
|
-
...
|
|
1065
|
+
...members,
|
|
897
1066
|
languageMode: swiftVersion >= 6 ? 6 : 5,
|
|
898
1067
|
mainActorIsolation: settings('SWIFT_DEFAULT_ACTOR_ISOLATION') === 'MainActor',
|
|
899
1068
|
defines: compilationConditions(settings),
|
|
1069
|
+
iosDeploymentTarget: settings('IPHONEOS_DEPLOYMENT_TARGET') || null,
|
|
1070
|
+
...(implementations.some((file) => /\.mm?$/.test(file))
|
|
1071
|
+
? {
|
|
1072
|
+
objc: {
|
|
1073
|
+
headers: Object.keys(ref.project.objects).flatMap((id) => {
|
|
1074
|
+
const file = ref.project.resolvePath(id)
|
|
1075
|
+
return file && /\.(h|hh|hpp|hxx|pch)$/.test(file) && fs.existsSync(file)
|
|
1076
|
+
? [file]
|
|
1077
|
+
: []
|
|
1078
|
+
}),
|
|
1079
|
+
prefixHeader: settings('GCC_PREFIX_HEADER')
|
|
1080
|
+
? absolute(settings('GCC_PREFIX_HEADER'))
|
|
1081
|
+
: null,
|
|
1082
|
+
bridgingHeader: settings('SWIFT_OBJC_BRIDGING_HEADER')
|
|
1083
|
+
? absolute(settings('SWIFT_OBJC_BRIDGING_HEADER'))
|
|
1084
|
+
: null,
|
|
1085
|
+
headerSearchPaths: [
|
|
1086
|
+
...settingList(settings('HEADER_SEARCH_PATHS')),
|
|
1087
|
+
...settingList(settings('USER_HEADER_SEARCH_PATHS')),
|
|
1088
|
+
].map(absolute),
|
|
1089
|
+
frameworkSearchPaths: settingList(settings('FRAMEWORK_SEARCH_PATHS')).map(
|
|
1090
|
+
absolute,
|
|
1091
|
+
),
|
|
1092
|
+
flags: [
|
|
1093
|
+
...(settings('CLANG_ENABLE_OBJC_ARC') === 'YES' ? ['-fobjc-arc'] : []),
|
|
1094
|
+
...(settings('CLANG_ENABLE_OBJC_WEAK') === 'YES'
|
|
1095
|
+
? ['-fobjc-weak']
|
|
1096
|
+
: []),
|
|
1097
|
+
...(settings('CLANG_ENABLE_MODULES') === 'YES' ? ['-fmodules'] : []),
|
|
1098
|
+
...(settings('GCC_C_LANGUAGE_STANDARD')
|
|
1099
|
+
? [`-std=${settings('GCC_C_LANGUAGE_STANDARD')}`]
|
|
1100
|
+
: []),
|
|
1101
|
+
...settingList(settings('GCC_PREPROCESSOR_DEFINITIONS')).map(
|
|
1102
|
+
(value) => `-D${value}`,
|
|
1103
|
+
),
|
|
1104
|
+
...settingList(settings('OTHER_CFLAGS')),
|
|
1105
|
+
],
|
|
1106
|
+
cxxFlags: [
|
|
1107
|
+
...(settings('CLANG_CXX_LANGUAGE_STANDARD')
|
|
1108
|
+
? [`-std=${settings('CLANG_CXX_LANGUAGE_STANDARD')}`]
|
|
1109
|
+
: []),
|
|
1110
|
+
...settingList(settings('OTHER_CPLUSPLUSFLAGS')),
|
|
1111
|
+
],
|
|
1112
|
+
fileFlags,
|
|
1113
|
+
},
|
|
1114
|
+
}
|
|
1115
|
+
: {}),
|
|
900
1116
|
} satisfies XcodeModuleTarget,
|
|
901
1117
|
}
|
|
902
1118
|
}
|
|
@@ -967,6 +1183,23 @@ export function readXcodeAppTarget(
|
|
|
967
1183
|
...projects.flatMap((project) =>
|
|
968
1184
|
Object.entries(project.objects).flatMap(([id, entry]) => {
|
|
969
1185
|
if (!isDict(entry)) return []
|
|
1186
|
+
if (entry.isa === 'PBXFileSystemSynchronizedRootGroup') {
|
|
1187
|
+
const folder = project.resolvePath(id)
|
|
1188
|
+
const pending = folder ? [folder] : []
|
|
1189
|
+
const packages: string[] = []
|
|
1190
|
+
while (pending.length > 0) {
|
|
1191
|
+
const dir = pending.pop()!
|
|
1192
|
+
if (fs.existsSync(path.join(dir, 'Package.swift'))) {
|
|
1193
|
+
packages.push(dir)
|
|
1194
|
+
continue
|
|
1195
|
+
}
|
|
1196
|
+
if (!isDirectory(dir)) continue
|
|
1197
|
+
for (const child of fs.readdirSync(dir, { withFileTypes: true }))
|
|
1198
|
+
if (child.isDirectory() && !child.name.startsWith('.'))
|
|
1199
|
+
pending.push(path.join(dir, child.name))
|
|
1200
|
+
}
|
|
1201
|
+
return packages
|
|
1202
|
+
}
|
|
970
1203
|
const candidate =
|
|
971
1204
|
entry.isa === 'XCLocalSwiftPackageReference'
|
|
972
1205
|
? path.resolve(project.projectDir, str(entry.relativePath) ?? '')
|
|
@@ -1046,6 +1279,13 @@ export function readXcodeAppTarget(
|
|
|
1046
1279
|
'Package.resolved',
|
|
1047
1280
|
)
|
|
1048
1281
|
|
|
1282
|
+
const packageResolved = fs.existsSync(resolvedFile)
|
|
1283
|
+
? [fs.realpathSync(resolvedFile)]
|
|
1284
|
+
: [...new Set(localPackageDirs.map((dir) => path.join(dir, 'Package.resolved')))]
|
|
1285
|
+
.filter((file) => fs.existsSync(file))
|
|
1286
|
+
.map((file) => fs.realpathSync(file))
|
|
1287
|
+
.sort()
|
|
1288
|
+
|
|
1049
1289
|
// the app is last in its own dependency order
|
|
1050
1290
|
const used = order.map((ref) => ref.project)
|
|
1051
1291
|
return {
|
|
@@ -1074,7 +1314,7 @@ export function readXcodeAppTarget(
|
|
|
1074
1314
|
binaryFrameworks: [...binaryFrameworks]
|
|
1075
1315
|
.map(([name, file]) => ({ name, path: file }))
|
|
1076
1316
|
.sort((left, right) => left.name.localeCompare(right.name)),
|
|
1077
|
-
packageResolved
|
|
1317
|
+
packageResolved,
|
|
1078
1318
|
appTargets: appTargetNames,
|
|
1079
1319
|
configuration,
|
|
1080
1320
|
settings,
|