Files
react-native-svg/scripts/codegen-utils.js
Maciej Stosio 567e90521a Feat: add scripts to sync archs (#2357)
Changes moved from:
https://github.com/software-mansion/react-native-screens/pull/2224

## Description

When changing native props on Fabric, codegen generates corresponding
interfaces and delegates. To make sure both implementations are
consistent, we implement those interfaces on Paper too. Currently, after
generating interfaces using codegen, developer needs to copy
corresponding files for paper manually. This task adds Gradle task, that
automates this.

## Changes
Current assumption: 
Two scripts: `check-archs-consistency` and `sync-archs`. The first one
generates codegen interfaces and compares them with what we have for
paper, the second generates and copies for paper to sync the archs.
- sync is run when staged on changes to `src/paper`
- check is run on CI when `src/paper` or
`android/src/paper/java/com/facebook/react/viewmanagers` changes

## Test code and steps to reproduce

Open `src/fabric/LineNativeComponent.ts` or/and
`src/fabric/NativeSvgRenderableModule.ts` and remove any proper form
interface. Now:
- when building paper, interface should be updated
- when committing, interface should be updated
- if committed and pushed, Test consistency between Paper & Fabric
should fail :)
Brining back the prop and repeating up should cause the interface back
and CI green.
Posting changes in other places should cause CI task to run. 

You can also run those commands yourself using `yarn
check-archs-consistency` and `yarn sync-archs`
2024-07-22 14:58:37 +02:00

132 lines
5.0 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const packageJSON = require('../package.json');
const ERROR_PREFIX = 'RNSVG';
const ROOT_DIR = path.resolve(__dirname, '..');
const ANDROID_DIR = path.resolve(ROOT_DIR, 'android');
const GENERATED_DIR = path.resolve(ANDROID_DIR, 'build/generated');
const OLD_ARCH_DIR = path.resolve(ANDROID_DIR, 'src/paper');
const SPECS_DIR = path.resolve(ROOT_DIR, packageJSON.codegenConfig.jsSrcsDir);
const PACKAGE_NAME = packageJSON.codegenConfig.android.javaPackageName;
const RN_DIR = path.resolve(ROOT_DIR, 'node_modules/react-native');
const RN_CODEGEN_DIR = path.resolve(
ROOT_DIR,
'node_modules/@react-native/codegen'
);
const SOURCE_FOLDER = 'java/com/facebook/react/viewmanagers';
const SOURCE_FOLDER_HORCRUX = 'java/com/horcrux/svg';
const SOURCE_FOLDERS = [
{codegenPath: `${GENERATED_DIR}/source/codegen/${SOURCE_FOLDER}`, oldArchPath: `${OLD_ARCH_DIR}/${SOURCE_FOLDER}`},
{codegenPath: `${GENERATED_DIR}/source/codegen/${SOURCE_FOLDER_HORCRUX}`, oldArchPath: `${OLD_ARCH_DIR}/${SOURCE_FOLDER_HORCRUX}`},
]
function exec(command) {
console.log(`[${ERROR_PREFIX}]> ` + command);
execSync(command);
}
function fixOldArchJavaForRN72Compat(dir) {
// see https://github.com/rnmapbox/maps/issues/3193
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const fileExtension = path.extname(file);
if (fileExtension === '.java') {
let fileContent = fs.readFileSync(filePath, 'utf-8');
let newFileContent = fileContent.replace(
/extends ReactContextBaseJavaModule implements TurboModule/g,
'extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule'
);
if (fileContent !== newFileContent) {
// also insert an import line with `import com.facebook.react.bridge.ReactModuleWithSpec;`
newFileContent = newFileContent.replace(
/import com.facebook.react.bridge.ReactMethod;/,
'import com.facebook.react.bridge.ReactMethod;\nimport com.facebook.react.bridge.ReactModuleWithSpec;'
);
console.log(' => fixOldArchJava applied to:', filePath);
fs.writeFileSync(filePath, newFileContent, 'utf-8');
}
} else if (fs.lstatSync(filePath).isDirectory()) {
fixOldArchJavaForRN72Compat(filePath);
}
});
}
async function generateCodegen() {
exec(`rm -rf ${GENERATED_DIR}`);
exec(`mkdir -p ${GENERATED_DIR}/source/codegen/`);
exec(
`node ${RN_CODEGEN_DIR}/lib/cli/combine/combine-js-to-schema-cli.js --platform android ${GENERATED_DIR}/source/codegen/schema.json ${SPECS_DIR}`
);
exec(
`node ${RN_DIR}/scripts/generate-specs-cli.js --platform android --schemaPath ${GENERATED_DIR}/source/codegen/schema.json --outputDir ${GENERATED_DIR}/source/codegen --javaPackageName ${PACKAGE_NAME}`
);
fixOldArchJavaForRN72Compat(`${GENERATED_DIR}/source/codegen/java/`);
}
async function generateCodegenJavaOldArch() {
await generateCodegen();
SOURCE_FOLDERS.forEach(({codegenPath, oldArchPath}) => {
const generatedFiles = fs.readdirSync(codegenPath);
const oldArchFiles = fs.readdirSync(oldArchPath);
const existingFilesSet = new Set(oldArchFiles.map(fileName => fileName));
generatedFiles.forEach(generatedFile => {
if (!existingFilesSet.has(generatedFile)) {
console.warn(
`[${ERROR_PREFIX}] ${generatedFile} not found in paper dir, if it's used on Android you need to copy it manually and implement yourself before using auto-copy feature.`
);
}
});
if (oldArchFiles.length === 0) {
console.warn(
`[${ERROR_PREFIX}] Paper destination with codegen interfaces is empty. This might be okay if you don't have any interfaces/delegates used on Android, otherwise please check if OLD_ARCH_DIR and SOURCE_FOLDERS are set properly.`
);
}
oldArchFiles.forEach(file => {
if (!fs.existsSync(`${codegenPath}/${file}`)) {
console.warn(
`[${ERROR_PREFIX}] ${file} file does not exist in codegen artifacts source destination. Please check if you still need this interface/delagete.`
);
} else {
exec(`cp -rf ${codegenPath}/${file} ${oldArchPath}/${file}`);
}
});
});
}
function compareFileAtTwoPaths(filename, firstPath, secondPath) {
const fileA = fs.readFileSync(`${firstPath}/${filename}`, 'utf-8');
const fileB = fs.readFileSync(`${secondPath}/${filename}`, 'utf-8');
if (fileA !== fileB) {
throw new Error(
`[${ERROR_PREFIX}] File ${filename} is different at ${firstPath} and ${secondPath}. Make sure you commited codegen autogenerated files.`
);
}
}
async function checkCodegenIntegrity() {
await generateCodegen();
SOURCE_FOLDERS.forEach(({codegenPath, oldArchPath}) => {
const oldArchFiles = fs.readdirSync(oldArchPath);
oldArchFiles.forEach(file => {
compareFileAtTwoPaths(file, codegenPath, oldArchPath);
});
});
}
module.exports = { generateCodegenJavaOldArch, checkCodegenIntegrity };