1# Copyright (c) Meta Platforms, Inc. and affiliates. 2# 3# This source code is licensed under the MIT license found in the 4# LICENSE file in the root directory of this source tree. 5 6require 'json' 7require_relative './helpers.rb' 8require_relative './codegen_script_phase_extractor.rb' 9 10class CodegenUtils 11 12 def initialize() 13 end 14 15 @@REACT_CODEGEN_PODSPEC_GENERATED = false 16 17 def self.set_react_codegen_podspec_generated(value) 18 @@REACT_CODEGEN_PODSPEC_GENERATED = value 19 end 20 21 def self.react_codegen_podspec_generated 22 @@REACT_CODEGEN_PODSPEC_GENERATED 23 end 24 25 @@REACT_CODEGEN_DISCOVERY_DONE = false 26 27 def self.set_react_codegen_discovery_done(value) 28 @@REACT_CODEGEN_DISCOVERY_DONE = value 29 end 30 31 def self.react_codegen_discovery_done 32 @@REACT_CODEGEN_DISCOVERY_DONE 33 end 34 35 # It takes some cocoapods specs and writes them into a file 36 # 37 # Parameters 38 # - spec: the cocoapod specs 39 # - codegen_output_dir: the output directory for the codegen 40 def generate_react_codegen_podspec!(spec, codegen_output_dir) 41 # This podspec file should only be create once in the session/pod install. 42 # This happens when multiple targets are calling use_react_native!. 43 if @@REACT_CODEGEN_PODSPEC_GENERATED 44 Pod::UI.puts "[Codegen] Skipping ABI47_0_0React-Codegen podspec generation." 45 return 46 end 47 48 relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) 49 output_dir = "#{relative_installation_root}/#{codegen_output_dir}" 50 Pod::Executable.execute_command("mkdir", ["-p", output_dir]); 51 52 podspec_path = File.join(output_dir, 'ABI47_0_0React-Codegen.podspec.json') 53 Pod::UI.puts "[Codegen] Generating #{podspec_path}" 54 55 File.open(podspec_path, 'w') do |f| 56 f.write(spec.to_json) 57 f.fsync 58 end 59 60 @@REACT_CODEGEN_PODSPEC_GENERATED = true 61 end 62 63 # It generates the podspec object that represents the `ABI47_0_0React-Codegen.podspec` file 64 # 65 # Parameters 66 # - package_json_file: the path to the `package.json`, required to extract the proper React Native version 67 # - fabric_enabled: whether fabric is enabled or not. 68 # - script_phases: whether we want to add some build script phases or not. 69 def get_react_codegen_spec(package_json_file, folly_version: '2021.07.22.00', fabric_enabled: false, script_phases: nil) 70 package = JSON.parse(File.read(package_json_file)) 71 version = package['version'] 72 73 folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 74 boost_compiler_flags = '-Wno-documentation' 75 76 spec = { 77 'name' => "ABI47_0_0React-Codegen", 78 'version' => version, 79 'summary' => 'Temp pod for generated files for React Native', 80 'homepage' => 'https://facebook.com/', 81 'license' => 'Unlicense', 82 'authors' => 'Facebook', 83 'compiler_flags' => "#{folly_compiler_flags} #{boost_compiler_flags} -Wno-nullability-completeness -std=c++17", 84 'source' => { :git => '' }, 85 'header_mappings_dir' => './', 86 'platforms' => { 87 'ios' => '11.0', 88 }, 89 'source_files' => "**/*.{h,mm,cpp}", 90 'pod_target_xcconfig' => { "HEADER_SEARCH_PATHS" => 91 [ 92 "\"$(PODS_ROOT)/boost\"", 93 "\"$(PODS_ROOT)/RCT-Folly\"", 94 "\"${PODS_ROOT}/Headers/Public/ABI47_0_0React-Codegen/react/renderer/components\"", 95 "\"$(PODS_ROOT)/Headers/Private/ABI47_0_0React-Fabric\"", 96 "\"$(PODS_ROOT)/Headers/Private/ABI47_0_0React-RCTFabric\"", 97 ].join(' ') 98 }, 99 'dependencies': { 100 "ABI47_0_0FBReactNativeSpec": [version], 101 "ABI47_0_0React-jsiexecutor": [version], 102 "RCT-Folly": [folly_version], 103 "ABI47_0_0RCTRequired": [version], 104 "ABI47_0_0RCTTypeSafety": [version], 105 "ABI47_0_0React-Core": [version], 106 "ABI47_0_0React-jsi": [version], 107 "ABI47_0_0ReactCommon/turbomodule/core": [version] 108 } 109 } 110 111 if fabric_enabled 112 spec[:'dependencies'].merge!({ 113 "ABI47_0_0React-graphics": [version], 114 "ABI47_0_0React-rncore": [version], 115 }); 116 end 117 118 if script_phases 119 Pod::UI.puts "[Codegen] Adding script_phases to ABI47_0_0React-Codegen." 120 spec[:'script_phases'] = script_phases 121 end 122 123 return spec 124 end 125 126 # It extracts the codegen config from the configuration file 127 # 128 # Parameters 129 # - config_path: a path to the configuration file 130 # - config_ket: the codegen configuration key 131 # 132 # Returns: the list of dependencies as extracted from the package.json 133 def get_codegen_config_from_file(config_path, config_key) 134 empty = {} 135 if !File.exist?(config_path) 136 return empty 137 end 138 139 config = JSON.parse(File.read(config_path)) 140 return config[config_key] ? config[config_key] : empty 141 end 142 143 # It creates a list of JS files that contains the JS specifications that Codegen needs to use to generate the code 144 # 145 # Parameters 146 # - app_codegen_config: an object that contains the configurations 147 # - app_path: path to the app 148 # 149 # Returns: the list of files that needs to be used by Codegen 150 def get_list_of_js_specs(app_codegen_config, app_path) 151 file_list = [] 152 153 if app_codegen_config['libraries'] then 154 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.' 155 app_codegen_config['libraries'].each do |library| 156 library_dir = File.join(app_path, library['jsSrcsDir']) 157 file_list.concat(Finder.find_codegen_file(library_dir)) 158 end 159 elsif app_codegen_config['jsSrcsDir'] then 160 codegen_dir = File.join(app_path, app_codegen_config['jsSrcsDir']) 161 file_list.concat (Finder.find_codegen_file(codegen_dir)) 162 end 163 164 input_files = file_list.map { |filename| "${PODS_ROOT}/../#{Pathname.new(filename).realpath().relative_path_from(Pod::Config.instance.installation_root)}" } 165 166 return input_files 167 end 168 169 # It generates the build script phase for the codegen 170 # 171 # Parameters 172 # - app_path: the path to the app 173 # - fabric_enabled: whether fabric is enabled or not 174 # - config_file_dir: the directory of the config file 175 # - react_native_path: the path to React Native 176 # - config_key: the configuration key to use in the package.json for the Codegen 177 # - codegen_utils: an object which exposes utilities functions for the codegen 178 # - script_phase_extractor: an object that is able to extract the Xcode Script Phases for React Native 179 # 180 # Return: an object containing the script phase 181 def get_react_codegen_script_phases( 182 app_path, 183 fabric_enabled: false, 184 config_file_dir: '', 185 react_native_path: "../node_modules/react-native", 186 config_key: 'codegenConfig', 187 codegen_utils: CodegenUtils.new(), 188 script_phase_extractor: CodegenScriptPhaseExtractor.new() 189 ) 190 if !app_path 191 Pod::UI.warn '[Codegen] error: app_path is requried to use codegen discovery.' 192 abort 193 end 194 195 # We need to convert paths to relative path from installation_root for the script phase for CI. 196 relative_app_root = Pathname.new(app_path).realpath().relative_path_from(Pod::Config.instance.installation_root) 197 198 relative_config_file_dir = '' 199 if config_file_dir != '' 200 relative_config_file_dir = Pathname.new(config_file_dir).relative_path_from(Pod::Config.instance.installation_root) 201 end 202 203 # Generate input files for in-app libaraies which will be used to check if the script needs to be run. 204 # TODO: Ideally, we generate the input_files list from generate-artifacts.js and read the result here. 205 # Or, generate this podspec in generate-artifacts.js as well. 206 app_package_path = File.join(app_path, 'package.json') 207 app_codegen_config = codegen_utils.get_codegen_config_from_file(app_package_path, config_key) 208 input_files = codegen_utils.get_list_of_js_specs(app_codegen_config, app_path) 209 210 # Add a script phase to trigger generate artifact. 211 # Some code is duplicated so that it's easier to delete the old way and switch over to this once it's stabilized. 212 return { 213 'name': 'Generate Specs', 214 'execution_position': :before_compile, 215 'input_files' => input_files, 216 'show_env_vars_in_log': true, 217 'output_files': ["${DERIVED_FILE_DIR}/react-codegen.log"], 218 'script': script_phase_extractor.extract_script_phase( 219 react_native_path: react_native_path, 220 relative_app_root: relative_app_root, 221 relative_config_file_dir: relative_config_file_dir, 222 fabric_enabled: fabric_enabled 223 ), 224 } 225 end 226 227 def use_react_native_codegen_discovery!( 228 codegen_disabled, 229 app_path, 230 react_native_path: "../node_modules/react-native", 231 fabric_enabled: false, 232 config_file_dir: '', 233 codegen_output_dir: 'build/generated/ios', 234 config_key: 'codegenConfig', 235 folly_version: '2021.07.22.00', 236 codegen_utils: CodegenUtils.new() 237 ) 238 return if codegen_disabled 239 240 if CodegenUtils.react_codegen_discovery_done() 241 Pod::UI.puts "[Codegen] Skipping use_react_native_codegen_discovery." 242 return 243 end 244 245 if !app_path 246 Pod::UI.warn '[Codegen] Error: app_path is required for use_react_native_codegen_discovery.' 247 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!`.' 248 abort 249 end 250 251 Pod::UI.warn '[Codegen] warn: using experimental new codegen integration' 252 relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) 253 254 # Generate ABI47_0_0React-Codegen podspec here to add the script phases. 255 script_phases = codegen_utils.get_react_codegen_script_phases( 256 app_path, 257 :fabric_enabled => fabric_enabled, 258 :config_file_dir => config_file_dir, 259 :react_native_path => react_native_path, 260 :config_key => config_key 261 ) 262 react_codegen_spec = codegen_utils.get_react_codegen_spec( 263 File.join(react_native_path, "package.json"), 264 :folly_version => folly_version, 265 :fabric_enabled => fabric_enabled, 266 :script_phases => script_phases 267 ) 268 codegen_utils.generate_react_codegen_podspec!(react_codegen_spec, codegen_output_dir) 269 270 out = Pod::Executable.execute_command( 271 'node', 272 [ 273 "#{relative_installation_root}/#{react_native_path}/scripts/generate-artifacts.js", 274 "-p", "#{app_path}", 275 "-o", Pod::Config.instance.installation_root, 276 "-e", "#{fabric_enabled}", 277 "-c", "#{config_file_dir}", 278 ]) 279 Pod::UI.puts out; 280 281 CodegenUtils.set_react_codegen_discovery_done(true) 282 end 283end 284