FBSimulatorControl/Management/FBSimulatorSet.m (205 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 "FBSimulatorSet.h"
#import "FBSimulatorSet+Private.h"
#import <CoreSimulator/SimDevice.h>
#import <CoreSimulator/SimDeviceSet.h>
#import <CoreSimulator/SimDeviceType.h>
#import <CoreSimulator/SimRuntime.h>
#import <CoreSimulator/SimServiceContext.h>
#import <FBControlCore/FBControlCore.h>
#import <objc/runtime.h>
#import "FBCoreSimulatorNotifier.h"
#import "FBSimulatorControl.h"
#import "FBSimulatorControlConfiguration.h"
#import "FBSimulatorControlFrameworkLoader.h"
#import "FBSimulatorDeletionStrategy.h"
#import "FBSimulatorEraseStrategy.h"
#import "FBSimulatorInflationStrategy.h"
#import "FBSimulatorNotificationUpdateStrategy.h"
#import "FBSimulatorShutdownStrategy.h"
@implementation FBSimulatorSet
@synthesize allSimulators = _allSimulators;
@synthesize delegate = _delegate;
#pragma mark Initializers
+ (void)initialize
{
[FBSimulatorControlFrameworkLoader.essentialFrameworks loadPrivateFrameworksOrAbort];
}
+ (instancetype)setWithConfiguration:(FBSimulatorControlConfiguration *)configuration deviceSet:(SimDeviceSet *)deviceSet delegate:(id<FBiOSTargetSetDelegate>)delegate logger:(id<FBControlCoreLogger>)logger reporter:(id<FBEventReporter>)reporter error:(NSError **)error
{
return [[FBSimulatorSet alloc] initWithConfiguration:configuration deviceSet:deviceSet delegate:delegate logger:logger reporter:reporter];
}
- (instancetype)initWithConfiguration:(FBSimulatorControlConfiguration *)configuration deviceSet:(SimDeviceSet *)deviceSet delegate:(id<FBiOSTargetSetDelegate>)delegate logger:(id<FBControlCoreLogger>)logger reporter:(id<FBEventReporter>)reporter
{
self = [super init];
if (!self) {
return nil;
}
_configuration = configuration;
_deviceSet = deviceSet;
_delegate = delegate;
_logger = logger;
_reporter = reporter;
_workQueue = dispatch_get_main_queue();
_asyncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_allSimulators = @[];
_inflationStrategy = [FBSimulatorInflationStrategy strategyForSet:self];
_notificationUpdateStrategy = [FBSimulatorNotificationUpdateStrategy strategyWithSet:self];
return self;
}
#pragma mark Querying
- (id<FBiOSTargetInfo>)targetWithUDID:(NSString *)udid
{
return [self simulatorWithUDID:udid];
}
- (FBSimulator *)simulatorWithUDID:(NSString *)udid
{
return [[self.allSimulators filteredArrayUsingPredicate:FBiOSTargetPredicateForUDID(udid)] firstObject];
}
#pragma mark Creation
- (FBFuture<FBSimulator *> *)createSimulatorWithConfiguration:(FBSimulatorConfiguration *)configuration
{
FBDeviceModel model = configuration.device.model;
// See if we meet the runtime requirements to create a Simulator with the given configuration.
NSError *innerError = nil;
SimDeviceType *deviceType = [configuration obtainDeviceTypeWithError:&innerError];
if (!deviceType) {
return [[[FBSimulatorError
describeFormat:@"Could not obtain a DeviceType for Configuration %@", configuration]
causedBy:innerError]
failFuture];
}
SimRuntime *runtime = [configuration obtainRuntimeWithError:&innerError];
if (!runtime) {
return [[[FBSimulatorError
describeFormat:@"Could not obtain a SimRuntime for Configuration %@", configuration]
causedBy:innerError]
failFuture];
}
// First, create the device.
[self.logger.debug logFormat:@"Creating device with Type %@ Runtime %@", deviceType, runtime];
return [[[FBSimulatorSet
onDeviceSet:self.deviceSet createDeviceWithType:deviceType runtime:runtime name:model queue:self.asyncQueue]
onQueue:self.workQueue fmap:^(SimDevice *device) {
return [self fetchNewlyMadeSimulator:device];
}]
onQueue:self.workQueue fmap:^(FBSimulator *simulator) {
simulator.configuration = configuration;
[self.logger.debug logFormat:@"Created Simulator %@ for configuration %@", simulator.udid, configuration];
// This step ensures that the Simulator is in a known-shutdown state after creation.
// This prevents racing with any 'booting' interaction that occurs immediately after allocation.
return [[[FBSimulatorShutdownStrategy
shutdown:simulator]
rephraseFailure:@"Could not get newly-created simulator into a shutdown state"]
mapReplace:simulator];
}];
}
- (FBFuture<FBSimulator *> *)cloneSimulator:(FBSimulator *)simulator toDeviceSet:(FBSimulatorSet *)destinationSet
{
NSParameterAssert(simulator.set == self);
return [[FBSimulatorSet
onDeviceSet:self.deviceSet cloneDevice:simulator.device toDeviceSet:destinationSet.deviceSet queue:self.asyncQueue]
onQueue:self.workQueue fmap:^(SimDevice *device) {
return [destinationSet fetchNewlyMadeSimulator:device];
}];
}
- (NSArray<FBSimulatorConfiguration *> *)configurationsForAbsentDefaultSimulators
{
NSSet<FBSimulatorConfiguration *> *existingConfigurations = [NSSet setWithArray:[self.allSimulators valueForKey:@"configuration"]];
NSMutableSet<FBSimulatorConfiguration *> *absentConfigurations = [NSMutableSet setWithArray:[FBSimulatorConfiguration allAvailableDefaultConfigurationsWithLogger:self.logger]];
[absentConfigurations minusSet:existingConfigurations];
return [absentConfigurations allObjects];
}
#pragma mark Destructive Methods
- (FBFuture<NSNull *> *)shutdown:(FBSimulator *)simulator
{
NSParameterAssert(simulator);
return [FBSimulatorShutdownStrategy shutdown:simulator];
}
- (FBFuture<NSNull *> *)erase:(FBSimulator *)simulator
{
NSParameterAssert(simulator);
return [FBSimulatorEraseStrategy erase:simulator];
}
- (FBFuture<NSNull *> *)delete:(FBSimulator *)simulator
{
NSParameterAssert(simulator);
return [FBSimulatorDeletionStrategy delete:simulator];
}
- (FBFuture<NSNull *> *)shutdownAll:(NSArray<FBSimulator *> *)simulators
{
NSParameterAssert(simulators);
return [FBSimulatorShutdownStrategy shutdownAll:simulators];
}
- (FBFuture<NSNull *> *)deleteAll:(NSArray<FBSimulator *> *)simulators;
{
NSParameterAssert(simulators);
return [FBSimulatorDeletionStrategy deleteAll:simulators];
}
- (FBFuture<NSNull *> *)shutdownAll
{
NSArray<FBSimulator *> *simulators = self.allSimulators;
return [FBSimulatorShutdownStrategy shutdownAll:simulators];
}
- (FBFuture<NSNull *> *)deleteAll
{
return [self deleteAll:self.allSimulators];
}
#pragma mark NSObject
- (NSString *)description
{
return [FBCollectionInformation oneLineDescriptionFromArray:self.allSimulators];
}
#pragma mark Private Methods
+ (NSDictionary<NSString *, FBSimulator *> *)keySimulatorsByUDID:(NSArray *)simulators
{
NSMutableDictionary<NSString *, FBSimulator *> *dictionary = [NSMutableDictionary dictionary];
for (FBSimulator *simulator in simulators) {
dictionary[simulator.udid] = simulator;
}
return [dictionary copy];
}
- (FBFuture<FBSimulator *> *)fetchNewlyMadeSimulator:(SimDevice *)device
{
// The SimDevice should now be in the DeviceSet and thus in the collection of Simulators.
FBSimulator *simulator = [FBSimulatorSet keySimulatorsByUDID:self.allSimulators][device.UDID.UUIDString];
if (!simulator) {
return [[FBSimulatorError
describeFormat:@"Expected simulator with UDID %@ to be inflated", device.UDID.UUIDString]
failFuture];
}
return [FBFuture futureWithResult:simulator];
}
#pragma mark Public Properties
- (NSArray<FBSimulator *> *)allSimulators
{
_allSimulators = [[self.inflationStrategy
inflateFromDevices:self.deviceSet.availableDevices exitingSimulators:_allSimulators]
sortedArrayUsingSelector:@selector(compare:)];
return _allSimulators;
}
#pragma mark FBiOSTargetSet Implementation
- (NSArray<id<FBiOSTarget>> *)allTargetInfos
{
return self.allSimulators;
}
#pragma mark Private Properties
+ (FBFuture<SimDevice *> *)onDeviceSet:(SimDeviceSet *)deviceSet createDeviceWithType:(SimDeviceType *)deviceType runtime:(SimRuntime *)runtime name:(NSString *)name queue:(dispatch_queue_t)queue
{
FBMutableFuture<SimDevice *> *future = FBMutableFuture.future;
[deviceSet createDeviceAsyncWithType:deviceType runtime:runtime name:name completionQueue:queue completionHandler:^(NSError *error, SimDevice *device) {
if (device) {
[future resolveWithResult:device];
} else {
[future resolveWithError:error];
}
}];
return future;
}
+ (FBFuture<SimDevice *> *)onDeviceSet:(SimDeviceSet *)deviceSet cloneDevice:(SimDevice *)device toDeviceSet:(SimDeviceSet *)destinationSet queue:(dispatch_queue_t)queue
{
FBMutableFuture<SimDevice *> *future = FBMutableFuture.future;
[deviceSet cloneDeviceAsync:device name:device.name toSet:destinationSet completionQueue:queue completionHandler:^(NSError *error, SimDevice *created) {
if (created) {
[future resolveWithResult:created];
} else {
[future resolveWithError:error];
}
}];
return future;
}
@end