react-native 0.87.0-rc.0 → 0.87.0-rc.2
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/Libraries/Core/InitializeCore.js +9 -1
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/Libraries/ReactNative/AppRegistry.flow.js +1 -0
- package/Libraries/ReactNative/AppRegistryImpl.js +2 -3
- package/Libraries/ReactPrivate/ReactNativePrivateInterface.js +5 -116
- package/Libraries/ReactPrivate/ReactNativePrivateInterface.js.flow +5 -31
- package/React/Base/RCTVersion.m +1 -1
- package/React/I18n/RCTLocalizedString.mm +38 -2
- package/React-Core-prebuilt.podspec +45 -17
- package/React-Core.podspec +9 -2
- package/ReactAndroid/external-artifacts/build.gradle.kts +49 -0
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/SynchronousMountItem.kt +8 -2
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/index.js +15 -3
- package/index.js.flow +0 -2
- package/package.json +27 -29
- package/react-native.config.js +81 -0
- package/scripts/cocoapods/fabric.rb +1 -1
- package/scripts/cocoapods/rncore.rb +35 -78
- package/scripts/cocoapods/rncore_facades.rb +232 -0
- package/scripts/cocoapods/rndependencies.rb +64 -3
- package/scripts/cocoapods/rndeps_facades.rb +193 -0
- package/scripts/cocoapods/spm.rb +78 -11
- package/scripts/codegen/templates/Package.swift.spm-template +97 -0
- package/scripts/react-native-xcode.sh +20 -0
- package/scripts/react_native_pods.rb +65 -16
- package/scripts/replace-rncore-version.js +53 -6
- package/scripts/setup-apple-spm.js +1165 -0
- package/scripts/spm/__doc__/rfc-spm-xcframework.md +707 -0
- package/scripts/spm/__doc__/spm-autolinking-plugins.md +244 -0
- package/scripts/spm/__doc__/spm-header-paths-contract.md +97 -0
- package/scripts/spm/__doc__/spm-plugins-assessment.md +128 -0
- package/scripts/spm/__doc__/spm-scripts.md +451 -0
- package/scripts/spm/autolinking-plugins.js +331 -0
- package/scripts/spm/download-spm-artifacts.js +1409 -0
- package/scripts/spm/expand-spm-dependencies.js +216 -0
- package/scripts/spm/flavored-frameworks.js +1008 -0
- package/scripts/spm/generate-spm-autolinking-config.js +161 -0
- package/scripts/spm/generate-spm-autolinking.js +1888 -0
- package/scripts/spm/generate-spm-package.js +302 -0
- package/scripts/spm/generate-spm-xcodeproj.js +2224 -0
- package/scripts/spm/read-podspec.js +695 -0
- package/scripts/spm/scaffold-package-swift.js +1206 -0
- package/scripts/spm/spm-pbxproj.js +654 -0
- package/scripts/spm/spm-types.js +517 -0
- package/scripts/spm/spm-utils.js +645 -0
- package/scripts/spm/sync-spm-autolinking.js +160 -0
- package/sdks/.hermesv1version +1 -0
- package/sdks/hermes-engine/utils/replace_hermes_version.js +18 -4
- package/sdks/hermes-engine/version.properties +1 -1
- package/src/asset-registry.js +1 -1
- package/src/react-private-interface.js +145 -0
- package/src/react-private-interface.js.flow +48 -0
- package/src/setup-env.js +22 -0
- package/src/unstable-internals-do-not-use.d.ts +214 -0
- package/src/unstable-internals-do-not-use.js +76 -0
- package/third-party-podspecs/ReactNativeDependencies.podspec +2 -2
- package/types_generated/Libraries/ReactNative/AppRegistry.flow.d.ts +2 -2
- package/types_generated/Libraries/ReactPrivate/ReactNativePrivateInterface.d.ts +6 -21
- package/types_generated/index.d.ts +1 -2
- package/types_generated/src/private/renderer/events/dispatchNativeEvent.d.ts +26 -0
- package/types_generated/src/react-private-interface.d.ts +33 -0
- package/Libraries/Utilities/SceneTracker.js +0 -42
- package/jest-preset.js +0 -26
- package/rn-get-polyfills.js +0 -13
- package/types/tsconfig.json +0 -17
- package/types_generated/Libraries/Components/Touchable/Touchable.d.ts +0 -261
- package/types_generated/tsconfig.test.json +0 -17
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @flow strict-local
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
/*:: import type {PodspecModel, PreprocessorDefine} from './spm-types'; */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* read-podspec.js — produces a flattened, SPM-friendly view of an iOS
|
|
17
|
+
* podspec for the scaffolder.
|
|
18
|
+
*
|
|
19
|
+
* Strategy: prefer `pod ipc spec <path>` (CocoaPods evaluates the Ruby DSL,
|
|
20
|
+
* including `install_modules_dependencies(s)` and `$config[:...]`
|
|
21
|
+
* interpolation, so the JSON output reflects the real spec). Fall back to a
|
|
22
|
+
* best-effort regex parser when CocoaPods isn't available — handles simple
|
|
23
|
+
* RN libs but skips subspec blocks and Ruby helpers with a warning.
|
|
24
|
+
*
|
|
25
|
+
* The output `PodspecModel` collapses the spec's default subspecs into a
|
|
26
|
+
* single logical target view, since that's how RN consumers compile these
|
|
27
|
+
* libraries in practice.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const {spawnSync} = require('child_process');
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
/*::
|
|
35
|
+
type RawSpec = {[string]: unknown};
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Pod-IPC primary path
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runs `pod ipc spec <path>` and parses the JSON it prints. Returns null
|
|
44
|
+
* on any failure: CocoaPods missing, command threw, output not JSON, etc.
|
|
45
|
+
* Callers should treat null as a signal to fall back to the regex parser.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Strips RN-specific Ruby helpers from a podspec source so `pod ipc spec`
|
|
49
|
+
* can evaluate it without RN's Podfile-side helpers loaded. The stripped
|
|
50
|
+
* helpers (`install_modules_dependencies(s)`) typically inject the React-Core
|
|
51
|
+
* / React-Fabric family — all of which the scaffolder collapses into the
|
|
52
|
+
* single `ReactNative` product anyway, so dropping them is safe as long as
|
|
53
|
+
* the podspec also has an explicit `s.dependency "React-Core"` (almost all
|
|
54
|
+
* RN libs do). Writes the patched content to a temp file and returns its
|
|
55
|
+
* path; caller deletes when done.
|
|
56
|
+
*/
|
|
57
|
+
function patchPodspecForPodIpc(podspecPath /*: string */) /*: string */ {
|
|
58
|
+
const content = fs.readFileSync(podspecPath, 'utf8');
|
|
59
|
+
// Comment out RN-Podfile-only helpers. We do NOT remove the lines so
|
|
60
|
+
// line numbers in any pod ipc errors still match the original file.
|
|
61
|
+
const patched = content
|
|
62
|
+
.replace(
|
|
63
|
+
/^(\s*)install_modules_dependencies\(([^)]*)\)/gm,
|
|
64
|
+
'$1# install_modules_dependencies($2) # stripped for pod ipc',
|
|
65
|
+
)
|
|
66
|
+
.replace(
|
|
67
|
+
/^(\s*)use_react_native!\(([^)]*)\)/gm,
|
|
68
|
+
'$1# use_react_native!($2) # stripped for pod ipc',
|
|
69
|
+
);
|
|
70
|
+
// Write the patched copy NEXT TO the original podspec, not in os.tmpdir.
|
|
71
|
+
// Podspecs commonly do `File.read(File.join(__dir__, 'package.json'))` or
|
|
72
|
+
// similar, expecting the package.json sibling. Keeping the patched file
|
|
73
|
+
// in the same directory makes those reads continue to work.
|
|
74
|
+
const depDir = path.dirname(podspecPath);
|
|
75
|
+
// pod ipc only accepts files ending in `.podspec` or `.podspec.json`.
|
|
76
|
+
// Use a dotfile prefix so the patched copy is invisible to normal listing
|
|
77
|
+
// but the suffix CocoaPods requires is preserved.
|
|
78
|
+
const tmpFile = path.join(
|
|
79
|
+
depDir,
|
|
80
|
+
`.spm-scaffold-${process.pid}-${path.basename(podspecPath)}`,
|
|
81
|
+
);
|
|
82
|
+
fs.writeFileSync(tmpFile, patched, 'utf8');
|
|
83
|
+
return tmpFile;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function runPodIpcSpec(podspecPath /*: string */) /*: RawSpec | null */ {
|
|
87
|
+
// RN podspecs often call `install_modules_dependencies(s)`, a helper
|
|
88
|
+
// defined by RN's Podfile-side scripts. `pod ipc spec` doesn't load
|
|
89
|
+
// those, so the helper is undefined and the whole spec fails to parse.
|
|
90
|
+
// Pre-process to strip those calls before invoking pod ipc.
|
|
91
|
+
let patchedPath /*: ?string */ = null;
|
|
92
|
+
try {
|
|
93
|
+
patchedPath = patchPodspecForPodIpc(podspecPath);
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
let result;
|
|
98
|
+
try {
|
|
99
|
+
result = spawnSync('pod', ['ipc', 'spec', patchedPath], {
|
|
100
|
+
encoding: 'utf8',
|
|
101
|
+
timeout: 30000,
|
|
102
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
103
|
+
// Many RN podspecs gate subspec definitions on `RCT_NEW_ARCH_ENABLED`
|
|
104
|
+
// (e.g. safe-area-context wraps its Fabric `common` + `fabric`
|
|
105
|
+
// subspecs in `if fabric_enabled`). RN 0.76+ defaults to the new
|
|
106
|
+
// architecture, so unless the caller has explicitly opted out, set
|
|
107
|
+
// the env var here so `pod ipc spec` evaluates the full podspec.
|
|
108
|
+
env: {
|
|
109
|
+
...process.env,
|
|
110
|
+
RCT_NEW_ARCH_ENABLED: process.env.RCT_NEW_ARCH_ENABLED ?? '1',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
114
|
+
cleanupPatchedPodspec(patchedPath);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
cleanupPatchedPodspec(patchedPath);
|
|
118
|
+
if (result == null || result.error != null) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (typeof result.status !== 'number' || result.status !== 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (typeof result.stdout !== 'string' || result.stdout.length === 0) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// Some podspecs print diagnostics to stdout during evaluation (e.g. skia:
|
|
128
|
+
// `-- SK_GRAPHITE: OFF ...`) before `pod ipc` emits the JSON, and a
|
|
129
|
+
// diagnostic can itself contain braces (`Building {module}`). `pod ipc`
|
|
130
|
+
// pretty-prints the spec as a JSON object whose outermost braces sit at
|
|
131
|
+
// column 0, so ANCHOR on a line-boundary `{`…`}` rather than the first/last
|
|
132
|
+
// brace anywhere — otherwise a brace inside a diagnostic shifts the slice and
|
|
133
|
+
// JSON.parse throws. When the authoritative parse is abandoned we log (not
|
|
134
|
+
// silently drop to the much weaker, subspec-blind regex parser).
|
|
135
|
+
const stdout = result.stdout;
|
|
136
|
+
const startMatch = stdout.match(/^\{/m);
|
|
137
|
+
const start = startMatch != null ? startMatch.index : -1;
|
|
138
|
+
const lastLineBrace = stdout.lastIndexOf('\n}');
|
|
139
|
+
const end =
|
|
140
|
+
lastLineBrace >= 0 ? lastLineBrace + 2 : stdout.lastIndexOf('}') + 1;
|
|
141
|
+
if (start < 0 || end <= start) {
|
|
142
|
+
console.warn(
|
|
143
|
+
'[read-podspec] pod ipc spec produced no line-anchored JSON object; ' +
|
|
144
|
+
'falling back to the regex parser (subspecs/helpers may be missed).',
|
|
145
|
+
);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(stdout.slice(start, end));
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[read-podspec] pod ipc spec JSON parse failed (${e.message}); ` +
|
|
153
|
+
'falling back to the regex parser (subspecs/helpers may be missed).',
|
|
154
|
+
);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function cleanupPatchedPodspec(patchedPath /*: ?string */) /*: void */ {
|
|
160
|
+
if (patchedPath == null) return;
|
|
161
|
+
try {
|
|
162
|
+
fs.unlinkSync(patchedPath);
|
|
163
|
+
} catch {
|
|
164
|
+
// best-effort cleanup; the file is named with `.spm-scaffold-...tmp`
|
|
165
|
+
// so a leftover is identifiable.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Regex fallback
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Best-effort Ruby podspec parser. Extracts the literal-string and
|
|
175
|
+
* literal-array fields most RN libs use. Skips subspec blocks, Ruby helper
|
|
176
|
+
* calls (install_modules_dependencies, ENV[], $config[:...]), and
|
|
177
|
+
* interpolation — appends a warning so the caller can surface it to the
|
|
178
|
+
* user. Always returns a RawSpec; pure-JS, no Ruby dep required.
|
|
179
|
+
*/
|
|
180
|
+
function regexPodspec(podspecPath /*: string */) /*: RawSpec */ {
|
|
181
|
+
const content = fs.readFileSync(podspecPath, 'utf8');
|
|
182
|
+
const warnings /*: Array<string> */ = [];
|
|
183
|
+
|
|
184
|
+
// Matches: s.<field> = "value" or s.<field> = 'value'
|
|
185
|
+
function getStringField(name /*: string */) /*: string | null */ {
|
|
186
|
+
const re = new RegExp(`(?:s|spec)\\.${name}\\s*=\\s*["']([^"']+)["']`);
|
|
187
|
+
const m = content.match(re);
|
|
188
|
+
return m ? m[1] : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Matches:
|
|
192
|
+
// s.<field> = ["a", "b"] (array)
|
|
193
|
+
// s.<field> = "single" (single value treated as 1-element array)
|
|
194
|
+
function getArrayField(name /*: string */) /*: Array<string> */ {
|
|
195
|
+
const reArr = new RegExp(`(?:s|spec)\\.${name}\\s*=\\s*\\[([^\\]]*)\\]`);
|
|
196
|
+
const mArr = content.match(reArr);
|
|
197
|
+
if (mArr != null) {
|
|
198
|
+
const inner = mArr[1];
|
|
199
|
+
const out /*: Array<string> */ = [];
|
|
200
|
+
const itemRe = /["']([^"']+)["']/g;
|
|
201
|
+
while (true) {
|
|
202
|
+
const m = itemRe.exec(inner);
|
|
203
|
+
if (m == null) break;
|
|
204
|
+
out.push(m[1]);
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
const single = getStringField(name);
|
|
209
|
+
return single != null ? [single] : [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Matches: s.dependency "Name" (with optional version constraint)
|
|
213
|
+
function getDependencies() /*: Array<string> */ {
|
|
214
|
+
const out /*: Array<string> */ = [];
|
|
215
|
+
const re = /(?:s|spec)\.dependency\s+["']([^"']+)["']/g;
|
|
216
|
+
while (true) {
|
|
217
|
+
const m = re.exec(content);
|
|
218
|
+
if (m == null) break;
|
|
219
|
+
out.push(m[1]);
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Matches:
|
|
225
|
+
// s.framework "X" → ["X"]
|
|
226
|
+
// s.framework = "X" → ["X"]
|
|
227
|
+
// s.frameworks = ["X","Y"] → ["X","Y"]
|
|
228
|
+
function getFrameworks(weak /*: boolean */) /*: Array<string> */ {
|
|
229
|
+
const prefix = weak ? 'weak_framework' : 'framework';
|
|
230
|
+
const out /*: Array<string> */ = [];
|
|
231
|
+
// Plural form: s.frameworks = [...] or s.frameworks = "Foo"
|
|
232
|
+
out.push(...getArrayField(`${prefix}s`));
|
|
233
|
+
// Singular form: s.framework = "Foo" — single value treated as a 1-elem list
|
|
234
|
+
out.push(...getArrayField(prefix));
|
|
235
|
+
// Method-call form: s.framework "Foo", "Bar" — no `=`. `getArrayField`
|
|
236
|
+
// only matches assignment, so handle this case separately.
|
|
237
|
+
const callRe = new RegExp(
|
|
238
|
+
`(?:s|spec)\\.${prefix}s?\\s+((?:["'][^"']+["']\\s*,?\\s*)+)`,
|
|
239
|
+
'g',
|
|
240
|
+
);
|
|
241
|
+
while (true) {
|
|
242
|
+
const m = callRe.exec(content);
|
|
243
|
+
if (m == null) break;
|
|
244
|
+
const itemRe = /["']([^"']+)["']/g;
|
|
245
|
+
while (true) {
|
|
246
|
+
const im = itemRe.exec(m[1]);
|
|
247
|
+
if (im == null) break;
|
|
248
|
+
out.push(im[1]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return Array.from(new Set(out));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Matches: 'HEADER_SEARCH_PATHS' => '...' or => ["...","..."]
|
|
255
|
+
// Returns the raw token strings (still containing $(PODS_TARGET_SRCROOT)
|
|
256
|
+
// etc.); substitution happens at translation time.
|
|
257
|
+
function getHeaderSearchPaths() /*: Array<string> */ {
|
|
258
|
+
const out /*: Array<string> */ = [];
|
|
259
|
+
// Array form
|
|
260
|
+
const arrRe = /["']HEADER_SEARCH_PATHS["']\s*=>\s*\[([\s\S]*?)\]/g;
|
|
261
|
+
while (true) {
|
|
262
|
+
const m = arrRe.exec(content);
|
|
263
|
+
if (m == null) break;
|
|
264
|
+
const itemRe = /["']((?:[^"'\\]|\\.)+)["']/g;
|
|
265
|
+
while (true) {
|
|
266
|
+
const im = itemRe.exec(m[1]);
|
|
267
|
+
if (im == null) break;
|
|
268
|
+
// Each entry can be a single path or a space-separated list of paths.
|
|
269
|
+
for (const token of im[1].split(/\s+/)) {
|
|
270
|
+
if (token.length > 0) {
|
|
271
|
+
out.push(stripWrappingQuotes(token));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// String form
|
|
277
|
+
const strRe =
|
|
278
|
+
/["']HEADER_SEARCH_PATHS["']\s*=>\s*["']((?:[^"'\\]|\\.)+)["']/g;
|
|
279
|
+
while (true) {
|
|
280
|
+
const m = strRe.exec(content);
|
|
281
|
+
if (m == null) break;
|
|
282
|
+
for (const token of m[1].split(/\s+/)) {
|
|
283
|
+
if (token.length > 0) {
|
|
284
|
+
out.push(stripWrappingQuotes(token));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return Array.from(new Set(out));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Surface known unparseable constructs so the caller can warn the user.
|
|
292
|
+
if (/(?:s|spec)\.subspec\s+["']/.test(content)) {
|
|
293
|
+
warnings.push(
|
|
294
|
+
'Subspecs detected — regex parser only extracts top-level fields. Install CocoaPods (`gem install cocoapods`) to enable full `pod ipc spec` parsing.',
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (/install_modules_dependencies/.test(content)) {
|
|
298
|
+
warnings.push(
|
|
299
|
+
'`install_modules_dependencies(s)` detected — dependency wiring may be incomplete without CocoaPods.',
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (/\bENV\[/.test(content) || /\$config\[/.test(content)) {
|
|
303
|
+
warnings.push(
|
|
304
|
+
'Env-var or Ruby-config interpolation detected — values may not translate cleanly without CocoaPods.',
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
name: getStringField('name'),
|
|
310
|
+
version: getStringField('version'),
|
|
311
|
+
source_files: getArrayField('source_files'),
|
|
312
|
+
public_header_files: getArrayField('public_header_files'),
|
|
313
|
+
private_header_files: getArrayField('private_header_files'),
|
|
314
|
+
exclude_files: getArrayField('exclude_files'),
|
|
315
|
+
header_mappings_dir: getStringField('header_mappings_dir'),
|
|
316
|
+
header_dir: getStringField('header_dir'),
|
|
317
|
+
frameworks: getFrameworks(false),
|
|
318
|
+
weak_frameworks: getFrameworks(true),
|
|
319
|
+
libraries: getArrayField('libraries'),
|
|
320
|
+
dependencies: getDependencies(),
|
|
321
|
+
compiler_flags: tokenizeFlags(getStringField('compiler_flags')),
|
|
322
|
+
pod_target_xcconfig: {HEADER_SEARCH_PATHS: getHeaderSearchPaths()},
|
|
323
|
+
resources: getArrayField('resources'),
|
|
324
|
+
requires_arc: /(?:s|spec)\.requires_arc\s*=\s*true/.test(content),
|
|
325
|
+
__regex_partial__: true,
|
|
326
|
+
__warnings__: warnings,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Strips outer quote-like characters from a captured token. Handles bare
|
|
331
|
+
// quotes (`"foo"`, `'foo'`) AND Ruby-escaped quotes (`\"foo\"`) which show
|
|
332
|
+
// up verbatim when the regex captures something like
|
|
333
|
+
// `"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/x\""`.
|
|
334
|
+
function stripWrappingQuotes(s /*: string */) /*: string */ {
|
|
335
|
+
return s.replace(/^\\?["']/, '').replace(/\\?["']$/, '');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function tokenizeFlags(value /*: string | null */) /*: Array<string> */ {
|
|
339
|
+
if (value == null) return [];
|
|
340
|
+
return value.split(/\s+/).filter(Boolean);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Split a flag string on whitespace while keeping quoted spans intact, so a
|
|
345
|
+
* define like `-DWORKLETS_FEATURE_FLAGS="[A:false][B:true]"` stays one token
|
|
346
|
+
* (the quotes are part of the macro value). A naive whitespace split would
|
|
347
|
+
* shred any define whose value contains spaces.
|
|
348
|
+
*/
|
|
349
|
+
function shellTokenize(value /*: string */) /*: Array<string> */ {
|
|
350
|
+
const tokens /*: Array<string> */ = [];
|
|
351
|
+
let cur = '';
|
|
352
|
+
let quote /*: string | null */ = null;
|
|
353
|
+
let has = false;
|
|
354
|
+
for (let i = 0; i < value.length; i++) {
|
|
355
|
+
const c = value[i];
|
|
356
|
+
if (quote != null) {
|
|
357
|
+
cur += c;
|
|
358
|
+
if (c === quote) quote = null;
|
|
359
|
+
} else if (c === '"' || c === "'") {
|
|
360
|
+
cur += c;
|
|
361
|
+
quote = c;
|
|
362
|
+
has = true;
|
|
363
|
+
} else if (/\s/.test(c)) {
|
|
364
|
+
if (has) tokens.push(cur);
|
|
365
|
+
cur = '';
|
|
366
|
+
has = false;
|
|
367
|
+
} else {
|
|
368
|
+
cur += c;
|
|
369
|
+
has = true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (has) tokens.push(cur);
|
|
373
|
+
return tokens;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// Subspec flattening
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Merges a podspec's default subspecs (or all subspecs when no defaults are
|
|
382
|
+
* declared) into a single logical view. RN libraries use subspecs for
|
|
383
|
+
* platform/feature gating (`apple` vs `common`); consumers compile the
|
|
384
|
+
* union anyway, so the flat view matches the actual build.
|
|
385
|
+
*
|
|
386
|
+
* Merge rules:
|
|
387
|
+
* - Array fields → concat + dedup
|
|
388
|
+
* - String fields → top-level wins; if absent, take the first subspec's value
|
|
389
|
+
* - pod_target_xcconfig HEADER_SEARCH_PATHS → array-union across all selected subspecs
|
|
390
|
+
* - dependencies → array-union of name strings (version constraints dropped)
|
|
391
|
+
*/
|
|
392
|
+
function flattenSubspecs(rawSpec /*: RawSpec */) /*: PodspecModel */ {
|
|
393
|
+
const warnings /*: Array<string> */ = [];
|
|
394
|
+
// $FlowFixMe[prop-missing] dynamic shape
|
|
395
|
+
const partial /*: boolean */ = rawSpec.__regex_partial__ === true;
|
|
396
|
+
if (Array.isArray(rawSpec.__warnings__)) {
|
|
397
|
+
// $FlowFixMe[incompatible-type] runtime-validated dynamic shape
|
|
398
|
+
const rawWarnings /*: ReadonlyArray<string> */ = rawSpec.__warnings__;
|
|
399
|
+
for (const w of rawWarnings) {
|
|
400
|
+
warnings.push(w);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Determine which subspecs to merge. pod ipc returns:
|
|
405
|
+
// - `subspecs`: array of nested specs (or undefined)
|
|
406
|
+
// - `default_subspecs`: array of names (or undefined → use all subspecs)
|
|
407
|
+
const subspecs = Array.isArray(rawSpec.subspecs) ? rawSpec.subspecs : [];
|
|
408
|
+
let selected = subspecs;
|
|
409
|
+
if (Array.isArray(rawSpec.default_subspecs)) {
|
|
410
|
+
const wanted = new Set(rawSpec.default_subspecs);
|
|
411
|
+
selected = subspecs.filter(s => {
|
|
412
|
+
// $FlowFixMe[prop-missing] dynamic shape
|
|
413
|
+
const name = s != null && typeof s.name === 'string' ? s.name : null;
|
|
414
|
+
return name != null && wanted.has(name);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const layers = [rawSpec, ...selected];
|
|
418
|
+
|
|
419
|
+
function mergeArrayField(key /*: string */) /*: Array<string> */ {
|
|
420
|
+
const out /*: Array<string> */ = [];
|
|
421
|
+
for (const layer of layers) {
|
|
422
|
+
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`; runtime-validated below
|
|
423
|
+
const value = layer != null ? layer[key] : null;
|
|
424
|
+
if (Array.isArray(value)) {
|
|
425
|
+
for (const v of value) {
|
|
426
|
+
if (typeof v === 'string') out.push(v);
|
|
427
|
+
}
|
|
428
|
+
} else if (typeof value === 'string') {
|
|
429
|
+
out.push(value);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return Array.from(new Set(out));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function mergeStringField(key /*: string */) /*: string | null */ {
|
|
436
|
+
for (const layer of layers) {
|
|
437
|
+
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`; runtime-validated below
|
|
438
|
+
const value = layer != null ? layer[key] : null;
|
|
439
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function mergeHeaderSearchPaths() /*: Array<string> */ {
|
|
447
|
+
const out /*: Array<string> */ = [];
|
|
448
|
+
// HSP can live in any of the xcconfig blocks, and a single value often
|
|
449
|
+
// PACKS multiple space-separated paths, each individually quoted, plus a
|
|
450
|
+
// CocoaPods `/**` recursive-glob suffix (e.g. skia:
|
|
451
|
+
// `"$(SRCROOT)/cpp/"/** "$(SRCROOT)/cpp/skia" ...`). Shell-tokenize to keep
|
|
452
|
+
// each path intact, then strip ALL quotes (not just wrapping) per token.
|
|
453
|
+
const XCCONFIG_KEYS = [
|
|
454
|
+
'pod_target_xcconfig',
|
|
455
|
+
'xcconfig',
|
|
456
|
+
'user_target_xcconfig',
|
|
457
|
+
];
|
|
458
|
+
for (const layer of layers) {
|
|
459
|
+
if (layer == null || typeof layer !== 'object') continue;
|
|
460
|
+
for (const xcKey of XCCONFIG_KEYS) {
|
|
461
|
+
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`
|
|
462
|
+
const xc = layer[xcKey];
|
|
463
|
+
if (xc == null || typeof xc !== 'object') continue;
|
|
464
|
+
const hsp = xc.HEADER_SEARCH_PATHS;
|
|
465
|
+
const values =
|
|
466
|
+
typeof hsp === 'string'
|
|
467
|
+
? [hsp]
|
|
468
|
+
: Array.isArray(hsp)
|
|
469
|
+
? hsp.filter(v => typeof v === 'string')
|
|
470
|
+
: [];
|
|
471
|
+
for (const v of values) {
|
|
472
|
+
for (const tok of shellTokenize(v)) {
|
|
473
|
+
const cleaned = tok.replace(/['"]/g, '');
|
|
474
|
+
if (cleaned.length > 0) out.push(cleaned);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return Array.from(new Set(out));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Lift preprocessor defines from pod_target_xcconfig across all layers:
|
|
483
|
+
// `-D` tokens in OTHER_CFLAGS, and NAME[=VALUE] entries in
|
|
484
|
+
// GCC_PREPROCESSOR_DEFINITIONS (incl. per-config `[config=*Debug*]` keys).
|
|
485
|
+
// Non-define compiler flags in OTHER_CFLAGS are intentionally dropped — only
|
|
486
|
+
// `-D`s are safe to forward; arbitrary flags may be machine- or
|
|
487
|
+
// example-app-specific. $(inherited), unresolved $(...) tokens, and invalid
|
|
488
|
+
// C identifiers are skipped.
|
|
489
|
+
function mergePreprocessorDefines() /*: Array<PreprocessorDefine> */ {
|
|
490
|
+
const out /*: Array<PreprocessorDefine> */ = [];
|
|
491
|
+
const seen /*: Set<string> */ = new Set();
|
|
492
|
+
const validName = /^[A-Za-z_]\w*$/;
|
|
493
|
+
const add = (
|
|
494
|
+
name /*: string */,
|
|
495
|
+
value /*: ?string */,
|
|
496
|
+
config /*: ?('debug' | 'release') */,
|
|
497
|
+
) => {
|
|
498
|
+
if (!validName.test(name)) return;
|
|
499
|
+
if (/\$[({]/.test(name) || (value != null && /\$[({]/.test(value))) {
|
|
500
|
+
return; // unresolved Xcode/Ruby token — don't emit a broken define
|
|
501
|
+
}
|
|
502
|
+
const key = `${name}|${config ?? ''}`;
|
|
503
|
+
if (seen.has(key)) return;
|
|
504
|
+
seen.add(key);
|
|
505
|
+
out.push({name, value, config});
|
|
506
|
+
};
|
|
507
|
+
// Defines can live in the target xcconfig (pod_target_xcconfig) OR the
|
|
508
|
+
// aggregate/user xcconfig (`s.xcconfig` / user_target_xcconfig). worklets
|
|
509
|
+
// puts its version define in pod_target_xcconfig; reanimated puts its in
|
|
510
|
+
// `s.xcconfig` — scan all three.
|
|
511
|
+
const XCCONFIG_KEYS = [
|
|
512
|
+
'pod_target_xcconfig',
|
|
513
|
+
'xcconfig',
|
|
514
|
+
'user_target_xcconfig',
|
|
515
|
+
];
|
|
516
|
+
for (const layer of layers) {
|
|
517
|
+
if (layer == null || typeof layer !== 'object') continue;
|
|
518
|
+
for (const xcKey of XCCONFIG_KEYS) {
|
|
519
|
+
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`
|
|
520
|
+
const xc = layer[xcKey];
|
|
521
|
+
if (xc == null || typeof xc !== 'object') continue;
|
|
522
|
+
for (const rawKey of Object.keys(xc)) {
|
|
523
|
+
const cflags = /^OTHER_CFLAGS(?:\[config=\*(\w+)\*\])?$/i.exec(
|
|
524
|
+
rawKey,
|
|
525
|
+
);
|
|
526
|
+
const ppDefs =
|
|
527
|
+
/^GCC_PREPROCESSOR_DEFINITIONS(?:\[config=\*(\w+)\*\])?$/i.exec(
|
|
528
|
+
rawKey,
|
|
529
|
+
);
|
|
530
|
+
if (cflags == null && ppDefs == null) continue;
|
|
531
|
+
const cfgRaw = (
|
|
532
|
+
(cflags?.[1] ?? ppDefs?.[1] ?? '') + ''
|
|
533
|
+
).toLowerCase();
|
|
534
|
+
const config =
|
|
535
|
+
cfgRaw === 'debug'
|
|
536
|
+
? 'debug'
|
|
537
|
+
: cfgRaw === 'release'
|
|
538
|
+
? 'release'
|
|
539
|
+
: null;
|
|
540
|
+
// $FlowFixMe[incompatible-use] xc value access is intentional
|
|
541
|
+
const val = xc[rawKey];
|
|
542
|
+
const strs =
|
|
543
|
+
typeof val === 'string'
|
|
544
|
+
? [val]
|
|
545
|
+
: Array.isArray(val)
|
|
546
|
+
? val.filter(v => typeof v === 'string')
|
|
547
|
+
: [];
|
|
548
|
+
for (const s of strs) {
|
|
549
|
+
for (const tok of shellTokenize(s)) {
|
|
550
|
+
if (tok === '$(inherited)') continue;
|
|
551
|
+
// OTHER_CFLAGS: only `-D` tokens are defines; others are flags.
|
|
552
|
+
// GCC_PREPROCESSOR_DEFINITIONS: every token is `NAME[=VALUE]`.
|
|
553
|
+
let body /*: ?string */ = null;
|
|
554
|
+
if (cflags != null) {
|
|
555
|
+
if (tok.startsWith('-D')) body = tok.slice(2);
|
|
556
|
+
} else {
|
|
557
|
+
body = tok;
|
|
558
|
+
}
|
|
559
|
+
if (body == null || body.length === 0) continue;
|
|
560
|
+
const eq = body.indexOf('=');
|
|
561
|
+
const name = eq >= 0 ? body.slice(0, eq) : body;
|
|
562
|
+
const value = eq >= 0 ? body.slice(eq + 1) : null;
|
|
563
|
+
add(name, value, config);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return out;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function mergeDependencies() /*: Array<string> */ {
|
|
573
|
+
const out /*: Array<string> */ = [];
|
|
574
|
+
for (const layer of layers) {
|
|
575
|
+
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`; runtime-validated below
|
|
576
|
+
const deps = layer != null ? layer.dependencies : null;
|
|
577
|
+
if (deps == null) continue;
|
|
578
|
+
// pod ipc returns deps as: {name: [versionConstraint, ...], ...}
|
|
579
|
+
// regex returns deps as: [name, name, ...]
|
|
580
|
+
if (Array.isArray(deps)) {
|
|
581
|
+
for (const d of deps) {
|
|
582
|
+
if (typeof d === 'string') out.push(d);
|
|
583
|
+
}
|
|
584
|
+
} else if (typeof deps === 'object') {
|
|
585
|
+
for (const name of Object.keys(deps)) {
|
|
586
|
+
out.push(name);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return Array.from(new Set(out));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function mergeCompilerFlags() /*: Array<string> */ {
|
|
594
|
+
const out /*: Array<string> */ = [];
|
|
595
|
+
for (const layer of layers) {
|
|
596
|
+
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`; runtime-validated below
|
|
597
|
+
const value = layer != null ? layer.compiler_flags : null;
|
|
598
|
+
if (typeof value === 'string') {
|
|
599
|
+
for (const tok of value.split(/\s+/)) {
|
|
600
|
+
if (tok.length > 0) out.push(tok);
|
|
601
|
+
}
|
|
602
|
+
} else if (Array.isArray(value)) {
|
|
603
|
+
for (const v of value) {
|
|
604
|
+
if (typeof v === 'string') {
|
|
605
|
+
for (const tok of v.split(/\s+/)) {
|
|
606
|
+
if (tok.length > 0) out.push(tok);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return out;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// $FlowFixMe[prop-missing] dynamic shape
|
|
616
|
+
const reqArc /*: unknown */ = rawSpec.requires_arc;
|
|
617
|
+
const requiresArc =
|
|
618
|
+
reqArc === false || (Array.isArray(reqArc) && reqArc.length === 0)
|
|
619
|
+
? false
|
|
620
|
+
: true; // RN ecosystem default
|
|
621
|
+
|
|
622
|
+
return {
|
|
623
|
+
name: mergeStringField('name') ?? '',
|
|
624
|
+
version: mergeStringField('version') ?? '',
|
|
625
|
+
sourceFiles: mergeArrayField('source_files'),
|
|
626
|
+
publicHeaderFiles: mergeArrayField('public_header_files'),
|
|
627
|
+
privateHeaderFiles: mergeArrayField('private_header_files'),
|
|
628
|
+
excludeFiles: mergeArrayField('exclude_files'),
|
|
629
|
+
headerMappingsDir: mergeStringField('header_mappings_dir'),
|
|
630
|
+
// ALL subspecs' header_mappings_dir values (not just the merged one). Each
|
|
631
|
+
// implies a header search path of its parent dir so namespaced includes
|
|
632
|
+
// (`<reanimated/apple/sensor/X.h>` for a header physically at
|
|
633
|
+
// `apple/reanimated/apple/sensor/X.h` with mappings dir `apple/reanimated`)
|
|
634
|
+
// resolve from the physical tree — CocoaPods does this via the
|
|
635
|
+
// header_mappings_dir copy step, which SPM has no equivalent for.
|
|
636
|
+
headerMappingsDirs: mergeArrayField('header_mappings_dir'),
|
|
637
|
+
headerDir: mergeStringField('header_dir'),
|
|
638
|
+
frameworks: mergeArrayField('frameworks'),
|
|
639
|
+
weakFrameworks: mergeArrayField('weak_frameworks'),
|
|
640
|
+
libraries: mergeArrayField('libraries'),
|
|
641
|
+
dependencies: mergeDependencies(),
|
|
642
|
+
preprocessorDefines: mergePreprocessorDefines(),
|
|
643
|
+
compilerFlags: mergeCompilerFlags(),
|
|
644
|
+
headerSearchPaths: mergeHeaderSearchPaths(),
|
|
645
|
+
resources: mergeArrayField('resources'),
|
|
646
|
+
requiresArc,
|
|
647
|
+
warnings,
|
|
648
|
+
partial,
|
|
649
|
+
// Default: overridden in readPodspec, which can see the podspec source.
|
|
650
|
+
usesInstallModulesDependencies: false,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ---------------------------------------------------------------------------
|
|
655
|
+
// Top-level entry
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Reads and flattens a podspec at `podspecPath`. Tries `pod ipc spec` first
|
|
660
|
+
* (full Ruby DSL evaluation); falls back to a regex parser when CocoaPods
|
|
661
|
+
* isn't available.
|
|
662
|
+
*
|
|
663
|
+
* Throws when the file doesn't exist. Otherwise always returns a
|
|
664
|
+
* PodspecModel — warnings on the model surface partial parses to the
|
|
665
|
+
* caller, who can decide whether to proceed or abort scaffolding for that
|
|
666
|
+
* dep.
|
|
667
|
+
*/
|
|
668
|
+
function readPodspec(podspecPath /*: string */) /*: PodspecModel */ {
|
|
669
|
+
if (!fs.existsSync(podspecPath)) {
|
|
670
|
+
throw new Error(`readPodspec: file does not exist: ${podspecPath}`);
|
|
671
|
+
}
|
|
672
|
+
const podIpc = runPodIpcSpec(podspecPath);
|
|
673
|
+
const raw = podIpc != null ? podIpc : regexPodspec(podspecPath);
|
|
674
|
+
const model = flattenSubspecs(raw);
|
|
675
|
+
// Record whether the podspec calls `install_modules_dependencies(s)`. We
|
|
676
|
+
// strip that helper before `pod ipc` and the regex parser can't expand it,
|
|
677
|
+
// so the React-Core family it injects never lands in `model.dependencies`.
|
|
678
|
+
// The scaffolder uses this as an implicit React-core marker (plain ObjC
|
|
679
|
+
// modules using the helper have no `codegenConfig` to detect instead).
|
|
680
|
+
try {
|
|
681
|
+
const src = fs.readFileSync(podspecPath, 'utf8');
|
|
682
|
+
model.usesInstallModulesDependencies =
|
|
683
|
+
/\binstall_modules_dependencies\b/.test(src);
|
|
684
|
+
} catch {
|
|
685
|
+
// best-effort; leave the flattenSubspecs default (false)
|
|
686
|
+
}
|
|
687
|
+
return model;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
module.exports = {
|
|
691
|
+
readPodspec,
|
|
692
|
+
runPodIpcSpec,
|
|
693
|
+
regexPodspec,
|
|
694
|
+
flattenSubspecs,
|
|
695
|
+
};
|