diff --git a/.github/workflows/check-archs-consistency.yml b/.github/workflows/check-archs-consistency.yml new file mode 100644 index 00000000..182f920d --- /dev/null +++ b/.github/workflows/check-archs-consistency.yml @@ -0,0 +1,27 @@ +name: Test consistency between Paper & Fabric +on: + pull_request: + branches: + - main + paths: + - src/fabric/** + - android/src/paper/java/com/facebook/react/viewmanagers/** + - android/src/paper/java/com/horcrux/svg/** +jobs: + check: + runs-on: ubuntu-latest + concurrency: + group: check-archs-consistency-${{ github.ref }} + cancel-in-progress: true + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Use Node.js 18 + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'yarn' + - name: Install node dependencies + run: yarn + - name: Check Android Paper & Fabric generated interfaces consistency + run: yarn check-archs-consistency diff --git a/android/spotless.gradle b/android/spotless.gradle index 22c25382..6ac6843f 100644 --- a/android/spotless.gradle +++ b/android/spotless.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.diffplug.spotless' spotless { java { - target 'src/main/java/**/*.java', 'src/paper/java/com/horcrux/svg/**/*.java' + target 'src/main/java/**/*.java' googleJavaFormat() } } \ No newline at end of file diff --git a/android/src/paper/java/com/horcrux/svg/NativeSvgRenderableModuleSpec.java b/android/src/paper/java/com/horcrux/svg/NativeSvgRenderableModuleSpec.java index b7184d3d..05d042e9 100644 --- a/android/src/paper/java/com/horcrux/svg/NativeSvgRenderableModuleSpec.java +++ b/android/src/paper/java/com/horcrux/svg/NativeSvgRenderableModuleSpec.java @@ -1,13 +1,15 @@ + /** - * This code was generated by - * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * - *
Do not edit this file as changes may cause incorrect behavior and will be lost once the code - * is regenerated. + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. * * @generated by codegen project: GenerateModuleJavaSpec.js + * * @nolint */ + package com.horcrux.svg; import com.facebook.proguard.annotations.DoNotStrip; @@ -15,14 +17,14 @@ import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReactModuleWithSpec; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.turbomodule.core.interfaces.TurboModule; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public abstract class NativeSvgRenderableModuleSpec extends ReactContextBaseJavaModule - implements TurboModule { +public abstract class NativeSvgRenderableModuleSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { public static final String NAME = "RNSVGRenderableModule"; public NativeSvgRenderableModuleSpec(ReactApplicationContext reactContext) { diff --git a/android/src/paper/java/com/horcrux/svg/NativeSvgViewModuleSpec.java b/android/src/paper/java/com/horcrux/svg/NativeSvgViewModuleSpec.java index e20a6db2..397ee9b0 100644 --- a/android/src/paper/java/com/horcrux/svg/NativeSvgViewModuleSpec.java +++ b/android/src/paper/java/com/horcrux/svg/NativeSvgViewModuleSpec.java @@ -1,13 +1,15 @@ + /** - * This code was generated by - * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). * - *
Do not edit this file as changes may cause incorrect behavior and will be lost once the code - * is regenerated. + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. * * @generated by codegen project: GenerateModuleJavaSpec.js + * * @nolint */ + package com.horcrux.svg; import com.facebook.proguard.annotations.DoNotStrip; @@ -15,13 +17,13 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReactModuleWithSpec; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.turbomodule.core.interfaces.TurboModule; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public abstract class NativeSvgViewModuleSpec extends ReactContextBaseJavaModule - implements TurboModule { +public abstract class NativeSvgViewModuleSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { public static final String NAME = "RNSVGSvgViewModule"; public NativeSvgViewModuleSpec(ReactApplicationContext reactContext) { @@ -35,6 +37,5 @@ public abstract class NativeSvgViewModuleSpec extends ReactContextBaseJavaModule @ReactMethod @DoNotStrip - public abstract void toDataURL( - @Nullable Double tag, @Nullable ReadableMap options, @Nullable Callback callback); + public abstract void toDataURL(@Nullable Double tag, @Nullable ReadableMap options, @Nullable Callback callback); } diff --git a/package.json b/package.json index accfaf55..bba55fff 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,9 @@ "prepare": "npm run bob && husky install", "release": "npm login && release-it", "test": "npm run lint && npm run tsc", - "tsc": "tsc --noEmit" + "tsc": "tsc --noEmit", + "check-archs-consistency": "node ./scripts/codegen-check-consistency.js", + "sync-archs": "node ./scripts/codegen-sync-archs.js" }, "peerDependencies": { "react": "*", @@ -110,7 +112,8 @@ "{src,Example}/**/*.{js,ts,tsx}": "yarn format-js", "src/**/*.{js,ts,tsx}": "yarn lint", "apple/**/*.{h,m,mm,cpp}": "yarn format-ios", - "android/src/**/*.java": "yarn format-java" + "android/src/**/*.java": "yarn format-java", + "src/fabric/*.ts": "yarn sync-archs" }, "nativePackage": true, "codegenConfig": { diff --git a/scripts/codegen-check-consistency.js b/scripts/codegen-check-consistency.js new file mode 100644 index 00000000..83ddabb4 --- /dev/null +++ b/scripts/codegen-check-consistency.js @@ -0,0 +1,3 @@ +const { checkCodegenIntegrity } = require('./codegen-utils'); + +checkCodegenIntegrity(); diff --git a/scripts/codegen-sync-archs.js b/scripts/codegen-sync-archs.js new file mode 100644 index 00000000..2829a58a --- /dev/null +++ b/scripts/codegen-sync-archs.js @@ -0,0 +1,3 @@ +const { generateCodegenJavaOldArch } = require('./codegen-utils'); + +generateCodegenJavaOldArch(); diff --git a/scripts/codegen-utils.js b/scripts/codegen-utils.js new file mode 100644 index 00000000..1f480a4c --- /dev/null +++ b/scripts/codegen-utils.js @@ -0,0 +1,131 @@ +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 };