1082815dcSEvan Baconimport fs from 'fs'; 2082815dcSEvan Baconimport path from 'path'; 3082815dcSEvan Bacon 4082815dcSEvan Baconimport { getAppDelegate, getSourceRoot } from './Paths'; 5082815dcSEvan Baconimport { withBuildSourceFile } from './XcodeProjectFile'; 6082815dcSEvan Baconimport { addResourceFileToGroup, getProjectName } from './utils/Xcodeproj'; 7*8a424bebSJames Ideimport { ConfigPlugin, XcodeProject } from '../Plugin.types'; 8*8a424bebSJames Ideimport { withXcodeProject } from '../plugins/ios-plugins'; 9082815dcSEvan Bacon 10082815dcSEvan Baconconst templateBridgingHeader = `// 11082815dcSEvan Bacon// Use this file to import your target's public headers that you would like to expose to Swift. 12082815dcSEvan Bacon// 13082815dcSEvan Bacon`; 14082815dcSEvan Bacon 15082815dcSEvan Bacon/** 16082815dcSEvan Bacon * Ensure a Swift bridging header is created for the project. 17082815dcSEvan Bacon * This helps fix problems related to using modules that are written in Swift (lottie, FBSDK). 18082815dcSEvan Bacon * 19082815dcSEvan Bacon * 1. Ensures the file exists given the project path. 20082815dcSEvan Bacon * 2. Writes the file and links to Xcode as a resource file. 21082815dcSEvan Bacon * 3. Sets the build configuration `SWIFT_OBJC_BRIDGING_HEADER = [PROJECT_NAME]-Bridging-Header.h` 22082815dcSEvan Bacon */ 23082815dcSEvan Baconexport const withSwiftBridgingHeader: ConfigPlugin = (config) => { 24082815dcSEvan Bacon return withXcodeProject(config, (config) => { 25082815dcSEvan Bacon config.modResults = ensureSwiftBridgingHeaderSetup({ 26082815dcSEvan Bacon project: config.modResults, 27082815dcSEvan Bacon projectRoot: config.modRequest.projectRoot, 28082815dcSEvan Bacon }); 29082815dcSEvan Bacon return config; 30082815dcSEvan Bacon }); 31082815dcSEvan Bacon}; 32082815dcSEvan Bacon 33082815dcSEvan Baconexport function ensureSwiftBridgingHeaderSetup({ 34082815dcSEvan Bacon projectRoot, 35082815dcSEvan Bacon project, 36082815dcSEvan Bacon}: { 37082815dcSEvan Bacon projectRoot: string; 38082815dcSEvan Bacon project: XcodeProject; 39082815dcSEvan Bacon}) { 40082815dcSEvan Bacon // Only create a bridging header if using objective-c 41082815dcSEvan Bacon if (shouldCreateSwiftBridgingHeader({ projectRoot, project })) { 42082815dcSEvan Bacon const projectName = getProjectName(projectRoot); 43082815dcSEvan Bacon const bridgingHeader = createBridgingHeaderFileName(projectName); 44082815dcSEvan Bacon // Ensure a bridging header is created in the Xcode project. 45082815dcSEvan Bacon project = createBridgingHeaderFile({ 46082815dcSEvan Bacon project, 47082815dcSEvan Bacon projectName, 48082815dcSEvan Bacon projectRoot, 49082815dcSEvan Bacon bridgingHeader, 50082815dcSEvan Bacon }); 51082815dcSEvan Bacon // Designate the newly created file as the Swift bridging header in the Xcode project. 52082815dcSEvan Bacon project = linkBridgingHeaderFile({ 53082815dcSEvan Bacon project, 54082815dcSEvan Bacon bridgingHeader: path.join(projectName, bridgingHeader), 55082815dcSEvan Bacon }); 56082815dcSEvan Bacon } 57082815dcSEvan Bacon return project; 58082815dcSEvan Bacon} 59082815dcSEvan Bacon 60082815dcSEvan Baconfunction shouldCreateSwiftBridgingHeader({ 61082815dcSEvan Bacon projectRoot, 62082815dcSEvan Bacon project, 63082815dcSEvan Bacon}: { 64082815dcSEvan Bacon projectRoot: string; 65082815dcSEvan Bacon project: XcodeProject; 66082815dcSEvan Bacon}): boolean { 67082815dcSEvan Bacon // Only create a bridging header if the project is using in Objective C (AppDelegate is written in Objc). 68ed3bd27bSEvan Bacon const isObjc = getAppDelegate(projectRoot).language !== 'swift'; 69082815dcSEvan Bacon return isObjc && !getDesignatedSwiftBridgingHeaderFileReference({ project }); 70082815dcSEvan Bacon} 71082815dcSEvan Bacon 72082815dcSEvan Bacon/** 73082815dcSEvan Bacon * @returns String matching the default name used when Xcode automatically creates a bridging header file. 74082815dcSEvan Bacon */ 75082815dcSEvan Baconfunction createBridgingHeaderFileName(projectName: string): string { 76082815dcSEvan Bacon return `${projectName}-Bridging-Header.h`; 77082815dcSEvan Bacon} 78082815dcSEvan Bacon 79082815dcSEvan Baconexport function getDesignatedSwiftBridgingHeaderFileReference({ 80082815dcSEvan Bacon project, 81082815dcSEvan Bacon}: { 82082815dcSEvan Bacon project: XcodeProject; 83082815dcSEvan Bacon}): string | null { 84082815dcSEvan Bacon const configurations = project.pbxXCBuildConfigurationSection(); 85082815dcSEvan Bacon // @ts-ignore 86082815dcSEvan Bacon for (const { buildSettings } of Object.values(configurations || {})) { 87082815dcSEvan Bacon // Guessing that this is the best way to emulate Xcode. 88082815dcSEvan Bacon // Using `project.addToBuildSettings` modifies too many targets. 89082815dcSEvan Bacon if (typeof buildSettings?.PRODUCT_NAME !== 'undefined') { 90082815dcSEvan Bacon if ( 91082815dcSEvan Bacon typeof buildSettings.SWIFT_OBJC_BRIDGING_HEADER === 'string' && 92082815dcSEvan Bacon buildSettings.SWIFT_OBJC_BRIDGING_HEADER 93082815dcSEvan Bacon ) { 94082815dcSEvan Bacon return buildSettings.SWIFT_OBJC_BRIDGING_HEADER; 95082815dcSEvan Bacon } 96082815dcSEvan Bacon } 97082815dcSEvan Bacon } 98082815dcSEvan Bacon return null; 99082815dcSEvan Bacon} 100082815dcSEvan Bacon 101082815dcSEvan Bacon/** 102082815dcSEvan Bacon * 103082815dcSEvan Bacon * @param bridgingHeader The bridging header filename ex: `ExpoAPIs-Bridging-Header.h` 104082815dcSEvan Bacon * @returns 105082815dcSEvan Bacon */ 106082815dcSEvan Baconexport function linkBridgingHeaderFile({ 107082815dcSEvan Bacon project, 108082815dcSEvan Bacon bridgingHeader, 109082815dcSEvan Bacon}: { 110082815dcSEvan Bacon project: XcodeProject; 111082815dcSEvan Bacon bridgingHeader: string; 112082815dcSEvan Bacon}): XcodeProject { 113082815dcSEvan Bacon const configurations = project.pbxXCBuildConfigurationSection(); 114082815dcSEvan Bacon // @ts-ignore 115082815dcSEvan Bacon for (const { buildSettings } of Object.values(configurations || {})) { 116082815dcSEvan Bacon // Guessing that this is the best way to emulate Xcode. 117082815dcSEvan Bacon // Using `project.addToBuildSettings` modifies too many targets. 118082815dcSEvan Bacon if (typeof buildSettings?.PRODUCT_NAME !== 'undefined') { 119082815dcSEvan Bacon buildSettings.SWIFT_OBJC_BRIDGING_HEADER = bridgingHeader; 120082815dcSEvan Bacon } 121082815dcSEvan Bacon } 122082815dcSEvan Bacon 123082815dcSEvan Bacon return project; 124082815dcSEvan Bacon} 125082815dcSEvan Bacon 126082815dcSEvan Baconexport function createBridgingHeaderFile({ 127082815dcSEvan Bacon projectRoot, 128082815dcSEvan Bacon projectName, 129082815dcSEvan Bacon project, 130082815dcSEvan Bacon bridgingHeader, 131082815dcSEvan Bacon}: { 132082815dcSEvan Bacon project: XcodeProject; 133082815dcSEvan Bacon projectName: string; 134082815dcSEvan Bacon projectRoot: string; 135082815dcSEvan Bacon bridgingHeader: string; 136082815dcSEvan Bacon}): XcodeProject { 137082815dcSEvan Bacon const bridgingHeaderProjectPath = path.join(getSourceRoot(projectRoot), bridgingHeader); 138082815dcSEvan Bacon if (!fs.existsSync(bridgingHeaderProjectPath)) { 139082815dcSEvan Bacon // Create the file 140082815dcSEvan Bacon fs.writeFileSync(bridgingHeaderProjectPath, templateBridgingHeader, 'utf8'); 141082815dcSEvan Bacon } 142082815dcSEvan Bacon 143082815dcSEvan Bacon // This is non-standard, Xcode generates the bridging header in `/ios` which is kinda annoying. 144082815dcSEvan Bacon // Instead, this'll generate the default header in the application code folder `/ios/myproject/`. 145082815dcSEvan Bacon const filePath = `${projectName}/${bridgingHeader}`; 146082815dcSEvan Bacon // Ensure the file is linked with Xcode resource files 147082815dcSEvan Bacon if (!project.hasFile(filePath)) { 148082815dcSEvan Bacon project = addResourceFileToGroup({ 149082815dcSEvan Bacon filepath: filePath, 150082815dcSEvan Bacon groupName: projectName, 151082815dcSEvan Bacon project, 152082815dcSEvan Bacon // Not sure why, but this is how Xcode generates it. 153082815dcSEvan Bacon isBuildFile: false, 154082815dcSEvan Bacon verbose: false, 155082815dcSEvan Bacon }); 156082815dcSEvan Bacon } 157082815dcSEvan Bacon return project; 158082815dcSEvan Bacon} 159082815dcSEvan Bacon 160082815dcSEvan Baconexport const withNoopSwiftFile: ConfigPlugin = (config) => { 161082815dcSEvan Bacon return withBuildSourceFile(config, { 162082815dcSEvan Bacon filePath: 'noop-file.swift', 163082815dcSEvan Bacon contents: [ 164082815dcSEvan Bacon '//', 165082815dcSEvan Bacon '// @generated', 166082815dcSEvan Bacon '// A blank Swift file must be created for native modules with Swift files to work correctly.', 167082815dcSEvan Bacon '//', 168082815dcSEvan Bacon '', 169082815dcSEvan Bacon ].join('\n'), 170082815dcSEvan Bacon }); 171082815dcSEvan Bacon}; 172