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
This commit is contained in:
Oskar Kwaśniewski
2024-09-20 03:33:31 -07:00
committed by Facebook GitHub Bot
parent 9e2e8c1a27
commit 1747f57c67
3 changed files with 91 additions and 56 deletions
@@ -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,
@@ -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
@@ -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;
}