FBControlCore/Utility/FBDeveloperDiskImage.m (166 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "FBDeveloperDiskImage.h" #import <FBControlCore/FBControlCore.h> static NSInteger ScoreVersions(NSOperatingSystemVersion current, NSOperatingSystemVersion target) { NSInteger major = ABS((current.majorVersion - target.majorVersion) * 10); NSInteger minor = ABS(current.minorVersion - target.minorVersion); return major + minor; } @implementation FBDeveloperDiskImage #pragma mark Private + (NSArray<FBDeveloperDiskImage *> *)allDiskImagesFromSearchPath:(NSString *)searchPath xcodeVersion:(NSOperatingSystemVersion)xcodeVersion logger:(id<FBControlCoreLogger>)logger { NSMutableArray<FBDeveloperDiskImage *> *images = NSMutableArray.array; [logger logFormat:@"Attempting to find Disk Images at path %@", searchPath]; for (NSString *fileName in [NSFileManager.defaultManager contentsOfDirectoryAtPath:searchPath error:nil] ?: @[]) { NSString *resolvedPath = [searchPath stringByAppendingPathComponent:fileName]; NSError *error = nil; FBDeveloperDiskImage *image = [self diskImageAtPath:resolvedPath xcodeVersion:xcodeVersion error:&error]; if (!image) { [logger logFormat:@"%@ does not contain a valid disk image", error]; continue; } [images addObject:image]; } return [images sortedArrayUsingSelector:@selector(compare:)]; } + (nullable FBDeveloperDiskImage *)diskImageAtPath:(NSString *)path xcodeVersion:(NSOperatingSystemVersion)xcodeVersion error:(NSError **)error { NSString *diskImagePath = [path stringByAppendingPathComponent:@"DeveloperDiskImage.dmg"]; if (![NSFileManager.defaultManager fileExistsAtPath:diskImagePath]) { return [[FBControlCoreError describeFormat:@"Disk image does not exist at expected path %@", diskImagePath] fail:error]; } NSString *signaturePath = [diskImagePath stringByAppendingString:@".signature"]; NSData *signature = [NSData dataWithContentsOfFile:signaturePath]; if (!signature) { return [[FBControlCoreError describeFormat:@"Failed to load signature at %@", signaturePath] fail:error]; } NSOperatingSystemVersion version = [FBOSVersion operatingSystemVersionFromName:path.lastPathComponent]; return [[FBDeveloperDiskImage alloc] initWithDiskImagePath:diskImagePath signature:signature version:version xcodeVersion:xcodeVersion]; } #pragma mark Initializers + (FBDeveloperDiskImage *)developerDiskImage:(NSOperatingSystemVersion)targetVersion logger:(id<FBControlCoreLogger>)logger error:(NSError **)error { NSArray<FBDeveloperDiskImage *> *images = FBDeveloperDiskImage.allDiskImages; return [self bestImageForImages:images targetVersion:targetVersion logger:logger error:error]; } + (NSArray<FBDeveloperDiskImage *> *)allDiskImages { static dispatch_once_t onceToken; static NSArray<FBDeveloperDiskImage *> *images = nil; dispatch_once(&onceToken, ^{ images = [self allDiskImagesFromSearchPath:[FBXcodeConfiguration.developerDirectory stringByAppendingPathComponent:@"Platforms/iPhoneOS.platform/DeviceSupport"] xcodeVersion:FBXcodeConfiguration.xcodeVersion logger:FBControlCoreGlobalConfiguration.defaultLogger]; }); return images; } - (instancetype)initWithDiskImagePath:(NSString *)diskImagePath signature:(NSData *)signature version:(NSOperatingSystemVersion)version xcodeVersion:(NSOperatingSystemVersion)xcodeVersion { self = [super init]; if (!self) { return nil; } _diskImagePath = diskImagePath; _signature = signature; _version = version; _xcodeVersion = xcodeVersion; return self; } #pragma mark Public + (NSString *)pathForDeveloperSymbols:(NSString *)buildVersion logger:(id<FBControlCoreLogger>)logger error:(NSError **)error { NSArray<NSString *> *searchPaths = @[ [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Developer/Xcode/iOS DeviceSupport"], [FBXcodeConfiguration.developerDirectory stringByAppendingPathComponent:@"Platforms/iPhoneOS.platform/DeviceSupport"], ]; [logger logFormat:@"Attempting to find Symbols directory by build version %@", buildVersion]; NSMutableArray<NSString *> *paths = NSMutableArray.array; for (NSString *searchPath in searchPaths) { NSError *innerError = nil; NSArray<NSString *> *supportPaths = [NSFileManager.defaultManager contentsOfDirectoryAtPath:searchPath error:&innerError]; if (!supportPaths) { continue; } for (NSString *supportName in supportPaths) { NSString *supportPath = [searchPath stringByAppendingPathComponent:supportName]; BOOL isDirectory = NO; if (![NSFileManager.defaultManager fileExistsAtPath:supportPath isDirectory:&isDirectory]) { continue; } if (isDirectory == NO) { continue; } NSString *symbolsPath = [supportPath stringByAppendingPathComponent:@"Symbols"]; if (![NSFileManager.defaultManager fileExistsAtPath:symbolsPath isDirectory:&isDirectory]) { continue; } if (isDirectory == NO) { continue; } [paths addObject:symbolsPath]; } } for (NSString *path in paths) { if (![path containsString:buildVersion]) { continue; } return path; } return [[FBControlCoreError describeFormat:@"Could not find the Symbols for %@ in any of %@", buildVersion, [FBCollectionInformation oneLineDescriptionFromArray:paths]] fail:error]; } + (nullable FBDeveloperDiskImage *)bestImageForImages:(NSArray<FBDeveloperDiskImage *> *)images targetVersion:(NSOperatingSystemVersion)targetVersion logger:(id<FBControlCoreLogger>)logger error:(NSError **)error { if (images.count == 0) { return [[FBControlCoreError describe:@"No disk images provided"] fail:error]; } // Sort the array such that the best matching version appears at the top. NSArray<FBDeveloperDiskImage *> *sorted = [images sortedArrayUsingComparator:^ NSComparisonResult (FBDeveloperDiskImage *left, FBDeveloperDiskImage *right) { NSOperatingSystemVersion leftVersion = left.version; NSOperatingSystemVersion rightVersion = right.version; NSInteger leftDelta = ScoreVersions(leftVersion, targetVersion); NSInteger rightDelta = ScoreVersions(rightVersion, targetVersion); if (leftDelta < rightDelta) { return NSOrderedAscending; } if (leftDelta > rightDelta) { return NSOrderedDescending; } return NSOrderedSame; }]; FBDeveloperDiskImage *best = sorted.firstObject; NSOperatingSystemVersion bestVersion = best.version; if (bestVersion.majorVersion == targetVersion.majorVersion && bestVersion.minorVersion == targetVersion.minorVersion) { [logger logFormat:@"Found the best match for %ld.%ld at %@", targetVersion.majorVersion, targetVersion.minorVersion, best]; return best; } if (bestVersion.majorVersion == targetVersion.majorVersion) { [logger logFormat:@"Found the closest match for %ld.%ld at %@", targetVersion.majorVersion, targetVersion.minorVersion, best]; return best; } return [[FBControlCoreError describeFormat:@"The best match %@ is not suitable for %ld.%ld", best, targetVersion.majorVersion, targetVersion.minorVersion] fail:error]; } - (NSString *)description { return [NSString stringWithFormat:@"%@: %lu.%lu", self.diskImagePath, self.version.majorVersion, self.version.minorVersion]; } - (NSComparisonResult)compare:(FBDeveloperDiskImage *)other { NSComparisonResult comparison = [@(self.version.majorVersion) compare:@(other.version.majorVersion)]; if (comparison != NSOrderedSame) { return comparison; } comparison = [@(self.version.minorVersion) compare:@(other.version.minorVersion)]; if (comparison != NSOrderedSame) { return comparison; } return [@(self.version.patchVersion) compare:@(other.version.patchVersion)]; } @end