From 0a4de5b32545f7b39499c3e3c4e0a7fc4ee2a24b Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 3 May 2022 05:41:39 -0700 Subject: [PATCH 1/5] Support the `type == all` properly Summary: This Diff introduces some changes in the CodeGen to properly generate the types in the right folder. ## Issue The codegen on iOS defines the output folder once, before creating the generated code. When the code we have to generate is just a TurboModule (TM) or a Fabric Component (FC), this mechanism works properly. However, if a library has to generate both TM and FC, actually using the library type `all`, all the code is generated using the TurboModules' output folder. (**Note:** Android only works in this way) This generates invalid code because all the FC's `#import` directives assumes that the code is generated in the FC output path which, in this case, is not. ## Solution The adopted solution moves the responsibility to decide where the files has to be generated to the CodeGen step instead of in the preparatory phases. The two paths are precomputed in the `generate-artifacts.js` script (the entry point for the CodeGen) and they are passed to all the scripts that requires them. Once they reach the `RNCodegen.js` file, the generators creates the files and save them in the proper paths. ## Changelog [iOS][Changed] - CodeGen now supports the `"all"` library type. Differential Revision: D35820848 fbshipit-source-id: 9c3c8dec6e052342b6ae111ec292abd170c69b9c --- .../src/generators/RNCodegen.js | 98 ++++++++++++------- .../generators/__test_fixtures__/fixtures.js | 82 ++++++++++++++++ .../generators/__tests__/RNCodegen-test.js | 69 +++++++++++++ scripts/generate-artifacts.js | 64 ++++++++---- scripts/generate-specs-cli.js | 70 ++++++++++--- 5 files changed, 317 insertions(+), 66 deletions(-) create mode 100644 packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js create mode 100644 packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js diff --git a/packages/react-native-codegen/src/generators/RNCodegen.js b/packages/react-native-codegen/src/generators/RNCodegen.js index 6444b2cfa87a..d9487e8832fe 100644 --- a/packages/react-native-codegen/src/generators/RNCodegen.js +++ b/packages/react-native-codegen/src/generators/RNCodegen.js @@ -48,6 +48,8 @@ type LibraryOptions = $ReadOnly<{ outputDirectory: string, packageName?: string, // Some platforms have a notion of package, which should be configurable. assumeNonnull: boolean, + componentsOutputDir?: string, // optional for backward compatibility + modulesOutputDir?: string, // optional for backward compatibility }>; type SchemasOptions = $ReadOnly<{ @@ -134,36 +136,39 @@ const SCHEMAS_GENERATORS = { ], }; -function writeMapToFiles(map: Map, outputDir: string) { +type CodeGenFile = { + name: string, + content: string, + outputDir: string, +}; + +function writeMapToFiles(map: Array) { let success = true; - map.forEach((contents: string, fileName: string) => { + map.forEach(file => { try { - const location = path.join(outputDir, fileName); + const location = path.join(file.outputDir, file.name); const dirName = path.dirname(location); if (!fs.existsSync(dirName)) { fs.mkdirSync(dirName, {recursive: true}); } - fs.writeFileSync(location, contents); + fs.writeFileSync(location, file.content); } catch (error) { success = false; - console.error(`Failed to write ${fileName} to ${outputDir}`, error); + console.error(`Failed to write ${file.name} to ${file.outputDir}`, error); } }); return success; } -function checkFilesForChanges( - map: Map, - outputDir: string, -): boolean { +function checkFilesForChanges(generated: Array): boolean { let hasChanged = false; - map.forEach((contents: string, fileName: string) => { - const location = path.join(outputDir, fileName); + generated.forEach(file => { + const location = path.join(file.outputDir, file.name); const currentContents = fs.readFileSync(location, 'utf8'); - if (currentContents !== contents) { - console.error(`- ${fileName} has changed`); + if (currentContents !== file.content) { + console.error(`- ${file.name} has changed`); hasChanged = true; } @@ -172,6 +177,16 @@ function checkFilesForChanges( return !hasChanged; } +function checkOrWriteFiles( + generatedFiles: Array, + test: void | boolean, +): boolean { + if (test === true) { + return checkFilesForChanges(generatedFiles); + } + return writeMapToFiles(generatedFiles); +} + module.exports = { generate( { @@ -180,27 +195,42 @@ module.exports = { outputDirectory, packageName, assumeNonnull, + componentsOutputDir, + modulesOutputDir, }: LibraryOptions, {generators, test}: LibraryConfig, ): boolean { schemaValidator.validate(schema); - const generatedFiles = []; + const outputFoldersForGenerators = { + componentsIOS: componentsOutputDir ?? outputDirectory, // fallback for backward compatibility + modulesIOS: modulesOutputDir ?? outputDirectory, // fallback for backward compatibility + descriptors: outputDirectory, + events: outputDirectory, + props: outputDirectory, + componentsAndroid: outputDirectory, + modulesAndroid: outputDirectory, + modulesCxx: outputDirectory, + tests: outputDirectory, + 'shadow-nodes': outputDirectory, + }; + + const generatedFiles: Array = []; + for (const name of generators) { for (const generator of LIBRARY_GENERATORS[name]) { - generatedFiles.push( - ...generator(libraryName, schema, packageName, assumeNonnull), + generator(libraryName, schema, packageName, assumeNonnull).forEach( + (contents: string, fileName: string) => { + generatedFiles.push({ + name: fileName, + content: contents, + outputDir: outputFoldersForGenerators[name], + }); + }, ); } } - - const filesToUpdate = new Map([...generatedFiles]); - - if (test === true) { - return checkFilesForChanges(filesToUpdate, outputDirectory); - } - - return writeMapToFiles(filesToUpdate, outputDirectory); + return checkOrWriteFiles(generatedFiles, test); }, generateFromSchemas( {schemas, outputDirectory}: SchemasOptions, @@ -210,20 +240,20 @@ module.exports = { schemaValidator.validate(schemas[libraryName]), ); - const generatedFiles = []; + const generatedFiles: Array = []; + for (const name of generators) { for (const generator of SCHEMAS_GENERATORS[name]) { - generatedFiles.push(...generator(schemas)); + generator(schemas).forEach((contents: string, fileName: string) => { + generatedFiles.push({ + name: fileName, + content: contents, + outputDir: outputDirectory, + }); + }); } } - - const filesToUpdate = new Map([...generatedFiles]); - - if (test === true) { - return checkFilesForChanges(filesToUpdate, outputDirectory); - } - - return writeMapToFiles(filesToUpdate, outputDirectory); + return checkOrWriteFiles(generatedFiles, test); }, generateViewConfig({libraryName, schema}: LibraryOptions): string { schemaValidator.validate(schema); diff --git a/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js new file mode 100644 index 000000000000..b86597683933 --- /dev/null +++ b/packages/react-native-codegen/src/generators/__test_fixtures__/fixtures.js @@ -0,0 +1,82 @@ +/** + * 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'; + +import type {SchemaType} from '../../CodegenSchema.js'; + +const SCHEMA_WITH_TM_AND_FC: SchemaType = { + modules: { + ColoredView: { + type: 'Component', + components: { + ColoredView: { + extendsProps: [ + { + type: 'ReactNativeBuiltInType', + knownTypeName: 'ReactNativeCoreViewProps', + }, + ], + events: [], + props: [ + { + name: 'color', + optional: false, + typeAnnotation: { + type: 'StringTypeAnnotation', + default: null, + }, + }, + ], + commands: [], + }, + }, + }, + NativeCalculator: { + type: 'NativeModule', + aliases: {}, + spec: { + properties: [ + { + name: 'add', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'PromiseTypeAnnotation', + }, + params: [ + { + name: 'a', + optional: false, + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + name: 'b', + optional: false, + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + }, + }, + ], + }, + moduleNames: ['Calculator'], + }, + }, +}; + +module.exports = { + all: SCHEMA_WITH_TM_AND_FC, +}; diff --git a/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js b/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js new file mode 100644 index 000000000000..a529379d72ee --- /dev/null +++ b/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js @@ -0,0 +1,69 @@ +/** + * 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. + * + * @emails oncall+react_native + * @flow strict-local + * @format + */ + +'use strict'; + +const rnCodegen = require('../RNCodegen.js'); +const fixture = require('../__test_fixtures__/fixtures.js'); +const path = require('path'); + +const invalidDirectory = 'invalid/'; +const packageName = 'na'; +const componentsOutputDir = 'react/renderer/components/library'; +const modulesOutputDir = 'library'; + +describe('RNCodegen.generate', () => { + it('when type `all`', () => { + const expectedPaths = { + 'library.h': modulesOutputDir, + 'library-generated.mm': modulesOutputDir, + 'ShadowNodes.h': componentsOutputDir, + 'ShadowNodes.cpp': componentsOutputDir, + 'Props.h': componentsOutputDir, + 'Props.cpp': componentsOutputDir, + 'RCTComponentViewHelpers.h': componentsOutputDir, + 'EventEmitters.h': componentsOutputDir, + 'EventEmitters.cpp': componentsOutputDir, + 'ComponentDescriptors.h': componentsOutputDir, + }; + + jest.mock('fs', () => ({ + existsSync: location => { + return true; + }, + writeFileSync: (location, content) => { + let receivedDir = path.dirname(location); + let receivedBasename = path.basename(location); + + let expectedPath = expectedPaths[receivedBasename]; + expect(receivedDir).toEqual(expectedPath); + }, + })); + + const res = rnCodegen.generate( + { + libraryName: 'library', + schema: fixture.all, + outputDirectory: invalidDirectory, + packageName: packageName, + assumeNonnull: true, + componentsOutputDir: componentsOutputDir, + modulesOutputDir: modulesOutputDir, + }, + { + generators: ['componentsIOS', 'modulesIOS'], + test: false, + }, + ); + + expect(res).toBeTruthy(); + }); +}); diff --git a/scripts/generate-artifacts.js b/scripts/generate-artifacts.js index 18d3a50c69e4..2feb2d9e2bcf 100644 --- a/scripts/generate-artifacts.js +++ b/scripts/generate-artifacts.js @@ -81,6 +81,10 @@ function executeNodeScript(script) { execSync(`${NODE} ${script}`); } +function isDirEmpty(dirPath) { + return fs.readdirSync(dirPath).length === 0; +} + function main(appRootDir, outputPath) { if (appRootDir == null) { console.error('Missing path to React Native application'); @@ -233,14 +237,19 @@ function main(appRootDir, outputPath) { library.libraryPath, library.config.jsSrcsDir, ); - const pathToOutputDirIOS = path.join( - iosOutputDir, - library.config.type === 'components' - ? 'react/renderer/components' - : './', - library.config.name, - ); - const pathToTempOutputDir = path.join(tmpDir, 'out'); + function composePath(intermediate) { + return path.join(iosOutputDir, intermediate, library.config.name); + } + + const outputDirsIOS = { + components: composePath('react/renderer/components'), + nativeModules: composePath('./'), + }; + + const tempOutputDirs = { + components: path.join(tmpDir, 'out', 'components'), + nativeModules: path.join(tmpDir, 'out', 'nativeModules'), + }; console.log(`\n\n[Codegen] >>>>> Processing ${library.config.name}`); // Generate one schema for the entire library... @@ -259,21 +268,38 @@ function main(appRootDir, outputPath) { const libraryTypeArg = library.config.type ? `--libraryType ${library.config.type}` : ''; - fs.mkdirSync(pathToTempOutputDir, {recursive: true}); + + Object.entries(tempOutputDirs).forEach(([type, dirPath]) => { + fs.mkdirSync(dirPath, {recursive: true}); + }); + + const deprecated_outputDir = + library.config.type === 'components' + ? tempOutputDirs.components + : tempOutputDirs.nativeModules; + executeNodeScript( - `${path.join( - RN_ROOT, - 'scripts', - 'generate-specs-cli.js', - )} --platform ios --schemaPath ${pathToSchema} --outputDir ${pathToTempOutputDir} --libraryName ${ - library.config.name - } ${libraryTypeArg}`, + `${path.join(RN_ROOT, 'scripts', 'generate-specs-cli.js')} \ + --platform ios \ + --schemaPath ${pathToSchema} \ + --outputDir ${deprecated_outputDir} \ + --componentsOutputDir ${tempOutputDirs.components} \ + --modulesOutputDirs ${tempOutputDirs.nativeModules} \ + --libraryName ${library.config.name} \ + ${libraryTypeArg}`, ); // Finally, copy artifacts to the final output directory. - fs.mkdirSync(pathToOutputDirIOS, {recursive: true}); - execSync(`cp -R ${pathToTempOutputDir}/* ${pathToOutputDirIOS}`); - console.log(`[Codegen] Generated artifacts: ${pathToOutputDirIOS}`); + Object.entries(outputDirsIOS).forEach(([type, dirPath]) => { + const outDir = tempOutputDirs[type]; + if (isDirEmpty(outDir)) { + return; // cp fails if we try to copy something empty. + } + + fs.mkdirSync(dirPath, {recursive: true}); + execSync(`cp -R ${outDir}/* ${dirPath}`); + console.log(`[Codegen] Generated artifacts: ${dirPath}`); + }); // Filter the react native core library out. // In the future, core library and third party library should diff --git a/scripts/generate-specs-cli.js b/scripts/generate-specs-cli.js index 550b363f371c..91c6039d81f7 100644 --- a/scripts/generate-specs-cli.js +++ b/scripts/generate-specs-cli.js @@ -36,7 +36,7 @@ const argv = yargs .option('o', { alias: 'outputDir', describe: - 'Path to directory where native code source files should be saved.', + 'DEPRECATED - Path to directory where native code source files should be saved.', }) .option('n', { alias: 'libraryName', @@ -53,6 +53,14 @@ const argv = yargs describe: 'all, components, or modules.', default: 'all', }) + .option('c', { + alias: 'componentsOutputDir', + describe: 'Output directory for the codeGen for Fabric Components', + }) + .option('m', { + alias: 'modulesOutputDirs', + describe: 'Output directory for the codeGen for TurboModules', + }) .usage('Usage: $0 ') .demandOption( ['platform', 'schemaPath', 'outputDir'], @@ -74,35 +82,67 @@ const GENERATORS = { }, }; -function generateSpec( - platform, - schemaPath, +function deprecated_createOutputDirectoryIfNeeded( outputDirectory, libraryName, - packageName, - libraryType, ) { + if (!outputDirectory) { + outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName); + } + mkdirp.sync(outputDirectory); +} + +function createFolderIfDefined(folder) { + if (folder) { + mkdirp.sync(folder); + } +} + +/** + * 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) { const schemaText = fs.readFileSync(schemaPath, 'utf-8'); if (schemaText == null) { throw new Error(`Can't find schema at ${schemaPath}`); } - if (!outputDirectory) { - outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName); - } - mkdirp.sync(outputDirectory); - - let schema; try { - schema = JSON.parse(schemaText); + return JSON.parse(schemaText); } catch (err) { throw new Error(`Can't parse schema to JSON. ${schemaPath}`); } +} +function validateLibraryType(libraryType) { if (GENERATORS[libraryType] == null) { throw new Error(`Invalid library type. ${libraryType}`); } +} + +function generateSpec( + platform, + schemaPath, + outputDirectory, + libraryName, + packageName, + libraryType, + componentsOutputDir, + modulesOutputDirs, +) { + validateLibraryType(libraryType); + + let schema = readAndParseSchema(schemaPath); + + createFolderIfDefined(componentsOutputDir); + createFolderIfDefined(modulesOutputDirs); + deprecated_createOutputDirectoryIfNeeded(outputDirectory, libraryName); RNCodegen.generate( { @@ -110,6 +150,8 @@ function generateSpec( schema, outputDirectory, packageName, + componentsOutputDir, + modulesOutputDirs, }, { generators: GENERATORS[libraryType][platform], @@ -140,6 +182,8 @@ function main() { argv.libraryName, argv.javaPackageName, argv.libraryType, + argv.componentsOutputDir, + argv.modulesOutputDirs, ); } From 9ff12f9e2c7560a10b9d647323dc14614598174b Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 3 May 2022 05:41:39 -0700 Subject: [PATCH 2/5] Refactor generate-artifacts to improve testability. Summary: This Diff splits the `generate-artifacts.js` script into: * `generate-artifacts-executor.js`: which contains the logic to generate the code for iOS. * `generate-artifacts.js`: which contains the argument parsing logic and invokes the executor. Finally, it introduces some tests. ## Changelog [iOS][Changed] - Refactor part of the codegen scripts and add tests. Differential Revision: D35846674 fbshipit-source-id: b33c6170d6363932bb944074fda063ca3cbe997f --- .../generate-artifacts-executor-test.js | 99 +++++ .../codegen/generate-artifacts-executor.js | 415 ++++++++++++++++++ scripts/generate-artifacts.js | 294 +------------ 3 files changed, 525 insertions(+), 283 deletions(-) create mode 100644 scripts/codegen/__tests__/generate-artifacts-executor-test.js create mode 100644 scripts/codegen/generate-artifacts-executor.js diff --git a/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/scripts/codegen/__tests__/generate-artifacts-executor-test.js new file mode 100644 index 000000000000..6b7addd60288 --- /dev/null +++ b/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -0,0 +1,99 @@ +/** + * 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. + * + * @emails oncall+react_native + * @format + */ + +'use strict'; + +const underTest = require('../generate-artifacts-executor'); +const path = require('path'); + +describe('generateCode', () => { + it('executeNodes with the right arguents', () => { + // Define variables and expected values + const iosOutputDir = 'app/ios/build/generated/ios'; + const library = {config: {name: 'library', type: 'all'}}; + const tmpDir = 'tmp'; + const node = 'usr/bin/node'; + const pathToSchema = 'app/build/schema.json'; + const rnRoot = path.join(__dirname, '../..'); + const libraryType = 'all'; + + const tmpComponentOutDirs = path.join(tmpDir, 'out', 'components'); + const tmpNativeModulesOutDir = path.join(tmpDir, 'out', 'nativeModules'); + const iOSComponentOutDirs = path.join( + iosOutputDir, + 'react/renderer/components', + library.config.name, + ); + const iOSNativeModulesOutDir = path.join( + iosOutputDir, + './', + library.config.name, + ); + + // mock used functions + let mkdirSyncInvocationCount = 0; + jest.mock('fs', () => ({ + readdirSync: dirpath => { + return ['test/dir']; //we only require to return something, so that the folder is not empty. + }, + mkdirSync: (location, config) => { + if (mkdirSyncInvocationCount === 0) { + expect(location).toEqual(tmpComponentOutDirs); + } + if (mkdirSyncInvocationCount === 1) { + expect(location).toEqual(tmpNativeModulesOutDir); + } + if (mkdirSyncInvocationCount === 2) { + expect(location).toEqual(iOSComponentOutDirs); + } + if (mkdirSyncInvocationCount === 3) { + expect(location).toEqual(iOSNativeModulesOutDir); + } + mkdirSyncInvocationCount += 1; + }, + })); + + let execSyncInvocationCount = 0; + jest.mock('child_process', () => ({ + execSync: command => { + if (execSyncInvocationCount === 0) { + const expectedCommand = `${node} ${path.join( + rnRoot, + 'generate-specs-cli.js', + )} \ + --platform ios \ + --schemaPath ${pathToSchema} \ + --outputDir ${tmpNativeModulesOutDir} \ + --componentsOutputDir ${tmpComponentOutDirs} \ + --modulesOutputDirs ${tmpNativeModulesOutDir} \ + --libraryName ${library.config.name} \ + --libraryType ${libraryType}`; + expect(command).toEqual(expectedCommand); + } + + if (execSyncInvocationCount === 1) { + expect(command).toEqual( + `cp -R ${tmpComponentOutDirs}/* ${iOSComponentOutDirs}`, + ); + } + if (execSyncInvocationCount === 2) { + expect(command).toEqual( + `cp -R ${tmpNativeModulesOutDir}/* ${iOSNativeModulesOutDir}`, + ); + } + + execSyncInvocationCount += 1; + }, + })); + + underTest._generateCode(iosOutputDir, library, tmpDir, node, pathToSchema); + expect(mkdirSyncInvocationCount).toBe(4); + }); +}); diff --git a/scripts/codegen/generate-artifacts-executor.js b/scripts/codegen/generate-artifacts-executor.js new file mode 100644 index 000000000000..281ed411f384 --- /dev/null +++ b/scripts/codegen/generate-artifacts-executor.js @@ -0,0 +1,415 @@ +/** + * 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 codegenConfigKey key + * in a codegenConfigFilename file. + */ + +const {execSync} = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const RN_ROOT = path.join(__dirname, '../..'); + +const CODEGEN_REPO_PATH = `${RN_ROOT}/packages/react-native-codegen`; +const CODEGEN_NPM_PATH = `${RN_ROOT}/../react-native-codegen`; +const CORE_LIBRARIES = new Set(['rncore', 'FBReactNativeSpec']); +const REACT_NATIVE_DEPENDENCY_NAME = 'react-native'; + +// HELPERS + +function isReactNativeCoreLibrary(libraryName) { + return CORE_LIBRARIES.has(libraryName); +} + +function executeNodeScript(node, script) { + execSync(`${node} ${script}`); +} + +function isDirEmpty(dirPath) { + return fs.readdirSync(dirPath).length === 0; +} + +function isAppRootValid(appRootDir) { + if (appRootDir == null) { + console.error('Missing path to React Native application'); + process.exitCode = 1; + return false; + } + return true; +} + +function readPackageJSON(appRootDir) { + return JSON.parse(fs.readFileSync(path.join(appRootDir, 'package.json'))); +} + +// Reading Libraries + +function handleReactNativeCodeLibraries( + libraries, + codegenConfigFilename, + codegenConfigKey, +) { + // Handle react-native core libraries. + // This is required when react-native is outside of node_modules. + console.log('[Codegen] Processing react-native core libraries'); + const reactNativePkgJson = path.join(RN_ROOT, codegenConfigFilename); + if (!fs.existsSync(reactNativePkgJson)) { + throw '[Codegen] Error: Could not find config file for react-native.'; + } + const reactNativeConfigFile = JSON.parse(fs.readFileSync(reactNativePkgJson)); + if ( + reactNativeConfigFile[codegenConfigKey] == null || + reactNativeConfigFile[codegenConfigKey].libraries == null + ) { + throw '[Codegen] Error: Could not find codegen config for react-native.'; + } + console.log('[Codegen] Found react-native'); + reactNativeConfigFile[codegenConfigKey].libraries.forEach(config => { + const libraryConfig = { + library: REACT_NATIVE_DEPENDENCY_NAME, + config, + libraryPath: RN_ROOT, + }; + libraries.push(libraryConfig); + }); +} + +function handleThirdPartyLibraries( + libraries, + baseCodegenConfigFileDir, + dependencies, + codegenConfigFilename, + codegenConfigKey, +) { + // Determine which of these are codegen-enabled libraries + const configDir = baseCodegenConfigFileDir || path.join(RN_ROOT, '..'); + console.log( + `\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${configDir}`, + ); + + // Handle third-party libraries + Object.keys(dependencies).forEach(dependency => { + if (dependency === REACT_NATIVE_DEPENDENCY_NAME) { + // react-native should already be added. + return; + } + const codegenConfigFileDir = path.join(configDir, dependency); + const configFilePath = path.join( + codegenConfigFileDir, + codegenConfigFilename, + ); + if (fs.existsSync(configFilePath)) { + const configFile = JSON.parse(fs.readFileSync(configFilePath)); + if ( + configFile[codegenConfigKey] != null && + configFile[codegenConfigKey].libraries != null + ) { + console.log(`[Codegen] Found ${dependency}`); + configFile[codegenConfigKey].libraries.forEach(config => { + const libraryConfig = { + library: dependency, + config, + libraryPath: codegenConfigFileDir, + }; + libraries.push(libraryConfig); + }); + } + } + }); +} + +function handleInAppLibraries( + libraries, + pkgJson, + codegenConfigKey, + appRootDir, +) { + console.log( + '\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in the app', + ); + + // Handle in-app libraries + if ( + pkgJson[codegenConfigKey] != null && + pkgJson[codegenConfigKey].libraries != null + ) { + console.log(`[Codegen] Found ${pkgJson.name}`); + pkgJson[codegenConfigKey].libraries.forEach(config => { + const libraryConfig = { + library: pkgJson.name, + config, + libraryPath: appRootDir, + }; + libraries.push(libraryConfig); + }); + } +} + +// CodeGen + +function getCodeGenCliPath() { + let codegenCliPath; + if (fs.existsSync(CODEGEN_REPO_PATH)) { + codegenCliPath = CODEGEN_REPO_PATH; + + if (!fs.existsSync(path.join(CODEGEN_REPO_PATH, 'lib'))) { + console.log('\n\n[Codegen] >>>>> Building react-native-codegen package'); + execSync('yarn install', { + cwd: codegenCliPath, + stdio: 'inherit', + }); + execSync('yarn build', { + cwd: codegenCliPath, + stdio: 'inherit', + }); + } + } else if (fs.existsSync(CODEGEN_NPM_PATH)) { + codegenCliPath = CODEGEN_NPM_PATH; + } else { + throw "error: Could not determine react-native-codegen location. Try running 'yarn install' or 'npm install' in your project root."; + } + return codegenCliPath; +} + +function computeIOSOutputDir(outputPath, appRootDir) { + return path.join(outputPath ? outputPath : appRootDir, 'build/generated/ios'); +} + +function generateSchema(tmpDir, library, node, codegenCliPath) { + const pathToSchema = path.join(tmpDir, 'schema.json'); + const pathToJavaScriptSources = path.join( + library.libraryPath, + library.config.jsSrcsDir, + ); + + console.log(`\n\n[Codegen] >>>>> Processing ${library.config.name}`); + // Generate one schema for the entire library... + executeNodeScript( + node, + `${path.join( + codegenCliPath, + 'lib', + 'cli', + 'combine', + 'combine-js-to-schema-cli.js', + )} ${pathToSchema} ${pathToJavaScriptSources}`, + ); + console.log(`[Codegen] Generated schema: ${pathToSchema}`); + return pathToSchema; +} + +function generateCode(iosOutputDir, library, tmpDir, node, pathToSchema) { + function composePath(intermediate) { + return path.join(iosOutputDir, intermediate, library.config.name); + } + + const outputDirsIOS = { + components: composePath('react/renderer/components'), + nativeModules: composePath('./'), + }; + + const tempOutputDirs = { + components: path.join(tmpDir, 'out', 'components'), + nativeModules: path.join(tmpDir, 'out', 'nativeModules'), + }; + + // ...then generate native code artifacts. + const libraryTypeArg = library.config.type + ? `--libraryType ${library.config.type}` + : ''; + + Object.entries(tempOutputDirs).forEach(([type, dirPath]) => { + fs.mkdirSync(dirPath, {recursive: true}); + }); + + const deprecated_outputDir = + library.config.type === 'components' + ? tempOutputDirs.components + : tempOutputDirs.nativeModules; + + executeNodeScript( + node, + `${path.join(RN_ROOT, 'scripts', 'generate-specs-cli.js')} \ + --platform ios \ + --schemaPath ${pathToSchema} \ + --outputDir ${deprecated_outputDir} \ + --componentsOutputDir ${tempOutputDirs.components} \ + --modulesOutputDirs ${tempOutputDirs.nativeModules} \ + --libraryName ${library.config.name} \ + ${libraryTypeArg}`, + ); + + // Finally, copy artifacts to the final output directory. + Object.entries(outputDirsIOS).forEach(([type, dirPath]) => { + const outDir = tempOutputDirs[type]; + if (isDirEmpty(outDir)) { + return; // cp fails if we try to copy something empty. + } + + fs.mkdirSync(dirPath, {recursive: true}); + execSync(`cp -R ${outDir}/* ${dirPath}`); + console.log(`[Codegen] Generated artifacts: ${dirPath}`); + }); +} + +function generateNativeCodegenFiles( + libraries, + fabricEnabled, + iosOutputDir, + node, + codegenCliPath, + schemaPaths, +) { + let fabricEnabledTypes = ['components', 'all']; + libraries.forEach(library => { + if ( + !fabricEnabled && + fabricEnabledTypes.indexOf(library.config.type) >= 0 + ) { + console.log( + `[Codegen] ${library.config.name} skipped because fabric is not enabled.`, + ); + return; + } + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), library.config.name)); + const pathToSchema = generateSchema(tmpDir, library, node, codegenCliPath); + generateCode(iosOutputDir, library, tmpDir, node, pathToSchema); + + // Filter the react native core library out. + // In the future, core library and third party library should + // use the same way to generate/register the fabric components. + if (!isReactNativeCoreLibrary(library.config.name)) { + schemaPaths[library.config.name] = pathToSchema; + } + }); +} + +function createComponentProvider( + fabricEnabled, + schemaPaths, + node, + iosOutputDir, +) { + if (fabricEnabled) { + console.log('\n\n>>>>> Creating component provider'); + // Save the list of spec paths to a temp file. + const schemaListTmpPath = `${os.tmpdir()}/rn-tmp-schema-list.json`; + const fd = fs.openSync(schemaListTmpPath, 'w'); + fs.writeSync(fd, JSON.stringify(schemaPaths)); + fs.closeSync(fd); + console.log(`Generated schema list: ${schemaListTmpPath}`); + + // Generate FabricComponentProvider. + // Only for iOS at this moment. + executeNodeScript( + node, + `${path.join( + RN_ROOT, + 'scripts', + 'generate-provider-cli.js', + )} --platform ios --schemaListPath "${schemaListTmpPath}" --outputDir ${iosOutputDir}`, + ); + console.log(`Generated provider in: ${iosOutputDir}`); + } +} + +// 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 appRootDir: the directory with the app source code, where the `codegenConfigFilename` lives. + * @parameter outputPath: the base output path for the CodeGen. + * @parameter node: the path to the node executable, used to run the codegen scripts. + * @parameter codegenConfigFilename: the file that contains the codeGen configuration. The default is `package.json`. + * @parameter codegenConfigKey: the key in the codegenConfigFile that controls the codegen. + * @parameter baseCodegenConfigFileDir: the directory of the codeGenConfigFile. + * @parameter fabricEnabled: whether fabric is enabled or not. + * @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( + appRootDir, + outputPath, + node, + codegenConfigFilename, + codegenConfigKey, + baseCodegenConfigFileDir, + fabricEnabled, +) { + if (!isAppRootValid(appRootDir)) { + return; + } + + try { + const pkgJson = readPackageJSON(appRootDir); + const dependencies = {...pkgJson.dependencies, ...pkgJson.devDependencies}; + const libraries = []; + + handleReactNativeCodeLibraries( + libraries, + codegenConfigFilename, + codegenConfigKey, + ); + handleThirdPartyLibraries( + libraries, + baseCodegenConfigFileDir, + dependencies, + codegenConfigFilename, + codegenConfigKey, + ); + handleInAppLibraries(libraries, pkgJson, codegenConfigKey, appRootDir); + + if (libraries.length === 0) { + console.log('[Codegen] No codegen-enabled libraries found.'); + return; + } + + const codegenCliPath = getCodeGenCliPath(); + + const schemaPaths = {}; + + const iosOutputDir = computeIOSOutputDir(outputPath, appRootDir); + + generateNativeCodegenFiles( + libraries, + fabricEnabled, + iosOutputDir, + node, + codegenCliPath, + schemaPaths, + ); + + createComponentProvider(fabricEnabled, schemaPaths, node, iosOutputDir); + } catch (err) { + console.error(err); + process.exitCode = 1; + } + + console.log('\n\n[Codegen] Done.'); + return; +} + +module.exports = { + execute: execute, + _executeNodeScript: executeNodeScript, // exported for testing purposes only + _generateCode: generateCode, // exported for testing purposes only +}; diff --git a/scripts/generate-artifacts.js b/scripts/generate-artifacts.js index 2feb2d9e2bcf..bde850b940de 100644 --- a/scripts/generate-artifacts.js +++ b/scripts/generate-artifacts.js @@ -9,17 +9,7 @@ '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 CODEGEN_CONFIG_KEY key - * in a CODEGEN_CONFIG_FILENAME file. - */ - -const {execSync} = require('child_process'); -const fs = require('fs'); -const os = require('os'); -const path = require('path'); +const executor = require('./codegen/generate-artifacts-executor.js'); const yargs = require('yargs'); const argv = yargs @@ -62,283 +52,21 @@ const argv = yargs .usage('Usage: $0 -p [path to app]') .demandOption(['p']).argv; -const RN_ROOT = path.join(__dirname, '..'); const CODEGEN_CONFIG_FILENAME = argv.f; const CODEGEN_CONFIG_FILE_DIR = argv.c; const CODEGEN_CONFIG_KEY = argv.k; const CODEGEN_FABRIC_ENABLED = argv.e; const NODE = argv.n; -const CODEGEN_REPO_PATH = `${RN_ROOT}/packages/react-native-codegen`; -const CODEGEN_NPM_PATH = `${RN_ROOT}/../react-native-codegen`; -const CORE_LIBRARIES = new Set(['rncore', 'FBReactNativeSpec']); -const REACT_NATIVE_DEPENDENCY_NAME = 'react-native'; - -function isReactNativeCoreLibrary(libraryName) { - return CORE_LIBRARIES.has(libraryName); -} - -function executeNodeScript(script) { - execSync(`${NODE} ${script}`); -} - -function isDirEmpty(dirPath) { - return fs.readdirSync(dirPath).length === 0; -} - -function main(appRootDir, outputPath) { - if (appRootDir == null) { - console.error('Missing path to React Native application'); - process.exitCode = 1; - return; - } - - try { - // Get app package.json - const pkgJson = JSON.parse( - fs.readFileSync(path.join(appRootDir, 'package.json')), - ); - - // Get dependencies for the app - const dependencies = {...pkgJson.dependencies, ...pkgJson.devDependencies}; - - const libraries = []; - - // Handle react-native core libraries. - // This is required when react-native is outside of node_modules. - console.log('[Codegen] Processing react-native core libraries'); - const reactNativePkgJson = path.join(RN_ROOT, CODEGEN_CONFIG_FILENAME); - if (!fs.existsSync(reactNativePkgJson)) { - throw '[Codegen] Error: Could not find config file for react-native.'; - } - const reactNativeConfigFile = JSON.parse( - fs.readFileSync(reactNativePkgJson), - ); - if ( - reactNativeConfigFile[CODEGEN_CONFIG_KEY] == null || - reactNativeConfigFile[CODEGEN_CONFIG_KEY].libraries == null - ) { - throw '[Codegen] Error: Could not find codegen config for react-native.'; - } - console.log('[Codegen] Found react-native'); - reactNativeConfigFile[CODEGEN_CONFIG_KEY].libraries.forEach(config => { - const libraryConfig = { - library: REACT_NATIVE_DEPENDENCY_NAME, - config, - libraryPath: RN_ROOT, - }; - libraries.push(libraryConfig); - }); - - // Determine which of these are codegen-enabled libraries - const confifDir = CODEGEN_CONFIG_FILE_DIR || path.join(RN_ROOT, '..'); - console.log( - `\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${confifDir}`, - ); - - // Handle third-party libraries - Object.keys(dependencies).forEach(dependency => { - if (dependency === REACT_NATIVE_DEPENDENCY_NAME) { - // react-native should already be added. - return; - } - const codegenConfigFileDir = path.join(confifDir, dependency); - const configFilePath = path.join( - codegenConfigFileDir, - CODEGEN_CONFIG_FILENAME, - ); - if (fs.existsSync(configFilePath)) { - const configFile = JSON.parse(fs.readFileSync(configFilePath)); - if ( - configFile[CODEGEN_CONFIG_KEY] != null && - configFile[CODEGEN_CONFIG_KEY].libraries != null - ) { - console.log(`[Codegen] Found ${dependency}`); - configFile[CODEGEN_CONFIG_KEY].libraries.forEach(config => { - const libraryConfig = { - library: dependency, - config, - libraryPath: codegenConfigFileDir, - }; - libraries.push(libraryConfig); - }); - } - } - }); - - console.log( - '\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in the app', - ); - - // Handle in-app libraries - if ( - pkgJson[CODEGEN_CONFIG_KEY] != null && - pkgJson[CODEGEN_CONFIG_KEY].libraries != null - ) { - console.log(`[Codegen] Found ${pkgJson.name}`); - pkgJson[CODEGEN_CONFIG_KEY].libraries.forEach(config => { - const libraryConfig = { - library: pkgJson.name, - config, - libraryPath: appRootDir, - }; - libraries.push(libraryConfig); - }); - } - - if (libraries.length === 0) { - console.log('[Codegen] No codegen-enabled libraries found.'); - return; - } - - // 4. Locate codegen package - let codegenCliPath; - if (fs.existsSync(CODEGEN_REPO_PATH)) { - codegenCliPath = CODEGEN_REPO_PATH; - - if (!fs.existsSync(path.join(CODEGEN_REPO_PATH, 'lib'))) { - console.log( - '\n\n[Codegen] >>>>> Building react-native-codegen package', - ); - execSync('yarn install', { - cwd: codegenCliPath, - stdio: 'inherit', - }); - execSync('yarn build', { - cwd: codegenCliPath, - stdio: 'inherit', - }); - } - } else if (fs.existsSync(CODEGEN_NPM_PATH)) { - codegenCliPath = CODEGEN_NPM_PATH; - } else { - throw "error: Could not determine react-native-codegen location. Try running 'yarn install' or 'npm install' in your project root."; - } - - const schemaPaths = {}; - - const iosOutputDir = path.join( - outputPath ? outputPath : appRootDir, - 'build/generated/ios', - ); - - // 5. For each codegen-enabled library, generate the native code spec files - libraries.forEach(library => { - if (!CODEGEN_FABRIC_ENABLED && library.config.type === 'components') { - console.log( - `[Codegen] ${library.config.name} skipped because fabric is not enabled.`, - ); - return; - } - const tmpDir = fs.mkdtempSync( - path.join(os.tmpdir(), library.config.name), - ); - const pathToSchema = path.join(tmpDir, 'schema.json'); - const pathToJavaScriptSources = path.join( - library.libraryPath, - library.config.jsSrcsDir, - ); - function composePath(intermediate) { - return path.join(iosOutputDir, intermediate, library.config.name); - } - - const outputDirsIOS = { - components: composePath('react/renderer/components'), - nativeModules: composePath('./'), - }; - - const tempOutputDirs = { - components: path.join(tmpDir, 'out', 'components'), - nativeModules: path.join(tmpDir, 'out', 'nativeModules'), - }; - - console.log(`\n\n[Codegen] >>>>> Processing ${library.config.name}`); - // Generate one schema for the entire library... - executeNodeScript( - `${path.join( - codegenCliPath, - 'lib', - 'cli', - 'combine', - 'combine-js-to-schema-cli.js', - )} ${pathToSchema} ${pathToJavaScriptSources}`, - ); - console.log(`[Codegen] Generated schema: ${pathToSchema}`); - - // ...then generate native code artifacts. - const libraryTypeArg = library.config.type - ? `--libraryType ${library.config.type}` - : ''; - - Object.entries(tempOutputDirs).forEach(([type, dirPath]) => { - fs.mkdirSync(dirPath, {recursive: true}); - }); - - const deprecated_outputDir = - library.config.type === 'components' - ? tempOutputDirs.components - : tempOutputDirs.nativeModules; - - executeNodeScript( - `${path.join(RN_ROOT, 'scripts', 'generate-specs-cli.js')} \ - --platform ios \ - --schemaPath ${pathToSchema} \ - --outputDir ${deprecated_outputDir} \ - --componentsOutputDir ${tempOutputDirs.components} \ - --modulesOutputDirs ${tempOutputDirs.nativeModules} \ - --libraryName ${library.config.name} \ - ${libraryTypeArg}`, - ); - - // Finally, copy artifacts to the final output directory. - Object.entries(outputDirsIOS).forEach(([type, dirPath]) => { - const outDir = tempOutputDirs[type]; - if (isDirEmpty(outDir)) { - return; // cp fails if we try to copy something empty. - } - - fs.mkdirSync(dirPath, {recursive: true}); - execSync(`cp -R ${outDir}/* ${dirPath}`); - console.log(`[Codegen] Generated artifacts: ${dirPath}`); - }); - - // Filter the react native core library out. - // In the future, core library and third party library should - // use the same way to generate/register the fabric components. - if (!isReactNativeCoreLibrary(library.config.name)) { - schemaPaths[library.config.name] = pathToSchema; - } - }); - - if (CODEGEN_FABRIC_ENABLED) { - console.log('\n\n>>>>> Creating component provider'); - // Save the list of spec paths to a temp file. - const schemaListTmpPath = `${os.tmpdir()}/rn-tmp-schema-list.json`; - const fd = fs.openSync(schemaListTmpPath, 'w'); - fs.writeSync(fd, JSON.stringify(schemaPaths)); - fs.closeSync(fd); - console.log(`Generated schema list: ${schemaListTmpPath}`); - - // Generate FabricComponentProvider. - // Only for iOS at this moment. - executeNodeScript( - `${path.join( - RN_ROOT, - 'scripts', - 'generate-provider-cli.js', - )} --platform ios --schemaListPath "${schemaListTmpPath}" --outputDir ${iosOutputDir}`, - ); - console.log(`Generated provider in: ${iosOutputDir}`); - } - } catch (err) { - console.error(err); - process.exitCode = 1; - } - - // 5. Done! - console.log('\n\n[Codegen] Done.'); - return; -} const appRoot = argv.path; const outputPath = argv.outputPath; -main(appRoot, outputPath); + +executor.execute( + appRoot, + outputPath, + NODE, + CODEGEN_CONFIG_FILENAME, + CODEGEN_CONFIG_KEY, + CODEGEN_CONFIG_FILE_DIR, + CODEGEN_FABRIC_ENABLED, +); From eac6173843f2875945185a009ab2285320877d50 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 3 May 2022 05:41:39 -0700 Subject: [PATCH 3/5] Refactor generate-specs-cli and add tests Summary: This Diff splits the `generate-specs-cli.js` script into: * `generate-specs-cli-executor.js`: which contains the logic to generate the code for iOS. * `generate-specs-cli.js`: which contains the argument parsing logic and invokes the executor. Finally it introduces some tests. ## Changelog [iOS][Changed] - Refactor part of the codegen scripts and add tests. Differential Revision: https://www.internalfb.com/diff/D35892576?entry_point=27 fbshipit-source-id: 8d5c012e15bfc396ada0303e71894a42246ad203 --- scripts/codegen/__test_fixtures__/fixtures.js | 87 ++++++++++++ .../generate-specs-cli-executor-test.js | 90 +++++++++++++ scripts/codegen/codegen-utils.js | 37 +++++ .../codegen/generate-specs-cli-executor.js | 127 ++++++++++++++++++ scripts/generate-specs-cli.js | 123 +---------------- 5 files changed, 343 insertions(+), 121 deletions(-) create mode 100644 scripts/codegen/__test_fixtures__/fixtures.js create mode 100644 scripts/codegen/__tests__/generate-specs-cli-executor-test.js create mode 100644 scripts/codegen/codegen-utils.js create mode 100644 scripts/codegen/generate-specs-cli-executor.js diff --git a/scripts/codegen/__test_fixtures__/fixtures.js b/scripts/codegen/__test_fixtures__/fixtures.js new file mode 100644 index 000000000000..ec6e6188440e --- /dev/null +++ b/scripts/codegen/__test_fixtures__/fixtures.js @@ -0,0 +1,87 @@ +/** + * 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. + * + * @emails oncall+react_native + * @format + */ + +'use-strict'; + +const SCHEMA_TEXT = ` + { + "modules": { + "ColoredView": { + "type": "Component", + "components": { + "ColoredView": { + "extendsProps": [ + { + "type": "ReactNativeBuiltInType", + "knownTypeName": "ReactNativeCoreViewProps" + } + ], + "events": [], + "props": [ + { + "name": "color", + "optional": false, + "typeAnnotation": { + "type": "StringTypeAnnotation", + "default": null + } + } + ], + "commands": [] + } + } + }, + "NativeCalculator": { + "type": "NativeModule", + "aliases": {}, + "spec": { + "properties": [ + { + "name": "add", + "optional": false, + "typeAnnotation": { + "type": "FunctionTypeAnnotation", + "returnTypeAnnotation": { + "type": "PromiseTypeAnnotation" + }, + "params": [ + { + "name": "a", + "optional": false, + "typeAnnotation": { + "type": "NumberTypeAnnotation" + } + }, + { + "name": "b", + "optional": false, + "typeAnnotation": { + "type": "NumberTypeAnnotation" + } + } + ] + } + } + ] + }, + "moduleNames": [ + "Calculator" + ] + } + } +} +`; + +const SCHEMA = JSON.parse(SCHEMA_TEXT); + +module.exports = { + schemaText: SCHEMA_TEXT, + schema: SCHEMA, +}; diff --git a/scripts/codegen/__tests__/generate-specs-cli-executor-test.js b/scripts/codegen/__tests__/generate-specs-cli-executor-test.js new file mode 100644 index 000000000000..41b8d3f2f285 --- /dev/null +++ b/scripts/codegen/__tests__/generate-specs-cli-executor-test.js @@ -0,0 +1,90 @@ +/** + * 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. + * + * @emails oncall+react_native + * @format + */ + +'use strict'; + +const sut = require('../generate-specs-cli-executor'); +const fixtures = require('../__test_fixtures__/fixtures'); + +describe('generateSpec', () => { + it('invokes RNCodegen with the right params', () => { + const platform = 'ios'; + const libraryType = 'all'; + const schemaPath = './'; + const componentsOutputDir = + 'app/ios/build/generated/ios/react/renderer/components/library'; + const modulesOutputDir = 'app/ios/build/generated/ios/./library'; + const outputDirectory = 'app/ios/build/generated/ios'; + const libraryName = 'library'; + const packageName = 'com.library'; + const generators = ['componentsIOS', 'modulesIOS']; + + jest.mock('fs', () => ({ + readFileSync: (path, encoding) => { + expect(path).toBe(schemaPath); + expect(encoding).toBe('utf-8'); + return fixtures.schemaText; + }, + })); + + let mkdirpSyncInvoked = 0; + jest.mock('mkdirp', () => ({ + sync: folder => { + if (mkdirpSyncInvoked === 0) { + expect(folder).toBe(componentsOutputDir); + } + + if (mkdirpSyncInvoked === 1) { + expect(folder).toBe(modulesOutputDir); + } + + if (mkdirpSyncInvoked === 2) { + expect(folder).toBe(outputDirectory); + } + + mkdirpSyncInvoked += 1; + }, + })); + + // We cannot mock directly the `RNCodegen` object because the + // code access the `lib` folder directly and request a file explicitly. + // This makes testing harder than usually. To overcome this, we created a utility + // to retrieve the `Codegen`. By doing that, we can mock the wrapper so that it returns + // an object with the same interface of the `RNCodegen` object. + jest.mock('../codegen-utils', () => ({ + getCodegen: () => ({ + generate: (libraryConfig, generatorConfigs) => { + expect(libraryConfig.libraryName).toBe(libraryName); + expect(libraryConfig.schema).toStrictEqual(fixtures.schema); + expect(libraryConfig.outputDirectory).toBe(outputDirectory); + expect(libraryConfig.packageName).toBe(packageName); + expect(libraryConfig.componentsOutputDir).toBe(componentsOutputDir); + expect(libraryConfig.modulesOutputDir).toBe(modulesOutputDir); + + expect(generatorConfigs.generators).toStrictEqual(generators); + expect(generatorConfigs.test).toBeUndefined(); + }, + }), + })); + + sut.execute( + platform, + schemaPath, + outputDirectory, + libraryName, + packageName, + libraryType, + componentsOutputDir, + modulesOutputDir, + ); + + expect(mkdirpSyncInvoked).toBe(3); + }); +}); diff --git a/scripts/codegen/codegen-utils.js b/scripts/codegen/codegen-utils.js new file mode 100644 index 000000000000..0943555e6a43 --- /dev/null +++ b/scripts/codegen/codegen-utils.js @@ -0,0 +1,37 @@ +/** + * 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'; + +/** + * 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() { + let RNCodegen; + try { + RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js'); + } catch (e) { + RNCodegen = require('../react-native-codegen/lib/generators/RNCodegen.js'); + } + if (!RNCodegen) { + throw 'RNCodegen not found.'; + } + return RNCodegen; +} + +module.exports = { + getCodegen: getCodegen, +}; diff --git a/scripts/codegen/generate-specs-cli-executor.js b/scripts/codegen/generate-specs-cli-executor.js new file mode 100644 index 000000000000..7ce95807b911 --- /dev/null +++ b/scripts/codegen/generate-specs-cli-executor.js @@ -0,0 +1,127 @@ +/** + * 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'; + +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const path = require('path'); +const utils = require('./codegen-utils'); +const RNCodegen = utils.getCodegen(); + +const GENERATORS = { + all: { + android: ['componentsAndroid', 'modulesAndroid'], + ios: ['componentsIOS', 'modulesIOS'], + }, + components: { + android: ['componentsAndroid'], + ios: ['componentsIOS'], + }, + modules: { + android: ['modulesAndroid'], + ios: ['modulesIOS'], + }, +}; + +function deprecated_createOutputDirectoryIfNeeded( + outputDirectory, + libraryName, +) { + if (!outputDirectory) { + outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName); + } + mkdirp.sync(outputDirectory); +} + +function createFolderIfDefined(folder) { + if (folder) { + mkdirp.sync(folder); + } +} + +/** + * 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) { + 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) { + if (GENERATORS[libraryType] == null) { + throw new Error(`Invalid library type. ${libraryType}`); + } +} + +function generateSpec( + platform, + schemaPath, + outputDirectory, + libraryName, + packageName, + libraryType, + componentsOutputDir, + modulesOutputDir, +) { + validateLibraryType(libraryType); + + let schema = readAndParseSchema(schemaPath); + + createFolderIfDefined(componentsOutputDir); + createFolderIfDefined(modulesOutputDir); + deprecated_createOutputDirectoryIfNeeded(outputDirectory, libraryName); + + RNCodegen.generate( + { + libraryName, + schema, + outputDirectory, + packageName, + componentsOutputDir, + modulesOutputDir, + }, + { + 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}`; + mkdirp.sync(jniOutputDirectory); + files + .filter(f => f.endsWith('.h') || f.endsWith('.cpp')) + .forEach(f => { + fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`); + }); + } +} + +module.exports = { + execute: generateSpec, +}; diff --git a/scripts/generate-specs-cli.js b/scripts/generate-specs-cli.js index 91c6039d81f7..3e5bc9549e38 100644 --- a/scripts/generate-specs-cli.js +++ b/scripts/generate-specs-cli.js @@ -9,20 +9,8 @@ 'use strict'; -let RNCodegen; -try { - RNCodegen = require('../packages/react-native-codegen/lib/generators/RNCodegen.js'); -} catch (e) { - RNCodegen = require('react-native-codegen/lib/generators/RNCodegen.js'); - if (!RNCodegen) { - throw 'RNCodegen not found.'; - } -} - -const fs = require('fs'); -const mkdirp = require('mkdirp'); -const path = require('path'); const yargs = require('yargs'); +const executor = require('./codegen/generate-specs-cli-executor'); const argv = yargs .option('p', { @@ -67,115 +55,8 @@ const argv = yargs 'Please provide platform, schema path, and output directory.', ).argv; -const GENERATORS = { - all: { - android: ['componentsAndroid', 'modulesAndroid'], - ios: ['componentsIOS', 'modulesIOS'], - }, - components: { - android: ['componentsAndroid'], - ios: ['componentsIOS'], - }, - modules: { - android: ['modulesAndroid'], - ios: ['modulesIOS'], - }, -}; - -function deprecated_createOutputDirectoryIfNeeded( - outputDirectory, - libraryName, -) { - if (!outputDirectory) { - outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName); - } - mkdirp.sync(outputDirectory); -} - -function createFolderIfDefined(folder) { - if (folder) { - mkdirp.sync(folder); - } -} - -/** - * 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) { - 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) { - if (GENERATORS[libraryType] == null) { - throw new Error(`Invalid library type. ${libraryType}`); - } -} - -function generateSpec( - platform, - schemaPath, - outputDirectory, - libraryName, - packageName, - libraryType, - componentsOutputDir, - modulesOutputDirs, -) { - validateLibraryType(libraryType); - - let schema = readAndParseSchema(schemaPath); - - createFolderIfDefined(componentsOutputDir); - createFolderIfDefined(modulesOutputDirs); - deprecated_createOutputDirectoryIfNeeded(outputDirectory, libraryName); - - RNCodegen.generate( - { - libraryName, - schema, - outputDirectory, - packageName, - componentsOutputDir, - modulesOutputDirs, - }, - { - 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}`; - mkdirp.sync(jniOutputDirectory); - files - .filter(f => f.endsWith('.h') || f.endsWith('.cpp')) - .forEach(f => { - fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`); - }); - } -} - function main() { - generateSpec( + executor.execute( argv.platform, argv.schemaPath, argv.outputDir, From 5ecc43f89d63b246e1e858a59b1dbc88ce98f67f Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 3 May 2022 05:41:39 -0700 Subject: [PATCH 4/5] Update CodeGen to leverage the `outputDir` as suggested in diff review (#33729) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/33729 This PR addresses [this comment](https://www.internalfb.com/diff/D35820848?dst_version_fbid=496290878846487&transaction_fbid=355967423221044). It makes the CodeGen to the `outputDir` as base directory for the codegen. Finally, it updates the unit tests accordingly. ## Changelog [iOS][Changed] - use `outputDir` as base directory for the codegen and remove the possibility to customize the intermediate path. The generated code requires specific paths in the `#include` directive. Differential Revision: D35935282 fbshipit-source-id: f9e0d29d987fdb4f88b37d540bf6e195798c95db --- package.json | 3 ++ packages/react-native-codegen/DEFS.bzl | 4 +- .../src/generators/RNCodegen.js | 15 ++++--- .../generators/__tests__/RNCodegen-test.js | 21 +++++---- .../generate-artifacts-executor-test.js | 42 +++-------------- .../generate-specs-cli-executor-test.js | 22 ++++----- scripts/codegen/codegen-utils.js | 2 +- .../codegen/generate-artifacts-executor.js | 45 +++---------------- .../codegen/generate-specs-cli-executor.js | 22 ++++----- scripts/generate-specs-cli.js | 12 +---- .../react_native_pods_utils/script_phases.sh | 8 ++-- 11 files changed, 68 insertions(+), 128 deletions(-) diff --git a/package.json b/package.json index 3ceb9cd4ebf0..532735d3c480 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,9 @@ "scripts/generate-artifacts.js", "scripts/generate-provider-cli.js", "scripts/generate-specs-cli.js", + "scripts/codegen/codegen-utils.js", + "scripts/codegen/generate-artifacts-executor.js", + "scripts/codegen/generate-specs-cli-executor.js", "scripts/ios-configure-glog.sh", "scripts/xcode/with-environment.sh", "scripts/launchPackager.bat", diff --git a/packages/react-native-codegen/DEFS.bzl b/packages/react-native-codegen/DEFS.bzl index aafc1d2a7a86..6f36f86d87a4 100644 --- a/packages/react-native-codegen/DEFS.bzl +++ b/packages/react-native-codegen/DEFS.bzl @@ -220,14 +220,14 @@ def rn_codegen_modules( # iOS Buck build isn't fully working in OSS, so let's skip it for OSS for now. fb_native.genrule( name = generate_module_hobjcpp_name, - cmd = "cp $(location :{})/{}.h $OUT".format(generate_fixtures_rule_name, name), + cmd = "cp $(location :{})/{}/{}.h $OUT".format(generate_fixtures_rule_name, name, name), out = "{}.h".format(name), labels = ["codegen_rule"], ) fb_native.genrule( name = generate_module_mm_name, - cmd = "cp $(location :{})/{}-generated.mm $OUT".format(generate_fixtures_rule_name, name), + cmd = "cp $(location :{})/{}/{}-generated.mm $OUT".format(generate_fixtures_rule_name, name, name), out = "{}-generated.mm".format(name), labels = ["codegen_rule"], ) diff --git a/packages/react-native-codegen/src/generators/RNCodegen.js b/packages/react-native-codegen/src/generators/RNCodegen.js index d9487e8832fe..dd9cd1283958 100644 --- a/packages/react-native-codegen/src/generators/RNCodegen.js +++ b/packages/react-native-codegen/src/generators/RNCodegen.js @@ -48,8 +48,6 @@ type LibraryOptions = $ReadOnly<{ outputDirectory: string, packageName?: string, // Some platforms have a notion of package, which should be configurable. assumeNonnull: boolean, - componentsOutputDir?: string, // optional for backward compatibility - modulesOutputDir?: string, // optional for backward compatibility }>; type SchemasOptions = $ReadOnly<{ @@ -195,16 +193,21 @@ module.exports = { outputDirectory, packageName, assumeNonnull, - componentsOutputDir, - modulesOutputDir, }: LibraryOptions, {generators, test}: LibraryConfig, ): boolean { schemaValidator.validate(schema); + function composePath(intermediate) { + return path.join(outputDirectory, intermediate, libraryName); + } + + const componentIOSOutput = composePath('react/renderer/components/'); + const modulesIOSOutput = composePath('./'); + const outputFoldersForGenerators = { - componentsIOS: componentsOutputDir ?? outputDirectory, // fallback for backward compatibility - modulesIOS: modulesOutputDir ?? outputDirectory, // fallback for backward compatibility + componentsIOS: componentIOSOutput, + modulesIOS: modulesIOSOutput, descriptors: outputDirectory, events: outputDirectory, props: outputDirectory, diff --git a/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js b/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js index a529379d72ee..4cc51277d1d0 100644 --- a/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js +++ b/packages/react-native-codegen/src/generators/__tests__/RNCodegen-test.js @@ -15,13 +15,17 @@ const rnCodegen = require('../RNCodegen.js'); const fixture = require('../__test_fixtures__/fixtures.js'); const path = require('path'); -const invalidDirectory = 'invalid/'; +const outputDirectory = 'tmp/out/'; const packageName = 'na'; -const componentsOutputDir = 'react/renderer/components/library'; -const modulesOutputDir = 'library'; describe('RNCodegen.generate', () => { - it('when type `all`', () => { + beforeEach(() => { + jest.resetModules(); + }); + it('when type `all`, with default paths', () => { + const componentsOutputDir = 'react/renderer/components/library'; + const modulesOutputDir = 'library'; + const expectedPaths = { 'library.h': modulesOutputDir, 'library-generated.mm': modulesOutputDir, @@ -43,7 +47,10 @@ describe('RNCodegen.generate', () => { let receivedDir = path.dirname(location); let receivedBasename = path.basename(location); - let expectedPath = expectedPaths[receivedBasename]; + let expectedPath = path.join( + outputDirectory, + expectedPaths[receivedBasename], + ); expect(receivedDir).toEqual(expectedPath); }, })); @@ -52,11 +59,9 @@ describe('RNCodegen.generate', () => { { libraryName: 'library', schema: fixture.all, - outputDirectory: invalidDirectory, + outputDirectory: outputDirectory, packageName: packageName, assumeNonnull: true, - componentsOutputDir: componentsOutputDir, - modulesOutputDir: modulesOutputDir, }, { generators: ['componentsIOS', 'modulesIOS'], diff --git a/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/scripts/codegen/__tests__/generate-artifacts-executor-test.js index 6b7addd60288..ed106ec820ef 100644 --- a/scripts/codegen/__tests__/generate-artifacts-executor-test.js +++ b/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -24,38 +24,19 @@ describe('generateCode', () => { const rnRoot = path.join(__dirname, '../..'); const libraryType = 'all'; - const tmpComponentOutDirs = path.join(tmpDir, 'out', 'components'); - const tmpNativeModulesOutDir = path.join(tmpDir, 'out', 'nativeModules'); - const iOSComponentOutDirs = path.join( - iosOutputDir, - 'react/renderer/components', - library.config.name, - ); - const iOSNativeModulesOutDir = path.join( - iosOutputDir, - './', - library.config.name, - ); + const tmpOutDir = path.join(tmpDir, 'out'); // mock used functions let mkdirSyncInvocationCount = 0; jest.mock('fs', () => ({ - readdirSync: dirpath => { - return ['test/dir']; //we only require to return something, so that the folder is not empty. - }, mkdirSync: (location, config) => { if (mkdirSyncInvocationCount === 0) { - expect(location).toEqual(tmpComponentOutDirs); + expect(location).toEqual(tmpOutDir); } if (mkdirSyncInvocationCount === 1) { - expect(location).toEqual(tmpNativeModulesOutDir); - } - if (mkdirSyncInvocationCount === 2) { - expect(location).toEqual(iOSComponentOutDirs); - } - if (mkdirSyncInvocationCount === 3) { - expect(location).toEqual(iOSNativeModulesOutDir); + expect(location).toEqual(iosOutputDir); } + mkdirSyncInvocationCount += 1; }, })); @@ -70,23 +51,14 @@ describe('generateCode', () => { )} \ --platform ios \ --schemaPath ${pathToSchema} \ - --outputDir ${tmpNativeModulesOutDir} \ - --componentsOutputDir ${tmpComponentOutDirs} \ - --modulesOutputDirs ${tmpNativeModulesOutDir} \ + --outputDir ${tmpOutDir} \ --libraryName ${library.config.name} \ --libraryType ${libraryType}`; expect(command).toEqual(expectedCommand); } if (execSyncInvocationCount === 1) { - expect(command).toEqual( - `cp -R ${tmpComponentOutDirs}/* ${iOSComponentOutDirs}`, - ); - } - if (execSyncInvocationCount === 2) { - expect(command).toEqual( - `cp -R ${tmpNativeModulesOutDir}/* ${iOSNativeModulesOutDir}`, - ); + expect(command).toEqual(`cp -R ${tmpOutDir}/* ${iosOutputDir}`); } execSyncInvocationCount += 1; @@ -94,6 +66,6 @@ describe('generateCode', () => { })); underTest._generateCode(iosOutputDir, library, tmpDir, node, pathToSchema); - expect(mkdirSyncInvocationCount).toBe(4); + expect(mkdirSyncInvocationCount).toBe(2); }); }); diff --git a/scripts/codegen/__tests__/generate-specs-cli-executor-test.js b/scripts/codegen/__tests__/generate-specs-cli-executor-test.js index 41b8d3f2f285..ce63546a566b 100644 --- a/scripts/codegen/__tests__/generate-specs-cli-executor-test.js +++ b/scripts/codegen/__tests__/generate-specs-cli-executor-test.js @@ -12,16 +12,20 @@ const sut = require('../generate-specs-cli-executor'); const fixtures = require('../__test_fixtures__/fixtures'); +const path = require('path'); describe('generateSpec', () => { it('invokes RNCodegen with the right params', () => { const platform = 'ios'; const libraryType = 'all'; const schemaPath = './'; - const componentsOutputDir = - 'app/ios/build/generated/ios/react/renderer/components/library'; - const modulesOutputDir = 'app/ios/build/generated/ios/./library'; - const outputDirectory = 'app/ios/build/generated/ios'; + const componentsOutputDir = path.normalize( + 'app/ios/build/generated/ios/react/renderer/components/library', + ); + const modulesOutputDir = path.normalize( + 'app/ios/build/generated/ios/library', + ); + const outputDirectory = path.normalize('app/ios/build/generated/ios'); const libraryName = 'library'; const packageName = 'com.library'; const generators = ['componentsIOS', 'modulesIOS']; @@ -38,15 +42,15 @@ describe('generateSpec', () => { jest.mock('mkdirp', () => ({ sync: folder => { if (mkdirpSyncInvoked === 0) { - expect(folder).toBe(componentsOutputDir); + expect(folder).toBe(outputDirectory); } if (mkdirpSyncInvoked === 1) { - expect(folder).toBe(modulesOutputDir); + expect(folder).toBe(componentsOutputDir); } if (mkdirpSyncInvoked === 2) { - expect(folder).toBe(outputDirectory); + expect(folder).toBe(modulesOutputDir); } mkdirpSyncInvoked += 1; @@ -65,8 +69,6 @@ describe('generateSpec', () => { expect(libraryConfig.schema).toStrictEqual(fixtures.schema); expect(libraryConfig.outputDirectory).toBe(outputDirectory); expect(libraryConfig.packageName).toBe(packageName); - expect(libraryConfig.componentsOutputDir).toBe(componentsOutputDir); - expect(libraryConfig.modulesOutputDir).toBe(modulesOutputDir); expect(generatorConfigs.generators).toStrictEqual(generators); expect(generatorConfigs.test).toBeUndefined(); @@ -81,8 +83,6 @@ describe('generateSpec', () => { libraryName, packageName, libraryType, - componentsOutputDir, - modulesOutputDir, ); expect(mkdirpSyncInvoked).toBe(3); diff --git a/scripts/codegen/codegen-utils.js b/scripts/codegen/codegen-utils.js index 0943555e6a43..469f426bb79b 100644 --- a/scripts/codegen/codegen-utils.js +++ b/scripts/codegen/codegen-utils.js @@ -24,7 +24,7 @@ function getCodegen() { try { RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js'); } catch (e) { - RNCodegen = require('../react-native-codegen/lib/generators/RNCodegen.js'); + RNCodegen = require('react-native-codegen/lib/generators/RNCodegen.js'); } if (!RNCodegen) { throw 'RNCodegen not found.'; diff --git a/scripts/codegen/generate-artifacts-executor.js b/scripts/codegen/generate-artifacts-executor.js index 281ed411f384..94278756326e 100644 --- a/scripts/codegen/generate-artifacts-executor.js +++ b/scripts/codegen/generate-artifacts-executor.js @@ -38,10 +38,6 @@ function executeNodeScript(node, script) { execSync(`${node} ${script}`); } -function isDirEmpty(dirPath) { - return fs.readdirSync(dirPath).length === 0; -} - function isAppRootValid(appRootDir) { if (appRootDir == null) { console.error('Missing path to React Native application'); @@ -212,57 +208,28 @@ function generateSchema(tmpDir, library, node, codegenCliPath) { } function generateCode(iosOutputDir, library, tmpDir, node, pathToSchema) { - function composePath(intermediate) { - return path.join(iosOutputDir, intermediate, library.config.name); - } - - const outputDirsIOS = { - components: composePath('react/renderer/components'), - nativeModules: composePath('./'), - }; - - const tempOutputDirs = { - components: path.join(tmpDir, 'out', 'components'), - nativeModules: path.join(tmpDir, 'out', 'nativeModules'), - }; - // ...then generate native code artifacts. const libraryTypeArg = library.config.type ? `--libraryType ${library.config.type}` : ''; - Object.entries(tempOutputDirs).forEach(([type, dirPath]) => { - fs.mkdirSync(dirPath, {recursive: true}); - }); - - const deprecated_outputDir = - library.config.type === 'components' - ? tempOutputDirs.components - : tempOutputDirs.nativeModules; + const tmpOutputDir = path.join(tmpDir, 'out'); + fs.mkdirSync(tmpOutputDir, {recursive: true}); executeNodeScript( node, `${path.join(RN_ROOT, 'scripts', 'generate-specs-cli.js')} \ --platform ios \ --schemaPath ${pathToSchema} \ - --outputDir ${deprecated_outputDir} \ - --componentsOutputDir ${tempOutputDirs.components} \ - --modulesOutputDirs ${tempOutputDirs.nativeModules} \ + --outputDir ${tmpOutputDir} \ --libraryName ${library.config.name} \ ${libraryTypeArg}`, ); // Finally, copy artifacts to the final output directory. - Object.entries(outputDirsIOS).forEach(([type, dirPath]) => { - const outDir = tempOutputDirs[type]; - if (isDirEmpty(outDir)) { - return; // cp fails if we try to copy something empty. - } - - fs.mkdirSync(dirPath, {recursive: true}); - execSync(`cp -R ${outDir}/* ${dirPath}`); - console.log(`[Codegen] Generated artifacts: ${dirPath}`); - }); + fs.mkdirSync(iosOutputDir, {recursive: true}); + execSync(`cp -R ${tmpOutputDir}/* ${iosOutputDir}`); + console.log(`[Codegen] Generated artifacts: ${iosOutputDir}`); } function generateNativeCodegenFiles( diff --git a/scripts/codegen/generate-specs-cli-executor.js b/scripts/codegen/generate-specs-cli-executor.js index 7ce95807b911..fe37b07fd9db 100644 --- a/scripts/codegen/generate-specs-cli-executor.js +++ b/scripts/codegen/generate-specs-cli-executor.js @@ -30,10 +30,7 @@ const GENERATORS = { }, }; -function deprecated_createOutputDirectoryIfNeeded( - outputDirectory, - libraryName, -) { +function createOutputDirectoryIfNeeded(outputDirectory, libraryName) { if (!outputDirectory) { outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName); } @@ -81,16 +78,21 @@ function generateSpec( libraryName, packageName, libraryType, - componentsOutputDir, - modulesOutputDir, ) { validateLibraryType(libraryType); let schema = readAndParseSchema(schemaPath); - createFolderIfDefined(componentsOutputDir); - createFolderIfDefined(modulesOutputDir); - deprecated_createOutputDirectoryIfNeeded(outputDirectory, libraryName); + createOutputDirectoryIfNeeded(outputDirectory, libraryName); + function composePath(intermediate) { + return path.join(outputDirectory, intermediate, libraryName); + } + + // These are hardcoded and should not be changed. + // The codegen creates some C++ code with #include directive + // which uses these paths. Those directive are not customizable yet. + createFolderIfDefined(composePath('react/renderer/components/')); + createFolderIfDefined(composePath('./')); RNCodegen.generate( { @@ -98,8 +100,6 @@ function generateSpec( schema, outputDirectory, packageName, - componentsOutputDir, - modulesOutputDir, }, { generators: GENERATORS[libraryType][platform], diff --git a/scripts/generate-specs-cli.js b/scripts/generate-specs-cli.js index 3e5bc9549e38..19862d20131d 100644 --- a/scripts/generate-specs-cli.js +++ b/scripts/generate-specs-cli.js @@ -24,7 +24,7 @@ const argv = yargs .option('o', { alias: 'outputDir', describe: - 'DEPRECATED - Path to directory where native code source files should be saved.', + 'Path to the root directory where native code source files should be saved.', }) .option('n', { alias: 'libraryName', @@ -41,14 +41,6 @@ const argv = yargs describe: 'all, components, or modules.', default: 'all', }) - .option('c', { - alias: 'componentsOutputDir', - describe: 'Output directory for the codeGen for Fabric Components', - }) - .option('m', { - alias: 'modulesOutputDirs', - describe: 'Output directory for the codeGen for TurboModules', - }) .usage('Usage: $0 ') .demandOption( ['platform', 'schemaPath', 'outputDir'], @@ -63,8 +55,6 @@ function main() { argv.libraryName, argv.javaPackageName, argv.libraryType, - argv.componentsOutputDir, - argv.modulesOutputDirs, ); } diff --git a/scripts/react_native_pods_utils/script_phases.sh b/scripts/react_native_pods_utils/script_phases.sh index 2e54a05de52d..6c41ce1cbaa2 100755 --- a/scripts/react_native_pods_utils/script_phases.sh +++ b/scripts/react_native_pods_utils/script_phases.sh @@ -87,12 +87,12 @@ generateCodegenArtifactsFromSchema () { describe "Generating codegen artifacts from schema" pushd "$RN_DIR" >/dev/null || exit 1 if [ "$RCT_SCRIPT_LIBRARY_TYPE" = "all" ]; then - runSpecCodegen "$TEMP_OUTPUT_DIR/$RCT_SCRIPT_CODEGEN_MODULE_DIR/$RCT_SCRIPT_LIBRARY_NAME" "modules" - runSpecCodegen "$TEMP_OUTPUT_DIR/$RCT_SCRIPT_CODEGEN_COMPONENT_DIR/$RCT_SCRIPT_LIBRARY_NAME" "components" + runSpecCodegen "$TEMP_OUTPUT_DIR" "modules" + runSpecCodegen "$TEMP_OUTPUT_DIR" "components" elif [ "$RCT_SCRIPT_LIBRARY_TYPE" = "components" ]; then - runSpecCodegen "$TEMP_OUTPUT_DIR/$RCT_SCRIPT_CODEGEN_COMPONENT_DIR/$RCT_SCRIPT_LIBRARY_NAME" "$RCT_SCRIPT_LIBRARY_TYPE" + runSpecCodegen "$TEMP_OUTPUT_DIR" "$RCT_SCRIPT_LIBRARY_TYPE" elif [ "$RCT_SCRIPT_LIBRARY_TYPE" = "modules" ]; then - runSpecCodegen "$TEMP_OUTPUT_DIR/$RCT_SCRIPT_CODEGEN_MODULE_DIR/$RCT_SCRIPT_LIBRARY_NAME" "$RCT_SCRIPT_LIBRARY_TYPE" + runSpecCodegen "$TEMP_OUTPUT_DIR" "$RCT_SCRIPT_LIBRARY_TYPE" fi popd >/dev/null || exit 1 } From 07c2c232999f4ffa13ed09672cfecbc334414308 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 3 May 2022 05:42:16 -0700 Subject: [PATCH 5/5] Bump React Native Codegen to 0.0.16 Summary: Yet another bump of the Codegen to ship new features for RN 0.69 Changelog: [Internal] [Changed] - Bump React Native Codegen to 0.0.16 Reviewed By: cipolleschi Differential Revision: D36096803 fbshipit-source-id: 57ae9d463a39b92876477dc8c295685c3108b1a6 --- packages/react-native-codegen/package.json | 2 +- repo-config/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-codegen/package.json b/packages/react-native-codegen/package.json index 17c796016add..eeaecf49ea9a 100644 --- a/packages/react-native-codegen/package.json +++ b/packages/react-native-codegen/package.json @@ -1,6 +1,6 @@ { "name": "react-native-codegen", - "version": "0.0.15", + "version": "0.0.16", "description": "⚛️ Code generation tools for React Native", "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-codegen", "repository": { diff --git a/repo-config/package.json b/repo-config/package.json index f227f97ad58b..6d277e3bec6e 100644 --- a/repo-config/package.json +++ b/repo-config/package.json @@ -43,7 +43,7 @@ "mkdirp": "^0.5.1", "prettier": "^2.4.1", "react": "17.0.2", - "react-native-codegen": "^0.0.15", + "react-native-codegen": "^0.0.16", "react-test-renderer": "17.0.2", "shelljs": "^0.8.5", "signedsource": "^1.0.0",