first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
/**
* Wrapper required to abstract away from the actual codegen.
* This is needed because, when running tests in Sandcastle, not everything is setup as usually.
* For example, the `@react-native/codegen` lib is not present.
*
* Thanks to this wrapper, we are able to mock the getter for the codegen in a way that allow us to return
* a custom object which mimics the Codegen interface.
*
* @return an object that can generate the code for the New Architecture.
*/
function getCodegen() /*: $FlowFixMe */ {
let RNCodegen;
try {
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js');
} catch (e) {
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen.js');
}
if (!RNCodegen) {
throw new Error('RNCodegen not found.');
}
return RNCodegen;
}
function getCombineJSToSchema() /*: $FlowFixMe */ {
let combineJSToSchema;
try {
// $FlowFixMe[cannot-resolve-module]
combineJSToSchema = require('../../packages/react-native-codegen/lib/cli/combine/combine-js-to-schema.js');
} catch (e) {
// $FlowFixMe[cannot-resolve-module]
combineJSToSchema = require('@react-native/codegen/lib/cli/combine/combine-js-to-schema.js');
}
if (!combineJSToSchema) {
throw new Error('combine-js-to-schema not found.');
}
return combineJSToSchema;
}
module.exports = {
getCodegen,
getCombineJSToSchema,
};

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const fs = require('fs');
const path = require('path');
const REACT_NATIVE_REPOSITORY_ROOT = path.join(
__dirname,
'..',
'..',
'..',
'..',
'..',
);
const REACT_NATIVE_PACKAGE_ROOT_FOLDER /*: string */ = path.join(
__dirname,
'..',
'..',
'..',
);
const CODEGEN_REPO_PATH = `${REACT_NATIVE_REPOSITORY_ROOT}/packages/react-native-codegen`;
const CORE_LIBRARIES_WITH_OUTPUT_FOLDER /*: {[string]: $FlowFixMe} */ = {
FBReactNativeSpec: {
ios: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'React',
'FBReactNativeSpec',
) /*:: as string */,
android: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'ReactAndroid',
'build',
'generated',
'source',
'codegen',
) /*:: as string */,
},
};
const packageJsonPath = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'package.json',
);
// $FlowFixMe[signature-verification-failure]
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const REACT_NATIVE = packageJson.name;
const TEMPLATES_FOLDER_PATH /*: string */ = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
);
module.exports = {
CODEGEN_REPO_PATH,
CORE_LIBRARIES_WITH_OUTPUT_FOLDER,
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
REACT_NATIVE,
TEMPLATES_FOLDER_PATH,
packageJson,
};

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH, packageJson} = require('./constants');
const {codegenLog} = require('./utils');
const fs = require('fs');
const path = require('path');
const APP_DEPENDENCY_PROVIDER_H_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTAppDependencyProviderH.template',
);
const APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTAppDependencyProviderMM.template',
);
const APP_DEPENDENCY_PROVIDER_PODSPEC_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'ReactAppDependencyProvider.podspec.template',
);
function generateAppDependencyProvider(outputDir /*: string */) {
fs.mkdirSync(outputDir, {recursive: true});
codegenLog('Generating RCTAppDependencyProvider');
const templateH = fs.readFileSync(
APP_DEPENDENCY_PROVIDER_H_TEMPLATE_PATH,
'utf8',
);
const finalPathH = path.join(outputDir, 'RCTAppDependencyProvider.h');
fs.writeFileSync(finalPathH, templateH);
codegenLog(`Generated artifact: ${finalPathH}`);
const templateMM = fs.readFileSync(
APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH,
'utf8',
);
const finalPathMM = path.join(outputDir, 'RCTAppDependencyProvider.mm');
fs.writeFileSync(finalPathMM, templateMM);
codegenLog(`Generated artifact: ${finalPathMM}`);
// Generate the podspec file
const templatePodspec = fs
.readFileSync(APP_DEPENDENCY_PROVIDER_PODSPEC_TEMPLATE_PATH, 'utf8')
.replace(/{react-native-version}/, packageJson.version)
.replace(/{react-native-licence}/, packageJson.license);
const finalPathPodspec = path.join(
outputDir,
'ReactAppDependencyProvider.podspec',
);
fs.writeFileSync(finalPathPodspec, templatePodspec);
codegenLog(`Generated podspec: ${finalPathPodspec}`);
}
module.exports = {
generateAppDependencyProvider,
};

View File

@@ -0,0 +1,122 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH} = require('./constants');
const {parseiOSAnnotations} = require('./utils');
const fs = require('fs');
const path = require('path');
const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTModulesConformingToProtocolsProviderH.template',
);
const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTModulesConformingToProtocolsProviderMM.template',
);
function generateCustomURLHandlers(
libraries /*: $ReadOnlyArray<$FlowFixMe> */,
outputDir /*: string */,
) {
const iosAnnotations = parseiOSAnnotations(libraries);
const imageURLLoaderModules = new Set /*::<string>*/();
const imageDataDecoderModules = new Set /*::<string>*/();
const urlRequestHandlersModules = new Set /*::<string>*/();
// $FlowFixMe[missing-local-annot]]
const wrapInArrayIfNecessary = value =>
Array.isArray(value) || value == null ? value : [value];
// Old API
for (const library of libraries) {
const modulesConformingToProtocol =
library?.config?.ios?.modulesConformingToProtocol;
if (modulesConformingToProtocol == null) {
continue;
}
wrapInArrayIfNecessary(
modulesConformingToProtocol.RCTImageURLLoader,
)?.forEach(moduleName => {
imageURLLoaderModules.add(moduleName);
});
wrapInArrayIfNecessary(
modulesConformingToProtocol.RCTImageDataDecoder,
)?.forEach(moduleName => {
imageDataDecoderModules.add(moduleName);
});
wrapInArrayIfNecessary(
modulesConformingToProtocol.RCTURLRequestHandler,
)?.forEach(moduleName => {
urlRequestHandlersModules.add(moduleName);
});
}
// New API
for (const {modules: moduleAnnotationMap} of Object.values(iosAnnotations)) {
for (const [moduleName, annotation] of Object.entries(
moduleAnnotationMap,
)) {
const conformsToProtocols = annotation.conformsToProtocols;
if (!conformsToProtocols) {
continue;
}
if (conformsToProtocols.includes('RCTImageURLLoader')) {
imageURLLoaderModules.add(moduleName);
}
if (conformsToProtocols.includes('RCTImageDataDecoder')) {
imageDataDecoderModules.add(moduleName);
}
if (conformsToProtocols.includes('RCTURLRequestHandler')) {
urlRequestHandlersModules.add(moduleName);
}
}
}
const customImageURLLoaderClasses = Array.from(imageURLLoaderModules)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customImageDataDecoderClasses = Array.from(imageDataDecoderModules)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customURLHandlerClasses = Array.from(urlRequestHandlersModules)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const template = fs.readFileSync(MODULES_PROTOCOLS_MM_TEMPLATE_PATH, 'utf8');
const finalMMFile = template
.replace(/{imageURLLoaderClassNames}/, customImageURLLoaderClasses)
.replace(/{imageDataDecoderClassNames}/, customImageDataDecoderClasses)
.replace(/{requestHandlersClassNames}/, customURLHandlerClasses);
fs.mkdirSync(outputDir, {recursive: true});
fs.writeFileSync(
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.mm'),
finalMMFile,
);
const templateH = fs.readFileSync(MODULES_PROTOCOLS_H_TEMPLATE_PATH, 'utf8');
fs.writeFileSync(
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.h'),
templateH,
);
}
module.exports = {
generateCustomURLHandlers,
};

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {generateCode} = require('./generateNativeCode');
const {generateSchemaInfo} = require('./generateSchemaInfos');
const {
buildCodegenIfNeeded,
findProjectRootLibraries,
readPkgJsonInDirectory,
} = require('./utils');
function generateFBReactNativeSpecIOS(projectRoot /*: string */) /*: void*/ {
const platform = 'ios';
buildCodegenIfNeeded();
const pkgJson = readPkgJsonInDirectory(projectRoot);
const fbReactNativeSpecLib = findProjectRootLibraries(
pkgJson,
projectRoot,
).filter(library => library.config.name === 'FBReactNativeSpec')[0];
if (!fbReactNativeSpecLib) {
throw new Error(
"[Codegen] Can't find FBReactNativeSpec library. Failed to generate artifacts",
);
}
const fbReactNativeSchemaInfo = generateSchemaInfo(
fbReactNativeSpecLib,
platform,
);
generateCode('', fbReactNativeSchemaInfo, false, platform);
}
module.exports = {
generateFBReactNativeSpecIOS,
};

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const generateSpecsCLIExecutor = require('../generate-specs-cli-executor');
const {CORE_LIBRARIES_WITH_OUTPUT_FOLDER} = require('./constants');
const {codegenLog} = require('./utils');
const fs = require('fs');
const os = require('os');
const path = require('path');
function generateNativeCode(
outputPath /*: string */,
schemaInfos /*: $ReadOnlyArray<$FlowFixMe> */,
includesGeneratedCode /*: boolean */,
platform /*: string */,
) /*: Array<void> */ {
return schemaInfos.map(schemaInfo => {
generateCode(outputPath, schemaInfo, includesGeneratedCode, platform);
});
}
function generateCode(
outputPath /*: string */,
schemaInfo /*: $FlowFixMe */,
includesGeneratedCode /*: boolean */,
platform /*: string */,
) {
if (shouldSkipGenerationForFBReactNativeSpec(schemaInfo, platform)) {
codegenLog(
'[Codegen - FBReactNativeSpec] Skipping iOS code generation for FBReactNativeSpec as it has been generated already.',
true,
);
return;
}
const libraryName = schemaInfo.library.config.name;
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), libraryName));
const tmpOutputDir = path.join(tmpDir, 'out');
fs.mkdirSync(tmpOutputDir, {recursive: true});
codegenLog(`Generating Native Code for ${libraryName} - ${platform}`);
const useLocalIncludePaths = includesGeneratedCode;
generateSpecsCLIExecutor.generateSpecFromInMemorySchema(
platform,
schemaInfo.schema,
tmpOutputDir,
libraryName,
'com.facebook.fbreact.specs',
schemaInfo.library.config.type,
useLocalIncludePaths,
);
// Finally, copy artifacts to the final output directory.
const outputDir =
reactNativeCoreLibraryOutputPath(libraryName, platform) ?? outputPath;
fs.mkdirSync(outputDir, {recursive: true});
// $FlowFixMe[prop-missing] - `fs.cpSync` is missing in Flow libdefs.
fs.cpSync(tmpOutputDir, outputDir, {recursive: true});
codegenLog(`Generated artifacts: ${outputDir}`);
}
function reactNativeCoreLibraryOutputPath(
libraryName /*: string */,
platform /*: string */,
) {
return CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName]
? CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName][platform]
: null;
}
function shouldSkipGenerationForFBReactNativeSpec(
schemaInfo /*: $FlowFixMe */,
platform /*: string */,
) {
if (
platform !== 'ios' ||
schemaInfo.library.config.name !== 'FBReactNativeSpec'
) {
return false;
}
const fbReactNativeSpecOutputPath =
CORE_LIBRARIES_WITH_OUTPUT_FOLDER.FBReactNativeSpec.ios;
const fbReactNativeSpecAbsolutePath = path.resolve(
fbReactNativeSpecOutputPath,
);
return (
fbReactNativeSpecAbsolutePath.includes('node_modules') &&
fs.existsSync(fbReactNativeSpecAbsolutePath) &&
fs.readdirSync(fbReactNativeSpecAbsolutePath).length > 0
);
}
module.exports = {
generateNativeCode,
generateCode,
};

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH} = require('./constants');
const {codegenLog} = require('./utils');
const fs = require('fs');
const path = require('path');
const PACKAGE_SWIFT_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'Package.swift.template',
);
function generatePackageSwift(
projectRoot /*: string */,
outputDir /*: string */,
reactNativePath /*: string */,
) {
const fullOutputPath = path.join(projectRoot, outputDir);
fs.mkdirSync(outputDir, {recursive: true});
// Generate PAckage.swift File
codegenLog('Generating Package.swift');
const templateH = fs
.readFileSync(PACKAGE_SWIFT_TEMPLATE_PATH, 'utf8')
.replace(
/{reactNativePath}/,
path.relative(fullOutputPath, reactNativePath),
);
const finalPathH = path.join(outputDir, 'Package.swift');
fs.writeFileSync(finalPathH, templateH);
codegenLog(`Generated artifact: ${finalPathH}`);
}
module.exports = {
generatePackageSwift,
};

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH} = require('./constants');
const {
codegenLog,
isReactNativeCoreLibrary,
parseiOSAnnotations,
} = require('./utils');
const fs = require('fs');
const path = require('path');
const MODULE_PROVIDERS_H_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTModuleProvidersH.template',
);
const MODULE_PROVIDERS_MM_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTModuleProvidersMM.template',
);
function generateRCTModuleProviders(
projectRoot /*: string */,
pkgJson /*: $FlowFixMe */,
libraries /*: $ReadOnlyArray<$FlowFixMe> */,
outputDir /*: string */,
) {
fs.mkdirSync(outputDir, {recursive: true});
// Generate Header File
codegenLog('Generating RCTModulesProvider.h');
const templateH = fs.readFileSync(MODULE_PROVIDERS_H_TEMPLATE_PATH, 'utf8');
const finalPathH = path.join(outputDir, 'RCTModuleProviders.h');
fs.writeFileSync(finalPathH, templateH);
codegenLog(`Generated artifact: ${finalPathH}`);
codegenLog('Generating RCTModuleProviders.mm');
let modulesInLibraries /*: {[string]: Array<$FlowFixMe>} */ = {};
let app = pkgJson.codegenConfig
? {config: pkgJson.codegenConfig, libraryPath: projectRoot}
: null;
const moduleLibraries = libraries
.concat(app)
.filter(Boolean)
.filter(({config, libraryPath}) => {
if (
isReactNativeCoreLibrary(config.name) ||
config.type === 'components'
) {
return false;
}
return true;
});
// Old API
moduleLibraries.forEach(({config, libraryPath}) => {
const libraryName = JSON.parse(
fs.readFileSync(path.join(libraryPath, 'package.json'), 'utf8'),
).name;
if (config.ios?.modulesProvider) {
modulesInLibraries[libraryName] = Object.keys(
config.ios?.modulesProvider,
).map(moduleName => {
return {
moduleName,
className: config.ios?.modulesProvider[moduleName],
};
});
}
});
// New API
const iosAnnotations = parseiOSAnnotations(moduleLibraries);
for (const [libraryName, {modules: moduleAnnotationMap}] of Object.entries(
iosAnnotations,
)) {
for (const [moduleName, annotation] of Object.entries(
moduleAnnotationMap,
)) {
if (annotation.className) {
modulesInLibraries[libraryName] = modulesInLibraries[libraryName] || [];
modulesInLibraries[libraryName].push({
moduleName,
className: annotation.className,
});
}
}
}
const modulesMapping = Object.keys(modulesInLibraries)
.flatMap(library => {
const modules = modulesInLibraries[library];
return modules.map(({moduleName, className}) => {
return `\t\t@"${moduleName}": @"${className}", // ${library}`;
});
})
.join('\n');
// Generate implementation file
const templateMM = fs
.readFileSync(MODULE_PROVIDERS_MM_TEMPLATE_PATH, 'utf8')
.replace(/{moduleMapping}/, modulesMapping);
const finalPathMM = path.join(outputDir, 'RCTModuleProviders.mm');
fs.writeFileSync(finalPathMM, templateMM);
codegenLog(`Generated artifact: ${finalPathMM}`);
}
module.exports = {
generateRCTModuleProviders,
};

View File

@@ -0,0 +1,230 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH} = require('./constants');
const {
codegenLog,
isReactNativeCoreLibrary,
parseiOSAnnotations,
} = require('./utils');
const fs = require('fs');
const path = require('path');
const THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTThirdPartyComponentsProviderH.template',
);
const THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'RCTThirdPartyComponentsProviderMM.template',
);
function generateRCTThirdPartyComponents(
libraries /*: $ReadOnlyArray<$FlowFixMe> */,
outputDir /*: string */,
) {
fs.mkdirSync(outputDir, {recursive: true});
// Generate Header File
codegenLog('Generating RCTThirdPartyComponentsProvider.h');
const templateH = fs.readFileSync(
THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH,
'utf8',
);
const finalPathH = path.join(outputDir, 'RCTThirdPartyComponentsProvider.h');
fs.writeFileSync(finalPathH, templateH);
codegenLog(`Generated artifact: ${finalPathH}`);
codegenLog('Generating RCTThirdPartyComponentsProvider.mm');
let componentsInLibraries /*: {[string]: Array<$FlowFixMe>} */ = {};
const componentLibraries = libraries.filter(({config, libraryPath}) => {
if (isReactNativeCoreLibrary(config.name) || config.type === 'modules') {
return false;
}
return true;
});
const librariesToCrawl /*: {[string]: $FlowFixMe} */ = {};
// Using new API explicitly or not using any config field to define components.
const componentLibrariesUsingNewApi = [];
const componentLibrariesUsingOldApi = [];
for (const library of componentLibraries) {
if (
library.config.ios?.components ||
!library.config.ios?.componentProvider
) {
componentLibrariesUsingNewApi.push(library);
} else {
componentLibrariesUsingOldApi.push(library);
}
}
// Old API
componentLibrariesUsingOldApi.forEach(library => {
const {config, libraryPath} = library;
const libraryName = JSON.parse(
fs.readFileSync(path.join(libraryPath, 'package.json'), 'utf8'),
).name;
librariesToCrawl[libraryName] = library;
const componentsProvider = config.ios?.componentProvider;
delete librariesToCrawl[libraryName];
componentsInLibraries[libraryName] =
componentsInLibraries[libraryName] || [];
Object.keys(componentsProvider).forEach(componentName => {
componentsInLibraries[libraryName].push({
componentName,
className: componentsProvider[componentName],
});
});
});
// New API
const iosAnnotations = parseiOSAnnotations(componentLibrariesUsingNewApi);
for (const [libraryName, annotationMap] of Object.entries(iosAnnotations)) {
const {library, components} = annotationMap;
librariesToCrawl[libraryName] = library;
for (const [componentName, annotation] of Object.entries(components)) {
if (annotation.className) {
delete librariesToCrawl[libraryName];
componentsInLibraries[libraryName] =
componentsInLibraries[libraryName] || [];
componentsInLibraries[libraryName].push({
componentName,
className: annotation.className,
});
}
}
}
Object.entries(librariesToCrawl).forEach(([libraryName, library]) => {
const {libraryPath} = library;
codegenLog(`Crawling ${libraryName} library for components`);
// crawl all files and subdirectories for file with the ".mm" extension
const files = findFilesWithExtension(libraryPath, '.mm');
const componentsMapping = files
.flatMap(file => findRCTComponentViewProtocolClass(file))
.filter(Boolean);
if (componentsMapping.length !== 0) {
codegenLog(
`[DEPRECATED] ${libraryName} should add the 'ios.componentProvider' property in their codegenConfig`,
true,
);
}
componentsInLibraries[libraryName] = componentsMapping;
});
const thirdPartyComponentsMapping = Object.keys(componentsInLibraries)
.flatMap(library => {
const components = componentsInLibraries[library];
return components.map(({componentName, className}) => {
return `\t\t@"${componentName}": NSClassFromString(@"${className}"), // ${library}`;
});
})
.join('\n');
// Generate implementation file
const templateMM = fs
.readFileSync(THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH, 'utf8')
.replace(/{thirdPartyComponentsMapping}/, thirdPartyComponentsMapping);
const finalPathMM = path.join(
outputDir,
'RCTThirdPartyComponentsProvider.mm',
);
fs.writeFileSync(finalPathMM, templateMM);
codegenLog(`Generated artifact: ${finalPathMM}`);
}
// Given a path, return the paths of all the files with extension .mm in
// the path dir and all its subdirectories.
function findFilesWithExtension(
filePath /*: string */,
extension /*: string */,
) /*: Array<string> */ {
const files = [];
const dir = fs.readdirSync(filePath);
dir.forEach(file => {
const absolutePath = path.join(filePath, file);
// Exclude files provided by react-native
if (absolutePath.includes(`${path.sep}react-native${path.sep}`)) {
return null;
}
// Skip hidden folders, that starts with `.` but allow `.pnpm`
if (
absolutePath.includes(`${path.sep}.`) &&
!absolutePath.includes(`${path.sep}.pnpm`)
) {
return null;
}
if (
fs.existsSync(absolutePath) &&
fs.statSync(absolutePath).isDirectory()
) {
files.push(...findFilesWithExtension(absolutePath, extension));
} else if (file.endsWith(extension)) {
files.push(absolutePath);
}
});
return files;
}
// Given a filepath, read the file and look for a string that starts with 'Class<RCTComponentViewProtocol> '
// and ends with 'Cls(void)'. Return the string between the two.
function findRCTComponentViewProtocolClass(filepath /*: string */) {
const fileContent = fs.readFileSync(filepath, 'utf8');
const regex = /Class<RCTComponentViewProtocol> (.*)Cls\(/;
const match = fileContent.match(regex);
if (match) {
const componentName = match[1];
// split the file by \n
// remove all the lines before the one that matches the regex above
// find the first return statement after that that ends with .class
// return what's between return and `.class`
const lines = fileContent.split('\n');
const signatureIndex = lines.findIndex(line => regex.test(line));
const returnRegex = /return (.*)\.class/;
const classNameMatch = String(lines.slice(signatureIndex).join('\n')).match(
returnRegex,
);
if (classNameMatch) {
const className = classNameMatch[1];
codegenLog(`Match found ${componentName} -> ${className}`);
return {
componentName,
className,
};
}
console.warn(
`Could not find class name for component ${componentName}. Register it manually`,
);
return null;
}
return null;
}
module.exports = {
generateRCTThirdPartyComponents,
};

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
TEMPLATES_FOLDER_PATH,
packageJson,
} = require('./constants');
const {codegenLog} = require('./utils');
const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
const REACT_CODEGEN_PODSPEC_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'ReactCodegen.podspec.template',
);
function generateReactCodegenPodspec(
appPath /*: string */,
appPkgJson /*: $FlowFixMe */,
outputPath /*: string */,
baseOutputPath /*: string */,
) {
const inputFiles = getInputFiles(appPath, appPkgJson);
const codegenScript = codegenScripts(appPath, baseOutputPath);
const template = fs.readFileSync(REACT_CODEGEN_PODSPEC_TEMPLATE_PATH, 'utf8');
const finalPodspec = template
.replace(/{react-native-version}/, packageJson.version)
.replace(/{input-files}/, inputFiles)
.replace(/{codegen-script}/, codegenScript);
const finalPathPodspec = path.join(outputPath, 'ReactCodegen.podspec');
fs.writeFileSync(finalPathPodspec, finalPodspec);
codegenLog(`Generated podspec: ${finalPathPodspec}`);
}
function getInputFiles(appPath /*: string */, appPkgJson /*: $FlowFixMe */) {
const jsSrcsDir = appPkgJson.codegenConfig?.jsSrcsDir;
if (!jsSrcsDir) {
return '[]';
}
const xcodeproj = String(
execSync(`find ${appPath} -type d -name "*.xcodeproj"`),
)
.trim()
.split('\n')
.filter(
projectPath =>
!projectPath.includes('/Pods/') && // exclude Pods/Pods.xcodeproj
!projectPath.includes('/node_modules/'), // exclude all the xcodeproj in node_modules of libraries
)[0];
if (!xcodeproj) {
throw new Error(
`Cannot find .xcodeproj file inside ${appPath}. This is required to determine codegen spec paths relative to native project.`,
);
}
const jsFiles = '-name "Native*.js" -or -name "*NativeComponent.js"';
const tsFiles = '-name "Native*.ts" -or -name "*NativeComponent.ts"';
const findCommand = `find ${path.join(appPath, jsSrcsDir)} -type f -not -path "*/__mocks__/*" -and \\( ${jsFiles} -or ${tsFiles} \\)`;
const list = String(execSync(findCommand))
.trim()
.split('\n')
.sort()
.map(filepath => `"\${PODS_ROOT}/${path.relative(xcodeproj, filepath)}"`)
.join(',\n');
return `[${list}]`;
}
function codegenScripts(appPath /*: string */, baseOutputPath /*: string */) {
const relativeAppPath = path.relative(baseOutputPath, appPath);
const relativeReactNativeRootFolder = path.relative(
baseOutputPath,
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
);
return `<<-SCRIPT
pushd "$PODS_ROOT/../" > /dev/null
RCT_SCRIPT_POD_INSTALLATION_ROOT=$(pwd)
popd >/dev/null
export RCT_SCRIPT_RN_DIR="$RCT_SCRIPT_POD_INSTALLATION_ROOT/${relativeReactNativeRootFolder}"
export RCT_SCRIPT_APP_PATH="$RCT_SCRIPT_POD_INSTALLATION_ROOT/${relativeAppPath.length === 0 ? '.' : relativeAppPath}"
export RCT_SCRIPT_OUTPUT_DIR="$RCT_SCRIPT_POD_INSTALLATION_ROOT"
export RCT_SCRIPT_TYPE="withCodegenDiscovery"
export SCRIPT_PHASES_SCRIPT="$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh"
export WITH_ENVIRONMENT="$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh"
/bin/sh -c '"$WITH_ENVIRONMENT" "$SCRIPT_PHASES_SCRIPT"'
SCRIPT`;
}
module.exports = {
generateReactCodegenPodspec,
};

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const CodegenUtils = require('../codegen-utils');
const {codegenLog} = require('./utils');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
function generateSchemaInfos(
libraries /*: $ReadOnlyArray<$FlowFixMe> */,
) /*: Array<$FlowFixMe> */ {
// $FlowFixMe[incompatible-type]
return libraries.map(generateSchemaInfo);
}
function generateSchemaInfo(
library /*: $FlowFixMe */,
platform /*: string */,
) /*: $FlowFixMe */ {
const pathToJavaScriptSources = path.join(
library.libraryPath,
library.config.jsSrcsDir,
);
codegenLog(`Processing ${library.config.name}`);
const supportedApplePlatforms = extractSupportedApplePlatforms(
library.config.name,
library.libraryPath,
);
// Generate one schema for the entire library...
return {
library: library,
supportedApplePlatforms,
schema: CodegenUtils.getCombineJSToSchema().combineSchemasInFileList(
[pathToJavaScriptSources],
platform,
/NativeSampleTurboModule/,
),
};
}
const APPLE_PLATFORMS = ['ios', 'macos', 'tvos', 'visionos'];
function extractSupportedApplePlatforms(
dependency /*: string */,
dependencyPath /*: string */,
) /*: ?{[string]: boolean} */ {
codegenLog('Searching for podspec in the project dependencies.', true);
const podspecs = glob.sync('*.podspec', {cwd: dependencyPath});
if (podspecs.length === 0) {
return;
}
// Take the first podspec found
const podspec = fs.readFileSync(
path.join(dependencyPath, podspecs[0]),
'utf8',
);
/**
* Podspec can have platforms defined in two ways:
* 1. `spec.platforms = { :ios => "11.0", :tvos => "11.0" }`
* 2. `s.ios.deployment_target = "11.0"`
* `s.tvos.deployment_target = "11.0"`
*/
const supportedPlatforms = podspec
.split('\n')
.filter(
line => line.includes('platform') || line.includes('deployment_target'),
)
.join('');
// Generate a map of supported platforms { [platform]: true/false }
const supportedPlatformsMap = APPLE_PLATFORMS.reduce(
(acc, platform) => ({
...acc,
[platform]: supportedPlatforms.includes(
getCocoaPodsPlatformKey(platform),
),
}),
{} /*:: as {[string]: boolean} */,
);
const supportedPlatformsList = Object.keys(supportedPlatformsMap).filter(
key => supportedPlatformsMap[key],
);
if (supportedPlatformsList.length > 0) {
codegenLog(
`Supported Apple platforms: ${supportedPlatformsList.join(
', ',
)} for ${dependency}`,
);
}
return supportedPlatformsMap;
}
// Cocoapods specific platform keys
function getCocoaPodsPlatformKey(platformName /*: string */) {
if (platformName === 'macos') {
return 'osx';
}
return platformName;
}
module.exports = {
generateSchemaInfos,
generateSchemaInfo,
extractSupportedApplePlatforms,
};

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH} = require('./constants');
const {parseiOSAnnotations} = require('./utils');
const fs = require('fs');
const path = require('path');
const UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_H_TEMPLATE_PATH =
path.join(
TEMPLATES_FOLDER_PATH,
'RCTUnstableModulesRequiringMainQueueSetupProviderH.template',
);
const UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_MM_TEMPLATE_PATH =
path.join(
TEMPLATES_FOLDER_PATH,
'RCTUnstableModulesRequiringMainQueueSetupProviderMM.template',
);
function generateUnstableModulesRequiringMainQueueSetupProvider(
libraries /*: $ReadOnlyArray<$FlowFixMe> */,
outputDir /*: string */,
) {
const iosAnnotations = parseiOSAnnotations(libraries);
const modulesRequiringMainQueueSetup = new Set /*::<string>*/();
// Old API
libraries.forEach(library => {
const {unstableModulesRequiringMainQueueSetup} = library?.config?.ios || {};
if (!unstableModulesRequiringMainQueueSetup) {
return;
}
for (const moduleName of unstableModulesRequiringMainQueueSetup) {
modulesRequiringMainQueueSetup.add(moduleName);
}
});
// New API
for (const {modules: moduleAnnotationMap} of Object.values(iosAnnotations)) {
for (const [moduleName, annotation] of Object.entries(
moduleAnnotationMap,
)) {
if (annotation.unstableRequiresMainQueueSetup) {
modulesRequiringMainQueueSetup.add(moduleName);
}
}
}
const modulesStr = Array.from(modulesRequiringMainQueueSetup)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const template = fs.readFileSync(
UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_MM_TEMPLATE_PATH,
'utf8',
);
const finalMMFile = template.replace(/{modules}/, modulesStr);
fs.mkdirSync(outputDir, {recursive: true});
fs.writeFileSync(
path.join(
outputDir,
'RCTUnstableModulesRequiringMainQueueSetupProvider.mm',
),
finalMMFile,
);
const templateH = fs.readFileSync(
UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_H_TEMPLATE_PATH,
'utf8',
);
fs.writeFileSync(
path.join(outputDir, 'RCTUnstableModulesRequiringMainQueueSetupProvider.h'),
templateH,
);
}
module.exports = {
generateUnstableModulesRequiringMainQueueSetupProvider,
};

View File

@@ -0,0 +1,290 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
/**
* This script crawls through a React Native application's dependencies and invokes the codegen
* for any libraries that require it.
* To enable codegen support, the library should include a config in the codegenConfig key
* in a package.json file.
*/
const {
generateAppDependencyProvider,
} = require('./generateAppDependencyProvider');
const {generateCustomURLHandlers} = require('./generateCustomURLHandlers');
const {generateNativeCode} = require('./generateNativeCode');
const {generatePackageSwift} = require('./generatePackageSwift');
const {generateRCTModuleProviders} = require('./generateRCTModuleProviders');
const {
generateRCTThirdPartyComponents,
} = require('./generateRCTThirdPartyComponents');
const {generateReactCodegenPodspec} = require('./generateReactCodegenPodspec');
const {generateSchemaInfos} = require('./generateSchemaInfos');
const {
generateUnstableModulesRequiringMainQueueSetupProvider,
} = require('./generateUnstableModulesRequiringMainQueueSetupProvider');
const {
buildCodegenIfNeeded,
cleanupEmptyFilesAndFolders,
codegenLog,
findCodegenEnabledLibraries,
findDisabledLibrariesByPlatform,
findReactNativeRootPath,
pkgJsonIncludesGeneratedCode,
readPkgJsonInDirectory,
readReactNativeConfig,
} = require('./utils');
const path = require('path');
/**
* This function is the entry point for the codegen. It:
* - reads the package json
* - extracts the libraries
* - setups the CLI to generate the code
* - generate the code
*
* @parameter projectRoot: the directory with the app source code, where the package.json lives.
* @parameter baseOutputPath: the base output path for the CodeGen.
* @parameter targetPlatform: the target platform. Supported values: 'android', 'ios', 'all'.
* @parameter source: the source that is invoking codegen. Supported values: 'app', 'library'.
* @throws If it can't find a config file for react-native.
* @throws If it can't find a CodeGen configuration in the file.
* @throws If it can't find a cli for the CodeGen.
*/
function execute(
projectRoot /*: string */,
targetPlatform /*: string */,
optionalBaseOutputPath /*: ?string */,
source /*: string */,
runReactNativeCodegen /*: boolean */ = true,
) {
try {
codegenLog(`Analyzing ${path.join(projectRoot, 'package.json')}`);
const supportedPlatforms = ['android', 'ios'];
if (
targetPlatform !== 'all' &&
!supportedPlatforms.includes(targetPlatform)
) {
throw new Error(
`Invalid target platform: ${targetPlatform}. Supported values are: ${supportedPlatforms.join(
', ',
)}, all`,
);
}
const pkgJson = readPkgJsonInDirectory(projectRoot);
if (runReactNativeCodegen) {
buildCodegenIfNeeded();
}
const platforms =
targetPlatform === 'all' ? supportedPlatforms : [targetPlatform];
// NOTE: We cache the external libraries search (which may not run) across platforms to not change previous behaviour
const externalLibrariesCache /*: { current?: ?Array<$FlowFixMe> } */ = {};
for (const platform of platforms) {
// NOTE: This needs to be computed per-platform since `platform` can alter the path via a `package.json:codegenConfig.outputDir[platform]` override
const baseOutputPath = computeBaseOutputPath(
projectRoot,
optionalBaseOutputPath,
pkgJson,
platform,
);
const reactNativeConfig = readReactNativeConfig(
projectRoot,
baseOutputPath,
);
const codegenEnabledLibraries = findCodegenEnabledLibraries(
pkgJson,
projectRoot,
baseOutputPath,
reactNativeConfig,
externalLibrariesCache,
);
if (codegenEnabledLibraries.length === 0) {
codegenLog('No codegen-enabled libraries found.', true);
}
const disabledLibraries = findDisabledLibrariesByPlatform(
reactNativeConfig,
platform,
);
const libraries = codegenEnabledLibraries.filter(
({name}) => !disabledLibraries.includes(name),
);
const outputPath = computeOutputPath(
projectRoot,
baseOutputPath,
pkgJson,
platform,
);
const reactCodegenOutputPath =
platform === 'android'
? outputPath
: path.join(outputPath, 'ReactCodegen');
if (runReactNativeCodegen) {
const schemaInfos = generateSchemaInfos(libraries);
generateNativeCode(
reactCodegenOutputPath,
schemaInfos.filter(schemaInfo =>
mustGenerateNativeCode(projectRoot, schemaInfo),
),
pkgJsonIncludesGeneratedCode(pkgJson),
platform,
);
}
if (source === 'app' && platform !== 'android') {
// These components are only required by apps, not by libraries and are Apple specific.
generateRCTThirdPartyComponents(libraries, reactCodegenOutputPath);
generateRCTModuleProviders(
projectRoot,
pkgJson,
libraries,
reactCodegenOutputPath,
);
generateCustomURLHandlers(libraries, reactCodegenOutputPath);
generateUnstableModulesRequiringMainQueueSetupProvider(
libraries,
reactCodegenOutputPath,
);
generateAppDependencyProvider(
path.join(outputPath, 'ReactAppDependencyProvider'),
);
generateReactCodegenPodspec(
projectRoot,
pkgJson,
reactCodegenOutputPath,
baseOutputPath,
);
generatePackageSwift(
projectRoot,
outputPath,
findReactNativeRootPath(projectRoot),
);
}
cleanupEmptyFilesAndFolders(outputPath);
}
} catch (err) {
codegenLog(err);
process.exitCode = 1;
}
codegenLog('Done.', true);
return;
}
function readOutputDirFromPkgJson(
pkgJson /*: $FlowFixMe */,
platform /*: string */,
) {
const codegenConfig = pkgJson.codegenConfig;
if (codegenConfig == null || typeof codegenConfig !== 'object') {
return null;
}
const outputDir = codegenConfig.outputDir;
if (outputDir == null) {
return null;
}
if (typeof outputDir === 'string') {
return outputDir;
}
if (typeof outputDir === 'object') {
return outputDir[platform];
}
return null;
}
function computeBaseOutputPath(
projectRoot /*: string */,
optionalBaseOutputPath /*: ?string */,
pkgJson /*: $FlowFixMe */,
platform /*: string */,
) {
if (
process.env.RCT_SCRIPT_OUTPUT_DIR != null &&
process.env.RCT_SCRIPT_OUTPUT_DIR.length > 0
) {
return process.env.RCT_SCRIPT_OUTPUT_DIR;
}
let baseOutputPath /*: string */;
if (optionalBaseOutputPath == null) {
const outputDirFromPkgJson = readOutputDirFromPkgJson(pkgJson, platform);
if (outputDirFromPkgJson != null) {
baseOutputPath = path.join(projectRoot, outputDirFromPkgJson);
} else {
baseOutputPath = projectRoot;
}
} else {
baseOutputPath = optionalBaseOutputPath;
}
return baseOutputPath;
}
function computeOutputPath(
projectRoot /*: string */,
baseOutputPath /*: string */,
pkgJson /*: $FlowFixMe */,
platform /*: string */,
) /*: string */ {
if (pkgJsonIncludesGeneratedCode(pkgJson)) {
// Don't create nested directories for libraries to make importing generated headers easier.
return baseOutputPath;
}
if (platform === 'android') {
return defaultOutputPathForAndroid(baseOutputPath);
}
if (platform === 'ios') {
return defaultOutputPathForIOS(baseOutputPath);
}
return baseOutputPath;
}
function defaultOutputPathForAndroid(baseOutputPath /*: string */) {
return path.join(
baseOutputPath,
'android',
'app',
'build',
'generated',
'source',
'codegen',
);
}
function defaultOutputPathForIOS(baseOutputPath /*: string */) {
return path.join(baseOutputPath, 'build', 'generated', 'ios');
}
function mustGenerateNativeCode(
includeLibraryPath /*: string */,
schemaInfo /*: $FlowFixMe */,
) {
// If library's 'codegenConfig' sets 'includesGeneratedCode' to 'true',
// then we assume that native code is shipped with the library,
// and we don't need to generate it.
return (
schemaInfo.library.libraryPath === includeLibraryPath ||
!schemaInfo.library.config.includesGeneratedCode
);
}
module.exports = {
execute,
};

View File

@@ -0,0 +1,502 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {
CODEGEN_REPO_PATH,
CORE_LIBRARIES_WITH_OUTPUT_FOLDER,
REACT_NATIVE,
} = require('./constants');
const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
function pkgJsonIncludesGeneratedCode(
pkgJson /*: $FlowFixMe */,
) /*: boolean */ {
return pkgJson.codegenConfig && pkgJson.codegenConfig.includesGeneratedCode;
}
const codegenLog = (text /*: string */, info /*: boolean */ = false) => {
// ANSI escape codes for colors and formatting
const reset = '\x1b[0m';
const cyan = '\x1b[36m';
const yellow = '\x1b[33m';
const bold = '\x1b[1m';
const color = info ? yellow : '';
console.log(`${cyan}${bold}[Codegen]${reset} ${color}${text}${reset}`);
};
function readPkgJsonInDirectory(dir /*: string */) /*: $FlowFixMe */ {
const pkgJsonPath = path.join(dir, 'package.json');
if (!fs.existsSync(pkgJsonPath)) {
throw new Error(`[Codegen] Error: ${pkgJsonPath} does not exist.`);
}
return JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
}
function buildCodegenIfNeeded() {
if (!fs.existsSync(CODEGEN_REPO_PATH)) {
return;
}
// Assuming we are working in the react-native repo. We might need to build the codegen.
// This will become unnecessary once we start using Babel Register for the codegen package.
const libPath = path.join(CODEGEN_REPO_PATH, 'lib');
if (fs.existsSync(libPath) && fs.readdirSync(libPath).length > 0) {
return;
}
codegenLog('Building react-native-codegen package.', true);
execSync('yarn install', {
cwd: CODEGEN_REPO_PATH,
stdio: 'inherit',
});
execSync('yarn build', {
cwd: CODEGEN_REPO_PATH,
stdio: 'inherit',
});
}
// It removes all the empty files and empty folders
// it finds, starting from `filepath`, recursively.
//
// This function is needed since, after aligning the codegen between
// iOS and Android, we have to create empty folders in advance and
// we don't know whether they will be populated up until the end of the process.
//
// @parameter filepath: the root path from which we want to remove the empty files and folders.
function cleanupEmptyFilesAndFolders(filepath /*: string */) {
const stats = fs.statSync(filepath);
if (stats.isFile() && stats.size === 0) {
fs.rmSync(filepath);
return;
} else if (stats.isFile()) {
return;
}
const dirContent = fs.readdirSync(filepath);
dirContent.forEach(contentPath =>
cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)),
);
// The original folder may be filled with empty folders
// if that the case, we would also like to remove the parent.
// Hence, we need to read the folder again.
const newContent = fs.readdirSync(filepath);
if (newContent.length === 0) {
fs.rmdirSync(filepath);
return;
}
}
function readGeneratedAutolinkingOutput(
projectRoot /*: string */,
baseOutputPath /*: string */,
) /*: $FlowFixMe */ {
// NOTE: Generated by scripts/cocoapods/autolinking.rb in list_native_modules (called by use_native_modules)
// The `baseOutputPath` is based on a CLI argument and optional
const autolinkingGeneratedPath = path.resolve(
baseOutputPath,
'build/generated/autolinking/autolinking.json',
);
if (fs.existsSync(autolinkingGeneratedPath)) {
// $FlowFixMe[unsupported-syntax]
return require(autolinkingGeneratedPath);
} else {
codegenLog(
`Could not find generated autolinking output at: ${autolinkingGeneratedPath}`,
);
return null;
}
}
function readReactNativeConfig(
projectRoot /*: string */,
baseOutputPath /*: string */,
) /*: $FlowFixMe */ {
const autolinkingOutput = readGeneratedAutolinkingOutput(
projectRoot,
baseOutputPath,
);
const rnConfigFilePath = path.resolve(projectRoot, 'react-native.config.js');
if (autolinkingOutput) {
return autolinkingOutput;
} else if (fs.existsSync(rnConfigFilePath)) {
// $FlowFixMe[unsupported-syntax]
return require(rnConfigFilePath);
} else {
codegenLog(`Could not find React Native config at: ${rnConfigFilePath}`);
return {};
}
}
/**
* Finding libraries!
*/
function findCodegenEnabledLibraries(
pkgJson /*: $FlowFixMe */,
projectRoot /*: string */,
baseOutputPath /*: string */,
reactNativeConfig /*: $FlowFixMe */,
externalLibrariesCache /*: { current?: ?Array<$FlowFixMe> } */ = {},
) /*: Array<$FlowFixMe> */ {
const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot);
if (pkgJsonIncludesGeneratedCode(pkgJson)) {
return projectLibraries;
} else {
const libraries = [...projectLibraries];
// If we ran autolinking, we shouldn't try to run our own "autolinking-like"
// library discovery
if (!readGeneratedAutolinkingOutput(projectRoot, baseOutputPath)) {
const externalLibraries =
externalLibrariesCache.current ??
(externalLibrariesCache.current = findExternalLibraries(
pkgJson,
projectRoot,
));
libraries.push(...externalLibraries);
}
libraries.push(
...findLibrariesFromReactNativeConfig(projectRoot, reactNativeConfig),
);
return libraries;
}
}
function findProjectRootLibraries(
pkgJson /*: $FlowFixMe */,
projectRoot /*: string */,
) /*: Array<$FlowFixMe> */ {
codegenLog('Searching for codegen-enabled libraries in the app.', true);
if (pkgJson.codegenConfig == null) {
codegenLog(
'The "codegenConfig" field is not defined in package.json. Assuming there is nothing to generate at the app level.',
true,
);
return [];
}
if (typeof pkgJson.codegenConfig !== 'object') {
throw new Error('The "codegenConfig" field must be an Object.');
}
return extractLibrariesFromJSON(pkgJson, projectRoot);
}
function findLibrariesFromReactNativeConfig(
projectRoot /*: string */,
rnConfig /*: $FlowFixMe */,
) /*: Array<$FlowFixMe> */ {
codegenLog(
`Searching for codegen-enabled libraries in react-native.config.js`,
true,
);
if (!rnConfig.dependencies) {
return [];
}
return Object.keys(rnConfig.dependencies).flatMap(name => {
const dependencyConfig = rnConfig.dependencies[name];
if (!dependencyConfig.root) {
return [];
}
const codegenConfigFileDir = path.resolve(
projectRoot,
dependencyConfig.root,
);
let configFile;
try {
configFile = readPkgJsonInDirectory(codegenConfigFileDir);
} catch {
return [];
}
return extractLibrariesFromJSON(configFile, codegenConfigFileDir);
});
}
function findExternalLibraries(
pkgJson /*: $FlowFixMe */,
projectRoot /*: string */,
) /*: Array<$FlowFixMe> */ {
const dependencies = {
...pkgJson.dependencies,
...pkgJson.devDependencies,
...pkgJson.peerDependencies,
};
// Determine which of these are codegen-enabled libraries
codegenLog(
'Searching for codegen-enabled libraries in the project dependencies.',
true,
);
// Handle third-party libraries
return Object.keys(dependencies).flatMap(dependency => {
let configFilePath = '';
try {
configFilePath = require.resolve(path.join(dependency, 'package.json'), {
paths: [projectRoot],
});
} catch (e) {
// require.resolve fails if the dependency is a local node module.
if (
// require.resolve fails if the `./package.json` subpath is not explicitly defined in the library's `exports` field in its package.json
'code' in e &&
e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED'
) {
// find the closest library's package.json with the search paths
// $FlowFixMe[prop-missing]
const paths /*: Array<string>*/ = require.main.paths;
for (const nodeModulesPath of paths) {
const packageJsonFilePath = path.join(
nodeModulesPath,
dependency,
'package.json',
);
if (fs.existsSync(packageJsonFilePath)) {
configFilePath = packageJsonFilePath;
break;
}
}
} else if (
// require.resolve fails if the dependency is a local node module.
dependencies[dependency].startsWith('.') || // handles relative paths
dependencies[dependency].startsWith('/') // handles absolute paths
) {
configFilePath = path.join(
projectRoot,
pkgJson.dependencies[dependency],
'package.json',
);
}
if (!configFilePath) {
return [];
}
}
const configFile = JSON.parse(fs.readFileSync(configFilePath, 'utf8'));
const codegenConfigFileDir = path.dirname(configFilePath);
return extractLibrariesFromJSON(configFile, codegenConfigFileDir);
});
}
function extractLibrariesFromJSON(
configFile /*: $FlowFixMe */,
dependencyPath /*: string */,
) /*: Array<$FlowFixMe> */ {
if (configFile.codegenConfig == null) {
return [];
}
codegenLog(`Found ${configFile.name}`);
if (configFile.codegenConfig.libraries == null) {
const config = configFile.codegenConfig;
return [
{
name: configFile.name,
config,
libraryPath: dependencyPath,
},
];
} else {
printDeprecationWarningIfNeeded(configFile.name);
return extractLibrariesFromConfigurationArray(configFile, dependencyPath);
}
}
function printDeprecationWarningIfNeeded(dependency /*: string */) {
if (dependency === REACT_NATIVE) {
return;
}
codegenLog(`CodegenConfig Deprecated Setup for ${dependency}.
The configuration file still contains the codegen in the libraries array.
If possible, replace it with a single object.
`);
codegenLog(`BEFORE:
{
// ...
"codegenConfig": {
"libraries": [
{
"name": "libName1",
"type": "all|components|modules",
"jsSrcsRoot": "libName1/js"
},
{
"name": "libName2",
"type": "all|components|modules",
"jsSrcsRoot": "libName2/src"
}
]
}
}
AFTER:
{
"codegenConfig": {
"name": "libraries",
"type": "all",
"jsSrcsRoot": "."
}
}
`);
}
function extractLibrariesFromConfigurationArray(
configFile /*: $FlowFixMe */,
dependencyPath /*: string */,
) {
return configFile.codegenConfig.libraries.map(config => {
return {
name: config.name,
config,
libraryPath: dependencyPath,
};
});
}
function isReactNativeCoreLibrary(libraryName /*: string */) /*: boolean */ {
return libraryName in CORE_LIBRARIES_WITH_OUTPUT_FOLDER;
}
/**
* Returns a map of this shape:
* {
* "libraryName": {
* "library": { ... }
* "modules": {
* "moduleName": {
* "conformsToProtocols": [ "protocol1", "protocol2" ],
* "className": "RCTFooModuler",
* }
* },
* "components": {
* "componentName": {
* "className": "RCTFooComponent",
* }
* }
* }
* }
*
* Validates that modules are defined in at most one library.
* Validates that components are defined in at most one library.
*/
function parseiOSAnnotations(
libraries /*: $ReadOnlyArray<$FlowFixMe> */,
) /*: {[string]: $FlowFixMe} */ {
const mLibraryMap /*: {[string]: $FlowFixMe} */ = {};
const cLibraryMap /*: {[string]: $FlowFixMe} */ = {};
const map = {};
for (const library of libraries) {
const iosConfig = library?.config?.ios;
if (!iosConfig) {
continue;
}
const libraryName = getLibraryName(library);
map[libraryName] = map[libraryName] || {
library,
modules: {},
components: {},
};
const {modules, components} = iosConfig;
if (modules) {
for (const [moduleName, annotation] of Object.entries(modules)) {
mLibraryMap[moduleName] = mLibraryMap[moduleName] || new Set();
mLibraryMap[moduleName].add(libraryName);
map[libraryName].modules[moduleName] = {...annotation};
}
}
if (components) {
for (const [moduleName, annotation] of Object.entries(components)) {
cLibraryMap[moduleName] = cLibraryMap[moduleName] || new Set();
cLibraryMap[moduleName].add(libraryName);
map[libraryName].components[moduleName] = {...annotation};
}
}
}
const moduleConflicts = Object.entries(mLibraryMap)
.filter(([_, libraryNames]) => libraryNames.size > 1)
.map(([moduleName, libraryNames]) => {
const libraryNamesString = Array.from(libraryNames).join(', ');
return ` Module { "${moduleName}" } => Libraries{ ${libraryNamesString} }\n`;
});
const componentConflicts = Object.entries(cLibraryMap)
.filter(([_, libraryNames]) => libraryNames.size > 1)
.map(([moduleName, libraryNames]) => {
const libraryNamesString = Array.from(libraryNames).join(', ');
return ` Component { "${moduleName}" } => Libraries{ ${libraryNamesString} }\n`;
});
if (moduleConflicts.length > 0 || componentConflicts.length > 0) {
throw new Error(
'Some components or modules are declared in more than one libraries: \n' +
[...moduleConflicts, ...componentConflicts].join('\n'),
);
}
return map;
}
function getLibraryName(library /*: $FlowFixMe */) {
return JSON.parse(
fs.readFileSync(path.join(library.libraryPath, 'package.json'), 'utf8'),
).name;
}
/**
* Finds all disabled libraries by platform based the react native config.
*
* This is needed when selectively disabling libraries in react-native.config.js since codegen should exclude those libraries as well.
*/
function findDisabledLibrariesByPlatform(
reactNativeConfig /*: $FlowFixMe */,
platform /*: string */,
) /*: Array<$FlowFixMe> */ {
const dependencies = reactNativeConfig.dependencies ?? {};
return Object.keys(dependencies).filter(
dependency => dependencies[dependency].platforms?.[platform] === null,
);
}
function findReactNativeRootPath(projectRoot /* : string */) /* : string */ {
const reactNativePackageJsonPath = require.resolve(
path.join('react-native', 'package.json'),
{
paths: [projectRoot],
},
);
return path.dirname(reactNativePackageJsonPath);
}
module.exports = {
buildCodegenIfNeeded,
pkgJsonIncludesGeneratedCode,
codegenLog,
readPkgJsonInDirectory,
isReactNativeCoreLibrary,
cleanupEmptyFilesAndFolders,
findCodegenEnabledLibraries,
findProjectRootLibraries,
extractLibrariesFromJSON,
parseiOSAnnotations,
readReactNativeConfig,
findDisabledLibrariesByPlatform,
findReactNativeRootPath,
};

View File

@@ -0,0 +1,137 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const utils = require('./codegen-utils');
const fs = require('fs');
const path = require('path');
const GENERATORS /*: {[string]: {[string]: $ReadOnlyArray<string>}} */ = {
all: {
android: ['componentsAndroid', 'modulesAndroid', 'modulesCxx'],
ios: ['componentsIOS', 'modulesIOS', 'modulesCxx'],
},
components: {
android: ['componentsAndroid'],
ios: ['componentsIOS'],
},
modules: {
android: ['modulesAndroid', 'modulesCxx'],
ios: ['modulesIOS', 'modulesCxx'],
},
};
function createOutputDirectoryIfNeeded(
outputDirectory /*: string */,
libraryName /*: string */,
) {
if (!outputDirectory) {
// $FlowFixMe[reassign-const]
outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName);
}
fs.mkdirSync(outputDirectory, {recursive: true});
}
/**
* This function read a JSON schema from a path and parses it.
* It throws if the schema don't exists or it can't be parsed.
*
* @parameter schemaPath: the path to the schema
* @return a valid schema
* @throw an Error if the schema doesn't exists in a given path or if it can't be parsed.
*/
function readAndParseSchema(schemaPath /*: string */) {
const schemaText = fs.readFileSync(schemaPath, 'utf-8');
if (schemaText == null) {
throw new Error(`Can't find schema at ${schemaPath}`);
}
try {
return JSON.parse(schemaText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
}
}
function validateLibraryType(libraryType /*: string */) {
if (GENERATORS[libraryType] == null) {
throw new Error(`Invalid library type. ${libraryType}`);
}
}
function generateSpecFromInMemorySchema(
platform /*: string */,
schema /*: string */,
outputDirectory /*: string */,
libraryName /*: string */,
packageName /*: string */,
libraryType /*: string */,
useLocalIncludePaths /*: boolean */,
) {
validateLibraryType(libraryType);
createOutputDirectoryIfNeeded(outputDirectory, libraryName);
const includeGetDebugPropsImplementation =
libraryName.includes('FBReactNativeSpec'); //only generate getDebugString for React Native Core Components
utils.getCodegen().generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull: platform === 'ios',
useLocalIncludePaths,
includeGetDebugPropsImplementation,
},
{
generators: GENERATORS[libraryType][platform],
},
);
if (platform === 'android') {
// Move all components C++ files to a structured jni folder for now.
// Note: this should've been done by RNCodegen's generators, but:
// * the generators don't support platform option yet
// * this subdir structure is Android-only, not applicable to iOS
const files = fs.readdirSync(outputDirectory);
const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`;
fs.mkdirSync(jniOutputDirectory, {recursive: true});
files
.filter(f => f.endsWith('.h') || f.endsWith('.cpp'))
.forEach(f => {
fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`);
});
}
}
function generateSpec(
platform /*: string */,
schemaPath /*: string */,
outputDirectory /*: string */,
libraryName /*: string */,
packageName /*: string */,
libraryType /*: string */,
) {
// $FlowFixMe[incompatible-type]
generateSpecFromInMemorySchema(
platform,
readAndParseSchema(schemaPath),
outputDirectory,
libraryName,
packageName,
libraryType,
);
}
module.exports = {
execute: generateSpec,
generateSpecFromInMemorySchema,
};

View File

@@ -0,0 +1,59 @@
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "React-GeneratedCode",
platforms: [.iOS(.v15), .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "ReactCodegen",
targets: ["ReactCodegen"]),
.library(
name: "ReactAppDependencyProvider",
targets: ["ReactAppDependencyProvider"]),
],
dependencies: [
.package(name: "React", path: "{reactNativePath}")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "ReactCodegen",
dependencies: ["React"],
path: "ReactCodegen",
exclude: ["ReactCodegen.podspec"],
publicHeadersPath: ".",
cSettings: [
.headerSearchPath("headers")
],
cxxSettings: [
.headerSearchPath("headers"),
.unsafeFlags(["-std=c++20"]),
],
linkerSettings: [
.linkedFramework("Foundation")
]
),
.target(
name: "ReactAppDependencyProvider",
dependencies: ["ReactCodegen"],
path: "ReactAppDependencyProvider",
exclude: ["ReactAppDependencyProvider.podspec"],
publicHeadersPath: ".",
cSettings: [
.headerSearchPath("headers"),
],
cxxSettings: [
.headerSearchPath("headers"),
.unsafeFlags(["-std=c++20"]),
],
linkerSettings: [
.linkedFramework("Foundation")
]
)
]
)

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#if __has_include(<React-RCTAppDelegate/RCTDependencyProvider.h>)
#import <React-RCTAppDelegate/RCTDependencyProvider.h>
#elif __has_include(<React_RCTAppDelegate/RCTDependencyProvider.h>)
#import <React_RCTAppDelegate/RCTDependencyProvider.h>
#else
#import "RCTDependencyProvider.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface RCTAppDependencyProvider : NSObject <RCTDependencyProvider>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTAppDependencyProvider.h"
#import <ReactCodegen/RCTModulesConformingToProtocolsProvider.h>
#import <ReactCodegen/RCTThirdPartyComponentsProvider.h>
#import <ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.h>
#import <ReactCodegen/RCTModuleProviders.h>
@implementation RCTAppDependencyProvider
- (nonnull NSArray<NSString *> *)URLRequestHandlerClassNames {
return RCTModulesConformingToProtocolsProvider.URLRequestHandlerClassNames;
}
- (nonnull NSArray<NSString *> *)imageDataDecoderClassNames {
return RCTModulesConformingToProtocolsProvider.imageDataDecoderClassNames;
}
- (nonnull NSArray<NSString *> *)imageURLLoaderClassNames {
return RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames;
}
- (nonnull NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup {
return RCTUnstableModulesRequiringMainQueueSetupProvider.modules;
}
- (nonnull NSDictionary<NSString *,Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents {
return RCTThirdPartyComponentsProvider.thirdPartyFabricComponents;
}
- (nonnull NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders {
return RCTModuleProviders.moduleProviders;
}
@end

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTModuleProvider;
@interface RCTModuleProviders: NSObject
+ (NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders;
@end

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTModuleProviders.h"
#import <ReactCommon/RCTTurboModule.h>
#import <React/RCTLog.h>
@implementation RCTModuleProviders
+ (NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders
{
static NSDictionary<NSString *, id<RCTModuleProvider>> *providers = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary<NSString *, NSString *> * moduleMapping = @{
{moduleMapping}
};
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:moduleMapping.count];
for (NSString *key in moduleMapping) {
NSString * moduleProviderName = moduleMapping[key];
Class klass = NSClassFromString(moduleProviderName);
if (!klass) {
RCTLogError(@"Module provider %@ cannot be found in the runtime", moduleProviderName);
continue;
}
id instance = [klass new];
if (![instance respondsToSelector:@selector(getTurboModule:)]) {
RCTLogError(@"Module provider %@ does not conform to RCTModuleProvider", moduleProviderName);
continue;
}
[dict setObject:instance forKey:key];
}
providers = dict;
});
return providers;
}
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface RCTModulesConformingToProtocolsProvider: NSObject
+(NSArray<NSString *> *)imageURLLoaderClassNames;
+(NSArray<NSString *> *)imageDataDecoderClassNames;
+(NSArray<NSString *> *)URLRequestHandlerClassNames;
@end

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTModulesConformingToProtocolsProvider.h"
@implementation RCTModulesConformingToProtocolsProvider
+(NSArray<NSString *> *)imageURLLoaderClassNames
{
static NSArray<NSString *> *classNames = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classNames = @[
{imageURLLoaderClassNames}
];
});
return classNames;
}
+(NSArray<NSString *> *)imageDataDecoderClassNames
{
static NSArray<NSString *> *classNames = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classNames = @[
{imageDataDecoderClassNames}
];
});
return classNames;
}
+(NSArray<NSString *> *)URLRequestHandlerClassNames
{
static NSArray<NSString *> *classNames = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classNames = @[
{requestHandlersClassNames}
];
});
return classNames;
}
@end

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTComponentViewProtocol;
@interface RCTThirdPartyComponentsProvider: NSObject
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
@end

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTThirdPartyComponentsProvider.h"
#import <React/RCTComponentViewProtocol.h>
@implementation RCTThirdPartyComponentsProvider
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
static NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *thirdPartyComponents = nil;
static dispatch_once_t nativeComponentsToken;
dispatch_once(&nativeComponentsToken, ^{
thirdPartyComponents = @{
{thirdPartyComponentsMapping}
};
});
return thirdPartyComponents;
}
@end

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface RCTUnstableModulesRequiringMainQueueSetupProvider: NSObject
+(NSArray<NSString *> *)modules;
@end

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTUnstableModulesRequiringMainQueueSetupProvider.h"
@implementation RCTUnstableModulesRequiringMainQueueSetupProvider
+(NSArray<NSString *> *)modules
{
return @[
{modules}
];
}
@end

View File

@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
version = "{react-native-version}"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "ReactAppDependencyProvider"
s.version = version
s.summary = "The third party dependency provider for the app"
s.homepage = "https://reactnative.dev/"
s.documentation_url = "https://reactnative.dev/"
s.license = "{react-native-licence}"
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "**/RCTAppDependencyProvider.{h,mm}"
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES"
}
s.dependency "ReactCodegen"
end

View File

@@ -0,0 +1,97 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
version = "{react-native-version}"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
use_frameworks = ENV['USE_FRAMEWORKS'] != nil
folly_compiler_flags = Helpers::Constants.folly_config[:compiler_flags]
boost_compiler_flags = Helpers::Constants.boost_config[:compiler_flags]
header_search_paths = []
framework_search_paths = []
header_search_paths = [
"\"$(PODS_ROOT)/ReactNativeDependencies\"",
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
"\"$(PODS_ROOT)/Headers/Private/React-Fabric\"",
"\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
"\"$(PODS_TARGET_SRCROOT)\"",
]
if use_frameworks
ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"])
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"]))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"]))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", []))
.each { |search_path|
header_search_paths << "\"#{search_path}\""
}
end
Pod::Spec.new do |s|
s.name = "ReactCodegen"
s.version = version
s.summary = 'Temp pod for generated files for React Native'
s.homepage = 'https://facebook.com/'
s.license = 'Unlicense'
s.authors = 'Facebook'
s.compiler_flags = "#{folly_compiler_flags} #{boost_compiler_flags} -Wno-nullability-completeness -std=c++20"
s.source = { :git => '' }
s.header_mappings_dir = './'
s.platforms = min_supported_versions
s.source_files = "**/*.{h,mm,cpp}"
s.exclude_files = "RCTAppDependencyProvider.{h,mm}" # these files are generated in the same codegen path but needs to belong to a different pod
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"FRAMEWORK_SEARCH_PATHS" => framework_search_paths,
"OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}"
}
s.dependency "React-jsiexecutor"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "React-Core"
s.dependency "React-jsi"
s.dependency "ReactCommon/turbomodule/bridging"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-NativeModulesApple"
s.dependency 'React-graphics'
s.dependency 'React-rendererdebug'
s.dependency 'React-Fabric'
s.dependency 'React-FabricImage'
s.dependency 'React-debug'
s.dependency 'React-utils'
s.dependency 'React-featureflags'
s.dependency 'React-RCTAppDelegate'
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.script_phases = {
'name' => 'Generate Specs',
'execution_position' => :before_compile,
'input_files' => {input-files},
'show_env_vars_in_log' => true,
'output_files' => ["${DERIVED_FILE_DIR}/react-codegen.log"],
'script': {codegen-script}
}
end