Move fabric setup to a separate file (#33818)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/33818

This Diff moves the fabric setup from the `react_native_pods` script to its own `fabric` file.

It also introduces tests for the file and some test utilities.

## Changelog
[iOS][Changed] - Move fabric setup to its own file

Reviewed By: cortinico, dmitryrykun

Differential Revision: D36344911

fbshipit-source-id: 586186684be2c0080f247390f26145f2defa9e97
This commit is contained in:
Riccardo Cipolleschi 2022-05-18 02:57:35 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 2f491bfa9f
Коммит 8a8c33aab9
12 изменённых файлов: 613 добавлений и 62 удалений

Просмотреть файл

@ -53,6 +53,8 @@
"scripts/react_native_pods_utils/script_phases.rb",
"scripts/react_native_pods_utils/script_phases.sh",
"scripts/react_native_pods.rb",
"scripts/cocoapods/codegen.rb",
"scripts/cocoapods/fabric.rb",
"scripts/cocoapods/flipper.rb",
"scripts/react-native-xcode.sh",
"sdks/hermes-engine",

Просмотреть файл

@ -0,0 +1,185 @@
# 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_relative "../codegen.rb"
require_relative "./test_utils/PodMock.rb"
require_relative "./test_utils/PathnameMock.rb"
require_relative "./test_utils/FileMock.rb"
require_relative "./test_utils/DirMock.rb"
require_relative "./test_utils/systemUtils.rb"
class CodegenTests < Test::Unit::TestCase
:third_party_provider_header
:third_party_provider_implementation
:base_path
:prefix
:tmp_schema_list_file
def setup
File.enable_testing_mode!
Dir.enable_testing_mode!
Pod::Config.reset()
@prefix = "../.."
@third_party_provider_header = "RCTThirdPartyFabricComponentsProvider.h"
@third_party_provider_implementation = "RCTThirdPartyFabricComponentsProvider.cpp"
@base_path = "~/app/ios"
@tmp_schema_list_file = "tmpSchemaList.txt"
Pathname.pwd!(@base_path)
Pod::Config.instance.installation_root.relative_path_from = @base_path
end
def teardown
system_reset_commands()
Pod::UI.reset()
Pod::Executable.reset()
Pathname.reset()
File.reset()
Dir.reset()
end
# ============================================== #
# Test - setup_fabric #
# ============================================== #
def testCheckAndGenerateEmptyThirdPartyProvider_whenFileAlreadyExists_doNothing()
# Arrange
File.mocked_existing_files([
@base_path + "/build/" + @third_party_provider_header,
@base_path + "/build/" + @third_party_provider_implementation,
])
# Act
checkAndGenerateEmptyThirdPartyProvider!(@prefix, false, 'build')
# Assert
assert_equal(Pathname.pwd_invocation_count, 1)
assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 1)
assert_equal(File.exist_invocation_params, [
@base_path + "/build/" + @third_party_provider_header,
@base_path + "/build/" + @third_party_provider_implementation,
])
assert_equal(Dir.exist_invocation_params, [])
assert_equal(Pod::UI.collected_messages, [])
assert_equal($collected_commands, [])
assert_equal(File.open_files.length, 0)
assert_equal(Pod::Executable.executed_commands.length, 0)
end
def testCheckAndGenerateEmptyThirdPartyProvider_whenHeaderMissingAndCodegenMissing_raiseError()
# Arrange
File.mocked_existing_files([
@base_path + "/build/" + @third_party_provider_implementation,
])
# Act
assert_raise {
checkAndGenerateEmptyThirdPartyProvider!(@prefix, false, 'build')
}
# Assert
assert_equal(Pathname.pwd_invocation_count, 1)
assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 1)
assert_equal(File.exist_invocation_params, [
@base_path + "/build/" + @third_party_provider_header
])
assert_equal(Dir.exist_invocation_params, [
@base_path + "/"+ @prefix + "/packages/react-native-codegen",
@base_path + "/"+ @prefix + "/../react-native-codegen",
])
assert_equal(Pod::UI.collected_messages, [])
assert_equal($collected_commands, [])
assert_equal(File.open_files.length, 0)
assert_equal(Pod::Executable.executed_commands.length, 0)
end
def testCheckAndGenerateEmptyThirdPartyProvider_whenImplementationMissingAndCodegenrepoExists_dontBuildCodegen()
# Arrange
File.mocked_existing_files([
@base_path + "/build/" + @third_party_provider_header,
@base_path + "/build/tmpSchemaList.txt"
])
Dir.mocked_existing_dirs([
@base_path + "/"+ @prefix + "/packages/react-native-codegen",
@base_path + "/"+ @prefix + "/packages/react-native-codegen/lib"
])
# Act
checkAndGenerateEmptyThirdPartyProvider!(@prefix, false, 'build')
# Assert
assert_equal(Pathname.pwd_invocation_count, 1)
assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 1)
assert_equal(File.exist_invocation_params, [
@base_path + "/build/" + @third_party_provider_header,
@base_path + "/build/" + @third_party_provider_implementation,
@base_path + "/build/tmpSchemaList.txt",
])
assert_equal(Dir.exist_invocation_params, [
@base_path + "/"+ @prefix + "/packages/react-native-codegen",
@base_path + "/"+ @prefix + "/packages/react-native-codegen/lib",
])
assert_equal(Pod::UI.collected_messages, ["[Codegen] generating an empty RCTThirdPartyFabricComponentsProvider"])
assert_equal($collected_commands, [])
assert_equal(File.open_invocation_count, 1)
assert_equal(File.open_files_with_mode[@base_path + "/build/tmpSchemaList.txt"], 'w')
assert_equal(File.open_files[0].collected_write, ["[]"])
assert_equal(File.open_files[0].fsync_invocation_count, 1)
assert_equal(Pod::Executable.executed_commands[0], {
"command" => "node",
"arguments" => [
@base_path + "/" + @prefix + "/scripts/generate-provider-cli.js",
"--platform", 'ios',
"--schemaListPath", @base_path + "/build/tmpSchemaList.txt",
"--outputDir", @base_path + "/build"
]
})
assert_equal(File.delete_invocation_count, 1)
assert_equal(File.deleted_files, [@base_path + "/build/tmpSchemaList.txt"])
end
def testCheckAndGenerateEmptyThirdPartyProvider_whenBothMissing_buildCodegen()
# Arrange
codegen_cli_path = @base_path + "/" + @prefix + "/../react-native-codegen"
Dir.mocked_existing_dirs([
codegen_cli_path,
])
# Act
checkAndGenerateEmptyThirdPartyProvider!(@prefix, false, 'build')
# Assert
assert_equal(Pathname.pwd_invocation_count, 1)
assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 1)
assert_equal(File.exist_invocation_params, [
@base_path + "/build/" + @third_party_provider_header,
@base_path + "/build/" + @tmp_schema_list_file
])
assert_equal(Dir.exist_invocation_params, [
@base_path + "/" + @prefix + "/packages/react-native-codegen",
codegen_cli_path,
codegen_cli_path + "/lib",
])
assert_equal(Pod::UI.collected_messages, [
"[Codegen] building #{codegen_cli_path}.",
"[Codegen] generating an empty RCTThirdPartyFabricComponentsProvider"
])
assert_equal($collected_commands, ["~/app/ios/../../../react-native-codegen/scripts/oss/build.sh"])
assert_equal(File.open_files[0].collected_write, ["[]"])
assert_equal(File.open_files[0].fsync_invocation_count, 1)
assert_equal(Pod::Executable.executed_commands[0], {
"command" => "node",
"arguments" => [
@base_path + "/" + @prefix + "/scripts/generate-provider-cli.js",
"--platform", 'ios',
"--schemaListPath", @base_path + "/build/" + @tmp_schema_list_file,
"--outputDir", @base_path + "/build"
]
})
end
end

Просмотреть файл

@ -0,0 +1,49 @@
# 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_relative "../fabric.rb"
require_relative "./test_utils/podSpy.rb"
class FabricTest < Test::Unit::TestCase
def setup
podSpy_cleanUp()
end
def test_setupFabric_installsPods
# Arrange
prefix = "../.."
# Act
setup_fabric!(prefix)
# Assert
check_installed_pods(prefix)
end
def check_installed_pods(prefix)
assert_equal($podInvocationCount, 6)
check_pod("React-Fabric", :path => "#{prefix}/ReactCommon")
check_pod("React-rncore", :path => "#{prefix}/ReactCommon")
check_pod("React-graphics", :path => "#{prefix}/ReactCommon/react/renderer/graphics")
check_pod("React-jsi/Fabric", :path => "#{prefix}/ReactCommon/jsi")
check_pod("React-RCTFabric", :path => "#{prefix}/React", :modular_headers => true)
check_pod("RCT-Folly/Fabric", :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec")
end
def check_pod(name, path: nil, modular_headers: nil, podspec: nil)
params = $podInvocation[name]
expected_params = {}
if path != nil then expected_params[:path] = path end
if modular_headers != nil then expected_params[:modular_headers] = modular_headers end
if podspec != nil then expected_params[:podspec] = podspec end
assert_equal(params, expected_params)
end
end

Просмотреть файл

@ -0,0 +1,47 @@
# 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 Dir
@@is_testing = false
@@exist_invocation_params = []
@@mocked_existing_dirs = []
# Monkey patched exists? method.
# It is used also by the test runner, so it can't start monkey patched
# To use this, invoke the `is_testing` method before starting your test.
# Remember to invoke `reset` after the test.
def self.exist?(path)
if !@@is_testing
return exists?(path)
end
@@exist_invocation_params.push(path)
return @@mocked_existing_dirs.include?(path)
end
# Getter for the `exist_invocation_params` to check that the exist method
# is invoked with the right parameters
def self.exist_invocation_params()
return @@exist_invocation_params
end
# Set the list of dirs the test must return as existing
def self.mocked_existing_dirs(dirs)
@@mocked_existing_dirs = dirs
end
# Turn on the mocking features of the File mock
def self.enable_testing_mode!()
@@is_testing = true
end
# Resets all the settings for the File mock
def self.reset()
@@mocked_existing_dirs = []
@@is_testing = false
@@exist_invocation_params = []
end
end

Просмотреть файл

@ -0,0 +1,115 @@
# 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 File
@@is_testing = false
@@exist_invocation_params = []
@@mocked_existing_files = []
@@delete_invocation_count = 0
@@deleted_files = []
@@open_files_with_mode = {}
@@open_invocation_count = 0
@@open_files = []
attr_reader :collected_write
attr_reader :fsync_invocation_count
def initialize()
@collected_write = []
@fsync_invocation_count = 0
end
# Monkey patched exists? method.
# It is used also by the test runner, so it can't start monkey patched
# To use this, invoke the `is_testing` method before starting your test.
# Remember to invoke `reset` after the test.
def self.exist?(path)
if !@@is_testing
return exists?(path)
end
@@exist_invocation_params.push(path)
return @@mocked_existing_files.include?(path)
end
def self.delete(path)
if !@@is_testing
delete(path)
return
end
@@delete_invocation_count += 1
@@deleted_files.push(path)
end
def self.delete_invocation_count
return @@delete_invocation_count
end
def self.deleted_files
return @@deleted_files
end
# Getter for the `exist_invocation_params` to check that the exist method
# is invoked the right number of times with the right parameters
def self.exist_invocation_params()
return @@exist_invocation_params
end
# Set the list of files the test must return as existing
def self.mocked_existing_files(files)
@@mocked_existing_files = files
end
# Turn on the mocking features of the File mock
def self.enable_testing_mode!()
@@is_testing = true
end
def self.open(path, mode, &block)
@@open_files_with_mode[path] = mode
@@open_invocation_count += 1
file = File.new()
@@open_files.push(file)
yield(file)
end
def self.open_files_with_mode
return @@open_files_with_mode
end
def self.open_invocation_count
return @@open_invocation_count
end
def self.open_files
return @@open_files
end
def write(text)
@collected_write.push(text.to_s)
end
def fsync()
@fsync_invocation_count += 1
end
# Resets all the settings for the File mock
def self.reset()
@@delete_invocation_count = 0
@@deleted_files = []
@@open_files = []
@@open_files_with_mode = {}
@@open_invocation_count = 0
@@mocked_existing_files = []
@@is_testing = false
@@exist_invocation_params = []
end
end

Просмотреть файл

@ -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 Pathname
@@pwd = ""
@@pwd_invocation_count = 0
def self.pwd!(pwd)
@@pwd = pwd
end
def self.pwd()
@@pwd_invocation_count += 1
return @@pwd
end
def self.pwd_invocation_count
return @@pwd_invocation_count
end
def self.reset()
@@pwd = ""
@@pwd_invocation_count = 0
end
end

Просмотреть файл

@ -0,0 +1,76 @@
# 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.
module Pod
class Config
@@instance = Config.new()
attr_reader :installation_root
def initialize()
@installation_root = InstallationRootMock.new()
end
def self.instance()
return @@instance
end
def self.reset()
@@instance = Config.new()
end
end
class InstallationRootMock
attr_accessor :relative_path_from
attr_reader :relative_path_from_invocation_count
def initialize()
@relative_path_from = ""
@relative_path_from_invocation_count = 0
end
def relative_path_from(path)
@relative_path_from_invocation_count += 1
return @relative_path_from
end
end
class UI
@@collected_messages = []
def self.puts(message)
@@collected_messages.push(message)
end
def self.collected_messages()
return @@collected_messages
end
def self.reset()
@@collected_messages = []
end
end
class Executable
@@executed_commands = []
def self.execute_command(command, arguments)
@@executed_commands.push({
"command" => command,
"arguments" => arguments
})
end
def self.executed_commands
return @@executed_commands
end
def self.reset()
@@executed_commands = []
end
end
end

Просмотреть файл

@ -26,11 +26,13 @@ def podSpy_cleanUp
$podInvocationCount = 0
end
def pod(name, version = nil, path: nil, configurations: nil)
def pod(name, version = nil, path: nil, configurations: nil, modular_headers: nil, podspec: nil)
$podInvocationCount += 1
params = {}
if version != nil then params[:version] = version end
if path != nil then params[:path] = path end
if configurations != nil then params[:configurations] = configurations end
if modular_headers != nil then params[:modular_headers] = modular_headers end
if podspec != nil then params[:podspec] = podspec end
$podInvocation[name] = params
end

Просмотреть файл

@ -0,0 +1,14 @@
# 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.
$collected_commands = []
def system(command)
$collected_commands.push(command)
end
def system_reset_commands()
$collected_commands = []
end

Просмотреть файл

@ -0,0 +1,67 @@
# 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.
# It builds the codegen CLI if it is not present
#
# @parameter react_native_path: the path to the react native installation
# @parameter relative_installation_root: the path to the relative installation root of the pods
# @throws an error if it could not find the codegen folder.
def build_codegen!(react_native_path, relative_installation_root)
codegen_repo_path = "#{relative_installation_root}/#{react_native_path}/packages/react-native-codegen";
codegen_npm_path = "#{relative_installation_root}/#{react_native_path}/../react-native-codegen";
codegen_cli_path = ""
if Dir.exist?(codegen_repo_path)
codegen_cli_path = codegen_repo_path
elsif Dir.exist?(codegen_npm_path)
codegen_cli_path = codegen_npm_path
else
raise "[codegen] Couldn't not find react-native-codegen."
end
if !Dir.exist?("#{codegen_cli_path}/lib")
Pod::UI.puts "[Codegen] building #{codegen_cli_path}."
system("#{codegen_cli_path}/scripts/oss/build.sh")
end
end
# It generates an empty `ThirdPartyProvider`, required by Fabric to load the components
#
# @parameter react_native_path: path to the react native framework
# @parameter new_arch_enabled: whether the New Architecture is enabled or not
# @parameter codegen_output_dir: the output directory for the codegen
def checkAndGenerateEmptyThirdPartyProvider!(react_native_path, new_arch_enabled, codegen_output_dir)
return if new_arch_enabled
relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
output_dir = "#{relative_installation_root}/#{codegen_output_dir}"
provider_h_path = "#{output_dir}/RCTThirdPartyFabricComponentsProvider.h"
provider_cpp_path ="#{output_dir}/RCTThirdPartyFabricComponentsProvider.cpp"
if(!File.exist?(provider_h_path) || !File.exist?(provider_cpp_path))
# build codegen
build_codegen!(react_native_path, relative_installation_root)
# Just use a temp empty schema list.
temp_schema_list_path = "#{output_dir}/tmpSchemaList.txt"
File.open(temp_schema_list_path, 'w') do |f|
f.write('[]')
f.fsync
end
Pod::UI.puts '[Codegen] generating an empty RCTThirdPartyFabricComponentsProvider'
Pod::Executable.execute_command(
'node',
[
"#{relative_installation_root}/#{react_native_path}/scripts/generate-provider-cli.js",
"--platform", 'ios',
"--schemaListPath", temp_schema_list_path,
"--outputDir", "#{output_dir}"
])
File.delete(temp_schema_list_path) if File.exist?(temp_schema_list_path)
end
end

Просмотреть файл

@ -0,0 +1,19 @@
# 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.
# It sets up the faric dependencies.
#
# @parameter prefix: prefix to use to reach react-native
# @parameter new_arch_enabled: whether the new arch is enabled or not
# @parameter codegen_output_dir: the directory where the code is generated
def setup_fabric!(prefix)
pod 'React-Fabric', :path => "#{prefix}/ReactCommon"
pod 'React-rncore', :path => "#{prefix}/ReactCommon"
pod 'React-graphics', :path => "#{prefix}/ReactCommon/react/renderer/graphics"
pod 'React-jsi/Fabric', :path => "#{prefix}/ReactCommon/jsi"
pod 'React-RCTFabric', :path => "#{prefix}/React", :modular_headers => true
pod 'RCT-Folly/Fabric', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec"
end

Просмотреть файл

@ -6,6 +6,8 @@
require 'pathname'
require_relative './react_native_pods_utils/script_phases.rb'
require_relative './cocoapods/flipper.rb'
require_relative './cocoapods/fabric.rb'
require_relative './cocoapods/codegen.rb'
$CODEGEN_OUTPUT_DIR = 'build/generated/ios'
$CODEGEN_COMPONENT_DIR = 'react/renderer/components'
@ -22,6 +24,9 @@ def use_react_native! (options={})
# Include Fabric dependencies
fabric_enabled = options[:fabric_enabled] ||= false
# New arch enabled
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
# Include DevSupport dependency
production = options[:production] ||= false
@ -73,7 +78,7 @@ 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 ENV['RCT_NEW_ARCH_ENABLED'] == '1'
if new_arch_enabled
app_path = options[:app_path]
config_file_dir = options[:config_file_dir]
use_react_native_codegen_discovery!({
@ -92,13 +97,8 @@ def use_react_native! (options={})
pod 'React-Codegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
if fabric_enabled
checkAndGenerateEmptyThirdPartyProvider!(prefix)
pod 'React-Fabric', :path => "#{prefix}/ReactCommon"
pod 'React-rncore', :path => "#{prefix}/ReactCommon"
pod 'React-graphics', :path => "#{prefix}/ReactCommon/react/renderer/graphics"
pod 'React-jsi/Fabric', :path => "#{prefix}/ReactCommon/jsi"
pod 'React-RCTFabric', :path => "#{prefix}/React", :modular_headers => true
pod 'RCT-Folly/Fabric', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec"
checkAndGenerateEmptyThirdPartyProvider!(prefix, new_arch_enabled, $CODEGEN_OUTPUT_DIR)
setup_fabric!(prefix)
end
if hermes_enabled
@ -248,59 +248,6 @@ def modify_flags_for_new_architecture(installer, cpp_flags)
end
end
def build_codegen!(react_native_path)
relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
codegen_repo_path = "#{relative_installation_root}/#{react_native_path}/packages/react-native-codegen";
codegen_npm_path = "#{relative_installation_root}/#{react_native_path}/../react-native-codegen";
codegen_cli_path = ""
if Dir.exist?(codegen_repo_path)
codegen_cli_path = codegen_repo_path
elsif Dir.exist?(codegen_npm_path)
codegen_cli_path = codegen_npm_path
else
raise "[codegen] Couldn't not find react-native-codegen."
end
if !Dir.exist?("#{codegen_cli_path}/lib")
Pod::UI.puts "[Codegen] building #{codegen_cli_path}."
system("#{codegen_cli_path}/scripts/oss/build.sh")
end
end
# This is a temporary supporting function until we enable use_react_native_codegen_discovery by default.
def checkAndGenerateEmptyThirdPartyProvider!(react_native_path)
return if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
output_dir = "#{relative_installation_root}/#{$CODEGEN_OUTPUT_DIR}"
provider_h_path = "#{output_dir}/RCTThirdPartyFabricComponentsProvider.h"
provider_cpp_path ="#{output_dir}/RCTThirdPartyFabricComponentsProvider.cpp"
if(!File.exist?(provider_h_path) || !File.exist?(provider_cpp_path))
# build codegen
build_codegen!(react_native_path)
# Just use a temp empty schema list.
temp_schema_list_path = "#{output_dir}/tmpSchemaList.txt"
File.open(temp_schema_list_path, 'w') do |f|
f.write('[]')
f.fsync
end
Pod::UI.puts '[Codegen] generating an empty RCTThirdPartyFabricComponentsProvider'
Pod::Executable.execute_command(
'node',
[
"#{relative_installation_root}/#{react_native_path}/scripts/generate-provider-cli.js",
"--platform", 'ios',
"--schemaListPath", temp_schema_list_path,
"--outputDir", "#{output_dir}"
])
File.delete(temp_schema_list_path) if File.exist?(temp_schema_list_path)
end
end
def get_react_codegen_spec(options={})
fabric_enabled = options[:fabric_enabled] ||= false
script_phases = options[:script_phases] ||= nil