diff --git a/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json new file mode 100644 index 00000000000..4157d919a32 --- /dev/null +++ b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/lib/test-library/package.json @@ -0,0 +1,39 @@ +{ + "name": "test-library", + "version": "0.0.0", + "private": true, + "scripts": { + }, + "dependencies": { + }, + "devDependencies": { + }, + "codegenConfig": { + "name": "TestLibraryConfig", + "type": "all", + "android": { + "javaPackageName": "com.testLibrary" + }, + "ios": { + "modulesConformingToProtocol": { + "RCTImageURLLoader": [ + "RCTTestLibraryImageURLLoader" + ], + "RCTURLRequestHandler": [ + "RCTTestLibraryURLRequestHandler" + ], + "RCTImageDataDecoder": [ + "RCTTestLibraryImageDataDecoder" + ] + }, + "modulesProvider": { + "TestLibraryImageURLLoader": "RCTTestLibraryImageURLLoader", + "TestLibraryURLRequestHandler": "RCTTestLibraryURLRequestHandler", + "TestLibraryImageDataDecoder": "RCTTestLibraryImageDataDecoder" + }, + "componentProvider": { + "TestLibraryComponent": "RCTTestLibraryComponentClass" + } + } + } +} diff --git a/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json new file mode 100644 index 00000000000..f794a6b03d7 --- /dev/null +++ b/packages/react-native/scripts/codegen/__test_fixtures__/test-app/package.json @@ -0,0 +1,40 @@ +{ + "name": "test-app", + "version": "0.0.0", + "private": true, + "scripts": { + }, + "dependencies": { + "test-library": "./lib/test-library" + }, + "devDependencies": { + }, + "codegenConfig": { + "name": "TestAppConfig", + "type": "all", + "android": { + "javaPackageName": "com.testApp" + }, + "ios": { + "modulesConformingToProtocol": { + "RCTImageURLLoader": [ + "RCTTestAppImageURLLoader" + ], + "RCTURLRequestHandler": [ + "RCTTestAppURLRequestHandler" + ], + "RCTImageDataDecoder": [ + "RCTTestAppImageDataDecoder" + ] + }, + "modulesProvider": { + "TestAppImageURLLoader": "RCTTestAppImageURLLoader", + "TestAppURLRequestHandler": "RCTTestAppURLRequestHandler", + "TestAppImageDataDecoder": "RCTTestAppImageDataDecoder" + }, + "componentProvider": { + "TestAppComponent": "RCTTestAppComponentClass" + } + } + } +} diff --git a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap new file mode 100644 index 00000000000..65fbda45a44 --- /dev/null +++ b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap @@ -0,0 +1,441 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute "RCTAppDependencyProvider.h" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +#if __has_include() +#import +#elif __has_include() +#import +#else +#import \\"RCTDependencyProvider.h\\" +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTAppDependencyProvider : NSObject + +@end + +NS_ASSUME_NONNULL_END +" +`; + +exports[`execute "RCTAppDependencyProvider.mm" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import \\"RCTAppDependencyProvider.h\\" +#import +#import +#import + +@implementation RCTAppDependencyProvider + +- (nonnull NSArray *)URLRequestHandlerClassNames { + return RCTModulesConformingToProtocolsProvider.URLRequestHandlerClassNames; +} + +- (nonnull NSArray *)imageDataDecoderClassNames { + return RCTModulesConformingToProtocolsProvider.imageDataDecoderClassNames; +} + +- (nonnull NSArray *)imageURLLoaderClassNames { + return RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames; +} + +- (nonnull NSDictionary> *)thirdPartyFabricComponents { + return RCTThirdPartyComponentsProvider.thirdPartyFabricComponents; +} + +- (nonnull NSDictionary> *)moduleProviders { + return RCTModuleProviders.moduleProviders; +} + +@end +" +`; + +exports[`execute "RCTModuleProviders.h" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@protocol RCTModuleProvider; + +@interface RCTModuleProviders: NSObject + ++ (NSDictionary> *)moduleProviders; + +@end +" +`; + +exports[`execute "RCTModuleProviders.mm" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import \\"RCTModuleProviders.h\\" +#import +#import + +@implementation RCTModuleProviders + ++ (NSDictionary> *)moduleProviders +{ + static NSDictionary> *providers = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + NSDictionary * moduleMapping = @{ + @\\"TestAppImageURLLoader\\": @\\"RCTTestAppImageURLLoader\\", // test-app + @\\"TestAppURLRequestHandler\\": @\\"RCTTestAppURLRequestHandler\\", // test-app + @\\"TestAppImageDataDecoder\\": @\\"RCTTestAppImageDataDecoder\\", // test-app + @\\"TestLibraryImageURLLoader\\": @\\"RCTTestLibraryImageURLLoader\\", // test-library + @\\"TestLibraryURLRequestHandler\\": @\\"RCTTestLibraryURLRequestHandler\\", // test-library + @\\"TestLibraryImageDataDecoder\\": @\\"RCTTestLibraryImageDataDecoder\\", // test-library + }; + + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:moduleMapping.count]; + + for (NSString *key in moduleMapping) { + NSString * moduleProviderName = moduleMapping[key]; + Class klass = NSClassFromString(moduleProviderName); + if (!klass) { + RCTLogError(@\\"Module provider %@ cannot be found in the runtime\\", moduleProviderName); + continue; + } + + id instance = [klass new]; + if (![instance respondsToSelector:@selector(getTurboModule:)]) { + RCTLogError(@\\"Module provider %@ does not conform to RCTModuleProvider\\", moduleProviderName); + continue; + } + + [dict setObject:instance forKey:key]; + } + + providers = dict; + }); + + return providers; +} + +@end +" +`; + +exports[`execute "RCTModulesConformingToProtocolsProvider.h" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@interface RCTModulesConformingToProtocolsProvider: NSObject + ++(NSArray *)imageURLLoaderClassNames; + ++(NSArray *)imageDataDecoderClassNames; + ++(NSArray *)URLRequestHandlerClassNames; + +@end +" +`; + +exports[`execute "RCTModulesConformingToProtocolsProvider.mm" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import \\"RCTModulesConformingToProtocolsProvider.h\\" + +@implementation RCTModulesConformingToProtocolsProvider + ++(NSArray *)imageURLLoaderClassNames +{ + static NSArray *classNames = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + classNames = @[ + @\\"RCTTestAppImageURLLoader\\", + @\\"RCTTestLibraryImageURLLoader\\" + ]; + }); + + return classNames; +} + ++(NSArray *)imageDataDecoderClassNames +{ + static NSArray *classNames = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + classNames = @[ + @\\"RCTTestAppImageDataDecoder\\", + @\\"RCTTestLibraryImageDataDecoder\\" + ]; + }); + + return classNames; +} + ++(NSArray *)URLRequestHandlerClassNames +{ + static NSArray *classNames = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + classNames = @[ + @\\"RCTTestAppURLRequestHandler\\", + @\\"RCTTestLibraryURLRequestHandler\\" + ]; + }); + + return classNames; +} + +@end +" +`; + +exports[`execute "RCTThirdPartyComponentsProvider.h" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +@protocol RCTComponentViewProtocol; + +@interface RCTThirdPartyComponentsProvider: NSObject + ++ (NSDictionary> *)thirdPartyFabricComponents; + +@end +" +`; + +exports[`execute "RCTThirdPartyComponentsProvider.mm" should match snapshot 1`] = ` +"/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + +#import + +#import \\"RCTThirdPartyComponentsProvider.h\\" +#import + +@implementation RCTThirdPartyComponentsProvider + ++ (NSDictionary> *)thirdPartyFabricComponents +{ + static NSDictionary> *thirdPartyComponents = nil; + static dispatch_once_t nativeComponentsToken; + + dispatch_once(&nativeComponentsToken, ^{ + thirdPartyComponents = @{ + @\\"TestAppComponent\\": NSClassFromString(@\\"RCTTestAppComponentClass\\"), // test-app + @\\"TestLibraryComponent\\": NSClassFromString(@\\"RCTTestLibraryComponentClass\\"), // test-library + }; + }); + + return thirdPartyComponents; +} + +@end +" +`; + +exports[`execute "ReactAppDependencyProvider.podspec" should match snapshot 1`] = ` +"# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +version = \\"1000.0.0\\" +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = \`git rev-parse HEAD\`.strip if system(\\"git rev-parse --git-dir > /dev/null 2>&1\\") +else + source[:tag] = \\"v#{version}\\" +end + +Pod::Spec.new do |s| + s.name = \\"ReactAppDependencyProvider\\" + s.version = version + s.summary = \\"The third party dependency provider for the app\\" + s.homepage = \\"https://reactnative.dev/\\" + s.documentation_url = \\"https://reactnative.dev/\\" + s.license = \\"MIT\\" + s.author = \\"Meta Platforms, Inc. and its affiliates\\" + s.platforms = min_supported_versions + s.source = source + s.source_files = \\"**/RCTAppDependencyProvider.{h,mm}\\" + + # This guard prevent to install the dependencies when we run \`pod install\` in the old architecture. + s.pod_target_xcconfig = { + \\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(), + \\"DEFINES_MODULE\\" => \\"YES\\" + } + + s.dependency \\"ReactCodegen\\" +end +" +`; + +exports[`execute "ReactCodegen.podspec" should match snapshot 1`] = ` +"# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +version = \\"1000.0.0\\" +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = \`git rev-parse HEAD\`.strip if system(\\"git rev-parse --git-dir > /dev/null 2>&1\\") +else + source[:tag] = \\"v#{version}\\" +end + +use_frameworks = ENV['USE_FRAMEWORKS'] != nil +folly_compiler_flags = Helpers::Constants.folly_config[:compiler_flags] +boost_compiler_flags = Helpers::Constants.boost_config[:compiler_flags] + +header_search_paths = [ + \\"\\\\\\"$(PODS_ROOT)/boost\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/RCT-Folly\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/DoubleConversion\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/fast_float/include\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/fmt/include\\\\\\"\\", + \\"\\\\\\"\${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-Fabric\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\\\\\\"\\", + \\"\\\\\\"$(PODS_ROOT)/Headers/Private/Yoga\\\\\\"\\", + \\"\\\\\\"$(PODS_TARGET_SRCROOT)\\\\\\"\\", +] +framework_search_paths = [] + +if use_frameworks + ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-Fabric\\", \\"React_Fabric\\", [\\"react/renderer/components/view/platform/cxx\\"]) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-rendererdebug\\", \\"React_rendererdebug\\", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-utils\\", \\"React_utils\\", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-featureflags\\", \\"React_featureflags\\", [])) + .each { |search_path| + header_search_paths << \\"\\\\\\"#{search_path}\\\\\\"\\" + } +end + +Pod::Spec.new do |s| + s.name = \\"ReactCodegen\\" + s.version = version + s.summary = 'Temp pod for generated files for React Native' + s.homepage = 'https://facebook.com/' + s.license = 'Unlicense' + s.authors = 'Facebook' + s.compiler_flags = \\"#{folly_compiler_flags} #{boost_compiler_flags} -Wno-nullability-completeness -std=c++20\\" + s.source = { :git => '' } + s.header_mappings_dir = './' + s.platforms = min_supported_versions + s.source_files = \\"**/*.{h,mm,cpp}\\" + s.exclude_files = \\"RCTAppDependencyProvider.{h,mm}\\" # these files are generated in the same codegen path but needs to belong to a different pod + s.pod_target_xcconfig = { + \\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '), + \\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths, + \\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\" + } + + s.dependency \\"React-jsiexecutor\\" + s.dependency \\"RCT-Folly\\" + s.dependency \\"RCTRequired\\" + s.dependency \\"RCTTypeSafety\\" + s.dependency \\"React-Core\\" + s.dependency \\"React-jsi\\" + s.dependency \\"ReactCommon/turbomodule/bridging\\" + s.dependency \\"ReactCommon/turbomodule/core\\" + s.dependency \\"React-NativeModulesApple\\" + s.dependency \\"glog\\" + s.dependency \\"DoubleConversion\\" + s.dependency 'React-graphics' + s.dependency 'React-rendererdebug' + s.dependency 'React-Fabric' + s.dependency 'React-FabricImage' + s.dependency 'React-debug' + s.dependency 'React-utils' + s.dependency 'React-featureflags' + s.dependency 'React-RCTAppDelegate' + + depend_on_js_engine(s) + + s.script_phases = { + 'name' => 'Generate Specs', + 'execution_position' => :before_compile, + 'input_files' => [], + 'show_env_vars_in_log' => true, + 'output_files' => [\\"\${DERIVED_FILE_DIR}/react-codegen.log\\"], + 'script': <<-SCRIPT +pushd \\"$PODS_ROOT/../\\" > /dev/null +RCT_SCRIPT_POD_INSTALLATION_ROOT=$(pwd) +popd >/dev/null + +export RCT_SCRIPT_RN_DIR=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT/../../../../..\\" +export RCT_SCRIPT_APP_PATH=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT/..\\", +export RCT_SCRIPT_OUTPUT_DIR=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT\\", +export RCT_SCRIPT_TYPE=\\"withCodegenDiscovery\\", + +SCRIPT_PHASES_SCRIPT=\\"$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh\\" +WITH_ENVIRONMENT=\\"$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh\\" +/bin/sh -c \\"$WITH_ENVIRONMENT $SCRIPT_PHASES_SCRIPT\\" +SCRIPT + } + +end +" +`; diff --git a/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js index b227a3af506..877bc0e2146 100644 --- a/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js +++ b/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -12,6 +12,7 @@ const fixtures = require('../__test_fixtures__/fixtures'); const underTest = require('../generate-artifacts-executor'); +const fs = require('fs'); const path = require('path'); const rootPath = path.join(__dirname, '../../..'); @@ -20,6 +21,39 @@ const packageJson = JSON.stringify({ name: 'react-native', }); +describe('execute', () => { + const appDir = path.join(__dirname, '../__test_fixtures__/test-app'); + const outputDir = path.join(appDir, 'temp'); + + beforeAll(() => { + underTest.execute(appDir, 'ios', outputDir, 'app', false); + }); + + afterAll(() => { + fs.rmdirSync(outputDir, {recursive: true}); + }); + + [ + 'RCTAppDependencyProvider.h', + 'RCTAppDependencyProvider.mm', + 'RCTModuleProviders.h', + 'RCTModuleProviders.mm', + 'RCTModulesConformingToProtocolsProvider.h', + 'RCTModulesConformingToProtocolsProvider.mm', + 'RCTThirdPartyComponentsProvider.h', + 'RCTThirdPartyComponentsProvider.mm', + 'ReactAppDependencyProvider.podspec', + 'ReactCodegen.podspec', + ].forEach(file => { + it(`"${file}" should match snapshot`, () => { + const generatedFileDir = path.join(outputDir, 'build/generated/ios'); + const generatedFile = path.join(generatedFileDir, file); + expect(fs.existsSync(generatedFile)).toBe(true); + expect(fs.readFileSync(generatedFile, 'utf8')).toMatchSnapshot(); + }); + }); +}); + describe('extractLibrariesFromJSON', () => { it('extracts a single dependency when config has no libraries', () => { let configFile = fixtures.noLibrariesConfigFile; diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor.js b/packages/react-native/scripts/codegen/generate-artifacts-executor.js index de9ca6ae020..8c55707869e 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor.js @@ -1036,7 +1036,13 @@ SCRIPT`; * @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, source) { +function execute( + projectRoot, + targetPlatform, + baseOutputPath, + source, + runReactNativeCodegen = true, +) { try { codegenLog(`Analyzing ${path.join(projectRoot, 'package.json')}`); @@ -1054,7 +1060,9 @@ function execute(projectRoot, targetPlatform, baseOutputPath, source) { const pkgJson = readPkgJsonInDirectory(projectRoot); - buildCodegenIfNeeded(); + if (runReactNativeCodegen) { + buildCodegenIfNeeded(); + } const libraries = findCodegenEnabledLibraries(pkgJson, projectRoot); @@ -1074,15 +1082,17 @@ function execute(projectRoot, targetPlatform, baseOutputPath, source) { platform, ); - const schemaInfos = generateSchemaInfos(libraries); - generateNativeCode( - outputPath, - schemaInfos.filter(schemaInfo => - mustGenerateNativeCode(projectRoot, schemaInfo), - ), - pkgJsonIncludesGeneratedCode(pkgJson), - platform, - ); + if (runReactNativeCodegen) { + const schemaInfos = generateSchemaInfos(libraries); + generateNativeCode( + outputPath, + schemaInfos.filter(schemaInfo => + mustGenerateNativeCode(projectRoot, schemaInfo), + ), + pkgJsonIncludesGeneratedCode(pkgJson), + platform, + ); + } if (source === 'app') { // These components are only required by apps, not by libraries