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 if (process.env.USE_DOGFOODING_PUBLISHED_KERNEL_MANIFEST) { 99 if (!config.DOGFOODING_PUBLISHED_KERNEL_MANIFEST) { 100 throw new Error(`Error downloading DOGFOODING published kernel manifest.\n`); 101 } 102 DEV_KERNEL_SOURCE = 'DOGFOODING'; 103 } 104 } 105 106 config.IS_DEV_KERNEL = IS_DEV_KERNEL; 107 config.DEV_KERNEL_SOURCE = DEV_KERNEL_SOURCE; 108 return config; 109} 110 111async function writeTemplatesAsync(expoKitPath: string, templateFilesPath: string) { 112 if (expoKitPath) { 113 await renderExpoKitPodspecAsync(expoKitPath, templateFilesPath); 114 await renderExpoKitPodfileAsync(expoKitPath, templateFilesPath); 115 } 116} 117 118export async function renderExpoKitPodspecAsync( 119 expoKitPath: string, 120 templateFilesPath: string 121): Promise<void> { 122 const podspecPath = path.join(expoKitPath, 'ios', 'ExpoKit.podspec'); 123 const podspecTemplatePath = path.join(templateFilesPath, 'ios', 'ExpoKit.podspec'); 124 125 console.log( 126 'Rendering %s from template %s ...', 127 chalk.cyan(path.relative(EXPO_DIR, podspecPath)), 128 chalk.cyan(path.relative(EXPO_DIR, podspecTemplatePath)) 129 ); 130 131 await IosPodsTools.renderExpoKitPodspecAsync(podspecTemplatePath, podspecPath, { 132 IOS_EXPONENT_CLIENT_VERSION: await ProjectVersions.getNewestSDKVersionAsync('ios'), 133 }); 134} 135 136async function renderExpoKitPodfileAsync( 137 expoKitPath: string, 138 templateFilesPath: string 139): Promise<void> { 140 const podfilePath = path.join(expoKitPath, 'exponent-view-template', 'ios', 'Podfile'); 141 const podfileTemplatePath = path.join(templateFilesPath, 'ios', 'ExpoKit-Podfile'); 142 143 console.log( 144 'Rendering %s from template %s ...', 145 chalk.cyan(path.relative(EXPO_DIR, podfilePath)), 146 chalk.cyan(path.relative(EXPO_DIR, podfileTemplatePath)) 147 ); 148 149 await IosPodsTools.renderPodfileAsync(podfileTemplatePath, podfilePath, { 150 TARGET_NAME: 'exponent-view-template', 151 EXPOKIT_PATH: '../..', 152 REACT_NATIVE_PATH: '../../react-native-lab/react-native', 153 UNIVERSAL_MODULES_PATH: '../../packages', 154 }); 155} 156 157export default class IosMacrosGenerator { 158 async generateAsync(options): Promise<void> { 159 const { infoPlistPath, buildConstantsPath, macros, templateSubstitutions } = options; 160 161 // Read Info.plist 162 const infoPlist = await readPlistAsync(infoPlistPath); 163 164 // Generate EXBuildConstants.plist 165 await generateBuildConstantsFromMacrosAsync( 166 path.resolve(buildConstantsPath), 167 macros, 168 options.configuration, 169 infoPlist, 170 templateSubstitutions 171 ); 172 173 // // Generate Podfile and ExpoKit podspec using template files. 174 await writeTemplatesAsync(options.expoKitPath, options.templateFilesPath); 175 } 176} 177