From 1747f57c6708ffa59ea511f9fbbacf37364fde72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Fri, 20 Sep 2024 03:33:31 -0700 Subject: [PATCH] feat(iOS): add colors to codegen output (#46557) Summary: This PR is a quality-of-life improvement, adds colors to codegen output: ![CleanShot 2024-09-18 at 12 18 00@2x](https://github.com/user-attachments/assets/13290a1c-8411-40e4-8abe-8fce4b88b6e7) Makes it a lot easier to grasp what's going on in the actions taken by codegen. ## Changelog: [GENERAL] [ADDED] - Add color formatting to codegen output Pull Request resolved: https://github.com/facebook/react-native/pull/46557 Test Plan: 1. Run codegen 2. Expect colored output Reviewed By: christophpurrer Differential Revision: D63031248 Pulled By: cipolleschi fbshipit-source-id: e86bc72f16582562dc9f1e3b55dc69af4c7838f5 --- .../cocoapods/__tests__/codegen_utils-test.rb | 32 +++++---- .../scripts/cocoapods/codegen_utils.rb | 46 +++++++++---- .../codegen/generate-artifacts-executor.js | 69 +++++++++++-------- 3 files changed, 91 insertions(+), 56 deletions(-) diff --git a/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb b/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb index a9b24eb3749..02137db8145 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb @@ -61,10 +61,10 @@ class CodegenUtilsTests < Test::Unit::TestCase CodegenUtils.set_react_codegen_podspec_generated(true) # Act - CodegenUtils.new().generate_react_codegen_podspec!(spec, codegen_output_dir, file_manager: FileMock) + CodegenUtils.new().generate_react_codegen_podspec!(spec, codegen_output_dir, file_manager: FileMock, logger: Pod::UI) # Assert - assert_equal(Pod::UI.collected_messages, ["[Codegen] Skipping ReactCodegen podspec generation."]) + assert_equal(Pod::UI.collected_messages, ["Skipping ReactCodegen podspec generation."]) assert_equal(Pathname.pwd_invocation_count, 0) assert_equal(Pod::Executable.executed_commands, []) assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 0) @@ -77,13 +77,13 @@ class CodegenUtilsTests < Test::Unit::TestCase codegen_output_dir = "build" # Act - CodegenUtils.new().generate_react_codegen_podspec!(spec, codegen_output_dir, file_manager: FileMock) + CodegenUtils.new().generate_react_codegen_podspec!(spec, codegen_output_dir, file_manager: FileMock, logger: Pod::UI) # Assert assert_equal(Pathname.pwd_invocation_count, 1) assert_equal(Pod::Config.instance.installation_root.relative_path_from_invocation_count, 1) assert_equal(Pod::Executable.executed_commands, [{ "command" => 'mkdir', "arguments" => ["-p", "~/app/ios/build"]}]) - assert_equal(Pod::UI.collected_messages, ["[Codegen] Generating ~/app/ios/build/ReactCodegen.podspec.json"]) + assert_equal(Pod::UI.collected_messages, ["Generating ~/app/ios/build/ReactCodegen.podspec.json"]) assert_equal(FileMock.open_files_with_mode["~/app/ios/build/ReactCodegen.podspec.json"], 'w') assert_equal(FileMock.open_files[0].collected_write, ['{"name":"Test Podspec"}']) assert_equal(FileMock.open_files[0].fsync_invocation_count, 1) @@ -103,12 +103,13 @@ class CodegenUtilsTests < Test::Unit::TestCase 'package.json', :hermes_enabled => true, :script_phases => "echo Test Script Phase", - :file_manager => FileMock + :file_manager => FileMock, + :logger => Pod::UI ) # Assert assert_equal(podspec, get_podspec_fabric_and_script_phases("echo Test Script Phase")) - assert_equal(Pod::UI.collected_messages, ["[Codegen] Adding script_phases to ReactCodegen."]) + assert_equal(Pod::UI.collected_messages, ["Adding script_phases to ReactCodegen."]) end def testGetReactCodegenSpec_whenUseFrameworksAndNewArch_generatesAPodspec @@ -238,10 +239,10 @@ class CodegenUtilsTests < Test::Unit::TestCase # Act assert_raises() { - CodegenUtils.new().get_react_codegen_script_phases(nil, file_manager: FileMock) + CodegenUtils.new().get_react_codegen_script_phases(nil, file_manager: FileMock, logger: Pod::UI) } # Assert - assert_equal(Pod::UI.collected_warns, ["[Codegen] error: app_path is required to use codegen discovery."]) + assert_equal(Pod::UI.collected_warns, ["error: app_path is required to use codegen discovery."]) end def testGetReactCodegenScriptPhases_returnTheScriptObject @@ -308,11 +309,11 @@ class CodegenUtilsTests < Test::Unit::TestCase CodegenUtils.set_react_codegen_discovery_done(true) # Act - CodegenUtils.new().use_react_native_codegen_discovery!(false, nil, file_manager: FileMock) + CodegenUtils.new().use_react_native_codegen_discovery!(false, nil, file_manager: FileMock, logger: Pod::UI) # Assert assert_true(CodegenUtils.react_codegen_discovery_done()) - assert_equal(Pod::UI.collected_messages, ["[Codegen] Skipping use_react_native_codegen_discovery."]) + assert_equal(Pod::UI.collected_messages, ["Skipping use_react_native_codegen_discovery."]) assert_equal(Pod::UI.collected_warns, []) end @@ -321,15 +322,15 @@ class CodegenUtilsTests < Test::Unit::TestCase # Act assert_raises(){ - CodegenUtils.new().use_react_native_codegen_discovery!(false, nil, file_manager: FileMock) + CodegenUtils.new().use_react_native_codegen_discovery!(false, nil, file_manager: FileMock, logger: Pod::UI) } # Assert assert_false(CodegenUtils.react_codegen_discovery_done()) assert_equal(Pod::UI.collected_messages, []) assert_equal(Pod::UI.collected_warns, [ - '[Codegen] Error: app_path is required for use_react_native_codegen_discovery.', - '[Codegen] If you are calling use_react_native_codegen_discovery! in your Podfile, please remove the call and pass `app_path` and/or `config_file_dir` to `use_react_native!`.' + 'Error: app_path is required for use_react_native_codegen_discovery.', + 'If you are calling use_react_native_codegen_discovery! in your Podfile, please remove the call and pass `app_path` and/or `config_file_dir` to `use_react_native!`.' ]) end @@ -349,13 +350,14 @@ class CodegenUtilsTests < Test::Unit::TestCase false, app_path, :codegen_utils => codegen_utils_mock, - :file_manager => FileMock + :file_manager => FileMock, + :logger => Pod::UI ) # Assert assert_true(CodegenUtils.react_codegen_discovery_done()) assert_equal(Pod::UI.collected_warns, [ - '[Codegen] warn: using experimental new codegen integration' + 'warn: using experimental new codegen integration' ]) assert_equal(codegen_utils_mock.get_react_codegen_script_phases_params, [{ :app_path => app_path, diff --git a/packages/react-native/scripts/cocoapods/codegen_utils.rb b/packages/react-native/scripts/cocoapods/codegen_utils.rb index 0ca78de2de9..3cb9bb1e6ef 100644 --- a/packages/react-native/scripts/cocoapods/codegen_utils.rb +++ b/packages/react-native/scripts/cocoapods/codegen_utils.rb @@ -39,11 +39,11 @@ class CodegenUtils # - 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) + def generate_react_codegen_podspec!(spec, codegen_output_dir, file_manager: File, logger: CodegenUtils::UI) # 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 ReactCodegen podspec generation." + logger.puts("Skipping ReactCodegen podspec generation.") return end @@ -52,7 +52,7 @@ class CodegenUtils Pod::Executable.execute_command("mkdir", ["-p", output_dir]); podspec_path = file_manager.join(output_dir, 'ReactCodegen.podspec.json') - Pod::UI.puts "[Codegen] Generating #{podspec_path}" + logger.puts("Generating #{podspec_path}") file_manager.open(podspec_path, 'w') do |f| f.write(spec.to_json) @@ -69,7 +69,7 @@ class CodegenUtils # - 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) + def get_react_codegen_spec(package_json_file, folly_version: get_folly_config()[:version], hermes_enabled: true, script_phases: nil, file_manager: File, logger: CodegenUtils::UI) package = JSON.parse(file_manager.read(package_json_file)) version = package['version'] use_frameworks = ENV['USE_FRAMEWORKS'] != nil @@ -157,7 +157,7 @@ class CodegenUtils end if script_phases - Pod::UI.puts "[Codegen] Adding script_phases to ReactCodegen." + logger.puts("Adding script_phases to ReactCodegen.") spec[:'script_phases'] = script_phases end @@ -231,10 +231,11 @@ class CodegenUtils config_key: 'codegenConfig', codegen_utils: CodegenUtils.new(), script_phase_extractor: CodegenScriptPhaseExtractor.new(), - file_manager: File + file_manager: File, + logger: CodegenUtils::UI ) if !app_path - Pod::UI.warn '[Codegen] error: app_path is required to use codegen discovery.' + logger.warn("error: app_path is required to use codegen discovery.") abort end @@ -281,22 +282,23 @@ class CodegenUtils config_key: 'codegenConfig', folly_version: get_folly_config()[:version], codegen_utils: CodegenUtils.new(), - file_manager: File + file_manager: File, + logger: CodegenUtils::UI ) return if codegen_disabled if CodegenUtils.react_codegen_discovery_done() - Pod::UI.puts "[Codegen] Skipping use_react_native_codegen_discovery." + logger.puts("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!`.' + logger.warn("Error: app_path is required for use_react_native_codegen_discovery.") + logger.warn("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' + logger.warn("warn: using experimental new codegen integration") relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) # Generate ReactCodegen podspec here to add the script phases. @@ -362,4 +364,24 @@ class CodegenUtils abort end end + + class UI + # ANSI escape codes for colors and formatting + CYAN = "\e[36m" + YELLOW = "\e[33m" + BOLD = "\e[1m" + RESET = "\e[0m" + + class << self + def puts(text, info: false) + prefix = "#{CYAN}#{BOLD}[Codegen]#{RESET}" + message = info ? "#{YELLOW}#{text}#{RESET}" : text + Pod::UI.puts "#{prefix} #{message}" + end + + def warn(text) + puts(text, info: true) + end + end + end end diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor.js b/packages/react-native/scripts/codegen/generate-artifacts-executor.js index ed8d1f718b4..4853f450b1c 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor.js @@ -76,6 +76,17 @@ const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join( 'RCTModulesConformingToProtocolsProviderMM.template', ); +const codegenLog = (text, info = false) => { + // ANSI escape codes for colors and formatting + const reset = '\x1b[0m'; + const cyan = '\x1b[36m'; + const yellow = '\x1b[33m'; + const bold = '\x1b[1m'; + + const color = info ? yellow : ''; + console.log(`${cyan}${bold}[Codegen]${reset} ${color}${text}${reset}`); +}; + // HELPERS function pkgJsonIncludesGeneratedCode(pkgJson) { @@ -98,11 +109,11 @@ function printDeprecationWarningIfNeeded(dependency) { if (dependency === REACT_NATIVE) { return; } - console.log(`[Codegen] CodegenConfig Deprecated Setup for ${dependency}. + codegenLog(`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: + codegenLog(`BEFORE: { // ... "codegenConfig": { @@ -146,7 +157,7 @@ function extractLibrariesFromJSON(configFile, dependencyPath) { if (configFile.codegenConfig == null) { return []; } - console.log(`[Codegen] Found ${configFile.name}`); + codegenLog(`Found ${configFile.name}`); if (configFile.codegenConfig.libraries == null) { const config = configFile.codegenConfig; return [ @@ -172,7 +183,7 @@ function getCocoaPodsPlatformKey(platformName) { } function extractSupportedApplePlatforms(dependency, dependencyPath) { - console.log('[Codegen] Searching for podspec in the project dependencies.'); + codegenLog('Searching for podspec in the project dependencies.', true); const podspecs = glob.sync('*.podspec', {cwd: dependencyPath}); if (podspecs.length === 0) { @@ -214,8 +225,8 @@ function extractSupportedApplePlatforms(dependency, dependencyPath) { ); if (supportedPlatformsList.length > 0) { - console.log( - `[Codegen] Supported Apple platforms: ${supportedPlatformsList.join( + codegenLog( + `Supported Apple platforms: ${supportedPlatformsList.join( ', ', )} for ${dependency}`, ); @@ -231,8 +242,9 @@ function findExternalLibraries(pkgJson, projectRoot) { ...pkgJson.peerDependencies, }; // Determine which of these are codegen-enabled libraries - console.log( - '[Codegen] Searching for codegen-enabled libraries in the project dependencies.', + codegenLog( + 'Searching for codegen-enabled libraries in the project dependencies.', + true, ); // Handle third-party libraries return Object.keys(dependencies).flatMap(dependency => { @@ -253,8 +265,9 @@ function findExternalLibraries(pkgJson, projectRoot) { function findLibrariesFromReactNativeConfig(projectRoot) { const rnConfigFileName = 'react-native.config.js'; - console.log( - `\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${rnConfigFileName}`, + codegenLog( + `Searching for codegen-enabled libraries in ${rnConfigFileName}`, + true, ); const rnConfigFilePath = path.resolve(projectRoot, rnConfigFileName); @@ -289,17 +302,18 @@ function findLibrariesFromReactNativeConfig(projectRoot) { } function findProjectRootLibraries(pkgJson, projectRoot) { - console.log('[Codegen] Searching for codegen-enabled libraries in the app.'); + codegenLog('Searching for codegen-enabled libraries in the app.', true); 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.', + codegenLog( + 'The "codegenConfig" field is not defined in package.json. Assuming there is nothing to generate at the app level.', + true, ); return []; } if (typeof pkgJson.codegenConfig !== 'object') { - throw '[Codegen] The "codegenConfig" field must be an Object.'; + throw 'The "codegenConfig" field must be an Object.'; } return extractLibrariesFromJSON(pkgJson, projectRoot); @@ -316,7 +330,7 @@ function buildCodegenIfNeeded() { if (fs.existsSync(libPath) && fs.readdirSync(libPath).length > 0) { return; } - console.log('[Codegen] Building react-native-codegen package.'); + codegenLog('Building react-native-codegen package.', true); execSync('yarn install', { cwd: CODEGEN_REPO_PATH, stdio: 'inherit', @@ -394,7 +408,7 @@ function generateSchemaInfo(library, platform) { library.libraryPath, library.config.jsSrcsDir, ); - console.log(`[Codegen] Processing ${library.config.name}`); + codegenLog(`Processing ${library.config.name}`); const supportedApplePlatforms = extractSupportedApplePlatforms( library.config.name, @@ -436,8 +450,9 @@ function shouldSkipGenerationForRncore(schemaInfo, platform) { function generateCode(outputPath, schemaInfo, includesGeneratedCode, platform) { if (shouldSkipGenerationForRncore(schemaInfo, platform)) { - console.log( + codegenLog( '[Codegen - rncore] Skipping iOS code generation for rncore as it has been generated already.', + true, ); return; } @@ -447,9 +462,7 @@ function generateCode(outputPath, schemaInfo, includesGeneratedCode, platform) { const tmpOutputDir = path.join(tmpDir, 'out'); fs.mkdirSync(tmpOutputDir, {recursive: true}); - console.log( - `[Codegen] Generating Native Code for ${libraryName} - ${platform}`, - ); + codegenLog(`Generating Native Code for ${libraryName} - ${platform}`); const useLocalIncludePaths = includesGeneratedCode; generateSpecsCLIExecutor.generateSpecFromInMemorySchema( platform, @@ -466,7 +479,7 @@ function generateCode(outputPath, schemaInfo, includesGeneratedCode, platform) { reactNativeCoreLibraryOutputPath(libraryName, platform) ?? outputPath; fs.mkdirSync(outputDir, {recursive: true}); fs.cpSync(tmpOutputDir, outputDir, {recursive: true}); - console.log(`[Codegen] Generated artifacts: ${outputDir}`); + codegenLog(`Generated artifacts: ${outputDir}`); } function generateSchemaInfos(libraries) { @@ -506,7 +519,7 @@ function mustGenerateNativeCode(includeLibraryPath, schemaInfo) { } function createComponentProvider(schemas, supportedApplePlatforms) { - console.log('[Codegen] Creating component provider.'); + codegenLog('Creating component provider.', true); const outputDir = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'React', @@ -523,7 +536,7 @@ function createComponentProvider(schemas, supportedApplePlatforms) { generators: ['providerIOS'], }, ); - console.log(`[Codegen] Generated provider in: ${outputDir}`); + codegenLog(`Generated provider in: ${outputDir}`); } function findCodegenEnabledLibraries(pkgJson, projectRoot) { @@ -652,9 +665,7 @@ function generateRNCoreComponentsIOS(projectRoot /*: string */) /*: void*/ { */ function execute(projectRoot, targetPlatform, baseOutputPath) { try { - console.log( - `[Codegen] Analyzing ${path.join(projectRoot, 'package.json')}`, - ); + codegenLog(`Analyzing ${path.join(projectRoot, 'package.json')}`); const supportedPlatforms = ['android', 'ios']; if ( @@ -675,7 +686,7 @@ function execute(projectRoot, targetPlatform, baseOutputPath) { const libraries = findCodegenEnabledLibraries(pkgJson, projectRoot); if (libraries.length === 0) { - console.log('[Codegen] No codegen-enabled libraries found.'); + codegenLog('No codegen-enabled libraries found.', true); return; } @@ -718,11 +729,11 @@ function execute(projectRoot, targetPlatform, baseOutputPath) { cleanupEmptyFilesAndFolders(outputPath); } } catch (err) { - console.error(err); + codegenLog(err); process.exitCode = 1; } - console.log('[Codegen] Done.'); + codegenLog('Done.', true); return; }