peachsim 0.1.2198 → 0.1.2200
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/cli/commands/inspect/settling.ts +20 -20
- package/cli/outbound-endpoints.ts +12 -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 +1 -1
- 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 +1532 -1170
- 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 +22 -22
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +45875 -39899
- 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 +50942 -49780
- package/dist-lib/host/fetch-proxy-handler.cjs +16 -4
- 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 +36 -24
- 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 +100 -10
- 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 +16 -4
- package/dist-lib/native-app-identity-project.cjs +14 -2
- package/dist-lib/native-app-identity-project.mjs +14 -2
- 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/pdfium-raster.cjs +1 -1
- package/dist-lib/pdfium-raster.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/public-origins-lib-esm.json +24 -24
- package/dist-lib/public-origins-lib.json +302 -289
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/reap-browser.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +21 -21
- package/dist-lib/sdk.mjs +21 -21
- package/dist-lib/skills.cjs +3622 -2962
- package/dist-lib/swift-compile.cjs +34 -3
- package/dist-lib/swift-compile.mjs +34 -3
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +129 -43
- package/dist-lib/vite.cjs +16 -4
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/dist-types/packages/peach-engine/src/engine/scene-table.d.ts +2 -0
- package/dist-types/packages/peach-engine/src/render-worker/app-prefetch-contract.d.ts +5 -0
- package/dist-types/packages/peach-engine/src/render-worker/protocol.d.ts +2 -0
- package/dist-types/packages/peach-engine/src/render-worker/tree-sync-port.d.ts +5 -0
- package/kotlin/sdk.tar.gz +0 -0
- package/package.json +1 -1
- package/runtime-companion.json +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h +2 -0
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSBundle.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSByteCountFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSCharacterSet.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSCompoundPredicate.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSDateIntervalFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSDecimalNumber.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSEnergyFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSExpression.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSFileHandle.h +2 -2
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSISO8601DateFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSIndexSet.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSLengthFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSLock.h +3 -3
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSMassFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSMeasurementFormatter.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSNotification.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSNotificationQueue.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h +4 -0
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSOrthography.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSScanner.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSThread.h +1 -1
- package/sdk/ios/System/Library/Frameworks/Foundation.framework/Headers/NSUnit.h +25 -25
- package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Headers/CAValueFunction.h +1 -1
- package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Headers/CoreAnimation.h +1 -0
- package/sdk/ios/System/Library/Frameworks/QuartzCore.framework/Modules/module.modulemap +1 -0
- package/sdk/ios/System/Library/Frameworks/UIKitText.framework/Headers/UIKitText.apinotes +14 -0
- package/src/android-layout-ids.ts +50 -0
- package/src/android-view-classes.ts +41 -0
- package/src/expo-native-workspace.ts +2 -3
- package/src/expo-swift-host.ts +6 -1
- package/src/java-module-abi.ts +2 -0
- package/src/javaAbiContract.ts +2 -0
- package/src/kotlin-app.ts +33 -7
- package/src/kotlin-host.ts +83 -6
- package/src/kotlin-maven-sources.ts +248 -0
- package/src/native-prebuilt.ts +36 -2
- package/src/native-swift-slices.ts +43 -2
- package/src/react-native-bridge-modules.ts +7 -1
- package/src/react-native-core-build.ts +176 -0
- package/src/react-native-core-digest.ts +1 -1
- package/src/react-native-core.ts +24 -7
- package/src/swift-packages.ts +3 -0
- package/src/swift-standin-emit.ts +9 -1
- package/src/swift-standins.ts +5 -2
- package/src/swift-wasm-prune.ts +41 -3
- package/src/turbo-compile.ts +2 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// a dependency outside Peach's pinned closure compiles from the sources jar
|
|
2
|
+
// the app's Maven repositories publish for that coordinate
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import os from 'node:os'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { inflateRawSync } from 'node:zlib'
|
|
8
|
+
|
|
9
|
+
const pending = new Map<string, Promise<string[]>>()
|
|
10
|
+
|
|
11
|
+
function braceBlock(text: string, from: number): { body: string; end: number } | null {
|
|
12
|
+
const open = text.indexOf('{', from)
|
|
13
|
+
if (open < 0) return null
|
|
14
|
+
let depth = 0
|
|
15
|
+
for (let index = open; index < text.length; index++) {
|
|
16
|
+
const char = text[index]
|
|
17
|
+
if (char === '{') depth++
|
|
18
|
+
else if (char === '}') {
|
|
19
|
+
depth--
|
|
20
|
+
if (depth === 0) return { body: text.slice(open + 1, index), end: index }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function repositoryRoot(value: string, where: string): string {
|
|
27
|
+
if (value.startsWith('https://')) return value.replace(/\/+$/, '')
|
|
28
|
+
if (value.startsWith('/')) return value.replace(/\/+$/, '')
|
|
29
|
+
if (value.startsWith('file:')) {
|
|
30
|
+
const pathname = new URL(value).pathname
|
|
31
|
+
if (!pathname.startsWith('/'))
|
|
32
|
+
throw new Error(`${where}: Maven repository ${value} is not a local path`)
|
|
33
|
+
return decodeURIComponent(pathname).replace(/\/+$/, '')
|
|
34
|
+
}
|
|
35
|
+
throw new Error(
|
|
36
|
+
`${where}: Maven repository ${value} is not an https URL or a local path`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// google(), mavenCentral(), mavenLocal(), or one literal maven URL
|
|
41
|
+
function withoutComments(text: string): string {
|
|
42
|
+
let out = ''
|
|
43
|
+
for (let index = 0; index < text.length; index++) {
|
|
44
|
+
const char = text[index]
|
|
45
|
+
const next = text[index + 1]
|
|
46
|
+
if (char === '"' || char === "'") {
|
|
47
|
+
const quote = char
|
|
48
|
+
out += char
|
|
49
|
+
index++
|
|
50
|
+
while (index < text.length && text[index] !== quote) {
|
|
51
|
+
out += text[index]
|
|
52
|
+
if (text[index] === '\\') {
|
|
53
|
+
index++
|
|
54
|
+
out += text[index] ?? ''
|
|
55
|
+
}
|
|
56
|
+
index++
|
|
57
|
+
}
|
|
58
|
+
out += text[index] ?? ''
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (char === '/' && next === '/') {
|
|
62
|
+
while (index < text.length && text[index] !== '\n') index++
|
|
63
|
+
out += '\n'
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
if (char === '/' && next === '*') {
|
|
67
|
+
index += 2
|
|
68
|
+
while (index < text.length && !(text[index] === '*' && text[index + 1] === '/'))
|
|
69
|
+
index++
|
|
70
|
+
index++
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
out += char
|
|
74
|
+
}
|
|
75
|
+
return out
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function repositories(body: string, where: string): string[] {
|
|
79
|
+
const stripped = withoutComments(body)
|
|
80
|
+
const roots: string[] = []
|
|
81
|
+
const leftover = stripped.replace(
|
|
82
|
+
/\b(google|mavenCentral|mavenLocal)\s*\(\s*\)|\bmaven\s*(?:\(\s*["']([^"']+)["']\s*\)|\{([^{}]*)\})/g,
|
|
83
|
+
(match, name: string, direct: string | undefined, block: string | undefined) => {
|
|
84
|
+
if (name === 'google') roots.push('https://dl.google.com/dl/android/maven2')
|
|
85
|
+
else if (name === 'mavenCentral') roots.push('https://repo1.maven.org/maven2')
|
|
86
|
+
else if (name === 'mavenLocal')
|
|
87
|
+
roots.push(path.join(os.homedir(), '.m2', 'repository'))
|
|
88
|
+
else {
|
|
89
|
+
const declared = direct ?? /["']([^"']+)["']/.exec(block ?? '')?.[1]
|
|
90
|
+
if (!declared)
|
|
91
|
+
throw new Error(`${where}: ${match.trim()} declares no repository URL`)
|
|
92
|
+
roots.push(repositoryRoot(declared, where))
|
|
93
|
+
}
|
|
94
|
+
return ''
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
if (leftover.replace(/[\s;]/g, ''))
|
|
98
|
+
throw new Error(`${where}: ${leftover.trim()} is not a literal Maven repository`)
|
|
99
|
+
return roots
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mavenRepositories(projectDir: string, where: string): string[] {
|
|
103
|
+
const file = ['settings.gradle.kts', 'settings.gradle']
|
|
104
|
+
.map((name) => path.join(projectDir, name))
|
|
105
|
+
.find((candidate) => fs.existsSync(candidate))
|
|
106
|
+
if (!file) throw new Error(`${where}: ${projectDir} has no settings.gradle`)
|
|
107
|
+
const text = fs.readFileSync(file, 'utf8')
|
|
108
|
+
const management = text.search(/\bdependencyResolutionManagement\b/)
|
|
109
|
+
if (management < 0)
|
|
110
|
+
throw new Error(`${where}: ${file} declares no dependency repositories`)
|
|
111
|
+
const managed = braceBlock(text, management)
|
|
112
|
+
if (!managed)
|
|
113
|
+
throw new Error(`${where}: ${file} dependencyResolutionManagement block never closes`)
|
|
114
|
+
const repositoriesAt = managed.body.search(/\brepositories\b/)
|
|
115
|
+
if (repositoriesAt < 0)
|
|
116
|
+
throw new Error(`${where}: ${file} declares no dependency repositories`)
|
|
117
|
+
const block = braceBlock(managed.body, repositoriesAt)
|
|
118
|
+
if (!block) throw new Error(`${where}: ${file} repositories block never closes`)
|
|
119
|
+
const roots = repositories(block.body, `${where}: ${file}`)
|
|
120
|
+
if (roots.length === 0)
|
|
121
|
+
throw new Error(`${where}: ${file} declares no dependency repositories`)
|
|
122
|
+
return roots
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function zipSources(bytes: Buffer, where: string): { name: string; data: Buffer }[] {
|
|
126
|
+
let end = -1
|
|
127
|
+
const earliest = Math.max(0, bytes.length - 22 - 0xffff)
|
|
128
|
+
for (let index = bytes.length - 22; index >= earliest; index--) {
|
|
129
|
+
if (bytes.readUInt32LE(index) === 0x06054b50) {
|
|
130
|
+
end = index
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (end < 0) throw new Error(`${where}: sources jar is not a zip`)
|
|
135
|
+
const count = bytes.readUInt16LE(end + 8)
|
|
136
|
+
if (count !== bytes.readUInt16LE(end + 10))
|
|
137
|
+
throw new Error(`${where}: sources jar spans zip disks`)
|
|
138
|
+
let offset = bytes.readUInt32LE(end + 16)
|
|
139
|
+
if (offset === 0xffffffff) throw new Error(`${where}: sources jar is zip64`)
|
|
140
|
+
const entries: { name: string; data: Buffer }[] = []
|
|
141
|
+
for (let index = 0; index < count; index++) {
|
|
142
|
+
if (bytes.readUInt32LE(offset) !== 0x02014b50)
|
|
143
|
+
throw new Error(`${where}: sources jar has a broken central directory`)
|
|
144
|
+
const flags = bytes.readUInt16LE(offset + 8)
|
|
145
|
+
const method = bytes.readUInt16LE(offset + 10)
|
|
146
|
+
const compressed = bytes.readUInt32LE(offset + 20)
|
|
147
|
+
const nameLength = bytes.readUInt16LE(offset + 28)
|
|
148
|
+
const extraLength = bytes.readUInt16LE(offset + 30)
|
|
149
|
+
const commentLength = bytes.readUInt16LE(offset + 32)
|
|
150
|
+
const localOffset = bytes.readUInt32LE(offset + 42)
|
|
151
|
+
const name = bytes.toString('utf8', offset + 46, offset + 46 + nameLength)
|
|
152
|
+
offset += 46 + nameLength + extraLength + commentLength
|
|
153
|
+
if (name.endsWith('/')) continue
|
|
154
|
+
if (flags & 1) throw new Error(`${where}: ${name} is encrypted`)
|
|
155
|
+
if (name.startsWith('/') || name.split('/').includes('..'))
|
|
156
|
+
throw new Error(`${where}: ${name} escapes the sources jar`)
|
|
157
|
+
if (bytes.readUInt32LE(localOffset) !== 0x04034b50)
|
|
158
|
+
throw new Error(`${where}: ${name} has no local header`)
|
|
159
|
+
const localName = bytes.readUInt16LE(localOffset + 26)
|
|
160
|
+
const localExtra = bytes.readUInt16LE(localOffset + 28)
|
|
161
|
+
const start = localOffset + 30 + localName + localExtra
|
|
162
|
+
const payload = bytes.subarray(start, start + compressed)
|
|
163
|
+
const data =
|
|
164
|
+
method === 0 ? Buffer.from(payload) : method === 8 ? inflateRawSync(payload) : null
|
|
165
|
+
if (!data) throw new Error(`${where}: ${name} uses zip method ${method}`)
|
|
166
|
+
entries.push({ name, data })
|
|
167
|
+
}
|
|
168
|
+
return entries
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extract(bytes: Buffer, where: string): Promise<string[]> {
|
|
172
|
+
const key = createHash('sha256').update(bytes).digest('hex')
|
|
173
|
+
const cached = pending.get(key)
|
|
174
|
+
if (cached) return cached
|
|
175
|
+
const request = (async () => {
|
|
176
|
+
const dest = path.join(os.tmpdir(), 'peach-maven-sources', key)
|
|
177
|
+
const marker = path.join(dest, '.complete')
|
|
178
|
+
if (!fs.existsSync(marker)) {
|
|
179
|
+
const entries = zipSources(bytes, where)
|
|
180
|
+
const java = entries.find((entry) => entry.name.endsWith('.java'))
|
|
181
|
+
if (java) throw new Error(`${where}: ${java.name} is a Java source`)
|
|
182
|
+
const kotlin = entries.filter((entry) => entry.name.endsWith('.kt'))
|
|
183
|
+
if (kotlin.length === 0) throw new Error(`${where}: sources jar has no Kotlin`)
|
|
184
|
+
const staging = `${dest}.partial-${process.pid}`
|
|
185
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
186
|
+
for (const entry of kotlin) {
|
|
187
|
+
const file = path.join(staging, entry.name)
|
|
188
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
189
|
+
fs.writeFileSync(file, entry.data)
|
|
190
|
+
}
|
|
191
|
+
fs.writeFileSync(path.join(staging, '.complete'), '')
|
|
192
|
+
try {
|
|
193
|
+
fs.renameSync(staging, dest)
|
|
194
|
+
} catch (error) {
|
|
195
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
196
|
+
if (!fs.existsSync(marker)) throw error
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return fs
|
|
200
|
+
.readdirSync(dest, { recursive: true })
|
|
201
|
+
.flatMap((entry) => {
|
|
202
|
+
const name = String(entry)
|
|
203
|
+
return name.endsWith('.kt') ? [path.join(dest, name)] : []
|
|
204
|
+
})
|
|
205
|
+
.sort()
|
|
206
|
+
})().catch((error: unknown) => {
|
|
207
|
+
pending.delete(key)
|
|
208
|
+
throw error
|
|
209
|
+
})
|
|
210
|
+
pending.set(key, request)
|
|
211
|
+
return request
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function sourcesJar(
|
|
215
|
+
root: string,
|
|
216
|
+
group: string,
|
|
217
|
+
artifact: string,
|
|
218
|
+
version: string,
|
|
219
|
+
): Promise<Buffer | null> {
|
|
220
|
+
const parts = [
|
|
221
|
+
...group.split('.'),
|
|
222
|
+
artifact,
|
|
223
|
+
version,
|
|
224
|
+
`${artifact}-${version}-sources.jar`,
|
|
225
|
+
]
|
|
226
|
+
if (root.startsWith('https://')) {
|
|
227
|
+
const url = `${root}/${parts.join('/')}`
|
|
228
|
+
const response = await fetch(url)
|
|
229
|
+
if (response.status === 404) return null
|
|
230
|
+
if (!response.ok) throw new Error(`${url} answered ${response.status}`)
|
|
231
|
+
return Buffer.from(await response.arrayBuffer())
|
|
232
|
+
}
|
|
233
|
+
const file = path.join(root, ...parts)
|
|
234
|
+
return fs.existsSync(file) ? fs.readFileSync(file) : null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function mavenKotlinSources(
|
|
238
|
+
projectDir: string,
|
|
239
|
+
coordinate: { group: string; artifact: string; version: string },
|
|
240
|
+
where: string,
|
|
241
|
+
): Promise<string[] | null> {
|
|
242
|
+
const { group, artifact, version } = coordinate
|
|
243
|
+
for (const root of mavenRepositories(projectDir, where)) {
|
|
244
|
+
const jar = await sourcesJar(root, group, artifact, version)
|
|
245
|
+
if (jar) return extract(jar, `${where}: ${group}:${artifact}:${version}`)
|
|
246
|
+
}
|
|
247
|
+
return null
|
|
248
|
+
}
|
package/src/native-prebuilt.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function nativeEntryKey(inputs: Record<string, string>): string {
|
|
|
58
58
|
export const nativeEntryPrefix = (kind: NativeEntryKind, name: string, key: string) =>
|
|
59
59
|
`native/${kind}/${name}/${key}`
|
|
60
60
|
|
|
61
|
-
const
|
|
61
|
+
export const nativeEntryCache = (kind: NativeEntryKind, name: string, key: string) =>
|
|
62
62
|
path.join(peachCacheDir(), 'native-prebuilt', kind, name, key)
|
|
63
63
|
|
|
64
64
|
// one GET; a missing object is null. a network failure is a miss too, since a
|
|
@@ -111,7 +111,7 @@ export function fetchNativeEntry(
|
|
|
111
111
|
name: string,
|
|
112
112
|
key: string,
|
|
113
113
|
): string | null {
|
|
114
|
-
const dir =
|
|
114
|
+
const dir = nativeEntryCache(kind, name, key)
|
|
115
115
|
const descriptorFile = path.join(dir, 'descriptor.json')
|
|
116
116
|
if (fs.existsSync(descriptorFile)) return dir
|
|
117
117
|
const base = `${NATIVE_PREBUILT_ORIGIN}/${nativeEntryPrefix(kind, name, key)}`
|
|
@@ -146,6 +146,40 @@ export function fetchNativeEntry(
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* installs a locally produced entry directory (descriptor beside its files) as
|
|
151
|
+
* the cache's entry for its key, after the same checks a download gets.
|
|
152
|
+
* a private distribution has no registry entries, so its host produces them.
|
|
153
|
+
*/
|
|
154
|
+
export function installNativeEntry(
|
|
155
|
+
kind: NativeEntryKind,
|
|
156
|
+
name: string,
|
|
157
|
+
key: string,
|
|
158
|
+
source: string,
|
|
159
|
+
): string {
|
|
160
|
+
const files = parseFiles(
|
|
161
|
+
fs.readFileSync(path.join(source, 'descriptor.json'), 'utf8'),
|
|
162
|
+
kind,
|
|
163
|
+
name,
|
|
164
|
+
key,
|
|
165
|
+
)
|
|
166
|
+
for (const file of files) {
|
|
167
|
+
const target = path.join(source, file.path)
|
|
168
|
+
if (fs.statSync(target).size !== file.size || sha256(target) !== file.sha256)
|
|
169
|
+
throw new Error(
|
|
170
|
+
`native prebuilt ${kind}/${name}/${key}: ${file.path} does not match its descriptor`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
const dir = entryCache(kind, name, key)
|
|
174
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true })
|
|
175
|
+
const staging = `${dir}.${process.pid}.partial`
|
|
176
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
177
|
+
fs.cpSync(source, staging, { recursive: true })
|
|
178
|
+
fs.rmSync(dir, { recursive: true, force: true })
|
|
179
|
+
fs.renameSync(staging, dir)
|
|
180
|
+
return dir
|
|
181
|
+
}
|
|
182
|
+
|
|
149
183
|
/** the descriptor for files a publisher is about to upload. */
|
|
150
184
|
export function describeNativeEntry(
|
|
151
185
|
kind: NativeEntryKind,
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
1
2
|
import { createHash } from 'node:crypto'
|
|
2
3
|
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
3
5
|
import path from 'node:path'
|
|
4
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
fetchNativeEntry,
|
|
8
|
+
installNativeEntry,
|
|
9
|
+
nativeEntryKey,
|
|
10
|
+
nativeEntryPrefix,
|
|
11
|
+
} from './native-prebuilt'
|
|
5
12
|
import {
|
|
6
13
|
swiftDistributionAt,
|
|
7
14
|
swiftInterfaceDeclarations,
|
|
8
15
|
withoutSwiftDeclarations,
|
|
9
16
|
} from './swift-compile'
|
|
17
|
+
import { privateSwiftDistributionRoot, sourceSwiftCheckout } from './swift-toolchain'
|
|
10
18
|
import { simulatorNativeLibrary } from './swift-xcframework'
|
|
11
19
|
import type { SwiftStandin } from './swift-standins'
|
|
12
20
|
|
|
@@ -44,6 +52,37 @@ export function nativeSwiftSliceEntry(
|
|
|
44
52
|
return { name: SENTRY_SWIFT_SLICE.name, inputs, key: nativeEntryKey(inputs) }
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
// the registry holds entries only for the pinned distribution; a private one
|
|
56
|
+
// (a canary or local candidate) has none, so its host produces the slice.
|
|
57
|
+
function privateSentrySlice(
|
|
58
|
+
entry: ReturnType<typeof nativeSwiftSliceEntry>,
|
|
59
|
+
moduleRoot: string,
|
|
60
|
+
): string | null {
|
|
61
|
+
const privateRoot = privateSwiftDistributionRoot()
|
|
62
|
+
if (!privateRoot || fs.realpathSync(privateRoot) !== fs.realpathSync(moduleRoot))
|
|
63
|
+
return null
|
|
64
|
+
const script = path.join(sourceSwiftCheckout(), 'scripts/build-sentry-wasi.ts')
|
|
65
|
+
if (!fs.existsSync(script))
|
|
66
|
+
throw new Error(
|
|
67
|
+
'the Sentry slice for a private Swift distribution builds from a Contrast source checkout',
|
|
68
|
+
)
|
|
69
|
+
const staging = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'sentry-slice-'))
|
|
70
|
+
try {
|
|
71
|
+
process.stderr.write('peach: building the Sentry slice for a private distribution…\n')
|
|
72
|
+
execFileSync('bun', [script, '--output', staging, '--module-root', moduleRoot], {
|
|
73
|
+
stdio: ['ignore', 2, 2],
|
|
74
|
+
})
|
|
75
|
+
return installNativeEntry(
|
|
76
|
+
'slice',
|
|
77
|
+
entry.name,
|
|
78
|
+
entry.key,
|
|
79
|
+
path.join(staging, nativeEntryPrefix('slice', entry.name, entry.key)),
|
|
80
|
+
)
|
|
81
|
+
} finally {
|
|
82
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
47
86
|
export function nativeSwiftSlice(
|
|
48
87
|
standin: SwiftStandin,
|
|
49
88
|
moduleRoot: string,
|
|
@@ -66,7 +105,9 @@ export function nativeSwiftSlice(
|
|
|
66
105
|
const distribution = swiftDistributionAt(moduleRoot)
|
|
67
106
|
if (!distribution) throw new Error('native Swift slices need a compiled distribution')
|
|
68
107
|
const entry = nativeSwiftSliceEntry(distribution, generator)
|
|
69
|
-
const directory =
|
|
108
|
+
const directory =
|
|
109
|
+
fetchNativeEntry('slice', entry.name, entry.key) ??
|
|
110
|
+
privateSentrySlice(entry, moduleRoot)
|
|
70
111
|
if (!directory)
|
|
71
112
|
throw new Error(
|
|
72
113
|
`native Swift slice ${entry.name}/${entry.key} is not built for this compiler and SDK`,
|
|
@@ -477,7 +477,13 @@ export function collectBridgeModuleSources(
|
|
|
477
477
|
if (swiftClass) {
|
|
478
478
|
const bridgingHeader = target.objc?.bridgingHeader
|
|
479
479
|
if (bridgingHeader && target.objc && app) {
|
|
480
|
-
nativeInputs ??= xcodeObjCInputs(
|
|
480
|
+
nativeInputs ??= xcodeObjCInputs(
|
|
481
|
+
app,
|
|
482
|
+
[],
|
|
483
|
+
null,
|
|
484
|
+
undefined,
|
|
485
|
+
installedWorkspace ? app.rootDir : projectRoot,
|
|
486
|
+
)
|
|
481
487
|
const native = target.objc
|
|
482
488
|
// an installed package a monorepo hoists above the project keeps its
|
|
483
489
|
// absolute path under one reserved prefix.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { cacheDir } from './home-paths'
|
|
5
|
+
import { describeNativeEntry, nativeEntryCache, nativeEntryKey } from './native-prebuilt'
|
|
6
|
+
import {
|
|
7
|
+
reactNativeCoreEntry,
|
|
8
|
+
reactNativeCoreFloor,
|
|
9
|
+
type ReactNativeCoreMode,
|
|
10
|
+
} from './react-native-core'
|
|
11
|
+
import { compileSwiftProjectUnit } from './swift-compile'
|
|
12
|
+
import { swiftModuleToolchain } from './swift-distribution'
|
|
13
|
+
import { isSwiftNativeTranslationUnit, swiftManifestTargets } from './swift-package'
|
|
14
|
+
import { withSwiftRootLock } from './swift-root-lock'
|
|
15
|
+
import { collectTurboCompileInput } from './turbo-compile'
|
|
16
|
+
import { collectTurboSources, prepareTurboSourceDependencies } from './turbo-source'
|
|
17
|
+
|
|
18
|
+
/** builds the React Native core for an installed app into the local
|
|
19
|
+
* native-prebuilt cache, the directory a published entry downloads into, and
|
|
20
|
+
* returns that directory. an entry already in the cache is returned as is. */
|
|
21
|
+
export async function buildReactNativeCore(
|
|
22
|
+
project: string,
|
|
23
|
+
reactNativeRoot: string,
|
|
24
|
+
mode: ReactNativeCoreMode,
|
|
25
|
+
moduleRoot: string,
|
|
26
|
+
): Promise<string> {
|
|
27
|
+
const entry = reactNativeCoreEntry(reactNativeRoot, moduleRoot, mode)
|
|
28
|
+
const directory = nativeEntryCache('react-native', entry.name, entry.key)
|
|
29
|
+
const work = path.join(cacheDir(), 'react-native-core-build', entry.key)
|
|
30
|
+
return withSwiftRootLock(work, async () => {
|
|
31
|
+
if (fs.existsSync(path.join(directory, 'descriptor.json'))) return directory
|
|
32
|
+
const module = { root: moduleRoot, ...swiftModuleToolchain(moduleRoot) }
|
|
33
|
+
const floor = reactNativeCoreFloor(reactNativeRoot)
|
|
34
|
+
await prepareTurboSourceDependencies(reactNativeRoot)
|
|
35
|
+
const codegenDir = path.join(work, 'codegen')
|
|
36
|
+
fs.mkdirSync(work, { recursive: true })
|
|
37
|
+
fs.rmSync(codegenDir, { recursive: true, force: true })
|
|
38
|
+
const collection = await collectTurboSources({
|
|
39
|
+
appRoot: project,
|
|
40
|
+
projectRoot: project,
|
|
41
|
+
reactNativeRoot,
|
|
42
|
+
providers: [],
|
|
43
|
+
codegenDir,
|
|
44
|
+
...(mode === 'turbo-cxx'
|
|
45
|
+
? { reactNativeCore: true }
|
|
46
|
+
: { reactNativeProvider: true }),
|
|
47
|
+
...(mode === 'turbo' ? { turboDispatch: true } : {}),
|
|
48
|
+
})
|
|
49
|
+
const producer = {
|
|
50
|
+
...collection,
|
|
51
|
+
files: { ...collection.files },
|
|
52
|
+
targets: [...collection.targets],
|
|
53
|
+
}
|
|
54
|
+
if (mode === 'turbo') {
|
|
55
|
+
// only the producer executable supplies this host callback; it is absent from the archives.
|
|
56
|
+
producer.targets.push({
|
|
57
|
+
name: 'PeachCoreProducerHost',
|
|
58
|
+
path: 'Sources/PeachCoreProducerHost',
|
|
59
|
+
executable: false,
|
|
60
|
+
languageMode: 5,
|
|
61
|
+
mainActorIsolation: false,
|
|
62
|
+
resources: false,
|
|
63
|
+
clangSettings: [{ tool: 'c', flags: ['-fblocks'] }],
|
|
64
|
+
})
|
|
65
|
+
producer.files['Sources/PeachCoreProducerHost/include/PeachCoreProducerHost.h'] =
|
|
66
|
+
'#pragma once\n'
|
|
67
|
+
producer.files['Sources/PeachCoreProducerHost/Dispatch.m'] =
|
|
68
|
+
'#import <dispatch/dispatch.h>\nvoid peachTurboDispatchAsync(dispatch_block_t block) { dispatch_async(dispatch_get_main_queue(), block); }\n'
|
|
69
|
+
}
|
|
70
|
+
const input = collectTurboCompileInput(producer, null, floor)
|
|
71
|
+
const { compiler: _compiler, recipe: _recipe, ...nativeInputs } = entry.inputs
|
|
72
|
+
const buildRoot = path.join(
|
|
73
|
+
cacheDir(),
|
|
74
|
+
'react-native-core-products',
|
|
75
|
+
nativeEntryKey(nativeInputs),
|
|
76
|
+
)
|
|
77
|
+
await compileSwiftProjectUnit(input, {
|
|
78
|
+
...module,
|
|
79
|
+
moduleRoot: module.root,
|
|
80
|
+
buildRoot,
|
|
81
|
+
intermediates: true,
|
|
82
|
+
})
|
|
83
|
+
const products = path.join(buildRoot, '.build/wasm32-unknown-wasip1/release')
|
|
84
|
+
const emitted = swiftManifestTargets(
|
|
85
|
+
fs.readFileSync(path.join(buildRoot, 'Package.swift'), 'utf8'),
|
|
86
|
+
)
|
|
87
|
+
const libraries: Record<string, string> = {}
|
|
88
|
+
const binaryModules: Record<string, string> = {}
|
|
89
|
+
for (const target of collection.targets) {
|
|
90
|
+
const objects = emitted
|
|
91
|
+
.filter((product) => !product.executable && product.path === target.path)
|
|
92
|
+
.flatMap((product) => {
|
|
93
|
+
const built = path.join(products, `${product.name}.build`)
|
|
94
|
+
return fs
|
|
95
|
+
.readdirSync(built, { recursive: true, encoding: 'utf8' })
|
|
96
|
+
.filter((file) => file.endsWith('.o'))
|
|
97
|
+
.map((file) => path.join(built, file))
|
|
98
|
+
})
|
|
99
|
+
.sort()
|
|
100
|
+
if (objects.length === 0)
|
|
101
|
+
throw new Error(`${target.name}: core build produced no objects`)
|
|
102
|
+
const members = path.join(work, 'members', target.name)
|
|
103
|
+
fs.rmSync(members, { recursive: true, force: true })
|
|
104
|
+
fs.mkdirSync(members, { recursive: true })
|
|
105
|
+
const stripped = objects.map((file, index) => {
|
|
106
|
+
const output = path.join(members, `${index}-${path.basename(file)}`)
|
|
107
|
+
execFileSync(path.join(module.toolchainBin, 'llvm-objcopy'), [
|
|
108
|
+
'--strip-debug',
|
|
109
|
+
file,
|
|
110
|
+
output,
|
|
111
|
+
])
|
|
112
|
+
return output
|
|
113
|
+
})
|
|
114
|
+
const archive = path.join(work, `lib${target.name}.a`)
|
|
115
|
+
fs.rmSync(archive, { force: true })
|
|
116
|
+
execFileSync(path.join(module.toolchainBin, 'llvm-ar'), [
|
|
117
|
+
'rcsD',
|
|
118
|
+
archive,
|
|
119
|
+
...stripped,
|
|
120
|
+
])
|
|
121
|
+
libraries[target.name] = fs.readFileSync(archive).toString('base64')
|
|
122
|
+
const swiftModule = path.join(products, 'Modules', `${target.name}.swiftmodule`)
|
|
123
|
+
if (fs.existsSync(swiftModule)) {
|
|
124
|
+
binaryModules[target.name] = fs.readFileSync(swiftModule).toString('base64')
|
|
125
|
+
collection.files[
|
|
126
|
+
`Sources/TurboCore/include/${target.name}/${target.name}-Swift.h`
|
|
127
|
+
] = fs.readFileSync(
|
|
128
|
+
path.join(buildRoot, '.build/peach-cxx-headers', `${target.name}-Swift.h`),
|
|
129
|
+
'utf8',
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
// keep textually included implementations available to the preprocessor.
|
|
133
|
+
target.exclude = [
|
|
134
|
+
...new Set([
|
|
135
|
+
...(target.exclude ?? []),
|
|
136
|
+
...Object.keys(collection.files)
|
|
137
|
+
.filter(
|
|
138
|
+
(file) =>
|
|
139
|
+
file.startsWith(`${target.path}/`) &&
|
|
140
|
+
(isSwiftNativeTranslationUnit(file) || file.endsWith('.swift')),
|
|
141
|
+
)
|
|
142
|
+
.map((file) => file.slice(target.path.length + 1)),
|
|
143
|
+
]),
|
|
144
|
+
]
|
|
145
|
+
collection.files[`${target.path}/PeachPrebuiltCore.c`] = '\n'
|
|
146
|
+
collection.files[`${target.path}/include/PeachPrebuiltCore.h`] = '#pragma once\n'
|
|
147
|
+
}
|
|
148
|
+
const prebuilt = collectTurboCompileInput(collection, null, floor)
|
|
149
|
+
const core = {
|
|
150
|
+
files: prebuilt.files,
|
|
151
|
+
entry: prebuilt.entry,
|
|
152
|
+
libraries,
|
|
153
|
+
binaryModules,
|
|
154
|
+
staticClangTargets: prebuilt.staticClangTargets ?? [],
|
|
155
|
+
...(prebuilt.headerAliases ? { headerAliases: prebuilt.headerAliases } : {}),
|
|
156
|
+
...(prebuilt.nativeTargets ? { nativeTargets: prebuilt.nativeTargets } : {}),
|
|
157
|
+
}
|
|
158
|
+
// the descriptor lands last, by rename, so a reader never sees a partial entry.
|
|
159
|
+
const staging = `${directory}.${process.pid}.building`
|
|
160
|
+
fs.rmSync(staging, { recursive: true, force: true })
|
|
161
|
+
fs.mkdirSync(staging, { recursive: true })
|
|
162
|
+
const coreFile = path.join(staging, 'core.json')
|
|
163
|
+
fs.writeFileSync(coreFile, JSON.stringify(core))
|
|
164
|
+
const descriptor = describeNativeEntry('react-native', entry.name, entry.inputs, [
|
|
165
|
+
coreFile,
|
|
166
|
+
])
|
|
167
|
+
fs.writeFileSync(path.join(staging, 'descriptor.json'), JSON.stringify(descriptor))
|
|
168
|
+
fs.mkdirSync(path.dirname(directory), { recursive: true })
|
|
169
|
+
fs.rmSync(directory, { recursive: true, force: true })
|
|
170
|
+
fs.renameSync(staging, directory)
|
|
171
|
+
console.error(
|
|
172
|
+
`[peach] React Native core ${entry.name}: built ${collection.targets.length} targets locally`,
|
|
173
|
+
)
|
|
174
|
+
return directory
|
|
175
|
+
})
|
|
176
|
+
}
|
|
@@ -10,7 +10,7 @@ export const REACT_NATIVE_CORE_SOURCES = [
|
|
|
10
10
|
'nitro-dependencies.ts',
|
|
11
11
|
'nitroAdapterContract.ts',
|
|
12
12
|
'podspec-sources.ts',
|
|
13
|
-
'
|
|
13
|
+
'react-native-core-build.ts',
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
export function reactNativeCoreDigest(directory: string): string {
|
package/src/react-native-core.ts
CHANGED
|
@@ -152,17 +152,34 @@ export function reactNativeCoreEntry(
|
|
|
152
152
|
return { name, inputs, key: nativeEntryKey(inputs) }
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
// the published core is a cache of this build: a cli whose compiler or a
|
|
156
|
+
// runtime whose distribution has no published entry yet builds it once into the
|
|
157
|
+
// same local cache instead of refusing the app.
|
|
158
|
+
const building = new Map<string, Promise<string>>()
|
|
159
|
+
|
|
160
|
+
export async function prebuiltReactNativeCore(
|
|
161
|
+
project: string,
|
|
156
162
|
reactNativeRoot: string,
|
|
157
163
|
moduleRoot: string,
|
|
158
164
|
mode: ReactNativeCoreMode,
|
|
159
|
-
): ReactNativeCoreInput {
|
|
165
|
+
): Promise<ReactNativeCoreInput> {
|
|
160
166
|
const entry = reactNativeCoreEntry(reactNativeRoot, moduleRoot, mode)
|
|
161
|
-
|
|
162
|
-
if (!dir)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
let dir = fetchNativeEntry('react-native', entry.name, entry.key)
|
|
168
|
+
if (!dir) {
|
|
169
|
+
let pending = building.get(entry.key)
|
|
170
|
+
if (!pending) {
|
|
171
|
+
console.error(
|
|
172
|
+
`[peach] no published React Native ${entry.name} core at ${NATIVE_PREBUILT_ORIGIN}/${nativeEntryPrefix('react-native', entry.name, entry.key)}; building it locally`,
|
|
173
|
+
)
|
|
174
|
+
pending = import('./react-native-core-build')
|
|
175
|
+
.then(({ buildReactNativeCore }) =>
|
|
176
|
+
buildReactNativeCore(project, reactNativeRoot, mode, moduleRoot),
|
|
177
|
+
)
|
|
178
|
+
.finally(() => building.delete(entry.key))
|
|
179
|
+
building.set(entry.key, pending)
|
|
180
|
+
}
|
|
181
|
+
dir = await pending
|
|
182
|
+
}
|
|
166
183
|
const file = path.join(dir, 'core.json')
|
|
167
184
|
const descriptor: unknown = JSON.parse(
|
|
168
185
|
fs.readFileSync(path.join(dir, 'descriptor.json'), 'utf8'),
|
package/src/swift-packages.ts
CHANGED
|
@@ -778,6 +778,9 @@ export function resolveSwiftPackages(options: {
|
|
|
778
778
|
)
|
|
779
779
|
)
|
|
780
780
|
return false
|
|
781
|
+
// an assembly target stands in from its public headers, so a stand-in
|
|
782
|
+
// peach built for it stays provided instead of compiling its sources
|
|
783
|
+
if (providedTargets.has(name) && assemblySources(files, target)) return false
|
|
781
784
|
claimed.add(key)
|
|
782
785
|
providedTargets.delete(name)
|
|
783
786
|
for (const dependency of target.dependencies) {
|
|
@@ -1296,8 +1296,16 @@ export function emitSwiftStandin(
|
|
|
1296
1296
|
[...superChain(owner.get(member.identifier.precise) ?? '')].some((at) =>
|
|
1297
1297
|
options.requiredInits?.has(`${pathOf(byId.get(at)!)}.init()`),
|
|
1298
1298
|
)
|
|
1299
|
+
// clang imports an init a class takes from a protocol requirement as
|
|
1300
|
+
// required, which a non-final class must declare to conform
|
|
1301
|
+
const protocolInit =
|
|
1302
|
+
member.kind.identifier === 'swift.init' &&
|
|
1303
|
+
/^c:objc\(pl\)\w+\(im\).*::SYNTHESIZED::c:objc\(cs\)/.test(
|
|
1304
|
+
member.identifier.precise,
|
|
1305
|
+
)
|
|
1299
1306
|
const required =
|
|
1300
|
-
context.inClass &&
|
|
1307
|
+
context.inClass &&
|
|
1308
|
+
(flags.has('required') || requiredInit || restatesRequired || protocolInit)
|
|
1301
1309
|
const final = context.inClass && flags.has('final') ? 'final ' : ''
|
|
1302
1310
|
const mutating = flags.has('mutating')
|
|
1303
1311
|
? 'mutating '
|
package/src/swift-standins.ts
CHANGED
|
@@ -659,9 +659,12 @@ function cxxOnly(surface: Surface): boolean {
|
|
|
659
659
|
if (surface.kind !== 'c' && surface.kind !== 'objc') return false
|
|
660
660
|
return files(surface.headers, (name) => name.endsWith('.h')).some((file) => {
|
|
661
661
|
const guards: boolean[] = []
|
|
662
|
-
|
|
662
|
+
// documentation prose such as "class that conforms to" is not a declaration
|
|
663
|
+
const code = fs
|
|
663
664
|
.readFileSync(path.join(surface.headers, file), 'utf8')
|
|
664
|
-
.
|
|
665
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
666
|
+
.replace(/\/\/.*$/gm, '')
|
|
667
|
+
for (const line of code.split('\n')) {
|
|
665
668
|
if (/^\s*#\s*if/.test(line))
|
|
666
669
|
guards.push(/__cplusplus/.test(line) && !/!\s*defined|ifndef/.test(line))
|
|
667
670
|
else if (/^\s*#\s*endif/.test(line)) guards.pop()
|