1import { readXMLAsync } from '../utils/XML'; 2import { findSchemeNames, findSchemePaths } from './Paths'; 3import { findSignableTargets, TargetType } from './Target'; 4import { getPbxproj, unquote } from './utils/Xcodeproj'; 5 6interface SchemeXML { 7 Scheme?: { 8 BuildAction?: { 9 BuildActionEntries?: { 10 BuildActionEntry?: BuildActionEntryType[]; 11 }[]; 12 }[]; 13 ArchiveAction?: { 14 $?: { 15 buildConfiguration?: string; 16 }; 17 }[]; 18 }; 19} 20 21interface BuildActionEntryType { 22 BuildableReference?: { 23 $?: { 24 BlueprintName?: string; 25 BuildableName?: string; 26 }; 27 }[]; 28} 29 30export function getSchemesFromXcodeproj(projectRoot: string): string[] { 31 return findSchemeNames(projectRoot); 32} 33 34export function getRunnableSchemesFromXcodeproj( 35 projectRoot: string, 36 { configuration = 'Debug' }: { configuration?: 'Debug' | 'Release' } = {} 37): { name: string; osType: string; type: string }[] { 38 const project = getPbxproj(projectRoot); 39 40 return findSignableTargets(project).map(([, target]) => { 41 let osType = 'iOS'; 42 const type = unquote(target.productType); 43 44 if (type === TargetType.WATCH) { 45 osType = 'watchOS'; 46 } else if ( 47 // (apps) com.apple.product-type.application 48 // (app clips) com.apple.product-type.application.on-demand-install-capable 49 // NOTE(EvanBacon): This matches against `watchOS` as well so we check for watch first. 50 type.startsWith(TargetType.APPLICATION) 51 ) { 52 // Attempt to resolve the platform SDK for each target so we can filter devices. 53 const xcConfigurationList = 54 project.hash.project.objects.XCConfigurationList[target.buildConfigurationList]; 55 56 if (xcConfigurationList) { 57 const buildConfiguration = 58 xcConfigurationList.buildConfigurations.find( 59 (value: { comment: string; value: string }) => value.comment === configuration 60 ) || xcConfigurationList.buildConfigurations[0]; 61 if (buildConfiguration?.value) { 62 const xcBuildConfiguration = 63 project.hash.project.objects.XCBuildConfiguration?.[buildConfiguration.value]; 64 65 const buildSdkRoot = xcBuildConfiguration.buildSettings.SDKROOT; 66 if ( 67 buildSdkRoot === 'appletvos' || 68 'TVOS_DEPLOYMENT_TARGET' in xcBuildConfiguration.buildSettings 69 ) { 70 // Is a TV app... 71 osType = 'tvOS'; 72 } else if (buildSdkRoot === 'iphoneos') { 73 osType = 'iOS'; 74 } 75 } 76 } 77 } 78 79 return { 80 name: unquote(target.name), 81 osType, 82 type: unquote(target.productType), 83 }; 84 }); 85} 86 87async function readSchemeAsync( 88 projectRoot: string, 89 scheme: string 90): Promise<SchemeXML | undefined> { 91 const allSchemePaths = findSchemePaths(projectRoot); 92 const re = new RegExp(`/${scheme}.xcscheme`, 'i'); 93 const schemePath = allSchemePaths.find((i) => re.exec(i)); 94 if (schemePath) { 95 return (await readXMLAsync({ path: schemePath })) as unknown as SchemeXML | undefined; 96 } else { 97 throw new Error(`scheme '${scheme}' does not exist, make sure it's marked as shared`); 98 } 99} 100 101export async function getApplicationTargetNameForSchemeAsync( 102 projectRoot: string, 103 scheme: string 104): Promise<string> { 105 const schemeXML = await readSchemeAsync(projectRoot, scheme); 106 const buildActionEntry = 107 schemeXML?.Scheme?.BuildAction?.[0]?.BuildActionEntries?.[0]?.BuildActionEntry; 108 const targetName = 109 buildActionEntry?.length === 1 110 ? getBlueprintName(buildActionEntry[0]) 111 : getBlueprintName( 112 buildActionEntry?.find((entry) => { 113 return entry.BuildableReference?.[0]?.['$']?.BuildableName?.endsWith('.app'); 114 }) 115 ); 116 if (!targetName) { 117 throw new Error(`${scheme}.xcscheme seems to be corrupted`); 118 } 119 return targetName; 120} 121 122export async function getArchiveBuildConfigurationForSchemeAsync( 123 projectRoot: string, 124 scheme: string 125): Promise<string> { 126 const schemeXML = await readSchemeAsync(projectRoot, scheme); 127 const buildConfiguration = schemeXML?.Scheme?.ArchiveAction?.[0]?.['$']?.buildConfiguration; 128 if (!buildConfiguration) { 129 throw new Error(`${scheme}.xcscheme seems to be corrupted`); 130 } 131 return buildConfiguration; 132} 133 134function getBlueprintName(entry?: BuildActionEntryType): string | undefined { 135 return entry?.BuildableReference?.[0]?.['$']?.BlueprintName; 136} 137