react-native 0.88.0-rc.0 → 0.88.0-rc.1
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/ReactNativeVersion.js +1 -1
- package/Libraries/Renderer/implementations/ReactFabric-dev.js +3056 -1531
- package/Libraries/Renderer/implementations/ReactFabric-prod.js +2239 -1011
- package/Libraries/Renderer/implementations/ReactFabric-profiling.js +2451 -1124
- package/React/Base/RCTVersion.m +1 -1
- package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +8 -0
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +7 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +11 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +12 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Canary_Android.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +15 -1
- package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +4 -1
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +5 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +6 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +74 -56
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +4 -2
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +5 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +10 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h +5 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +2 -1
- package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +6 -1
- package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +3 -1
- package/package.json +10 -10
- package/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js +18 -4
- package/scripts/setup-apple-spm.js +82 -4
- package/scripts/spm/autolinking-plugins.js +8 -5
- package/scripts/spm/expand-spm-dependencies.js +217 -156
- package/scripts/spm/generate-spm-autolinking.js +114 -88
- package/scripts/spm/generate-spm-xcodeproj.js +67 -22
- package/scripts/spm/ios-deployment-target.js +378 -0
- package/scripts/spm/read-podspec.js +156 -8
- package/scripts/spm/scaffold-package-swift.js +272 -105
- package/scripts/spm/spm-pbxproj.js +36 -21
- package/scripts/spm/spm-types.js +25 -8
- package/scripts/spm/spm-utils.js +26 -0
- package/scripts/spm/swiftpm-config.js +245 -0
- package/scripts/spm/sync-spm-autolinking.js +11 -2
- package/sdks/hermes-engine/version.properties +1 -1
- package/src/private/featureflags/ReactNativeFeatureFlags.js +10 -5
- package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +2 -1
- package/src/private/webapis/dom/nodes/ReactNativeElement.js +6 -4
- package/src/private/webapis/dom/nodes/ReadOnlyText.js +6 -4
- package/src/react-private-interface.js +4 -0
- package/src/react-private-interface.js.flow +2 -0
- package/types_generated/Libraries/ReactNative/FabricUIManager.d.ts +47 -0
- package/types_generated/src/react-private-interface.d.ts +4 -1
|
@@ -0,0 +1,378 @@
|
|
|
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
|
+
/**
|
|
14
|
+
* The iOS platform floor of the SwiftPM manifests React Native generates.
|
|
15
|
+
* SwiftPM refuses to link a product whose minimum is above the depending
|
|
16
|
+
* target's, so the floor tracks the app's own deployment target rather than a
|
|
17
|
+
* hardcoded value a dependency (every Expo package: iOS 16.4) can exceed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const {targetBuildConfigUuids} = require('./generate-spm-xcodeproj');
|
|
21
|
+
const {
|
|
22
|
+
findApplicationTargets,
|
|
23
|
+
findField,
|
|
24
|
+
findObjectByUuid,
|
|
25
|
+
findProjectObject,
|
|
26
|
+
forEachObjectInSection,
|
|
27
|
+
uuidsInArray,
|
|
28
|
+
} = require('./spm-pbxproj');
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
|
|
32
|
+
// Keep in sync with `min_ios_version_supported` in scripts/cocoapods/helpers.rb.
|
|
33
|
+
const MIN_IOS_VERSION_SUPPORTED /*: string */ = '15.1';
|
|
34
|
+
|
|
35
|
+
const IOS_VERSION_RE = /^\d+(\.\d+){0,2}$/;
|
|
36
|
+
|
|
37
|
+
const DEPLOYMENT_TARGET_KEY = 'IPHONEOS_DEPLOYMENT_TARGET';
|
|
38
|
+
|
|
39
|
+
// An xcconfig may `#include` another; cap the chain instead of trusting it.
|
|
40
|
+
const MAX_XCCONFIG_DEPTH = 16;
|
|
41
|
+
|
|
42
|
+
/*::
|
|
43
|
+
// Everything the reader needs to follow a configuration's xcconfig. Without a
|
|
44
|
+
// srcRoot the xcconfig step is skipped (a caller with only pbxproj text).
|
|
45
|
+
type XcconfigContext = {srcRoot: ?string, readFile: (absPath: string) => ?string};
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
function normalizeIosVersion(version /*: string */) /*: string */ {
|
|
49
|
+
return version.includes('.') ? version : `${version}.0`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function segment(
|
|
53
|
+
parts /*: Array<string> */,
|
|
54
|
+
index /*: number */,
|
|
55
|
+
) /*: number */ {
|
|
56
|
+
return index < parts.length ? Number(parts[index]) : 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Numeric, not lexicographic: `9.0` < `10.0` and `16.4` < `16.10`. */
|
|
60
|
+
function compareIosVersions(a /*: string */, b /*: string */) /*: number */ {
|
|
61
|
+
const left = a.split('.');
|
|
62
|
+
const right = b.split('.');
|
|
63
|
+
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
64
|
+
const delta = segment(left, i) - segment(right, i);
|
|
65
|
+
if (delta !== 0) {
|
|
66
|
+
return delta < 0 ? -1 : 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function unquote(value /*: string */) /*: string */ {
|
|
73
|
+
return value.replace(/^"|"$/g, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A version reduced to something safe to interpolate into a manifest:
|
|
78
|
+
* normalized, and never below React Native's own minimum.
|
|
79
|
+
*/
|
|
80
|
+
function sanitizeIosDeploymentTarget(raw /*: ?string */) /*: string */ {
|
|
81
|
+
if (raw == null || !IOS_VERSION_RE.test(raw)) {
|
|
82
|
+
return MIN_IOS_VERSION_SUPPORTED;
|
|
83
|
+
}
|
|
84
|
+
const version = normalizeIosVersion(raw);
|
|
85
|
+
return compareIosVersions(version, MIN_IOS_VERSION_SUPPORTED) > 0
|
|
86
|
+
? version
|
|
87
|
+
: MIN_IOS_VERSION_SUPPORTED;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function defaultReadFile(absPath /*: string */) /*: ?string */ {
|
|
91
|
+
try {
|
|
92
|
+
return fs.readFileSync(absPath, 'utf8');
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* `IPHONEOS_DEPLOYMENT_TARGET` as an xcconfig defines it, following `#include`
|
|
100
|
+
* lines in place so a later assignment in the includer wins. Null when the file
|
|
101
|
+
* is unreadable, sets no unconditional value, or sets one that is not a plain
|
|
102
|
+
* version (`$(inherited)`).
|
|
103
|
+
*/
|
|
104
|
+
function parseXcconfigSetting(
|
|
105
|
+
absPath /*: string */,
|
|
106
|
+
content /*: string */,
|
|
107
|
+
readFile /*: (absPath: string) => ?string */,
|
|
108
|
+
visited /*: Set<string> */ = new Set(),
|
|
109
|
+
depth /*: number */ = 0,
|
|
110
|
+
) /*: ?string */ {
|
|
111
|
+
if (visited.has(absPath)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
visited.add(absPath);
|
|
115
|
+
const assignment = new RegExp(`^${DEPLOYMENT_TARGET_KEY}\\s*=\\s*([^;]*);?$`);
|
|
116
|
+
let value /*: ?string */ = null;
|
|
117
|
+
for (const rawLine of content.split('\n')) {
|
|
118
|
+
const line = rawLine.replace(/\/\/.*$/, '').trim();
|
|
119
|
+
const include = line.match(/^#include\??\s+"([^"]+)"/);
|
|
120
|
+
if (include != null) {
|
|
121
|
+
const included = readXcconfigSetting(
|
|
122
|
+
path.resolve(path.dirname(absPath), include[1]),
|
|
123
|
+
readFile,
|
|
124
|
+
visited,
|
|
125
|
+
depth + 1,
|
|
126
|
+
);
|
|
127
|
+
if (included != null) {
|
|
128
|
+
value = included;
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const match = line.match(assignment);
|
|
133
|
+
if (match != null) {
|
|
134
|
+
const raw = unquote(match[1].trim());
|
|
135
|
+
value = IOS_VERSION_RE.test(raw) ? raw : null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** parseXcconfigSetting for a path not read yet — the `#include` entry point. */
|
|
142
|
+
function readXcconfigSetting(
|
|
143
|
+
absPath /*: string */,
|
|
144
|
+
readFile /*: (absPath: string) => ?string */,
|
|
145
|
+
visited /*: Set<string> */,
|
|
146
|
+
depth /*: number */,
|
|
147
|
+
) /*: ?string */ {
|
|
148
|
+
if (depth > MAX_XCCONFIG_DEPTH) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const content = readFile(absPath);
|
|
152
|
+
return content != null
|
|
153
|
+
? parseXcconfigSetting(absPath, content, readFile, visited, depth)
|
|
154
|
+
: null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Directory components of the PBXGroup chain holding `uuid`, outermost first —
|
|
159
|
+
* how a `"<group>"` file reference's path is anchored to the project dir.
|
|
160
|
+
*/
|
|
161
|
+
function groupPathPrefix(
|
|
162
|
+
text /*: string */,
|
|
163
|
+
uuid /*: string */,
|
|
164
|
+
) /*: Array<string> */ {
|
|
165
|
+
const groups /*: Array<{uuid: string, path: ?string, children: Set<string>}> */ =
|
|
166
|
+
[];
|
|
167
|
+
forEachObjectInSection(text, 'PBXGroup', ({uuid: groupUuid, ...body}) => {
|
|
168
|
+
const children = findField(text, body, 'children');
|
|
169
|
+
const groupPath = findField(text, body, 'path');
|
|
170
|
+
groups.push({
|
|
171
|
+
uuid: groupUuid,
|
|
172
|
+
path: groupPath != null ? unquote(groupPath.value) : null,
|
|
173
|
+
children: children != null ? uuidsInArray(children.value) : new Set(),
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const parts /*: Array<string> */ = [];
|
|
178
|
+
let current = uuid;
|
|
179
|
+
for (let i = 0; i < groups.length; i++) {
|
|
180
|
+
const parent = groups.find(group => group.children.has(current));
|
|
181
|
+
if (parent == null) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
if (parent.path != null) {
|
|
185
|
+
parts.unshift(parent.path);
|
|
186
|
+
}
|
|
187
|
+
current = parent.uuid;
|
|
188
|
+
}
|
|
189
|
+
return parts;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Absolute paths to try for the xcconfig a configuration is based on, in order:
|
|
194
|
+
* the reference's own anchoring first, then plain `<srcRoot>/<path>`.
|
|
195
|
+
*/
|
|
196
|
+
function xcconfigCandidates(
|
|
197
|
+
text /*: string */,
|
|
198
|
+
configObj /*: {bodyOpen: number, bodyClose: number, ...} */,
|
|
199
|
+
srcRoot /*: string */,
|
|
200
|
+
) /*: Array<string> */ {
|
|
201
|
+
const base = findField(text, configObj, 'baseConfigurationReference');
|
|
202
|
+
const refUuid = base?.value.match(/[0-9A-Fa-f]{24}/)?.[0];
|
|
203
|
+
if (refUuid == null) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const ref = findObjectByUuid(text, refUuid);
|
|
207
|
+
if (ref == null) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
const pathField = findField(text, ref, 'path');
|
|
211
|
+
if (pathField == null) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const refPath = unquote(pathField.value);
|
|
215
|
+
if (path.isAbsolute(refPath)) {
|
|
216
|
+
return [refPath];
|
|
217
|
+
}
|
|
218
|
+
const sourceTreeField = findField(text, ref, 'sourceTree');
|
|
219
|
+
const sourceTree =
|
|
220
|
+
sourceTreeField != null ? unquote(sourceTreeField.value) : '';
|
|
221
|
+
const fallback = path.join(srcRoot, refPath);
|
|
222
|
+
if (sourceTree !== '<group>') {
|
|
223
|
+
return [fallback];
|
|
224
|
+
}
|
|
225
|
+
const anchored = path.join(
|
|
226
|
+
srcRoot,
|
|
227
|
+
...groupPathPrefix(text, refUuid),
|
|
228
|
+
refPath,
|
|
229
|
+
);
|
|
230
|
+
return anchored === fallback ? [fallback] : [anchored, fallback];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function configName(
|
|
234
|
+
text /*: string */,
|
|
235
|
+
configObj /*: {bodyOpen: number, bodyClose: number, ...} */,
|
|
236
|
+
) /*: ?string */ {
|
|
237
|
+
const field = findField(text, configObj, 'name');
|
|
238
|
+
return field != null ? unquote(field.value) : null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** A plain-version `IPHONEOS_DEPLOYMENT_TARGET` literal in a configuration. */
|
|
242
|
+
function configLiteral(
|
|
243
|
+
text /*: string */,
|
|
244
|
+
configObj /*: {bodyOpen: number, bodyClose: number, ...} */,
|
|
245
|
+
) /*: ?string */ {
|
|
246
|
+
const settings = findField(text, configObj, 'buildSettings');
|
|
247
|
+
if (settings == null) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const field = findField(
|
|
251
|
+
text,
|
|
252
|
+
{bodyOpen: settings.valueStart, bodyClose: settings.tokenEnd - 1},
|
|
253
|
+
DEPLOYMENT_TARGET_KEY,
|
|
254
|
+
);
|
|
255
|
+
if (field == null) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const raw = unquote(field.value);
|
|
259
|
+
return IOS_VERSION_RE.test(raw) ? raw : null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* What one configuration declares: its own literal, else the xcconfig it is
|
|
264
|
+
* based on (Xcode's own precedence).
|
|
265
|
+
*/
|
|
266
|
+
function configDeploymentTarget(
|
|
267
|
+
text /*: string */,
|
|
268
|
+
configObj /*: {bodyOpen: number, bodyClose: number, ...} */,
|
|
269
|
+
ctx /*: XcconfigContext */,
|
|
270
|
+
) /*: ?string */ {
|
|
271
|
+
const literal = configLiteral(text, configObj);
|
|
272
|
+
if (literal != null || ctx.srcRoot == null) {
|
|
273
|
+
return literal;
|
|
274
|
+
}
|
|
275
|
+
for (const candidate of xcconfigCandidates(text, configObj, ctx.srcRoot)) {
|
|
276
|
+
const content = ctx.readFile(candidate);
|
|
277
|
+
if (content != null) {
|
|
278
|
+
return parseXcconfigSetting(candidate, content, ctx.readFile);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The app's declared iOS deployment target, or null when nothing declares one.
|
|
286
|
+
* Target selection: the marker's `targetUuid`, else the app target named
|
|
287
|
+
* `targetName` (`--product-name`), else every app target. Each configuration
|
|
288
|
+
* falls back to the project-level one of the same name (each level accepting a
|
|
289
|
+
* literal or the xcconfig it is based on), and the lowest wins — a single
|
|
290
|
+
* manifest floor has to hold for every configuration.
|
|
291
|
+
*/
|
|
292
|
+
function readIosDeploymentTargetFromPbxproj(
|
|
293
|
+
text /*: string */,
|
|
294
|
+
opts /*:: ?: {
|
|
295
|
+
targetUuid?: ?string,
|
|
296
|
+
targetName?: ?string,
|
|
297
|
+
srcRoot?: ?string,
|
|
298
|
+
readFile?: (absPath: string) => ?string,
|
|
299
|
+
} */,
|
|
300
|
+
) /*: ?string */ {
|
|
301
|
+
const ctx /*: XcconfigContext */ = {
|
|
302
|
+
srcRoot: opts?.srcRoot,
|
|
303
|
+
readFile: opts?.readFile ?? defaultReadFile,
|
|
304
|
+
};
|
|
305
|
+
const targetUuid = opts?.targetUuid;
|
|
306
|
+
const marked = targetUuid != null ? findObjectByUuid(text, targetUuid) : null;
|
|
307
|
+
const apps = findApplicationTargets(text);
|
|
308
|
+
const named =
|
|
309
|
+
opts?.targetName != null
|
|
310
|
+
? apps.find(app => app.name === opts.targetName)
|
|
311
|
+
: null;
|
|
312
|
+
const targets = marked != null ? [marked] : named != null ? [named] : apps;
|
|
313
|
+
|
|
314
|
+
const projectDefaults /*: Map<string, string> */ = new Map();
|
|
315
|
+
const project = findProjectObject(text);
|
|
316
|
+
if (project != null) {
|
|
317
|
+
for (const uuid of targetBuildConfigUuids(text, project)) {
|
|
318
|
+
const config = findObjectByUuid(text, uuid);
|
|
319
|
+
const name = config != null ? configName(text, config) : null;
|
|
320
|
+
const value =
|
|
321
|
+
config != null ? configDeploymentTarget(text, config, ctx) : null;
|
|
322
|
+
if (name != null && value != null) {
|
|
323
|
+
projectDefaults.set(name, value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let floor /*: ?string */ = null;
|
|
329
|
+
for (const target of targets) {
|
|
330
|
+
for (const uuid of targetBuildConfigUuids(text, target)) {
|
|
331
|
+
const config = findObjectByUuid(text, uuid);
|
|
332
|
+
if (config == null) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const name = configName(text, config);
|
|
336
|
+
const value =
|
|
337
|
+
configDeploymentTarget(text, config, ctx) ??
|
|
338
|
+
(name != null ? projectDefaults.get(name) : null) ??
|
|
339
|
+
null;
|
|
340
|
+
if (
|
|
341
|
+
value != null &&
|
|
342
|
+
(floor == null || compareIosVersions(value, floor) < 0)
|
|
343
|
+
) {
|
|
344
|
+
floor = value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return floor;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* The sanitized floor read from `<xcodeprojPath>/project.pbxproj`, or null when
|
|
353
|
+
* there is no project, it cannot be read, or it declares nothing usable.
|
|
354
|
+
*/
|
|
355
|
+
function resolveIosDeploymentTarget(
|
|
356
|
+
opts /*: {xcodeprojPath: ?string, targetUuid?: ?string, targetName?: ?string} */,
|
|
357
|
+
) /*: ?string */ {
|
|
358
|
+
const {xcodeprojPath, targetUuid, targetName} = opts;
|
|
359
|
+
if (xcodeprojPath == null) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const found = readIosDeploymentTargetFromPbxproj(
|
|
364
|
+
fs.readFileSync(path.join(xcodeprojPath, 'project.pbxproj'), 'utf8'),
|
|
365
|
+
{targetUuid, targetName, srcRoot: path.dirname(xcodeprojPath)},
|
|
366
|
+
);
|
|
367
|
+
return found != null ? sanitizeIosDeploymentTarget(found) : null;
|
|
368
|
+
} catch {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = {
|
|
374
|
+
MIN_IOS_VERSION_SUPPORTED,
|
|
375
|
+
readIosDeploymentTargetFromPbxproj,
|
|
376
|
+
resolveIosDeploymentTarget,
|
|
377
|
+
sanitizeIosDeploymentTarget,
|
|
378
|
+
};
|
|
@@ -170,6 +170,27 @@ function cleanupPatchedPodspec(patchedPath /*: ?string */) /*: void */ {
|
|
|
170
170
|
// Regex fallback
|
|
171
171
|
// ---------------------------------------------------------------------------
|
|
172
172
|
|
|
173
|
+
// Comment-only lines are dropped so a commented-out `# s.header_dir` cannot win
|
|
174
|
+
// over the live one. A trailing `#` and a `#` inside a string survive: only the
|
|
175
|
+
// first non-whitespace character counts.
|
|
176
|
+
function readPodspecSource(podspecPath /*: string */) /*: string */ {
|
|
177
|
+
return fs
|
|
178
|
+
.readFileSync(podspecPath, 'utf8')
|
|
179
|
+
.split('\n')
|
|
180
|
+
.filter(line => !/^\s*#/.test(line))
|
|
181
|
+
.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// A subspec that re-binds a receiver the identity regexes trust: inside
|
|
185
|
+
// `s.subspec "common" do |s|`, `s` is the CHILD, and no regex over flat text
|
|
186
|
+
// can see that. Any other block variable (`do |ss|`) is unambiguous.
|
|
187
|
+
const SHADOWING_SUBSPEC_RE =
|
|
188
|
+
/\.subspec\s+["'][^"']*["']\s+do\s*\|\s*(?:s|spec)\s*\|/;
|
|
189
|
+
|
|
190
|
+
function hasShadowingSubspec(source /*: string */) /*: boolean */ {
|
|
191
|
+
return SHADOWING_SUBSPEC_RE.test(source);
|
|
192
|
+
}
|
|
193
|
+
|
|
173
194
|
/**
|
|
174
195
|
* Best-effort Ruby podspec parser. Extracts the literal-string and
|
|
175
196
|
* literal-array fields most RN libs use. Skips subspec blocks, Ruby helper
|
|
@@ -178,16 +199,39 @@ function cleanupPatchedPodspec(patchedPath /*: ?string */) /*: void */ {
|
|
|
178
199
|
* user. Always returns a RawSpec; pure-JS, no Ruby dep required.
|
|
179
200
|
*/
|
|
180
201
|
function regexPodspec(podspecPath /*: string */) /*: RawSpec */ {
|
|
181
|
-
const content =
|
|
202
|
+
const content = readPodspecSource(podspecPath);
|
|
182
203
|
const warnings /*: Array<string> */ = [];
|
|
183
204
|
|
|
184
|
-
// Matches: s.<field> = "value" or s.<field> = 'value'
|
|
205
|
+
// Matches: s.<field> = "value" or s.<field> = 'value' — at ANY scope,
|
|
206
|
+
// so a subspec's value counts. Right for the fields flattenSubspecs merges
|
|
207
|
+
// (source globs, header mappings); wrong for the fields that identify the
|
|
208
|
+
// spec, which use getSpecStringField.
|
|
185
209
|
function getStringField(name /*: string */) /*: string | null */ {
|
|
186
210
|
const re = new RegExp(`(?:s|spec)\\.${name}\\s*=\\s*["']([^"']+)["']`);
|
|
187
211
|
const m = content.match(re);
|
|
188
212
|
return m ? m[1] : null;
|
|
189
213
|
}
|
|
190
214
|
|
|
215
|
+
// The same, but only where the receiver starts the statement — so a subspec's
|
|
216
|
+
// block variable (`ss.header_dir`, the shape react-native-screens and
|
|
217
|
+
// react-native-svg ship) cannot answer for the spec itself.
|
|
218
|
+
function getSpecStringField(name /*: string */) /*: string | null */ {
|
|
219
|
+
const re = new RegExp(
|
|
220
|
+
`(?:^|[;|])\\s*(?:s|spec)\\.${name}\\s*=\\s*["']([^"']+)["']`,
|
|
221
|
+
'm',
|
|
222
|
+
);
|
|
223
|
+
const m = content.match(re);
|
|
224
|
+
return m ? m[1] : null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The fields that identify the spec, and only when the receiver spelling
|
|
228
|
+
// still says which scope declared them: a subspec re-binding `s` puts its own
|
|
229
|
+
// `header_dir` exactly where the library's would be.
|
|
230
|
+
const shadowedIdentity = hasShadowingSubspec(content);
|
|
231
|
+
function getIdentityField(name /*: string */) /*: string | null */ {
|
|
232
|
+
return shadowedIdentity ? null : getSpecStringField(name);
|
|
233
|
+
}
|
|
234
|
+
|
|
191
235
|
// Matches:
|
|
192
236
|
// s.<field> = ["a", "b"] (array)
|
|
193
237
|
// s.<field> = "single" (single value treated as 1-element array)
|
|
@@ -289,6 +333,11 @@ function regexPodspec(podspecPath /*: string */) /*: RawSpec */ {
|
|
|
289
333
|
}
|
|
290
334
|
|
|
291
335
|
// Surface known unparseable constructs so the caller can warn the user.
|
|
336
|
+
if (shadowedIdentity) {
|
|
337
|
+
warnings.push(
|
|
338
|
+
"A subspec rebinds the spec variable (`do |s|`), so its `name`, `module_name` and `header_dir` cannot be told from the library's own — those are left unread. Install CocoaPods (`gem install cocoapods`) to enable full `pod ipc spec` parsing.",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
292
341
|
if (/(?:s|spec)\.subspec\s+["']/.test(content)) {
|
|
293
342
|
warnings.push(
|
|
294
343
|
'Subspecs detected — regex parser only extracts top-level fields. Install CocoaPods (`gem install cocoapods`) to enable full `pod ipc spec` parsing.',
|
|
@@ -306,14 +355,15 @@ function regexPodspec(podspecPath /*: string */) /*: RawSpec */ {
|
|
|
306
355
|
}
|
|
307
356
|
|
|
308
357
|
return {
|
|
309
|
-
name:
|
|
310
|
-
|
|
358
|
+
name: getIdentityField('name'),
|
|
359
|
+
module_name: getIdentityField('module_name'),
|
|
360
|
+
version: getSpecStringField('version'),
|
|
311
361
|
source_files: getArrayField('source_files'),
|
|
312
362
|
public_header_files: getArrayField('public_header_files'),
|
|
313
363
|
private_header_files: getArrayField('private_header_files'),
|
|
314
364
|
exclude_files: getArrayField('exclude_files'),
|
|
315
365
|
header_mappings_dir: getStringField('header_mappings_dir'),
|
|
316
|
-
header_dir:
|
|
366
|
+
header_dir: getIdentityField('header_dir'),
|
|
317
367
|
frameworks: getFrameworks(false),
|
|
318
368
|
weak_frameworks: getFrameworks(true),
|
|
319
369
|
libraries: getArrayField('libraries'),
|
|
@@ -432,6 +482,14 @@ function flattenSubspecs(rawSpec /*: RawSpec */) /*: PodspecModel */ {
|
|
|
432
482
|
return Array.from(new Set(out));
|
|
433
483
|
}
|
|
434
484
|
|
|
485
|
+
// Only the spec's own value: a subspec's `header_dir` names that subspec's
|
|
486
|
+
// headers, and several subspecs can disagree.
|
|
487
|
+
function specStringField(key /*: string */) /*: string | null */ {
|
|
488
|
+
// $FlowFixMe[incompatible-use] dynamic shape
|
|
489
|
+
const value = rawSpec[key];
|
|
490
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
491
|
+
}
|
|
492
|
+
|
|
435
493
|
function mergeStringField(key /*: string */) /*: string | null */ {
|
|
436
494
|
for (const layer of layers) {
|
|
437
495
|
// $FlowFixMe[incompatible-use] layer narrowed from `mixed`; runtime-validated below
|
|
@@ -620,8 +678,8 @@ function flattenSubspecs(rawSpec /*: RawSpec */) /*: PodspecModel */ {
|
|
|
620
678
|
: true; // RN ecosystem default
|
|
621
679
|
|
|
622
680
|
return {
|
|
623
|
-
name:
|
|
624
|
-
version:
|
|
681
|
+
name: specStringField('name') ?? '',
|
|
682
|
+
version: specStringField('version') ?? '',
|
|
625
683
|
sourceFiles: mergeArrayField('source_files'),
|
|
626
684
|
publicHeaderFiles: mergeArrayField('public_header_files'),
|
|
627
685
|
privateHeaderFiles: mergeArrayField('private_header_files'),
|
|
@@ -634,7 +692,8 @@ function flattenSubspecs(rawSpec /*: RawSpec */) /*: PodspecModel */ {
|
|
|
634
692
|
// resolve from the physical tree — CocoaPods does this via the
|
|
635
693
|
// header_mappings_dir copy step, which SPM has no equivalent for.
|
|
636
694
|
headerMappingsDirs: mergeArrayField('header_mappings_dir'),
|
|
637
|
-
headerDir:
|
|
695
|
+
headerDir: specStringField('header_dir'),
|
|
696
|
+
moduleName: specStringField('module_name'),
|
|
638
697
|
frameworks: mergeArrayField('frameworks'),
|
|
639
698
|
weakFrameworks: mergeArrayField('weak_frameworks'),
|
|
640
699
|
libraries: mergeArrayField('libraries'),
|
|
@@ -687,8 +746,97 @@ function readPodspec(podspecPath /*: string */) /*: PodspecModel */ {
|
|
|
687
746
|
return model;
|
|
688
747
|
}
|
|
689
748
|
|
|
749
|
+
const podspecModels /*: Map<string, PodspecModel> */ = new Map();
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* readPodspec memoized on the resolved path. `pod ipc spec` costs a process
|
|
753
|
+
* spawn, and one autolinking run reads the same podspec from several sites
|
|
754
|
+
* (name resolution, header search paths, scaffolding). The model is shared, so
|
|
755
|
+
* callers must treat it as read-only.
|
|
756
|
+
*/
|
|
757
|
+
function readPodspecCached(podspecPath /*: string */) /*: PodspecModel */ {
|
|
758
|
+
const key = path.resolve(podspecPath);
|
|
759
|
+
const cached = podspecModels.get(key);
|
|
760
|
+
if (cached != null) {
|
|
761
|
+
return cached;
|
|
762
|
+
}
|
|
763
|
+
const model = readPodspec(podspecPath);
|
|
764
|
+
podspecModels.set(key, model);
|
|
765
|
+
return model;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* The three fields that name a library, read WITHOUT `pod ipc spec`'s process
|
|
770
|
+
* spawn — name resolution runs for every dep, including self-managed ones that
|
|
771
|
+
* need nothing else from the podspec.
|
|
772
|
+
*
|
|
773
|
+
* Null when the answer cannot be trusted without Ruby: an interpolated value,
|
|
774
|
+
* or a spec-level `header_dir`/`module_name` the regex could not read. Both
|
|
775
|
+
* outrank the pod name, so a literal name alone is not enough — resolving
|
|
776
|
+
* without them would pick the wrong prefix. A value only a subspec declares is
|
|
777
|
+
* not the library's, so it neither answers nor defers — unless that subspec
|
|
778
|
+
* re-binds `s` (or `spec`), which makes every field's scope unreadable.
|
|
779
|
+
*/
|
|
780
|
+
function readPodspecNames(
|
|
781
|
+
podspecPath /*: string */,
|
|
782
|
+
) /*: ?{name: ?string, moduleName: ?string, headerDir: ?string} */ {
|
|
783
|
+
const raw = regexPodspec(podspecPath);
|
|
784
|
+
const field = (key /*: string */) /*: ?string */ => {
|
|
785
|
+
// $FlowFixMe[prop-missing] RawSpec is dynamically shaped
|
|
786
|
+
const value = raw[key];
|
|
787
|
+
return typeof value === 'string' &&
|
|
788
|
+
value.length > 0 &&
|
|
789
|
+
!value.includes('#{')
|
|
790
|
+
? value
|
|
791
|
+
: null;
|
|
792
|
+
};
|
|
793
|
+
const source = readPodspecSource(podspecPath);
|
|
794
|
+
if (hasShadowingSubspec(source)) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
const declaredUnread = (
|
|
798
|
+
key /*: string */,
|
|
799
|
+
value /*: ?string */,
|
|
800
|
+
) /*: boolean */ =>
|
|
801
|
+
value == null &&
|
|
802
|
+
new RegExp(`(?:^|[;|])\\s*(?:s|spec)\\.${key}\\s*=`, 'm').test(source);
|
|
803
|
+
|
|
804
|
+
const name = field('name');
|
|
805
|
+
const moduleName = field('module_name');
|
|
806
|
+
const headerDir = field('header_dir');
|
|
807
|
+
if (
|
|
808
|
+
declaredUnread('header_dir', headerDir) ||
|
|
809
|
+
declaredUnread('module_name', moduleName)
|
|
810
|
+
) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return name == null && moduleName == null && headerDir == null
|
|
814
|
+
? null
|
|
815
|
+
: {name, moduleName, headerDir};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Every podspec in `dir`, sorted so two of them name a target the same way on
|
|
820
|
+
* every machine (readdir order is unspecified). Skips a crashed run's leftover
|
|
821
|
+
* `.spm-scaffold-<pid>-<name>.podspec` copy.
|
|
822
|
+
*/
|
|
823
|
+
function findPodspecs(dir /*: string */) /*: Array<string> */ {
|
|
824
|
+
try {
|
|
825
|
+
return fs
|
|
826
|
+
.readdirSync(dir)
|
|
827
|
+
.filter(e => e.endsWith('.podspec') && !e.startsWith('.spm-scaffold-'))
|
|
828
|
+
.sort()
|
|
829
|
+
.map(e => path.join(dir, e));
|
|
830
|
+
} catch {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
690
835
|
module.exports = {
|
|
836
|
+
findPodspecs,
|
|
691
837
|
readPodspec,
|
|
838
|
+
readPodspecCached,
|
|
839
|
+
readPodspecNames,
|
|
692
840
|
runPodIpcSpec,
|
|
693
841
|
regexPodspec,
|
|
694
842
|
flattenSubspecs,
|