1082815dcSEvan Baconimport { findSchemeNames, findSchemePaths } from './Paths';
2082815dcSEvan Baconimport { findSignableTargets, TargetType } from './Target';
3082815dcSEvan Baconimport { getPbxproj, unquote } from './utils/Xcodeproj';
4*8a424bebSJames Ideimport { readXMLAsync } from '../utils/XML';
5082815dcSEvan Bacon
6082815dcSEvan Baconinterface SchemeXML {
7082815dcSEvan Bacon  Scheme?: {
8082815dcSEvan Bacon    BuildAction?: {
9082815dcSEvan Bacon      BuildActionEntries?: {
10082815dcSEvan Bacon        BuildActionEntry?: BuildActionEntryType[];
11082815dcSEvan Bacon      }[];
12082815dcSEvan Bacon    }[];
13082815dcSEvan Bacon    ArchiveAction?: {
14082815dcSEvan Bacon      $?: {
15082815dcSEvan Bacon        buildConfiguration?: string;
16082815dcSEvan Bacon      };
17082815dcSEvan Bacon    }[];
18082815dcSEvan Bacon  };
19082815dcSEvan Bacon}
20082815dcSEvan Bacon
21082815dcSEvan Baconinterface BuildActionEntryType {
22082815dcSEvan Bacon  BuildableReference?: {
23082815dcSEvan Bacon    $?: {
24082815dcSEvan Bacon      BlueprintName?: string;
25082815dcSEvan Bacon      BuildableName?: string;
26082815dcSEvan Bacon    };
27082815dcSEvan Bacon  }[];
28082815dcSEvan Bacon}
29082815dcSEvan Bacon
30082815dcSEvan Baconexport function getSchemesFromXcodeproj(projectRoot: string): string[] {
31082815dcSEvan Bacon  return findSchemeNames(projectRoot);
32082815dcSEvan Bacon}
33082815dcSEvan Bacon
34082815dcSEvan Baconexport function getRunnableSchemesFromXcodeproj(
35082815dcSEvan Bacon  projectRoot: string,
36082815dcSEvan Bacon  { configuration = 'Debug' }: { configuration?: 'Debug' | 'Release' } = {}
37082815dcSEvan Bacon): { name: string; osType: string; type: string }[] {
38082815dcSEvan Bacon  const project = getPbxproj(projectRoot);
39082815dcSEvan Bacon
40082815dcSEvan Bacon  return findSignableTargets(project).map(([, target]) => {
41082815dcSEvan Bacon    let osType = 'iOS';
42082815dcSEvan Bacon    const type = unquote(target.productType);
43082815dcSEvan Bacon
44082815dcSEvan Bacon    if (type === TargetType.WATCH) {
45082815dcSEvan Bacon      osType = 'watchOS';
46082815dcSEvan Bacon    } else if (
47082815dcSEvan Bacon      // (apps) com.apple.product-type.application
48082815dcSEvan Bacon      // (app clips) com.apple.product-type.application.on-demand-install-capable
49082815dcSEvan Bacon      // NOTE(EvanBacon): This matches against `watchOS` as well so we check for watch first.
50082815dcSEvan Bacon      type.startsWith(TargetType.APPLICATION)
51082815dcSEvan Bacon    ) {
52082815dcSEvan Bacon      // Attempt to resolve the platform SDK for each target so we can filter devices.
53082815dcSEvan Bacon      const xcConfigurationList =
54082815dcSEvan Bacon        project.hash.project.objects.XCConfigurationList[target.buildConfigurationList];
55082815dcSEvan Bacon
56082815dcSEvan Bacon      if (xcConfigurationList) {
57082815dcSEvan Bacon        const buildConfiguration =
58082815dcSEvan Bacon          xcConfigurationList.buildConfigurations.find(
59082815dcSEvan Bacon            (value: { comment: string; value: string }) => value.comment === configuration
60082815dcSEvan Bacon          ) || xcConfigurationList.buildConfigurations[0];
61082815dcSEvan Bacon        if (buildConfiguration?.value) {
62082815dcSEvan Bacon          const xcBuildConfiguration =
63082815dcSEvan Bacon            project.hash.project.objects.XCBuildConfiguration?.[buildConfiguration.value];
64082815dcSEvan Bacon
65082815dcSEvan Bacon          const buildSdkRoot = xcBuildConfiguration.buildSettings.SDKROOT;
66082815dcSEvan Bacon          if (
67082815dcSEvan Bacon            buildSdkRoot === 'appletvos' ||
68082815dcSEvan Bacon            'TVOS_DEPLOYMENT_TARGET' in xcBuildConfiguration.buildSettings
69082815dcSEvan Bacon          ) {
70082815dcSEvan Bacon            // Is a TV app...
71082815dcSEvan Bacon            osType = 'tvOS';
72082815dcSEvan Bacon          } else if (buildSdkRoot === 'iphoneos') {
73082815dcSEvan Bacon            osType = 'iOS';
74082815dcSEvan Bacon          }
75082815dcSEvan Bacon        }
76082815dcSEvan Bacon      }
77082815dcSEvan Bacon    }
78082815dcSEvan Bacon
79082815dcSEvan Bacon    return {
80082815dcSEvan Bacon      name: unquote(target.name),
81082815dcSEvan Bacon      osType,
82082815dcSEvan Bacon      type: unquote(target.productType),
83082815dcSEvan Bacon    };
84082815dcSEvan Bacon  });
85082815dcSEvan Bacon}
86082815dcSEvan Bacon
87082815dcSEvan Baconasync function readSchemeAsync(
88082815dcSEvan Bacon  projectRoot: string,
89082815dcSEvan Bacon  scheme: string
90082815dcSEvan Bacon): Promise<SchemeXML | undefined> {
91082815dcSEvan Bacon  const allSchemePaths = findSchemePaths(projectRoot);
92082815dcSEvan Bacon  const re = new RegExp(`/${scheme}.xcscheme`, 'i');
93082815dcSEvan Bacon  const schemePath = allSchemePaths.find((i) => re.exec(i));
94082815dcSEvan Bacon  if (schemePath) {
95082815dcSEvan Bacon    return (await readXMLAsync({ path: schemePath })) as unknown as SchemeXML | undefined;
96082815dcSEvan Bacon  } else {
97082815dcSEvan Bacon    throw new Error(`scheme '${scheme}' does not exist, make sure it's marked as shared`);
98082815dcSEvan Bacon  }
99082815dcSEvan Bacon}
100082815dcSEvan Bacon
101082815dcSEvan Baconexport async function getApplicationTargetNameForSchemeAsync(
102082815dcSEvan Bacon  projectRoot: string,
103082815dcSEvan Bacon  scheme: string
104082815dcSEvan Bacon): Promise<string> {
105082815dcSEvan Bacon  const schemeXML = await readSchemeAsync(projectRoot, scheme);
106082815dcSEvan Bacon  const buildActionEntry =
107082815dcSEvan Bacon    schemeXML?.Scheme?.BuildAction?.[0]?.BuildActionEntries?.[0]?.BuildActionEntry;
108082815dcSEvan Bacon  const targetName =
109082815dcSEvan Bacon    buildActionEntry?.length === 1
110082815dcSEvan Bacon      ? getBlueprintName(buildActionEntry[0])
111082815dcSEvan Bacon      : getBlueprintName(
112082815dcSEvan Bacon          buildActionEntry?.find((entry) => {
113082815dcSEvan Bacon            return entry.BuildableReference?.[0]?.['$']?.BuildableName?.endsWith('.app');
114082815dcSEvan Bacon          })
115082815dcSEvan Bacon        );
116082815dcSEvan Bacon  if (!targetName) {
117082815dcSEvan Bacon    throw new Error(`${scheme}.xcscheme seems to be corrupted`);
118082815dcSEvan Bacon  }
119082815dcSEvan Bacon  return targetName;
120082815dcSEvan Bacon}
121082815dcSEvan Bacon
122082815dcSEvan Baconexport async function getArchiveBuildConfigurationForSchemeAsync(
123082815dcSEvan Bacon  projectRoot: string,
124082815dcSEvan Bacon  scheme: string
125082815dcSEvan Bacon): Promise<string> {
126082815dcSEvan Bacon  const schemeXML = await readSchemeAsync(projectRoot, scheme);
127082815dcSEvan Bacon  const buildConfiguration = schemeXML?.Scheme?.ArchiveAction?.[0]?.['$']?.buildConfiguration;
128082815dcSEvan Bacon  if (!buildConfiguration) {
129082815dcSEvan Bacon    throw new Error(`${scheme}.xcscheme seems to be corrupted`);
130082815dcSEvan Bacon  }
131082815dcSEvan Bacon  return buildConfiguration;
132082815dcSEvan Bacon}
133082815dcSEvan Bacon
134082815dcSEvan Baconfunction getBlueprintName(entry?: BuildActionEntryType): string | undefined {
135082815dcSEvan Bacon  return entry?.BuildableReference?.[0]?.['$']?.BlueprintName;
136082815dcSEvan Bacon}
137