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