FBSimulatorControl/Commands/FBSimulatorLifecycleCommands.m (199 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 "FBSimulatorLifecycleCommands.h" #import <CoreSimulator/SimDevice.h> #import <AppKit/AppKit.h> #import "FBCoreSimulatorNotifier.h" #import "FBSimulator.h" #import "FBSimulatorBootConfiguration.h" #import "FBSimulatorBootStrategy.h" #import "FBSimulatorBridge.h" #import "FBSimulatorConfiguration+CoreSimulator.h" #import "FBSimulatorConfiguration.h" #import "FBSimulatorControl.h" #import "FBSimulatorControlConfiguration.h" #import "FBSimulatorError.h" @interface FBSimulatorLifecycleCommands () @property (nonatomic, weak, readonly) FBSimulator *simulator; @property (nonatomic, strong, readwrite, nullable) FBFramebuffer *framebuffer; @property (nonatomic, strong, readwrite, nullable) FBSimulatorHID *hid; @property (nonatomic, strong, readwrite, nullable) FBSimulatorBridge *bridge; @end @implementation FBSimulatorLifecycleCommands #pragma mark Initializers + (instancetype)commandsWithTarget:(FBSimulator *)target { return [[self alloc] initWithSimulator:target]; } - (instancetype)initWithSimulator:(FBSimulator *)simulator { self = [super init]; if (!self) { return nil; } _simulator = simulator; return self; } #pragma mark Boot/Shutdown - (FBFuture<NSNull *> *)boot:(FBSimulatorBootConfiguration *)configuration { return [FBSimulatorBootStrategy boot:self.simulator withConfiguration:configuration]; } #pragma mark FBPowerCommands - (FBFuture<NSNull *> *)shutdown { return [[self.simulator.set shutdown:self.simulator] mapReplace:NSNull.null]; } - (FBFuture<NSNull *> *)reboot { return [[self shutdown] onQueue:self.simulator.workQueue fmap:^(id _) { return [self boot:FBSimulatorBootConfiguration.defaultConfiguration]; }]; } #pragma mark Erase - (FBFuture<NSNull *> *)erase { return [[self.simulator.set erase:self.simulator] mapReplace:NSNull.null]; } #pragma mark States - (FBFuture<NSNull *> *)resolveState:(FBiOSTargetState)state { return FBiOSTargetResolveState(self.simulator, state); } - (FBFuture<NSNull *> *)resolveLeavesState:(FBiOSTargetState)state { return [FBCoreSimulatorNotifier resolveLeavesState:state forSimDevice:self.simulator.device]; } #pragma mark Focus - (FBFuture<NSNull *> *)focus { // We cannot 'focus' a SimulatorApp for the non-default device set. NSString *deviceSetPath = self.simulator.customDeviceSetPath; if (deviceSetPath) { return [[FBSimulatorError describeFormat:@"Focusing on the Simulator App for a simulator in a custom device set (%@) is not supported", deviceSetPath] failFuture]; } // Find the running instances of SimulatorApp. NSArray<NSRunningApplication *> *apps = NSWorkspace.sharedWorkspace.runningApplications; NSPredicate *simulatorAppPredicate = [NSPredicate predicateWithBlock:^(NSRunningApplication *application, NSDictionary<NSString *,id> *__) { return [application.bundleIdentifier isEqualToString:@"com.apple.iphonesimulator"]; }]; NSArray<NSRunningApplication *> *simulatorApps = [apps filteredArrayUsingPredicate:simulatorAppPredicate]; // If we have no SimulatorApp running then we can instead launch one in a focused state if (simulatorApps.count == 0) { NSError *error = nil; NSRunningApplication *simulatorApp = [FBSimulatorLifecycleCommands launchSimulatorApplicationForDefaultDeviceSetWithError:&error]; if (!simulatorApp) { return [FBFuture futureWithError:error]; } return FBFuture.empty; } // Multiple apps, we don't know which to select. if (simulatorApps.count > 1) { return [[FBSimulatorError describeFormat:@"More than one SimulatorApp %@ running, focus is ambiguous", [FBCollectionInformation oneLineDescriptionFromArray:simulatorApps]] failFuture]; } // Otherwise we have a single Simulator App to activate. NSRunningApplication *simulatorApp = simulatorApps.firstObject; if (![simulatorApp activateWithOptions:NSApplicationActivateIgnoringOtherApps]) { return [[FBSimulatorError describeFormat:@"Failed to focus %@", simulatorApp] failFuture]; } return FBFuture.empty; } + (NSRunningApplication *)launchSimulatorApplicationForDefaultDeviceSetWithError:(NSError **)error { // Obtain the location of the SimulatorApp FBBundleDescriptor *applicationBundle = FBXcodeConfiguration.simulatorApp; NSURL *applicationURL = [NSURL fileURLWithPath:applicationBundle.path]; // We only want to ever connect to the default SimulatorApp, including re-activating it rather than creating a new instance. NSError *innerError = nil; NSRunningApplication *application = [NSWorkspace.sharedWorkspace launchApplicationAtURL:applicationURL options:NSWorkspaceLaunchDefault configuration:@{} error:&innerError]; if (!application) { return [[[FBSimulatorError describe:@"Failed to launch SimulatorApp"] causedBy:innerError] fail:error]; } return application; } #pragma mark Connection - (FBFuture<NSNull *> *)disconnectWithTimeout:(NSTimeInterval)timeout logger:(nullable id<FBControlCoreLogger>)logger { NSDate *date = NSDate.date; return [[[self terminateConnections] timeout:timeout waitingFor:@"Simulator connections to teardown"] onQueue:self.simulator.workQueue map:^(id _) { [logger.debug logFormat:@"Simulator connections torn down in %f seconds", [NSDate.date timeIntervalSinceDate:date]]; return NSNull.null; }]; } - (FBFuture<NSNull *> *)terminateConnections { FBSimulatorHID *hid = self.hid; FBSimulatorBridge *bridge = self.bridge; return [[FBFuture futureWithFutures:@[ (hid ? [hid disconnect] : FBFuture.empty), (bridge ? [bridge disconnect] : FBFuture.empty), ]] onQueue:self.simulator.workQueue chain:^(FBFuture *_) { // Nullify self.framebuffer = nil; self.hid = nil; self.bridge = nil; return FBFuture.empty; }]; } #pragma mark Bridge - (FBFuture<FBSimulatorBridge *> *)connectToBridge { if (self.bridge) { return [FBFuture futureWithResult:self.bridge]; } return [[FBSimulatorBridge bridgeForSimulator:self.simulator] onQueue:self.simulator.workQueue map:^(FBSimulatorBridge *bridge) { self.bridge = bridge; return bridge; }]; } #pragma mark Framebuffer - (FBFuture<FBFramebuffer *> *)connectToFramebuffer { if (self.framebuffer) { return [FBFuture futureWithResult:self.framebuffer]; } FBSimulator *simulator = self.simulator; return [FBFuture onQueue:simulator.workQueue resolveValue:^(NSError **error) { return [FBFramebuffer mainScreenSurfaceForSimulator:simulator logger:simulator.logger error:error]; }]; } #pragma mark Bridge - (FBFuture<FBSimulatorHID *> *)connectToHID { if (self.hid) { return [FBFuture futureWithResult:self.hid]; } return [[FBSimulatorHID hidForSimulator:self.simulator] onQueue:self.simulator.workQueue map:^(FBSimulatorHID *hid) { self.hid = hid; return hid; }]; } #pragma mark URLs - (FBFuture<NSNull *> *)openURL:(NSURL *)url { NSParameterAssert(url); NSError *error = nil; if (![self.simulator.device openURL:url error:&error]) { return [[[FBSimulatorError describeFormat:@"Failed to open URL %@ on simulator %@", url, self.simulator] causedBy:error] failFuture]; } return [FBFuture futureWithResult:[NSNull null]]; } @end