1082815dcSEvan Baconimport { PBXNativeTarget, PBXTargetDependency, XCBuildConfiguration, XcodeProject } from 'xcode'; 2082815dcSEvan Bacon 3082815dcSEvan Baconimport { getApplicationTargetNameForSchemeAsync } from './BuildScheme'; 4082815dcSEvan Baconimport { 5082815dcSEvan Bacon getBuildConfigurationForListIdAndName, 6082815dcSEvan Bacon getPbxproj, 7082815dcSEvan Bacon isNotComment, 8082815dcSEvan Bacon NativeTargetSectionEntry, 9082815dcSEvan Bacon} from './utils/Xcodeproj'; 10082815dcSEvan Baconimport { trimQuotes } from './utils/string'; 11082815dcSEvan Bacon 12082815dcSEvan Baconexport enum TargetType { 13082815dcSEvan Bacon APPLICATION = 'com.apple.product-type.application', 14082815dcSEvan Bacon EXTENSION = 'com.apple.product-type.app-extension', 15082815dcSEvan Bacon WATCH = 'com.apple.product-type.application.watchapp', 16082815dcSEvan Bacon APP_CLIP = 'com.apple.product-type.application.on-demand-install-capable', 17082815dcSEvan Bacon STICKER_PACK_EXTENSION = 'com.apple.product-type.app-extension.messages-sticker-pack', 18*f421f4ecSDominik Sokal FRAMEWORK = 'com.apple.product-type.framework', 19082815dcSEvan Bacon OTHER = 'other', 20082815dcSEvan Bacon} 21082815dcSEvan Bacon 22082815dcSEvan Baconexport interface Target { 23082815dcSEvan Bacon name: string; 24082815dcSEvan Bacon type: TargetType; 25*f421f4ecSDominik Sokal signable: boolean; 26082815dcSEvan Bacon dependencies?: Target[]; 27082815dcSEvan Bacon} 28082815dcSEvan Bacon 29082815dcSEvan Baconexport function getXCBuildConfigurationFromPbxproj( 30082815dcSEvan Bacon project: XcodeProject, 31082815dcSEvan Bacon { 32082815dcSEvan Bacon targetName, 33082815dcSEvan Bacon buildConfiguration = 'Release', 34082815dcSEvan Bacon }: { targetName?: string; buildConfiguration?: string } = {} 35082815dcSEvan Bacon): XCBuildConfiguration | null { 36082815dcSEvan Bacon const [, nativeTarget] = targetName 37082815dcSEvan Bacon ? findNativeTargetByName(project, targetName) 38082815dcSEvan Bacon : findFirstNativeTarget(project); 39082815dcSEvan Bacon const [, xcBuildConfiguration] = getBuildConfigurationForListIdAndName(project, { 40082815dcSEvan Bacon configurationListId: nativeTarget.buildConfigurationList, 41082815dcSEvan Bacon buildConfiguration, 42082815dcSEvan Bacon }); 43082815dcSEvan Bacon return xcBuildConfiguration ?? null; 44082815dcSEvan Bacon} 45082815dcSEvan Bacon 46082815dcSEvan Baconexport async function findApplicationTargetWithDependenciesAsync( 47082815dcSEvan Bacon projectRoot: string, 48082815dcSEvan Bacon scheme: string 49082815dcSEvan Bacon): Promise<Target> { 50082815dcSEvan Bacon const applicationTargetName = await getApplicationTargetNameForSchemeAsync(projectRoot, scheme); 51082815dcSEvan Bacon const project = getPbxproj(projectRoot); 52082815dcSEvan Bacon const [, applicationTarget] = findNativeTargetByName(project, applicationTargetName); 53082815dcSEvan Bacon const dependencies = getTargetDependencies(project, applicationTarget); 54082815dcSEvan Bacon return { 55082815dcSEvan Bacon name: trimQuotes(applicationTarget.name), 56082815dcSEvan Bacon type: TargetType.APPLICATION, 57*f421f4ecSDominik Sokal signable: true, 58082815dcSEvan Bacon dependencies, 59082815dcSEvan Bacon }; 60082815dcSEvan Bacon} 61082815dcSEvan Bacon 62082815dcSEvan Baconfunction getTargetDependencies( 63082815dcSEvan Bacon project: XcodeProject, 64082815dcSEvan Bacon parentTarget: PBXNativeTarget 65082815dcSEvan Bacon): Target[] | undefined { 66082815dcSEvan Bacon if (!parentTarget.dependencies || parentTarget.dependencies.length === 0) { 67082815dcSEvan Bacon return undefined; 68082815dcSEvan Bacon } 69*f421f4ecSDominik Sokal 70*f421f4ecSDominik Sokal const nonSignableTargetTypes: TargetType[] = [TargetType.FRAMEWORK]; 71*f421f4ecSDominik Sokal 72082815dcSEvan Bacon return parentTarget.dependencies.map(({ value }) => { 73082815dcSEvan Bacon const { target: targetId } = project.getPBXGroupByKeyAndType( 74082815dcSEvan Bacon value, 75082815dcSEvan Bacon 'PBXTargetDependency' 76082815dcSEvan Bacon ) as PBXTargetDependency; 77082815dcSEvan Bacon 78082815dcSEvan Bacon const [, target] = findNativeTargetById(project, targetId); 79082815dcSEvan Bacon 80082815dcSEvan Bacon const type = isTargetOfType(target, TargetType.EXTENSION) 81082815dcSEvan Bacon ? TargetType.EXTENSION 82082815dcSEvan Bacon : TargetType.OTHER; 83082815dcSEvan Bacon return { 84082815dcSEvan Bacon name: trimQuotes(target.name), 85082815dcSEvan Bacon type, 86*f421f4ecSDominik Sokal signable: !nonSignableTargetTypes.some((signableTargetType) => 87*f421f4ecSDominik Sokal isTargetOfType(target, signableTargetType) 88*f421f4ecSDominik Sokal ), 89082815dcSEvan Bacon dependencies: getTargetDependencies(project, target), 90082815dcSEvan Bacon }; 91082815dcSEvan Bacon }); 92082815dcSEvan Bacon} 93082815dcSEvan Bacon 94082815dcSEvan Baconexport function isTargetOfType(target: PBXNativeTarget, targetType: TargetType): boolean { 95082815dcSEvan Bacon return trimQuotes(target.productType) === targetType; 96082815dcSEvan Bacon} 97082815dcSEvan Bacon 98082815dcSEvan Baconexport function getNativeTargets(project: XcodeProject): NativeTargetSectionEntry[] { 99082815dcSEvan Bacon const section = project.pbxNativeTargetSection(); 100082815dcSEvan Bacon return Object.entries(section).filter(isNotComment); 101082815dcSEvan Bacon} 102082815dcSEvan Bacon 103082815dcSEvan Baconexport function findSignableTargets(project: XcodeProject): NativeTargetSectionEntry[] { 104082815dcSEvan Bacon const targets = getNativeTargets(project); 105082815dcSEvan Bacon 106*f421f4ecSDominik Sokal const signableTargetTypes: TargetType[] = [ 107082815dcSEvan Bacon TargetType.APPLICATION, 108082815dcSEvan Bacon TargetType.APP_CLIP, 109082815dcSEvan Bacon TargetType.EXTENSION, 110082815dcSEvan Bacon TargetType.WATCH, 111082815dcSEvan Bacon TargetType.STICKER_PACK_EXTENSION, 112082815dcSEvan Bacon ]; 113082815dcSEvan Bacon 114082815dcSEvan Bacon const applicationTargets = targets.filter(([, target]) => { 115082815dcSEvan Bacon for (const targetType of signableTargetTypes) { 116082815dcSEvan Bacon if (isTargetOfType(target, targetType)) { 117082815dcSEvan Bacon return true; 118082815dcSEvan Bacon } 119082815dcSEvan Bacon } 120082815dcSEvan Bacon return false; 121082815dcSEvan Bacon }); 122082815dcSEvan Bacon if (applicationTargets.length === 0) { 123082815dcSEvan Bacon throw new Error(`Could not find any signable targets in project.pbxproj`); 124082815dcSEvan Bacon } 125082815dcSEvan Bacon return applicationTargets; 126082815dcSEvan Bacon} 127082815dcSEvan Bacon 128082815dcSEvan Baconexport function findFirstNativeTarget(project: XcodeProject): NativeTargetSectionEntry { 129082815dcSEvan Bacon const targets = getNativeTargets(project); 130082815dcSEvan Bacon const applicationTargets = targets.filter(([, target]) => 131082815dcSEvan Bacon isTargetOfType(target, TargetType.APPLICATION) 132082815dcSEvan Bacon ); 133082815dcSEvan Bacon if (applicationTargets.length === 0) { 134082815dcSEvan Bacon throw new Error(`Could not find any application target in project.pbxproj`); 135082815dcSEvan Bacon } 136082815dcSEvan Bacon return applicationTargets[0]; 137082815dcSEvan Bacon} 138082815dcSEvan Bacon 139082815dcSEvan Baconexport function findNativeTargetByName( 140082815dcSEvan Bacon project: XcodeProject, 141082815dcSEvan Bacon targetName: string 142082815dcSEvan Bacon): NativeTargetSectionEntry { 143082815dcSEvan Bacon const nativeTargets = getNativeTargets(project); 144082815dcSEvan Bacon const nativeTargetEntry = nativeTargets.find(([, i]) => trimQuotes(i.name) === targetName); 145082815dcSEvan Bacon if (!nativeTargetEntry) { 146082815dcSEvan Bacon throw new Error(`Could not find target '${targetName}' in project.pbxproj`); 147082815dcSEvan Bacon } 148082815dcSEvan Bacon return nativeTargetEntry; 149082815dcSEvan Bacon} 150082815dcSEvan Bacon 151082815dcSEvan Baconfunction findNativeTargetById(project: XcodeProject, targetId: string): NativeTargetSectionEntry { 152082815dcSEvan Bacon const nativeTargets = getNativeTargets(project); 153082815dcSEvan Bacon const nativeTargetEntry = nativeTargets.find(([key]) => key === targetId); 154082815dcSEvan Bacon if (!nativeTargetEntry) { 155082815dcSEvan Bacon throw new Error(`Could not find target with id '${targetId}' in project.pbxproj`); 156082815dcSEvan Bacon } 157082815dcSEvan Bacon return nativeTargetEntry; 158082815dcSEvan Bacon} 159