From 7d069b25835ad20654a46ebb1e09631d826598e0 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Tue, 19 Jul 2022 00:03:23 -0700 Subject: [PATCH] Extract Codegen code from the react_native_pods to its own file (#34176) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/34176 It extracts the code related to the codegen from the main `react_native_pods` script to a dedicated file, adding also tests. ## Changelog [iOS][Changed] - Move codegen in separate files Reviewed By: cortinico Differential Revision: D37755818 fbshipit-source-id: 99760d1def26ddbf065fdd234e0d183c2795513c --- scripts/cocoapods/__tests__/codegen-test.rb | 59 +++ .../cocoapods/__tests__/codegen_utils-test.rb | 435 ++++++++++++++++++ .../CodegenScriptPhaseExtractorMock.rb | 21 + .../__tests__/test_utils/CodegenUtilsMock.rb | 107 +++++ .../__tests__/test_utils/FinderMock.rb | 28 ++ .../__tests__/test_utils/PathnameMock.rb | 14 + scripts/cocoapods/__tests__/utils-test.rb | 2 +- scripts/cocoapods/codegen.rb | 35 ++ .../codegen_script_phase_extractor.rb | 13 + scripts/cocoapods/codegen_utils.rb | 286 ++++++++++++ scripts/cocoapods/helpers.rb | 8 + scripts/react_native_pods.rb | 253 +--------- 12 files changed, 1025 insertions(+), 236 deletions(-) create mode 100644 scripts/cocoapods/__tests__/codegen_utils-test.rb create mode 100644 scripts/cocoapods/__tests__/test_utils/CodegenScriptPhaseExtractorMock.rb create mode 100644 scripts/cocoapods/__tests__/test_utils/CodegenUtilsMock.rb create mode 100644 scripts/cocoapods/__tests__/test_utils/FinderMock.rb create mode 100644 scripts/cocoapods/codegen_script_phase_extractor.rb create mode 100644 scripts/cocoapods/codegen_utils.rb diff --git a/scripts/cocoapods/__tests__/codegen-test.rb b/scripts/cocoapods/__tests__/codegen-test.rb index 62526d9b4b5001..ef574251c366bd 100644 --- a/scripts/cocoapods/__tests__/codegen-test.rb +++ b/scripts/cocoapods/__tests__/codegen-test.rb @@ -10,6 +10,7 @@ require_relative "./test_utils/FileMock.rb" require_relative "./test_utils/DirMock.rb" require_relative "./test_utils/systemUtils.rb" +require_relative "./test_utils/CodegenUtilsMock.rb" class CodegenTests < Test::Unit::TestCase :third_party_provider_header @@ -182,4 +183,62 @@ def testCheckAndGenerateEmptyThirdPartyProvider_whenBothMissing_buildCodegen() ] }) end + + # ================= # + # Test - RunCodegen # + # ================= # + def testRunCodegen_whenNewArchEnabled_runsCodegen + # Arrange + app_path = "~/app" + config_file = "" + codegen_utils_mock = CodegenUtilsMock.new() + + # Act + run_codegen!(app_path, config_file, :new_arch_enabled => true, :codegen_utils => codegen_utils_mock) + + # Assert + assert_equal(codegen_utils_mock.use_react_native_codegen_discovery_params, [{ + :app_path=>"~/app", + :codegen_disabled=>false, + :codegen_output_dir=>"build/generated/ios", + :config_file_dir=>"", + :fabric_enabled=>false, + :folly_version=>"2021.07.22.00", + :react_native_path=>"../node_modules/react-native" + }]) + assert_equal(codegen_utils_mock.get_react_codegen_spec_params, []) + assert_equal(codegen_utils_mock.generate_react_codegen_spec_params, []) + end + + def testRunCodegen_whenNewArchDisabled_runsCodegen + # Arrange + app_path = "~/app" + config_file = "" + package_json_file = "~/app/package.json" + codegen_specs = { "name" => "React-Codegen" } + codegen_utils_mock = CodegenUtilsMock.new(:react_codegen_spec => codegen_specs) + + # Act + run_codegen!( + app_path, + config_file, + :new_arch_enabled => false, + :fabric_enabled => true, + :package_json_file => package_json_file, + :codegen_utils => codegen_utils_mock) + + # Assert + assert_equal(codegen_utils_mock.use_react_native_codegen_discovery_params, []) + assert_equal(codegen_utils_mock.get_react_codegen_spec_params, [{ + :fabric_enabled => true, + :folly_version=>"2021.07.22.00", + :package_json_file => "~/app/package.json", + :script_phases => nil + }]) + assert_equal(codegen_utils_mock.generate_react_codegen_spec_params, [{ + :codegen_output_dir=>"build/generated/ios", + :react_codegen_spec=>{"name"=>"React-Codegen"} + }]) + + end end diff --git a/scripts/cocoapods/__tests__/codegen_utils-test.rb b/scripts/cocoapods/__tests__/codegen_utils-test.rb new file mode 100644 index 00000000000000..e933b647b75130 --- /dev/null +++ b/scripts/cocoapods/__tests__/codegen_utils-test.rb @@ -0,0 +1,435 @@ +# 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. + +require "test/unit" +require "json" +require_relative "../codegen_utils.rb" +require_relative "./test_utils/FileMock.rb" +require_relative "./test_utils/DirMock.rb" +require_relative "./test_utils/PodMock.rb" +require_relative "./test_utils/PathnameMock.rb" +require_relative "./test_utils/FinderMock.rb" +require_relative "./test_utils/CodegenUtilsMock.rb" +require_relative "./test_utils/CodegenScriptPhaseExtractorMock.rb" + +class CodegenUtilsTests < Test::Unit::TestCase + :base_path + + def setup + CodegenUtils.set_react_codegen_discovery_done(false) + CodegenUtils.set_react_codegen_podspec_generated(false) + Pod::Config.reset() + File.enable_testing_mode! + Dir.enable_testing_mode! + @base_path = "~/app/ios" + Pathname.pwd!(@base_path) + Pod::Config.instance.installation_root.relative_path_from = @base_path + end + + def teardown + Finder.reset() + Pathname.reset() + Pod::UI.reset() + Pod::Executable.reset() + File.reset() + Dir.reset() + end + + # ================================== # + # Test - GenerateReactCodegenPodspec # + # ================================== # + + def testGenerateReactCodegenPodspec_whenItHasBeenAlreadyGenerated_doesNothing + # Arrange + spec = { :name => "Test Podspec" } + codegen_output_dir = "build" + CodegenUtils.set_react_codegen_podspec_generated(true) + + # Act + CodegenUtils.new().generate_react_codegen_podspec!(spec, codegen_output_dir) + + # Assert + assert_equal(Pod::UI.collected_messages, ["[Codegen] Skipping React-Codegen podspec generation."]) + assert_equal(Pathname.pwd_invocation_count, 0) + assert_equal(Pod::Executable.executed_commands, []) + assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 0) + assert_true(CodegenUtils.react_codegen_podspec_generated) + end + + def testGenerateReactCodegenPodspec_whenItHasNotBeenAlreadyGenerated_generatesIt + # Arrange + spec = { :name => "Test Podspec" } + codegen_output_dir = "build" + + # Act + CodegenUtils.new().generate_react_codegen_podspec!(spec, codegen_output_dir) + + # Assert + assert_equal(Pathname.pwd_invocation_count, 1) + assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 1) + assert_equal(Pod::Executable.executed_commands, [{ "command" => 'mkdir', "arguments" => ["-p", "~/app/ios/build"]}]) + assert_equal(Pod::UI.collected_messages, ["[Codegen] Generating ~/app/ios/build/React-Codegen.podspec.json"]) + assert_equal(File.open_files_with_mode["~/app/ios/build/React-Codegen.podspec.json"], 'w') + assert_equal(File.open_files[0].collected_write, ['{"name":"Test Podspec"}']) + assert_equal(File.open_files[0].fsync_invocation_count, 1) + + assert_true(CodegenUtils.react_codegen_podspec_generated) + end + + # ========================== # + # Test - GetReactCodegenSpec # + # ========================== # + + def testGetReactCodegenSpec_whenFabricDisabledAndNoScriptPhases_generatesAPodspec + # Arrange + File.files_to_read('package.json' => '{ "version": "99.98.97"}') + + # Act + podspec = CodegenUtils.new().get_react_codegen_spec( + 'package.json', + :fabric_enabled => false, + :script_phases => nil + ) + + # Assert + assert_equal(podspec, get_podspec_no_fabric_no_script()) + assert_equal(Pod::UI.collected_messages, []) + end + + def testGetReactCodegenSpec_whenFabricEnabledAndScriptPhases_generatesAPodspec + # Arrange + File.files_to_read('package.json' => '{ "version": "99.98.97"}') + + # Act + podspec = CodegenUtils.new().get_react_codegen_spec( + 'package.json', + :fabric_enabled => true, + :script_phases => "echo Test Script Phase" + ) + + # Assert + assert_equal(podspec, get_podspec_fabric_and_script_phases("echo Test Script Phase")) + assert_equal(Pod::UI.collected_messages, ["[Codegen] Adding script_phases to React-Codegen."]) + end + + # =============================== # + # Test - GetCodegenConfigFromFile # + # =============================== # + + def testGetCodegenConfigFromFile_whenFileDoesNotExists_returnEmpty + # Arrange + + # Act + codegen = CodegenUtils.new().get_codegen_config_from_file('package.json', 'codegenConfig') + + # Assert + assert_equal(codegen, {'libraries' => []}) + end + + def testGetCodegenConfigFromFile_whenFileExistsButHasNoKey_returnEmpty + # Arrange + File.mocked_existing_files(['package.json']) + File.files_to_read('package.json' => '{ "codegenConfig": {}}') + + # Act + codegen = CodegenUtils.new().get_codegen_config_from_file('package.json', 'codegen') + + # Assert + assert_equal(codegen, {'libraries' => []}) + end + + def testGetCodegenConfigFromFile_whenFileExistsAndHasKey_returnObject + # Arrange + File.mocked_existing_files(['package.json']) + File.files_to_read('package.json' => '{ "codegenConfig": {"name": "MySpec"}}') + + # Act + codegen = CodegenUtils.new().get_codegen_config_from_file('package.json', 'codegenConfig') + + # Assert + assert_equal(codegen, { "name" => "MySpec"}) + end + + # ======================= # + # Test - GetListOfJSSpecs # + # ======================= # + def testGetListOfJSSpecs_whenUsesLibraries_returnAListOfFiles + # Arrange + app_codegen_config = { + 'libraries' => [ + { + 'name' => 'First Lib', + 'jsSrcsDir' => './firstlib/js' + }, + { + 'name' => 'Second Lib', + 'jsSrcsDir' => './secondlib/js' + }, + ] + } + app_path = "~/MyApp/" + Finder.set_files_for_paths({ + '~/MyApp/./firstlib/js' => ["MyFabricComponent1NativeComponent.js", "MyFabricComponent2NativeComponent.js"], + '~/MyApp/./secondlib/js' => ["NativeModule1.js", "NativeModule2.js"], + }) + + # Act + files = CodegenUtils.new().get_list_of_js_specs(app_codegen_config, app_path) + + # Assert + assert_equal(Pod::UI.collected_warns , ["[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."]) + assert_equal(Finder.captured_paths, ['~/MyApp/./firstlib/js', '~/MyApp/./secondlib/js']) + assert_equal(files, [ + "${PODS_ROOT}/../MyFabricComponent1NativeComponent.js", + "${PODS_ROOT}/../MyFabricComponent2NativeComponent.js", + "${PODS_ROOT}/../NativeModule1.js", + "${PODS_ROOT}/../NativeModule2.js", + ]) + end + + def testGetListOfJSSpecs_whenDoesNotUsesLibraries_returnAListOfFiles + # Arrange + app_codegen_config = { + 'name' => 'First Lib', + 'jsSrcsDir' => './js' + } + + app_path = "~/MyApp/" + Finder.set_files_for_paths({ + '~/MyApp/./js' => ["MyFabricComponent1NativeComponent.js", "NativeModule1.js"], + }) + + # Act + files = CodegenUtils.new().get_list_of_js_specs(app_codegen_config, app_path) + + # Assert + assert_equal(Pod::UI.collected_warns , []) + assert_equal(Finder.captured_paths, ['~/MyApp/./js']) + assert_equal(files, [ + "${PODS_ROOT}/../MyFabricComponent1NativeComponent.js", + "${PODS_ROOT}/../NativeModule1.js", + ]) + end + + def testGetListOfJSSpecs_whenMisconfigured_aborts + # Arrange + app_codegen_config = {} + app_path = "~/MyApp/" + # Act + assert_raises() { + files = CodegenUtils.new().get_list_of_js_specs(app_codegen_config, app_path) + } + # Assert + assert_equal(Pod::UI.collected_warns , ["[Error] Codegen not properly configured. Please add the `codegenConfig` entry to your `package.json`"]) + + end + + # ================================== # + # Test - GetReactCodegenScriptPhases # + # ================================== # + + def testGetReactCodegenScriptPhases_whenAppPathNotDefined_abort + # Arrange + + # Act + assert_raises() { + CodegenUtils.new().get_react_codegen_script_phases(nil) + } + # Assert + assert_equal(Pod::UI.collected_warns, ["[Codegen] error: app_path is requried to use codegen discovery."]) + end + + def testGetReactCodegenScriptPhases_returnTheScriptObject + # Arrange + app_path = "~/MyApp" + input_files = ["${PODS_ROOT}/../MyFabricComponent1NativeComponent.js", "${PODS_ROOT}/../NativeModule1.js"] + computed_script = "echo ScriptPhases" + codegen_config = { "name" => "MyCodegenModule", "jsSrcsDir" => "./js"} + codegen_utils_mock = CodegenUtilsMock.new(:js_spec_list => input_files, :codegen_config => codegen_config) + script_phase_extractor_mock = CodegenScriptPhaseExtractorMock.new(computed_script) + + # Act + + scripts = CodegenUtils.new().get_react_codegen_script_phases( + app_path, + :codegen_utils => codegen_utils_mock, + :script_phase_extractor => script_phase_extractor_mock + ) + + # Assert + assert_equal(codegen_utils_mock.get_codegen_config_from_file_params, [{ + "config_key" => "codegenConfig", + "config_path" => "~/MyApp/package.json" + }]) + assert_equal(codegen_utils_mock.get_list_of_js_specs_params, [{ + "app_codegen_config" => {"jsSrcsDir"=>"./js", "name"=>"MyCodegenModule"}, + "app_path" => "~/MyApp" + }]) + assert_equal(script_phase_extractor_mock.extract_script_phase_params, [{ + fabric_enabled: false, + react_native_path: "../node_modules/react-native", + relative_app_root: "~/MyApp", + relative_config_file_dir: "" + }]) + assert_equal(scripts, { + 'name': 'Generate Specs', + 'execution_position': :before_compile, + 'input_files' => input_files, + 'show_env_vars_in_log': true, + 'output_files': ["${DERIVED_FILE_DIR}/react-codegen.log"], + 'script': computed_script + }) + end + + # ================================ # + # Test - UseReactCodegenDiscovery! # + # ================================ # + + def testUseReactCodegenDiscovery_whenCodegenDisabled_doNothing + # Arrange + + # Act + CodegenUtils.new().use_react_native_codegen_discovery!(true, nil) + + # Assert + assert_false(CodegenUtils.react_codegen_discovery_done()) + assert_equal(Pod::UI.collected_messages, []) + assert_equal(Pod::UI.collected_warns, []) + end + + def testUseReactCodegenDiscovery_whenDiscoveryDone_doNothing + # Arrange + CodegenUtils.set_react_codegen_discovery_done(true) + + # Act + CodegenUtils.new().use_react_native_codegen_discovery!(false, nil) + + # Assert + assert_true(CodegenUtils.react_codegen_discovery_done()) + assert_equal(Pod::UI.collected_messages, ["[Codegen] Skipping use_react_native_codegen_discovery."]) + assert_equal(Pod::UI.collected_warns, []) + end + + def testUseReactCodegenDiscovery_whenAppPathUndefined_abort + # Arrange + + # Act + assert_raises(){ + CodegenUtils.new().use_react_native_codegen_discovery!(false, nil) + } + + # Assert + assert_false(CodegenUtils.react_codegen_discovery_done()) + assert_equal(Pod::UI.collected_messages, []) + assert_equal(Pod::UI.collected_warns, [ + '[Codegen] Error: app_path is required for use_react_native_codegen_discovery.', + '[Codegen] If you are calling use_react_native_codegen_discovery! in your Podfile, please remove the call and pass `app_path` and/or `config_file_dir` to `use_react_native!`.' + ]) + end + + def testUseReactCodegenDiscovery_whenParametersAreGood_executeCodegen + # Arrange + app_path = "~/app" + computed_script = "echo TestScript" + codegen_spec = {"name" => "React-Codegen"} + + codegen_utils_mock = CodegenUtilsMock.new( + :react_codegen_script_phases => computed_script, + :react_codegen_spec => codegen_spec + ) + + # Act + CodegenUtils.new().use_react_native_codegen_discovery!( + false, + app_path, + :codegen_utils => codegen_utils_mock + ) + + # Assert + assert_true(CodegenUtils.react_codegen_discovery_done()) + assert_equal(Pod::UI.collected_warns, [ + '[Codegen] warn: using experimental new codegen integration' + ]) + assert_equal(codegen_utils_mock.get_react_codegen_script_phases_params, [{ + :app_path => "~/app", + :config_file_dir => "", + :config_key => "codegenConfig", + :fabric_enabled => false, + :react_native_path => "../node_modules/react-native"} + ]) + assert_equal(codegen_utils_mock.get_react_codegen_spec_params, [{ + :fabric_enabled => false, + :folly_version=>"2021.07.22.00", + :package_json_file => "../node_modules/react-native/package.json", + :script_phases => "echo TestScript" + }]) + assert_equal(codegen_utils_mock.generate_react_codegen_spec_params, [{ + :codegen_output_dir=>"build/generated/ios", + :react_codegen_spec=>{"name"=>"React-Codegen"} + }]) + assert_equal(Pod::Executable.executed_commands, [ + { + "command" => "node", + "arguments"=> ["~/app/ios/../node_modules/react-native/scripts/generate-artifacts.js", + "-p", "~/app", + "-o", Pod::Config.instance.installation_root, + "-e", "false", + "-c", ""] + } + ]) + end + + private + + def get_podspec_no_fabric_no_script + spec = { + 'name' => "React-Codegen", + 'version' => "99.98.97", + 'summary' => 'Temp pod for generated files for React Native', + 'homepage' => 'https://facebook.com/', + 'license' => 'Unlicense', + 'authors' => 'Facebook', + 'compiler_flags' => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32 -Wno-documentation -Wno-nullability-completeness -std=c++17", + 'source' => { :git => '' }, + 'header_mappings_dir' => './', + 'platforms' => { + 'ios' => '11.0', + }, + 'source_files' => "**/*.{h,mm,cpp}", + 'pod_target_xcconfig' => { "HEADER_SEARCH_PATHS" => + [ + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"${PODS_ROOT}/Headers/Public/React-Codegen/react/renderer/components\"", + "\"$(PODS_ROOT)/Headers/Private/React-Fabric\"", + "\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"", + ].join(' ') + }, + 'dependencies': { + "FBReactNativeSpec": ["99.98.97"], + "React-jsiexecutor": ["99.98.97"], + "RCT-Folly": ["2021.07.22.00"], + "RCTRequired": ["99.98.97"], + "RCTTypeSafety": ["99.98.97"], + "React-Core": ["99.98.97"], + "React-jsi": ["99.98.97"], + "ReactCommon/turbomodule/core": ["99.98.97"] + } + } + end + + def get_podspec_fabric_and_script_phases(script_phases) + specs = get_podspec_no_fabric_no_script() + + specs[:dependencies].merge!({ + 'React-graphics': ["99.98.97"], + 'React-rncore': ["99.98.97"], + }) + + specs[:'script_phases'] = script_phases + + return specs + end +end diff --git a/scripts/cocoapods/__tests__/test_utils/CodegenScriptPhaseExtractorMock.rb b/scripts/cocoapods/__tests__/test_utils/CodegenScriptPhaseExtractorMock.rb new file mode 100644 index 00000000000000..621419d52a698f --- /dev/null +++ b/scripts/cocoapods/__tests__/test_utils/CodegenScriptPhaseExtractorMock.rb @@ -0,0 +1,21 @@ +# 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. + +class CodegenScriptPhaseExtractorMock + + attr_reader :extract_script_phase_params + @script_phase + + def initialize(script_phase) + @script_phase = script_phase + @extract_script_phase_params = [] + end + + def extract_script_phase(options) + @extract_script_phase_params.push(options) + return @script_phase + end + +end diff --git a/scripts/cocoapods/__tests__/test_utils/CodegenUtilsMock.rb b/scripts/cocoapods/__tests__/test_utils/CodegenUtilsMock.rb new file mode 100644 index 00000000000000..8d9750157e2e84 --- /dev/null +++ b/scripts/cocoapods/__tests__/test_utils/CodegenUtilsMock.rb @@ -0,0 +1,107 @@ +# 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. + +class CodegenUtilsMock + @js_spec_list + @codegen_config + + @react_codegen_script_phases + @react_codegen_spec + + attr_reader :get_codegen_config_from_file_params + attr_reader :get_list_of_js_specs_params + attr_reader :get_react_codegen_script_phases_params + attr_reader :get_react_codegen_spec_params + attr_reader :generate_react_codegen_spec_params + attr_reader :use_react_native_codegen_discovery_params + + def initialize(js_spec_list: [], codegen_config: {}, react_codegen_script_phases: "", react_codegen_spec: {}) + @js_spec_list = js_spec_list + @codegen_config = codegen_config + @get_codegen_config_from_file_params = [] + @get_list_of_js_specs_params = [] + + @react_codegen_script_phases = react_codegen_script_phases + @react_codegen_spec = react_codegen_spec + @get_react_codegen_script_phases_params = [] + @get_react_codegen_spec_params = [] + @generate_react_codegen_spec_params = [] + @use_react_native_codegen_discovery_params = [] + end + + def get_codegen_config_from_file(config_path, config_key) + @get_codegen_config_from_file_params.push({ + "config_path" => config_path, + "config_key" => config_key + }) + return @codegen_config + end + + def get_list_of_js_specs(app_codegen_config, app_path) + @get_list_of_js_specs_params.push({ + "app_codegen_config" => app_codegen_config, + "app_path" => app_path + }) + return @js_spec_list + end + + def get_react_codegen_script_phases( + app_path, + fabric_enabled: false, + config_file_dir: '', + react_native_path: "../node_modules/react-native", + config_key: 'codegenConfig', + codegen_utils: CodegenUtils.new(), + script_phase_extractor: CodegenScriptPhaseExtractor.new() + ) + @get_react_codegen_script_phases_params.push({ + app_path: app_path, + fabric_enabled: fabric_enabled, + config_file_dir: config_file_dir, + react_native_path: react_native_path, + config_key: config_key + }) + return @react_codegen_script_phases + end + + def get_react_codegen_spec(package_json_file, folly_version: '2021.07.22.00', fabric_enabled: false, script_phases: nil) + @get_react_codegen_spec_params.push({ + package_json_file: package_json_file, + folly_version: folly_version, + fabric_enabled: fabric_enabled, + script_phases: script_phases + }) + return @react_codegen_spec + end + + def generate_react_codegen_podspec!(react_codegen_spec, codegen_output_dir) + @generate_react_codegen_spec_params.push({ + react_codegen_spec: react_codegen_spec, + codegen_output_dir: codegen_output_dir + }) + end + + def use_react_native_codegen_discovery!( + codegen_disabled, + app_path, + react_native_path: "../node_modules/react-native", + fabric_enabled: false, + config_file_dir: '', + codegen_output_dir: 'build/generated/ios', + config_key: 'codegenConfig', + folly_version: "2021.07.22.00", + codegen_utils: CodegenUtils.new() + ) + @use_react_native_codegen_discovery_params.push({ + codegen_disabled: codegen_disabled, + app_path: app_path, + react_native_path: react_native_path, + fabric_enabled: fabric_enabled, + config_file_dir: config_file_dir, + codegen_output_dir: codegen_output_dir, + folly_version: folly_version + }) + end +end diff --git a/scripts/cocoapods/__tests__/test_utils/FinderMock.rb b/scripts/cocoapods/__tests__/test_utils/FinderMock.rb new file mode 100644 index 00000000000000..2d91d160104c33 --- /dev/null +++ b/scripts/cocoapods/__tests__/test_utils/FinderMock.rb @@ -0,0 +1,28 @@ +# 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. + +class Finder + @@captured_paths = [] + @@files_for_paths = {} + + + def self.find_codegen_file(path) + @@captured_paths.push(path) + return @@files_for_paths[path] + end + + def self.set_files_for_paths(files_for_paths) + @@files_for_paths = files_for_paths + end + + def self.captured_paths + return @@captured_paths + end + + def self.reset() + @@captured_paths = [] + @@files_for_paths = {} + end +end diff --git a/scripts/cocoapods/__tests__/test_utils/PathnameMock.rb b/scripts/cocoapods/__tests__/test_utils/PathnameMock.rb index 7aa763bc2e5cb9..0e4078fa03450c 100644 --- a/scripts/cocoapods/__tests__/test_utils/PathnameMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/PathnameMock.rb @@ -7,6 +7,20 @@ class Pathname @@pwd = "" @@pwd_invocation_count = 0 + attr_reader :path + + def initialize(path) + @path = path + end + + def realpath + return self + end + + def relative_path_from(path) + return @path + end + def self.pwd!(pwd) @@pwd = pwd end diff --git a/scripts/cocoapods/__tests__/utils-test.rb b/scripts/cocoapods/__tests__/utils-test.rb index b4cbaddca9d465..cb636838b92d17 100644 --- a/scripts/cocoapods/__tests__/utils-test.rb +++ b/scripts/cocoapods/__tests__/utils-test.rb @@ -352,7 +352,7 @@ def test_applyMacCatalystPatches_correctlyAppliesNecessaryPatches third_target.build_configurations.each do |config| assert_equal(config.build_settings["CODE_SIGN_IDENTITY[sdk=macosx*]"], "-") end - + user_project_mock.native_targets.each do |target| target.build_configurations.each do |config| assert_equal(config.build_settings["DEAD_CODE_STRIPPING"], "YES") diff --git a/scripts/cocoapods/codegen.rb b/scripts/cocoapods/codegen.rb index fff57845ef12f8..9e144039db7c4b 100644 --- a/scripts/cocoapods/codegen.rb +++ b/scripts/cocoapods/codegen.rb @@ -65,3 +65,38 @@ def checkAndGenerateEmptyThirdPartyProvider!(react_native_path, new_arch_enabled File.delete(temp_schema_list_path) if File.exist?(temp_schema_list_path) end end + +def run_codegen!( + app_path, + config_file_dir, + new_arch_enabled: false, + disable_codegen: false, + react_native_path: "../node_modules/react-native", + fabric_enabled: false, + codegen_output_dir: 'build/generated/ios', + config_key: 'codegenConfig', + package_json_file: '~/app/package.json', + folly_version: '2021.07.22.00', + codegen_utils: CodegenUtils.new() + ) + if new_arch_enabled + codegen_utils.use_react_native_codegen_discovery!( + disable_codegen, + app_path, + :react_native_path => react_native_path, + :fabric_enabled => fabric_enabled, + :config_file_dir => config_file_dir, + :codegen_output_dir => codegen_output_dir, + :config_key => config_key, + :folly_version => folly_version + ) + else + # Generate a podspec file for generated files. + # This gets generated in use_react_native_codegen_discovery when codegen discovery is enabled. + react_codegen_spec = codegen_utils.get_react_codegen_spec( + package_json_file, + :fabric_enabled => fabric_enabled + ) + codegen_utils.generate_react_codegen_podspec!(react_codegen_spec, codegen_output_dir) + end +end diff --git a/scripts/cocoapods/codegen_script_phase_extractor.rb b/scripts/cocoapods/codegen_script_phase_extractor.rb new file mode 100644 index 00000000000000..225ede7d2c4701 --- /dev/null +++ b/scripts/cocoapods/codegen_script_phase_extractor.rb @@ -0,0 +1,13 @@ +# 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. + +class CodegenScriptPhaseExtractor + def initialize() + end + + def extract_script_phase(options) + get_script_phases_with_codegen_discovery(options) + end +end diff --git a/scripts/cocoapods/codegen_utils.rb b/scripts/cocoapods/codegen_utils.rb new file mode 100644 index 00000000000000..1c1290b3d67b78 --- /dev/null +++ b/scripts/cocoapods/codegen_utils.rb @@ -0,0 +1,286 @@ +# 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. + +require 'json' +require_relative './helpers.rb' +require_relative './codegen_script_phase_extractor.rb' + +class CodegenUtils + + def initialize() + end + + @@REACT_CODEGEN_PODSPEC_GENERATED = false + + def self.set_react_codegen_podspec_generated(value) + @@REACT_CODEGEN_PODSPEC_GENERATED = value + end + + def self.react_codegen_podspec_generated + @@REACT_CODEGEN_PODSPEC_GENERATED + end + + @@REACT_CODEGEN_DISCOVERY_DONE = false + + def self.set_react_codegen_discovery_done(value) + @@REACT_CODEGEN_DISCOVERY_DONE = value + end + + def self.react_codegen_discovery_done + @@REACT_CODEGEN_DISCOVERY_DONE + end + + # It takes some cocoapods specs and writes them into a file + # + # Parameters + # - spec: the cocoapod specs + # - codegen_output_dir: the output directory for the codegen + def generate_react_codegen_podspec!(spec, codegen_output_dir) + # This podspec file should only be create once in the session/pod install. + # This happens when multiple targets are calling use_react_native!. + if @@REACT_CODEGEN_PODSPEC_GENERATED + Pod::UI.puts "[Codegen] Skipping React-Codegen podspec generation." + return + end + + relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) + output_dir = "#{relative_installation_root}/#{codegen_output_dir}" + Pod::Executable.execute_command("mkdir", ["-p", output_dir]); + + podspec_path = File.join(output_dir, 'React-Codegen.podspec.json') + Pod::UI.puts "[Codegen] Generating #{podspec_path}" + + File.open(podspec_path, 'w') do |f| + f.write(spec.to_json) + f.fsync + end + + @@REACT_CODEGEN_PODSPEC_GENERATED = true + end + + # It generates the podspec object that represents the `React-Codegen.podspec` file + # + # Parameters + # - package_json_file: the path to the `package.json`, required to extract the proper React Native version + # - fabric_enabled: whether fabric is enabled or not. + # - script_phases: whether we want to add some build script phases or not. + def get_react_codegen_spec(package_json_file, folly_version: '2021.07.22.00', fabric_enabled: false, script_phases: nil) + package = JSON.parse(File.read(package_json_file)) + version = package['version'] + + folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + boost_compiler_flags = '-Wno-documentation' + + spec = { + 'name' => "React-Codegen", + 'version' => version, + 'summary' => 'Temp pod for generated files for React Native', + 'homepage' => 'https://facebook.com/', + 'license' => 'Unlicense', + 'authors' => 'Facebook', + 'compiler_flags' => "#{folly_compiler_flags} #{boost_compiler_flags} -Wno-nullability-completeness -std=c++17", + 'source' => { :git => '' }, + 'header_mappings_dir' => './', + 'platforms' => { + 'ios' => '11.0', + }, + 'source_files' => "**/*.{h,mm,cpp}", + 'pod_target_xcconfig' => { "HEADER_SEARCH_PATHS" => + [ + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"${PODS_ROOT}/Headers/Public/React-Codegen/react/renderer/components\"", + "\"$(PODS_ROOT)/Headers/Private/React-Fabric\"", + "\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"", + ].join(' ') + }, + 'dependencies': { + "FBReactNativeSpec": [version], + "React-jsiexecutor": [version], + "RCT-Folly": [folly_version], + "RCTRequired": [version], + "RCTTypeSafety": [version], + "React-Core": [version], + "React-jsi": [version], + "ReactCommon/turbomodule/core": [version] + } + } + + if fabric_enabled + spec[:'dependencies'].merge!({ + 'React-graphics': [version], + 'React-rncore': [version], + }); + end + + if script_phases + Pod::UI.puts "[Codegen] Adding script_phases to React-Codegen." + spec[:'script_phases'] = script_phases + end + + return spec + end + + # It extracts the codegen config from the configuration file + # + # Parameters + # - config_path: a path to the configuration file + # - config_ket: the codegen configuration key + # + # Returns: the list of dependencies as extracted from the package.json + def get_codegen_config_from_file(config_path, config_key) + empty = {'libraries' => []} + if !File.exist?(config_path) + return empty + end + + config = JSON.parse(File.read(config_path)) + return config[config_key] ? config[config_key] : empty + end + + # It creates a list of JS files that contains the JS specifications that Codegen needs to use to generate the code + # + # Parameters + # - app_codegen_config: an object that contains the configurations + # - app_path: path to the app + # + # Returns: the list of files that needs to be used by Codegen + def get_list_of_js_specs(app_codegen_config, app_path) + file_list = [] + + 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(Finder.find_codegen_file(library_dir)) + end + elsif app_codegen_config['jsSrcsDir'] then + codegen_dir = File.join(app_path, app_codegen_config['jsSrcsDir']) + file_list.concat (Finder.find_codegen_file(codegen_dir)) + else + Pod::UI.warn '[Error] Codegen not properly configured. Please add the `codegenConfig` entry to your `package.json`' + abort + end + + input_files = file_list.map { |filename| "${PODS_ROOT}/../#{Pathname.new(filename).realpath().relative_path_from(Pod::Config.instance.installation_root)}" } + + return input_files + end + + # It generates the build script phase for the codegen + # + # Parameters + # - app_path: the path to the app + # - fabric_enabled: whether fabric is enabled or not + # - config_file_dir: the directory of the config file + # - react_native_path: the path to React Native + # - config_key: the configuration key to use in the package.json for the Codegen + # - codegen_utils: an object which exposes utilities functions for the codegen + # - script_phase_extractor: an object that is able to extract the Xcode Script Phases for React Native + # + # Return: an object containing the script phase + def get_react_codegen_script_phases( + app_path, + fabric_enabled: false, + config_file_dir: '', + react_native_path: "../node_modules/react-native", + config_key: 'codegenConfig', + codegen_utils: CodegenUtils.new(), + script_phase_extractor: CodegenScriptPhaseExtractor.new() + ) + if !app_path + Pod::UI.warn '[Codegen] error: app_path is requried to use codegen discovery.' + abort + end + + # We need to convert paths to relative path from installation_root for the script phase for CI. + relative_app_root = Pathname.new(app_path).realpath().relative_path_from(Pod::Config.instance.installation_root) + + relative_config_file_dir = '' + if config_file_dir != '' + relative_config_file_dir = Pathname.new(config_file_dir).relative_path_from(Pod::Config.instance.installation_root) + end + + # Generate input files for in-app libaraies which will be used to check if the script needs to be run. + # TODO: Ideally, we generate the input_files list from generate-artifacts.js and read the result here. + # Or, generate this podspec in generate-artifacts.js as well. + app_package_path = File.join(app_path, 'package.json') + app_codegen_config = codegen_utils.get_codegen_config_from_file(app_package_path, config_key) + input_files = codegen_utils.get_list_of_js_specs(app_codegen_config, app_path) + + # Add a script phase to trigger generate artifact. + # Some code is duplicated so that it's easier to delete the old way and switch over to this once it's stabilized. + return { + 'name': 'Generate Specs', + 'execution_position': :before_compile, + 'input_files' => input_files, + 'show_env_vars_in_log': true, + 'output_files': ["${DERIVED_FILE_DIR}/react-codegen.log"], + 'script': script_phase_extractor.extract_script_phase( + react_native_path: react_native_path, + relative_app_root: relative_app_root, + relative_config_file_dir: relative_config_file_dir, + fabric_enabled: fabric_enabled + ), + } + end + + def use_react_native_codegen_discovery!( + codegen_disabled, + app_path, + react_native_path: "../node_modules/react-native", + fabric_enabled: false, + config_file_dir: '', + codegen_output_dir: 'build/generated/ios', + config_key: 'codegenConfig', + folly_version: '2021.07.22.00', + codegen_utils: CodegenUtils.new() + ) + return if codegen_disabled + + if CodegenUtils.react_codegen_discovery_done() + Pod::UI.puts "[Codegen] Skipping use_react_native_codegen_discovery." + return + end + + if !app_path + Pod::UI.warn '[Codegen] Error: app_path is required for use_react_native_codegen_discovery.' + Pod::UI.warn '[Codegen] If you are calling use_react_native_codegen_discovery! in your Podfile, please remove the call and pass `app_path` and/or `config_file_dir` to `use_react_native!`.' + abort + end + + Pod::UI.warn '[Codegen] warn: using experimental new codegen integration' + relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) + + # Generate React-Codegen podspec here to add the script phases. + script_phases = codegen_utils.get_react_codegen_script_phases( + app_path, + :fabric_enabled => fabric_enabled, + :config_file_dir => config_file_dir, + :react_native_path => react_native_path, + :config_key => config_key + ) + react_codegen_spec = codegen_utils.get_react_codegen_spec( + File.join(react_native_path, "package.json"), + :folly_version => folly_version, + :fabric_enabled => fabric_enabled, + :script_phases => script_phases + ) + codegen_utils.generate_react_codegen_podspec!(react_codegen_spec, codegen_output_dir) + + out = Pod::Executable.execute_command( + 'node', + [ + "#{relative_installation_root}/#{react_native_path}/scripts/generate-artifacts.js", + "-p", "#{app_path}", + "-o", Pod::Config.instance.installation_root, + "-e", "#{fabric_enabled}", + "-c", "#{config_file_dir}", + ]) + Pod::UI.puts out; + + CodegenUtils.set_react_codegen_discovery_done(true) + end +end diff --git a/scripts/cocoapods/helpers.rb b/scripts/cocoapods/helpers.rb index 9008968393bbff..03e3a5cbeac67c 100644 --- a/scripts/cocoapods/helpers.rb +++ b/scripts/cocoapods/helpers.rb @@ -18,3 +18,11 @@ def ruby_platform return RUBY_PLATFORM end end + +class Finder + def self.find_codegen_file(path) + js_files = '-name "Native*.js" -or -name "*NativeComponent.js"' + ts_files = '-name "Native*.ts" -or -name "*NativeComponent.ts"' + return `find #{path} -type f \\( #{js_files} -or #{ts_files} \\)`.split("\n").sort() + end +end diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index 3d86c4f3de4e3d..27a57e36fbd498 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -11,6 +11,7 @@ require_relative './cocoapods/flipper.rb' require_relative './cocoapods/fabric.rb' require_relative './cocoapods/codegen.rb' +require_relative './cocoapods/codegen_utils.rb' require_relative './cocoapods/utils.rb' require_relative './cocoapods/new_architecture.rb' require_relative './cocoapods/local_podspec_patch.rb' @@ -18,12 +19,13 @@ $CODEGEN_OUTPUT_DIR = 'build/generated/ios' $CODEGEN_COMPONENT_DIR = 'react/renderer/components' $CODEGEN_MODULE_DIR = '.' -$REACT_CODEGEN_PODSPEC_GENERATED = false -$REACT_CODEGEN_DISCOVERY_DONE = false $START_TIME = Time.now.to_i def use_react_native! (options={}) + # The version of folly that must be used + folly_version = '2021.07.22.00' + # The prefix to react-native prefix = options[:path] ||= "../node_modules/react-native" @@ -79,21 +81,17 @@ def use_react_native! (options={}) pod 'boost', :podspec => "#{prefix}/third-party-podspecs/boost.podspec" pod 'RCT-Folly', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec", :modular_headers => true - if new_arch_enabled - app_path = options[:app_path] - config_file_dir = options[:config_file_dir] - use_react_native_codegen_discovery!({ - react_native_path: prefix, - app_path: app_path, - fabric_enabled: fabric_enabled, - config_file_dir: config_file_dir, - }) - else - # Generate a podspec file for generated files. - # This gets generated in use_react_native_codegen_discovery when codegen discovery is enabled. - react_codegen_spec = get_react_codegen_spec(fabric_enabled: fabric_enabled) - generate_react_codegen_podspec!(react_codegen_spec) - end + run_codegen!( + options[:app_path], + options[:config_file_dir], + :new_arch_enabled => new_arch_enabled, + :disable_codegen => ENV['DISABLE_CODEGEN'] == '1', + :react_native_path => prefix, + :fabric_enabled => fabric_enabled, + :codegen_output_dir => $CODEGEN_OUTPUT_DIR, + :package_json_file => File.join(__dir__, "..", "package.json"), + :folly_version => folly_version + ) pod 'React-Codegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true @@ -150,224 +148,9 @@ def react_native_post_install(installer, react_native_path = "../node_modules/re Pod::UI.puts "Pod install took #{Time.now.to_i - $START_TIME} [s] to run".green end -def get_react_codegen_spec(options={}) - fabric_enabled = options[:fabric_enabled] ||= false - script_phases = options[:script_phases] ||= nil - - package = JSON.parse(File.read(File.join(__dir__, "..", "package.json"))) - version = package['version'] - - source = { :git => 'https://github.com/facebook/react-native.git' } - if version == '1000.0.0' - # This is an unpublished version, use the latest commit hash of the react-native repo, which 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 - - folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' - folly_version = '2021.07.22.00' - boost_version = '1.76.0' - boost_compiler_flags = '-Wno-documentation' - - spec = { - 'name' => "React-Codegen", - 'version' => version, - 'summary' => 'Temp pod for generated files for React Native', - 'homepage' => 'https://facebook.com/', - 'license' => 'Unlicense', - 'authors' => 'Facebook', - 'compiler_flags' => "#{folly_compiler_flags} #{boost_compiler_flags} -Wno-nullability-completeness -std=c++17", - 'source' => { :git => '' }, - 'header_mappings_dir' => './', - 'platforms' => { - 'ios' => '11.0', - }, - 'source_files' => "**/*.{h,mm,cpp}", - 'pod_target_xcconfig' => { "HEADER_SEARCH_PATHS" => - [ - "\"$(PODS_ROOT)/boost\"", - "\"$(PODS_ROOT)/RCT-Folly\"", - "\"${PODS_ROOT}/Headers/Public/React-Codegen/react/renderer/components\"", - "\"$(PODS_ROOT)/Headers/Private/React-Fabric\"", - "\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"", - ].join(' ') - }, - 'dependencies': { - "FBReactNativeSpec": [version], - "React-jsiexecutor": [version], - "RCT-Folly": [folly_version], - "RCTRequired": [version], - "RCTTypeSafety": [version], - "React-Core": [version], - "React-jsi": [version], - "ReactCommon/turbomodule/core": [version] - } - } - - if fabric_enabled - spec[:'dependencies'].merge!({ - 'React-graphics': [version], - 'React-rncore': [version], - }); - end - - if script_phases - Pod::UI.puts "[Codegen] Adding script_phases to React-Codegen." - spec[:'script_phases'] = script_phases - end - - return spec -end - -def get_codegen_config_from_file(config_path, config_key) - empty = {'libraries' => []} - if !File.exist?(config_path) - return empty - end - - config = JSON.parse(File.read(config_path)) - return config[config_key] ? config[config_key] : empty -end - -def get_react_codegen_script_phases(options={}) - app_path = options[:app_path] ||= '' - if !app_path - Pod::UI.warn '[Codegen] error: app_path is requried to use codegen discovery.' - exit 1 - end - - # We need to convert paths to relative path from installation_root for the script phase for CI. - relative_app_root = Pathname.new(app_path).realpath().relative_path_from(Pod::Config.instance.installation_root) - - config_file_dir = options[:config_file_dir] ||= '' - relative_config_file_dir = '' - if config_file_dir != '' - relative_config_file_dir = Pathname.new(config_file_dir).relative_path_from(Pod::Config.instance.installation_root) - end - - fabric_enabled = options[:fabric_enabled] ||= false - - # react_native_path should be relative already. - react_native_path = options[:react_native_path] ||= "../node_modules/react-native" - - # Generate input files for in-app libaraies which will be used to check if the script needs to be run. - # TODO: Ideally, we generate the input_files list from generate-artifacts.js and read the result here. - # Or, generate this podspec in generate-artifacts.js as well. - config_key = options[:config_key] ||= 'codegenConfig' - app_package_path = File.join(app_path, 'package.json') - app_codegen_config = get_codegen_config_from_file(app_package_path, config_key) - file_list = [] - 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. - # Some code is duplicated so that it's easier to delete the old way and switch over to this once it's stabilized. - return { - 'name': 'Generate Specs', - 'execution_position': :before_compile, - 'input_files' => input_files, - 'show_env_vars_in_log': true, - 'output_files': ["${DERIVED_FILE_DIR}/react-codegen.log"], - 'script': get_script_phases_with_codegen_discovery( - react_native_path: react_native_path, - relative_app_root: relative_app_root, - relative_config_file_dir: relative_config_file_dir, - fabric_enabled: fabric_enabled - ), - } - -end - -def set_react_codegen_podspec_generated(value) - $REACT_CODEGEN_PODSPEC_GENERATED = value -end - -def has_react_codegen_podspec_generated() - return $REACT_CODEGEN_PODSPEC_GENERATED -end - -def generate_react_codegen_podspec!(spec) - # This podspec file should only be create once in the session/pod install. - # This happens when multiple targets are calling use_react_native!. - if has_react_codegen_podspec_generated() - Pod::UI.puts "[Codegen] Skipping React-Codegen podspec generation." - return - end - relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) - output_dir = "#{relative_installation_root}/#{$CODEGEN_OUTPUT_DIR}" - Pod::Executable.execute_command("mkdir", ["-p", output_dir]); - - podspec_path = File.join(output_dir, 'React-Codegen.podspec.json') - Pod::UI.puts "[Codegen] Generating #{podspec_path}" - - File.open(podspec_path, 'w') do |f| - f.write(spec.to_json) - f.fsync - end - - set_react_codegen_podspec_generated(true) - - return { - "spec" => spec, - "path" => $CODEGEN_OUTPUT_DIR, # Path needs to be relative to `Podfile` - } -end - - -def use_react_native_codegen_discovery!(options={}) - return if ENV['DISABLE_CODEGEN'] == '1' - - if $REACT_CODEGEN_DISCOVERY_DONE - Pod::UI.puts "[Codegen] Skipping use_react_native_codegen_discovery." - return - end - - Pod::UI.warn '[Codegen] warn: using experimental new codegen integration' - react_native_path = options[:react_native_path] ||= "../node_modules/react-native" - app_path = options[:app_path] - fabric_enabled = options[:fabric_enabled] ||= false - config_file_dir = options[:config_file_dir] ||= '' - relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) - - if !app_path - Pod::UI.warn '[Codegen] Error: app_path is required for use_react_native_codegen_discovery.' - Pod::UI.warn '[Codegen] If you are calling use_react_native_codegen_discovery! in your Podfile, please remove the call and pass `app_path` and/or `config_file_dir` to `use_react_native!`.' - exit 1 - end - - # Generate React-Codegen podspec here to add the script phases. - script_phases = get_react_codegen_script_phases(options) - react_codegen_spec = get_react_codegen_spec(fabric_enabled: fabric_enabled, script_phases: script_phases) - generate_react_codegen_podspec!(react_codegen_spec) - - out = Pod::Executable.execute_command( - 'node', - [ - "#{relative_installation_root}/#{react_native_path}/scripts/generate-artifacts.js", - "-p", "#{app_path}", - "-o", Pod::Config.instance.installation_root, - "-e", "#{fabric_enabled}", - "-c", "#{config_file_dir}", - ]) - Pod::UI.puts out; - - $REACT_CODEGEN_DISCOVERY_DONE = true -end - +# === LEGACY METHOD === +# We need to keep this while we continue to support the old architecture. +# ===================== def use_react_native_codegen!(spec, options={}) return if ENV['RCT_NEW_ARCH_ENABLED'] == '1' # TODO: Once the new codegen approach is ready for use, we should output a warning here to let folks know to migrate.