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 chunkString(s: string, len: number) { 21 const size = Math.ceil(s.length / len); 22 const r = Array(size); 23 let offset = 0; 24 25 for (let i = 0; i < size; i++) { 26 r[i] = s.substr(offset, len); 27 offset += len; 28 } 29 30 return r; 31} 32 33function formatJavaLiteral(value) { 34 if (value == null) { 35 return 'null'; 36 } else if (typeof value === 'string') { 37 return `"${value.replace(/"/g, '\\"')}"`; 38 } else if (typeof value === 'number') { 39 return value; 40 } 41 throw new Error(`Unsupported literal value: ${value}`); 42} 43 44async function readExistingSourceAsync(filepath) { 45 try { 46 return await fs.readFile(filepath, 'utf8'); 47 } catch { 48 return null; 49 } 50} 51 52export async function generateAndroidBuildConstantsFromMacrosAsync(macros) { 53 // android falls back to published dev home if local dev home 54 // doesn't exist or had an error. 55 const isLocalManifestEmpty = 56 !macros.BUILD_MACHINE_KERNEL_MANIFEST || macros.BUILD_MACHINE_KERNEL_MANIFEST === ''; 57 58 let versionUsed = 'local'; 59 if (isLocalManifestEmpty) { 60 macros.BUILD_MACHINE_KERNEL_MANIFEST = macros.DEV_PUBLISHED_KERNEL_MANIFEST; 61 versionUsed = 'published dev'; 62 } 63 console.log(`Using ${chalk.yellow(versionUsed)} version of Expo Home.`); 64 65 delete macros['DEV_PUBLISHED_KERNEL_MANIFEST']; 66 67 const BUILD_MACHINE_KERNEL_MANIFEST = macros.BUILD_MACHINE_KERNEL_MANIFEST; 68 69 delete macros['BUILD_MACHINE_KERNEL_MANIFEST']; 70 71 const definitions = Object.entries(macros).map( 72 ([name, value]) => 73 ` public static final ${formatJavaType(value)} ${name} = ${formatJavaLiteral(value)};` 74 ); 75 76 const functions = BUILD_MACHINE_KERNEL_MANIFEST 77 ? ` 78 public static String getBuildMachineKernelManifestAndAssetRequestHeaders() { 79 return new StringBuilder()${chunkString(BUILD_MACHINE_KERNEL_MANIFEST, 1000) 80 .map((s) => `\n.append("${s.replace(/"/g, '\\"')}")`) 81 .join('')}.toString(); 82 } 83 ` 84 : null; 85 86 const source = ` 87package host.exp.exponent.generated; 88 89public class ExponentBuildConstants { 90${definitions.join('\n')} 91 92${functions} 93}`; 94 95 return ( 96 ` 97// Copyright 2016-present 650 Industries. All rights reserved. 98// @generated by \`expotools android-generate-dynamic-macros\` 99 100${source.trim()} 101`.trim() + '\n' 102 ); 103} 104 105async function updateBuildConstants(buildConstantsPath, macros) { 106 console.log( 107 'Generating build config %s ...', 108 chalk.cyan(path.relative(EXPO_DIR, buildConstantsPath)) 109 ); 110 111 const [source, existingSource] = await Promise.all([ 112 generateAndroidBuildConstantsFromMacrosAsync(macros), 113 readExistingSourceAsync(path.resolve(buildConstantsPath)), 114 ]); 115 116 if (source !== existingSource) { 117 await fs.ensureDir(path.dirname(buildConstantsPath)); 118 await fs.writeFile(buildConstantsPath, source, 'utf8'); 119 } 120} 121 122export default class AndroidMacrosGenerator { 123 async generateAsync(options): Promise<void> { 124 const { buildConstantsPath, macros } = options; 125 126 await updateBuildConstants(path.resolve(buildConstantsPath), macros); 127 } 128 129 async cleanupAsync(options): Promise<void> { 130 // Nothing to clean on Android 131 } 132} 133