From d065b06da1a5c136cd9f4140b631f45933bd8ea0 Mon Sep 17 00:00:00 2001 From: Sota Ogo Date: Wed, 10 Nov 2021 10:08:15 -0800 Subject: [PATCH] Generate ThirdpartyFabricComponentsProvider to extend RCTFabricComponentsPlugins Summary: Currently, we don't have a way to extend RCTFabricComponentsPlugins in OSS. This diff adds a codegen to generate ThirdpartyFabricComponentsProviderwhich will be used to extend RCTFabricComponentsPlugins. It works almost exactly the same as RCTFabricComponentsPlugins, and in the future we might want to consider merging them together. Changelog: [internal] Reviewed By: hramos Differential Revision: D32128760 fbshipit-source-id: b4f3a082f94c3053251cc6a0323c488f649deaa9 --- .../src/generators/RNCodegen.js | 68 ++++++++++-- ...rateThirdPartyFabricComponentsProviderH.js | 105 ++++++++++++++++++ ...hirdPartyFabricComponentsProviderObjCpp.js | 99 +++++++++++++++++ ...hirdPartyFabricComponentsProviderH-test.js | 21 ++++ ...artyFabricComponentsProviderObjCpp-test.js | 21 ++++ ...artyFabricComponentsProviderH-test.js.snap | 63 +++++++++++ ...abricComponentsProviderObjCpp-test.js.snap | 86 ++++++++++++++ 7 files changed, 454 insertions(+), 9 deletions(-) create mode 100644 packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js create mode 100644 packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js create mode 100644 packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderH-test.js create mode 100644 packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js create mode 100644 packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap create mode 100644 packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap diff --git a/packages/react-native-codegen/src/generators/RNCodegen.js b/packages/react-native-codegen/src/generators/RNCodegen.js index 1343c598c7d..b033d13e4eb 100644 --- a/packages/react-native-codegen/src/generators/RNCodegen.js +++ b/packages/react-native-codegen/src/generators/RNCodegen.js @@ -34,13 +34,15 @@ const generatePropsJavaDelegate = require('./components/GeneratePropsJavaDelegat const generateTests = require('./components/GenerateTests.js'); const generateShadowNodeCpp = require('./components/GenerateShadowNodeCpp.js'); const generateShadowNodeH = require('./components/GenerateShadowNodeH.js'); +const generateThirdPartyFabricComponentsProviderObjCpp = require('./components/GenerateThirdPartyFabricComponentsProviderObjCpp.js'); +const generateThirdPartyFabricComponentsProviderH = require('./components/GenerateThirdPartyFabricComponentsProviderH.js'); const generateViewConfigJs = require('./components/GenerateViewConfigJs.js'); const path = require('path'); const schemaValidator = require('../SchemaValidator.js'); import type {SchemaType} from '../CodegenSchema'; -type Options = $ReadOnly<{ +type LibraryOptions = $ReadOnly<{ libraryName: string, schema: SchemaType, outputDirectory: string, @@ -48,7 +50,12 @@ type Options = $ReadOnly<{ assumeNonnull: boolean, }>; -type Generators = +type SchemasOptions = $ReadOnly<{ + schemas: {[string]: SchemaType}, + outputDirectory: string, +}>; + +type LibraryGenerators = | 'componentsAndroid' | 'componentsIOS' | 'descriptors' @@ -60,12 +67,19 @@ type Generators = | 'modulesCxx' | 'modulesIOS'; -type Config = $ReadOnly<{ - generators: Array, +type SchemasGenerators = 'providerIOS'; + +type LibraryConfig = $ReadOnly<{ + generators: Array, test?: boolean, }>; -const GENERATORS = { +type SchemasConfig = $ReadOnly<{ + generators: Array, + test?: boolean, +}>; + +const LIBRARY_GENERATORS = { descriptors: [generateComponentDescriptorH.generate], events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate], props: [ @@ -113,6 +127,13 @@ const GENERATORS = { ], }; +const SCHEMAS_GENERATORS = { + providerIOS: [ + generateThirdPartyFabricComponentsProviderObjCpp.generate, + generateThirdPartyFabricComponentsProviderH.generate, + ], +}; + function writeMapToFiles(map: Map, outputDir: string) { let success = true; map.forEach((contents: string, fileName: string) => { @@ -153,14 +174,20 @@ function checkFilesForChanges( module.exports = { generate( - {libraryName, schema, outputDirectory, packageName, assumeNonnull}: Options, - {generators, test}: Config, + { + libraryName, + schema, + outputDirectory, + packageName, + assumeNonnull, + }: LibraryOptions, + {generators, test}: LibraryConfig, ): boolean { schemaValidator.validate(schema); const generatedFiles = []; for (const name of generators) { - for (const generator of GENERATORS[name]) { + for (const generator of LIBRARY_GENERATORS[name]) { generatedFiles.push( ...generator(libraryName, schema, packageName, assumeNonnull), ); @@ -175,7 +202,30 @@ module.exports = { return writeMapToFiles(filesToUpdate, outputDirectory); }, - generateViewConfig({libraryName, schema}: Options): string { + generateFromSchemas( + {schemas, outputDirectory}: SchemasOptions, + {generators, test}: SchemasConfig, + ): boolean { + Object.keys(schemas).forEach(libraryName => + schemaValidator.validate(schemas[libraryName]), + ); + + const generatedFiles = []; + for (const name of generators) { + for (const generator of SCHEMAS_GENERATORS[name]) { + generatedFiles.push(...generator(schemas)); + } + } + + const filesToUpdate = new Map([...generatedFiles]); + + if (test === true) { + return checkFilesForChanges(filesToUpdate, outputDirectory); + } + + return writeMapToFiles(filesToUpdate, outputDirectory); + }, + generateViewConfig({libraryName, schema}: LibraryOptions): string { schemaValidator.validate(schema); const result = generateViewConfigJs diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js new file mode 100644 index 00000000000..2dd0caf3ee8 --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js @@ -0,0 +1,105 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +import type {SchemaType} from '../../CodegenSchema'; + +// File path -> contents +type FilesOutput = Map; + +const template = ` +/* + * ${'C'}opyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderH + */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage" + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +Class RCTThirdPartyFabricComponentsProvider(const char *name); + +::_LOOKUP_FUNCS_:: + +#ifdef __cplusplus +} +#endif + +#pragma GCC diagnostic pop + +`; + +const lookupFuncTemplate = ` +Class ::_CLASSNAME_::Cls(void) __attribute__((used)); // ::_LIBRARY_NAME_:: +`.trim(); + +module.exports = { + generate(schemas: {[string]: SchemaType}): FilesOutput { + const fileName = 'RCTThirdPartyFabricComponentsProvider.h'; + + const lookupFuncs = Object.keys(schemas) + .map(libraryName => { + const schema = schemas[libraryName]; + return Object.keys(schema.modules) + .map(moduleName => { + const module = schema.modules[moduleName]; + if (module.type !== 'Component') { + return; + } + + const {components} = module; + // No components in this module + if (components == null) { + return null; + } + + return Object.keys(components) + .filter(componentName => { + const component = components[componentName]; + return !( + component.excludedPlatforms && + component.excludedPlatforms.includes('iOS') + ); + }) + .map(componentName => { + const component = components[componentName]; + if (component.interfaceOnly === true) { + return; + } + + return lookupFuncTemplate + .replace(/::_LIBRARY_NAME_::/g, libraryName) + .replace(/::_CLASSNAME_::/g, componentName); + }) + .join('\n'); + }) + .filter(Boolean) + .join('\n'); + }) + .join('\n'); + + const replacedTemplate = template.replace( + /::_LOOKUP_FUNCS_::/g, + lookupFuncs, + ); + + return new Map([[fileName, replacedTemplate]]); + }, +}; diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js new file mode 100644 index 00000000000..b2a10acba1f --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js @@ -0,0 +1,99 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +import type {SchemaType} from '../../CodegenSchema'; + +// File path -> contents +type FilesOutput = Map; + +const template = ` +/** + * ${'C'}opyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderCpp + */ + +// OSS-compatibility layer + +#import "RCTThirdPartyFabricComponentsProvider.h" + +#import +#import + +Class RCTThirdPartyFabricComponentsProvider(const char *name) { + static std::unordered_map sFabricComponentsClassMap = { +::_LOOKUP_MAP_:: + }; + + auto p = sFabricComponentsClassMap.find(name); + if (p != sFabricComponentsClassMap.end()) { + auto classFunc = p->second; + return classFunc(); + } + return nil; +} +`; + +const lookupMapTemplate = ` + {"::_CLASSNAME_::", ::_CLASSNAME_::Cls}, // ::_LIBRARY_NAME_::`; + +module.exports = { + generate(schemas: {[string]: SchemaType}): FilesOutput { + const fileName = 'RCTThirdPartyFabricComponentsProvider.mm'; + + const lookupMap = Object.keys(schemas) + .map(libraryName => { + const schema = schemas[libraryName]; + return Object.keys(schema.modules) + .map(moduleName => { + const module = schema.modules[moduleName]; + if (module.type !== 'Component') { + return; + } + + const {components} = module; + // No components in this module + if (components == null) { + return null; + } + + return Object.keys(components) + .filter(componentName => { + const component = components[componentName]; + return !( + component.excludedPlatforms && + component.excludedPlatforms.includes('iOS') + ); + }) + .map(componentName => { + if (components[componentName].interfaceOnly === true) { + return; + } + const replacedTemplate = lookupMapTemplate + .replace(/::_CLASSNAME_::/g, componentName) + .replace(/::_LIBRARY_NAME_::/g, libraryName); + + return replacedTemplate; + }); + }) + .filter(Boolean); + }) + .join('\n'); + + const replacedTemplate = template.replace(/::_LOOKUP_MAP_::/g, lookupMap); + + return new Map([[fileName, replacedTemplate]]); + }, +}; diff --git a/packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderH-test.js b/packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderH-test.js new file mode 100644 index 00000000000..c3f67d8604c --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderH-test.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+react_native + * @flow strict-local + * @format + */ + +'use strict'; + +const fixtures = require('../__test_fixtures__/fixtures.js'); +const generator = require('../GenerateThirdPartyFabricComponentsProviderH.js'); + +describe('GenerateThirdPartyFabricComponentsProviderH', () => { + it(`can generate fixtures`, () => { + expect(generator.generate(fixtures)).toMatchSnapshot(); + }); +}); diff --git a/packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js b/packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js new file mode 100644 index 00000000000..aa70282c0a3 --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/__tests__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+react_native + * @flow strict-local + * @format + */ + +'use strict'; + +const fixtures = require('../__test_fixtures__/fixtures.js'); +const generator = require('../GenerateThirdPartyFabricComponentsProviderObjCpp.js'); + +describe('GenerateThirdPartyFabricComponentsProviderObjCpp', () => { + it(`can generate fixtures`, () => { + expect(generator.generate(fixtures)).toMatchSnapshot(); + }); +}); diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap new file mode 100644 index 00000000000..223fb9f34cb --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderH-test.js.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GenerateThirdPartyFabricComponentsProviderH can generate fixtures 1`] = ` +Map { + "RCTThirdPartyFabricComponentsProvider.h" => " +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by GenerateRCTThirdPartyFabricComponentsProviderH + */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored \\"-Wreturn-type-c-linkage\\" + +#import + +#ifdef __cplusplus +extern \\"C\\" { +#endif + +Class RCTThirdPartyFabricComponentsProvider(const char *name); + +Class NoPropsNoEventsComponentCls(void) __attribute__((used)); // NO_PROPS_NO_EVENTS + +Class BooleanPropNativeComponentCls(void) __attribute__((used)); // BOOLEAN_PROP +Class StringPropComponentCls(void) __attribute__((used)); // STRING_PROP +Class IntegerPropNativeComponentCls(void) __attribute__((used)); // INTEGER_PROPS +Class DoublePropNativeComponentCls(void) __attribute__((used)); // DOUBLE_PROPS +Class FloatPropNativeComponentCls(void) __attribute__((used)); // FLOAT_PROPS +Class ColorPropNativeComponentCls(void) __attribute__((used)); // COLOR_PROP +Class ImagePropNativeComponentCls(void) __attribute__((used)); // IMAGE_PROP +Class PointPropNativeComponentCls(void) __attribute__((used)); // POINT_PROP +Class InsetsPropNativeComponentCls(void) __attribute__((used)); // INSETS_PROP +Class ArrayPropsNativeComponentCls(void) __attribute__((used)); // ARRAY_PROPS +Class ArrayPropsNativeComponentCls(void) __attribute__((used)); // ARRAY_PROPS_WITH_NESTED_OBJECT +Class ObjectPropsCls(void) __attribute__((used)); // OBJECT_PROPS +Class ImageColorPropNativeComponentCls(void) __attribute__((used)); // MULTI_NATIVE_PROP +Class StringEnumPropsNativeComponentCls(void) __attribute__((used)); // STRING_ENUM_PROP +Class Int32EnumPropsNativeComponentCls(void) __attribute__((used)); // INT32_ENUM_PROP +Class EventsNativeComponentCls(void) __attribute__((used)); // EVENT_PROPS + +Class EventsNestedObjectNativeComponentCls(void) __attribute__((used)); // EVENT_NESTED_OBJECT_PROPS +Class MultiComponent1NativeComponentCls(void) __attribute__((used)); // TWO_COMPONENTS_SAME_FILE +Class MultiComponent2NativeComponentCls(void) __attribute__((used)); // TWO_COMPONENTS_SAME_FILE +Class MultiFile1NativeComponentCls(void) __attribute__((used)); // TWO_COMPONENTS_DIFFERENT_FILES +Class MultiFile2NativeComponentCls(void) __attribute__((used)); // TWO_COMPONENTS_DIFFERENT_FILES +Class CommandNativeComponentCls(void) __attribute__((used)); // COMMANDS +Class CommandNativeComponentCls(void) __attribute__((used)); // COMMANDS_AND_PROPS +Class ExcludedAndroidComponentCls(void) __attribute__((used)); // EXCLUDE_ANDROID + + +#ifdef __cplusplus +} +#endif + +#pragma GCC diagnostic pop + +", +} +`; diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap new file mode 100644 index 00000000000..bd0191208ac --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateThirdPartyFabricComponentsProviderObjCpp-test.js.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GenerateThirdPartyFabricComponentsProviderObjCpp can generate fixtures 1`] = ` +Map { + "RCTThirdPartyFabricComponentsProvider.mm" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by GenerateRCTThirdPartyFabricComponentsProviderCpp + */ + +// OSS-compatibility layer + +#import \\"RCTThirdPartyFabricComponentsProvider.h\\" + +#import +#import + +Class RCTThirdPartyFabricComponentsProvider(const char *name) { + static std::unordered_map sFabricComponentsClassMap = { + + {\\"NoPropsNoEventsComponent\\", NoPropsNoEventsComponentCls}, // NO_PROPS_NO_EVENTS + + + {\\"BooleanPropNativeComponent\\", BooleanPropNativeComponentCls}, // BOOLEAN_PROP + + {\\"StringPropComponent\\", StringPropComponentCls}, // STRING_PROP + + {\\"IntegerPropNativeComponent\\", IntegerPropNativeComponentCls}, // INTEGER_PROPS + + {\\"DoublePropNativeComponent\\", DoublePropNativeComponentCls}, // DOUBLE_PROPS + + {\\"FloatPropNativeComponent\\", FloatPropNativeComponentCls}, // FLOAT_PROPS + + {\\"ColorPropNativeComponent\\", ColorPropNativeComponentCls}, // COLOR_PROP + + {\\"ImagePropNativeComponent\\", ImagePropNativeComponentCls}, // IMAGE_PROP + + {\\"PointPropNativeComponent\\", PointPropNativeComponentCls}, // POINT_PROP + + {\\"InsetsPropNativeComponent\\", InsetsPropNativeComponentCls}, // INSETS_PROP + + {\\"ArrayPropsNativeComponent\\", ArrayPropsNativeComponentCls}, // ARRAY_PROPS + + {\\"ArrayPropsNativeComponent\\", ArrayPropsNativeComponentCls}, // ARRAY_PROPS_WITH_NESTED_OBJECT + + {\\"ObjectProps\\", ObjectPropsCls}, // OBJECT_PROPS + + {\\"ImageColorPropNativeComponent\\", ImageColorPropNativeComponentCls}, // MULTI_NATIVE_PROP + + {\\"StringEnumPropsNativeComponent\\", StringEnumPropsNativeComponentCls}, // STRING_ENUM_PROP + + {\\"Int32EnumPropsNativeComponent\\", Int32EnumPropsNativeComponentCls}, // INT32_ENUM_PROP + + {\\"EventsNativeComponent\\", EventsNativeComponentCls}, // EVENT_PROPS + + + {\\"EventsNestedObjectNativeComponent\\", EventsNestedObjectNativeComponentCls}, // EVENT_NESTED_OBJECT_PROPS + + {\\"MultiComponent1NativeComponent\\", MultiComponent1NativeComponentCls}, // TWO_COMPONENTS_SAME_FILE, + {\\"MultiComponent2NativeComponent\\", MultiComponent2NativeComponentCls}, // TWO_COMPONENTS_SAME_FILE + + {\\"MultiFile1NativeComponent\\", MultiFile1NativeComponentCls}, // TWO_COMPONENTS_DIFFERENT_FILES, + {\\"MultiFile2NativeComponent\\", MultiFile2NativeComponentCls}, // TWO_COMPONENTS_DIFFERENT_FILES + + {\\"CommandNativeComponent\\", CommandNativeComponentCls}, // COMMANDS + + {\\"CommandNativeComponent\\", CommandNativeComponentCls}, // COMMANDS_AND_PROPS + + {\\"ExcludedAndroidComponent\\", ExcludedAndroidComponentCls}, // EXCLUDE_ANDROID + + }; + + auto p = sFabricComponentsClassMap.find(name); + if (p != sFabricComponentsClassMap.end()) { + auto classFunc = p->second; + return classFunc(); + } + return nil; +} +", +} +`;