jiuyiUniapp/service/node_modules/react-native/scripts/codegen/generate-artifacts-executor.js

978 lines
28 KiB
JavaScript

/**
* 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.
*
* @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 utils = require('./codegen-utils');
const generateSpecsCLIExecutor = require('./generate-specs-cli-executor');
const {execSync} = require('child_process');
const fs = require('fs');
const glob = require('glob');
const os = require('os');
const path = require('path');
const REACT_NATIVE_REPOSITORY_ROOT = path.join(
__dirname,
'..',
'..',
'..',
'..',
);
const REACT_NATIVE_PACKAGE_ROOT_FOLDER = path.join(__dirname, '..', '..');
const CODEGEN_REPO_PATH = `${REACT_NATIVE_REPOSITORY_ROOT}/packages/react-native-codegen`;
const RNCORE_CONFIGS = {
ios: path.join(REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'ReactCommon'),
android: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'ReactAndroid',
'build',
'generated',
'source',
'codegen',
),
};
const CORE_LIBRARIES_WITH_OUTPUT_FOLDER = {
rncore: RNCORE_CONFIGS,
FBReactNativeSpec: {
ios: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'React',
'FBReactNativeSpec',
),
android: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'ReactAndroid',
'build',
'generated',
'source',
'codegen',
),
},
};
const packageJsonPath = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'package.json',
);
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
const REACT_NATIVE = packageJson.name;
const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTModulesConformingToProtocolsProviderH.template',
);
const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTModulesConformingToProtocolsProviderMM.template',
);
const THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTThirdPartyComponentsProviderH.template',
);
const THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTThirdPartyComponentsProviderMM.template',
);
const APP_DEPENDENCY_PROVIDER_H_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTAppDependencyProviderH.template',
);
const APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTAppDependencyProviderMM.template',
);
const APP_DEPENDENCY_PROVIDER_PODSPEC_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'ReactAppDependencyProvider.podspec.template',
);
const codegenLog = (text, info = 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}`);
};
// HELPERS
function pkgJsonIncludesGeneratedCode(pkgJson) {
return pkgJson.codegenConfig && pkgJson.codegenConfig.includesGeneratedCode;
}
function isReactNativeCoreLibrary(libraryName) {
return libraryName in CORE_LIBRARIES_WITH_OUTPUT_FOLDER;
}
function readPkgJsonInDirectory(dir) {
const pkgJsonPath = path.join(dir, 'package.json');
if (!fs.existsSync(pkgJsonPath)) {
throw `[Codegen] Error: ${pkgJsonPath} does not exist.`;
}
return JSON.parse(fs.readFileSync(pkgJsonPath));
}
function printDeprecationWarningIfNeeded(dependency) {
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": "."
}
}
`);
}
// Reading Libraries
function extractLibrariesFromConfigurationArray(configFile, dependencyPath) {
return configFile.codegenConfig.libraries.map(config => {
return {
config,
libraryPath: dependencyPath,
};
});
}
function extractLibrariesFromJSON(configFile, dependencyPath) {
if (configFile.codegenConfig == null) {
return [];
}
codegenLog(`Found ${configFile.name}`);
if (configFile.codegenConfig.libraries == null) {
const config = configFile.codegenConfig;
return [
{
config,
libraryPath: dependencyPath,
},
];
} else {
printDeprecationWarningIfNeeded(configFile.name);
return extractLibrariesFromConfigurationArray(configFile, dependencyPath);
}
}
const APPLE_PLATFORMS = ['ios', 'macos', 'tvos', 'visionos'];
// Cocoapods specific platform keys
function getCocoaPodsPlatformKey(platformName) {
if (platformName === 'macos') {
return 'osx';
}
return platformName;
}
function extractSupportedApplePlatforms(dependency, dependencyPath) {
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),
),
}),
{},
);
const supportedPlatformsList = Object.keys(supportedPlatformsMap).filter(
key => supportedPlatformsMap[key],
);
if (supportedPlatformsList.length > 0) {
codegenLog(
`Supported Apple platforms: ${supportedPlatformsList.join(
', ',
)} for ${dependency}`,
);
}
return supportedPlatformsMap;
}
function findExternalLibraries(pkgJson, projectRoot) {
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 (
dependencies[dependency].startsWith('.') || // handles relative paths
dependencies[dependency].startsWith('/') // handles absolute paths
) {
configFilePath = path.join(
projectRoot,
pkgJson.dependencies[dependency],
'package.json',
);
} else {
return [];
}
}
const configFile = JSON.parse(fs.readFileSync(configFilePath));
const codegenConfigFileDir = path.dirname(configFilePath);
return extractLibrariesFromJSON(configFile, codegenConfigFileDir);
});
}
function findLibrariesFromReactNativeConfig(projectRoot) {
const rnConfigFileName = 'react-native.config.js';
codegenLog(
`Searching for codegen-enabled libraries in ${rnConfigFileName}`,
true,
);
const rnConfigFilePath = path.resolve(projectRoot, rnConfigFileName);
if (!fs.existsSync(rnConfigFilePath)) {
return [];
}
const rnConfig = require(rnConfigFilePath);
if (rnConfig.dependencies == null) {
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 findProjectRootLibraries(pkgJson, projectRoot) {
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 'The "codegenConfig" field must be an Object.';
}
return extractLibrariesFromJSON(pkgJson, projectRoot);
}
// CodeGen
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',
});
}
function readOutputDirFromPkgJson(pkgJson, platform) {
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 defaultOutputPathForAndroid(baseOutputPath) {
return path.join(
baseOutputPath,
'android',
'app',
'build',
'generated',
'source',
'codegen',
);
}
function defaultOutputPathForIOS(baseOutputPath) {
return path.join(baseOutputPath, 'build', 'generated', 'ios');
}
function computeOutputPath(projectRoot, baseOutputPath, pkgJson, platform) {
if (baseOutputPath == null) {
const outputDirFromPkgJson = readOutputDirFromPkgJson(pkgJson, platform);
if (outputDirFromPkgJson != null) {
baseOutputPath = path.join(projectRoot, outputDirFromPkgJson);
} else {
baseOutputPath = projectRoot;
}
}
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 reactNativeCoreLibraryOutputPath(libraryName, platform) {
return CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName]
? CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName][platform]
: null;
}
function generateSchemaInfo(library, platform) {
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: utils
.getCombineJSToSchema()
.combineSchemasInFileList(
[pathToJavaScriptSources],
platform,
/NativeSampleTurboModule/,
),
};
}
function shouldSkipGenerationForRncore(schemaInfo, platform) {
if (platform !== 'ios' || schemaInfo.library.config.name !== 'rncore') {
return false;
}
const rncoreOutputPath = CORE_LIBRARIES_WITH_OUTPUT_FOLDER.rncore.ios;
const rncoreAbsolutePath = path.resolve(rncoreOutputPath);
return (
rncoreAbsolutePath.includes('node_modules') &&
fs.existsSync(rncoreAbsolutePath) &&
fs.readdirSync(rncoreAbsolutePath).length > 0
);
}
function shouldSkipGenerationForFBReactNativeSpec(schemaInfo, platform) {
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
);
}
function generateCode(outputPath, schemaInfo, includesGeneratedCode, platform) {
if (shouldSkipGenerationForRncore(schemaInfo, platform)) {
codegenLog(
'[Codegen - rncore] Skipping iOS code generation for rncore as it has been generated already.',
true,
);
return;
}
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});
fs.cpSync(tmpOutputDir, outputDir, {recursive: true});
codegenLog(`Generated artifacts: ${outputDir}`);
}
function generateSchemaInfos(libraries) {
return libraries.map(generateSchemaInfo);
}
function generateNativeCode(
outputPath,
schemaInfos,
includesGeneratedCode,
platform,
) {
return schemaInfos.map(schemaInfo => {
generateCode(outputPath, schemaInfo, includesGeneratedCode, platform);
});
}
function mustGenerateNativeCode(includeLibraryPath, schemaInfo) {
// 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
);
}
function findCodegenEnabledLibraries(pkgJson, projectRoot) {
const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot);
if (pkgJsonIncludesGeneratedCode(pkgJson)) {
return projectLibraries;
} else {
return [
...projectLibraries,
...findExternalLibraries(pkgJson, projectRoot),
...findLibrariesFromReactNativeConfig(projectRoot),
];
}
}
function generateCustomURLHandlers(libraries, outputDir) {
const customImageURLLoaderClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTImageURLLoader,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customImageDataDecoderClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTImageDataDecoder,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customURLHandlerClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTURLRequestHandler,
)
.filter(Boolean)
.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,
);
}
function generateAppDependencyProvider(outputDir) {
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}`);
}
function generateRCTThirdPartyComponents(libraries, outputDir) {
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 = {};
libraries.forEach(({config, libraryPath}) => {
if (isReactNativeCoreLibrary(config.name) || config.type === 'modules') {
return;
}
const libraryName = JSON.parse(
fs.readFileSync(path.join(libraryPath, 'package.json')),
).name;
if (config.ios?.componentProvider) {
componentsInLibraries[libraryName] = Object.keys(
config.ios?.componentProvider,
).map(componentName => {
return {
componentName,
className: config.ios?.componentProvider[componentName],
};
});
return;
}
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, extension) {
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 `.`
if (absolutePath.includes(`${path.sep}.`)) {
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) {
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)).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;
}
// 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) {
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 generateRNCoreComponentsIOS(projectRoot /*: string */) /*: void*/ {
const ios = 'ios';
buildCodegenIfNeeded();
const pkgJson = readPkgJsonInDirectory(projectRoot);
const rncoreLib = findProjectRootLibraries(pkgJson, projectRoot).filter(
library => library.config.name === 'rncore',
)[0];
if (!rncoreLib) {
throw new Error(
"[Codegen] Can't find rncore library. Failed to generate rncore artifacts",
);
}
const rncoreSchemaInfo = generateSchemaInfo(rncoreLib, ios);
generateCode('', rncoreSchemaInfo, false, ios);
}
function generateFBReactNativeSpecIOS(projectRoot /*: string */) /*: void*/ {
const ios = '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 rncore artifacts",
);
}
const fbReactNativeSchemaInfo = generateSchemaInfo(fbReactNativeSpecLib, ios);
generateCode('', fbReactNativeSchemaInfo, false, ios);
}
// Execute
/**
* 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'.
* @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, targetPlatform, baseOutputPath) {
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);
buildCodegenIfNeeded();
const libraries = findCodegenEnabledLibraries(pkgJson, projectRoot);
if (libraries.length === 0) {
codegenLog('No codegen-enabled libraries found.', true);
return;
}
let platforms =
targetPlatform === 'all' ? supportedPlatforms : [targetPlatform];
for (const platform of platforms) {
const outputPath = computeOutputPath(
projectRoot,
baseOutputPath,
pkgJson,
platform,
);
const schemaInfos = generateSchemaInfos(libraries);
generateNativeCode(
outputPath,
schemaInfos.filter(schemaInfo =>
mustGenerateNativeCode(projectRoot, schemaInfo),
),
pkgJsonIncludesGeneratedCode(pkgJson),
platform,
);
generateRCTThirdPartyComponents(libraries, outputPath);
generateCustomURLHandlers(libraries, outputPath);
generateAppDependencyProvider(outputPath);
cleanupEmptyFilesAndFolders(outputPath);
}
} catch (err) {
codegenLog(err);
process.exitCode = 1;
}
codegenLog('Done.', true);
return;
}
module.exports = {
execute,
generateRNCoreComponentsIOS,
generateFBReactNativeSpecIOS,
// exported for testing purposes only:
_extractLibrariesFromJSON: extractLibrariesFromJSON,
_cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders,
_extractSupportedApplePlatforms: extractSupportedApplePlatforms,
};