|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #import <Foundation/Foundation.h> | 
|  | #include <asl.h> | 
|  | #include <libgen.h> | 
|  | #include <stdarg.h> | 
|  | #include <stdio.h> | 
|  |  | 
|  | // An executable (iossim) that runs an app in the iOS Simulator. | 
|  | // Run 'iossim -h' for usage information. | 
|  | // | 
|  | // For best results, the iOS Simulator application should not be running when | 
|  | // iossim is invoked. | 
|  | // | 
|  | // Headers for iPhoneSimulatorRemoteClient and other frameworks used in this | 
|  | // tool are generated by class-dump, via GYP. | 
|  | // (class-dump is available at http://www.codethecode.com/projects/class-dump/) | 
|  | // | 
|  | // However, there are some forward declarations required to get things to | 
|  | // compile. | 
|  |  | 
|  | // TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed | 
|  | // (crbug.com/385030). | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | @class DVTStackBacktrace; | 
|  | #import "DVTFoundation.h" | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  |  | 
|  | @protocol OS_dispatch_queue | 
|  | @end | 
|  | @protocol OS_dispatch_source | 
|  | @end | 
|  | // TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed | 
|  | // (crbug.com/385030). | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | @protocol OS_xpc_object | 
|  | @end | 
|  | @protocol SimBridge; | 
|  | @class SimDeviceSet; | 
|  | @class SimDeviceType; | 
|  | @class SimRuntime; | 
|  | @class SimServiceConnectionManager; | 
|  | #import "CoreSimulator.h" | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  |  | 
|  | @interface DVTPlatform : NSObject | 
|  | + (BOOL)loadAllPlatformsReturningError:(id*)arg1; | 
|  | @end | 
|  | @class DTiPhoneSimulatorApplicationSpecifier; | 
|  | @class DTiPhoneSimulatorSession; | 
|  | @class DTiPhoneSimulatorSessionConfig; | 
|  | @class DTiPhoneSimulatorSystemRoot; | 
|  | @class DVTConfinementServiceConnection; | 
|  | @class DVTDispatchLock; | 
|  | @class DVTiPhoneSimulatorMessenger; | 
|  | @class DVTNotificationToken; | 
|  | @class DVTTask; | 
|  | // The DTiPhoneSimulatorSessionDelegate protocol is referenced | 
|  | // by the iPhoneSimulatorRemoteClient framework, but not defined in the object | 
|  | // file, so it must be defined here before importing the generated | 
|  | // iPhoneSimulatorRemoteClient.h file. | 
|  | @protocol DTiPhoneSimulatorSessionDelegate | 
|  | - (void)session:(DTiPhoneSimulatorSession*)session | 
|  | didEndWithError:(NSError*)error; | 
|  | - (void)session:(DTiPhoneSimulatorSession*)session | 
|  | didStart:(BOOL)started | 
|  | withError:(NSError*)error; | 
|  | @end | 
|  | #import "DVTiPhoneSimulatorRemoteClient.h" | 
|  |  | 
|  | // An undocumented system log key included in messages from launchd. The value | 
|  | // is the PID of the process the message is about (as opposed to launchd's PID). | 
|  | #define ASL_KEY_REF_PID "RefPID" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Name of environment variables that control the user's home directory in the | 
|  | // simulator. | 
|  | const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME"; | 
|  | const char* const kHomeEnvVariable = "HOME"; | 
|  |  | 
|  | // Device family codes for iPhone and iPad. | 
|  | const int kIPhoneFamily = 1; | 
|  | const int kIPadFamily = 2; | 
|  |  | 
|  | // Max number of seconds to wait for the simulator session to start. | 
|  | // This timeout must allow time to start up iOS Simulator, install the app | 
|  | // and perform any other black magic that is encoded in the | 
|  | // iPhoneSimulatorRemoteClient framework to kick things off. Normal start up | 
|  | // time is only a couple seconds but machine load, disk caches, etc., can all | 
|  | // affect startup time in the wild so the timeout needs to be fairly generous. | 
|  | // If this timeout occurs iossim will likely exit with non-zero status; the | 
|  | // exception being if the app is invoked and completes execution before the | 
|  | // session is started (this case is handled in session:didStart:withError). | 
|  | const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30; | 
|  |  | 
|  | // While the simulated app is running, its stdout is redirected to a file which | 
|  | // is polled by iossim and written to iossim's stdout using the following | 
|  | // polling interval. | 
|  | const NSTimeInterval kOutputPollIntervalSeconds = 0.1; | 
|  |  | 
|  | NSString* const kDVTFoundationRelativePath = | 
|  | @"../SharedFrameworks/DVTFoundation.framework"; | 
|  | NSString* const kDevToolsFoundationRelativePath = | 
|  | @"../OtherFrameworks/DevToolsFoundation.framework"; | 
|  | NSString* const kSimulatorRelativePath = | 
|  | @"Platforms/iPhoneSimulator.platform/Developer/Applications/" | 
|  | @"iPhone Simulator.app"; | 
|  |  | 
|  | // Simulator Error String Key. This can be found by looking in the Simulator's | 
|  | // Localizable.strings files. | 
|  | NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit."; | 
|  |  | 
|  | const char* gToolName = "iossim"; | 
|  |  | 
|  | // Exit status codes. | 
|  | const int kExitSuccess = EXIT_SUCCESS; | 
|  | const int kExitFailure = EXIT_FAILURE; | 
|  | const int kExitInvalidArguments = 2; | 
|  | const int kExitInitializationFailure = 3; | 
|  | const int kExitAppFailedToStart = 4; | 
|  | const int kExitAppCrashed = 5; | 
|  | const int kExitUnsupportedXcodeVersion = 6; | 
|  |  | 
|  | void LogError(NSString* format, ...) { | 
|  | va_list list; | 
|  | va_start(list, format); | 
|  |  | 
|  | NSString* message = | 
|  | [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; | 
|  |  | 
|  | fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]); | 
|  | fflush(stderr); | 
|  |  | 
|  | va_end(list); | 
|  | } | 
|  |  | 
|  | void LogWarning(NSString* format, ...) { | 
|  | va_list list; | 
|  | va_start(list, format); | 
|  |  | 
|  | NSString* message = | 
|  | [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; | 
|  |  | 
|  | fprintf(stderr, "%s: WARNING: %s\n", gToolName, [message UTF8String]); | 
|  | fflush(stderr); | 
|  |  | 
|  | va_end(list); | 
|  | } | 
|  |  | 
|  | // Helper to find a class by name and die if it isn't found. | 
|  | Class FindClassByName(NSString* nameOfClass) { | 
|  | Class theClass = NSClassFromString(nameOfClass); | 
|  | if (!theClass) { | 
|  | LogError(@"Failed to find class %@ at runtime.", nameOfClass); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  | return theClass; | 
|  | } | 
|  |  | 
|  | // Returns the a NSString containing the stdout from running an NSTask that | 
|  | // launches |toolPath| with th given command line |args|. | 
|  | NSString* GetOutputFromTask(NSString* toolPath, NSArray* args) { | 
|  | NSTask* task = [[[NSTask alloc] init] autorelease]; | 
|  | [task setLaunchPath:toolPath]; | 
|  | [task setArguments:args]; | 
|  | NSPipe* outputPipe = [NSPipe pipe]; | 
|  | [task setStandardOutput:outputPipe]; | 
|  | NSFileHandle* outputFile = [outputPipe fileHandleForReading]; | 
|  |  | 
|  | [task launch]; | 
|  | NSData* outputData = [outputFile readDataToEndOfFile]; | 
|  | [task waitUntilExit]; | 
|  | if ([task isRunning]) { | 
|  | LogError(@"Task '%@ %@' is still running.", | 
|  | toolPath, | 
|  | [args componentsJoinedByString:@" "]); | 
|  | return nil; | 
|  | } else if ([task terminationStatus]) { | 
|  | LogError(@"Task '%@ %@' exited with return code %d.", | 
|  | toolPath, | 
|  | [args componentsJoinedByString:@" "], | 
|  | [task terminationStatus]); | 
|  | return nil; | 
|  | } | 
|  | return [[[NSString alloc] initWithData:outputData | 
|  | encoding:NSUTF8StringEncoding] autorelease]; | 
|  | } | 
|  |  | 
|  | // Finds the Xcode version via xcodebuild -version. Output from xcodebuild is | 
|  | // expected to look like: | 
|  | //   Xcode <version> | 
|  | //   Build version 5B130a | 
|  | // where <version> is the string returned by this function (e.g. 6.0). | 
|  | NSString* FindXcodeVersion() { | 
|  | NSString* output = GetOutputFromTask(@"/usr/bin/xcodebuild", | 
|  | @[ @"-version" ]); | 
|  | // Scan past the "Xcode ", then scan the rest of the line into |version|. | 
|  | NSScanner* scanner = [NSScanner scannerWithString:output]; | 
|  | BOOL valid = [scanner scanString:@"Xcode " intoString:NULL]; | 
|  | NSString* version; | 
|  | valid = | 
|  | [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] | 
|  | intoString:&version]; | 
|  | if (!valid) { | 
|  | LogError(@"Unable to find Xcode version. 'xcodebuild -version' " | 
|  | @"returned \n%@", output); | 
|  | return nil; | 
|  | } | 
|  | return version; | 
|  | } | 
|  |  | 
|  | // Returns true if iossim is running with Xcode 6 or later installed on the | 
|  | // host. | 
|  | BOOL IsRunningWithXcode6OrLater() { | 
|  | static NSString* xcodeVersion = FindXcodeVersion(); | 
|  | if (!xcodeVersion) { | 
|  | return false; | 
|  | } | 
|  | NSArray* components = [xcodeVersion componentsSeparatedByString:@"."]; | 
|  | if ([components count] < 1) { | 
|  | return false; | 
|  | } | 
|  | NSInteger majorVersion = [[components objectAtIndex:0] integerValue]; | 
|  | return majorVersion >= 6; | 
|  | } | 
|  |  | 
|  | // Prints supported devices and SDKs. | 
|  | void PrintSupportedDevices() { | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | printf("Supported device/SDK combinations:\n"); | 
|  | Class simDeviceSetClass = FindClassByName(@"SimDeviceSet"); | 
|  | id deviceSet = | 
|  | [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]]; | 
|  | for (id simDevice in [deviceSet availableDevices]) { | 
|  | NSString* deviceInfo = | 
|  | [NSString stringWithFormat:@"  -d '%@' -s '%@'\n", | 
|  | [simDevice name], [[simDevice runtime] versionString]]; | 
|  | printf("%s", [deviceInfo UTF8String]); | 
|  | } | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  | } else { | 
|  | printf("Supported SDK versions:\n"); | 
|  | Class rootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot"); | 
|  | for (id root in [rootClass knownRoots]) { | 
|  | printf("  '%s'\n", [[root sdkVersion] UTF8String]); | 
|  | } | 
|  | // This is the list of devices supported on Xcode 5.1.x. | 
|  | printf("Supported devices:\n"); | 
|  | printf("  'iPhone'\n"); | 
|  | printf("  'iPhone Retina (3.5-inch)'\n"); | 
|  | printf("  'iPhone Retina (4-inch)'\n"); | 
|  | printf("  'iPhone Retina (4-inch 64-bit)'\n"); | 
|  | printf("  'iPad'\n"); | 
|  | printf("  'iPad Retina'\n"); | 
|  | printf("  'iPad Retina (64-bit)'\n"); | 
|  | } | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | // A delegate that is called when the simulated app is started or ended in the | 
|  | // simulator. | 
|  | @interface SimulatorDelegate : NSObject <DTiPhoneSimulatorSessionDelegate> { | 
|  | @private | 
|  | NSString* stdioPath_; | 
|  | NSString* developerDir_; | 
|  | NSString* simulatorHome_; | 
|  | NSThread* outputThread_; | 
|  | NSBundle* simulatorBundle_; | 
|  | BOOL appRunning_; | 
|  | } | 
|  | @end | 
|  |  | 
|  | // An implementation that copies the simulated app's stdio to stdout of this | 
|  | // executable. While it would be nice to get stdout and stderr independently | 
|  | // from iOS Simulator, issues like I/O buffering and interleaved output | 
|  | // between iOS Simulator and the app would cause iossim to display things out | 
|  | // of order here. Printing all output to a single file keeps the order correct. | 
|  | // Instances of this classe should be initialized with the location of the | 
|  | // simulated app's output file. When the simulated app starts, a thread is | 
|  | // started which handles copying data from the simulated app's output file to | 
|  | // the stdout of this executable. | 
|  | @implementation SimulatorDelegate | 
|  |  | 
|  | // Specifies the file locations of the simulated app's stdout and stderr. | 
|  | - (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath | 
|  | developerDir:(NSString*)developerDir | 
|  | simulatorHome:(NSString*)simulatorHome { | 
|  | self = [super init]; | 
|  | if (self) { | 
|  | stdioPath_ = [stdioPath copy]; | 
|  | developerDir_ = [developerDir copy]; | 
|  | simulatorHome_ = [simulatorHome copy]; | 
|  | } | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (void)dealloc { | 
|  | [stdioPath_ release]; | 
|  | [developerDir_ release]; | 
|  | [simulatorBundle_ release]; | 
|  | [super dealloc]; | 
|  | } | 
|  |  | 
|  | // Reads data from the simulated app's output and writes it to stdout. This | 
|  | // method blocks, so it should be called in a separate thread. The iOS | 
|  | // Simulator takes a file path for the simulated app's stdout and stderr, but | 
|  | // this path isn't always available (e.g. when the stdout is Xcode's build | 
|  | // window). As a workaround, iossim creates a temp file to hold output, which | 
|  | // this method reads and copies to stdout. | 
|  | - (void)tailOutputForSession:(DTiPhoneSimulatorSession*)session { | 
|  | NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; | 
|  |  | 
|  | NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_]; | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | // With iOS 8 simulators on Xcode 6, the app output is relative to the | 
|  | // simulator's data directory. | 
|  | NSString* versionString = | 
|  | [[[session sessionConfig] simulatedSystemRoot] sdkVersion]; | 
|  | NSInteger majorVersion = [[[versionString componentsSeparatedByString:@"."] | 
|  | objectAtIndex:0] intValue]; | 
|  | if (majorVersion >= 8) { | 
|  | NSString* dataPath = session.sessionConfig.device.dataPath; | 
|  | NSString* appOutput = | 
|  | [dataPath stringByAppendingPathComponent:stdioPath_]; | 
|  | simio = [NSFileHandle fileHandleForReadingAtPath:appOutput]; | 
|  | } | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  | } | 
|  | NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput]; | 
|  | // Copy data to stdout/stderr while the app is running. | 
|  | while (appRunning_) { | 
|  | NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init]; | 
|  | [standardOutput writeData:[simio readDataToEndOfFile]]; | 
|  | [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; | 
|  | [innerPool drain]; | 
|  | } | 
|  |  | 
|  | // Once the app is no longer running, copy any data that was written during | 
|  | // the last sleep cycle. | 
|  | [standardOutput writeData:[simio readDataToEndOfFile]]; | 
|  |  | 
|  | [pool drain]; | 
|  | } | 
|  |  | 
|  | // Fetches a localized error string from the Simulator. | 
|  | - (NSString *)localizedSimulatorErrorString:(NSString*)stringKey { | 
|  | // Lazy load of the simulator bundle. | 
|  | if (simulatorBundle_ == nil) { | 
|  | NSString* simulatorPath = [developerDir_ | 
|  | stringByAppendingPathComponent:kSimulatorRelativePath]; | 
|  | simulatorBundle_ = [NSBundle bundleWithPath:simulatorPath]; | 
|  | } | 
|  | NSString *localizedStr = | 
|  | [simulatorBundle_ localizedStringForKey:stringKey | 
|  | value:nil | 
|  | table:nil]; | 
|  | if ([localizedStr length]) | 
|  | return localizedStr; | 
|  | // Failed to get a value, follow Cocoa conventions and use the key as the | 
|  | // string. | 
|  | return stringKey; | 
|  | } | 
|  |  | 
|  | - (void)session:(DTiPhoneSimulatorSession*)session | 
|  | didStart:(BOOL)started | 
|  | withError:(NSError*)error { | 
|  | if (!started) { | 
|  | // If the test executes very quickly (<30ms), the SimulatorDelegate may not | 
|  | // get the initial session:started:withError: message indicating successful | 
|  | // startup of the simulated app. Instead the delegate will get a | 
|  | // session:started:withError: message after the timeout has elapsed. To | 
|  | // account for this case, check if the simulated app's stdio file was | 
|  | // ever created and if it exists dump it to stdout and return success. | 
|  | NSFileManager* fileManager = [NSFileManager defaultManager]; | 
|  | if ([fileManager fileExistsAtPath:stdioPath_]) { | 
|  | appRunning_ = NO; | 
|  | [self tailOutputForSession:session]; | 
|  | // Note that exiting in this state leaves a process running | 
|  | // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will | 
|  | // prevent future simulator sessions from being started for 30 seconds | 
|  | // unless the iOS Simulator application is killed altogether. | 
|  | [self session:session didEndWithError:nil]; | 
|  |  | 
|  | // session:didEndWithError should not return (because it exits) so | 
|  | // the execution path should never get here. | 
|  | exit(kExitFailure); | 
|  | } | 
|  |  | 
|  | LogError(@"Simulator failed to start: \"%@\" (%@:%ld)", | 
|  | [error localizedDescription], | 
|  | [error domain], static_cast<long int>([error code])); | 
|  | PrintSupportedDevices(); | 
|  | exit(kExitAppFailedToStart); | 
|  | } | 
|  |  | 
|  | // Start a thread to write contents of outputPath to stdout. | 
|  | appRunning_ = YES; | 
|  | outputThread_ = | 
|  | [[NSThread alloc] initWithTarget:self | 
|  | selector:@selector(tailOutputForSession:) | 
|  | object:session]; | 
|  | [outputThread_ start]; | 
|  | } | 
|  |  | 
|  | - (void)session:(DTiPhoneSimulatorSession*)session | 
|  | didEndWithError:(NSError*)error { | 
|  | appRunning_ = NO; | 
|  | // Wait for the output thread to finish copying data to stdout. | 
|  | if (outputThread_) { | 
|  | while (![outputThread_ isFinished]) { | 
|  | [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; | 
|  | } | 
|  | [outputThread_ release]; | 
|  | outputThread_ = nil; | 
|  | } | 
|  |  | 
|  | if (error) { | 
|  | // There appears to be a race condition where sometimes the simulator | 
|  | // framework will end with an error, but the error is that the simulated | 
|  | // app cleanly shut down; try to trap this error and don't fail the | 
|  | // simulator run. | 
|  | NSString* localizedDescription = [error localizedDescription]; | 
|  | NSString* ignorableErrorStr = | 
|  | [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey]; | 
|  | if ([ignorableErrorStr isEqual:localizedDescription]) { | 
|  | LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)", | 
|  | localizedDescription, [error domain], | 
|  | static_cast<long int>([error code])); | 
|  | } else { | 
|  | LogError(@"Simulator ended with error: \"%@\" (%@:%ld)", | 
|  | localizedDescription, [error domain], | 
|  | static_cast<long int>([error code])); | 
|  | exit(kExitFailure); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Try to determine if the simulated app crashed or quit with a non-zero | 
|  | // status code. iOS Simluator handles things a bit differently depending on | 
|  | // the version, so first determine the iOS version being used. | 
|  | BOOL badEntryFound = NO; | 
|  | NSString* versionString = | 
|  | [[[session sessionConfig] simulatedSystemRoot] sdkVersion]; | 
|  | NSInteger majorVersion = [[[versionString componentsSeparatedByString:@"."] | 
|  | objectAtIndex:0] intValue]; | 
|  | if (majorVersion <= 6) { | 
|  | // In iOS 6 and before, logging from the simulated apps went to the main | 
|  | // system logs, so use ASL to check if the simulated app exited abnormally | 
|  | // by looking for system log messages from launchd that refer to the | 
|  | // simulated app's PID. Limit query to messages in the last minute since | 
|  | // PIDs are cyclical. | 
|  | aslmsg query = asl_new(ASL_TYPE_QUERY); | 
|  | asl_set_query(query, ASL_KEY_SENDER, "launchd", | 
|  | ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); | 
|  | char session_id[20]; | 
|  | if (snprintf(session_id, 20, "%d", [session simulatedApplicationPID]) < 0) { | 
|  | LogError(@"Failed to get [session simulatedApplicationPID]"); | 
|  | exit(kExitFailure); | 
|  | } | 
|  | asl_set_query(query, ASL_KEY_REF_PID, session_id, ASL_QUERY_OP_EQUAL); | 
|  | asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL); | 
|  |  | 
|  | // Log any messages found, and take note of any messages that may indicate | 
|  | // the app crashed or did not exit cleanly. | 
|  | aslresponse response = asl_search(NULL, query); | 
|  | aslmsg entry; | 
|  | while ((entry = aslresponse_next(response)) != NULL) { | 
|  | const char* message = asl_get(entry, ASL_KEY_MSG); | 
|  | LogWarning(@"Console message: %s", message); | 
|  | // Some messages are harmless, so don't trigger a failure for them. | 
|  | if (strstr(message, "The following job tried to hijack the service")) | 
|  | continue; | 
|  | badEntryFound = YES; | 
|  | } | 
|  | } else { | 
|  | // Otherwise, the iOS Simulator's system logging is sandboxed, so parse the | 
|  | // sandboxed system.log file for known errors. | 
|  | NSString* path; | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | NSString* dataPath = session.sessionConfig.device.dataPath; | 
|  | path = | 
|  | [dataPath stringByAppendingPathComponent:@"Library/Logs/system.log"]; | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  | } else { | 
|  | NSString* relativePathToSystemLog = | 
|  | [NSString stringWithFormat: | 
|  | @"Library/Logs/iOS Simulator/%@/system.log", versionString]; | 
|  | path = [simulatorHome_ | 
|  | stringByAppendingPathComponent:relativePathToSystemLog]; | 
|  | } | 
|  | NSFileManager* fileManager = [NSFileManager defaultManager]; | 
|  | if ([fileManager fileExistsAtPath:path]) { | 
|  | NSString* content = | 
|  | [NSString stringWithContentsOfFile:path | 
|  | encoding:NSUTF8StringEncoding | 
|  | error:NULL]; | 
|  | NSArray* lines = [content componentsSeparatedByCharactersInSet: | 
|  | [NSCharacterSet newlineCharacterSet]]; | 
|  | NSString* simulatedAppPID = | 
|  | [NSString stringWithFormat:@"%d", session.simulatedApplicationPID]; | 
|  | NSArray* kErrorStrings = @[ | 
|  | @"Service exited with abnormal code:", | 
|  | @"Service exited due to signal:", | 
|  | ]; | 
|  | for (NSString* line in lines) { | 
|  | if ([line rangeOfString:simulatedAppPID].location != NSNotFound) { | 
|  | for (NSString* errorString in kErrorStrings) { | 
|  | if ([line rangeOfString:errorString].location != NSNotFound) { | 
|  | LogWarning(@"Console message: %@", line); | 
|  | badEntryFound = YES; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (badEntryFound) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | // Remove the log file so subsequent invocations of iossim won't be | 
|  | // looking at stale logs. | 
|  | remove([path fileSystemRepresentation]); | 
|  | } else { | 
|  | LogWarning(@"Unable to find system log at '%@'.", path); | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the query returned any nasty-looking results, iossim should exit with | 
|  | // non-zero status. | 
|  | if (badEntryFound) { | 
|  | LogError(@"Simulated app crashed or exited with non-zero status"); | 
|  | exit(kExitAppCrashed); | 
|  | } | 
|  | exit(kExitSuccess); | 
|  | } | 
|  | @end | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Finds the developer dir via xcode-select or the DEVELOPER_DIR environment | 
|  | // variable. | 
|  | NSString* FindDeveloperDir() { | 
|  | // Check the env first. | 
|  | NSDictionary* env = [[NSProcessInfo processInfo] environment]; | 
|  | NSString* developerDir = [env objectForKey:@"DEVELOPER_DIR"]; | 
|  | if ([developerDir length] > 0) | 
|  | return developerDir; | 
|  |  | 
|  | // Go look for it via xcode-select. | 
|  | NSString* output = GetOutputFromTask(@"/usr/bin/xcode-select", | 
|  | @[ @"-print-path" ]); | 
|  | output = [output stringByTrimmingCharactersInSet: | 
|  | [NSCharacterSet whitespaceAndNewlineCharacterSet]]; | 
|  | if ([output length] == 0) | 
|  | output = nil; | 
|  | return output; | 
|  | } | 
|  |  | 
|  | // Loads the Simulator framework from the given developer dir. | 
|  | NSBundle* LoadSimulatorFramework(NSString* developerDir) { | 
|  | // The Simulator framework depends on some of the other Xcode private | 
|  | // frameworks; manually load them first so everything can be linked up. | 
|  | NSString* dvtFoundationPath = [developerDir | 
|  | stringByAppendingPathComponent:kDVTFoundationRelativePath]; | 
|  | NSBundle* dvtFoundationBundle = | 
|  | [NSBundle bundleWithPath:dvtFoundationPath]; | 
|  | if (![dvtFoundationBundle load]) | 
|  | return nil; | 
|  |  | 
|  | NSString* devToolsFoundationPath = [developerDir | 
|  | stringByAppendingPathComponent:kDevToolsFoundationRelativePath]; | 
|  | NSBundle* devToolsFoundationBundle = | 
|  | [NSBundle bundleWithPath:devToolsFoundationPath]; | 
|  | if (![devToolsFoundationBundle load]) | 
|  | return nil; | 
|  |  | 
|  | // Prime DVTPlatform. | 
|  | NSError* error; | 
|  | Class DVTPlatformClass = FindClassByName(@"DVTPlatform"); | 
|  | if (![DVTPlatformClass loadAllPlatformsReturningError:&error]) { | 
|  | LogError(@"Unable to loadAllPlatformsReturningError. Error: %@", | 
|  | [error localizedDescription]); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | // The path within the developer dir of the private Simulator frameworks. | 
|  | NSString* simulatorFrameworkRelativePath; | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | simulatorFrameworkRelativePath = | 
|  | @"../SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework"; | 
|  | NSString* const kCoreSimulatorRelativePath = | 
|  | @"Library/PrivateFrameworks/CoreSimulator.framework"; | 
|  | NSString* coreSimulatorPath = [developerDir | 
|  | stringByAppendingPathComponent:kCoreSimulatorRelativePath]; | 
|  | NSBundle* coreSimulatorBundle = | 
|  | [NSBundle bundleWithPath:coreSimulatorPath]; | 
|  | if (![coreSimulatorBundle load]) | 
|  | return nil; | 
|  | } else { | 
|  | simulatorFrameworkRelativePath = | 
|  | @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/" | 
|  | @"DVTiPhoneSimulatorRemoteClient.framework"; | 
|  | } | 
|  | NSString* simBundlePath = [developerDir | 
|  | stringByAppendingPathComponent:simulatorFrameworkRelativePath]; | 
|  | NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath]; | 
|  | if (![simBundle load]) | 
|  | return nil; | 
|  | return simBundle; | 
|  | } | 
|  |  | 
|  | // Converts the given app path to an application spec, which requires an | 
|  | // absolute path. | 
|  | DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { | 
|  | Class applicationSpecifierClass = | 
|  | FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier"); | 
|  | if (![appPath isAbsolutePath]) { | 
|  | NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath]; | 
|  | appPath = [cwd stringByAppendingPathComponent:appPath]; | 
|  | } | 
|  | appPath = [appPath stringByStandardizingPath]; | 
|  | NSFileManager* fileManager = [NSFileManager defaultManager]; | 
|  | if (![fileManager fileExistsAtPath:appPath]) { | 
|  | LogError(@"File not found: %@", appPath); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  | return [applicationSpecifierClass specifierWithApplicationPath:appPath]; | 
|  | } | 
|  |  | 
|  | // Returns the system root for the given SDK version. If sdkVersion is nil, the | 
|  | // default system root is returned.  Will return nil if the sdkVersion is not | 
|  | // valid. | 
|  | DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) { | 
|  | Class systemRootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot"); | 
|  | DTiPhoneSimulatorSystemRoot* systemRoot = [systemRootClass defaultRoot]; | 
|  | if (sdkVersion) | 
|  | systemRoot = [systemRootClass rootWithSDKVersion:sdkVersion]; | 
|  |  | 
|  | return systemRoot; | 
|  | } | 
|  |  | 
|  | // Builds a config object for starting the specified app. | 
|  | DTiPhoneSimulatorSessionConfig* BuildSessionConfig( | 
|  | DTiPhoneSimulatorApplicationSpecifier* appSpec, | 
|  | DTiPhoneSimulatorSystemRoot* systemRoot, | 
|  | NSString* stdoutPath, | 
|  | NSString* stderrPath, | 
|  | NSArray* appArgs, | 
|  | NSDictionary* appEnv, | 
|  | NSNumber* deviceFamily, | 
|  | NSString* deviceName) { | 
|  | Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig"); | 
|  | DTiPhoneSimulatorSessionConfig* sessionConfig = | 
|  | [[[sessionConfigClass alloc] init] autorelease]; | 
|  | sessionConfig.applicationToSimulateOnStart = appSpec; | 
|  | sessionConfig.simulatedSystemRoot = systemRoot; | 
|  | sessionConfig.localizedClientName = @"chromium"; | 
|  | sessionConfig.simulatedApplicationStdErrPath = stderrPath; | 
|  | sessionConfig.simulatedApplicationStdOutPath = stdoutPath; | 
|  | sessionConfig.simulatedApplicationLaunchArgs = appArgs; | 
|  | sessionConfig.simulatedApplicationLaunchEnvironment = appEnv; | 
|  | sessionConfig.simulatedDeviceInfoName = deviceName; | 
|  | sessionConfig.simulatedDeviceFamily = deviceFamily; | 
|  |  | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | Class simDeviceTypeClass = FindClassByName(@"SimDeviceType"); | 
|  | id simDeviceType = | 
|  | [simDeviceTypeClass supportedDeviceTypesByName][deviceName]; | 
|  | Class simRuntimeClass = FindClassByName(@"SimRuntime"); | 
|  | NSString* identifier = systemRoot.runtime.identifier; | 
|  | id simRuntime = [simRuntimeClass supportedRuntimesByIdentifier][identifier]; | 
|  |  | 
|  | // Attempt to use an existing device, but create one if a suitable match | 
|  | // can't be found. For example, if the simulator is running with a | 
|  | // non-default home directory (e.g. via iossim's -u command line arg) then | 
|  | // there won't be any devices so one will have to be created. | 
|  | Class simDeviceSetClass = FindClassByName(@"SimDeviceSet"); | 
|  | id deviceSet = | 
|  | [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]]; | 
|  | id simDevice = nil; | 
|  | for (id device in [deviceSet availableDevices]) { | 
|  | if ([device runtime] == simRuntime && | 
|  | [device deviceType] == simDeviceType) { | 
|  | simDevice = device; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!simDevice) { | 
|  | NSError* error = nil; | 
|  | // n.b. only the device name is necessary because the iOS Simulator menu | 
|  | // already splits devices by runtime version. | 
|  | NSString* name = [NSString stringWithFormat:@"iossim - %@ ", deviceName]; | 
|  | simDevice = [deviceSet createDeviceWithType:simDeviceType | 
|  | runtime:simRuntime | 
|  | name:name | 
|  | error:&error]; | 
|  | if (error) { | 
|  | LogError(@"Failed to create device: %@", error); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  | } | 
|  | sessionConfig.device = simDevice; | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  | } | 
|  | return sessionConfig; | 
|  | } | 
|  |  | 
|  | // Builds a simulator session that will use the given delegate. | 
|  | DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) { | 
|  | Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession"); | 
|  | DTiPhoneSimulatorSession* session = | 
|  | [[[sessionClass alloc] init] autorelease]; | 
|  | session.delegate = delegate; | 
|  | return session; | 
|  | } | 
|  |  | 
|  | // Creates a temporary directory with a unique name based on the provided | 
|  | // template. The template should not contain any path separators and be suffixed | 
|  | // with X's, which will be substituted with a unique alphanumeric string (see | 
|  | // 'man mkdtemp' for details). The directory will be created as a subdirectory | 
|  | // of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX', | 
|  | // this method would return something like '/path/to/tempdir/test-3n2'. | 
|  | // | 
|  | // Returns the absolute path of the newly-created directory, or nill if unable | 
|  | // to create a unique directory. | 
|  | NSString* CreateTempDirectory(NSString* dirNameTemplate) { | 
|  | NSString* fullPathTemplate = | 
|  | [NSTemporaryDirectory() stringByAppendingPathComponent:dirNameTemplate]; | 
|  | char* fullPath = mkdtemp(const_cast<char*>([fullPathTemplate UTF8String])); | 
|  | if (fullPath == NULL) | 
|  | return nil; | 
|  |  | 
|  | return [NSString stringWithUTF8String:fullPath]; | 
|  | } | 
|  |  | 
|  | // Creates the necessary directory structure under the given user home directory | 
|  | // path. | 
|  | // Returns YES if successful, NO if unable to create the directories. | 
|  | BOOL CreateHomeDirSubDirs(NSString* userHomePath) { | 
|  | NSFileManager* fileManager = [NSFileManager defaultManager]; | 
|  |  | 
|  | // Create user home and subdirectories. | 
|  | NSArray* subDirsToCreate = [NSArray arrayWithObjects: | 
|  | @"Documents", | 
|  | @"Library/Caches", | 
|  | @"Library/Preferences", | 
|  | nil]; | 
|  | for (NSString* subDir in subDirsToCreate) { | 
|  | NSString* path = [userHomePath stringByAppendingPathComponent:subDir]; | 
|  | NSError* error; | 
|  | if (![fileManager createDirectoryAtPath:path | 
|  | withIntermediateDirectories:YES | 
|  | attributes:nil | 
|  | error:&error]) { | 
|  | LogError(@"Unable to create directory: %@. Error: %@", | 
|  | path, [error localizedDescription]); | 
|  | return NO; | 
|  | } | 
|  | } | 
|  |  | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | // Creates the necessary directory structure under the given user home directory | 
|  | // path, then sets the path in the appropriate environment variable. | 
|  | // Returns YES if successful, NO if unable to create or initialize the given | 
|  | // directory. | 
|  | BOOL InitializeSimulatorUserHome(NSString* userHomePath) { | 
|  | if (!CreateHomeDirSubDirs(userHomePath)) | 
|  | return NO; | 
|  |  | 
|  | // Update the environment to use the specified directory as the user home | 
|  | // directory. | 
|  | // Note: the third param of setenv specifies whether or not to overwrite the | 
|  | // variable's value if it has already been set. | 
|  | if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) || | 
|  | (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) { | 
|  | LogError(@"Unable to set environment variables for home directory."); | 
|  | return NO; | 
|  | } | 
|  |  | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | // Performs a case-insensitive search to see if |stringToSearch| begins with | 
|  | // |prefixToFind|. Returns true if a match is found. | 
|  | BOOL CaseInsensitivePrefixSearch(NSString* stringToSearch, | 
|  | NSString* prefixToFind) { | 
|  | NSStringCompareOptions options = (NSAnchoredSearch | NSCaseInsensitiveSearch); | 
|  | NSRange range = [stringToSearch rangeOfString:prefixToFind | 
|  | options:options]; | 
|  | return range.location != NSNotFound; | 
|  | } | 
|  |  | 
|  | // Prints the usage information to stderr. | 
|  | void PrintUsage() { | 
|  | fprintf(stderr, "Usage: iossim [-d device] [-s sdkVersion] [-u homeDir] " | 
|  | "[-e envKey=value]* [-t startupTimeout] <appPath> [<appArgs>]\n" | 
|  | "  where <appPath> is the path to the .app directory and appArgs are any" | 
|  | " arguments to send the simulated app.\n" | 
|  | "\n" | 
|  | "Options:\n" | 
|  | "  -d  Specifies the device (must be one of the values from the iOS" | 
|  | " Simulator's Hardware -> Device menu. Defaults to 'iPhone'.\n" | 
|  | "  -s  Specifies the SDK version to use (e.g '4.3')." | 
|  | " Will use system default if not specified.\n" | 
|  | "  -u  Specifies a user home directory for the simulator." | 
|  | " Will create a new directory if not specified.\n" | 
|  | "  -e  Specifies an environment key=value pair that will be" | 
|  | " set in the simulated application's environment.\n" | 
|  | "  -t  Specifies the session startup timeout (in seconds)." | 
|  | " Defaults to %d.\n" | 
|  | "  -l  List supported devices and iOS versions.\n", | 
|  | static_cast<int>(kDefaultSessionStartTimeoutSeconds)); | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | void EnsureSupportForCurrentXcodeVersion() { | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | #if !IOSSIM_USE_XCODE_6 | 
|  | LogError(@"Running on Xcode 6, but Xcode 6 support was not compiled in."); | 
|  | exit(kExitUnsupportedXcodeVersion); | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  | } | 
|  | } | 
|  |  | 
|  | int main(int argc, char* const argv[]) { | 
|  | NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; | 
|  |  | 
|  | EnsureSupportForCurrentXcodeVersion(); | 
|  |  | 
|  | // basename() may modify the passed in string and it returns a pointer to an | 
|  | // internal buffer. Give it a copy to modify, and copy what it returns. | 
|  | char* worker = strdup(argv[0]); | 
|  | char* toolName = basename(worker); | 
|  | if (toolName != NULL) { | 
|  | toolName = strdup(toolName); | 
|  | if (toolName != NULL) | 
|  | gToolName = toolName; | 
|  | } | 
|  | if (worker != NULL) | 
|  | free(worker); | 
|  |  | 
|  | NSString* appPath = nil; | 
|  | NSString* appName = nil; | 
|  | NSString* sdkVersion = nil; | 
|  | NSString* deviceName = | 
|  | IsRunningWithXcode6OrLater() ? @"iPhone 5s" : @"iPhone"; | 
|  | NSString* simHomePath = nil; | 
|  | NSMutableArray* appArgs = [NSMutableArray array]; | 
|  | NSMutableDictionary* appEnv = [NSMutableDictionary dictionary]; | 
|  | NSTimeInterval sessionStartTimeout = kDefaultSessionStartTimeoutSeconds; | 
|  |  | 
|  | NSString* developerDir = FindDeveloperDir(); | 
|  | if (!developerDir) { | 
|  | LogError(@"Unable to find developer directory."); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  |  | 
|  | NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir); | 
|  | if (!simulatorFramework) { | 
|  | LogError(@"Failed to load the Simulator Framework."); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  |  | 
|  | // Parse the optional arguments | 
|  | int c; | 
|  | while ((c = getopt(argc, argv, "hs:d:u:e:t:l")) != -1) { | 
|  | switch (c) { | 
|  | case 's': | 
|  | sdkVersion = [NSString stringWithUTF8String:optarg]; | 
|  | break; | 
|  | case 'd': | 
|  | deviceName = [NSString stringWithUTF8String:optarg]; | 
|  | break; | 
|  | case 'u': | 
|  | simHomePath = [[NSFileManager defaultManager] | 
|  | stringWithFileSystemRepresentation:optarg length:strlen(optarg)]; | 
|  | break; | 
|  | case 'e': { | 
|  | NSString* envLine = [NSString stringWithUTF8String:optarg]; | 
|  | NSRange range = [envLine rangeOfString:@"="]; | 
|  | if (range.location == NSNotFound) { | 
|  | LogError(@"Invalid key=value argument for -e."); | 
|  | PrintUsage(); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  | NSString* key = [envLine substringToIndex:range.location]; | 
|  | NSString* value = [envLine substringFromIndex:(range.location + 1)]; | 
|  | [appEnv setObject:value forKey:key]; | 
|  | } | 
|  | break; | 
|  | case 't': { | 
|  | int timeout = atoi(optarg); | 
|  | if (timeout > 0) { | 
|  | sessionStartTimeout = static_cast<NSTimeInterval>(timeout); | 
|  | } else { | 
|  | LogError(@"Invalid startup timeout (%s).", optarg); | 
|  | PrintUsage(); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case 'l': | 
|  | PrintSupportedDevices(); | 
|  | exit(kExitSuccess); | 
|  | break; | 
|  | case 'h': | 
|  | PrintUsage(); | 
|  | exit(kExitSuccess); | 
|  | default: | 
|  | PrintUsage(); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  | } | 
|  |  | 
|  | // There should be at least one arg left, specifying the app path. Any | 
|  | // additional args are passed as arguments to the app. | 
|  | if (optind < argc) { | 
|  | appPath = [[NSFileManager defaultManager] | 
|  | stringWithFileSystemRepresentation:argv[optind] | 
|  | length:strlen(argv[optind])]; | 
|  | appName = [appPath lastPathComponent]; | 
|  | while (++optind < argc) { | 
|  | [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]]; | 
|  | } | 
|  | } else { | 
|  | LogError(@"Unable to parse command line arguments."); | 
|  | PrintUsage(); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  |  | 
|  | // Make sure the app path provided is legit. | 
|  | DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath); | 
|  | if (!appSpec) { | 
|  | LogError(@"Invalid app path: %@", appPath); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  |  | 
|  | // Make sure the SDK path provided is legit (or nil). | 
|  | DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion); | 
|  | if (!systemRoot) { | 
|  | LogError(@"Invalid SDK version: %@", sdkVersion); | 
|  | PrintSupportedDevices(); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  |  | 
|  | // Get the paths for stdout and stderr so the simulated app's output will show | 
|  | // up in the caller's stdout/stderr. | 
|  | NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX"); | 
|  | NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"]; | 
|  |  | 
|  | // Determine the deviceFamily based on the deviceName | 
|  | NSNumber* deviceFamily = nil; | 
|  | if (IsRunningWithXcode6OrLater()) { | 
|  | #if defined(IOSSIM_USE_XCODE_6) | 
|  | Class simDeviceTypeClass = FindClassByName(@"SimDeviceType"); | 
|  | if ([simDeviceTypeClass supportedDeviceTypesByName][deviceName] == nil) { | 
|  | LogError(@"Invalid device name: %@.", deviceName); | 
|  | PrintSupportedDevices(); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  | #endif  // IOSSIM_USE_XCODE_6 | 
|  | } else { | 
|  | if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) { | 
|  | deviceFamily = [NSNumber numberWithInt:kIPhoneFamily]; | 
|  | } else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) { | 
|  | deviceFamily = [NSNumber numberWithInt:kIPadFamily]; | 
|  | } | 
|  | else { | 
|  | LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'", | 
|  | deviceName); | 
|  | exit(kExitInvalidArguments); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Set up the user home directory for the simulator only if a non-default | 
|  | // value was specified. | 
|  | if (simHomePath) { | 
|  | if (!InitializeSimulatorUserHome(simHomePath)) { | 
|  | LogError(@"Unable to initialize home directory for simulator: %@", | 
|  | simHomePath); | 
|  | exit(kExitInitializationFailure); | 
|  | } | 
|  | } else { | 
|  | simHomePath = NSHomeDirectory(); | 
|  | } | 
|  |  | 
|  | // Create the config and simulator session. | 
|  | DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, | 
|  | systemRoot, | 
|  | stdioPath, | 
|  | stdioPath, | 
|  | appArgs, | 
|  | appEnv, | 
|  | deviceFamily, | 
|  | deviceName); | 
|  | SimulatorDelegate* delegate = | 
|  | [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath | 
|  | developerDir:developerDir | 
|  | simulatorHome:simHomePath] autorelease]; | 
|  | DTiPhoneSimulatorSession* session = BuildSession(delegate); | 
|  |  | 
|  | // Start the simulator session. | 
|  | NSError* error; | 
|  | BOOL started = [session requestStartWithConfig:config | 
|  | timeout:sessionStartTimeout | 
|  | error:&error]; | 
|  |  | 
|  | // Spin the runtime indefinitely. When the delegate gets the message that the | 
|  | // app has quit it will exit this program. | 
|  | if (started) { | 
|  | [[NSRunLoop mainRunLoop] run]; | 
|  | } else { | 
|  | LogError(@"Simulator failed request to start:  \"%@\" (%@:%ld)", | 
|  | [error localizedDescription], | 
|  | [error domain], static_cast<long int>([error code])); | 
|  | } | 
|  |  | 
|  | // Note that this code is only executed if the simulator fails to start | 
|  | // because once the main run loop is started, only the delegate calling | 
|  | // exit() will end the program. | 
|  | [pool drain]; | 
|  | return kExitFailure; | 
|  | } |