From 17226c41a6bca20f03f12725eac758327f385289 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Thu, 19 May 2022 03:22:47 -0700 Subject: [PATCH] Align Codegen between iOS and Android (#33864) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/33864 This Diff aligns the way in which iOS and Android codegen the modules and components. Android takes all the JS in the project root folder and creates components starting from there. iOS used to required to specify a specific path for each component, within a JSON field called `libraries`. This Diff let iOS work in the same way as android does **Backward compatibility:** This diff still support the old way for iOS, but we are deprecating it. ## Changelog [iOS][Added] - Support codegen from a single folder Reviewed By: cortinico Differential Revision: D36473005 fbshipit-source-id: f752f1e80cf95fa567514b1cd7c24c42a052b0a6 --- packages/rn-tester/BUCK | 4 +- .../ios/RNTMyNativeViewComponentView.mm | 8 +- packages/rn-tester/package.json | 19 +-- scripts/codegen/__test_fixtures__/fixtures.js | 45 +++++ .../generate-artifacts-executor-test.js | 132 +++++++++++++++ .../codegen/generate-artifacts-executor.js | 156 ++++++++++++------ scripts/react_native_pods.rb | 16 +- 7 files changed, 309 insertions(+), 71 deletions(-) diff --git a/packages/rn-tester/BUCK b/packages/rn-tester/BUCK index f9b0023a2522..6a1137be3e29 100644 --- a/packages/rn-tester/BUCK +++ b/packages/rn-tester/BUCK @@ -65,7 +65,7 @@ rn_library( "pfh:ReactNative_CommonInfrastructurePlaceholder", "supermodule:xplat/default/public.react_native.playground", ], - native_component_spec_name = "MyNativeViewSpec", + native_component_spec_name = "AppSpecs", skip_processors = True, visibility = ["PUBLIC"], deps = [ @@ -317,7 +317,7 @@ rn_xplat_cxx_library2( reexport_all_header_dependencies = False, visibility = ["PUBLIC"], deps = [ - ":generated_components-MyNativeViewSpec", + ":generated_components-AppSpecs", "//xplat/js/react-native-github:RCTFabricComponentViewsBase", ], ) diff --git a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm index 7c5d0ac794c5..7fe66d4050d7 100644 --- a/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm +++ b/packages/rn-tester/NativeComponentExample/ios/RNTMyNativeViewComponentView.mm @@ -7,10 +7,10 @@ #import "RNTMyNativeViewComponentView.h" -#import -#import -#import -#import +#import +#import +#import +#import #import "RCTFabricComponentsPlugins.h" diff --git a/packages/rn-tester/package.json b/packages/rn-tester/package.json index d3de2629d0f6..4a34e4655626 100644 --- a/packages/rn-tester/package.json +++ b/packages/rn-tester/package.json @@ -31,21 +31,8 @@ "ws": "^6.1.4" }, "codegenConfig": { - "libraries": [ - { - "name": "ScreenshotManagerSpec", - "type": "modules", - "ios": {}, - "android": {}, - "jsSrcsDir": "NativeModuleExample" - }, - { - "name": "MyNativeViewSpec", - "type": "components", - "ios": {}, - "android": {}, - "jsSrcsDir": "NativeComponentExample/js" - } - ] + "name": "AppSpecs", + "type": "all", + "jsSrcsDir": "." } } diff --git a/scripts/codegen/__test_fixtures__/fixtures.js b/scripts/codegen/__test_fixtures__/fixtures.js index ec6e6188440e..86adfa0cd594 100644 --- a/scripts/codegen/__test_fixtures__/fixtures.js +++ b/scripts/codegen/__test_fixtures__/fixtures.js @@ -10,6 +10,48 @@ 'use-strict'; +const SINGLE_LIBRARY_CODEGEN_CONFIG = { + codegenConfig: { + libraries: [ + { + name: 'react-native', + type: 'all', + jsSrcsDir: '.', + }, + ], + }, +}; + +const MULTIPLE_LIBRARIES_CODEGEN_CONFIG = { + codegenConfig: { + libraries: [ + { + name: 'react-native', + type: 'all', + jsSrcsDir: '.', + }, + { + name: 'my-component', + type: 'components', + jsSrcsDir: 'component/js', + }, + { + name: 'my-module', + type: 'module', + jsSrcsDir: 'module/js', + }, + ], + }, +}; + +const NO_LIBRARIES_CONFIG_FILE = { + codegenConfig: { + name: 'AppModules', + type: 'all', + jsSrcsDir: '.', + }, +}; + const SCHEMA_TEXT = ` { "modules": { @@ -84,4 +126,7 @@ const SCHEMA = JSON.parse(SCHEMA_TEXT); module.exports = { schemaText: SCHEMA_TEXT, schema: SCHEMA, + noLibrariesConfigFile: NO_LIBRARIES_CONFIG_FILE, + singleLibraryCodegenConfig: SINGLE_LIBRARY_CODEGEN_CONFIG, + multipleLibrariesCodegenConfig: MULTIPLE_LIBRARIES_CODEGEN_CONFIG, }; diff --git a/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/scripts/codegen/__tests__/generate-artifacts-executor-test.js index ed106ec820ef..b53806d26f77 100644 --- a/scripts/codegen/__tests__/generate-artifacts-executor-test.js +++ b/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -11,8 +11,13 @@ 'use strict'; const underTest = require('../generate-artifacts-executor'); +const fixtures = require('../__test_fixtures__/fixtures'); const path = require('path'); +const codegenConfigKey = 'codegenConfig'; +const reactNativeDependencyName = 'react-native'; +const rootPath = path.join(__dirname, '../../..'); + describe('generateCode', () => { it('executeNodes with the right arguents', () => { // Define variables and expected values @@ -69,3 +74,130 @@ describe('generateCode', () => { expect(mkdirSyncInvocationCount).toBe(2); }); }); + +describe('extractLibrariesFromJSON', () => { + it('throws if in react-native and no dependencies found', () => { + let libraries = []; + let configFile = {}; + expect(() => { + underTest._extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + ); + }).toThrow(); + }); + + it('it skips if not into react-native and no dependencies found', () => { + let libraries = []; + let configFile = {}; + + underTest._extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + 'some-node-module', + 'node_modules/some', + ); + expect(libraries.length).toBe(0); + }); + + it('extracts a single dependency when config has no libraries', () => { + let libraries = []; + let configFile = fixtures.noLibrariesConfigFile; + underTest._extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + 'my-app', + '.', + ); + expect(libraries.length).toBe(1); + expect(libraries[0]).toEqual({ + library: 'my-app', + config: { + name: 'AppModules', + type: 'all', + jsSrcsDir: '.', + }, + libraryPath: '.', + }); + }); + + it("extract codegenConfig when it's empty", () => { + const configFile = {codegenConfig: {libraries: []}}; + let libraries = []; + underTest._extractLibrariesFromJSON( + configFile, + codegenConfigKey, + libraries, + reactNativeDependencyName, + rootPath, + ); + expect(libraries.length).toBe(0); + }); + + it('extract codegenConfig when dependency is one', () => { + const configFile = fixtures.singleLibraryCodegenConfig; + let libraries = []; + underTest._extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + reactNativeDependencyName, + rootPath, + ); + expect(libraries.length).toBe(1); + expect(libraries[0]).toEqual({ + library: reactNativeDependencyName, + config: { + name: 'react-native', + type: 'all', + jsSrcsDir: '.', + }, + libraryPath: rootPath, + }); + }); + + it('extract codegenConfig with multiple dependencies', () => { + const configFile = fixtures.multipleLibrariesCodegenConfig; + const myDependency = 'my-dependency'; + const myDependencyPath = path.join(__dirname, myDependency); + let libraries = []; + underTest._extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + myDependency, + myDependencyPath, + ); + expect(libraries.length).toBe(3); + expect(libraries[0]).toEqual({ + library: myDependency, + config: { + name: 'react-native', + type: 'all', + jsSrcsDir: '.', + }, + libraryPath: myDependencyPath, + }); + expect(libraries[1]).toEqual({ + library: myDependency, + config: { + name: 'my-component', + type: 'components', + jsSrcsDir: 'component/js', + }, + libraryPath: myDependencyPath, + }); + expect(libraries[2]).toEqual({ + library: myDependency, + config: { + name: 'my-module', + type: 'module', + jsSrcsDir: 'module/js', + }, + libraryPath: myDependencyPath, + }); + }); +}); diff --git a/scripts/codegen/generate-artifacts-executor.js b/scripts/codegen/generate-artifacts-executor.js index 94278756326e..784f44a2631a 100644 --- a/scripts/codegen/generate-artifacts-executor.js +++ b/scripts/codegen/generate-artifacts-executor.js @@ -52,6 +52,97 @@ function readPackageJSON(appRootDir) { } // Reading Libraries +function extractLibrariesFromConfigurationArray( + configFile, + codegenConfigKey, + libraries, + dependency, + dependencyPath, +) { + console.log(`[Codegen] Found ${dependency}`); + configFile[codegenConfigKey].libraries.forEach(config => { + const libraryConfig = { + library: dependency, + config, + libraryPath: dependencyPath, + }; + libraries.push(libraryConfig); + }); +} + +function extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + dependency, + dependencyPath, +) { + var isBlocking = false; + if (dependency == null) { + dependency = REACT_NATIVE_DEPENDENCY_NAME; + dependencyPath = RN_ROOT; + // If we are exploring the ReactNative libraries, we want to raise an error + // if the codegen is not properly configured. + isBlocking = true; + } + + if (configFile[codegenConfigKey] == null) { + if (isBlocking) { + throw `[Codegen] Error: Could not find codegen config for ${dependency} .`; + } + return; + } + + if (configFile[codegenConfigKey].libraries == null) { + console.log(`[Codegen] Found ${dependency}`); + var config = configFile[codegenConfigKey]; + libraries.push({ + library: dependency, + config, + libraryPath: dependencyPath, + }); + } else { + console.log(`[Codegen] CodegenConfig Deprecated Setup for ${dependency}. + The configuration file still contains the codegen in the libraries array. + If possible, replace it with a single object. + `); + console.debug(`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": "." + } + } + `); + extractLibrariesFromConfigurationArray( + configFile, + codegenConfigKey, + libraries, + dependency, + dependencyPath, + ); + } +} function handleReactNativeCodeLibraries( libraries, @@ -66,21 +157,7 @@ function handleReactNativeCodeLibraries( 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); - }); + extractLibrariesFromJSON(reactNativeConfigFile, libraries, codegenConfigKey); } function handleThirdPartyLibraries( @@ -109,20 +186,13 @@ function handleThirdPartyLibraries( ); 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); - }); - } + extractLibrariesFromJSON( + configFile, + libraries, + codegenConfigKey, + dependency, + codegenConfigFileDir, + ); } }); } @@ -137,21 +207,13 @@ function handleInAppLibraries( '\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); - }); - } + extractLibrariesFromJSON( + pkgJson, + libraries, + codegenConfigKey, + pkgJson.name, + appRootDir, + ); } // CodeGen @@ -377,6 +439,8 @@ function execute( module.exports = { execute: execute, - _executeNodeScript: executeNodeScript, // exported for testing purposes only - _generateCode: generateCode, // exported for testing purposes only + // exported for testing purposes only: + _extractLibrariesFromJSON: extractLibrariesFromJSON, + _executeNodeScript: executeNodeScript, + _generateCode: generateCode, }; diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index d17d089df14b..52a18c6a3e2a 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -356,10 +356,20 @@ def get_react_codegen_script_phases(options={}) app_package_path = File.join(app_path, 'package.json') app_codegen_config = get_codegen_config_from_file(app_package_path, config_key) file_list = [] - app_codegen_config['libraries'].each do |library| - library_dir = File.join(app_path, library['jsSrcsDir']) - file_list.concat (`find #{library_dir} -type f \\( -name "Native*.js" -or -name "*NativeComponent.js" \\)`.split("\n").sort) + if app_codegen_config['libraries'] then + Pod::UI.warn '[Deprecated] You are using the old `libraries` array to list all your codegen.\nThis method will be removed in the future.\nUpdate your `package.json` with a single object.' + app_codegen_config['libraries'].each do |library| + library_dir = File.join(app_path, library['jsSrcsDir']) + file_list.concat (`find #{library_dir} -type f \\( -name "Native*.js" -or -name "*NativeComponent.js" \\)`.split("\n").sort) + end + elsif app_codegen_config['jsSrcsDir'] then + codegen_dir = File.join(app_path, app_codegen_config['jsSrcsDir']) + file_list.concat (`find #{codegen_dir} -type f \\( -name "Native*.js" -or -name "*NativeComponent.js" \\)`.split("\n").sort) + else + Pod::UI.warn '[Error] Codegen not properly configured. Please add the `codegenConf` entry to your `package.json`' + exit 1 end + input_files = file_list.map { |filename| "${PODS_ROOT}/../#{Pathname.new(filename).realpath().relative_path_from(Pod::Config.instance.installation_root)}" } # Add a script phase to trigger generate artifact.