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 (e) {
36    return null;
37  }
38}
39
40export async function generateAndroidBuildConstantsFromMacrosAsync(macros) {
41  let source;
42
43  // android falls back to published dev home if local dev home
44  // doesn't exist or had an error.
45  const isLocalManifestEmpty =
46    !macros.BUILD_MACHINE_KERNEL_MANIFEST || macros.BUILD_MACHINE_KERNEL_MANIFEST === '';
47
48  let versionUsed = 'local';
49  if (process.env.USE_DOGFOODING_PUBLISHED_KERNEL_MANIFEST) {
50    macros.BUILD_MACHINE_KERNEL_MANIFEST = macros.DOGFOODING_PUBLISHED_KERNEL_MANIFEST;
51    versionUsed = 'doogfooding';
52  } else if (isLocalManifestEmpty) {
53    macros.BUILD_MACHINE_KERNEL_MANIFEST = macros.DEV_PUBLISHED_KERNEL_MANIFEST;
54    versionUsed = 'published dev';
55  }
56  console.log(`Using ${chalk.yellow(versionUsed)} version of Expo Home.`);
57
58  delete macros['DEV_PUBLISHED_KERNEL_MANIFEST'];
59  delete macros['DOGFOODING_PUBLISHED_KERNEL_MANIFEST'];
60
61  const definitions = Object.entries(macros).map(
62    ([name, value]) =>
63      `  public static final ${formatJavaType(value)} ${name} = ${formatJavaLiteral(value)};`
64  );
65
66  source = `
67package host.exp.exponent.generated;
68
69public class ExponentBuildConstants {
70${definitions.join('\n')}
71}`;
72
73  return (
74    `
75// Copyright 2016-present 650 Industries. All rights reserved.
76// @generated by \`expotools android-generate-dynamic-macros\`
77
78${source.trim()}
79`.trim() + '\n'
80  );
81}
82
83async function updateBuildConstants(buildConstantsPath, macros) {
84  console.log(
85    'Generating build config %s ...',
86    chalk.cyan(path.relative(EXPO_DIR, buildConstantsPath))
87  );
88
89  const [source, existingSource] = await Promise.all([
90    generateAndroidBuildConstantsFromMacrosAsync(macros),
91    readExistingSourceAsync(path.resolve(buildConstantsPath)),
92  ]);
93
94  if (source !== existingSource) {
95    await fs.ensureDir(path.dirname(buildConstantsPath));
96    await fs.writeFile(buildConstantsPath, source, 'utf8');
97  }
98}
99
100export default class AndroidMacrosGenerator {
101  async generateAsync(options): Promise<void> {
102    const { buildConstantsPath, macros } = options;
103
104    await updateBuildConstants(path.resolve(buildConstantsPath), macros);
105  }
106
107  async cleanupAsync(options): Promise<void> {
108    // Nothing to clean on Android
109  }
110}
111