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