1import { ExpoConfig } from '@expo/config-types';
2import { XcodeProject } from 'xcode';
3
4import { ConfigPlugin } from '../Plugin.types';
5import { withXcodeProject } from '../plugins/ios-plugins';
6import { addWarningIOS } from '../utils/warnings';
7import { isNotComment } from './utils/Xcodeproj';
8
9type Bitcode = NonNullable<ExpoConfig['ios']>['bitcode'];
10
11/**
12 * Plugin to set a bitcode preference for the Xcode project
13 * based on the project's Expo config `ios.bitcode` value.
14 */
15export const withBitcode: ConfigPlugin = (config) => {
16  return withXcodeProject(config, async (config) => {
17    config.modResults = await setBitcodeWithConfig(config, {
18      project: config.modResults,
19    });
20    return config;
21  });
22};
23
24/**
25 * Plugin to set a custom bitcode preference for the Xcode project.
26 * Does not read from the Expo config `ios.bitcode`.
27 *
28 * @param bitcode custom bitcode setting.
29 */
30export const withCustomBitcode: ConfigPlugin<Bitcode> = (config, bitcode) => {
31  return withXcodeProject(config, async (config) => {
32    config.modResults = await setBitcode(bitcode, {
33      project: config.modResults,
34    });
35    return config;
36  });
37};
38
39/**
40 * Get the bitcode preference from the Expo config.
41 */
42export function getBitcode(config: Pick<ExpoConfig, 'ios'>): Bitcode {
43  return config.ios?.bitcode;
44}
45
46/**
47 * Enable or disable the `ENABLE_BITCODE` property of the project configurations.
48 */
49export function setBitcodeWithConfig(
50  config: Pick<ExpoConfig, 'ios'>,
51  { project }: { project: XcodeProject }
52): XcodeProject {
53  const bitcode = getBitcode(config);
54  return setBitcode(bitcode, { project });
55}
56
57/**
58 * Enable or disable the `ENABLE_BITCODE` property.
59 */
60export function setBitcode(bitcode: Bitcode, { project }: { project: XcodeProject }): XcodeProject {
61  const isDefaultBehavior = bitcode == null;
62  // If the value is undefined, then do nothing.
63  if (isDefaultBehavior) {
64    return project;
65  }
66
67  const targetName = typeof bitcode === 'string' ? bitcode : undefined;
68  const isBitcodeEnabled = !!bitcode;
69  if (targetName) {
70    // Assert if missing
71    const configs = Object.entries(project.pbxXCBuildConfigurationSection()).filter(isNotComment);
72    const hasConfiguration = configs.find(([, configuration]) => configuration.name === targetName);
73    if (hasConfiguration) {
74      // If targetName is defined then disable bitcode everywhere.
75      project.addBuildProperty('ENABLE_BITCODE', 'NO');
76    } else {
77      const names = [
78        // Remove duplicates, wrap in double quotes, and sort alphabetically.
79        ...new Set(configs.map(([, configuration]) => `"${configuration.name}"`)),
80      ].sort();
81      addWarningIOS(
82        'ios.bitcode',
83        `No configuration named "${targetName}". Expected one of: ${names.join(', ')}.`
84      );
85    }
86  }
87
88  project.addBuildProperty('ENABLE_BITCODE', isBitcodeEnabled ? 'YES' : 'NO', targetName);
89
90  return project;
91}
92