diff --git a/packages/react-native-codegen/src/generators/RNCodegen.js b/packages/react-native-codegen/src/generators/RNCodegen.js index ea9f7398d94b4e..5b7297f2ac0382 100644 --- a/packages/react-native-codegen/src/generators/RNCodegen.js +++ b/packages/react-native-codegen/src/generators/RNCodegen.js @@ -83,6 +83,7 @@ type LibraryOptions = $ReadOnly<{ type SchemasOptions = $ReadOnly<{ schemas: {[string]: SchemaType}, outputDirectory: string, + supportedApplePlatforms?: {[string]: {[string]: boolean}}, }>; type LibraryGenerators = @@ -289,7 +290,7 @@ module.exports = { return checkOrWriteFiles(generatedFiles, test); }, generateFromSchemas( - {schemas, outputDirectory}: SchemasOptions, + {schemas, outputDirectory, supportedApplePlatforms}: SchemasOptions, {generators, test}: SchemasConfig, ): boolean { Object.keys(schemas).forEach(libraryName => @@ -300,13 +301,15 @@ module.exports = { for (const name of generators) { for (const generator of SCHEMAS_GENERATORS[name]) { - generator(schemas).forEach((contents: string, fileName: string) => { - generatedFiles.push({ - name: fileName, - content: contents, - outputDir: outputDirectory, - }); - }); + generator(schemas, supportedApplePlatforms).forEach( + (contents: string, fileName: string) => { + generatedFiles.push({ + name: fileName, + content: contents, + outputDir: outputDirectory, + }); + }, + ); } } return checkOrWriteFiles(generatedFiles, test); diff --git a/packages/react-native-codegen/src/generators/components/ComponentsProviderUtils.js b/packages/react-native-codegen/src/generators/components/ComponentsProviderUtils.js new file mode 100644 index 00000000000000..bb050eec7c3f6e --- /dev/null +++ b/packages/react-native-codegen/src/generators/components/ComponentsProviderUtils.js @@ -0,0 +1,50 @@ +/** + * Copyright (c) Meta Platforms, Inc. and 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 + */ + +const APPLE_PLATFORMS_MACRO_MAP = { + ios: 'TARGET_OS_IOS', + macos: 'TARGET_OS_OSX', + tvos: 'TARGET_OS_TV', + visionos: 'TARGET_OS_VISION', +}; + +/** + * Adds compiler macros to the file template to exclude unsupported platforms. + */ +function generateSupportedApplePlatformsMacro( + fileTemplate: string, + supportedPlatformsMap: ?{[string]: boolean}, +): string { + if (!supportedPlatformsMap) { + return fileTemplate; + } + + const compilerMacroString = Object.keys(supportedPlatformsMap) + .reduce((acc: string[], platform) => { + if (!supportedPlatformsMap[platform]) { + return [...acc, `!${APPLE_PLATFORMS_MACRO_MAP[platform]}`]; + } + return acc; + }, []) + .join(' && '); + + if (!compilerMacroString) { + return fileTemplate; + } + + return `#if ${compilerMacroString} +${fileTemplate} +#endif +`; +} + +module.exports = { + generateSupportedApplePlatformsMacro, +}; diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js index 6598ba357f99b0..cc42edb5a04b1e 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderH.js @@ -12,6 +12,10 @@ import type {SchemaType} from '../../CodegenSchema'; +const { + generateSupportedApplePlatformsMacro, +} = require('./ComponentsProviderUtils'); + // File path -> contents type FilesOutput = Map; @@ -63,13 +67,18 @@ Class ${className}Cls(void) __attribute__((used)); // `.trim(); module.exports = { - generate(schemas: {[string]: SchemaType}): FilesOutput { + generate( + schemas: {[string]: SchemaType}, + supportedApplePlatforms?: {[string]: {[string]: boolean}}, + ): FilesOutput { const fileName = 'RCTThirdPartyFabricComponentsProvider.h'; const lookupFuncs = Object.keys(schemas) .map(libraryName => { const schema = schemas[libraryName]; - return Object.keys(schema.modules) + const librarySupportedApplePlatforms = + supportedApplePlatforms?.[libraryName]; + const generatedLookup = Object.keys(schema.modules) .map(moduleName => { const module = schema.modules[moduleName]; if (module.type !== 'Component') { @@ -100,6 +109,11 @@ module.exports = { }) .filter(Boolean) .join('\n'); + + return generateSupportedApplePlatformsMacro( + generatedLookup, + librarySupportedApplePlatforms, + ); }) .join('\n'); diff --git a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js index ced1c4e908f39f..ad99c964437eaa 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateThirdPartyFabricComponentsProviderObjCpp.js @@ -12,6 +12,10 @@ import type {SchemaType} from '../../CodegenSchema'; +const { + generateSupportedApplePlatformsMacro, +} = require('./ComponentsProviderUtils'); + // File path -> contents type FilesOutput = Map; @@ -60,13 +64,19 @@ const LookupMapTemplate = ({ {"${className}", ${className}Cls}, // ${libraryName}`; module.exports = { - generate(schemas: {[string]: SchemaType}): FilesOutput { + generate( + schemas: {[string]: SchemaType}, + supportedApplePlatforms?: {[string]: {[string]: boolean}}, + ): FilesOutput { const fileName = 'RCTThirdPartyFabricComponentsProvider.mm'; const lookupMap = Object.keys(schemas) .map(libraryName => { const schema = schemas[libraryName]; - return Object.keys(schema.modules) + const librarySupportedApplePlatforms = + supportedApplePlatforms?.[libraryName]; + + const generatedLookup = Object.keys(schema.modules) .map(moduleName => { const module = schema.modules[moduleName]; if (module.type !== 'Component') { @@ -98,7 +108,13 @@ module.exports = { return componentTemplates.length > 0 ? componentTemplates : null; }) - .filter(Boolean); + .filter(Boolean) + .join('\n'); + + return generateSupportedApplePlatformsMacro( + generatedLookup, + librarySupportedApplePlatforms, + ); }) .join('\n'); 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 index 89d82d1eedb55d..7543550f9946f4 100644 --- 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 @@ -71,7 +71,8 @@ Class RCTThirdPartyFabricComponentsProvider(const char {\\"MultiComponent1NativeComponent\\", MultiComponent1NativeComponentCls}, // TWO_COMPONENTS_SAME_FILE, {\\"MultiComponent2NativeComponent\\", MultiComponent2NativeComponentCls}, // TWO_COMPONENTS_SAME_FILE - {\\"MultiFile1NativeComponent\\", MultiFile1NativeComponentCls}, // TWO_COMPONENTS_DIFFERENT_FILES, + {\\"MultiFile1NativeComponent\\", MultiFile1NativeComponentCls}, // TWO_COMPONENTS_DIFFERENT_FILES + {\\"MultiFile2NativeComponent\\", MultiFile2NativeComponentCls}, // TWO_COMPONENTS_DIFFERENT_FILES {\\"CommandNativeComponent\\", CommandNativeComponentCls}, // COMMANDS diff --git a/packages/react-native/scripts/codegen/__test_fixtures__/test-library-2/test-library-2.podspec b/packages/react-native/scripts/codegen/__test_fixtures__/test-library-2/test-library-2.podspec new file mode 100644 index 00000000000000..0e292a8a55c465 --- /dev/null +++ b/packages/react-native/scripts/codegen/__test_fixtures__/test-library-2/test-library-2.podspec @@ -0,0 +1,12 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +Pod::Spec.new do |s| + s.name = "test-library-2" + s.version = "0.0.0" + s.ios.deployment_target = "9.0" + s.osx.deployment_target = "13.0" + s.tvos.deployment_target = "1.0" +end diff --git a/packages/react-native/scripts/codegen/__test_fixtures__/test-library/test-library.podspec b/packages/react-native/scripts/codegen/__test_fixtures__/test-library/test-library.podspec new file mode 100644 index 00000000000000..e1697ec5efca9a --- /dev/null +++ b/packages/react-native/scripts/codegen/__test_fixtures__/test-library/test-library.podspec @@ -0,0 +1,10 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +Pod::Spec.new do |s| + s.name = "test-library" + s.version = "0.0.0" + s.platforms = { :ios => "9.0", :osx => "13.0", visionos: "1.0" } +end diff --git a/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js b/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js index 79fa376c6ade0b..6c091f18b9868e 100644 --- a/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js +++ b/packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js @@ -87,6 +87,44 @@ describe('extractLibrariesFromJSON', () => { }); }); +describe('extractSupportedApplePlatforms', () => { + it('extracts platforms when podspec specifies object of platforms', () => { + const myDependency = 'test-library'; + const myDependencyPath = path.join( + __dirname, + `../__test_fixtures__/${myDependency}`, + ); + let platforms = underTest._extractSupportedApplePlatforms( + myDependency, + myDependencyPath, + ); + expect(platforms).toEqual({ + ios: true, + macos: true, + tvos: false, + visionos: true, + }); + }); + + it('extracts platforms when podspec specifies platforms separately', () => { + const myDependency = 'test-library-2'; + const myDependencyPath = path.join( + __dirname, + `../__test_fixtures__/${myDependency}`, + ); + let platforms = underTest._extractSupportedApplePlatforms( + myDependency, + myDependencyPath, + ); + expect(platforms).toEqual({ + ios: true, + macos: true, + tvos: true, + visionos: false, + }); + }); +}); + describe('delete empty files and folders', () => { beforeEach(() => { jest.resetModules(); diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor.js b/packages/react-native/scripts/codegen/generate-artifacts-executor.js index 890384539743d9..cf0f594affc7a3 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor.js @@ -20,6 +20,7 @@ const utils = require('./codegen-utils'); const generateSpecsCLIExecutor = require('./generate-specs-cli-executor'); const {execSync} = require('child_process'); const fs = require('fs'); +const glob = require('glob'); const mkdirp = require('mkdirp'); const os = require('os'); const path = require('path'); @@ -144,6 +145,63 @@ function extractLibrariesFromJSON(configFile, dependencyPath) { } } +const APPLE_PLATFORMS = ['ios', 'macos', 'tvos', 'visionos']; + +// Cocoapods specific platform keys +function getCocoaPodsPlatformKey(platformName) { + if (platformName === 'macos') { + return 'osx'; + } + return platformName; +} + +function extractSupportedApplePlatforms(dependency, dependencyPath) { + console.log('[Codegen] Searching for podspec in the project dependencies.'); + const podspecs = glob.sync('*.podspec', {cwd: dependencyPath}); + + if (podspecs.length === 0) { + return; + } + + // Take the first podspec found + const podspec = fs.readFileSync( + path.join(dependencyPath, podspecs[0]), + 'utf8', + ); + + /** + * Podspec can have platforms defined in two ways: + * 1. `spec.platforms = { :ios => "11.0", :tvos => "11.0" }` + * 2. `s.ios.deployment_target = "11.0"` + * `s.tvos.deployment_target = "11.0"` + */ + const supportedPlatforms = podspec + .split('\n') + .filter( + line => line.includes('platform') || line.includes('deployment_target'), + ) + .join(''); + + // Generate a map of supported platforms { [platform]: true/false } + const supportedPlatformsMap = APPLE_PLATFORMS.reduce( + (acc, platform) => ({ + ...acc, + [platform]: supportedPlatforms.includes( + getCocoaPodsPlatformKey(platform), + ), + }), + {}, + ); + + console.log( + `[Codegen] Supported Apple platforms: ${Object.keys(supportedPlatformsMap) + .filter(key => supportedPlatformsMap[key]) + .join(', ')} for ${dependency}`, + ); + + return supportedPlatformsMap; +} + function findExternalLibraries(pkgJson) { const dependencies = { ...pkgJson.dependencies, @@ -276,9 +334,16 @@ function generateSchemaInfo(library, platform) { library.config.jsSrcsDir, ); console.log(`[Codegen] Processing ${library.config.name}`); + + const supportedApplePlatforms = extractSupportedApplePlatforms( + library.config.name, + library.libraryPath, + ); + // Generate one schema for the entire library... return { library: library, + supportedApplePlatforms, schema: utils .getCombineJSToSchema() .combineSchemasInFileList( @@ -356,7 +421,7 @@ function mustGenerateNativeCode(includeLibraryPath, schemaInfo) { ); } -function createComponentProvider(schemas) { +function createComponentProvider(schemas, supportedApplePlatforms) { console.log('[Codegen] Creating component provider.'); const outputDir = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, @@ -368,6 +433,7 @@ function createComponentProvider(schemas) { { schemas: schemas, outputDirectory: outputDir, + supportedApplePlatforms, }, { generators: ['providerIOS'], @@ -487,10 +553,15 @@ function execute(projectRoot, targetPlatform, baseOutputPath) { if ( rootCodegenTargetNeedsThirdPartyComponentProvider(pkgJson, platform) ) { - const schemas = schemaInfos - .filter(dependencyNeedsThirdPartyComponentProvider) - .map(schemaInfo => schemaInfo.schema); - createComponentProvider(schemas); + const filteredSchemas = schemaInfos.filter( + dependencyNeedsThirdPartyComponentProvider, + ); + const schemas = filteredSchemas.map(schemaInfo => schemaInfo.schema); + const supportedApplePlatforms = filteredSchemas.map( + schemaInfo => schemaInfo.supportedApplePlatforms, + ); + + createComponentProvider(schemas, supportedApplePlatforms); } cleanupEmptyFilesAndFolders(outputPath); } @@ -508,4 +579,5 @@ module.exports = { // exported for testing purposes only: _extractLibrariesFromJSON: extractLibrariesFromJSON, _cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders, + _extractSupportedApplePlatforms: extractSupportedApplePlatforms, };