1import chalk from 'chalk'; 2import fs from 'fs-extra'; 3import path from 'path'; 4 5import * as Directories from '../Directories'; 6 7const EXPO_DIR = Directories.getExpoRepositoryRootDir(); 8 9function formatJavaType(value) { 10 if (value == null) { 11 return 'String'; 12 } else if (typeof value === 'string') { 13 return 'String'; 14 } else if (typeof value === 'number') { 15 return 'int'; 16 } 17 throw new Error(`Unsupported literal value: ${value}`); 18} 19 20function formatJavaLiteral(value) { 21 if (value == null) { 22 return 'null'; 23 } else if (typeof value === 'string') { 24 value = value.replace(/"/g, '\\"'); 25 return `"${value}"`; 26 } else if (typeof value === 'number') { 27 return value; 28 } 29 throw new Error(`Unsupported literal value: ${value}`); 30} 31 32async function readExistingSourceAsync(filepath) { 33 try { 34 return await fs.readFile(filepath, 'utf8'); 35 } catch { 36 return null; 37 } 38} 39 40export async function generateAndroidBuildConstantsFromMacrosAsync(macros) { 41 // android falls back to published dev home if local dev home 42 // doesn't exist or had an error. 43 const isLocalManifestEmpty = 44 !macros.BUILD_MACHINE_KERNEL_MANIFEST || macros.BUILD_MACHINE_KERNEL_MANIFEST === ''; 45 46 let versionUsed = 'local'; 47 if (process.env.USE_DOGFOODING_PUBLISHED_KERNEL_MANIFEST) { 48 macros.BUILD_MACHINE_KERNEL_MANIFEST = macros.DOGFOODING_PUBLISHED_KERNEL_MANIFEST; 49 versionUsed = 'doogfooding'; 50 } else if (isLocalManifestEmpty) { 51 macros.BUILD_MACHINE_KERNEL_MANIFEST = macros.DEV_PUBLISHED_KERNEL_MANIFEST; 52 versionUsed = 'published dev'; 53 } 54 console.log(`Using ${chalk.yellow(versionUsed)} version of Expo Home.`); 55 56 delete macros['DEV_PUBLISHED_KERNEL_MANIFEST']; 57 delete macros['DOGFOODING_PUBLISHED_KERNEL_MANIFEST']; 58 59 const definitions = Object.entries(macros).map( 60 ([name, value]) => 61 ` public static final ${formatJavaType(value)} ${name} = ${formatJavaLiteral(value)};` 62 ); 63 64 const source = ` 65package host.exp.exponent.generated; 66 67public class ExponentBuildConstants { 68${definitions.join('\n')} 69}`; 70 71 return ( 72 ` 73// Copyright 2016-present 650 Industries. All rights reserved. 74// @generated by \`expotools android-generate-dynamic-macros\` 75 76${source.trim()} 77`.trim() + '\n' 78 ); 79} 80 81async function updateBuildConstants(buildConstantsPath, macros) { 82 console.log( 83 'Generating build config %s ...', 84 chalk.cyan(path.relative(EXPO_DIR, buildConstantsPath)) 85 ); 86 87 const [source, existingSource] = await Promise.all([ 88 generateAndroidBuildConstantsFromMacrosAsync(macros), 89 readExistingSourceAsync(path.resolve(buildConstantsPath)), 90 ]); 91 92 if (source !== existingSource) { 93 await fs.ensureDir(path.dirname(buildConstantsPath)); 94 await fs.writeFile(buildConstantsPath, source, 'utf8'); 95 } 96} 97 98export default class AndroidMacrosGenerator { 99 async generateAsync(options): Promise<void> { 100 const { buildConstantsPath, macros } = options; 101 102 await updateBuildConstants(path.resolve(buildConstantsPath), macros); 103 } 104 105 async cleanupAsync(options): Promise<void> { 106 // Nothing to clean on Android 107 } 108} 109