feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,59 @@
# 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_relative './helpers.rb'
# It builds the codegen CLI if it is not present
#
# Parameters:
# - react_native_path: the path to the react native installation
# - relative_installation_root: the path to the relative installation root of the pods
# - dir_manager: a class that implements the `Dir` interface. Defaults to `Dir`, the Dependency can be injected for testing purposes.
# @throws an error if it could not find the codegen folder.
def build_codegen!(react_native_path, relative_installation_root, dir_manager: Dir)
codegen_repo_path = "#{basePath(react_native_path, relative_installation_root)}/../react-native-codegen"
return unless dir_manager.exist?(codegen_repo_path) && !dir_manager.exist?("#{codegen_repo_path}/lib")
Pod::UI.puts "[Codegen] building #{codegen_repo_path}"
system("#{codegen_repo_path}/scripts/oss/build.sh")
end
# keeping the run_codegen! method for testing purposes
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,
hermes_enabled: true,
codegen_output_dir: 'build/generated/ios',
config_key: 'codegenConfig',
package_json_file: '~/app/package.json',
folly_version: Helpers::Constants.folly_config()[:version],
codegen_utils: CodegenUtils.new()
)
codegen_utils.use_react_native_codegen_discovery!(
disable_codegen,
app_path,
:react_native_path => react_native_path,
:fabric_enabled => fabric_enabled,
:hermes_enabled => hermes_enabled,
:config_file_dir => config_file_dir,
:codegen_output_dir => codegen_output_dir,
:config_key => config_key,
:folly_version => folly_version
)
end
def basePath(react_native_path, relative_installation_root)
expanded_path = File.expand_path(react_native_path)
if expanded_path == react_native_path
react_native_path
else
File.join(relative_installation_root.to_s, react_native_path)
end
end

View File

@@ -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

View File

@@ -0,0 +1,364 @@
# 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 './utils.rb'
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
# - file_manager: a class that implements the `File` interface. Defaults to `File`, the Dependency can be injected for testing purposes.
def generate_react_codegen_podspec!(spec, codegen_output_dir, file_manager: File)
# 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_manager.join(output_dir, 'React-Codegen.podspec.json')
Pod::UI.puts "[Codegen] Generating #{podspec_path}"
file_manager.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
# - hermes_enabled: whether hermes is enabled or not.
# - script_phases: whether we want to add some build script phases or not.
# - file_manager: a class that implements the `File` interface. Defaults to `File`, the Dependency can be injected for testing purposes.
def get_react_codegen_spec(package_json_file, folly_version: get_folly_config()[:version], hermes_enabled: true, script_phases: nil, file_manager: File)
package = JSON.parse(file_manager.read(package_json_file))
version = package['version']
use_frameworks = ENV['USE_FRAMEWORKS'] != nil
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -Wno-comma -Wno-shorten-64-to-32'
boost_compiler_flags = '-Wno-documentation'
header_search_paths = [
"\"$(PODS_ROOT)/boost\"",
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
"\"${PODS_ROOT}/Headers/Public/React-Codegen/react/renderer/components\"",
"\"$(PODS_ROOT)/Headers/Private/React-Fabric\"",
"\"$(PODS_ROOT)/Headers/Private/React-RCTFabric\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
"\"$(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
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++20",
'source' => { :git => '' },
'header_mappings_dir' => './',
'platforms' => min_supported_versions,
'source_files' => "**/*.{h,mm,cpp}",
'pod_target_xcconfig' => {
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"FRAMEWORK_SEARCH_PATHS" => framework_search_paths
},
'dependencies': {
"React-jsiexecutor": [],
"RCT-Folly": [],
"RCTRequired": [],
"RCTTypeSafety": [],
"React-Core": [],
"React-jsi": [],
"ReactCommon/turbomodule/bridging": [],
"ReactCommon/turbomodule/core": [],
"React-NativeModulesApple": [],
"glog": [],
"DoubleConversion": [],
'React-graphics': [],
'React-rendererdebug': [],
'React-Fabric': [],
'React-FabricImage': [],
'React-debug': [],
'React-utils': [],
'React-featureflags': [],
}
}
if hermes_enabled
spec[:'dependencies'].merge!({
'hermes-engine': [],
});
else
spec[:'dependencies'].merge!({
'React-jsc': [],
});
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_key: the codegen configuration key
# - file_manager: a class that implements the `File` interface. Defaults to `File`, the Dependency can be injected for testing purposes.
#
# Returns: the list of dependencies as extracted from the package.json
def get_codegen_config_from_file(config_path, config_key, file_manager: File)
empty = {}
if !file_manager.exist?(config_path)
return empty
end
config = JSON.parse(file_manager.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
# - file_manager: a class that implements the `File` interface. Defaults to `File`, the Dependency can be injected for testing purposes.
#
# Returns: the list of files that needs to be used by Codegen
def get_list_of_js_specs(app_codegen_config, app_path, file_manager: File)
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_manager.join(app_path, library['jsSrcsDir'])
file_list.concat(Finder.find_codegen_file(library_dir))
end
elsif app_codegen_config['jsSrcsDir'] then
codegen_dir = file_manager.join(app_path, app_codegen_config['jsSrcsDir'])
file_list.concat (Finder.find_codegen_file(codegen_dir))
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
# - file_manager: a class that implements the `File` interface. Defaults to `File`, the Dependency can be injected for testing purposes.
#
# Return: an object containing the script phase
def get_react_codegen_script_phases(
app_path,
fabric_enabled: false,
hermes_enabled: false,
config_file_dir: '',
react_native_path: "../node_modules/react-native",
config_key: 'codegenConfig',
codegen_utils: CodegenUtils.new(),
script_phase_extractor: CodegenScriptPhaseExtractor.new(),
file_manager: File
)
if !app_path
Pod::UI.warn '[Codegen] error: app_path is required 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-codegen-artifacts.js and read the result here.
# Or, generate this podspec in generate-codegen-artifacts.js as well.
app_package_path = file_manager.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,
hermes_enabled: true,
config_file_dir: '',
codegen_output_dir: 'build/generated/ios',
config_key: 'codegenConfig',
folly_version: get_folly_config()[:version],
codegen_utils: CodegenUtils.new(),
file_manager: File
)
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_manager.join(relative_installation_root, react_native_path, "package.json"),
:folly_version => folly_version,
:hermes_enabled => hermes_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-codegen-artifacts.js",
"-p", "#{app_path}",
"-o", Pod::Config.instance.installation_root,
"-t", "ios",
])
Pod::UI.puts out;
CodegenUtils.set_react_codegen_discovery_done(true)
end
@@CLEANUP_DONE = false
def self.set_cleanup_done(newValue)
@@CLEANUP_DONE = newValue
end
def self.cleanup_done
return @@CLEANUP_DONE
end
def self.clean_up_build_folder(rn_path, codegen_dir, dir_manager: Dir, file_manager: File)
return if CodegenUtils.cleanup_done()
CodegenUtils.set_cleanup_done(true)
ios_folder = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
codegen_path = file_manager.join(ios_folder, codegen_dir)
return if !dir_manager.exist?(codegen_path)
FileUtils.rm_rf(dir_manager.glob("#{codegen_path}/*"))
base_provider_path = file_manager.join(rn_path, 'React', 'Fabric', 'RCTThirdPartyFabricComponentsProvider')
FileUtils.rm_rf("#{base_provider_path}.h")
FileUtils.rm_rf("#{base_provider_path}.mm")
CodegenUtils.assert_codegen_folder_is_empty(codegen_path, dir_manager: dir_manager)
end
# Need to split this function from the previous one to be able to test it properly.
def self.assert_codegen_folder_is_empty(codegen_path, dir_manager: Dir)
# double check that the files have actually been deleted.
# Emit an error message if not.
if dir_manager.exist?(codegen_path) && dir_manager.glob("#{codegen_path}/*").length() != 0
Pod::UI.warn "Unable to remove the content of #{codegen_path} folder. Please run rm -rf #{codegen_path} and try again."
abort
end
end
end

View File

@@ -0,0 +1,17 @@
# 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 Fabric dependencies.
#
# @parameter react_native_path: relative path to react-native
def setup_fabric!(react_native_path: "../node_modules/react-native")
pod 'React-Fabric', :path => "#{react_native_path}/ReactCommon"
pod 'React-graphics', :path => "#{react_native_path}/ReactCommon/react/renderer/graphics"
pod 'React-RCTFabric', :path => "#{react_native_path}/React", :modular_headers => true
pod 'React-ImageManager', :path => "#{react_native_path}/ReactCommon/react/renderer/imagemanager/platform/ios"
pod 'RCT-Folly/Fabric', :podspec => "#{react_native_path}/third-party-podspecs/RCT-Folly.podspec"
pod 'React-FabricImage', :path => "#{react_native_path}/ReactCommon"
end

View File

@@ -0,0 +1,55 @@
# 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.
# Helper object to wrap the invocation of sysctl
# This makes it easier to mock the behaviour in tests
class SysctlChecker
def call_sysctl_arm64
return `/usr/sbin/sysctl -n hw.optional.arm64 2>&1`.to_i
end
end
# Helper class that is used to easily send commands to Xcodebuild
# And that can be subclassed for testing purposes.
class Xcodebuild
def self.version
`xcodebuild -version`
end
end
# Helper object to wrap system properties like RUBY_PLATFORM
# This makes it easier to mock the behaviour in tests
class Environment
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
module Helpers
class Constants
def self.min_ios_version_supported
return '13.4'
end
def self.min_xcode_version_supported
return '14.3'
end
def self.folly_config
return {
:version => '2024.01.01.00',
:compiler_flags => '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -Wno-comma -Wno-shorten-64-to-32'
}
end
end
end

View File

@@ -0,0 +1,33 @@
# 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_relative './utils.rb'
# It sets up the JavaScriptCore and JSI pods.
#
# @parameter react_native_path: relative path to react-native
# @parameter fabric_enabled: whether Fabirc is enabled
def setup_jsc!(react_native_path: "../node_modules/react-native", fabric_enabled: false)
pod 'React-jsi', :path => "#{react_native_path}/ReactCommon/jsi"
pod 'React-jsc', :path => "#{react_native_path}/ReactCommon/jsc"
if fabric_enabled
pod 'React-jsc/Fabric', :path => "#{react_native_path}/ReactCommon/jsc"
end
end
# It sets up the Hermes and JSI pods.
#
# @parameter react_native_path: relative path to react-native
# @parameter fabric_enabled: whether Fabirc is enabled
def setup_hermes!(react_native_path: "../node_modules/react-native")
react_native_dir = Pod::Config.instance.installation_root.join(react_native_path)
pod 'React-jsi', :path => "#{react_native_path}/ReactCommon/jsi"
# This `:tag => hermestag` below is only to tell CocoaPods to update hermes-engine when React Native version changes.
# We have custom logic to compute the source for hermes-engine. See sdks/hermes-engine/*
hermestag_file = File.join(react_native_dir, "sdks", ".hermesversion")
hermestag = File.exist?(hermestag_file) ? File.read(hermestag_file).strip : ''
pod 'hermes-engine', :podspec => "#{react_native_path}/sdks/hermes-engine/hermes-engine.podspec", :tag => hermestag
pod 'React-hermes', :path => "#{react_native_path}/ReactCommon/hermes"
end

View File

@@ -0,0 +1,51 @@
# 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.
# Monkeypatch of `Pod::Lockfile` to ensure automatic update of dependencies integrated with a local podspec when their version changed.
# This is necessary because local podspec dependencies must be otherwise manually updated.
module LocalPodspecPatch
# Returns local podspecs whose versions differ from the one in the `react-native` package.
def self.pods_to_update(react_native_path: "../node_modules/react-native", dir_manager: Dir, file_manager: File)
@@local_podspecs = dir_manager.glob("#{react_native_path}/third-party-podspecs/*").map { |file| file_manager.basename(file, ".podspec") }
@@local_podspecs = @@local_podspecs.select do |podspec_name|
# Read local podspec to determine the cached version
local_podspec_path = file_manager.join(
dir_manager.pwd, "Pods/Local Podspecs/#{podspec_name}.podspec.json"
)
# Local podspec cannot be outdated if it does not exist, yet
next unless file_manager.exist?(local_podspec_path)
local_podspec = file_manager.read(local_podspec_path)
local_podspec_json = JSON.parse(local_podspec)
local_version = local_podspec_json["version"]
# Read the version from a podspec from the `react-native` package
podspec_path = "#{react_native_path}/third-party-podspecs/#{podspec_name}.podspec"
current_podspec = Pod::Specification.from_file(podspec_path)
current_version = current_podspec.version.to_s
current_version != local_version
end
@@local_podspecs
end
# Patched `detect_changes_with_podfile` method
def detect_changes_with_podfile(podfile)
Pod::UI.puts "Invoke detect_changes_with_podfile patched method".red
changes = super(podfile)
return patch_detect_changes_with_podfile(changes)
end
def patch_detect_changes_with_podfile(changes)
@@local_podspecs.each do |local_podspec|
next unless changes[:unchanged].include?(local_podspec)
changes[:unchanged].delete(local_podspec)
changes[:changed] << local_podspec
end
changes
end
end

View File

@@ -0,0 +1,191 @@
# 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 "./utils.rb"
require_relative "./helpers.rb"
class NewArchitectureHelper
@@cplusplus_version = "c++20"
@@NewArchWarningEmitted = false # Used not to spam warnings to the user.
def self.set_clang_cxx_language_standard_if_needed(installer)
language_standard = nil
installer.pods_project.targets.each do |target|
# The React-Core pod may have a suffix added by Cocoapods, so we test whether 'React-Core' is a substring, and do not require exact match
if target.name.include? 'React-Core'
language_standard = target.resolved_build_setting("CLANG_CXX_LANGUAGE_STANDARD", resolve_against_xcconfig: true).values[0]
end
end
unless language_standard.nil?
projects = installer.aggregate_targets
.map{ |t| t.user_project }
.uniq{ |p| p.path }
projects.each do |project|
Pod::UI.puts("Setting CLANG_CXX_LANGUAGE_STANDARD to #{ language_standard } on #{ project.path }")
project.build_configurations.each do |config|
config.build_settings["CLANG_CXX_LANGUAGE_STANDARD"] = language_standard
end
project.save()
end
end
end
def self.computeFlags(is_new_arch_enabled)
new_arch_flag = is_new_arch_enabled ? "-DRCT_NEW_ARCH_ENABLED=1 " : ""
return " #{new_arch_flag}#{Helpers::Constants.folly_config()[:compiler_flags]}"
end
def self.modify_flags_for_new_architecture(installer, is_new_arch_enabled)
# Add flags to Target pods xcconfig
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.xcconfigs.each do |config_name, config_file|
ReactNativePodsUtils.add_flag_to_map_with_inheritance(config_file.attributes, "OTHER_CPLUSPLUSFLAGS", self.computeFlags(is_new_arch_enabled))
xcconfig_path = aggregate_target.xcconfig_path(config_name)
config_file.save_as(xcconfig_path)
end
end
# Add flags to Target pods xcconfig
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
# The React-Core pod may have a suffix added by Cocoapods, so we test whether 'React-Core' is a substring, and do not require exact match
if pod_name.include? 'React-Core'
target_installation_result.native_target.build_configurations.each do |config|
ReactNativePodsUtils.add_flag_to_map_with_inheritance(config.build_settings, "OTHER_CPLUSPLUSFLAGS", self.computeFlags(is_new_arch_enabled))
end
end
end
end
def self.install_modules_dependencies(spec, new_arch_enabled, folly_version = get_folly_config()[:version])
# Pod::Specification does not have getters so, we have to read
# the existing values from a hash representation of the object.
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
hash = spec.to_hash
compiler_flags = hash["compiler_flags"] ? hash["compiler_flags"] : ""
current_config = hash["pod_target_xcconfig"] != nil ? hash["pod_target_xcconfig"] : {}
current_headers = current_config["HEADER_SEARCH_PATHS"] != nil ? current_config["HEADER_SEARCH_PATHS"] : ""
header_search_paths = ["\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\""]
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_ROOT)/DoubleConversion\""
header_search_paths << "\"$(PODS_ROOT)/fmt/include\""
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", "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", "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-utils", "React_utils", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", []))
.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-ImageManager", "React_ImageManager", []))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", []))
.each { |search_path|
header_search_paths << "\"#{search_path}\""
}
end
header_search_paths_string = header_search_paths.join(" ")
spec.compiler_flags = compiler_flags.empty? ? self.computeFlags(new_arch_enabled).strip! : "#{compiler_flags} #{self.computeFlags(new_arch_enabled)}"
current_config["HEADER_SEARCH_PATHS"] = current_headers.empty? ?
header_search_paths_string :
"#{current_headers} #{header_search_paths_string}"
current_config["CLANG_CXX_LANGUAGE_STANDARD"] = @@cplusplus_version
spec.dependency "React-Core"
spec.dependency "RCT-Folly", folly_version
spec.dependency "glog"
ReactNativePodsUtils.add_flag_to_map_with_inheritance(current_config, "OTHER_CPLUSPLUSFLAGS", self.computeFlags(new_arch_enabled))
spec.dependency "React-RCTFabric" # This is for Fabric Component
spec.dependency "React-Codegen"
spec.dependency "RCTRequired"
spec.dependency "RCTTypeSafety"
spec.dependency "ReactCommon/turbomodule/bridging"
spec.dependency "ReactCommon/turbomodule/core"
spec.dependency "React-NativeModulesApple"
spec.dependency "Yoga"
spec.dependency "React-Fabric"
spec.dependency "React-graphics"
spec.dependency "React-utils"
spec.dependency "React-featureflags"
spec.dependency "React-debug"
spec.dependency "React-ImageManager"
spec.dependency "React-rendererdebug"
# This dependency is required for the cases when the pod includes generated sources, specifically Props.cpp.
spec.dependency "DoubleConversion"
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
spec.dependency "hermes-engine"
else
spec.dependency "React-jsi"
end
spec.pod_target_xcconfig = current_config
end
def self.folly_compiler_flags
folly_config = get_folly_config()
return folly_config[:compiler_flags]
end
def self.extract_react_native_version(react_native_path, file_manager: File, json_parser: JSON)
package_json_file = File.join(react_native_path, "package.json")
if !file_manager.exist?(package_json_file)
raise "Couldn't find the React Native package.json file at #{package_json_file}"
end
package = json_parser.parse(file_manager.read(package_json_file))
return package["version"]
end
def self.compute_new_arch_enabled(new_arch_enabled, react_native_version)
# Regex that identify a version with the syntax `<major>.<minor>.<patch>[-<prerelease>[.-]k]
# where
# - major is a number
# - minor is a number
# - patch is a number
# - prerelease is a string (can include numbers)
# - k is a number
version_regex = /^(\d+)\.(\d+)\.(\d+)(?:-(\w+(?:[-.]\d+)?))?$/
if match_data = react_native_version.match(version_regex)
prerelease = match_data[4].to_s
# We want to enforce the new architecture for 1.0.0 and greater,
# but not for 1000 as version 1000 is currently main.
if prerelease.include?("prealpha")
if ENV['RCT_NEW_ARCH_ENABLED'] != nil && !@@NewArchWarningEmitted
warning_message = "[New Architecture] Starting from version 1.0.0-prealpha the value of the " \
"RCT_NEW_ARCH_ENABLED flag is ignored and the New Architecture is enabled by default."
Pod::UI.warn warning_message
@@NewArchWarningEmitted = true
end
return "1"
end
end
return new_arch_enabled ? "1" : "0"
end
def self.new_arch_enabled
return ENV["RCT_NEW_ARCH_ENABLED"] == "1"
end
end

View File

@@ -0,0 +1,18 @@
# 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.
# Set up Bridgeless dependencies
#
# @parameter react_native_path: relative path to react-native
def setup_bridgeless!(react_native_path: "../node_modules/react-native", use_hermes: true)
pod "React-jsitracing", :path => "#{react_native_path}/ReactCommon/hermes/executor/"
pod "React-runtimescheduler", :path => "#{react_native_path}/ReactCommon/react/renderer/runtimescheduler"
pod 'React-RuntimeCore', :path => "#{react_native_path}/ReactCommon/react/runtime"
pod 'React-RuntimeApple', :path => "#{react_native_path}/ReactCommon/react/runtime/platform/ios"
if use_hermes
pod 'React-RuntimeHermes', :path => "#{react_native_path}/ReactCommon/react/runtime"
end
end

View File

@@ -0,0 +1,732 @@
# 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_relative "./helpers.rb"
# Utilities class for React Native Cocoapods
class ReactNativePodsUtils
def self.warn_if_not_on_arm64
if SysctlChecker.new().call_sysctl_arm64() == 1 && !Environment.new().ruby_platform().include?('arm64')
Pod::UI.warn 'Do not use "pod install" from inside Rosetta2 (x86_64 emulation on arm64).'
Pod::UI.warn ' - Emulated x86_64 is slower than native arm64'
Pod::UI.warn ' - May result in mixed architectures in rubygems (eg: ffi_c.bundle files may be x86_64 with an arm64 interpreter)'
Pod::UI.warn 'Run "env /usr/bin/arch -arm64 /bin/bash --login" then try again.'
end
end
# deprecated. These checks are duplicated in the react_native_pods function
# and we don't really need them. Removing this function will make it easy to
# move forward.
def self.get_default_flags
flags = {
:fabric_enabled => false,
:hermes_enabled => true,
}
if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
flags[:fabric_enabled] = true
flags[:hermes_enabled] = true
end
if ENV['USE_HERMES'] == '0'
flags[:hermes_enabled] = false
end
return flags
end
def self.has_pod(installer, name)
installer.pods_project.pod_group(name) != nil
end
def self.set_gcc_preprocessor_definition_for_React_hermes(installer)
self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", "Debug")
self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "hermes-engine", "Debug")
end
def self.turn_off_resource_bundle_react_core(installer)
# this is needed for Xcode 14, see more details here https://github.com/facebook/react-native/issues/34673
# we should be able to remove this once CocoaPods catches up to it, see more details here https://github.com/CocoaPods/CocoaPods/issues/11402
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
if pod_name.to_s == 'React-Core'
target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
resource_bundle_target.build_configurations.each do |config|
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
end
end
end
end
end
def self.set_use_hermes_build_setting(installer, hermes_enabled)
Pod::UI.puts("Setting USE_HERMES build settings")
projects = self.extract_projects(installer)
projects.each do |project|
project.build_configurations.each do |config|
config.build_settings["USE_HERMES"] = hermes_enabled
end
project.save()
end
end
def self.set_node_modules_user_settings(installer, react_native_path)
Pod::UI.puts("Setting REACT_NATIVE build settings")
projects = self.extract_projects(installer)
projects.each do |project|
project.build_configurations.each do |config|
config.build_settings["REACT_NATIVE_PATH"] = File.join("${PODS_ROOT}", "..", react_native_path)
end
project.save()
end
end
def self.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled)
projects = self.extract_projects(installer)
ccache_path = `command -v ccache`.strip
ccache_available = !ccache_path.empty?
message_prefix = "[Ccache]"
if ccache_available
Pod::UI.puts("#{message_prefix}: Ccache found at #{ccache_path}")
end
# Using scripts wrapping the ccache executable, to allow injection of configurations
ccache_clang_sh = File.join("$(REACT_NATIVE_PATH)", 'scripts', 'xcode', 'ccache-clang.sh')
ccache_clangpp_sh = File.join("$(REACT_NATIVE_PATH)", 'scripts', 'xcode', 'ccache-clang++.sh')
if ccache_available and ccache_enabled
Pod::UI.puts("#{message_prefix}: Setting CC, LD, CXX & LDPLUSPLUS build settings")
projects.each do |project|
project.build_configurations.each do |config|
# Using the un-qualified names means you can swap in different implementations, for example ccache
config.build_settings["CC"] = ccache_clang_sh
config.build_settings["LD"] = ccache_clang_sh
config.build_settings["CXX"] = ccache_clangpp_sh
config.build_settings["LDPLUSPLUS"] = ccache_clangpp_sh
end
project.save()
end
elsif ccache_available and !ccache_enabled
Pod::UI.puts("#{message_prefix}: Pass ':ccache_enabled => true' to 'react_native_post_install' in your Podfile or set environment variable 'USE_CCACHE=1' to increase the speed of subsequent builds")
elsif !ccache_available and ccache_enabled
Pod::UI.warn("#{message_prefix}: Install ccache or ensure your neither passing ':ccache_enabled => true' nor setting environment variable 'USE_CCACHE=1'")
else
Pod::UI.puts("#{message_prefix}: Removing Ccache from CC, LD, CXX & LDPLUSPLUS build settings")
projects.each do |project|
project.build_configurations.each do |config|
# Using the un-qualified names means you can swap in different implementations, for example ccache
config.build_settings["CC"] = config.build_settings["CC"] ? config.build_settings["CC"].gsub(/#{Regexp.escape(ccache_clang_sh)}/, '') : ""
config.build_settings["LD"] = config.build_settings["LD"] ? config.build_settings["LD"].gsub(/#{Regexp.escape(ccache_clang_sh)}/, "") : ""
config.build_settings["CXX"] = config.build_settings["CXX"] ? config.build_settings["CXX"].gsub(/#{Regexp.escape(ccache_clangpp_sh)}/, "") : ""
config.build_settings["LDPLUSPLUS"] = config.build_settings["LDPLUSPLUS"] ? config.build_settings["LDPLUSPLUS"].gsub(/#{Regexp.escape(ccache_clangpp_sh)}/, "") : ""
end
project.save()
end
end
end
def self.fix_library_search_paths(installer)
projects = self.extract_projects(installer)
projects.each do |project|
project.build_configurations.each do |config|
self.fix_library_search_path(config)
end
project.native_targets.each do |target|
target.build_configurations.each do |config|
self.fix_library_search_path(config)
end
end
project.save()
end
end
def self.apply_mac_catalyst_patches(installer)
# Fix bundle signing issues
installer.pods_project.targets.each do |target|
if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
target.build_configurations.each do |config|
config.build_settings['CODE_SIGN_IDENTITY[sdk=macosx*]'] = '-'
end
end
end
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.user_project.native_targets.each do |target|
target.build_configurations.each do |config|
# Explicitly set dead code stripping flags
config.build_settings['DEAD_CODE_STRIPPING'] = 'YES'
config.build_settings['PRESERVE_DEAD_CODE_INITS_AND_TERMS'] = 'YES'
# Modify library search paths
config.build_settings['LIBRARY_SEARCH_PATHS'] = ['$(SDKROOT)/usr/lib/swift', '$(SDKROOT)/System/iOSSupport/usr/lib/swift', '$(inherited)']
end
end
aggregate_target.user_project.save()
end
end
def self.apply_xcode_15_patch(installer, xcodebuild_manager: Xcodebuild)
projects = self.extract_projects(installer)
other_ld_flags_key = 'OTHER_LDFLAGS'
xcode15_compatibility_flags = '-Wl -ld_classic '
projects.each do |project|
project.build_configurations.each do |config|
# fix for weak linking
self.safe_init(config, other_ld_flags_key)
if self.is_using_xcode15_0(:xcodebuild_manager => xcodebuild_manager)
self.add_value_to_setting_if_missing(config, other_ld_flags_key, xcode15_compatibility_flags)
else
self.remove_value_from_setting_if_present(config, other_ld_flags_key, xcode15_compatibility_flags)
end
end
project.save()
end
end
private
def self.add_build_settings_to_pod(installer, settings_name, settings_value, target_pod_name, configuration)
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
if pod_name.to_s == target_pod_name
target_installation_result.native_target.build_configurations.each do |config|
if configuration == nil || (configuration != nil && config.name.include?(configuration))
config.build_settings[settings_name] ||= '$(inherited) '
config.build_settings[settings_name] << settings_value
end
end
end
end
end
def self.fix_library_search_path(config)
lib_search_paths = config.build_settings["LIBRARY_SEARCH_PATHS"]
if lib_search_paths == nil
# No search paths defined, return immediately
return
end
if lib_search_paths.include?("$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)") || lib_search_paths.include?("\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"")
# $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) causes problem with Xcode 12.5 + arm64 (Apple Silicon)
# since the libraries there are only built for x86_64 and i386.
lib_search_paths.delete("$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)")
lib_search_paths.delete("\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"")
end
if !(lib_search_paths.include?("$(SDKROOT)/usr/lib/swift") || lib_search_paths.include?("\"$(SDKROOT)/usr/lib/swift\""))
# however, $(SDKROOT)/usr/lib/swift is required, at least if user is not running CocoaPods 1.11
lib_search_paths.insert(0, "$(SDKROOT)/usr/lib/swift")
end
end
def self.create_xcode_env_if_missing(file_manager: File)
relative_path = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
file_path = file_manager.join(relative_path, '.xcode.env')
if !file_manager.exist?(file_path)
system("echo 'export NODE_BINARY=$(command -v node)' > #{file_path}")
end
if !file_manager.exist?("#{file_path}.local")
node_binary = `command -v node`
system("echo 'export NODE_BINARY=#{node_binary}' > #{file_path}.local")
end
end
# It examines the target_definition property and sets the appropriate value for
# ENV['USE_FRAMEWORKS'] variable.
#
# - parameter target_definition: The current target definition
def self.detect_use_frameworks(target_definition)
if ENV['USE_FRAMEWORKS'] != nil
return
end
framework_build_type = target_definition.build_type.to_s
Pod::UI.puts("Framework build type is #{framework_build_type}")
if framework_build_type === "static framework"
ENV['USE_FRAMEWORKS'] = 'static'
elsif framework_build_type === "dynamic framework"
ENV['USE_FRAMEWORKS'] = 'dynamic'
else
ENV['USE_FRAMEWORKS'] = nil
end
end
def self.create_header_search_path_for_frameworks(base_folder, pod_name, framework_name, additional_paths, include_base_path = true)
platforms = $RN_PLATFORMS != nil ? $RN_PLATFORMS : []
search_paths = []
if platforms.empty?() || platforms.length() == 1
base_path = File.join("${#{base_folder}}", pod_name, "#{framework_name}.framework", "Headers")
self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path)
else
platforms.each { |platform|
base_path = File.join("${#{base_folder}}", "#{pod_name}-#{platform}", "#{framework_name}.framework", "Headers")
self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path)
}
end
return search_paths
end
# Add a new dependency to an existing spec, configuring also the headers search paths
def self.add_dependency(spec, dependency_name, base_folder_for_frameworks, framework_name, additional_paths: [], version: nil, subspec_dependency: nil)
# Update Search Path
optional_current_search_path = spec.to_hash["pod_target_xcconfig"]["HEADER_SEARCH_PATHS"]
current_search_paths = (optional_current_search_path != nil ? optional_current_search_path : "")
.split(" ")
create_header_search_path_for_frameworks(base_folder_for_frameworks, dependency_name, framework_name, additional_paths)
.each { |path|
wrapped_path = "\"#{path}\""
current_search_paths << wrapped_path
}
current_pod_target_xcconfig = spec.to_hash["pod_target_xcconfig"]
current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] = current_search_paths.join(" ")
spec.pod_target_xcconfig = current_pod_target_xcconfig
actual_dependency = subspec_dependency != nil ? "#{dependency_name}/#{subspec_dependency}" : dependency_name
# Set Dependency
if !version
spec.dependency actual_dependency
else
spec.dependency actual_dependency, version
end
end
def self.update_search_paths(installer)
return if ENV['USE_FRAMEWORKS'] == nil
projects = self.extract_projects(installer)
projects.each do |project|
project.build_configurations.each do |config|
header_search_paths = config.build_settings["HEADER_SEARCH_PATHS"] ||= "$(inherited)"
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", "ReactCommon-Samples", "ReactCommon_Samples", ["platform/ios"]))
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"], false))
.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-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"]))
.each{ |search_path|
header_search_paths = self.add_search_path_if_not_included(header_search_paths, search_path)
}
config.build_settings["HEADER_SEARCH_PATHS"] = header_search_paths
end
project.save()
end
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
if self.react_native_pods.include?(pod_name) || pod_name.include?("Pod") || pod_name.include?("Tests")
next
end
self.set_rctfolly_search_paths(target_installation_result)
self.set_codegen_search_paths(target_installation_result)
self.set_reactcommon_searchpaths(target_installation_result)
self.set_rctfabric_search_paths(target_installation_result)
self.set_imagemanager_search_path(target_installation_result)
end
end
def self.updateOSDeploymentTarget(installer)
installer.target_installation_results.pod_target_installation_results
.each do |pod_name, target_installation_result|
target_installation_result.native_target.build_configurations.each do |config|
old_iphone_deploy_target = config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] ?
config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] :
Helpers::Constants.min_ios_version_supported
config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = [Helpers::Constants.min_ios_version_supported.to_f, old_iphone_deploy_target.to_f].max.to_s
end
end
end
def self.set_dynamic_frameworks_flags(installer)
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
# Set "RCT_DYNAMIC_FRAMEWORKS=1" if pod are installed with USE_FRAMEWORKS=dynamic
# This helps with backward compatibility.
if pod_name == 'React-RCTFabric' && ENV['USE_FRAMEWORKS'] == 'dynamic'
Pod::UI.puts "Setting -DRCT_DYNAMIC_FRAMEWORKS=1 to React-RCTFabric".green
rct_dynamic_framework_flag = " -DRCT_DYNAMIC_FRAMEWORKS=1"
target_installation_result.native_target.build_configurations.each do |config|
prev_build_settings = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherithed)"
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = prev_build_settings + rct_dynamic_framework_flag
end
end
end
end
# ========= #
# Utilities #
# ========= #
def self.extract_projects(installer)
return installer.aggregate_targets
.map{ |t| t.user_project }
.uniq{ |p| p.path }
.push(installer.pods_project)
end
def self.safe_init(config, setting_name)
old_config = config.build_settings[setting_name]
if old_config == nil
config.build_settings[setting_name] ||= '$(inherited) '
end
end
def self.add_value_to_setting_if_missing(config, setting_name, value)
old_config = config.build_settings[setting_name]
if old_config.is_a?(Array)
old_config = old_config.join(" ")
end
trimmed_value = value.strip()
if !old_config.include?(trimmed_value)
config.build_settings[setting_name] = "#{old_config.strip()} #{trimmed_value}".strip()
end
end
def self.remove_value_from_setting_if_present(config, setting_name, value)
old_config = config.build_settings[setting_name]
if old_config.is_a?(Array)
old_config = old_config.join(" ")
end
trimmed_value = value.strip()
if old_config.include?(trimmed_value)
new_config = old_config.gsub(trimmed_value, "")
config.build_settings[setting_name] = new_config.strip()
end
end
def self.is_using_xcode15_0(xcodebuild_manager: Xcodebuild)
xcodebuild_version = xcodebuild_manager.version
if version = self.parse_xcode_version(xcodebuild_version)
return version["major"] == 15 && version["minor"] == 0
end
return false
end
def self.parse_xcode_version(version_string)
# The output of xcodebuild -version is something like
# Xcode 15.0
# or
# Xcode 14.3.1
# We want to capture the version digits
match = version_string.match(/(\d+)\.(\d+)(?:\.(\d+))?/)
return nil if match.nil?
return {"str" => match[0], "major" => match[1].to_i, "minor" => match[2].to_i};
end
def self.check_minimum_required_xcode(xcodebuild_manager: Xcodebuild)
version = self.parse_xcode_version(xcodebuild_manager.version)
if (version.nil? || !Gem::Version::correct?(version["str"]))
Pod::UI.warn "Unexpected XCode version string '#{xcodebuild_manager.version}'"
return
end
current = version["str"]
min_required = Helpers::Constants.min_xcode_version_supported
if Gem::Version::new(current) < Gem::Version::new(min_required)
Pod::UI.puts "React Native requires XCode >= #{min_required}. Found #{current}.".red
raise "Please upgrade XCode"
end
end
def self.add_compiler_flag_to_project(installer, flag, configuration: nil)
projects = self.extract_projects(installer)
projects.each do |project|
project.build_configurations.each do |config|
self.set_flag_in_config(config, flag, configuration: configuration)
end
project.save()
end
end
def self.remove_compiler_flag_from_project(installer, flag, configuration: nil)
projects = self.extract_projects(installer)
projects.each do |project|
project.build_configurations.each do |config|
self.remove_flag_in_config(config, flag, configuration: configuration)
end
project.save()
end
end
def self.add_compiler_flag_to_pods(installer, flag, configuration: nil)
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
target_installation_result.native_target.build_configurations.each do |config|
self.set_flag_in_config(config, flag, configuration: configuration)
end
end
end
def self.set_flag_in_config(config, flag, configuration: nil)
if configuration == nil || config.name == configuration
self.add_flag_for_key(config, flag, "OTHER_CFLAGS")
self.add_flag_for_key(config, flag, "OTHER_CPLUSPLUSFLAGS")
end
end
def self.remove_flag_in_config(config, flag, configuration: nil)
if configuration == nil || config.name == configuration
self.remove_flag_for_key(config, flag, "OTHER_CFLAGS")
self.remove_flag_for_key(config, flag, "OTHER_CPLUSPLUSFLAGS")
end
end
def self.add_flag_for_key(config, flag, key)
current_setting = config.build_settings[key] ? config.build_settings[key] : "$(inherited)"
if current_setting.kind_of?(Array)
current_setting = current_setting
.map { |s| s.gsub('"', '') }
.map { |s| s.gsub('\"', '') }
.join(" ")
end
if !current_setting.include?(flag)
current_setting = "#{current_setting} #{flag}"
end
config.build_settings[key] = current_setting
end
def self.remove_flag_for_key(config, flag, key)
current_setting = config.build_settings[key] ? config.build_settings[key] : "$(inherited)"
if current_setting.kind_of?(Array)
current_setting = current_setting
.map { |s| s.gsub('"', '') }
.map { |s| s.gsub('\"', '') }
.join(" ")
end
if current_setting.include?(flag)
current_setting.slice! flag
end
config.build_settings[key] = current_setting
end
def self.add_search_path_if_not_included(current_search_paths, new_search_path)
if !current_search_paths.include?(new_search_path)
current_search_paths << " #{new_search_path}"
end
return current_search_paths
end
def self.update_header_paths_if_depends_on(target_installation_result, dependency_name, header_paths)
depends_on_framework = target_installation_result.native_target.dependencies.any? { |d| d.name == dependency_name }
if depends_on_framework
target_installation_result.native_target.build_configurations.each do |config|
header_search_path = config.build_settings["HEADER_SEARCH_PATHS"] != nil ? config.build_settings["HEADER_SEARCH_PATHS"] : "$(inherited)"
header_paths.each { |header| header_search_path = ReactNativePodsUtils.add_search_path_if_not_included(header_search_path, header) }
config.build_settings["HEADER_SEARCH_PATHS"] = header_search_path
end
end
end
def self.set_rctfolly_search_paths(target_installation_result)
ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "RCT-Folly", [
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
"\"$(PODS_ROOT)/boost\""
])
end
def self.set_codegen_search_paths(target_installation_result)
header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Codegen", "React_Codegen", [])
.map { |search_path| "\"#{search_path}\"" }
ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-Codegen", header_search_paths)
end
def self.set_reactcommon_searchpaths(target_installation_result)
header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"])
.map { |search_path| "\"#{search_path}\"" }
ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "ReactCommon", header_search_paths)
end
def self.set_rctfabric_search_paths(target_installation_result)
header_search_paths = 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-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"]))
.map { |search_path| "\"#{search_path}\"" }
ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-RCTFabric", header_search_paths)
end
def self.set_imagemanager_search_path(target_installation_result)
header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/imagemanager/platform/ios"])
.map { |search_path| "\"#{search_path}\"" }
ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-ImageManager", header_search_paths)
end
def self.get_privacy_manifest_paths_from(user_project)
privacy_manifests = user_project
.files
.select { |p|
p.path&.end_with?('PrivacyInfo.xcprivacy')
}
return privacy_manifests
end
def self.add_privacy_manifest_if_needed(installer)
user_project = installer.aggregate_targets
.map{ |t| t.user_project }
.first
privacy_manifest = self.get_privacy_manifest_paths_from(user_project).first
if privacy_manifest.nil?
file_timestamp_reason = {
"NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategoryFileTimestamp",
"NSPrivacyAccessedAPITypeReasons" => ["C617.1"],
}
user_defaults_reason = {
"NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategoryUserDefaults",
"NSPrivacyAccessedAPITypeReasons" => ["CA92.1"],
}
boot_time_reason = {
"NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategorySystemBootTime",
"NSPrivacyAccessedAPITypeReasons" => ["35F9.1"],
}
privacy_manifest = {
"NSPrivacyCollectedDataTypes" => [],
"NSPrivacyTracking" => false,
"NSPrivacyAccessedAPITypes" => [file_timestamp_reason, user_defaults_reason, boot_time_reason]
}
path = File.join(user_project.path.parent, "PrivacyInfo.xcprivacy")
Xcodeproj::Plist.write_to_path(privacy_manifest, path)
Pod::UI.puts "Your app does not have a privacy manifest! A template has been generated containing Required Reasons API usage in the core React Native library. Please add the PrivacyInfo.xcprivacy file to your project and complete data use, tracking and any additional required reasons your app is using according to Apple's guidance: https://developer.apple.com/.../privacy_manifest_files. Then, you will need to manually add this file to your project in Xcode.".red
end
end
def self.react_native_pods
return [
"DoubleConversion",
"FBLazyVector",
"RCT-Folly",
"RCTRequired",
"RCTTypeSafety",
"React",
"React-Codegen",
"React-Core",
"React-CoreModules",
"React-Fabric",
"React-FabricImage",
"React-ImageManager",
"React-RCTActionSheet",
"React-RCTAnimation",
"React-RCTAppDelegate",
"React-RCTBlob",
"React-RCTFabric",
"React-RCTImage",
"React-RCTLinking",
"React-RCTNetwork",
"React-RCTPushNotification",
"React-RCTSettings",
"React-RCTText",
"React-RCTTest",
"React-RCTVibration",
"React-callinvoker",
"React-cxxreact",
"React-graphics",
"React-jsc",
"React-jsi",
"React-jsiexecutor",
"React-jsinspector",
"React-logger",
"React-perflogger",
"React-rncore",
"React-runtimeexecutor",
"ReactCommon",
"Yoga",
"boost",
"fmt",
"glog",
"hermes-engine",
"React-hermes",
]
end
def self.add_search_path_to_result(result, base_path, additional_paths, include_base_path)
if (include_base_path)
result << base_path
end
additional_paths.each { |extra_path|
result << File.join(base_path, extra_path)
}
return result
end
def self.add_ndebug_flag_to_pods_in_release(installer)
ndebug_flag = " -DNDEBUG"
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.xcconfigs.each do |config_name, config_file|
is_release = config_name.downcase.include?("release") || config_name.downcase.include?("production")
unless is_release
next
end
self.add_flag_to_map_with_inheritance(config_file.attributes, 'OTHER_CPLUSPLUSFLAGS', ndebug_flag);
self.add_flag_to_map_with_inheritance(config_file.attributes, 'OTHER_CFLAGS', ndebug_flag);
xcconfig_path = aggregate_target.xcconfig_path(config_name)
config_file.save_as(xcconfig_path)
end
end
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
target_installation_result.native_target.build_configurations.each do |config|
is_release = config.name.downcase.include?("release") || config.name.downcase.include?("production")
unless is_release
next
end
self.add_flag_to_map_with_inheritance(config.build_settings, 'OTHER_CPLUSPLUSFLAGS', ndebug_flag);
self.add_flag_to_map_with_inheritance(config.build_settings, 'OTHER_CFLAGS', ndebug_flag);
end
end
end
def self.add_flag_to_map_with_inheritance(map, field, flag)
if map[field] == nil
map[field] = "$(inherited)" + flag
else
unless map[field].include?(flag)
map[field] = map[field] + flag
end
unless map[field].include?("$(inherited)")
map[field] = "$(inherited) " + map[field]
end
end
end
end

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
/**
* Wrapper required to abstract away from the actual codegen.
* This is needed because, when running tests in Sandcastle, not everything is setup as usually.
* For example, the `@react-native/codegen` lib is not present.
*
* Thanks to this wrapper, we are able to mock the getter for the codegen in a way that allow us to return
* a custom object which mimics the Codegen interface.
*
* @return an object that can generate the code for the New Architecture.
*/
function getCodegen() {
let RNCodegen;
try {
RNCodegen = require('../../packages/react-native-codegen/lib/generators/RNCodegen.js');
} catch (e) {
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen.js');
}
if (!RNCodegen) {
throw 'RNCodegen not found.';
}
return RNCodegen;
}
function getCombineJSToSchema() {
let combineJSToSchema;
try {
combineJSToSchema = require('../../packages/react-native-codegen/lib/cli/combine/combine-js-to-schema.js');
} catch (e) {
combineJSToSchema = require('@react-native/codegen/lib/cli/combine/combine-js-to-schema.js');
}
if (!combineJSToSchema) {
throw 'combine-js-to-schema not found.';
}
return combineJSToSchema;
}
module.exports = {
getCodegen: getCodegen,
getCombineJSToSchema: getCombineJSToSchema,
};

View File

@@ -0,0 +1,695 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
/**
* This script crawls through a React Native application's dependencies and invokes the codegen
* for any libraries that require it.
* To enable codegen support, the library should include a config in the codegenConfig key
* in a package.json file.
*/
const utils = require('./codegen-utils');
const generateSpecsCLIExecutor = require('./generate-specs-cli-executor');
const {execSync} = require('child_process');
const fs = require('fs');
const glob = require('glob');
const mkdirp = require('mkdirp');
const os = require('os');
const path = require('path');
const REACT_NATIVE_REPOSITORY_ROOT = path.join(
__dirname,
'..',
'..',
'..',
'..',
);
const REACT_NATIVE_PACKAGE_ROOT_FOLDER = path.join(__dirname, '..', '..');
const CODEGEN_REPO_PATH = `${REACT_NATIVE_REPOSITORY_ROOT}/packages/react-native-codegen`;
const CORE_LIBRARIES_WITH_OUTPUT_FOLDER = {
rncore: {
ios: path.join(REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'ReactCommon'),
android: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'ReactAndroid',
'build',
'generated',
'source',
'codegen',
),
},
FBReactNativeSpec: {
ios: null,
android: path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'ReactAndroid',
'build',
'generated',
'source',
'codegen',
),
},
};
const REACT_NATIVE = 'react-native';
const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTModulesConformingToProtocolsProviderH.template',
);
const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'scripts',
'codegen',
'templates',
'RCTModulesConformingToProtocolsProviderMM.template',
);
// HELPERS
function pkgJsonIncludesGeneratedCode(pkgJson) {
return pkgJson.codegenConfig && pkgJson.codegenConfig.includesGeneratedCode;
}
function isReactNativeCoreLibrary(libraryName) {
return libraryName in CORE_LIBRARIES_WITH_OUTPUT_FOLDER;
}
function readPkgJsonInDirectory(dir) {
const pkgJsonPath = path.join(dir, 'package.json');
if (!fs.existsSync(pkgJsonPath)) {
throw `[Codegen] Error: ${pkgJsonPath} does not exist.`;
}
return JSON.parse(fs.readFileSync(pkgJsonPath));
}
function printDeprecationWarningIfNeeded(dependency) {
if (dependency === REACT_NATIVE) {
return;
}
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": "."
}
}
`);
}
// Reading Libraries
function extractLibrariesFromConfigurationArray(configFile, dependencyPath) {
return configFile.codegenConfig.libraries.map(config => {
return {
config,
libraryPath: dependencyPath,
};
});
}
function extractLibrariesFromJSON(configFile, dependencyPath) {
if (configFile.codegenConfig == null) {
return [];
}
console.log(`[Codegen] Found ${configFile.name}`);
if (configFile.codegenConfig.libraries == null) {
const config = configFile.codegenConfig;
return [
{
config,
libraryPath: dependencyPath,
},
];
} else {
printDeprecationWarningIfNeeded(configFile.name);
return extractLibrariesFromConfigurationArray(configFile, dependencyPath);
}
}
const APPLE_PLATFORMS = ['ios', 'macos', 'tvos', 'visionos'];
// Cocoapods specific platform keys
function getCocoaPodsPlatformKey(platformName) {
if (platformName === 'macos') {
return 'osx';
}
return platformName;
}
function extractSupportedApplePlatforms(dependency, dependencyPath) {
console.log('[Codegen] Searching for podspec in the project dependencies.');
const podspecs = glob.sync('*.podspec', {cwd: dependencyPath});
if (podspecs.length === 0) {
return;
}
// Take the first podspec found
const podspec = fs.readFileSync(
path.join(dependencyPath, podspecs[0]),
'utf8',
);
/**
* Podspec can have platforms defined in two ways:
* 1. `spec.platforms = { :ios => "11.0", :tvos => "11.0" }`
* 2. `s.ios.deployment_target = "11.0"`
* `s.tvos.deployment_target = "11.0"`
*/
const supportedPlatforms = podspec
.split('\n')
.filter(
line => line.includes('platform') || line.includes('deployment_target'),
)
.join('');
// Generate a map of supported platforms { [platform]: true/false }
const supportedPlatformsMap = APPLE_PLATFORMS.reduce(
(acc, platform) => ({
...acc,
[platform]: supportedPlatforms.includes(
getCocoaPodsPlatformKey(platform),
),
}),
{},
);
const supportedPlatformsList = Object.keys(supportedPlatformsMap).filter(
key => supportedPlatformsMap[key],
);
if (supportedPlatformsList.length > 0) {
console.log(
`[Codegen] Supported Apple platforms: ${supportedPlatformsList.join(
', ',
)} for ${dependency}`,
);
}
return supportedPlatformsMap;
}
function findExternalLibraries(pkgJson) {
const dependencies = {
...pkgJson.dependencies,
...pkgJson.devDependencies,
...pkgJson.peerDependencies,
};
// Determine which of these are codegen-enabled libraries
console.log(
'[Codegen] Searching for codegen-enabled libraries in the project dependencies.',
);
// Handle third-party libraries
return Object.keys(dependencies).flatMap(dependency => {
try {
const configFilePath = require.resolve(
path.join(dependency, 'package.json'),
);
const configFile = JSON.parse(fs.readFileSync(configFilePath));
const codegenConfigFileDir = path.dirname(configFilePath);
return extractLibrariesFromJSON(configFile, codegenConfigFileDir);
} catch (e) {
return [];
}
});
}
function findLibrariesFromReactNativeConfig(projectRoot) {
const rnConfigFileName = 'react-native.config.js';
console.log(
`\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${rnConfigFileName}`,
);
const rnConfigFilePath = path.resolve(projectRoot, rnConfigFileName);
if (!fs.existsSync(rnConfigFilePath)) {
return [];
}
const rnConfig = require(rnConfigFilePath);
if (rnConfig.dependencies == null) {
return [];
}
return Object.keys(rnConfig.dependencies).flatMap(name => {
const dependencyConfig = rnConfig.dependencies[name];
if (!dependencyConfig.root) {
return [];
}
const codegenConfigFileDir = path.resolve(
projectRoot,
dependencyConfig.root,
);
let configFile;
try {
configFile = readPkgJsonInDirectory(codegenConfigFileDir);
} catch {
return [];
}
return extractLibrariesFromJSON(configFile, codegenConfigFileDir);
});
}
function findProjectRootLibraries(pkgJson, projectRoot) {
console.log('[Codegen] Searching for codegen-enabled libraries in the app.');
if (pkgJson.codegenConfig == null) {
console.log(
'[Codegen] The "codegenConfig" field is not defined in package.json. Assuming there is nothing to generate at the app level.',
);
return [];
}
if (typeof pkgJson.codegenConfig !== 'object') {
throw '[Codegen] The "codegenConfig" field must be an Object.';
}
return extractLibrariesFromJSON(pkgJson, projectRoot);
}
// CodeGen
function buildCodegenIfNeeded() {
if (!fs.existsSync(CODEGEN_REPO_PATH)) {
return;
}
// Assuming we are working in the react-native repo. We might need to build the codegen.
// This will become unnecessary once we start using Babel Register for the codegen package.
const libPath = path.join(CODEGEN_REPO_PATH, 'lib');
if (fs.existsSync(libPath) && fs.readdirSync(libPath).length > 0) {
return;
}
console.log('[Codegen] Building react-native-codegen package.');
execSync('yarn install', {
cwd: CODEGEN_REPO_PATH,
stdio: 'inherit',
});
execSync('yarn build', {
cwd: CODEGEN_REPO_PATH,
stdio: 'inherit',
});
}
function readOutputDirFromPkgJson(pkgJson, platform) {
const codegenConfig = pkgJson.codegenConfig;
if (codegenConfig == null || typeof codegenConfig !== 'object') {
return null;
}
const outputDir = codegenConfig.outputDir;
if (outputDir == null) {
return null;
}
if (typeof outputDir === 'string') {
return outputDir;
}
if (typeof outputDir === 'object') {
return outputDir[platform];
}
return null;
}
function defaultOutputPathForAndroid(baseOutputPath) {
return path.join(
baseOutputPath,
'android',
'app',
'build',
'generated',
'source',
'codegen',
);
}
function defaultOutputPathForIOS(baseOutputPath) {
return path.join(baseOutputPath, 'build', 'generated', 'ios');
}
function computeOutputPath(projectRoot, baseOutputPath, pkgJson, platform) {
if (baseOutputPath == null) {
const outputDirFromPkgJson = readOutputDirFromPkgJson(pkgJson, platform);
if (outputDirFromPkgJson != null) {
baseOutputPath = outputDirFromPkgJson;
} else {
baseOutputPath = projectRoot;
}
}
if (pkgJsonIncludesGeneratedCode(pkgJson)) {
// Don't create nested directories for libraries to make importing generated headers easier.
return baseOutputPath;
}
if (platform === 'android') {
return defaultOutputPathForAndroid(baseOutputPath);
}
if (platform === 'ios') {
return defaultOutputPathForIOS(baseOutputPath);
}
return baseOutputPath;
}
function reactNativeCoreLibraryOutputPath(libraryName, platform) {
return CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName]
? CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName][platform]
: null;
}
function generateSchemaInfo(library, platform) {
const pathToJavaScriptSources = path.join(
library.libraryPath,
library.config.jsSrcsDir,
);
console.log(`[Codegen] Processing ${library.config.name}`);
const supportedApplePlatforms = extractSupportedApplePlatforms(
library.config.name,
library.libraryPath,
);
// Generate one schema for the entire library...
return {
library: library,
supportedApplePlatforms,
schema: utils
.getCombineJSToSchema()
.combineSchemasInFileList(
[pathToJavaScriptSources],
platform,
/NativeSampleTurboModule/,
),
};
}
function generateCode(outputPath, schemaInfo, includesGeneratedCode, platform) {
const tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), schemaInfo.library.config.name),
);
const tmpOutputDir = path.join(tmpDir, 'out');
fs.mkdirSync(tmpOutputDir, {recursive: true});
console.log(`[Codegen] Generating Native Code for ${platform}`);
const useLocalIncludePaths = includesGeneratedCode;
generateSpecsCLIExecutor.generateSpecFromInMemorySchema(
platform,
schemaInfo.schema,
tmpOutputDir,
schemaInfo.library.config.name,
'com.facebook.fbreact.specs',
schemaInfo.library.config.type,
useLocalIncludePaths,
);
// Finally, copy artifacts to the final output directory.
const outputDir =
reactNativeCoreLibraryOutputPath(
schemaInfo.library.config.name,
platform,
) ?? outputPath;
fs.mkdirSync(outputDir, {recursive: true});
// TODO: Fix this. This will not work on Windows.
execSync(`cp -R ${tmpOutputDir}/* "${outputDir}"`);
console.log(`[Codegen] Generated artifacts: ${outputDir}`);
}
function generateSchemaInfos(libraries) {
return libraries.map(generateSchemaInfo);
}
function generateNativeCode(
outputPath,
schemaInfos,
includesGeneratedCode,
platform,
) {
return schemaInfos.map(schemaInfo => {
generateCode(outputPath, schemaInfo, includesGeneratedCode, platform);
});
}
function rootCodegenTargetNeedsThirdPartyComponentProvider(pkgJson, platform) {
return !pkgJsonIncludesGeneratedCode(pkgJson) && platform === 'ios';
}
function dependencyNeedsThirdPartyComponentProvider(schemaInfo, platform) {
// Filter the react native core library out.
// In the future, core library and third party library should
// use the same way to generate/register the fabric components.
return !isReactNativeCoreLibrary(schemaInfo.library.config.name, platform);
}
function mustGenerateNativeCode(includeLibraryPath, schemaInfo) {
// If library's 'codegenConfig' sets 'includesGeneratedCode' to 'true',
// then we assume that native code is shipped with the library,
// and we don't need to generate it.
return (
schemaInfo.library.libraryPath === includeLibraryPath ||
!schemaInfo.library.config.includesGeneratedCode
);
}
function createComponentProvider(schemas, supportedApplePlatforms) {
console.log('[Codegen] Creating component provider.');
const outputDir = path.join(
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
'React',
'Fabric',
);
mkdirp.sync(outputDir);
utils.getCodegen().generateFromSchemas(
{
schemas: schemas,
outputDirectory: outputDir,
supportedApplePlatforms,
},
{
generators: ['providerIOS'],
},
);
console.log(`[Codegen] Generated provider in: ${outputDir}`);
}
function findCodegenEnabledLibraries(pkgJson, projectRoot) {
const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot);
if (pkgJsonIncludesGeneratedCode(pkgJson)) {
return projectLibraries;
} else {
return [
...projectLibraries,
...findExternalLibraries(pkgJson),
...findLibrariesFromReactNativeConfig(projectRoot),
];
}
}
function generateCustomURLHandlers(libraries, outputDir) {
const customImageURLLoaderClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTImageURLLoader,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customImageDataDecoderClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTImageDataDecoder,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const customURLHandlerClasses = libraries
.flatMap(
library =>
library?.config?.ios?.modulesConformingToProtocol?.RCTURLRequestHandler,
)
.filter(Boolean)
.map(className => `@"${className}"`)
.join(',\n\t\t');
const template = fs.readFileSync(MODULES_PROTOCOLS_MM_TEMPLATE_PATH, 'utf8');
const finalMMFile = template
.replace(/{imageURLLoaderClassNames}/, customImageURLLoaderClasses)
.replace(/{imageDataDecoderClassNames}/, customImageDataDecoderClasses)
.replace(/{requestHandlersClassNames}/, customURLHandlerClasses);
fs.writeFileSync(
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.mm'),
finalMMFile,
);
const templateH = fs.readFileSync(MODULES_PROTOCOLS_H_TEMPLATE_PATH, 'utf8');
fs.writeFileSync(
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.h'),
templateH,
);
}
// It removes all the empty files and empty folders
// it finds, starting from `filepath`, recursively.
//
// This function is needed since, after aligning the codegen between
// iOS and Android, we have to create empty folders in advance and
// we don't know whether they will be populated up until the end of the process.
//
// @parameter filepath: the root path from which we want to remove the empty files and folders.
function cleanupEmptyFilesAndFolders(filepath) {
const stats = fs.statSync(filepath);
if (stats.isFile() && stats.size === 0) {
fs.rmSync(filepath);
return;
} else if (stats.isFile()) {
return;
}
const dirContent = fs.readdirSync(filepath);
dirContent.forEach(contentPath =>
cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)),
);
// The original folder may be filled with empty folders
// if that the case, we would also like to remove the parent.
// Hence, we need to read the folder again.
const newContent = fs.readdirSync(filepath);
if (newContent.length === 0) {
fs.rmdirSync(filepath);
return;
}
}
// Execute
/**
* This function is the entry point for the codegen. It:
* - reads the package json
* - extracts the libraries
* - setups the CLI to generate the code
* - generate the code
*
* @parameter projectRoot: the directory with the app source code, where the package.json lives.
* @parameter baseOutputPath: the base output path for the CodeGen.
* @parameter targetPlatform: the target platform. Supported values: 'android', 'ios', 'all'.
* @throws If it can't find a config file for react-native.
* @throws If it can't find a CodeGen configuration in the file.
* @throws If it can't find a cli for the CodeGen.
*/
function execute(projectRoot, targetPlatform, baseOutputPath) {
try {
console.log(
`[Codegen] Analyzing ${path.join(projectRoot, 'package.json')}`,
);
const supportedPlatforms = ['android', 'ios'];
if (
targetPlatform !== 'all' &&
!supportedPlatforms.includes(targetPlatform)
) {
throw new Error(
`Invalid target platform: ${targetPlatform}. Supported values are: ${supportedPlatforms.join(
', ',
)}, all`,
);
}
const pkgJson = readPkgJsonInDirectory(projectRoot);
buildCodegenIfNeeded();
const libraries = findCodegenEnabledLibraries(pkgJson, projectRoot);
if (libraries.length === 0) {
console.log('[Codegen] No codegen-enabled libraries found.');
return;
}
let platforms =
targetPlatform === 'all' ? supportedPlatforms : [targetPlatform];
for (const platform of platforms) {
const outputPath = computeOutputPath(
projectRoot,
baseOutputPath,
pkgJson,
platform,
);
const schemaInfos = generateSchemaInfos(libraries);
generateNativeCode(
outputPath,
schemaInfos.filter(schemaInfo =>
mustGenerateNativeCode(projectRoot, schemaInfo),
),
pkgJsonIncludesGeneratedCode(pkgJson),
platform,
);
if (
rootCodegenTargetNeedsThirdPartyComponentProvider(pkgJson, platform)
) {
const filteredSchemas = schemaInfos.filter(
dependencyNeedsThirdPartyComponentProvider,
);
const schemas = filteredSchemas.map(schemaInfo => schemaInfo.schema);
const supportedApplePlatforms = filteredSchemas.map(
schemaInfo => schemaInfo.supportedApplePlatforms,
);
createComponentProvider(schemas, supportedApplePlatforms);
generateCustomURLHandlers(libraries, outputPath);
}
cleanupEmptyFilesAndFolders(outputPath);
}
} catch (err) {
console.error(err);
process.exitCode = 1;
}
console.log('[Codegen] Done.');
return;
}
module.exports = {
execute: execute,
// exported for testing purposes only:
_extractLibrariesFromJSON: extractLibrariesFromJSON,
_cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders,
_extractSupportedApplePlatforms: extractSupportedApplePlatforms,
};

View File

@@ -0,0 +1,128 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const utils = require('./codegen-utils');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const GENERATORS = {
all: {
android: ['componentsAndroid', 'modulesAndroid', 'modulesCxx'],
ios: ['componentsIOS', 'modulesIOS', 'modulesCxx'],
},
components: {
android: ['componentsAndroid'],
ios: ['componentsIOS'],
},
modules: {
android: ['modulesAndroid', 'modulesCxx'],
ios: ['modulesIOS', 'modulesCxx'],
},
};
function createOutputDirectoryIfNeeded(outputDirectory, libraryName) {
if (!outputDirectory) {
outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName);
}
mkdirp.sync(outputDirectory);
}
/**
* This function read a JSON schema from a path and parses it.
* It throws if the schema don't exists or it can't be parsed.
*
* @parameter schemaPath: the path to the schema
* @return a valid schema
* @throw an Error if the schema doesn't exists in a given path or if it can't be parsed.
*/
function readAndParseSchema(schemaPath) {
const schemaText = fs.readFileSync(schemaPath, 'utf-8');
if (schemaText == null) {
throw new Error(`Can't find schema at ${schemaPath}`);
}
try {
return JSON.parse(schemaText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
}
}
function validateLibraryType(libraryType) {
if (GENERATORS[libraryType] == null) {
throw new Error(`Invalid library type. ${libraryType}`);
}
}
function generateSpecFromInMemorySchema(
platform,
schema,
outputDirectory,
libraryName,
packageName,
libraryType,
useLocalIncludePaths,
) {
validateLibraryType(libraryType);
createOutputDirectoryIfNeeded(outputDirectory, libraryName);
utils.getCodegen().generate(
{
libraryName,
schema,
outputDirectory,
packageName,
useLocalIncludePaths,
},
{
generators: GENERATORS[libraryType][platform],
},
);
if (platform === 'android') {
// Move all components C++ files to a structured jni folder for now.
// Note: this should've been done by RNCodegen's generators, but:
// * the generators don't support platform option yet
// * this subdir structure is Android-only, not applicable to iOS
const files = fs.readdirSync(outputDirectory);
const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`;
mkdirp.sync(jniOutputDirectory);
files
.filter(f => f.endsWith('.h') || f.endsWith('.cpp'))
.forEach(f => {
fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`);
});
}
}
function generateSpec(
platform,
schemaPath,
outputDirectory,
libraryName,
packageName,
libraryType,
) {
generateSpecFromInMemorySchema(
platform,
readAndParseSchema(schemaPath),
outputDirectory,
libraryName,
packageName,
libraryType,
);
}
module.exports = {
execute: generateSpec,
generateSpecFromInMemorySchema: generateSpecFromInMemorySchema,
};

View File

@@ -0,0 +1,18 @@
/*
* 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 <Foundation/Foundation.h>
@interface RCTModulesConformingToProtocolsProvider: NSObject
+(NSArray<NSString *> *)imageURLLoaderClassNames;
+(NSArray<NSString *> *)imageDataDecoderClassNames;
+(NSArray<NSString *> *)URLRequestHandlerClassNames;
@end

View File

@@ -0,0 +1,33 @@
/*
* 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<NSString *> *)imageURLLoaderClassNames
{
return @[
{imageURLLoaderClassNames}
];
}
+(NSArray<NSString *> *)imageDataDecoderClassNames
{
return @[
{imageDataDecoderClassNames}
];
}
+(NSArray<NSString *> *)URLRequestHandlerClassNames
{
return @[
{requestHandlersClassNames}
];
}
@end

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const fs = require('fs');
const {composeSourceMaps} = require('metro-source-map');
const argv = process.argv.slice(2);
let outputPath;
for (let i = 0; i < argv.length; ) {
if (argv[i] === '-o') {
outputPath = argv[i + 1];
argv.splice(i, 2);
continue;
}
++i;
}
if (!argv.length) {
process.stderr.write(
'Usage: node compose-source-maps.js <packager_sourcemap> <compiler_sourcemap> [-o output_file]\n',
);
process.exitCode = -1;
} else {
const [packagerSourcemapPath, compilerSourcemapPath] = argv.splice(0, 2);
const packagerSourcemap = JSON.parse(
fs.readFileSync(packagerSourcemapPath, 'utf8'),
);
const compilerSourcemap = JSON.parse(
fs.readFileSync(compilerSourcemapPath, 'utf8'),
);
if (
packagerSourcemap.x_facebook_offsets != null ||
compilerSourcemap.x_facebook_offsets != null
) {
throw new Error(
'Random Access Bundle (RAM) format is not supported by this tool; ' +
'it cannot process the `x_facebook_offsets` field provided ' +
'in the base and/or target source map(s)',
);
}
if (compilerSourcemap.x_facebook_segments != null) {
throw new Error(
'This tool cannot process the `x_facebook_segments` field provided ' +
'in the target source map.',
);
}
const composedMapJSON = JSON.stringify(
composeSourceMaps([packagerSourcemap, compilerSourcemap]),
);
if (outputPath) {
fs.writeFileSync(outputPath, composedMapJSON, 'utf8');
} else {
process.stdout.write(composedMapJSON);
}
}

View File

@@ -0,0 +1,83 @@
#!/bin/bash
# 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.
set -e
# WHY WE NEED THIS:
# This script is used to find a valid instance of `node` installed in the machine.
# This script is sourced by other scripts to get access to node.
# Specifically, it is used by the `react-native-xcode.sh` script, invoked by a
# post-build phase in Xcode, to build the js files required by React Native.
#
# DEPRECATION NOTE:
# React Native should not make assumptions on your current node environment.
# This file is deprecated and will be removed in a future release in favor of something
# node-agnostic and configurable by the developers.
# remove global prefix if it's already set
# the running shell process will choose a node binary and a global package directory breaks version managers
unset PREFIX
# Support Homebrew on Apple Silicon
HOMEBREW_APPLE_SILICON_BIN=/opt/homebrew/bin
if [[ -d $HOMEBREW_APPLE_SILICON_BIN && ! $PATH =~ $HOMEBREW_APPLE_SILICON_BIN ]]; then
export PATH="$HOMEBREW_APPLE_SILICON_BIN:$PATH"
fi
# Define NVM_DIR and source the nvm.sh setup script
[ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm"
# Source nvm with '--no-use' and then `nvm use` to respect .nvmrc
# See: https://github.com/nvm-sh/nvm/issues/2053
if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
# shellcheck source=/dev/null
. "$HOME/.nvm/nvm.sh" --no-use
nvm use 2> /dev/null || nvm use default
elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then
# shellcheck source=/dev/null
. "$(brew --prefix nvm)/nvm.sh" --no-use
nvm use 2> /dev/null || nvm use default
fi
# Set up the nodenv node version manager if present
if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then
eval "$("$HOME/.nodenv/bin/nodenv" init -)"
elif [[ -x "$(command -v brew)" && -x "$(brew --prefix nodenv)/bin/nodenv" ]]; then
eval "$("$(brew --prefix nodenv)/bin/nodenv" init -)"
fi
# Set up the ndenv of anyenv if preset
if [[ ! -x node && -d ${HOME}/.anyenv/bin ]]; then
export PATH=${HOME}/.anyenv/bin:${PATH}
if [[ "$(anyenv envs | grep -c ndenv )" -eq 1 ]]; then
eval "$(anyenv init -)"
fi
fi
# Set up asdf-vm if present
if [[ -f "$ASDF_DIR/asdf.sh" ]]; then
# shellcheck source=/dev/null
. "$ASDF_DIR/asdf.sh"
elif [[ -f "$HOME/.asdf/asdf.sh" ]]; then
# shellcheck source=/dev/null
. "$HOME/.asdf/asdf.sh"
elif [[ -x "$(command -v brew)" && -f "$(brew --prefix asdf)/asdf.sh" ]]; then
# shellcheck source=/dev/null
. "$(brew --prefix asdf)/asdf.sh"
fi
# Set up volta if present
if [[ -x "$HOME/.volta/bin/node" ]]; then
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"
fi
# Set up the fnm node version manager if present
if [[ -x "$HOME/.fnm/fnm" ]]; then
eval "$("$HOME/.fnm/fnm" env)"
elif [[ -x "$(command -v brew)" && -x "$(brew --prefix fnm)/bin/fnm" ]]; then
eval "$("$(brew --prefix fnm)/bin/fnm" env)"
fi

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const executor = require('./codegen/generate-artifacts-executor.js');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'path',
description: 'Path to the React Native project root.',
})
.option('t', {
alias: 'targetPlatform',
description: 'Target platform. Supported values: "android", "ios", "all".',
})
.option('o', {
alias: 'outputPath',
description: 'Path where generated artifacts will be output to.',
})
.usage('Usage: $0 -p [path to app] -t [target platform] -o [output path]')
.demandOption(['p', 't']).argv;
executor.execute(argv.path, argv.targetPlatform, argv.outputPath);

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const utils = require('./codegen/codegen-utils');
const fs = require('fs');
const mkdirp = require('mkdirp');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
describe: 'Platform to generate native code artifacts for.',
})
.option('s', {
alias: 'schemaListPath',
describe: 'The path to the schema list file.',
})
.option('o', {
alias: 'outputDir',
describe:
'Path to directory where native code source files should be saved.',
})
.usage('Usage: $0 <args>')
.demandOption(
['platform', 'schemaListPath', 'outputDir'],
'Please provide platform, schema path, and output directory.',
).argv;
const GENERATORS = {
android: [],
ios: ['providerIOS'],
};
function generateProvider(platform, schemaListPath, outputDirectory) {
const schemaListText = fs.readFileSync(schemaListPath, 'utf-8');
if (schemaListText == null) {
throw new Error(`Can't find schema list file at ${schemaListPath}`);
}
if (!outputDirectory) {
throw new Error('outputDir is required');
}
mkdirp.sync(outputDirectory);
let schemaPaths;
try {
schemaPaths = JSON.parse(schemaListText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaListPath}`);
}
const schemas = {};
try {
for (const libraryName of Object.keys(schemaPaths)) {
const tmpSchemaText = fs.readFileSync(schemaPaths[libraryName], 'utf-8');
schemas[libraryName] = JSON.parse(tmpSchemaText);
}
} catch (err) {
throw new Error(`Failed to read schema file. ${err.message}`);
}
if (GENERATORS[platform] == null) {
throw new Error(`Invalid platform type. ${platform}`);
}
utils.getCodegen().generateFromSchemas(
{
schemas,
outputDirectory,
},
{
generators: GENERATORS[platform],
},
);
}
function main() {
generateProvider(argv.platform, argv.schemaListPath, argv.outputDir);
}
main();

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const executor = require('./codegen/generate-specs-cli-executor');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
describe: 'Platform to generate native code artifacts for.',
})
.option('s', {
alias: 'schemaPath',
describe: 'The path to the schema file.',
})
.option('o', {
alias: 'outputDir',
describe:
'Path to the root directory where native code source files should be saved.',
})
.option('n', {
alias: 'libraryName',
describe: 'Name of specs library.',
default: 'FBReactNativeSpec',
})
.option('j', {
alias: 'javaPackageName',
describe: 'Name of Java package.',
default: 'com.facebook.fbreact.specs',
})
.option('t', {
alias: 'libraryType',
describe: 'all, components, or modules.',
default: 'all',
})
.usage('Usage: $0 <args>')
.demandOption(
['platform', 'schemaPath', 'outputDir'],
'Please provide platform, schema path, and output directory.',
).argv;
function main() {
executor.execute(
argv.platform,
argv.schemaPath,
argv.outputDir,
argv.libraryName,
argv.javaPackageName,
argv.libraryType,
);
}
main();

View File

@@ -0,0 +1,337 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {execSync, spawnSync} = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
/*::
type BuildType = 'dry-run' | 'release' | 'nightly' | 'prealpha';
*/
const SDKS_DIR = path.normalize(path.join(__dirname, '..', '..', 'sdks'));
const HERMES_DIR = path.join(SDKS_DIR, 'hermes');
const HERMES_TAG_FILE_PATH = path.join(SDKS_DIR, '.hermesversion');
const HERMES_SOURCE_TARBALL_BASE_URL =
'https://github.com/facebook/hermes/tarball/';
const HERMES_TARBALL_DOWNLOAD_DIR = path.join(SDKS_DIR, 'download');
const MACOS_BIN_DIR = path.join(SDKS_DIR, 'hermesc', 'osx-bin');
const MACOS_HERMESC_PATH = path.join(MACOS_BIN_DIR, 'hermesc');
const MACOS_IMPORT_HERMESC_PATH = path.join(
MACOS_BIN_DIR,
'ImportHermesc.cmake',
);
/**
* Delegate execution to the supplied command.
*
* @param command Path to the command.
* @param args Array of arguments pass to the command.
* @param options child process options.
*/
function delegateSync(
command /*: string */,
args /*: (Array<string> | child_process$spawnSyncOpts) */,
options /*: ?child_process$spawnSyncOpts */,
) {
return spawnSync(command, args, {stdio: 'inherit', ...options});
}
function readHermesTag() /*: string */ {
if (fs.existsSync(HERMES_TAG_FILE_PATH)) {
const data = fs
.readFileSync(HERMES_TAG_FILE_PATH, {
encoding: 'utf8',
flag: 'r',
})
.trim();
if (data.length > 0) {
return data;
} else {
throw new Error('[Hermes] .hermesversion file is empty.');
}
}
return 'main';
}
function setHermesTag(hermesTag /*: string */) {
if (readHermesTag() === hermesTag) {
// No need to update.
return;
}
if (!fs.existsSync(SDKS_DIR)) {
fs.mkdirSync(SDKS_DIR, {recursive: true});
}
fs.writeFileSync(HERMES_TAG_FILE_PATH, hermesTag.trim());
console.log('Hermes tag has been updated. Please commit your changes.');
}
function getHermesTagSHA(hermesTag /*: string */) /*: string */ {
return execSync(
`git ls-remote https://github.com/facebook/hermes ${hermesTag} | cut -f 1`,
)
.toString()
.trim();
}
function getHermesTarballDownloadPath(hermesTag /*: string */) /*: string */ {
const hermesTagSHA = getHermesTagSHA(hermesTag);
return path.join(HERMES_TARBALL_DOWNLOAD_DIR, `hermes-${hermesTagSHA}.tgz`);
}
function downloadHermesSourceTarball() {
const hermesTag = readHermesTag();
const hermesTagSHA = getHermesTagSHA(hermesTag);
const hermesTarballDownloadPath = getHermesTarballDownloadPath(hermesTag);
let hermesTarballUrl = HERMES_SOURCE_TARBALL_BASE_URL + hermesTag;
if (fs.existsSync(hermesTarballDownloadPath)) {
return;
}
if (!fs.existsSync(HERMES_TARBALL_DOWNLOAD_DIR)) {
fs.mkdirSync(HERMES_TARBALL_DOWNLOAD_DIR, {recursive: true});
}
console.info(
`[Hermes] Downloading Hermes source code for commit ${hermesTagSHA}`,
);
try {
delegateSync('curl', [hermesTarballUrl, '-Lo', hermesTarballDownloadPath]);
} catch (error) {
throw new Error(`[Hermes] Failed to download Hermes tarball. ${error}`);
}
}
function expandHermesSourceTarball() {
const hermesTag = readHermesTag();
const hermesTagSHA = getHermesTagSHA(hermesTag);
const hermesTarballDownloadPath = getHermesTarballDownloadPath(hermesTag);
if (!fs.existsSync(hermesTarballDownloadPath)) {
throw new Error('[Hermes] Could not locate Hermes tarball.');
}
if (!fs.existsSync(HERMES_DIR)) {
fs.mkdirSync(HERMES_DIR, {recursive: true});
}
console.info(`[Hermes] Expanding Hermes tarball for commit ${hermesTagSHA}`);
try {
delegateSync('tar', [
'-zxf',
hermesTarballDownloadPath,
'--strip-components=1',
'--directory',
HERMES_DIR,
]);
} catch (error) {
throw new Error('[Hermes] Failed to expand Hermes tarball.');
}
}
function copyBuildScripts() {
if (!fs.existsSync(SDKS_DIR)) {
throw new Error(
'[Hermes] Failed to copy Hermes build scripts, no SDKs directory found.',
);
}
if (!fs.existsSync(HERMES_DIR)) {
fs.mkdirSync(path.join(HERMES_DIR, 'utils'), {recursive: true});
}
fs.copyFileSync(
path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-apple-framework.sh'),
path.join(HERMES_DIR, 'utils', 'build-apple-framework.sh'),
);
fs.copyFileSync(
path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-ios-framework.sh'),
path.join(HERMES_DIR, 'utils', 'build-ios-framework.sh'),
);
fs.copyFileSync(
path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-mac-framework.sh'),
path.join(HERMES_DIR, 'utils', 'build-mac-framework.sh'),
);
}
function copyPodSpec() {
if (!fs.existsSync(SDKS_DIR)) {
throw new Error(
'[Hermes] Failed to copy Hermes Podspec, no SDKs directory found.',
);
}
if (!fs.existsSync(HERMES_DIR)) {
fs.mkdirSync(HERMES_DIR, {recursive: true});
}
const podspec = 'hermes-engine.podspec';
fs.copyFileSync(
path.join(SDKS_DIR, 'hermes-engine', podspec),
path.join(HERMES_DIR, podspec),
);
const utils = 'hermes-utils.rb';
fs.copyFileSync(
path.join(SDKS_DIR, 'hermes-engine', utils),
path.join(HERMES_DIR, utils),
);
}
function isTestingAgainstLocalHermesTarball() {
return 'HERMES_ENGINE_TARBALL_PATH' in process.env;
}
function shouldBuildHermesFromSource(isInCI /*: boolean */) /*: boolean */ {
return !isTestingAgainstLocalHermesTarball() && isInCI;
}
function shouldUsePrebuiltHermesC(
platform /*: 'macos' | 'windows' */,
) /*: boolean */ {
if (platform === 'macos') {
return fs.existsSync(MACOS_HERMESC_PATH);
}
return false;
}
function configureMakeForPrebuiltHermesC() {
const IMPORT_HERMESC_TEMPLATE = `add_executable(native-hermesc IMPORTED)
set_target_properties(native-hermesc PROPERTIES
IMPORTED_LOCATION "${MACOS_HERMESC_PATH}"
)`;
try {
fs.mkdirSync(MACOS_BIN_DIR, {recursive: true});
fs.writeFileSync(MACOS_IMPORT_HERMESC_PATH, IMPORT_HERMESC_TEMPLATE);
} catch (error) {
console.warn(
`[Hermes] Re-compiling hermesc. Unable to configure make: ${error}`,
);
}
}
function getHermesPrebuiltArtifactsTarballName(
buildType /*: string */,
) /*: string */ {
if (!buildType) {
throw Error('Did not specify build type.');
}
return `hermes-ios-${buildType.toLowerCase()}.tar.gz`;
}
/**
* Creates a tarball with the contents of the supplied directory.
*/
function createTarballFromDirectory(
directory /*: string */,
filename /*: string */,
) {
const args = ['-C', directory, '-czvf', filename, '.'];
delegateSync('tar', args);
}
function createHermesPrebuiltArtifactsTarball(
hermesDir /*: string */,
buildType /*: string */,
tarballOutputDir /*: string */,
excludeDebugSymbols /*: boolean */,
) /*: string */ {
validateHermesFrameworksExist(path.join(hermesDir, 'destroot'));
if (!fs.existsSync(tarballOutputDir)) {
fs.mkdirSync(tarballOutputDir, {recursive: true});
}
let tarballTempDir;
try {
tarballTempDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'hermes-engine-destroot-'),
);
let args = ['-a'];
if (excludeDebugSymbols) {
args.push('--exclude=dSYMs/');
args.push('--exclude=*.dSYM/');
}
args.push('./destroot');
args.push(tarballTempDir);
delegateSync('rsync', args, {
cwd: hermesDir,
});
if (fs.existsSync(path.join(hermesDir, 'LICENSE'))) {
delegateSync('cp', ['LICENSE', tarballTempDir], {cwd: hermesDir});
}
} catch (error) {
throw new Error(`Failed to copy destroot to tempdir: ${error}`);
}
const tarballFilename = path.join(
tarballOutputDir,
getHermesPrebuiltArtifactsTarballName(buildType),
);
try {
createTarballFromDirectory(tarballTempDir, tarballFilename);
} catch (error) {
throw new Error(`[Hermes] Failed to create tarball: ${error}`);
}
if (!fs.existsSync(tarballFilename)) {
throw new Error(
`Tarball creation failed, could not locate tarball at ${tarballFilename}`,
);
}
return tarballFilename;
}
function validateHermesFrameworksExist(destrootDir /*: string */) {
if (
!fs.existsSync(
path.join(destrootDir, 'Library/Frameworks/macosx/hermes.framework'),
)
) {
throw new Error(
'Error: Hermes macOS Framework not found. Are you sure Hermes has been built?',
);
}
if (
!fs.existsSync(
path.join(destrootDir, 'Library/Frameworks/universal/hermes.xcframework'),
)
) {
throw new Error(
'Error: Hermes iOS XCFramework not found. Are you sure Hermes has been built?',
);
}
}
module.exports = {
configureMakeForPrebuiltHermesC,
copyBuildScripts,
copyPodSpec,
createHermesPrebuiltArtifactsTarball,
createTarballFromDirectory,
downloadHermesSourceTarball,
expandHermesSourceTarball,
getHermesTagSHA,
getHermesTarballDownloadPath,
getHermesPrebuiltArtifactsTarballName,
readHermesTag,
setHermesTag,
shouldBuildHermesFromSource,
shouldUsePrebuiltHermesC,
};

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
/**
* This script prepares Hermes to be built as part of the
* iOS build pipeline on macOS.
*/
const {
configureMakeForPrebuiltHermesC,
copyBuildScripts,
copyPodSpec,
downloadHermesSourceTarball,
expandHermesSourceTarball,
shouldBuildHermesFromSource,
shouldUsePrebuiltHermesC,
} = require('./hermes-utils');
async function main(isInCI) {
if (!shouldBuildHermesFromSource(isInCI)) {
copyPodSpec();
return;
}
downloadHermesSourceTarball();
expandHermesSourceTarball();
copyPodSpec();
copyBuildScripts();
if (shouldUsePrebuiltHermesC('macos')) {
console.log('[Hermes] Using pre-built HermesC');
configureMakeForPrebuiltHermesC();
}
}
const isInCI = process.env.CI === 'true';
main(isInCI).then(() => {
process.exit(0);
});

View File

@@ -0,0 +1,83 @@
#!/bin/bash
# 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.
set -e
PLATFORM_NAME="${PLATFORM_NAME:-iphoneos}"
CURRENT_ARCH="${CURRENT_ARCH}"
if [ -z "$CURRENT_ARCH" ] || [ "$CURRENT_ARCH" == "undefined_arch" ]; then
# Xcode 10 beta sets CURRENT_ARCH to "undefined_arch", this leads to incorrect linker arg.
# it's better to rely on platform name as fallback because architecture differs between simulator and device
if [[ "$PLATFORM_NAME" == *"simulator"* ]]; then
CURRENT_ARCH="x86_64"
else
CURRENT_ARCH="arm64"
fi
fi
# @lint-ignore-every TXT2 Tab Literal
if [ "$CURRENT_ARCH" == "arm64" ]; then
cat <<\EOF >>fix_glog_0.3.5_apple_silicon.patch
diff --git a/config.sub b/config.sub
index 1761d8b..43fa2e8 100755
--- a/config.sub
+++ b/config.sub
@@ -1096,6 +1096,9 @@ case $basic_machine in
basic_machine=z8k-unknown
os=-sim
;;
+ arm64-*)
+ basic_machine=$(echo $basic_machine | sed 's/arm64/aarch64/')
+ ;;
none)
basic_machine=none-none
os=-none
EOF
patch -p1 config.sub fix_glog_0.3.5_apple_silicon.patch
fi
export CC="$(xcrun -find -sdk $PLATFORM_NAME cc) -arch $CURRENT_ARCH -isysroot $(xcrun -sdk $PLATFORM_NAME --show-sdk-path)"
export CXX="$CC"
# Remove automake symlink if it exists
if [ -h "test-driver" ]; then
rm test-driver
fi
# Manually disable gflags include to fix issue https://github.com/facebook/react-native/issues/28446
sed -i.bak -e 's/\@ac_cv_have_libgflags\@/0/' src/glog/logging.h.in && rm src/glog/logging.h.in.bak
sed -i.bak -e 's/HAVE_LIB_GFLAGS/HAVE_LIB_GFLAGS_DISABLED/' src/config.h.in && rm src/config.h.in.bak
./configure --host arm-apple-darwin
cat << EOF >> src/config.h
/* Add in so we have Apple Target Conditionals */
#ifdef __APPLE__
#include <TargetConditionals.h>
#include <Availability.h>
#endif
/* Special configuration for ucontext */
#undef HAVE_UCONTEXT_H
#undef PC_FROM_UCONTEXT
#if defined(__x86_64__)
#define PC_FROM_UCONTEXT uc_mcontext->__ss.__rip
#elif defined(__i386__)
#define PC_FROM_UCONTEXT uc_mcontext->__ss.__eip
#endif
EOF
# Prepare exported header include
EXPORTED_INCLUDE_DIR="exported/glog"
mkdir -p exported/glog
cp -f src/glog/log_severity.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/logging.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/raw_logging.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/stl_logging.h "$EXPORTED_INCLUDE_DIR/"
cp -f src/glog/vlog_is_on.h "$EXPORTED_INCLUDE_DIR/"

View File

@@ -0,0 +1,6 @@
# 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_relative '../../@react-native-community/cli-platform-ios/native_modules'

View File

@@ -0,0 +1,18 @@
#!/bin/bash
# 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.
[ -z "$NODE_BINARY" ] && export NODE_BINARY="node"
nodejs_not_found()
{
echo "error: Can't find the '$NODE_BINARY' binary to build the React Native bundle. " \
"If you have a non-standard Node.js installation, select your project in Xcode, find " \
"'Build Phases' - 'Bundle React Native code and images' and change NODE_BINARY to an " \
"absolute path to your node executable. You can find it by invoking 'which node' in the terminal." >&2
exit 2
}
type "$NODE_BINARY" >/dev/null 2>&1 || nodejs_not_found

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const logPath = process.env.RCT_PACKAGER_LOG_PATH;
if (logPath != null && logPath !== '') {
const JsonReporter = require('metro/src/lib/JsonReporter');
const fs = require('fs');
const path = require('path');
module.exports = class extends JsonReporter {
constructor() {
fs.mkdirSync(path.dirname(logPath), {
recursive: true,
});
super(fs.createWriteStream(logPath));
}
};
} else {
module.exports = require('metro/src/lib/TerminalReporter');
}

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# 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.
# scripts directory
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
REACT_NATIVE_ROOT="$THIS_DIR/.."
# Application root directory - General use case: react-native is a dependency
PROJECT_ROOT=${PROJECT_ROOT:-"$THIS_DIR/../../.."}
# check and assign NODE_BINARY env
# shellcheck disable=SC1090
source "${THIS_DIR}/node-binary.sh"
# Start packager from PROJECT_ROOT
cd "$PROJECT_ROOT" || exit
"$NODE_BINARY" "$REACT_NATIVE_ROOT/cli.js" start --custom-log-reporter-path "$THIS_DIR/packager-reporter.js" "$@"

View File

@@ -0,0 +1,179 @@
#!/bin/bash
# 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.
# Bundle React Native app's code and image assets.
# This script is supposed to be invoked as part of Xcode build process
# and relies on environment variables (including PWD) set by Xcode
# Print commands before executing them (useful for troubleshooting)
set -x -e
DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
# Enables iOS devices to get the IP address of the machine running Metro
if [[ ! "$SKIP_BUNDLING_METRO_IP" && "$CONFIGURATION" = *Debug* && ! "$PLATFORM_NAME" == *simulator ]]; then
for num in 0 1 2 3 4 5 6 7 8; do
IP=$(ipconfig getifaddr en${num} || echo "")
if [ ! -z "$IP" ]; then
break
fi
done
if [ -z "$IP" ]; then
IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | grep -v ' 169.254.' |cut -d\ -f2 | awk 'NR==1{print $1}')
fi
echo "$IP" > "$DEST/ip.txt"
fi
if [[ "$SKIP_BUNDLING" ]]; then
echo "SKIP_BUNDLING enabled; skipping."
exit 0;
fi
case "$CONFIGURATION" in
*Debug*)
if [[ "$PLATFORM_NAME" == *simulator ]]; then
if [[ "$FORCE_BUNDLING" ]]; then
echo "FORCE_BUNDLING enabled; continuing to bundle."
else
echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING flag to change this behavior."
exit 0;
fi
else
echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior."
fi
DEV=true
;;
"")
echo "$0 must be invoked by Xcode"
exit 1
;;
*)
DEV=false
;;
esac
# Path to react-native folder inside node_modules
REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Most projects have their project root, one level up from their Xcode project dir (the "ios" directory)
PROJECT_ROOT=${PROJECT_ROOT:-"$PROJECT_DIR/.."}
cd "$PROJECT_ROOT" || exit
# Define entry file
if [[ "$ENTRY_FILE" ]]; then
# Use ENTRY_FILE defined by user
:
elif [[ -s "index.ios.js" ]]; then
ENTRY_FILE=${1:-index.ios.js}
else
ENTRY_FILE=${1:-index.js}
fi
# check and assign NODE_BINARY env
# shellcheck source=/dev/null
source "$REACT_NATIVE_DIR/scripts/node-binary.sh"
HERMES_ENGINE_PATH="$PODS_ROOT/hermes-engine"
[ -z "$HERMES_CLI_PATH" ] && HERMES_CLI_PATH="$HERMES_ENGINE_PATH/destroot/bin/hermesc"
# If hermesc is not available and USE_HERMES is not set to false, show error.
if [[ $USE_HERMES != false && -f "$HERMES_ENGINE_PATH" && ! -f "$HERMES_CLI_PATH" ]]; then
echo "error: Hermes is enabled but the hermesc binary could not be found at ${HERMES_CLI_PATH}." \
"Perhaps you need to run 'bundle exec pod install' or otherwise " \
"point the HERMES_CLI_PATH variable to your custom location." >&2
exit 2
fi
[ -z "$NODE_ARGS" ] && export NODE_ARGS=""
[ -z "$CLI_PATH" ] && export CLI_PATH="$REACT_NATIVE_DIR/cli.js"
[ -z "$BUNDLE_COMMAND" ] && BUNDLE_COMMAND="bundle"
[ -z "$COMPOSE_SOURCEMAP_PATH" ] && COMPOSE_SOURCEMAP_PATH="$REACT_NATIVE_DIR/scripts/compose-source-maps.js"
if [[ -z "$BUNDLE_CONFIG" ]]; then
CONFIG_ARG=""
else
CONFIG_ARG="--config $BUNDLE_CONFIG"
fi
BUNDLE_FILE="$CONFIGURATION_BUILD_DIR/main.jsbundle"
EXTRA_ARGS=()
case "$PLATFORM_NAME" in
"macosx")
BUNDLE_PLATFORM="macos"
;;
*)
BUNDLE_PLATFORM="ios"
;;
esac
if [ "${IS_MACCATALYST}" = "YES" ]; then
BUNDLE_PLATFORM="ios"
fi
EMIT_SOURCEMAP=
if [[ ! -z "$SOURCEMAP_FILE" ]]; then
EMIT_SOURCEMAP=true
fi
PACKAGER_SOURCEMAP_FILE=
if [[ $EMIT_SOURCEMAP == true ]]; then
if [[ $USE_HERMES != false ]]; then
PACKAGER_SOURCEMAP_FILE="$CONFIGURATION_BUILD_DIR/$(basename "$SOURCEMAP_FILE")"
else
PACKAGER_SOURCEMAP_FILE="$SOURCEMAP_FILE"
fi
EXTRA_ARGS+=("--sourcemap-output" "$PACKAGER_SOURCEMAP_FILE")
fi
# Hermes doesn't require JS minification.
if [[ $USE_HERMES != false && $DEV == false ]]; then
EXTRA_ARGS+=("--minify" "false")
fi
"$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \
$CONFIG_ARG \
--entry-file "$ENTRY_FILE" \
--platform "$BUNDLE_PLATFORM" \
--dev $DEV \
--reset-cache \
--bundle-output "$BUNDLE_FILE" \
--assets-dest "$DEST" \
"${EXTRA_ARGS[@]}" \
$EXTRA_PACKAGER_ARGS
if [[ $USE_HERMES == false ]]; then
cp "$BUNDLE_FILE" "$DEST/"
BUNDLE_FILE="$DEST/main.jsbundle"
else
EXTRA_COMPILER_ARGS=
if [[ $DEV == true ]]; then
EXTRA_COMPILER_ARGS=-Og
else
EXTRA_COMPILER_ARGS=-O
fi
if [[ $EMIT_SOURCEMAP == true ]]; then
EXTRA_COMPILER_ARGS="$EXTRA_COMPILER_ARGS -output-source-map"
fi
"$HERMES_CLI_PATH" -emit-binary -max-diagnostic-width=80 $EXTRA_COMPILER_ARGS -out "$DEST/main.jsbundle" "$BUNDLE_FILE"
if [[ $EMIT_SOURCEMAP == true ]]; then
HBC_SOURCEMAP_FILE="$DEST/main.jsbundle.map"
"$NODE_BINARY" "$COMPOSE_SOURCEMAP_PATH" "$PACKAGER_SOURCEMAP_FILE" "$HBC_SOURCEMAP_FILE" -o "$SOURCEMAP_FILE"
rm "$HBC_SOURCEMAP_FILE"
rm "$PACKAGER_SOURCEMAP_FILE"
fi
BUNDLE_FILE="$DEST/main.jsbundle"
fi
if [[ $DEV != true && ! -f "$BUNDLE_FILE" ]]; then
echo "error: File $BUNDLE_FILE does not exist. Your environment is misconfigured as Metro was not able to produce the bundle so your release application won't work!" >&2
exit 2
fi

View File

@@ -0,0 +1,302 @@
# 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 'open3'
require 'pathname'
require_relative './react_native_pods_utils/script_phases.rb'
require_relative './cocoapods/jsengine.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'
require_relative './cocoapods/runtime.rb'
require_relative './cocoapods/helpers.rb'
$CODEGEN_OUTPUT_DIR = 'build/generated/ios'
$CODEGEN_COMPONENT_DIR = 'react/renderer/components'
$CODEGEN_MODULE_DIR = '.'
$START_TIME = Time.now.to_i
# `@react-native-community/cli-platform-ios/native_modules` defines
# use_native_modules. We use node to resolve its path to allow for
# different packager and workspace setups. This is reliant on
# `@react-native-community/cli-platform-ios` being a direct dependency
# of `react-native`.
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"@react-native-community/cli-platform-ios/native_modules.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
def min_ios_version_supported
return Helpers::Constants.min_ios_version_supported
end
# This function returns the min supported OS versions supported by React Native
# By using this function, you won't have to manually change your Podfile
# when we change the minimum version supported by the framework.
def min_supported_versions
return { :ios => min_ios_version_supported }
end
# This function prepares the project for React Native, before processing
# all the target exposed by the framework.
def prepare_react_native_project!
# Temporary solution to suppress duplicated GUID error.
# Can be removed once we move to generate files outside pod install.
install! 'cocoapods', :deterministic_uuids => false
ReactNativePodsUtils.create_xcode_env_if_missing
end
# Function that setup all the react native dependencies
# 
# Parameters
# - path: path to react_native installation.
# - fabric_enabled: whether fabric should be enabled or not.
# - new_arch_enabled: whether the new architecture should be enabled or not.
# - :production [DEPRECATED] whether the dependencies must be installed to target a Debug or a Release build.
# - hermes_enabled: whether Hermes should be enabled or not.
# - app_path: path to the React Native app. Required by the New Architecture.
# - config_file_dir: directory of the `package.json` file, required by the New Architecture.
def use_react_native! (
path: "../node_modules/react-native",
fabric_enabled: false,
new_arch_enabled: NewArchitectureHelper.new_arch_enabled,
production: false, # deprecated
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
app_path: '..',
config_file_dir: ''
)
# Set the app_path as env variable so the podspecs can access it.
ENV['APP_PATH'] = app_path
ENV['REACT_NATIVE_PATH'] = path
ReactNativePodsUtils.check_minimum_required_xcode()
# Current target definition is provided by Cocoapods and it refers to the target
# that has invoked the `use_react_native!` function.
ReactNativePodsUtils.detect_use_frameworks(current_target_definition)
CodegenUtils.clean_up_build_folder(path, $CODEGEN_OUTPUT_DIR)
# We are relying on this flag also in third parties libraries to proper install dependencies.
# Better to rely and enable this environment flag if the new architecture is turned on using flags.
relative_path_from_current = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
react_native_version = NewArchitectureHelper.extract_react_native_version(File.join(relative_path_from_current, path))
ENV['RCT_NEW_ARCH_ENABLED'] = NewArchitectureHelper.compute_new_arch_enabled(new_arch_enabled, react_native_version)
fabric_enabled = fabric_enabled || NewArchitectureHelper.new_arch_enabled
ENV['RCT_FABRIC_ENABLED'] = fabric_enabled ? "1" : "0"
ENV['USE_HERMES'] = hermes_enabled ? "1" : "0"
prefix = path
ReactNativePodsUtils.warn_if_not_on_arm64()
build_codegen!(prefix, relative_path_from_current)
# The Pods which should be included in all projects
pod 'FBLazyVector', :path => "#{prefix}/Libraries/FBLazyVector"
pod 'RCTRequired', :path => "#{prefix}/Libraries/Required"
pod 'RCTTypeSafety', :path => "#{prefix}/Libraries/TypeSafety", :modular_headers => true
pod 'React', :path => "#{prefix}/"
pod 'React-Core', :path => "#{prefix}/"
pod 'React-CoreModules', :path => "#{prefix}/React/CoreModules"
pod 'React-RCTAppDelegate', :path => "#{prefix}/Libraries/AppDelegate"
pod 'React-RCTActionSheet', :path => "#{prefix}/Libraries/ActionSheetIOS"
pod 'React-RCTAnimation', :path => "#{prefix}/Libraries/NativeAnimation"
pod 'React-RCTBlob', :path => "#{prefix}/Libraries/Blob"
pod 'React-RCTImage', :path => "#{prefix}/Libraries/Image"
pod 'React-RCTLinking', :path => "#{prefix}/Libraries/LinkingIOS"
pod 'React-RCTNetwork', :path => "#{prefix}/Libraries/Network"
pod 'React-RCTSettings', :path => "#{prefix}/Libraries/Settings"
pod 'React-RCTText', :path => "#{prefix}/Libraries/Text"
pod 'React-RCTVibration', :path => "#{prefix}/Libraries/Vibration"
pod 'React-Core/RCTWebSocket', :path => "#{prefix}/"
pod 'React-rncore', :path => "#{prefix}/ReactCommon"
pod 'React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact"
pod 'React-debug', :path => "#{prefix}/ReactCommon/react/debug"
pod 'React-utils', :path => "#{prefix}/ReactCommon/react/utils"
pod 'React-featureflags', :path => "#{prefix}/ReactCommon/react/featureflags"
pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon"
pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler"
pod 'React-nativeconfig', :path => "#{prefix}/ReactCommon"
pod 'RCTDeprecation', :path => "#{prefix}/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
if hermes_enabled
setup_hermes!(:react_native_path => prefix)
else
setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
end
pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor"
pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector-modern"
pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker"
pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor"
pod 'React-runtimescheduler', :path => "#{prefix}/ReactCommon/react/renderer/runtimescheduler"
pod 'React-rendererdebug', :path => "#{prefix}/ReactCommon/react/renderer/debug"
pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"
pod 'React-logger', :path => "#{prefix}/ReactCommon/logger"
pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :modular_headers => true
pod 'React-NativeModulesApple', :path => "#{prefix}/ReactCommon/react/nativemodule/core/platform/ios", :modular_headers => true
pod 'Yoga', :path => "#{prefix}/ReactCommon/yoga", :modular_headers => true
pod 'DoubleConversion', :podspec => "#{prefix}/third-party-podspecs/DoubleConversion.podspec"
pod 'glog', :podspec => "#{prefix}/third-party-podspecs/glog.podspec"
pod 'boost', :podspec => "#{prefix}/third-party-podspecs/boost.podspec"
pod 'fmt', :podspec => "#{prefix}/third-party-podspecs/fmt.podspec"
pod 'RCT-Folly', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec", :modular_headers => true
folly_config = get_folly_config()
run_codegen!(
app_path,
config_file_dir,
:new_arch_enabled => NewArchitectureHelper.new_arch_enabled,
:disable_codegen => ENV['DISABLE_CODEGEN'] == '1',
:react_native_path => prefix,
:fabric_enabled => fabric_enabled,
:hermes_enabled => hermes_enabled,
:codegen_output_dir => $CODEGEN_OUTPUT_DIR,
:package_json_file => File.join(__dir__, "..", "package.json"),
:folly_version => folly_config[:version]
)
pod 'React-Codegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
# Always need fabric to access the RCTSurfacePresenterBridgeAdapter which allow to enable the RuntimeScheduler
# If the New Arch is turned off, we will use the Old Renderer, though.
# RNTester always installed Fabric, this change is required to make the template work.
setup_fabric!(:react_native_path => prefix)
setup_bridgeless!(:react_native_path => prefix, :use_hermes => hermes_enabled)
pods_to_update = LocalPodspecPatch.pods_to_update(:react_native_path => prefix)
if !pods_to_update.empty?
if Pod::Lockfile.public_instance_methods.include?(:detect_changes_with_podfile)
Pod::Lockfile.prepend(LocalPodspecPatch)
else
Pod::UI.warn "Automatically updating #{pods_to_update.join(", ")} has failed, please run `pod update #{pods_to_update.join(" ")} --no-repo-update` manually to fix the issue."
end
end
end
# Getter to retrieve the folly flags in case contributors need to apply them manually.
#
# Returns: the folly compiler flags
def folly_flags()
return NewArchitectureHelper.folly_compiler_flags
end
# Add a dependency to a spec, making sure that the HEADER_SERACH_PATHS are set properly.
# This function automate the requirement to specify the HEADER_SEARCH_PATHS which was error prone
# and hard to pull out properly to begin with.
# Secondly, it prepares the podspec to work also with other platforms, because this function is
# able to generate search paths that are compatible with macOS and other platform if specified by
# the $RN_PLATFORMS variable.
# To generate Header Search Paths for multiple platforms, define in your Podfile or Ruby infra a
# $RN_PLATFORMS static variable with the list of supported platforms, for example:
# `$RN_PLATFORMS = ["iOS", "macOS"]`
#
# Parameters:
# - spec: the spec that needs to be modified
# - pod_name: the name of the dependency we had to add to the spec
# - additional_framework_paths: additional sub paths we had to add to the HEADER_SEARCH_PATH
# - framework_name: the name of the framework in case it is different from the pod_name
# - version: the version of the pod_name the spec needs to depend on
# - base_dir: Base directory from where we need to start looking. Defaults to PODS_CONFIGURATION_BUILD_DIR
def add_dependency(spec, pod_name, subspec: nil, additional_framework_paths: [], framework_name: nil, version: nil, base_dir: "PODS_CONFIGURATION_BUILD_DIR")
fixed_framework_name = framework_name != nil ? framework_name : pod_name.gsub("-", "_") # frameworks can't have "-" in their name
ReactNativePodsUtils.add_dependency(spec, pod_name, base_dir, fixed_framework_name, :additional_paths => additional_framework_paths, :version => version)
end
# This function generates an array of HEADER_SEARCH_PATH that can be added to the HEADER_SEARCH_PATH property when use_frameworks! is enabled
#
# Parameters:
# - pod_name: the name of the dependency we had to add to the spec
# - additional_framework_paths: additional sub paths we had to add to the HEADER_SEARCH_PATH
# - framework_name: the name of the framework in case it is different from the pod_name
# - base_dir: Base directory from where we need to start looking. Defaults to PODS_CONFIGURATION_BUILD_DIR
# - include_base_folder: whether the array must include the base import path or only the additional_framework_paths
def create_header_search_path_for_frameworks(pod_name, additional_framework_paths: [], framework_name: nil, base_dir: "PODS_CONFIGURATION_BUILD_DIR", include_base_folder: true)
fixed_framework_name = framework_name != nil ? framework_name : pod_name.gsub("-", "_")
return ReactNativePodsUtils.create_header_search_path_for_frameworks(base_dir, pod_name, fixed_framework_name, additional_framework_paths, include_base_folder)
end
# This function can be used by library developer to prepare their modules for the New Architecture.
# It passes the Folly Flags to the module, it configures the search path and installs some New Architecture specific dependencies.
#
# Parameters:
# - spec: The spec that has to be configured with the New Architecture code
# - new_arch_enabled: Whether the module should install dependencies for the new architecture
def install_modules_dependencies(spec, new_arch_enabled: NewArchitectureHelper.new_arch_enabled)
folly_config = get_folly_config()
NewArchitectureHelper.install_modules_dependencies(spec, new_arch_enabled, folly_config[:version])
end
# It returns the default flags.
# deprecated.
def get_default_flags()
warn 'get_default_flags is deprecated. Please remove the keys from the `use_react_native!` function'
warn 'if you are using the default already and pass the value you need in case you don\'t want the default'
return ReactNativePodsUtils.get_default_flags()
end
# This method returns an hash with the folly version and the folli compiler flags
# that can be used to configure libraries.
# In this way, we can update those values in react native, and all the libraries will benefit
# from it.
# @return an hash with the `:version` and `:compiler_flags` fields.
def get_folly_config()
return Helpers::Constants.folly_config
end
# Function that executes after React Native has been installed to configure some flags and build settings.
#
# Parameters
# - installer: the Cocoapod object that allows to customize the project.
# - react_native_path: path to React Native.
# - mac_catalyst_enabled: whether we are running the Pod on a Mac Catalyst project or not.
# - enable_hermes_profiler: whether the hermes profiler should be turned on in Release mode
def react_native_post_install(
installer,
react_native_path = "../node_modules/react-native",
mac_catalyst_enabled: false,
ccache_enabled: ENV['USE_CCACHE'] == '1'
)
ReactNativePodsUtils.turn_off_resource_bundle_react_core(installer)
ReactNativePodsUtils.apply_mac_catalyst_patches(installer) if mac_catalyst_enabled
fabric_enabled = ENV['RCT_FABRIC_ENABLED'] == '1'
hermes_enabled = ENV['USE_HERMES'] == '1'
if hermes_enabled
ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer)
end
ReactNativePodsUtils.fix_library_search_paths(installer)
ReactNativePodsUtils.update_search_paths(installer)
ReactNativePodsUtils.set_use_hermes_build_setting(installer, hermes_enabled)
ReactNativePodsUtils.set_node_modules_user_settings(installer, react_native_path)
ReactNativePodsUtils.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled)
ReactNativePodsUtils.apply_xcode_15_patch(installer)
ReactNativePodsUtils.updateOSDeploymentTarget(installer)
ReactNativePodsUtils.set_dynamic_frameworks_flags(installer)
ReactNativePodsUtils.add_ndebug_flag_to_pods_in_release(installer)
ReactNativePodsUtils.add_privacy_manifest_if_needed(installer)
NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer)
NewArchitectureHelper.modify_flags_for_new_architecture(installer, NewArchitectureHelper.new_arch_enabled)
Pod::UI.puts "Pod install took #{Time.now.to_i - $START_TIME} [s] to run".green
end

View File

@@ -0,0 +1,51 @@
# 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.
# Run test manually by running `ruby react-native/scripts/react_native_pods_utils/__tests__/script_phases.test.rb`
require "erb"
def get_script_phases_with_codegen_discovery(options)
export_vars = {
'RCT_SCRIPT_RN_DIR' => "$RCT_SCRIPT_POD_INSTALLATION_ROOT/#{options[:react_native_path]}",
'RCT_SCRIPT_APP_PATH' => "$RCT_SCRIPT_POD_INSTALLATION_ROOT/#{options[:relative_app_root]}",
'RCT_SCRIPT_OUTPUT_DIR' => "$RCT_SCRIPT_POD_INSTALLATION_ROOT",
'RCT_SCRIPT_TYPE' => "withCodegenDiscovery",
}
return get_script_template(options[:react_native_path], export_vars)
end
def get_script_phases_no_codegen_discovery(options)
export_vars = {
'RCT_SCRIPT_RN_DIR' => "${PODS_TARGET_SRCROOT}/#{options[:react_native_path]}",
'RCT_SCRIPT_LIBRARY_NAME' => "#{options[:library_name]}",
'RCT_SCRIPT_OUTPUT_DIR' => "$RCT_SCRIPT_POD_INSTALLATION_ROOT/#{options[:codegen_output_dir]}",
'RCT_SCRIPT_LIBRARY_TYPE' => "#{options[:library_type] ? options[:library_type] : 'all'}",
'RCT_SCRIPT_JS_SRCS_PATTERN' => "#{options[:js_srcs_pattern]}",
'RCT_SCRIPT_JS_SRCS_DIR' => "#{options[:js_srcs_dir]}",
'RCT_SCRIPT_CODEGEN_MODULE_DIR' => "#{options[:codegen_module_dir]}",
'RCT_SCRIPT_CODEGEN_COMPONENT_DIR' => "#{options[:codegen_component_dir]}",
'RCT_SCRIPT_FILE_LIST' => ("#{options[:file_list]}").dump,
}
return get_script_template(options[:react_native_path], export_vars)
end
def get_script_template(react_native_path, export_vars={})
template =<<~EOS
pushd "$PODS_ROOT/../" > /dev/null
RCT_SCRIPT_POD_INSTALLATION_ROOT=$(pwd)
popd >/dev/null
<% export_vars.each do |(varname, value)| %>
export <%= varname -%>=<%= value -%>
<% end %>
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"
EOS
result = ERB.new(template, trim_mode: '->').result(binding)
return result
end

View File

@@ -0,0 +1,135 @@
#!/bin/bash
# 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.
set -o pipefail
set -e
GENERATED_SRCS_DIR="$DERIVED_FILE_DIR/generated/source/codegen"
TEMP_OUTPUT_DIR="$GENERATED_SRCS_DIR/out"
GENERATED_SCHEMA_FILE="$GENERATED_SRCS_DIR/schema.json"
cd "$RCT_SCRIPT_RN_DIR"
CODEGEN_CLI_PATH=""
error () {
echo "$1"
"[Codegen] $1" >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1
exit 1
}
find_node () {
NODE_BINARY="${NODE_BINARY:-$(command -v node || true)}"
if [ -z "$NODE_BINARY" ]; then
error "[Error] Could not find node. It looks like that the .xcode.env or .xcode.env.local " \
"files are misconfigured. Please check that they are exporting a valid NODE_BINARY " \
"variable, pointing to a node executable."
fi
}
find_codegen () {
CODEGEN_CLI_PATH=$("$NODE_BINARY" --print "require('path').dirname(require.resolve('@react-native/codegen/package.json'))")
if ! [ -d "$CODEGEN_CLI_PATH" ]; then
error "error: Could not determine @react-native/codegen location, using node module resolution. Try running 'yarn install' or 'npm install' in your project root."
fi
}
setup_dirs () {
set +e
rm -rf "$GENERATED_SRCS_DIR"
set -e
mkdir -p "$GENERATED_SRCS_DIR" "$TEMP_OUTPUT_DIR"
# Clear output files
true > "${SCRIPT_OUTPUT_FILE_0}"
}
describe () {
message="
>>>>> $1
"
echo "$message" >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1
}
runSpecCodegen () {
"$NODE_BINARY" "scripts/generate-specs-cli.js" --platform ios --schemaPath "$GENERATED_SCHEMA_FILE" --outputDir "$1" --libraryName "$RCT_SCRIPT_LIBRARY_NAME" --libraryType "$2"
}
generateCodegenSchemaFromJavaScript () {
describe "Generating codegen schema from JavaScript"
SRCS_PATTERN="$RCT_SCRIPT_JS_SRCS_PATTERN"
SRCS_DIR="$RCT_SCRIPT_JS_SRCS_DIR"
if [ "$SRCS_PATTERN" ]; then
JS_SRCS=$(find "$PODS_TARGET_SRCROOT/$SRCS_DIR" -type f -name "$SRCS_PATTERN" -print0 | xargs -0)
echo "$RCT_SCRIPT_FILE_LIST" >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1
else
JS_SRCS="$PODS_TARGET_SRCROOT/$SRCS_DIR"
echo "$RCT_SCRIPT_JS_SRCS_DIR" >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1
fi
# shellcheck disable=SC2086
# $JS_SRCS not having double quotations is intentional
"$NODE_BINARY" "$CODEGEN_CLI_PATH/lib/cli/combine/combine-js-to-schema-cli.js" --exclude NativeSampleTurboModule "$GENERATED_SCHEMA_FILE" $JS_SRCS
}
generateCodegenArtifactsFromSchema () {
describe "Generating codegen artifacts from schema"
pushd "$RN_DIR" >/dev/null || exit 1
if [ "$RCT_SCRIPT_LIBRARY_TYPE" = "all" ]; then
runSpecCodegen "$TEMP_OUTPUT_DIR" "modules"
runSpecCodegen "$TEMP_OUTPUT_DIR" "components"
elif [ "$RCT_SCRIPT_LIBRARY_TYPE" = "components" ]; then
runSpecCodegen "$TEMP_OUTPUT_DIR" "$RCT_SCRIPT_LIBRARY_TYPE"
elif [ "$RCT_SCRIPT_LIBRARY_TYPE" = "modules" ]; then
runSpecCodegen "$TEMP_OUTPUT_DIR" "$RCT_SCRIPT_LIBRARY_TYPE"
fi
popd >/dev/null || exit 1
}
generateArtifacts () {
describe "Generating codegen artifacts"
pushd "$RCT_SCRIPT_RN_DIR" >/dev/null || exit 1
"$NODE_BINARY" "scripts/generate-codegen-artifacts.js" --path "$RCT_SCRIPT_APP_PATH" --outputPath "$TEMP_OUTPUT_DIR" --targetPlatform "ios"
popd >/dev/null || exit 1
}
moveOutputs () {
mkdir -p "$RCT_SCRIPT_OUTPUT_DIR"
# Copy all output to output_dir
cp -R -X "$TEMP_OUTPUT_DIR/." "$RCT_SCRIPT_OUTPUT_DIR" || exit 1
echo "$LIBRARY_NAME output has been written to $RCT_SCRIPT_OUTPUT_DIR:" >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1
ls -1 "$RCT_SCRIPT_OUTPUT_DIR" >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1
}
withCodegenDiscovery () {
setup_dirs
find_node
find_codegen
generateArtifacts
moveOutputs
}
noCodegenDiscovery () {
setup_dirs
find_node
find_codegen
generateCodegenSchemaFromJavaScript
generateCodegenArtifactsFromSchema
moveOutputs
}
if [ "$RCT_SCRIPT_TYPE" = "withCodegenDiscovery" ]; then
withCodegenDiscovery "$@"
else
noCodegenDiscovery "$@"
fi
echo 'Done.' >> "${SCRIPT_OUTPUT_FILE_0}" 2>&1

View File

@@ -0,0 +1,14 @@
#!/bin/sh
# 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.
# Get the absolute path of this script
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
REACT_NATIVE_CCACHE_CONFIGPATH=$SCRIPT_DIR/ccache.conf
# Provide our config file if none is already provided
export CCACHE_CONFIGPATH="${CCACHE_CONFIGPATH:-$REACT_NATIVE_CCACHE_CONFIGPATH}"
exec ccache clang++ "$@"

View File

@@ -0,0 +1,14 @@
#!/bin/sh
# 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.
# Get the absolute path of this script
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
REACT_NATIVE_CCACHE_CONFIGPATH=$SCRIPT_DIR/ccache.conf
# Provide our config file if none is already provided
export CCACHE_CONFIGPATH="${CCACHE_CONFIGPATH:-$REACT_NATIVE_CCACHE_CONFIGPATH}"
exec ccache clang "$@"

View File

@@ -0,0 +1,11 @@
# 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.
# See https://ccache.dev/manual/4.3.html#_configuration_options for details and available options
sloppiness = clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
file_clone = true
depend_mode = true
inode_cache = true

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# 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.
# This script is used to source in Xcode the environment settings required to run properly.
# The script first sources the base `.xcode.env` file.
# Then it sources the `.xcode.env.local` file if present, to override some local config
# Finally, it will execute the command passed i input if any.
#
# USAGE:
# ./with-environment.sh command
# Start with a default
NODE_BINARY=$(command -v node || echo "")
export NODE_BINARY
# Override the default with the global environment
ENV_PATH="$PODS_ROOT/../.xcode.env"
if [ -f "$ENV_PATH" ]; then
source "$ENV_PATH"
fi
# Override the global with the local environment
LOCAL_ENV_PATH="${ENV_PATH}.local"
if [ -f "$LOCAL_ENV_PATH" ]; then
source "$LOCAL_ENV_PATH"
fi
# Check whether NODE_BINARY has been properly set, otherwise help the users with a meaningful error.
if [ -n "$NODE_BINARY" ]; then
echo "Node found at: ${NODE_BINARY}"
else
echo '[Warning] You need to configure your node path in the `".xcode.env" file` environment. ' \
'You can set it up quickly by running: ' \
'`echo export NODE_BINARY=$(command -v node) > .xcode.env` ' \
'in the ios folder. This is needed by React Native to work correctly. ' \
'We fallback to the DEPRECATED behavior of finding `node`. This will be REMOVED in a future version. ' \
'You can read more about this here: https://reactnative.dev/docs/environment-setup#optional-configuring-your-environment' >&2
source "${REACT_NATIVE_PATH}/scripts/find-node-for-xcode.sh"
fi
# Execute argument, if present
if [ -n "$1" ]; then
$1
fi