1import { PBXNativeTarget, PBXTargetDependency, XCBuildConfiguration, XcodeProject } from 'xcode'; 2 3import { getApplicationTargetNameForSchemeAsync } from './BuildScheme'; 4import { 5 getBuildConfigurationForListIdAndName, 6 getPbxproj, 7 isNotComment, 8 NativeTargetSectionEntry, 9} from './utils/Xcodeproj'; 10import { trimQuotes } from './utils/string'; 11 12export enum TargetType { 13 APPLICATION = 'com.apple.product-type.application', 14 EXTENSION = 'com.apple.product-type.app-extension', 15 WATCH = 'com.apple.product-type.application.watchapp', 16 APP_CLIP = 'com.apple.product-type.application.on-demand-install-capable', 17 STICKER_PACK_EXTENSION = 'com.apple.product-type.app-extension.messages-sticker-pack', 18 OTHER = 'other', 19} 20 21export interface Target { 22 name: string; 23 type: TargetType; 24 dependencies?: Target[]; 25} 26 27export function getXCBuildConfigurationFromPbxproj( 28 project: XcodeProject, 29 { 30 targetName, 31 buildConfiguration = 'Release', 32 }: { targetName?: string; buildConfiguration?: string } = {} 33): XCBuildConfiguration | null { 34 const [, nativeTarget] = targetName 35 ? findNativeTargetByName(project, targetName) 36 : findFirstNativeTarget(project); 37 const [, xcBuildConfiguration] = getBuildConfigurationForListIdAndName(project, { 38 configurationListId: nativeTarget.buildConfigurationList, 39 buildConfiguration, 40 }); 41 return xcBuildConfiguration ?? null; 42} 43 44export async function findApplicationTargetWithDependenciesAsync( 45 projectRoot: string, 46 scheme: string 47): Promise<Target> { 48 const applicationTargetName = await getApplicationTargetNameForSchemeAsync(projectRoot, scheme); 49 const project = getPbxproj(projectRoot); 50 const [, applicationTarget] = findNativeTargetByName(project, applicationTargetName); 51 const dependencies = getTargetDependencies(project, applicationTarget); 52 return { 53 name: trimQuotes(applicationTarget.name), 54 type: TargetType.APPLICATION, 55 dependencies, 56 }; 57} 58 59function getTargetDependencies( 60 project: XcodeProject, 61 parentTarget: PBXNativeTarget 62): Target[] | undefined { 63 if (!parentTarget.dependencies || parentTarget.dependencies.length === 0) { 64 return undefined; 65 } 66 return parentTarget.dependencies.map(({ value }) => { 67 const { target: targetId } = project.getPBXGroupByKeyAndType( 68 value, 69 'PBXTargetDependency' 70 ) as PBXTargetDependency; 71 72 const [, target] = findNativeTargetById(project, targetId); 73 74 const type = isTargetOfType(target, TargetType.EXTENSION) 75 ? TargetType.EXTENSION 76 : TargetType.OTHER; 77 return { 78 name: trimQuotes(target.name), 79 type, 80 dependencies: getTargetDependencies(project, target), 81 }; 82 }); 83} 84 85export function isTargetOfType(target: PBXNativeTarget, targetType: TargetType): boolean { 86 return trimQuotes(target.productType) === targetType; 87} 88 89export function getNativeTargets(project: XcodeProject): NativeTargetSectionEntry[] { 90 const section = project.pbxNativeTargetSection(); 91 return Object.entries(section).filter(isNotComment); 92} 93 94export function findSignableTargets(project: XcodeProject): NativeTargetSectionEntry[] { 95 const targets = getNativeTargets(project); 96 97 const signableTargetTypes = [ 98 TargetType.APPLICATION, 99 TargetType.APP_CLIP, 100 TargetType.EXTENSION, 101 TargetType.WATCH, 102 TargetType.STICKER_PACK_EXTENSION, 103 ]; 104 105 const applicationTargets = targets.filter(([, target]) => { 106 for (const targetType of signableTargetTypes) { 107 if (isTargetOfType(target, targetType)) { 108 return true; 109 } 110 } 111 return false; 112 }); 113 if (applicationTargets.length === 0) { 114 throw new Error(`Could not find any signable targets in project.pbxproj`); 115 } 116 return applicationTargets; 117} 118 119export function findFirstNativeTarget(project: XcodeProject): NativeTargetSectionEntry { 120 const targets = getNativeTargets(project); 121 const applicationTargets = targets.filter(([, target]) => 122 isTargetOfType(target, TargetType.APPLICATION) 123 ); 124 if (applicationTargets.length === 0) { 125 throw new Error(`Could not find any application target in project.pbxproj`); 126 } 127 return applicationTargets[0]; 128} 129 130export function findNativeTargetByName( 131 project: XcodeProject, 132 targetName: string 133): NativeTargetSectionEntry { 134 const nativeTargets = getNativeTargets(project); 135 const nativeTargetEntry = nativeTargets.find(([, i]) => trimQuotes(i.name) === targetName); 136 if (!nativeTargetEntry) { 137 throw new Error(`Could not find target '${targetName}' in project.pbxproj`); 138 } 139 return nativeTargetEntry; 140} 141 142function findNativeTargetById(project: XcodeProject, targetId: string): NativeTargetSectionEntry { 143 const nativeTargets = getNativeTargets(project); 144 const nativeTargetEntry = nativeTargets.find(([key]) => key === targetId); 145 if (!nativeTargetEntry) { 146 throw new Error(`Could not find target with id '${targetId}' in project.pbxproj`); 147 } 148 return nativeTargetEntry; 149} 150