1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import <Foundation/Foundation.h>
11 // An executable (iossim) that runs an app in the iOS Simulator.
12 // Run 'iossim -h' for usage information.
14 // For best results, the iOS Simulator application should not be running when
17 // Headers for the iPhoneSimulatorRemoteClient framework used in this tool are
18 // generated by class-dump, via GYP.
19 // (class-dump is available at http://www.codethecode.com/projects/class-dump/)
21 // However, there are some forward declarations required to get things to
22 // compile. Also, the DTiPhoneSimulatorSessionDelegate protocol is referenced
23 // by the iPhoneSimulatorRemoteClient framework, but not defined in the object
24 // file, so it must be defined here before importing the generated
25 // iPhoneSimulatorRemoteClient.h file.
27 @class DTiPhoneSimulatorApplicationSpecifier;
28 @class DTiPhoneSimulatorSession;
29 @class DTiPhoneSimulatorSessionConfig;
30 @class DTiPhoneSimulatorSystemRoot;
32 @protocol DTiPhoneSimulatorSessionDelegate
33 - (void)session:(DTiPhoneSimulatorSession*)session
34 didEndWithError:(NSError*)error;
35 - (void)session:(DTiPhoneSimulatorSession*)session
36 didStart:(BOOL)started
37 withError:(NSError*)error;
40 #import "iPhoneSimulatorRemoteClient.h"
42 // An undocumented system log key included in messages from launchd. The value
43 // is the PID of the process the message is about (as opposed to launchd's PID).
44 #define ASL_KEY_REF_PID "RefPID"
48 // Name of environment variables that control the user's home directory in the
50 const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME";
51 const char* const kHomeEnvVariable = "HOME";
53 // Device family codes for iPhone and iPad.
54 const int kIPhoneFamily = 1;
55 const int kIPadFamily = 2;
57 // Max number of seconds to wait for the simulator session to start.
58 // This timeout must allow time to start up iOS Simulator, install the app
59 // and perform any other black magic that is encoded in the
60 // iPhoneSimulatorRemoteClient framework to kick things off. Normal start up
61 // time is only a couple seconds but machine load, disk caches, etc., can all
62 // affect startup time in the wild so the timeout needs to be fairly generous.
63 // If this timeout occurs iossim will likely exit with non-zero status; the
64 // exception being if the app is invoked and completes execution before the
65 // session is started (this case is handled in session:didStart:withError).
66 const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30;
68 // While the simulated app is running, its stdout is redirected to a file which
69 // is polled by iossim and written to iossim's stdout using the following
71 const NSTimeInterval kOutputPollIntervalSeconds = 0.1;
73 // The path within the developer dir of the private Simulator frameworks.
74 NSString* const kSimulatorFrameworkRelativePath =
75 @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/"
76 @"iPhoneSimulatorRemoteClient.framework";
77 NSString* const kDevToolsFoundationRelativePath =
78 @"../OtherFrameworks/DevToolsFoundation.framework";
79 NSString* const kSimulatorRelativePath =
80 @"Platforms/iPhoneSimulator.platform/Developer/Applications/"
81 @"iPhone Simulator.app";
83 // Simulator Error String Key. This can be found by looking in the Simulator's
84 // Localizable.strings files.
85 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit.";
87 const char* gToolName = "iossim";
90 const int kExitSuccess = EXIT_SUCCESS;
91 const int kExitFailure = EXIT_FAILURE;
92 const int kExitInvalidArguments = 2;
93 const int kExitInitializationFailure = 3;
94 const int kExitAppFailedToStart = 4;
95 const int kExitAppCrashed = 5;
97 void LogError(NSString* format, ...) {
99 va_start(list, format);
102 [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
104 fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]);
110 void LogWarning(NSString* format, ...) {
112 va_start(list, format);
115 [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
117 fprintf(stderr, "%s: WARNING: %s\n", gToolName, [message UTF8String]);
125 // A delegate that is called when the simulated app is started or ended in the
127 @interface SimulatorDelegate : NSObject <DTiPhoneSimulatorSessionDelegate> {
129 NSString* stdioPath_;
130 NSString* developerDir_;
131 NSString* simulatorHome_;
132 NSThread* outputThread_;
133 NSBundle* simulatorBundle_;
138 // An implementation that copies the simulated app's stdio to stdout of this
139 // executable. While it would be nice to get stdout and stderr independently
140 // from iOS Simulator, issues like I/O buffering and interleaved output
141 // between iOS Simulator and the app would cause iossim to display things out
142 // of order here. Printing all output to a single file keeps the order correct.
143 // Instances of this classe should be initialized with the location of the
144 // simulated app's output file. When the simulated app starts, a thread is
145 // started which handles copying data from the simulated app's output file to
146 // the stdout of this executable.
147 @implementation SimulatorDelegate
149 // Specifies the file locations of the simulated app's stdout and stderr.
150 - (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath
151 developerDir:(NSString*)developerDir
152 simulatorHome:(NSString*)simulatorHome {
155 stdioPath_ = [stdioPath copy];
156 developerDir_ = [developerDir copy];
157 simulatorHome_ = [simulatorHome copy];
164 [stdioPath_ release];
165 [developerDir_ release];
166 [simulatorBundle_ release];
170 // Reads data from the simulated app's output and writes it to stdout. This
171 // method blocks, so it should be called in a separate thread. The iOS
172 // Simulator takes a file path for the simulated app's stdout and stderr, but
173 // this path isn't always available (e.g. when the stdout is Xcode's build
174 // window). As a workaround, iossim creates a temp file to hold output, which
175 // this method reads and copies to stdout.
177 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
179 // Copy data to stdout/stderr while the app is running.
180 NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_];
181 NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput];
182 while (appRunning_) {
183 NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init];
184 [standardOutput writeData:[simio readDataToEndOfFile]];
185 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds];
189 // Once the app is no longer running, copy any data that was written during
190 // the last sleep cycle.
191 [standardOutput writeData:[simio readDataToEndOfFile]];
196 // Fetches a localized error string from the Simulator.
197 - (NSString *)localizedSimulatorErrorString:(NSString*)stringKey {
198 // Lazy load of the simulator bundle.
199 if (simulatorBundle_ == nil) {
200 NSString* simulatorPath = [developerDir_
201 stringByAppendingPathComponent:kSimulatorRelativePath];
202 simulatorBundle_ = [NSBundle bundleWithPath:simulatorPath];
204 NSString *localizedStr =
205 [simulatorBundle_ localizedStringForKey:stringKey
208 if ([localizedStr length])
210 // Failed to get a value, follow Cocoa conventions and use the key as the
215 - (void)session:(DTiPhoneSimulatorSession*)session
216 didStart:(BOOL)started
217 withError:(NSError*)error {
219 // If the test executes very quickly (<30ms), the SimulatorDelegate may not
220 // get the initial session:started:withError: message indicating successful
221 // startup of the simulated app. Instead the delegate will get a
222 // session:started:withError: message after the timeout has elapsed. To
223 // account for this case, check if the simulated app's stdio file was
224 // ever created and if it exists dump it to stdout and return success.
225 NSFileManager* fileManager = [NSFileManager defaultManager];
226 if ([fileManager fileExistsAtPath:stdioPath_]) {
229 // Note that exiting in this state leaves a process running
230 // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will
231 // prevent future simulator sessions from being started for 30 seconds
232 // unless the iOS Simulator application is killed altogether.
233 [self session:session didEndWithError:nil];
235 // session:didEndWithError should not return (because it exits) so
236 // the execution path should never get here.
240 LogError(@"Simulator failed to start: \"%@\" (%@:%ld)",
241 [error localizedDescription],
242 [error domain], static_cast<long int>([error code]));
243 exit(kExitAppFailedToStart);
246 // Start a thread to write contents of outputPath to stdout.
248 outputThread_ = [[NSThread alloc] initWithTarget:self
249 selector:@selector(tailOutput)
251 [outputThread_ start];
254 - (void)session:(DTiPhoneSimulatorSession*)session
255 didEndWithError:(NSError*)error {
257 // Wait for the output thread to finish copying data to stdout.
259 while (![outputThread_ isFinished]) {
260 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds];
262 [outputThread_ release];
267 // There appears to be a race condition where sometimes the simulator
268 // framework will end with an error, but the error is that the simulated
269 // app cleanly shut down; try to trap this error and don't fail the
271 NSString* localizedDescription = [error localizedDescription];
272 NSString* ignorableErrorStr =
273 [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey];
274 if ([ignorableErrorStr isEqual:localizedDescription]) {
275 LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)",
276 localizedDescription, [error domain],
277 static_cast<long int>([error code]));
279 LogError(@"Simulator ended with error: \"%@\" (%@:%ld)",
280 localizedDescription, [error domain],
281 static_cast<long int>([error code]));
286 // Try to determine if the simulated app crashed or quit with a non-zero
287 // status code. iOS Simluator handles things a bit differently depending on
288 // the version, so first determine the iOS version being used.
289 BOOL badEntryFound = NO;
290 NSString* versionString =
291 [[[session sessionConfig] simulatedSystemRoot] sdkVersion];
292 NSInteger majorVersion = [[[versionString componentsSeparatedByString:@"."]
293 objectAtIndex:0] intValue];
294 if (majorVersion <= 6) {
295 // In iOS 6 and before, logging from the simulated apps went to the main
296 // system logs, so use ASL to check if the simulated app exited abnormally
297 // by looking for system log messages from launchd that refer to the
298 // simulated app's PID. Limit query to messages in the last minute since
299 // PIDs are cyclical.
300 aslmsg query = asl_new(ASL_TYPE_QUERY);
301 asl_set_query(query, ASL_KEY_SENDER, "launchd",
302 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING);
303 asl_set_query(query, ASL_KEY_REF_PID,
304 [[[session simulatedApplicationPID] stringValue] UTF8String],
306 asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL);
308 // Log any messages found, and take note of any messages that may indicate
309 // the app crashed or did not exit cleanly.
310 aslresponse response = asl_search(NULL, query);
312 while ((entry = aslresponse_next(response)) != NULL) {
313 const char* message = asl_get(entry, ASL_KEY_MSG);
314 LogWarning(@"Console message: %s", message);
315 // Some messages are harmless, so don't trigger a failure for them.
316 if (strstr(message, "The following job tried to hijack the service"))
321 // Otherwise, the iOS Simulator's system logging is sandboxed, so parse the
322 // sandboxed system.log file for known errors.
323 NSString* relativePathToSystemLog =
324 [NSString stringWithFormat:
325 @"Library/Logs/iOS Simulator/%@/system.log", versionString];
327 [simulatorHome_ stringByAppendingPathComponent:relativePathToSystemLog];
328 NSFileManager* fileManager = [NSFileManager defaultManager];
329 if ([fileManager fileExistsAtPath:path]) {
331 [NSString stringWithContentsOfFile:path
332 encoding:NSUTF8StringEncoding
334 NSArray* lines = [content componentsSeparatedByCharactersInSet:
335 [NSCharacterSet newlineCharacterSet]];
336 for (NSString* line in lines) {
337 NSString* const kErrorString = @"Service exited with abnormal code:";
338 if ([line rangeOfString:kErrorString].location != NSNotFound) {
339 LogWarning(@"Console message: %@", line);
344 // Remove the log file so subsequent invocations of iossim won't be
345 // looking at stale logs.
346 remove([path fileSystemRepresentation]);
348 LogWarning(@"Unable to find sandboxed system log.");
352 // If the query returned any nasty-looking results, iossim should exit with
355 LogError(@"Simulated app crashed or exited with non-zero status");
356 exit(kExitAppCrashed);
364 // Finds the developer dir via xcode-select or the DEVELOPER_DIR environment
366 NSString* FindDeveloperDir() {
367 // Check the env first.
368 NSDictionary* env = [[NSProcessInfo processInfo] environment];
369 NSString* developerDir = [env objectForKey:@"DEVELOPER_DIR"];
370 if ([developerDir length] > 0)
373 // Go look for it via xcode-select.
374 NSTask* xcodeSelectTask = [[[NSTask alloc] init] autorelease];
375 [xcodeSelectTask setLaunchPath:@"/usr/bin/xcode-select"];
376 [xcodeSelectTask setArguments:[NSArray arrayWithObject:@"-print-path"]];
378 NSPipe* outputPipe = [NSPipe pipe];
379 [xcodeSelectTask setStandardOutput:outputPipe];
380 NSFileHandle* outputFile = [outputPipe fileHandleForReading];
382 [xcodeSelectTask launch];
383 NSData* outputData = [outputFile readDataToEndOfFile];
384 [xcodeSelectTask terminate];
387 [[[NSString alloc] initWithData:outputData
388 encoding:NSUTF8StringEncoding] autorelease];
389 output = [output stringByTrimmingCharactersInSet:
390 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
391 if ([output length] == 0)
396 // Loads the Simulator framework from the given developer dir.
397 NSBundle* LoadSimulatorFramework(NSString* developerDir) {
398 // The Simulator framework depends on some of the other Xcode private
399 // frameworks; manually load them first so everything can be linked up.
400 NSString* devToolsFoundationPath = [developerDir
401 stringByAppendingPathComponent:kDevToolsFoundationRelativePath];
402 NSBundle* devToolsFoundationBundle =
403 [NSBundle bundleWithPath:devToolsFoundationPath];
404 if (![devToolsFoundationBundle load])
406 NSString* simBundlePath = [developerDir
407 stringByAppendingPathComponent:kSimulatorFrameworkRelativePath];
408 NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath];
409 if (![simBundle load])
414 // Helper to find a class by name and die if it isn't found.
415 Class FindClassByName(NSString* nameOfClass) {
416 Class theClass = NSClassFromString(nameOfClass);
418 LogError(@"Failed to find class %@ at runtime.", nameOfClass);
419 exit(kExitInitializationFailure);
424 // Converts the given app path to an application spec, which requires an
426 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) {
427 Class applicationSpecifierClass =
428 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier");
429 if (![appPath isAbsolutePath]) {
430 NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath];
431 appPath = [cwd stringByAppendingPathComponent:appPath];
433 appPath = [appPath stringByStandardizingPath];
434 return [applicationSpecifierClass specifierWithApplicationPath:appPath];
437 // Returns the system root for the given SDK version. If sdkVersion is nil, the
438 // default system root is returned. Will return nil if the sdkVersion is not
440 DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) {
441 Class systemRootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot");
442 DTiPhoneSimulatorSystemRoot* systemRoot = [systemRootClass defaultRoot];
444 systemRoot = [systemRootClass rootWithSDKVersion:sdkVersion];
449 // Builds a config object for starting the specified app.
450 DTiPhoneSimulatorSessionConfig* BuildSessionConfig(
451 DTiPhoneSimulatorApplicationSpecifier* appSpec,
452 DTiPhoneSimulatorSystemRoot* systemRoot,
453 NSString* stdoutPath,
454 NSString* stderrPath,
456 NSDictionary* appEnv,
457 NSNumber* deviceFamily) {
458 Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig");
459 DTiPhoneSimulatorSessionConfig* sessionConfig =
460 [[[sessionConfigClass alloc] init] autorelease];
461 sessionConfig.applicationToSimulateOnStart = appSpec;
462 sessionConfig.simulatedSystemRoot = systemRoot;
463 sessionConfig.localizedClientName = @"chromium";
464 sessionConfig.simulatedApplicationStdErrPath = stderrPath;
465 sessionConfig.simulatedApplicationStdOutPath = stdoutPath;
466 sessionConfig.simulatedApplicationLaunchArgs = appArgs;
467 sessionConfig.simulatedApplicationLaunchEnvironment = appEnv;
468 sessionConfig.simulatedDeviceFamily = deviceFamily;
469 return sessionConfig;
472 // Builds a simulator session that will use the given delegate.
473 DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) {
474 Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession");
475 DTiPhoneSimulatorSession* session =
476 [[[sessionClass alloc] init] autorelease];
477 session.delegate = delegate;
481 // Creates a temporary directory with a unique name based on the provided
482 // template. The template should not contain any path separators and be suffixed
483 // with X's, which will be substituted with a unique alphanumeric string (see
484 // 'man mkdtemp' for details). The directory will be created as a subdirectory
485 // of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX',
486 // this method would return something like '/path/to/tempdir/test-3n2'.
488 // Returns the absolute path of the newly-created directory, or nill if unable
489 // to create a unique directory.
490 NSString* CreateTempDirectory(NSString* dirNameTemplate) {
491 NSString* fullPathTemplate =
492 [NSTemporaryDirectory() stringByAppendingPathComponent:dirNameTemplate];
493 char* fullPath = mkdtemp(const_cast<char*>([fullPathTemplate UTF8String]));
494 if (fullPath == NULL)
497 return [NSString stringWithUTF8String:fullPath];
500 // Creates the necessary directory structure under the given user home directory
502 // Returns YES if successful, NO if unable to create the directories.
503 BOOL CreateHomeDirSubDirs(NSString* userHomePath) {
504 NSFileManager* fileManager = [NSFileManager defaultManager];
506 // Create user home and subdirectories.
507 NSArray* subDirsToCreate = [NSArray arrayWithObjects:
510 @"Library/Preferences",
512 for (NSString* subDir in subDirsToCreate) {
513 NSString* path = [userHomePath stringByAppendingPathComponent:subDir];
515 if (![fileManager createDirectoryAtPath:path
516 withIntermediateDirectories:YES
519 LogError(@"Unable to create directory: %@. Error: %@",
520 path, [error localizedDescription]);
528 // Creates the necessary directory structure under the given user home directory
529 // path, then sets the path in the appropriate environment variable.
530 // Returns YES if successful, NO if unable to create or initialize the given
532 BOOL InitializeSimulatorUserHome(NSString* userHomePath, NSString* deviceName) {
533 if (!CreateHomeDirSubDirs(userHomePath))
536 // Set the device to simulate. Note that the iOS Simulator must not be running
537 // for this setting to take effect.
538 CFStringRef iPhoneSimulatorAppID = CFSTR("com.apple.iphonesimulator");
539 CFPreferencesSetAppValue(CFSTR("SimulateDevice"),
541 iPhoneSimulatorAppID);
542 CFPreferencesAppSynchronize(iPhoneSimulatorAppID);
544 // Update the environment to use the specified directory as the user home
546 // Note: the third param of setenv specifies whether or not to overwrite the
547 // variable's value if it has already been set.
548 if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) ||
549 (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) {
550 LogError(@"Unable to set environment variables for home directory.");
557 // Performs a case-insensitive search to see if |stringToSearch| begins with
558 // |prefixToFind|. Returns true if a match is found.
559 BOOL CaseInsensitivePrefixSearch(NSString* stringToSearch,
560 NSString* prefixToFind) {
561 NSStringCompareOptions options = (NSAnchoredSearch | NSCaseInsensitiveSearch);
562 NSRange range = [stringToSearch rangeOfString:prefixToFind
564 return range.location != NSNotFound;
567 // Prints the usage information to stderr.
569 fprintf(stderr, "Usage: iossim [-d device] [-s sdkVersion] [-u homeDir] "
570 "[-e envKey=value]* [-t startupTimeout] <appPath> [<appArgs>]\n"
571 " where <appPath> is the path to the .app directory and appArgs are any"
572 " arguments to send the simulated app.\n"
575 " -d Specifies the device (must be one of the values from the iOS"
576 " Simulator's Hardware -> Device menu. Defaults to 'iPhone'.\n"
577 " -s Specifies the SDK version to use (e.g '4.3')."
578 " Will use system default if not specified.\n"
579 " -u Specifies a user home directory for the simulator."
580 " Will create a new directory if not specified.\n"
581 " -e Specifies an environment key=value pair that will be"
582 " set in the simulated application's environment.\n"
583 " -t Specifies the session startup timeout (in seconds)."
584 " Defaults to %d.\n",
585 static_cast<int>(kDefaultSessionStartTimeoutSeconds));
590 int main(int argc, char* const argv[]) {
591 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
593 // basename() may modify the passed in string and it returns a pointer to an
594 // internal buffer. Give it a copy to modify, and copy what it returns.
595 char* worker = strdup(argv[0]);
596 char* toolName = basename(worker);
597 if (toolName != NULL) {
598 toolName = strdup(toolName);
599 if (toolName != NULL)
600 gToolName = toolName;
605 NSString* appPath = nil;
606 NSString* appName = nil;
607 NSString* sdkVersion = nil;
608 NSString* deviceName = @"iPhone";
609 NSString* simHomePath = nil;
610 NSMutableArray* appArgs = [NSMutableArray array];
611 NSMutableDictionary* appEnv = [NSMutableDictionary dictionary];
612 NSTimeInterval sessionStartTimeout = kDefaultSessionStartTimeoutSeconds;
614 // Parse the optional arguments
616 while ((c = getopt(argc, argv, "hs:d:u:e:t:")) != -1) {
619 sdkVersion = [NSString stringWithUTF8String:optarg];
622 deviceName = [NSString stringWithUTF8String:optarg];
625 simHomePath = [[NSFileManager defaultManager]
626 stringWithFileSystemRepresentation:optarg length:strlen(optarg)];
629 NSString* envLine = [NSString stringWithUTF8String:optarg];
630 NSRange range = [envLine rangeOfString:@"="];
631 if (range.location == NSNotFound) {
632 LogError(@"Invalid key=value argument for -e.");
634 exit(kExitInvalidArguments);
636 NSString* key = [envLine substringToIndex:range.location];
637 NSString* value = [envLine substringFromIndex:(range.location + 1)];
638 [appEnv setObject:value forKey:key];
642 int timeout = atoi(optarg);
644 sessionStartTimeout = static_cast<NSTimeInterval>(timeout);
646 LogError(@"Invalid startup timeout (%s).", optarg);
648 exit(kExitInvalidArguments);
657 exit(kExitInvalidArguments);
661 // There should be at least one arg left, specifying the app path. Any
662 // additional args are passed as arguments to the app.
664 appPath = [[NSFileManager defaultManager]
665 stringWithFileSystemRepresentation:argv[optind]
666 length:strlen(argv[optind])];
667 appName = [appPath lastPathComponent];
668 while (++optind < argc) {
669 [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]];
672 LogError(@"Unable to parse command line arguments.");
674 exit(kExitInvalidArguments);
677 NSString* developerDir = FindDeveloperDir();
679 LogError(@"Unable to find developer directory.");
680 exit(kExitInitializationFailure);
683 NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir);
684 if (!simulatorFramework) {
685 LogError(@"Failed to load the Simulator Framework.");
686 exit(kExitInitializationFailure);
689 // Make sure the app path provided is legit.
690 DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath);
692 LogError(@"Invalid app path: %@", appPath);
693 exit(kExitInitializationFailure);
696 // Make sure the SDK path provided is legit (or nil).
697 DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion);
699 LogError(@"Invalid SDK version: %@", sdkVersion);
700 exit(kExitInitializationFailure);
703 // Get the paths for stdout and stderr so the simulated app's output will show
704 // up in the caller's stdout/stderr.
705 NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX");
706 NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"];
708 // Determine the deviceFamily based on the deviceName
709 NSNumber* deviceFamily = nil;
710 if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) {
711 deviceFamily = [NSNumber numberWithInt:kIPhoneFamily];
712 } else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) {
713 deviceFamily = [NSNumber numberWithInt:kIPadFamily];
715 LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'",
717 exit(kExitInvalidArguments);
720 // Set up the user home directory for the simulator
722 NSString* dirNameTemplate =
723 [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName];
724 simHomePath = CreateTempDirectory(dirNameTemplate);
726 LogError(@"Unable to create unique directory for template %@",
728 exit(kExitInitializationFailure);
731 if (!InitializeSimulatorUserHome(simHomePath, deviceName)) {
732 LogError(@"Unable to initialize home directory for simulator: %@",
734 exit(kExitInitializationFailure);
737 // Create the config and simulator session.
738 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec,
745 SimulatorDelegate* delegate =
746 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath
747 developerDir:developerDir
748 simulatorHome:simHomePath] autorelease];
749 DTiPhoneSimulatorSession* session = BuildSession(delegate);
751 // Start the simulator session.
753 BOOL started = [session requestStartWithConfig:config
754 timeout:sessionStartTimeout
757 // Spin the runtime indefinitely. When the delegate gets the message that the
758 // app has quit it will exit this program.
760 [[NSRunLoop mainRunLoop] run];
762 LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)",
763 [error localizedDescription],
764 [error domain], static_cast<long int>([error code]));
767 // Note that this code is only executed if the simulator fails to start
768 // because once the main run loop is started, only the delegate calling
769 // exit() will end the program.