1import { IosPlist, IosPodsTools } from '@expo/xdl'; 2import chalk from 'chalk'; 3import fs from 'fs-extra'; 4import path from 'path'; 5import plist from 'plist'; 6 7import * as Directories from '../Directories'; 8import * as ProjectVersions from '../ProjectVersions'; 9 10interface PlistObject { 11 [key: string]: any; 12} 13 14const EXPO_DIR = Directories.getExpoRepositoryRootDir(); 15 16async function readPlistAsync(plistPath: string): Promise<PlistObject> { 17 const plistFileContent = await fs.readFile(plistPath, 'utf8'); 18 return plist.parse(plistFileContent); 19} 20 21async function generateBuildConstantsFromMacrosAsync( 22 buildConfigPlistPath, 23 macros, 24 buildConfiguration, 25 infoPlistContents, 26 keys 27): Promise<PlistObject> { 28 const plistPath = path.dirname(buildConfigPlistPath); 29 const plistName = path.basename(buildConfigPlistPath); 30 31 if (!(await fs.pathExists(buildConfigPlistPath))) { 32 await IosPlist.createBlankAsync(plistPath, plistName); 33 } 34 35 console.log( 36 'Generating build config %s ...', 37 chalk.cyan(path.relative(EXPO_DIR, buildConfigPlistPath)) 38 ); 39 40 const result = await IosPlist.modifyAsync(plistPath, plistName, (config) => { 41 if (config.USE_GENERATED_DEFAULTS === false) { 42 // this flag means don't generate anything, let the user override. 43 return config; 44 } 45 46 for (const [name, value] of Object.entries(macros)) { 47 config[name] = value || ''; 48 } 49 50 config.EXPO_RUNTIME_VERSION = infoPlistContents.CFBundleVersion 51 ? infoPlistContents.CFBundleVersion 52 : infoPlistContents.CFBundleShortVersionString; 53 54 if (!config.API_SERVER_ENDPOINT) { 55 config.API_SERVER_ENDPOINT = 'https://exp.host/--/api/v2/'; 56 } 57 if (keys) { 58 const { GOOGLE_MAPS_IOS_API_KEY } = keys; 59 config.DEFAULT_API_KEYS = { GOOGLE_MAPS_IOS_API_KEY }; 60 } 61 return validateBuildConstants(config, buildConfiguration); 62 }); 63 64 return result; 65} 66 67/** 68 * Adds IS_DEV_KERNEL (bool) and DEV_KERNEL_SOURCE (PUBLISHED, LOCAL) 69 * and errors if there's a problem with the chosen environment. 70 */ 71function validateBuildConstants(config, buildConfiguration) { 72 config.USE_GENERATED_DEFAULTS = true; 73 74 let IS_DEV_KERNEL = false; 75 let DEV_KERNEL_SOURCE = ''; 76 if (buildConfiguration === 'Debug') { 77 IS_DEV_KERNEL = true; 78 DEV_KERNEL_SOURCE = config.DEV_KERNEL_SOURCE; 79 if (!DEV_KERNEL_SOURCE) { 80 // default to dev published build if nothing specified 81 DEV_KERNEL_SOURCE = 'PUBLISHED'; 82 } 83 } else { 84 IS_DEV_KERNEL = false; 85 } 86 87 if (IS_DEV_KERNEL) { 88 if (DEV_KERNEL_SOURCE === 'LOCAL' && !config.BUILD_MACHINE_KERNEL_MANIFEST) { 89 throw new Error( 90 `Error generating local kernel manifest.\nMake sure a local kernel is being served, or switch DEV_KERNEL_SOURCE to use PUBLISHED instead.` 91 ); 92 } 93 94 if (DEV_KERNEL_SOURCE === 'PUBLISHED' && !config.DEV_PUBLISHED_KERNEL_MANIFEST) { 95 throw new Error(`Error downloading DEV published kernel manifest.\n`); 96 } 97 } 98 99 config.IS_DEV_KERNEL = IS_DEV_KERNEL; 100 config.DEV_KERNEL_SOURCE = DEV_KERNEL_SOURCE; 101 return config; 102} 103 104async function writeTemplatesAsync(expoKitPath: string, templateFilesPath: string) { 105 if (expoKitPath) { 106 await renderExpoKitPodspecAsync(expoKitPath, templateFilesPath); 107 await renderExpoKitPodfileAsync(expoKitPath, templateFilesPath); 108 } 109} 110 111export async function renderExpoKitPodspecAsync( 112 expoKitPath: string, 113 templateFilesPath: string 114): Promise<void> { 115 const podspecPath = path.join(expoKitPath, 'ios', 'ExpoKit.podspec'); 116 const podspecTemplatePath = path.join(templateFilesPath, 'ios', 'ExpoKit.podspec'); 117 118 console.log( 119 'Rendering %s from template %s ...', 120 chalk.cyan(path.relative(EXPO_DIR, podspecPath)), 121 chalk.cyan(path.relative(EXPO_DIR, podspecTemplatePath)) 122 ); 123 124 await IosPodsTools.renderExpoKitPodspecAsync(podspecTemplatePath, podspecPath, { 125 IOS_EXPONENT_CLIENT_VERSION: await ProjectVersions.getNewestSDKVersionAsync('ios'), 126 }); 127} 128 129async function renderExpoKitPodfileAsync( 130 expoKitPath: string, 131 templateFilesPath: string 132): Promise<void> { 133 const podfilePath = path.join(expoKitPath, 'exponent-view-template', 'ios', 'Podfile'); 134 const podfileTemplatePath = path.join(templateFilesPath, 'ios', 'ExpoKit-Podfile'); 135 136 console.log( 137 'Rendering %s from template %s ...', 138 chalk.cyan(path.relative(EXPO_DIR, podfilePath)), 139 chalk.cyan(path.relative(EXPO_DIR, podfileTemplatePath)) 140 ); 141 142 await IosPodsTools.renderPodfileAsync(podfileTemplatePath, podfilePath, { 143 TARGET_NAME: 'exponent-view-template', 144 EXPOKIT_PATH: '../..', 145 REACT_NATIVE_PATH: '../../react-native-lab/react-native', 146 UNIVERSAL_MODULES_PATH: '../../packages', 147 }); 148} 149 150export default class IosMacrosGenerator { 151 async generateAsync(options): Promise<void> { 152 const { infoPlistPath, buildConstantsPath, macros, templateSubstitutions } = options; 153 154 // Read Info.plist 155 const infoPlist = await readPlistAsync(infoPlistPath); 156 157 // Generate EXBuildConstants.plist 158 await generateBuildConstantsFromMacrosAsync( 159 path.resolve(buildConstantsPath), 160 macros, 161 options.configuration, 162 infoPlist, 163 templateSubstitutions 164 ); 165 166 // // Generate Podfile and ExpoKit podspec using template files. 167 await writeTemplatesAsync(options.expoKitPath, options.templateFilesPath); 168 } 169} 170