peachsim 0.1.1625 → 0.1.1640
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +15 -2
- package/cli/app-config.ts +18 -6
- package/cli/app-fonts.ts +37 -17
- package/cli/app-stubs.ts +284 -20
- package/cli/bridge-flow-runner.ts +66 -45
- package/cli/cloud-client.ts +60 -40
- package/cli/command-registry.ts +1 -0
- package/cli/commands/control.ts +283 -49
- package/cli/commands/doctor.ts +30 -189
- package/cli/commands/flow.ts +79 -11
- package/cli/commands/inspect/core.ts +10 -5
- package/cli/commands/inspect/settling.ts +14 -2
- package/cli/commands/inspect/shared.ts +9 -4
- package/cli/commands/inspect/wait-ready.ts +12 -0
- package/cli/commands/inspect.ts +91 -50
- package/cli/commands/install-desktop.ts +1 -0
- package/cli/commands/maestro-compare.ts +97 -38
- package/cli/commands/maestro.ts +9 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/record.ts +118 -48
- package/cli/commands/screenshot.ts +23 -1
- package/cli/commands/serve.ts +7 -2
- package/cli/commands/setup.ts +19 -42
- package/cli/commands/swift.ts +14 -3
- package/cli/commands/upload.ts +93 -48
- package/cli/desktop-companion.ts +102 -17
- package/cli/drivers/desktop.ts +14 -1
- package/cli/drivers/playwright-sim-host.ts +103 -45
- package/cli/drivers/playwright.ts +9 -8
- package/cli/drivers/types.ts +5 -0
- package/cli/maestro-accessibility.ts +1 -1
- package/cli/maestro-compare.ts +84 -12
- package/cli/main.ts +3 -2
- package/cli/outbound-endpoints.ts +20 -6
- package/cli/pause-aware-timeout.ts +106 -0
- package/cli/scene-video.ts +89 -0
- package/cli/ws-bridge.ts +37 -19
- package/detox/attempt-events.cjs +66 -5
- package/detox/attempt-events.d.cts +7 -0
- package/detox/expectations.ts +7 -3
- package/detox/index.ts +269 -170
- package/detox/motion-change.cjs +206 -21
- package/detox/motion-change.d.cts +39 -2
- package/detox/navigation-lifecycle.ts +35 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +8 -7
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +13 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +14 -8
- package/dist-lib/bridge-contract.mjs +13 -7
- package/dist-lib/bundle-channel.cjs +11887 -7900
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +6 -3
- package/dist-lib/config.mjs +6 -3
- package/dist-lib/detox/index.cjs +1379 -725
- package/dist-lib/dev-bundle-resolution.cjs +14 -2
- package/dist-lib/expo-swift-bundle.cjs +9341 -5462
- package/dist-lib/expo-swift-metadata.cjs +11 -7
- package/dist-lib/expo-swift-metadata.mjs +11 -7
- package/dist-lib/home-paths.cjs +32 -3
- package/dist-lib/host/bridge-host.cjs +50204 -7258
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +3541 -448
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +2931 -104
- package/dist-lib/native-app-identity-project.cjs +289 -49
- package/dist-lib/native-app-identity-project.mjs +289 -49
- package/dist-lib/native-app-identity.cjs +77 -26
- package/dist-lib/native-app-identity.mjs +76 -26
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +6 -2
- package/dist-lib/react-native-host-modules.mjs +6 -2
- package/dist-lib/scripts/dev-server-scanner.cjs +22 -8
- package/dist-lib/sdk.cjs +27 -11
- package/dist-lib/sdk.mjs +27 -10
- package/dist-lib/skills.cjs +72201 -26208
- package/dist-lib/swift-compile.cjs +1007 -140
- package/dist-lib/swift-compile.mjs +1049 -174
- package/dist-lib/swift-package.cjs +12 -3
- package/dist-lib/swift-package.mjs +12 -3
- package/dist-lib/swift-submit.cjs +13 -5
- package/dist-lib/swift-submit.mjs +11 -5
- package/dist-lib/swift-toolchain.cjs +423 -33
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +8628 -5268
- package/dist-lib/vite.cjs +2859 -12
- package/dist-lib/worklet-executable.cjs +14 -2
- package/dist-lib/worklet-executable.mjs +14 -2
- package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
- package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +2 -1
- package/skills/peach-perf/SKILL.md +11 -5
- package/skills/peach-setup/SKILL.md +16 -19
- package/src/backend-origin.ts +13 -6
- package/src/bridge-constants.ts +11 -0
- package/src/bridge-contract.ts +22 -34
- package/src/config.ts +15 -0
- package/src/desktop-host-delivery.ts +5 -1
- package/src/dev-bundle-resolution.ts +2 -1
- package/src/expo-manifest-body.ts +13 -0
- package/src/expo-swift-host.ts +196 -49
- package/src/expo-swift-ready.ts +78 -0
- package/src/expoKotlinContract.ts +4 -7
- package/src/expoSwiftContract.ts +17 -6
- package/src/home-paths.ts +48 -2
- package/src/host/bridge-host.ts +5 -2
- package/src/host/staged-host-files.ts +9 -0
- package/src/native-app-identity-project.ts +27 -6
- package/src/native-app-identity.ts +91 -4
- package/src/native-firebase-config.ts +209 -13
- package/src/nativeAppIconContract.ts +1 -4
- package/src/output-lock-stdio.ts +36 -0
- package/src/process-group.ts +25 -2
- package/src/react-native-bridge-modules.ts +431 -0
- package/src/react-native-host-modules.ts +8 -1
- package/src/settings-bundle.ts +1 -4
- package/src/swift-binary-modules.ts +2 -2
- package/src/swift-compile.ts +918 -109
- package/src/swift-compiler-digest.ts +14 -0
- package/src/swift-distribution.lock.json +24 -12
- package/src/swift-distribution.ts +103 -4
- package/src/swift-host.ts +180 -55
- package/src/swift-icu.ts +27 -13
- package/src/swift-objc-compile.ts +355 -0
- package/src/swift-objc-sdk.ts +821 -27
- package/src/swift-objc-standin.ts +148 -14
- package/src/swift-package-manifest.ts +60 -7
- package/src/swift-package.ts +15 -1
- package/src/swift-packages.ts +154 -81
- package/src/swift-standin-emit.ts +96 -15
- package/src/swift-standins.ts +530 -116
- package/src/swift-submit.ts +13 -5
- package/src/swift-toolchain.ts +500 -42
- package/src/vite-plugin.ts +12 -1
- package/src/worker-system-chunks.ts +11 -12
- package/src/worklet-executable.ts +19 -1
- package/src/xcode-project.ts +270 -30
package/src/swift-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,9 +10,13 @@ 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' }
|
|
18
|
+
import { type NativeAppIdentity } from './native-app-identity'
|
|
19
|
+
import { readNativeAppIdentityFromXcodeTarget } from './native-app-identity-project'
|
|
17
20
|
import { swiftBinaryModules } from './swift-binary-modules'
|
|
18
21
|
import {
|
|
19
22
|
compileSwiftProjectUnit,
|
|
@@ -27,6 +30,7 @@ import {
|
|
|
27
30
|
} from './swift-compile'
|
|
28
31
|
import { designTimeValues } from './swift-design-time'
|
|
29
32
|
import { swiftDistributionRoot, swiftModuleToolchain } from './swift-distribution'
|
|
33
|
+
import { compileXcodeObjCImplementations } from './swift-objc-compile'
|
|
30
34
|
import {
|
|
31
35
|
swiftImplicitResourcePaths,
|
|
32
36
|
swiftMetalSourceFiles,
|
|
@@ -39,13 +43,13 @@ import {
|
|
|
39
43
|
} from './swift-package'
|
|
40
44
|
import { swiftPackageManifest } from './swift-package-manifest'
|
|
41
45
|
import {
|
|
42
|
-
|
|
46
|
+
remoteSwiftCheckouts,
|
|
43
47
|
resolveSwiftPackages,
|
|
44
48
|
type SwiftPackageBinary,
|
|
45
49
|
type SwiftPackageGap,
|
|
46
50
|
type SwiftPackages,
|
|
47
51
|
} from './swift-packages'
|
|
48
|
-
import { readPackageFile } from './swift-source-file'
|
|
52
|
+
import { readPackageFile as readSwiftSourceFile } from './swift-source-file'
|
|
49
53
|
import { readSwiftStandinBehavior } from './swift-standin-config'
|
|
50
54
|
import {
|
|
51
55
|
standinGenerationInputs,
|
|
@@ -68,6 +72,7 @@ import {
|
|
|
68
72
|
MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES,
|
|
69
73
|
isSwiftNativeModules,
|
|
70
74
|
compileSwiftProject,
|
|
75
|
+
DEFAULT_SWIFT_COMPILE_BUILD_MS,
|
|
71
76
|
httpSwiftCompileSubmit,
|
|
72
77
|
swiftNativeEntry,
|
|
73
78
|
type SwiftImageUpload,
|
|
@@ -242,6 +247,8 @@ interface XcodeProjectEntry {
|
|
|
242
247
|
packages?: {
|
|
243
248
|
key: string
|
|
244
249
|
standinKey: string
|
|
250
|
+
/** generation always uses the original closure, before stand-ins replace targets. */
|
|
251
|
+
binaries: SwiftPackageBinary[]
|
|
245
252
|
packages: SwiftPackages
|
|
246
253
|
standins: SwiftStandin[]
|
|
247
254
|
/** why a framework the project vendors got no stand-in, by name. */
|
|
@@ -263,7 +270,9 @@ export function openXcodeProject(
|
|
|
263
270
|
moduleRoot: string | undefined,
|
|
264
271
|
configuration?: string,
|
|
265
272
|
): string {
|
|
266
|
-
const app = readXcodeAppTarget(xcodeproj, target, configuration
|
|
273
|
+
const app = readXcodeAppTarget(xcodeproj, target, configuration, {
|
|
274
|
+
prepareGeneratedSources: true,
|
|
275
|
+
})
|
|
267
276
|
const root = app.rootDir
|
|
268
277
|
xcodeProjects.set(root, {
|
|
269
278
|
xcodeproj,
|
|
@@ -308,37 +317,46 @@ function swiftPackages(
|
|
|
308
317
|
projectGaps: Map<string, string>
|
|
309
318
|
binaries: SwiftPackageBinary[]
|
|
310
319
|
} {
|
|
311
|
-
const stamp = (file: string
|
|
312
|
-
|
|
320
|
+
const stamp = (file: string) =>
|
|
321
|
+
fs.existsSync(file)
|
|
322
|
+
? createHash('sha256').update(fs.readFileSync(file)).digest('hex')
|
|
323
|
+
: null
|
|
313
324
|
const key = JSON.stringify([
|
|
314
325
|
app.linkedProducts,
|
|
315
|
-
app.packageResolved,
|
|
316
|
-
stamp(app.packageResolved),
|
|
326
|
+
app.packageResolved.map((file) => [file, stamp(file)]),
|
|
317
327
|
app.localPackageDirs.map((dir) => stamp(path.join(dir, 'Package.swift'))),
|
|
318
328
|
app.linkedProducts.map(({ source }) =>
|
|
319
329
|
source.kind === 'local' ? stamp(path.join(source.dir, 'Package.swift')) : 0,
|
|
320
330
|
),
|
|
321
331
|
])
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
// emitter), so a changed input rebuilds instead of reusing stale output.
|
|
332
|
+
// resolution follows lock and manifest contents even when a checkout restores
|
|
333
|
+
// timestamps. the stand-in key also covers artifacts, peach modules and emitter.
|
|
325
334
|
let cached = entry.packages
|
|
326
335
|
if (cached?.key !== key) {
|
|
336
|
+
const packages = resolveSwiftPackages({
|
|
337
|
+
rootDir: app.rootDir,
|
|
338
|
+
resolved: app.packageResolved,
|
|
339
|
+
products: app.linkedProducts,
|
|
340
|
+
localDirs: app.localPackageDirs,
|
|
341
|
+
provided: entry.provided,
|
|
342
|
+
})
|
|
327
343
|
cached = {
|
|
328
344
|
key,
|
|
329
345
|
standinKey: '',
|
|
330
|
-
packages
|
|
331
|
-
|
|
332
|
-
resolved: app.packageResolved,
|
|
333
|
-
products: app.linkedProducts,
|
|
334
|
-
localDirs: app.localPackageDirs,
|
|
335
|
-
provided: entry.provided,
|
|
336
|
-
}),
|
|
346
|
+
packages,
|
|
347
|
+
binaries: packages.binaries,
|
|
337
348
|
standins: [],
|
|
338
349
|
projectGaps: new Map(),
|
|
339
350
|
}
|
|
340
351
|
entry.packages = cached
|
|
341
352
|
}
|
|
353
|
+
if (
|
|
354
|
+
entry.moduleRoot &&
|
|
355
|
+
[app, ...app.frameworks].some((target) =>
|
|
356
|
+
target.allSources.some((file) => /\.mm?$/.test(file)),
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
compileXcodeObjCImplementations(app, cached.binaries, entry.moduleRoot)
|
|
342
360
|
// a reached binary target with stand-ins for every module it ships
|
|
343
361
|
// resolves as provided, so what depends on it compiles; the rest keep
|
|
344
362
|
// their reason with why no stand-in was built. surfaces peach already
|
|
@@ -360,10 +378,7 @@ function swiftPackages(
|
|
|
360
378
|
]
|
|
361
379
|
: [],
|
|
362
380
|
)
|
|
363
|
-
const reached = [
|
|
364
|
-
...cached.packages.binaries.filter((binary) => binary.reached),
|
|
365
|
-
...vendored,
|
|
366
|
-
]
|
|
381
|
+
const reached = [...cached.binaries.filter((binary) => binary.reached), ...vendored]
|
|
367
382
|
if (entry.moduleRoot && entry.provided && reached.length > 0) {
|
|
368
383
|
const resolved = swiftModuleToolchain(entry.moduleRoot)
|
|
369
384
|
const toolchain = {
|
|
@@ -385,6 +400,8 @@ function swiftPackages(
|
|
|
385
400
|
]),
|
|
386
401
|
binaryGaps: built.gaps,
|
|
387
402
|
})
|
|
403
|
+
for (const target of new Set(built.standins.map((standin) => standin.target)))
|
|
404
|
+
console.error(`Peach: target ${target} was replaced with a stand-in`)
|
|
388
405
|
cached.standins = built.standins
|
|
389
406
|
cached.projectGaps = new Map(
|
|
390
407
|
vendored.flatMap(({ target }) => {
|
|
@@ -408,14 +425,17 @@ const XCODE_FRAMEWORKS_DIR = '__frameworks__'
|
|
|
408
425
|
// remote package checkouts compile here: swiftpm owns `.build` in the
|
|
409
426
|
// compile's package root as its scratch directory.
|
|
410
427
|
const XCODE_PACKAGES_DIR = '__packages__'
|
|
411
|
-
const CHECKOUTS_PREFIX = `${REMOTE_CHECKOUTS.split(path.sep).join(path.posix.sep)}/`
|
|
412
428
|
|
|
413
429
|
// the compile input key of a root-relative path in an xcode layout.
|
|
414
430
|
function xcodeInputKey(layout: XcodeLayout, file: string, relative: string): string {
|
|
415
431
|
const owner = layout.targetDirs.get(file)
|
|
416
432
|
if (owner) return path.posix.join(owner, relative)
|
|
417
|
-
|
|
418
|
-
|
|
433
|
+
const checkouts = remoteSwiftCheckouts(layout.app.rootDir)
|
|
434
|
+
return file.startsWith(`${checkouts}${path.sep}`)
|
|
435
|
+
? path.posix.join(
|
|
436
|
+
XCODE_PACKAGES_DIR,
|
|
437
|
+
path.relative(checkouts, file).split(path.sep).join('/'),
|
|
438
|
+
)
|
|
419
439
|
: relative
|
|
420
440
|
}
|
|
421
441
|
|
|
@@ -442,11 +462,26 @@ function xcodeLayout(root: string): XcodeLayout | null {
|
|
|
442
462
|
registered.xcodeproj,
|
|
443
463
|
registered.target,
|
|
444
464
|
registered.configuration,
|
|
465
|
+
{ prepareGeneratedSources: true },
|
|
466
|
+
)
|
|
467
|
+
const implementations = [app, ...app.frameworks].flatMap((target) =>
|
|
468
|
+
target.allSources
|
|
469
|
+
.filter((file) => /\.mm?$/i.test(file))
|
|
470
|
+
.map((file) => `${target.name}: ${path.relative(app.rootDir, file)}`),
|
|
445
471
|
)
|
|
472
|
+
if (implementations.length > 0 && !registered.moduleRoot)
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Peach cannot compile Objective-C implementations in Xcode targets until the runtime and shared Swift object bridge are available:\n${implementations.map((file) => ` ${file}`).join('\n')}`,
|
|
475
|
+
)
|
|
446
476
|
const appSources = app.allSources.filter(
|
|
447
477
|
(file) => file.endsWith('.swift') || file.endsWith('.metal'),
|
|
448
478
|
)
|
|
449
|
-
const files = new Set([
|
|
479
|
+
const files = new Set([
|
|
480
|
+
...app.projectFiles,
|
|
481
|
+
...app.packageResolved,
|
|
482
|
+
...appSources,
|
|
483
|
+
...app.resources,
|
|
484
|
+
])
|
|
450
485
|
if (app.infoPlistFile) files.add(app.infoPlistFile)
|
|
451
486
|
const targetDirs = new Map(
|
|
452
487
|
[...appSources, ...app.resources].map((file) => [file, XCODE_APP_DIR]),
|
|
@@ -489,6 +524,7 @@ function xcodeLayout(root: string): XcodeLayout | null {
|
|
|
489
524
|
targets.push({
|
|
490
525
|
name: standin.module,
|
|
491
526
|
path: path.posix.join(SWIFT_STANDINS_DIR, standin.module),
|
|
527
|
+
dependencies: standin.dependencies?.map((name) => ({ kind: 'target', name })),
|
|
492
528
|
executable: false,
|
|
493
529
|
resources: false,
|
|
494
530
|
exclude: [],
|
|
@@ -500,7 +536,7 @@ function xcodeLayout(root: string): XcodeLayout | null {
|
|
|
500
536
|
const packageConfiguration = app.configuration === 'Debug' ? 'debug' : 'release'
|
|
501
537
|
for (const target of packages.targets) {
|
|
502
538
|
const relative = path.relative(
|
|
503
|
-
target.remote ?
|
|
539
|
+
target.remote ? remoteSwiftCheckouts(root) : root,
|
|
504
540
|
target.dir,
|
|
505
541
|
)
|
|
506
542
|
if (
|
|
@@ -524,6 +560,13 @@ function xcodeLayout(root: string): XcodeLayout | null {
|
|
|
524
560
|
)
|
|
525
561
|
.map((define) => define.name),
|
|
526
562
|
],
|
|
563
|
+
linkedLibraries: target.packageLibraries
|
|
564
|
+
.filter(
|
|
565
|
+
(library) =>
|
|
566
|
+
library.configuration === null ||
|
|
567
|
+
library.configuration === packageConfiguration,
|
|
568
|
+
)
|
|
569
|
+
.map((library) => library.name),
|
|
527
570
|
})
|
|
528
571
|
if (target.resources) {
|
|
529
572
|
resourceBundles.push({
|
|
@@ -568,14 +611,19 @@ function xcodeManifest(layout: XcodeLayout): string {
|
|
|
568
611
|
...(target.mainActorIsolation ? ['.defaultIsolation(MainActor.self)'] : []),
|
|
569
612
|
...(target.defines ?? []).map((name) => `.define(${JSON.stringify(name)})`),
|
|
570
613
|
].join(', ')}]`
|
|
614
|
+
const libraries = (target: SwiftPackageTarget) =>
|
|
615
|
+
target.linkedLibraries?.length
|
|
616
|
+
? `, linkerSettings: [${target.linkedLibraries.map((name) => `.linkedLibrary(${JSON.stringify(name)})`).join(', ')}]`
|
|
617
|
+
: ''
|
|
571
618
|
return [
|
|
572
619
|
'// swift-tools-version: 6.2',
|
|
573
620
|
'import PackageDescription',
|
|
574
621
|
'let package = Package(name: "app", targets: [',
|
|
575
622
|
` .executableTarget(name: "${layout.app.moduleName}", path: "${XCODE_APP_DIR}", ${settings(layout.app)}),`,
|
|
576
|
-
...layout.targets.map(
|
|
577
|
-
|
|
578
|
-
` .
|
|
623
|
+
...layout.targets.map((target) =>
|
|
624
|
+
target.systemLibrary
|
|
625
|
+
? ` .systemLibrary(name: "${target.name}", path: "${target.path}"),`
|
|
626
|
+
: ` .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
627
|
),
|
|
580
628
|
'])',
|
|
581
629
|
'',
|
|
@@ -584,10 +632,29 @@ function xcodeManifest(layout: XcodeLayout): string {
|
|
|
584
632
|
|
|
585
633
|
// preflight the entire input tree before reading any source or resource bytes.
|
|
586
634
|
// a nested package is a separate compile unit; ignored build trees never enter it.
|
|
587
|
-
function packageFiles(root: string): string[] {
|
|
635
|
+
function packageFiles(root: string, includeBuildInputs = true): string[] {
|
|
588
636
|
const canonicalRoot = fs.realpathSync(root)
|
|
589
637
|
const xcode = xcodeLayout(canonicalRoot)
|
|
590
|
-
|
|
638
|
+
if (!xcode) return sourceTreeFiles(canonicalRoot, canonicalRoot)
|
|
639
|
+
if (includeBuildInputs) return xcode.files
|
|
640
|
+
const buildInputs = new Set(xcode.app.projectFiles)
|
|
641
|
+
return xcode.files.filter(
|
|
642
|
+
(file) =>
|
|
643
|
+
!buildInputs.has(file) ||
|
|
644
|
+
xcode.targetDirs.has(file) ||
|
|
645
|
+
xcode.packages.targets.some((target) => target.files.includes(file)),
|
|
646
|
+
)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// remote dependencies are enumerated from peach's checkout cache, while app
|
|
650
|
+
// inputs retain their selected-project boundary and all symlink checks.
|
|
651
|
+
function readPackageFile(root: string, file: string, maxBytes: number): Buffer {
|
|
652
|
+
if (xcodeProjects.has(root)) {
|
|
653
|
+
const checkouts = remoteSwiftCheckouts(root)
|
|
654
|
+
if (file.startsWith(`${checkouts}${path.sep}`))
|
|
655
|
+
return readSwiftSourceFile(checkouts, file, maxBytes)
|
|
656
|
+
}
|
|
657
|
+
return readSwiftSourceFile(root, file, maxBytes)
|
|
591
658
|
}
|
|
592
659
|
|
|
593
660
|
// every input file under `dir`, which must stay inside `canonicalRoot`.
|
|
@@ -636,16 +703,18 @@ function isPackageSource(file: string): boolean {
|
|
|
636
703
|
}
|
|
637
704
|
|
|
638
705
|
export function sourceFiles(root: string): string[] {
|
|
639
|
-
return packageFiles(root).filter(isPackageSource)
|
|
706
|
+
return packageFiles(root, false).filter(isPackageSource)
|
|
640
707
|
}
|
|
641
708
|
|
|
642
709
|
// project files and the xcconfigs their settings read are build inputs, never resources
|
|
643
710
|
function resourceCandidateFiles(root: string): string[] {
|
|
644
|
-
return packageFiles(root).filter(
|
|
711
|
+
return packageFiles(root, false).filter(
|
|
645
712
|
(file) =>
|
|
646
713
|
!isPackageSource(file) &&
|
|
647
714
|
!file.endsWith('.xcconfig') &&
|
|
648
|
-
!['project.pbxproj', 'project.xcproj'].includes(
|
|
715
|
+
!['project.pbxproj', 'project.xcproj', 'Package.resolved'].includes(
|
|
716
|
+
path.basename(file),
|
|
717
|
+
),
|
|
649
718
|
)
|
|
650
719
|
}
|
|
651
720
|
|
|
@@ -653,6 +722,7 @@ function resourceCandidateFiles(root: string): string[] {
|
|
|
653
722
|
export function swiftProjectAppInfo(packagePath: string): {
|
|
654
723
|
name: string
|
|
655
724
|
icon: { bytes: Buffer; contentType: string } | null
|
|
725
|
+
identity: NativeAppIdentity | null
|
|
656
726
|
} {
|
|
657
727
|
const registered = xcodeProjects.get(packagePath)
|
|
658
728
|
const app = registered
|
|
@@ -715,12 +785,17 @@ export function swiftProjectAppInfo(packagePath: string): {
|
|
|
715
785
|
}
|
|
716
786
|
break
|
|
717
787
|
}
|
|
718
|
-
return {
|
|
788
|
+
return {
|
|
789
|
+
name: app?.name ?? path.basename(packagePath),
|
|
790
|
+
icon,
|
|
791
|
+
identity: app ? readNativeAppIdentityFromXcodeTarget(app) : null,
|
|
792
|
+
}
|
|
719
793
|
}
|
|
720
794
|
|
|
721
795
|
// the hosted compile service caps what a project sends it; a local compile
|
|
722
796
|
// reads the author's own files, so nothing caps them there.
|
|
723
797
|
const HOSTED_LIMITS = {
|
|
798
|
+
sourceBytes: MAX_SWIFT_COMPILE_SOURCE_BYTES,
|
|
724
799
|
resources: MAX_SWIFT_COMPILE_RESOURCES,
|
|
725
800
|
resourceBytes: MAX_SWIFT_COMPILE_RESOURCE_BYTES,
|
|
726
801
|
resourceTotalBytes: MAX_SWIFT_COMPILE_RESOURCE_TOTAL_BYTES,
|
|
@@ -729,6 +804,7 @@ const HOSTED_LIMITS = {
|
|
|
729
804
|
imageTotalBytes: MAX_SWIFT_COMPILE_IMAGE_TOTAL_BYTES,
|
|
730
805
|
}
|
|
731
806
|
const LOCAL_LIMITS: typeof HOSTED_LIMITS = {
|
|
807
|
+
sourceBytes: Infinity,
|
|
732
808
|
resources: Infinity,
|
|
733
809
|
resourceBytes: Infinity,
|
|
734
810
|
resourceTotalBytes: Infinity,
|
|
@@ -937,7 +1013,7 @@ export function buildSwiftBundleFiles(
|
|
|
937
1013
|
const xcode = xcodeLayout(packagePath)
|
|
938
1014
|
const bundles =
|
|
939
1015
|
xcode?.resourceBundles ??
|
|
940
|
-
swiftResourceBundles(inputs ?? packageSources(packagePath), relative)
|
|
1016
|
+
swiftResourceBundles(inputs ?? packageSources(packagePath, hosted), relative)
|
|
941
1017
|
const addFile = (key: string, bytes: Buffer) => {
|
|
942
1018
|
if (
|
|
943
1019
|
Object.keys(files).length >= limits.resources ||
|
|
@@ -983,6 +1059,13 @@ export function buildSwiftBundleFiles(
|
|
|
983
1059
|
const disk = path.join(packagePath, ...file.split('/'))
|
|
984
1060
|
const bytes = readPackageFile(packagePath, disk, limits.resourceBytes)
|
|
985
1061
|
addFile(bundlePath === '.' ? base : `${bundlePath}/${base}`, bytes)
|
|
1062
|
+
if (base.endsWith('.xcstrings')) {
|
|
1063
|
+
for (const [name, table] of Object.entries(
|
|
1064
|
+
compileStringCatalog(bytes.toString('utf8'), base.slice(0, -10)),
|
|
1065
|
+
)) {
|
|
1066
|
+
addFile(bundlePath === '.' ? name : `${bundlePath}/${name}`, Buffer.from(table))
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
986
1069
|
}
|
|
987
1070
|
}
|
|
988
1071
|
return { files, bundles, watched }
|
|
@@ -1048,6 +1131,24 @@ export function sourceStamp(
|
|
|
1048
1131
|
return hash.digest('hex').slice(0, 16)
|
|
1049
1132
|
}
|
|
1050
1133
|
|
|
1134
|
+
// sourceStamp's inputs read by content and relative to the package, so
|
|
1135
|
+
// another process's build copy of the same sources stamps the same.
|
|
1136
|
+
export function sourceContentStamp(
|
|
1137
|
+
packagePath: string,
|
|
1138
|
+
moduleRootOverride?: string,
|
|
1139
|
+
): string {
|
|
1140
|
+
const hash = createHash('sha256')
|
|
1141
|
+
hash.update(JSON.stringify(xcodeProjects.get(packagePath)?.behavior ?? {}))
|
|
1142
|
+
const libraries = projects.get(packagePath)?.libraries
|
|
1143
|
+
if (libraries) hash.update(JSON.stringify(libraries))
|
|
1144
|
+
for (const file of [...new Set(packageFiles(packagePath))].sort()) {
|
|
1145
|
+
hash.update(path.relative(packagePath, file))
|
|
1146
|
+
hash.update(fs.readFileSync(file))
|
|
1147
|
+
}
|
|
1148
|
+
hash.update(moduleStamp(packagePath, moduleRootOverride, false))
|
|
1149
|
+
return hash.digest('hex').slice(0, 16)
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1051
1152
|
// the linked module's part of the source stamp
|
|
1052
1153
|
function moduleStamp(
|
|
1053
1154
|
packagePath: string,
|
|
@@ -1118,6 +1219,7 @@ export function standinLibraries(
|
|
|
1118
1219
|
|
|
1119
1220
|
function packageSources(
|
|
1120
1221
|
packagePath: string,
|
|
1222
|
+
hosted = false,
|
|
1121
1223
|
linked: ReadonlySet<SwiftStandin> = new Set(),
|
|
1122
1224
|
): Record<string, string> {
|
|
1123
1225
|
const xcode = xcodeLayout(packagePath)
|
|
@@ -1127,13 +1229,14 @@ function packageSources(
|
|
|
1127
1229
|
),
|
|
1128
1230
|
)
|
|
1129
1231
|
const files: Record<string, string> = {}
|
|
1232
|
+
const limits = inputLimits(hosted)
|
|
1130
1233
|
for (const file of sourceFiles(packagePath)) {
|
|
1131
1234
|
const relative = path.relative(packagePath, file).split(path.sep).join(path.posix.sep)
|
|
1132
1235
|
if (manifests.has(file)) continue
|
|
1133
1236
|
files[xcode ? xcodeInputKey(xcode, file, relative) : relative] = readPackageFile(
|
|
1134
1237
|
packagePath,
|
|
1135
1238
|
file,
|
|
1136
|
-
|
|
1239
|
+
limits.sourceBytes,
|
|
1137
1240
|
).toString('utf8')
|
|
1138
1241
|
}
|
|
1139
1242
|
if (xcode) {
|
|
@@ -1164,7 +1267,11 @@ function packageSnapshot(project: SwiftProject, entry: string): SwiftPackageSnap
|
|
|
1164
1267
|
const linked = layout
|
|
1165
1268
|
? standinLibraries(layout.binaries, layout.standins, layout.app.rootDir)
|
|
1166
1269
|
: new Map<string, SwiftStandin[]>()
|
|
1167
|
-
const files = packageSources(
|
|
1270
|
+
const files = packageSources(
|
|
1271
|
+
project.packagePath,
|
|
1272
|
+
Boolean(project.hosted),
|
|
1273
|
+
new Set([...linked.values()].flat()),
|
|
1274
|
+
)
|
|
1168
1275
|
const native = nativeSourceDescriptor(project.packagePath)
|
|
1169
1276
|
return {
|
|
1170
1277
|
files,
|
|
@@ -1177,9 +1284,7 @@ function packageSnapshot(project: SwiftProject, entry: string): SwiftPackageSnap
|
|
|
1177
1284
|
.relative(project.packagePath, file)
|
|
1178
1285
|
.split(path.sep)
|
|
1179
1286
|
.join(path.posix.sep)
|
|
1180
|
-
return layout
|
|
1181
|
-
? xcodeInputKey(layout, file, relative)
|
|
1182
|
-
: relative
|
|
1287
|
+
return layout ? xcodeInputKey(layout, file, relative) : relative
|
|
1183
1288
|
}),
|
|
1184
1289
|
),
|
|
1185
1290
|
resources: buildSwiftResourceTable(project, '', files),
|
|
@@ -1207,7 +1312,7 @@ function packageSnapshot(project: SwiftProject, entry: string): SwiftPackageSnap
|
|
|
1207
1312
|
glue: readPackageFile(
|
|
1208
1313
|
project.packagePath,
|
|
1209
1314
|
path.join(project.packagePath, NATIVE_GLUE),
|
|
1210
|
-
|
|
1315
|
+
inputLimits(Boolean(project.hosted)).sourceBytes,
|
|
1211
1316
|
).toString('utf8'),
|
|
1212
1317
|
}
|
|
1213
1318
|
: {}),
|
|
@@ -1304,13 +1409,20 @@ async function compile(
|
|
|
1304
1409
|
if (process.env.CONTRAST_SWIFT_BUILD_ROOT)
|
|
1305
1410
|
return process.env.CONTRAST_SWIFT_BUILD_ROOT
|
|
1306
1411
|
const resolvedPath = path.resolve(packagePath)
|
|
1412
|
+
// units of one app share a root so the module cache stays warm.
|
|
1413
|
+
// peach-expo-swift/<app>/<scope>/<package> shares <app>/<scope>, and
|
|
1414
|
+
// the older <app>/<package> layout shares <app>. the scope is this
|
|
1415
|
+
// process, so two servers do not compile into one tree.
|
|
1416
|
+
const parts = resolvedPath.split(path.sep)
|
|
1417
|
+
const expoAt = parts.lastIndexOf('peach-expo-swift')
|
|
1418
|
+
const scoped = expoAt >= 0 && parts.length >= expoAt + 4
|
|
1307
1419
|
const buildKeyPath =
|
|
1308
|
-
|
|
1309
|
-
?
|
|
1420
|
+
expoAt >= 0 && parts.length >= expoAt + 3
|
|
1421
|
+
? parts.slice(0, scoped ? expoAt + 3 : expoAt + 2).join(path.sep)
|
|
1310
1422
|
: resolvedPath
|
|
1311
1423
|
return path.join(
|
|
1312
|
-
|
|
1313
|
-
'
|
|
1424
|
+
cacheDir(),
|
|
1425
|
+
'swift-builds',
|
|
1314
1426
|
createHash('sha256')
|
|
1315
1427
|
.update(`${moduleRoot}\0${buildKeyPath}`)
|
|
1316
1428
|
.digest('hex')
|
|
@@ -1349,6 +1461,21 @@ const API_GAP_PATTERNS: [RegExp, (match: RegExpExecArray) => string][] = [
|
|
|
1349
1461
|
(match) => `${match[1]}.${match[2]}`,
|
|
1350
1462
|
],
|
|
1351
1463
|
[/^'([^']+)' is unavailable/, (match) => match[1]!],
|
|
1464
|
+
// an instance api Peach declares static, or the reverse
|
|
1465
|
+
[
|
|
1466
|
+
/^(?:static|instance) member '([^']+)' cannot be used on (?:instance of )?type '([^']+)'/,
|
|
1467
|
+
(match) => `${match[2]}.${match[1]}`,
|
|
1468
|
+
],
|
|
1469
|
+
// an overridable api Peach declares in an extension
|
|
1470
|
+
[
|
|
1471
|
+
/^(?:non-@objc )?[\w ]+ '([^']+)' is declared in extension of '([^']+)' and cannot be overridden/,
|
|
1472
|
+
(match) => `${match[2]}.${match[1]}`,
|
|
1473
|
+
],
|
|
1474
|
+
// a form of a macro Peach's expansion does not accept
|
|
1475
|
+
[
|
|
1476
|
+
/^(@\w+) requires an initial value/,
|
|
1477
|
+
(match) => `${match[1]} without an initial value`,
|
|
1478
|
+
],
|
|
1352
1479
|
[/^no such module '([^']+)'/, (match) => `module ${match[1]}`],
|
|
1353
1480
|
]
|
|
1354
1481
|
|
|
@@ -1363,7 +1490,7 @@ export function swiftApiGapReport(output: string): string | null {
|
|
|
1363
1490
|
const file = diagnostic[1]!
|
|
1364
1491
|
.replace(new RegExp(`^(?:${XCODE_APP_DIR}|${XCODE_FRAMEWORKS_DIR}/[^/]+)/`), '')
|
|
1365
1492
|
.replace(new RegExp(`^${XCODE_PACKAGES_DIR}/`), '')
|
|
1366
|
-
const message = diagnostic[4]
|
|
1493
|
+
const message = diagnostic[4]!.trim()
|
|
1367
1494
|
const rule = API_GAP_PATTERNS.find(([pattern]) => pattern.test(message))
|
|
1368
1495
|
const key = rule ? rule[1](rule[0].exec(message)!) : message
|
|
1369
1496
|
const table = rule ? gaps : other
|
|
@@ -1496,7 +1623,7 @@ async function compileRemote(
|
|
|
1496
1623
|
hosted?.authorization ?? (auth ? authHeaderValue(auth) : undefined)
|
|
1497
1624
|
const signal = AbortSignal.any([
|
|
1498
1625
|
...(ownerSignal ? [ownerSignal] : []),
|
|
1499
|
-
AbortSignal.timeout(
|
|
1626
|
+
AbortSignal.timeout(DEFAULT_SWIFT_COMPILE_BUILD_MS + 60_000),
|
|
1500
1627
|
])
|
|
1501
1628
|
const submit = httpSwiftCompileSubmit({
|
|
1502
1629
|
endpoint: origin,
|
|
@@ -1664,9 +1791,8 @@ export function swiftPackageWatchFiles(
|
|
|
1664
1791
|
packagePath = fs.realpathSync(packagePath)
|
|
1665
1792
|
return [
|
|
1666
1793
|
...(xcodeProjects.has(packagePath) ? [] : [path.join(packagePath, 'Package.swift')]),
|
|
1667
|
-
...
|
|
1794
|
+
...packageFiles(packagePath),
|
|
1668
1795
|
...nativeSourceFiles(packagePath),
|
|
1669
|
-
...resourceCandidateFiles(packagePath),
|
|
1670
1796
|
...(moduleRoot
|
|
1671
1797
|
? swiftModuleInputFiles(moduleRoot).map((file) => path.join(moduleRoot, file))
|
|
1672
1798
|
: []),
|
|
@@ -1682,14 +1808,14 @@ export async function swiftPackageMetadata(
|
|
|
1682
1808
|
signal?: AbortSignal,
|
|
1683
1809
|
): Promise<SwiftEntryModuleArgs> {
|
|
1684
1810
|
packagePath = fs.realpathSync(packagePath)
|
|
1811
|
+
const project = projectFor(packagePath, hosted, signal)
|
|
1812
|
+
const limits = inputLimits(Boolean(project.hosted))
|
|
1685
1813
|
const mains = sourceFiles(packagePath).filter(
|
|
1686
1814
|
(file) =>
|
|
1687
1815
|
file.endsWith('.swift') &&
|
|
1688
1816
|
swiftApplicationEntry(
|
|
1689
1817
|
file,
|
|
1690
|
-
readPackageFile(packagePath, file,
|
|
1691
|
-
'utf8',
|
|
1692
|
-
),
|
|
1818
|
+
readPackageFile(packagePath, file, limits.sourceBytes).toString('utf8'),
|
|
1693
1819
|
),
|
|
1694
1820
|
)
|
|
1695
1821
|
const native = nativeSourceDescriptor(packagePath)
|
|
@@ -1698,7 +1824,6 @@ export async function swiftPackageMetadata(
|
|
|
1698
1824
|
`swift package ${packageName} needs exactly one application entry source or generated native modules; found ${mains.length}`,
|
|
1699
1825
|
)
|
|
1700
1826
|
}
|
|
1701
|
-
const project = projectFor(packagePath, hosted, signal)
|
|
1702
1827
|
const entry =
|
|
1703
1828
|
mains.length === 1
|
|
1704
1829
|
? path.relative(packagePath, mains[0]).split(path.sep).join(path.posix.sep)
|
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 { swiftEnvironment, 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,
|
|
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(
|
|
58
|
-
|
|
59
|
-
|
|
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,16 +73,23 @@ 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()], {
|
|
77
|
+
env: swiftEnvironment(),
|
|
78
|
+
})
|
|
70
79
|
if (!listed.split('\n').some((line) => line.trim() === sdk))
|
|
71
80
|
throw new Error(`swift-icu: ${sdk} is not installed for ${swift}`)
|
|
72
|
-
const { stdout } = await run(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
const { stdout } = await run(
|
|
82
|
+
swift,
|
|
83
|
+
[
|
|
84
|
+
'sdk',
|
|
85
|
+
'configure',
|
|
86
|
+
...swiftSdkOptions(),
|
|
87
|
+
'--show-configuration',
|
|
88
|
+
sdk,
|
|
89
|
+
'wasm32-unknown-wasip1',
|
|
90
|
+
],
|
|
91
|
+
{ env: swiftEnvironment() },
|
|
92
|
+
)
|
|
79
93
|
const resources = /^swiftStaticResourcesPath: (.+)$/m.exec(stdout)?.[1]?.trim()
|
|
80
94
|
if (!resources) throw new Error(`swift-icu: ${sdk} names no static resources`)
|
|
81
95
|
// a copy, so the 42 MB archive it was sliced from can be collected
|