peachsim 0.1.1640 → 0.1.1644
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/README.md +5 -0
- package/cli/app-bundle-resources.ts +57 -0
- package/cli/app-stubs.ts +38 -13
- package/cli/cloud-session.ts +16 -0
- package/cli/commands/assert.ts +17 -4
- package/cli/commands/box.ts +33 -19
- package/cli/commands/control.ts +12 -0
- package/cli/commands/doctor.ts +24 -15
- package/cli/commands/inspect/actions.ts +3 -2
- package/cli/commands/inspect/core.ts +0 -8
- package/cli/commands/inspect.ts +0 -14
- package/cli/commands/platform.ts +2 -1
- package/cli/commands/record.ts +64 -28
- package/cli/commands/swift.ts +10 -2
- package/cli/commands/three-mode.ts +4 -38
- package/cli/commands/upload.ts +8 -8
- package/cli/drivers/playwright.ts +39 -0
- package/cli/flow-live-status.ts +0 -2
- package/cli/main.ts +1 -1
- package/cli/outbound-endpoints.ts +4 -2
- package/cli/ws-bridge.ts +0 -6
- package/detox/attempt-events.cjs +41 -5
- package/detox/index.ts +155 -109
- package/detox/jest-environment.cjs +5 -1
- package/detox/motion-change.cjs +7 -5
- package/detox/motion-change.d.cts +1 -0
- package/detox/motion-leg-starts.cjs +20 -0
- package/detox/motion-leg-starts.d.cts +8 -0
- 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 +12 -3
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -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 +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/bundle-channel.cjs +2671 -1833
- 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 +1 -1
- package/dist-lib/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +578 -130
- package/dist-lib/detox/jest-environment.cjs +5 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +2374 -1554
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +2968 -2141
- 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 +132 -57
- 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 +14 -6
- package/dist-lib/menu.mjs +14 -6
- 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 +120 -53
- package/dist-lib/native-app-identity-project.cjs +96 -44
- package/dist-lib/native-app-identity-project.mjs +95 -44
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- 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 +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +5 -3
- package/dist-lib/sdk.mjs +5 -3
- package/dist-lib/skills.cjs +3932 -2868
- package/dist-lib/swift-compile.cjs +587 -374
- package/dist-lib/swift-compile.mjs +595 -369
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +8 -3
- package/dist-lib/swift-submit.mjs +8 -3
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +2263 -1467
- package/dist-lib/vite.cjs +1 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/package.json +1 -1
- package/src/appBundleResourcesContract.ts +51 -0
- package/src/backend-origin.ts +11 -1
- package/src/host/bridge-host.ts +17 -11
- package/src/host/staged-host-files.ts +28 -4
- package/src/launchMetadataContract.ts +1 -0
- package/src/menu.ts +12 -4
- package/src/native-app-identity-project.ts +47 -38
- package/src/react-native-bridge-modules.ts +89 -76
- package/src/runtime-delivery.ts +6 -7
- package/src/swift-compile.ts +150 -112
- package/src/swift-compiler-digest.ts +5 -3
- package/src/swift-distribution.lock.json +9 -8
- package/src/swift-host.ts +6 -4
- package/src/swift-objc-compile.ts +416 -194
- package/src/swift-objc-metadata.ts +204 -0
- package/src/swift-objc-sdk.ts +195 -2
- package/src/swift-package-manifest.ts +6 -3
- package/src/swift-root-lock.ts +302 -0
- package/src/swift-submit.ts +15 -2
- package/src/swiftContract.ts +12 -0
- package/src/xcode-project.ts +77 -7
|
@@ -5,14 +5,25 @@ import path from 'node:path'
|
|
|
5
5
|
import { cacheDir } from './home-paths'
|
|
6
6
|
import { swiftSdkPaths, swiftModuleSdk } from './swift-compile'
|
|
7
7
|
import { swiftModuleToolchain } from './swift-distribution'
|
|
8
|
+
import {
|
|
9
|
+
compileObjcPeerMetadata,
|
|
10
|
+
ObjcPeerMetadataError,
|
|
11
|
+
validateObjcPeerClasses,
|
|
12
|
+
} from './swift-objc-metadata'
|
|
8
13
|
import { IOS_SDK_ID, materializeObjcSdk } from './swift-objc-standin'
|
|
9
14
|
import { swiftTargetModuleName } from './swift-package'
|
|
10
15
|
import { swiftPackageManifest, swiftTargetFiles } from './swift-package-manifest'
|
|
11
16
|
import { remoteSwiftCheckouts, type SwiftPackageBinary } from './swift-packages'
|
|
12
17
|
import { readPackageFile } from './swift-source-file'
|
|
13
18
|
import { swiftEnvironment } from './swift-toolchain'
|
|
19
|
+
import type {
|
|
20
|
+
SwiftObjCImplementationPreparation,
|
|
21
|
+
SwiftObjCPeerClass,
|
|
22
|
+
} from './swiftContract'
|
|
14
23
|
import type { XcodeAppTarget } from './xcode-project'
|
|
15
24
|
|
|
25
|
+
export class SwiftObjCCompilationError extends Error {}
|
|
26
|
+
|
|
16
27
|
/** compile original implementations before any header-derived replacement can hide them.
|
|
17
28
|
* successful objects still require the canonical framework bridge before linking. */
|
|
18
29
|
export function compileXcodeObjCImplementations(
|
|
@@ -20,6 +31,33 @@ export function compileXcodeObjCImplementations(
|
|
|
20
31
|
binaries: readonly SwiftPackageBinary[],
|
|
21
32
|
moduleRoot: string,
|
|
22
33
|
): never {
|
|
34
|
+
const prepared = prepareXcodeObjCImplementations(app, binaries, moduleRoot)
|
|
35
|
+
throw new SwiftObjCCompilationError(
|
|
36
|
+
`Objective-C implementations compiled, but linking their original method bodies requires the canonical Swift framework object bridge. No implementation was replaced. Inputs and objects: ${prepared.receipt}`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// a link's target, or null when the target is proven absent; other failures surface.
|
|
41
|
+
function linkedStat(file: string): fs.Stats | null {
|
|
42
|
+
try {
|
|
43
|
+
return fs.statSync(file)
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return null
|
|
46
|
+
throw error
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const isSdkInclude = (file: string): boolean =>
|
|
51
|
+
file === '/usr/include' || file.startsWith('/usr/include/')
|
|
52
|
+
|
|
53
|
+
/** prepares genuine objects without authorizing unsupported framework linkage. */
|
|
54
|
+
export function prepareXcodeObjCImplementations(
|
|
55
|
+
app: XcodeAppTarget,
|
|
56
|
+
binaries: readonly SwiftPackageBinary[],
|
|
57
|
+
moduleRoot: string,
|
|
58
|
+
peers?: readonly SwiftObjCPeerClass[],
|
|
59
|
+
): SwiftObjCImplementationPreparation {
|
|
60
|
+
if (peers) validateObjcPeerClasses(peers)
|
|
23
61
|
const toolchain = swiftModuleToolchain(moduleRoot)
|
|
24
62
|
const sdk = swiftSdkPaths(moduleRoot, toolchain.toolchainBin)
|
|
25
63
|
const sources = new Map<string, Buffer>()
|
|
@@ -38,12 +76,24 @@ export function compileXcodeObjCImplementations(
|
|
|
38
76
|
),
|
|
39
77
|
)
|
|
40
78
|
}
|
|
79
|
+
const walked = new Set<string>()
|
|
80
|
+
const ancestors = new Set<string>()
|
|
41
81
|
const addHeaders = (directory: string): void => {
|
|
82
|
+
// a versioned framework links Headers to a real directory: stage each path it is reached by, once, never around a link cycle.
|
|
83
|
+
const real = fs.realpathSync(directory)
|
|
84
|
+
if (walked.has(directory) || ancestors.has(real)) return
|
|
85
|
+
walked.add(directory)
|
|
86
|
+
ancestors.add(real)
|
|
42
87
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
88
|
+
if (entry.name.startsWith('.') && !entry.isFile()) continue
|
|
43
89
|
const file = path.join(directory, entry.name)
|
|
44
|
-
|
|
90
|
+
// a link to nothing is never opened by the compiler: a header that is needed fails there as not found.
|
|
91
|
+
const linked = entry.isSymbolicLink() ? linkedStat(file) : null
|
|
92
|
+
if (entry.isSymbolicLink() && !linked) continue
|
|
93
|
+
if (entry.isDirectory() || linked?.isDirectory()) addHeaders(file)
|
|
45
94
|
else if (/\.(h|hh|hpp|hxx|inc|inl|modulemap)$/.test(entry.name)) add(file, true)
|
|
46
95
|
}
|
|
96
|
+
ancestors.delete(real)
|
|
47
97
|
}
|
|
48
98
|
const targets: {
|
|
49
99
|
name: string
|
|
@@ -66,13 +116,26 @@ export function compileXcodeObjCImplementations(
|
|
|
66
116
|
throw new Error(`missing original implementation target ${binary.target}`)
|
|
67
117
|
const files = swiftTargetFiles(manifest, target)
|
|
68
118
|
for (const file of files) add(file)
|
|
69
|
-
|
|
119
|
+
// explicit sources select compile units, not the private headers they include.
|
|
120
|
+
addHeaders(target.dir)
|
|
70
121
|
for (const dependency of binary.headerDependencies ?? [])
|
|
71
122
|
addHeaders(path.resolve(binary.dir, dependency.path))
|
|
72
123
|
const settings = target.nativeSettings.filter(
|
|
73
124
|
(setting) =>
|
|
74
125
|
setting.configuration === null || setting.configuration === configuration,
|
|
75
126
|
)
|
|
127
|
+
const nativeFlags = settings.flatMap((setting) => setting.flags)
|
|
128
|
+
for (const [index, flag] of nativeFlags.entries()) {
|
|
129
|
+
const directory = ['-I', '-iquote', '-isystem'].includes(
|
|
130
|
+
nativeFlags[index - 1] ?? '',
|
|
131
|
+
)
|
|
132
|
+
? flag
|
|
133
|
+
: /^-I(.+)$/.exec(flag)?.[1]
|
|
134
|
+
if (directory) {
|
|
135
|
+
const include = path.resolve(target.dir, directory)
|
|
136
|
+
if (fs.existsSync(include)) addHeaders(include)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
76
139
|
targets.push({
|
|
77
140
|
name: target.name,
|
|
78
141
|
deploymentTarget: app.iosDeploymentTarget,
|
|
@@ -113,6 +176,12 @@ export function compileXcodeObjCImplementations(
|
|
|
113
176
|
),
|
|
114
177
|
])
|
|
115
178
|
add(file, true)
|
|
179
|
+
// search paths name directories of headers the target includes; /usr/include maps to the wasm sysroot.
|
|
180
|
+
for (const directory of [
|
|
181
|
+
...target.objc.headerSearchPaths,
|
|
182
|
+
...target.objc.frameworkSearchPaths,
|
|
183
|
+
])
|
|
184
|
+
if (!isSdkInclude(directory) && fs.existsSync(directory)) addHeaders(directory)
|
|
116
185
|
targets.push({
|
|
117
186
|
name: target.moduleName,
|
|
118
187
|
deploymentTarget: target.iosDeploymentTarget,
|
|
@@ -131,7 +200,9 @@ export function compileXcodeObjCImplementations(
|
|
|
131
200
|
[...sources].map(([file, bytes]) => [file, digest(bytes)]),
|
|
132
201
|
)
|
|
133
202
|
const identity = {
|
|
134
|
-
version:
|
|
203
|
+
version: 8,
|
|
204
|
+
peers,
|
|
205
|
+
metadataDeploymentTarget: peers ? app.iosDeploymentTarget : undefined,
|
|
135
206
|
release: toolchain.release,
|
|
136
207
|
compiler: digest(fs.readFileSync(path.join(toolchain.toolchainBin, 'clang'))),
|
|
137
208
|
sdk: swiftModuleSdk(moduleRoot),
|
|
@@ -144,212 +215,363 @@ export function compileXcodeObjCImplementations(
|
|
|
144
215
|
fs.mkdirSync(parent, { recursive: true })
|
|
145
216
|
const key = digest(JSON.stringify(identity))
|
|
146
217
|
const work = path.join(parent, key)
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
result.key !== key ||
|
|
159
|
-
typeof result.message !== 'string' ||
|
|
160
|
-
!result.message ||
|
|
161
|
-
!record(result.files) ||
|
|
162
|
-
Object.keys(result.files).length < sources.size + 1 ||
|
|
163
|
-
!('receipt.json' in result.files)
|
|
218
|
+
const descriptor = fs.openSync(`${work}.lock`, 'a')
|
|
219
|
+
try {
|
|
220
|
+
// the inherited descriptor keeps the kernel lock after perl exits.
|
|
221
|
+
const locked = spawnSync(
|
|
222
|
+
'perl',
|
|
223
|
+
['-e', 'open(my $f, ">&=", 3) or die $!; flock($f, 2) or die $!'],
|
|
224
|
+
{ stdio: ['ignore', 'pipe', 'pipe', descriptor], encoding: 'utf8' },
|
|
225
|
+
)
|
|
226
|
+
if (locked.error || locked.status !== 0)
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Could not lock Objective-C implementation inputs: ${locked.error?.message ?? locked.stderr}`,
|
|
164
229
|
)
|
|
165
|
-
|
|
166
|
-
|
|
230
|
+
const resultFile = path.join(work, 'result.json')
|
|
231
|
+
const preparation = (): SwiftObjCImplementationPreparation => ({
|
|
232
|
+
receipt: path.join(work, 'receipt.json'),
|
|
233
|
+
objects: targets.flatMap((target, index) =>
|
|
234
|
+
target.files.map((source, unit) => ({
|
|
235
|
+
target: target.name,
|
|
236
|
+
source,
|
|
237
|
+
file: path.join(work, `${index}-${unit}.o`),
|
|
238
|
+
})),
|
|
239
|
+
),
|
|
240
|
+
...(peers
|
|
241
|
+
? {
|
|
242
|
+
metadata: path.join(work, 'peer-metadata.o'),
|
|
243
|
+
registration: path.join(work, 'peer-registration.swift'),
|
|
244
|
+
}
|
|
245
|
+
: {}),
|
|
246
|
+
})
|
|
247
|
+
const record = (value: unknown): value is Record<string, unknown> =>
|
|
248
|
+
value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
249
|
+
if (fs.existsSync(work)) {
|
|
250
|
+
let message: string | undefined
|
|
251
|
+
let interrupted = false
|
|
252
|
+
let prepared = false
|
|
253
|
+
try {
|
|
254
|
+
const result: unknown = JSON.parse(
|
|
255
|
+
readPackageFile(work, resultFile, Infinity).toString(),
|
|
256
|
+
)
|
|
257
|
+
if (
|
|
258
|
+
!record(result) ||
|
|
259
|
+
result.key !== key ||
|
|
260
|
+
typeof result.reusable !== 'boolean' ||
|
|
261
|
+
typeof result.prepared !== 'boolean' ||
|
|
262
|
+
typeof result.message !== 'string' ||
|
|
263
|
+
!result.message ||
|
|
264
|
+
!record(result.files) ||
|
|
265
|
+
Object.keys(result.files).length < sources.size + 1 ||
|
|
266
|
+
!('receipt.json' in result.files)
|
|
267
|
+
)
|
|
268
|
+
throw new Error('invalid completed receipt')
|
|
269
|
+
for (const [file, hash] of Object.entries(result.files)) {
|
|
270
|
+
if (
|
|
271
|
+
typeof hash !== 'string' ||
|
|
272
|
+
digest(readPackageFile(work, path.resolve(work, file), Infinity)) !== hash
|
|
273
|
+
)
|
|
274
|
+
throw new Error(`retained file changed: ${file}`)
|
|
275
|
+
}
|
|
276
|
+
if (result.prepared) {
|
|
277
|
+
for (const file of [
|
|
278
|
+
...preparation().objects.map((object) => object.file),
|
|
279
|
+
...(peers
|
|
280
|
+
? [
|
|
281
|
+
'peer-metadata.o',
|
|
282
|
+
'peer-metadata.m',
|
|
283
|
+
'peer-metadata.log',
|
|
284
|
+
'peer-metadata.json',
|
|
285
|
+
'peer-registration.swift',
|
|
286
|
+
].map((file) => path.join(work, file))
|
|
287
|
+
: []),
|
|
288
|
+
])
|
|
289
|
+
if (!(path.relative(work, file) in result.files))
|
|
290
|
+
throw new Error(`missing prepared object or metadata input: ${file}`)
|
|
291
|
+
prepared = true
|
|
292
|
+
} else if (result.reusable) message = result.message
|
|
293
|
+
else interrupted = true
|
|
294
|
+
} catch {
|
|
295
|
+
// no writer can remain under this kernel lock; rebuild invalid proof.
|
|
296
|
+
fs.rmSync(work, { recursive: true })
|
|
297
|
+
}
|
|
298
|
+
if (prepared) return preparation()
|
|
299
|
+
if (message !== undefined) throw new SwiftObjCCompilationError(message)
|
|
300
|
+
// retain the first and latest complete interruptions for this input identity.
|
|
301
|
+
if (interrupted) {
|
|
302
|
+
const first = `${work}-interrupted-first`
|
|
303
|
+
const destination = fs.existsSync(first) ? `${work}-interrupted-latest` : first
|
|
304
|
+
fs.rmSync(destination, { recursive: true, force: true })
|
|
305
|
+
fs.renameSync(work, destination)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
fs.mkdirSync(work)
|
|
309
|
+
const staged = (file: string): string => {
|
|
310
|
+
for (const { root, name } of [
|
|
311
|
+
{ root: checkouts, name: 'packages' },
|
|
312
|
+
{ root: app.rootDir, name: 'project' },
|
|
313
|
+
]) {
|
|
314
|
+
const relative = path.relative(root, file)
|
|
167
315
|
if (
|
|
168
|
-
|
|
169
|
-
|
|
316
|
+
relative !== '..' &&
|
|
317
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
318
|
+
!path.isAbsolute(relative)
|
|
170
319
|
)
|
|
171
|
-
|
|
320
|
+
return path.join(work, name, relative)
|
|
172
321
|
}
|
|
173
|
-
|
|
174
|
-
|
|
322
|
+
// native SDK include paths select the wasm SDK, never host system headers.
|
|
323
|
+
if (isSdkInclude(file))
|
|
324
|
+
return path.join(sdk.sysroot, 'include', file.slice('/usr/include'.length))
|
|
175
325
|
throw new Error(
|
|
176
|
-
`Objective-C
|
|
177
|
-
{ cause },
|
|
326
|
+
`Objective-C compiler input is outside the project and its pinned packages: ${file}`,
|
|
178
327
|
)
|
|
179
328
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const staged = (file: string): string => {
|
|
185
|
-
for (const { root, name } of [
|
|
186
|
-
{ root: checkouts, name: 'packages' },
|
|
187
|
-
{ root: app.rootDir, name: 'project' },
|
|
188
|
-
]) {
|
|
189
|
-
const relative = path.relative(root, file)
|
|
190
|
-
if (
|
|
191
|
-
relative !== '..' &&
|
|
192
|
-
!relative.startsWith(`..${path.sep}`) &&
|
|
193
|
-
!path.isAbsolute(relative)
|
|
194
|
-
)
|
|
195
|
-
return path.join(work, name, relative)
|
|
329
|
+
for (const [file, bytes] of sources) {
|
|
330
|
+
const output = staged(file)
|
|
331
|
+
fs.mkdirSync(path.dirname(output), { recursive: true })
|
|
332
|
+
fs.writeFileSync(output, bytes)
|
|
196
333
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
for (const [file, bytes] of sources) {
|
|
205
|
-
const output = staged(file)
|
|
206
|
-
fs.mkdirSync(path.dirname(output), { recursive: true })
|
|
207
|
-
fs.writeFileSync(output, bytes)
|
|
208
|
-
}
|
|
209
|
-
const headers = materializeObjcSdk(path.join(cacheDir(), 'swift-standins'))
|
|
210
|
-
const modulemaps = targets.flatMap((target, index) => {
|
|
211
|
-
if (!target.publicHeaders) return []
|
|
212
|
-
const custom = path.join(target.publicHeaders, 'module.modulemap')
|
|
213
|
-
if (sources.has(custom)) return [`-fmodule-map-file=${staged(custom)}`]
|
|
214
|
-
const output = path.join(work, `module-${index}.modulemap`)
|
|
215
|
-
fs.writeFileSync(
|
|
216
|
-
output,
|
|
217
|
-
`module ${swiftTargetModuleName(target.name)} {\n umbrella ${JSON.stringify(staged(target.publicHeaders))}\n export *\n}\n`,
|
|
218
|
-
)
|
|
219
|
-
return [`-fmodule-map-file=${output}`]
|
|
220
|
-
})
|
|
221
|
-
const commands: {
|
|
222
|
-
target: string
|
|
223
|
-
source: string
|
|
224
|
-
args: string[]
|
|
225
|
-
status: number | null
|
|
226
|
-
signal: string | null
|
|
227
|
-
object?: string
|
|
228
|
-
}[] = []
|
|
229
|
-
const receipt = path.join(work, 'receipt.json')
|
|
230
|
-
const save = (status: string) =>
|
|
231
|
-
fs.writeFileSync(
|
|
232
|
-
receipt,
|
|
233
|
-
JSON.stringify({ ...identity, work, status, commands }, null, 2),
|
|
234
|
-
)
|
|
235
|
-
const finish = (status: string, message: string): never => {
|
|
236
|
-
save(status)
|
|
237
|
-
const files = [
|
|
238
|
-
...[...sources.keys()].map(staged),
|
|
239
|
-
...modulemaps.map((flag) => flag.slice('-fmodule-map-file='.length)),
|
|
240
|
-
receipt,
|
|
241
|
-
...commands.flatMap((command) => {
|
|
242
|
-
const output = command.args[command.args.lastIndexOf('-o') + 1]!
|
|
243
|
-
return [output.replace(/\.o$/, '.log'), ...(command.object ? [output] : [])]
|
|
244
|
-
}),
|
|
245
|
-
]
|
|
246
|
-
const result = {
|
|
247
|
-
key,
|
|
248
|
-
message,
|
|
249
|
-
files: Object.fromEntries(
|
|
250
|
-
files.map((file) => [path.relative(work, file), digest(fs.readFileSync(file))]),
|
|
251
|
-
),
|
|
252
|
-
}
|
|
253
|
-
fs.writeFileSync(`${resultFile}.pending`, JSON.stringify(result, null, 2))
|
|
254
|
-
fs.renameSync(`${resultFile}.pending`, resultFile)
|
|
255
|
-
throw new Error(message)
|
|
256
|
-
}
|
|
257
|
-
save('compiling')
|
|
258
|
-
for (const [index, target] of targets.entries()) {
|
|
259
|
-
const version = /^(\d{1,2})(?:\.(\d{1,2}))?(?:\.(\d{1,2}))?$/.exec(
|
|
260
|
-
target.deploymentTarget ?? '',
|
|
261
|
-
)
|
|
262
|
-
if (!version) {
|
|
263
|
-
return finish(
|
|
264
|
-
'unsupported-deployment-target',
|
|
265
|
-
`${target.name}: Objective-C compilation requires an explicit numeric IPHONEOS_DEPLOYMENT_TARGET; received ${JSON.stringify(target.deploymentTarget)}. Inputs: ${receipt}`,
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
const minimum =
|
|
269
|
-
Number(version[1]) * 10_000 +
|
|
270
|
-
Number(version[2] ?? 0) * 100 +
|
|
271
|
-
Number(version[3] ?? 0)
|
|
272
|
-
for (const [unit, file] of target.files.entries()) {
|
|
273
|
-
const output = path.join(work, `${index}-${unit}.o`)
|
|
274
|
-
const cxx = /\.(mm|cc|cpp|cxx)$/.test(file)
|
|
275
|
-
const objc = /\.mm?$/.test(file)
|
|
276
|
-
const flags = [
|
|
277
|
-
...target.flags.filter(
|
|
278
|
-
(flag) =>
|
|
279
|
-
(!cxx || !flag.startsWith('-std=')) && (objc || flag !== '-fobjc-arc'),
|
|
280
|
-
),
|
|
281
|
-
...(cxx ? target.cxxFlags : []),
|
|
282
|
-
...(target.fileFlags[file] ?? []),
|
|
283
|
-
].map((flag, index, flags) => {
|
|
284
|
-
if (
|
|
285
|
-
['-I', '-F', '-iquote', '-isystem', '-include'].includes(flags[index - 1] ?? '')
|
|
286
|
-
)
|
|
287
|
-
return staged(flag)
|
|
288
|
-
const include = /^(-I|-F)(\/.*)$/.exec(flag)
|
|
289
|
-
return include ? `${include[1]}${staged(include[2]!)}` : flag
|
|
290
|
-
})
|
|
291
|
-
const args = [
|
|
292
|
-
'--target=wasm32-unknown-wasip1',
|
|
293
|
-
`--sysroot=${sdk.sysroot}`,
|
|
294
|
-
'-fwasm-exceptions',
|
|
295
|
-
'-fexceptions',
|
|
296
|
-
'-D_WASI_EMULATED_SIGNAL',
|
|
297
|
-
'-D_WASI_EMULATED_MMAN',
|
|
298
|
-
`-D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=${minimum}`,
|
|
299
|
-
`-D__IPHONE_OS_VERSION_MIN_REQUIRED=${minimum}`,
|
|
300
|
-
...(objc ? ['-fobjc-runtime=gnustep-2.2', '-fobjc-exceptions', '-fblocks'] : []),
|
|
301
|
-
'-F',
|
|
302
|
-
path.join(headers, 'System/Library/Frameworks'),
|
|
303
|
-
// installed libc and objc runtime headers own the target ABI.
|
|
304
|
-
'-idirafter',
|
|
305
|
-
path.join(headers, 'usr/include'),
|
|
306
|
-
...target.includes.flatMap((include) => ['-I', staged(include)]),
|
|
307
|
-
...target.frameworks.flatMap((framework) => ['-F', staged(framework)]),
|
|
308
|
-
...modulemaps,
|
|
309
|
-
`-fmodule-name=${swiftTargetModuleName(target.name)}`,
|
|
310
|
-
`-fmodules-cache-path=${path.join(work, 'module-cache')}`,
|
|
311
|
-
...flags,
|
|
312
|
-
...(target.prefix ? ['-include', staged(target.prefix)] : []),
|
|
313
|
-
'-Os',
|
|
314
|
-
'-c',
|
|
315
|
-
staged(file),
|
|
316
|
-
'-o',
|
|
334
|
+
const headers = materializeObjcSdk(path.join(cacheDir(), 'swift-standins'))
|
|
335
|
+
const modulemaps = targets.flatMap((target, index) => {
|
|
336
|
+
if (!target.publicHeaders) return []
|
|
337
|
+
const custom = path.join(target.publicHeaders, 'module.modulemap')
|
|
338
|
+
if (sources.has(custom)) return [`-fmodule-map-file=${staged(custom)}`]
|
|
339
|
+
const output = path.join(work, `module-${index}.modulemap`)
|
|
340
|
+
fs.writeFileSync(
|
|
317
341
|
output,
|
|
318
|
-
|
|
319
|
-
const result = spawnSync(
|
|
320
|
-
path.join(toolchain.toolchainBin, cxx ? 'clang++' : 'clang'),
|
|
321
|
-
args,
|
|
322
|
-
{
|
|
323
|
-
cwd: work,
|
|
324
|
-
encoding: 'utf8',
|
|
325
|
-
env: swiftEnvironment(),
|
|
326
|
-
timeout: 120_000,
|
|
327
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
328
|
-
},
|
|
342
|
+
`module ${swiftTargetModuleName(target.name)} {\n umbrella ${JSON.stringify(staged(target.publicHeaders))}\n export *\n}\n`,
|
|
329
343
|
)
|
|
344
|
+
return [`-fmodule-map-file=${output}`]
|
|
345
|
+
})
|
|
346
|
+
const commands: {
|
|
347
|
+
target: string
|
|
348
|
+
source: string
|
|
349
|
+
args: string[]
|
|
350
|
+
status: number | null
|
|
351
|
+
signal: string | null
|
|
352
|
+
error?: string
|
|
353
|
+
object?: string
|
|
354
|
+
}[] = []
|
|
355
|
+
const receipt = path.join(work, 'receipt.json')
|
|
356
|
+
const save = (status: string) =>
|
|
330
357
|
fs.writeFileSync(
|
|
331
|
-
|
|
332
|
-
|
|
358
|
+
receipt,
|
|
359
|
+
JSON.stringify({ ...identity, work, status, commands }, null, 2),
|
|
360
|
+
)
|
|
361
|
+
let metadataFiles: string[] = []
|
|
362
|
+
const complete = (
|
|
363
|
+
status: string,
|
|
364
|
+
message: string,
|
|
365
|
+
reusable = true,
|
|
366
|
+
additionalFiles: string[] = [],
|
|
367
|
+
): void => {
|
|
368
|
+
save(status)
|
|
369
|
+
const files = [
|
|
370
|
+
...[...sources.keys()].map(staged),
|
|
371
|
+
...modulemaps.map((flag) => flag.slice('-fmodule-map-file='.length)),
|
|
372
|
+
receipt,
|
|
373
|
+
...metadataFiles,
|
|
374
|
+
...additionalFiles,
|
|
375
|
+
...commands.flatMap((command) => {
|
|
376
|
+
const output = command.args[command.args.lastIndexOf('-o') + 1]!
|
|
377
|
+
return [output.replace(/\.o$/, '.log'), ...(command.object ? [output] : [])]
|
|
378
|
+
}),
|
|
379
|
+
]
|
|
380
|
+
const result = {
|
|
381
|
+
key,
|
|
382
|
+
message,
|
|
383
|
+
reusable,
|
|
384
|
+
prepared: status === 'prepared',
|
|
385
|
+
files: Object.fromEntries(
|
|
386
|
+
files.map((file) => [path.relative(work, file), digest(fs.readFileSync(file))]),
|
|
387
|
+
),
|
|
388
|
+
}
|
|
389
|
+
fs.writeFileSync(`${resultFile}.pending`, JSON.stringify(result, null, 2))
|
|
390
|
+
fs.renameSync(`${resultFile}.pending`, resultFile)
|
|
391
|
+
}
|
|
392
|
+
const finish = (
|
|
393
|
+
status: string,
|
|
394
|
+
message: string,
|
|
395
|
+
reusable = true,
|
|
396
|
+
metadataFiles: string[] = [],
|
|
397
|
+
): never => {
|
|
398
|
+
complete(status, message, reusable, metadataFiles)
|
|
399
|
+
throw new SwiftObjCCompilationError(message)
|
|
400
|
+
}
|
|
401
|
+
save('compiling')
|
|
402
|
+
const minimumFor = (target: {
|
|
403
|
+
name: string
|
|
404
|
+
deploymentTarget: string | null
|
|
405
|
+
}): number => {
|
|
406
|
+
const version = /^(\d{1,2})(?:\.(\d{1,2}))?(?:\.(\d{1,2}))?$/.exec(
|
|
407
|
+
target.deploymentTarget ?? '',
|
|
333
408
|
)
|
|
334
|
-
|
|
335
|
-
target: target.name,
|
|
336
|
-
source: file,
|
|
337
|
-
args,
|
|
338
|
-
status: result.status,
|
|
339
|
-
signal: result.signal,
|
|
340
|
-
...(result.status === 0 ? { object: digest(fs.readFileSync(output)) } : {}),
|
|
341
|
-
})
|
|
342
|
-
if (result.error || result.status !== 0) {
|
|
409
|
+
if (!version) {
|
|
343
410
|
return finish(
|
|
344
|
-
'
|
|
345
|
-
|
|
411
|
+
'unsupported-deployment-target',
|
|
412
|
+
`${target.name}: Objective-C compilation requires an explicit numeric IPHONEOS_DEPLOYMENT_TARGET; received ${JSON.stringify(target.deploymentTarget)}. Inputs: ${receipt}`,
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
const minimum =
|
|
416
|
+
Number(version[1]) * 10_000 +
|
|
417
|
+
Number(version[2] ?? 0) * 100 +
|
|
418
|
+
Number(version[3] ?? 0)
|
|
419
|
+
return minimum
|
|
420
|
+
}
|
|
421
|
+
if (targets.length === 0)
|
|
422
|
+
return finish(
|
|
423
|
+
'missing-target',
|
|
424
|
+
'Objective-C preparation requires an original implementation target',
|
|
425
|
+
)
|
|
426
|
+
const minimums = targets.map(minimumFor)
|
|
427
|
+
if (peers) {
|
|
428
|
+
try {
|
|
429
|
+
metadataFiles = compileObjcPeerMetadata({
|
|
430
|
+
classes: peers,
|
|
431
|
+
work,
|
|
432
|
+
headerCache: path.join(cacheDir(), 'swift-standins'),
|
|
433
|
+
toolchainBin: toolchain.toolchainBin,
|
|
434
|
+
sysroot: sdk.sysroot,
|
|
435
|
+
minimum: minimumFor({
|
|
436
|
+
name: app.moduleName,
|
|
437
|
+
deploymentTarget: app.iosDeploymentTarget,
|
|
438
|
+
}),
|
|
439
|
+
descriptor,
|
|
440
|
+
}).files
|
|
441
|
+
} catch (error) {
|
|
442
|
+
if (error instanceof ObjcPeerMetadataError)
|
|
443
|
+
return finish(
|
|
444
|
+
error.reusable ? 'metadata-failed' : 'metadata-interrupted',
|
|
445
|
+
error.message,
|
|
446
|
+
error.reusable,
|
|
447
|
+
error.files,
|
|
448
|
+
)
|
|
449
|
+
throw error
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const failures: string[] = []
|
|
453
|
+
let interrupted = false
|
|
454
|
+
compile: for (const [index, target] of targets.entries()) {
|
|
455
|
+
const minimum = minimums[index]!
|
|
456
|
+
for (const [unit, file] of target.files.entries()) {
|
|
457
|
+
const output = path.join(work, `${index}-${unit}.o`)
|
|
458
|
+
const cxx = /\.(mm|cc|cpp|cxx)$/.test(file)
|
|
459
|
+
const objc = /\.mm?$/.test(file)
|
|
460
|
+
const flags = [
|
|
461
|
+
...target.flags.filter(
|
|
462
|
+
(flag) =>
|
|
463
|
+
(!cxx || !flag.startsWith('-std=')) && (objc || flag !== '-fobjc-arc'),
|
|
464
|
+
),
|
|
465
|
+
...(cxx ? target.cxxFlags : []),
|
|
466
|
+
...(target.fileFlags[file] ?? []),
|
|
467
|
+
].map((flag, index, flags) => {
|
|
468
|
+
if (
|
|
469
|
+
['-I', '-F', '-iquote', '-isystem', '-include'].includes(
|
|
470
|
+
flags[index - 1] ?? '',
|
|
471
|
+
)
|
|
472
|
+
)
|
|
473
|
+
return staged(flag)
|
|
474
|
+
const include = /^(-I|-F)(\/.*)$/.exec(flag)
|
|
475
|
+
return include ? `${include[1]}${staged(include[2]!)}` : flag
|
|
476
|
+
})
|
|
477
|
+
const args = [
|
|
478
|
+
'--target=wasm32-unknown-wasip1',
|
|
479
|
+
`--sysroot=${sdk.sysroot}`,
|
|
480
|
+
'-fwasm-exceptions',
|
|
481
|
+
'-fexceptions',
|
|
482
|
+
'-D_WASI_EMULATED_SIGNAL',
|
|
483
|
+
'-D_WASI_EMULATED_MMAN',
|
|
484
|
+
`-D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=${minimum}`,
|
|
485
|
+
`-D__IPHONE_OS_VERSION_MIN_REQUIRED=${minimum}`,
|
|
486
|
+
...(objc
|
|
487
|
+
? ['-fobjc-runtime=gnustep-2.2', '-fobjc-exceptions', '-fblocks']
|
|
488
|
+
: []),
|
|
489
|
+
'-F',
|
|
490
|
+
path.join(headers, 'System/Library/Frameworks'),
|
|
491
|
+
// installed libc and objc runtime headers own the target ABI.
|
|
492
|
+
'-idirafter',
|
|
493
|
+
path.join(headers, 'usr/include'),
|
|
494
|
+
...target.includes.flatMap((include) => ['-I', staged(include)]),
|
|
495
|
+
...target.frameworks.flatMap((framework) => ['-F', staged(framework)]),
|
|
496
|
+
...modulemaps,
|
|
497
|
+
`-fmodule-name=${swiftTargetModuleName(target.name)}`,
|
|
498
|
+
`-fmodules-cache-path=${path.join(work, 'module-cache')}`,
|
|
499
|
+
...flags,
|
|
500
|
+
...(target.prefix ? ['-include', staged(target.prefix)] : []),
|
|
501
|
+
'-Os',
|
|
502
|
+
'-c',
|
|
503
|
+
staged(file),
|
|
504
|
+
'-o',
|
|
505
|
+
output,
|
|
506
|
+
]
|
|
507
|
+
const result = spawnSync(
|
|
508
|
+
path.join(toolchain.toolchainBin, cxx ? 'clang++' : 'clang'),
|
|
509
|
+
args,
|
|
510
|
+
{
|
|
511
|
+
cwd: work,
|
|
512
|
+
encoding: 'utf8',
|
|
513
|
+
stdio: ['ignore', 'pipe', 'pipe', descriptor],
|
|
514
|
+
env: swiftEnvironment(),
|
|
515
|
+
timeout: 120_000,
|
|
516
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
517
|
+
},
|
|
346
518
|
)
|
|
519
|
+
const diagnostics = `${result.stdout ?? ''}${result.stderr ?? ''}${result.error ? `\n${result.error.message}` : ''}${result.signal ? `\nClang terminated by ${result.signal}` : ''}`
|
|
520
|
+
fs.writeFileSync(path.join(work, `${index}-${unit}.log`), diagnostics)
|
|
521
|
+
commands.push({
|
|
522
|
+
target: target.name,
|
|
523
|
+
source: file,
|
|
524
|
+
args,
|
|
525
|
+
status: result.status,
|
|
526
|
+
signal: result.signal,
|
|
527
|
+
...(result.error ? { error: result.error.message } : {}),
|
|
528
|
+
...(!result.error && !result.signal && result.status === 0
|
|
529
|
+
? { object: digest(fs.readFileSync(output)) }
|
|
530
|
+
: {}),
|
|
531
|
+
})
|
|
532
|
+
if (result.error || result.signal || result.status !== 0) {
|
|
533
|
+
failures.push(
|
|
534
|
+
`${target.name}: ${file}\n${diagnostics.trimEnd() || `Clang exited with status ${result.status}`}`,
|
|
535
|
+
)
|
|
536
|
+
// clang's driver also returns 1 when its frontend crashes or cannot write.
|
|
537
|
+
if (
|
|
538
|
+
result.error ||
|
|
539
|
+
result.signal ||
|
|
540
|
+
result.status !== 1 ||
|
|
541
|
+
/^(?:PLEASE submit a bug report|LLVM ERROR:|(?:clang(?:\+\+)?(?:-\d+)?: )?(?:fatal )?error: (?:error in backend:|unable to (?:execute command|open output file|make temporary file)|.*command failed))/m.test(
|
|
542
|
+
result.stderr ?? '',
|
|
543
|
+
)
|
|
544
|
+
) {
|
|
545
|
+
interrupted = true
|
|
546
|
+
break compile
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
save('compiling')
|
|
347
550
|
}
|
|
348
|
-
save('compiling')
|
|
349
551
|
}
|
|
552
|
+
if (failures.length > 0) {
|
|
553
|
+
const selected = targets.reduce((count, target) => count + target.files.length, 0)
|
|
554
|
+
const successes = commands.filter((command) => command.object !== undefined).length
|
|
555
|
+
return finish(
|
|
556
|
+
interrupted ? 'compile-interrupted' : 'compile-failed',
|
|
557
|
+
[
|
|
558
|
+
interrupted
|
|
559
|
+
? `Objective-C implementation compilation interrupted after ${commands.length} of ${selected} translation units; this attempt will not be reused.`
|
|
560
|
+
: `Objective-C implementation compilation failed in ${failures.length} of ${selected} translation units.`,
|
|
561
|
+
`${successes} successful object${successes === 1 ? '' : 's'} retained.`,
|
|
562
|
+
...failures,
|
|
563
|
+
`Original inputs, compiler commands and diagnostics: ${receipt}`,
|
|
564
|
+
].join('\n\n'),
|
|
565
|
+
!interrupted,
|
|
566
|
+
)
|
|
567
|
+
}
|
|
568
|
+
complete(
|
|
569
|
+
'prepared',
|
|
570
|
+
'Original Objective-C objects prepared; framework linkage remains unqualified.',
|
|
571
|
+
true,
|
|
572
|
+
)
|
|
573
|
+
return preparation()
|
|
574
|
+
} finally {
|
|
575
|
+
fs.closeSync(descriptor)
|
|
350
576
|
}
|
|
351
|
-
return finish(
|
|
352
|
-
'requires-framework-bridge',
|
|
353
|
-
`Objective-C implementations compiled, but linking their original method bodies requires the canonical Swift framework object bridge. No implementation was replaced. Inputs and objects: ${receipt}`,
|
|
354
|
-
)
|
|
355
577
|
}
|