OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import <Foundation/Foundation.h> | 5 #import <Foundation/Foundation.h> |
6 #include <asl.h> | 6 #include <asl.h> |
7 #include <libgen.h> | 7 #include <libgen.h> |
8 #include <stdarg.h> | 8 #include <stdarg.h> |
9 #include <stdio.h> | 9 #include <stdio.h> |
10 | 10 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
63 // If this timeout occurs iossim will likely exit with non-zero status; the | 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 | 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). | 65 // session is started (this case is handled in session:didStart:withError). |
66 const NSTimeInterval kSessionStartTimeoutSeconds = 30; | 66 const NSTimeInterval kSessionStartTimeoutSeconds = 30; |
67 | 67 |
68 // While the simulated app is running, its stdout is redirected to a file which | 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 | 69 // is polled by iossim and written to iossim's stdout using the following |
70 // polling interval. | 70 // polling interval. |
71 const NSTimeInterval kOutputPollIntervalSeconds = 0.1; | 71 const NSTimeInterval kOutputPollIntervalSeconds = 0.1; |
72 | 72 |
73 // The path within the developer dir of the private Simulator frameworks. | |
74 NSString * const kSimulatorFrameworkRelativePath = | |
stuartmorgan
2012/07/30 21:12:25
NSString* to match the rest of the file.
TVL
2012/07/30 21:17:28
Done.
| |
75 @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/iPh oneSimulatorRemoteClient.framework"; | |
76 NSString * const kDevToolsFoundationRelativePath = | |
77 @"../OtherFrameworks/DevToolsFoundation.framework"; | |
78 | |
73 const char* gToolName = "iossim"; | 79 const char* gToolName = "iossim"; |
74 | 80 |
75 void LogError(NSString* format, ...) { | 81 void LogError(NSString* format, ...) { |
76 va_list list; | 82 va_list list; |
77 va_start(list, format); | 83 va_start(list, format); |
78 | 84 |
79 NSString* message = | 85 NSString* message = |
80 [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; | 86 [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; |
81 | 87 |
82 fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]); | 88 fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]); |
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
241 if (entryFound) { | 247 if (entryFound) { |
242 LogError(@"Simulated app crashed or exited with non-zero status"); | 248 LogError(@"Simulated app crashed or exited with non-zero status"); |
243 exit(EXIT_FAILURE); | 249 exit(EXIT_FAILURE); |
244 } | 250 } |
245 exit(EXIT_SUCCESS); | 251 exit(EXIT_SUCCESS); |
246 } | 252 } |
247 @end | 253 @end |
248 | 254 |
249 namespace { | 255 namespace { |
250 | 256 |
257 // Finds the developer dir via xcode-select or the DEVELOPER_DIR environment | |
258 // variable. | |
259 NSString* FindDeveloperDir() { | |
260 // Check the env first. | |
261 NSDictionary* env = [[NSProcessInfo processInfo] environment]; | |
262 NSString* developerDir = [env objectForKey:@"DEVELOPER_DIR"]; | |
263 if ([developerDir length] > 0) | |
264 return developerDir; | |
265 | |
266 // Go look for it via xcode-select. | |
267 NSTask* xcodeSelectTask = [[[NSTask alloc] init] autorelease]; | |
268 [xcodeSelectTask setLaunchPath:@"/usr/bin/xcode-select"]; | |
269 [xcodeSelectTask setArguments:[NSArray arrayWithObject:@"-print-path"]]; | |
270 | |
271 NSPipe* outputPipe = [NSPipe pipe]; | |
272 [xcodeSelectTask setStandardOutput:outputPipe]; | |
273 NSFileHandle* outputFile = [outputPipe fileHandleForReading]; | |
274 | |
275 [xcodeSelectTask launch]; | |
276 NSData* outputData = [outputFile readDataToEndOfFile]; | |
277 [xcodeSelectTask terminate]; | |
278 | |
279 NSString* output = | |
280 [[[NSString alloc] initWithData:outputData | |
281 encoding:NSUTF8StringEncoding] autorelease]; | |
282 output = | |
283 [output stringByTrimmingCharactersInSet: | |
stuartmorgan
2012/07/30 21:12:25
Pull this up to the previous line (and then just i
TVL
2012/07/30 21:17:28
Done.
| |
284 [NSCharacterSet whitespaceAndNewlineCharacterSet]]; | |
285 if ([output length] == 0) | |
286 output = nil; | |
287 return output; | |
288 } | |
289 | |
290 // Loads the Simulator framework from the given developer dir. | |
291 NSBundle* LoadSimulatorFramework(NSString* developerDir) { | |
292 // The Simulator framework depends on some of the other Xcode private | |
293 // frameworks; manually load them first so everything can be linked up. | |
294 NSString* devToolsFoundationPath = | |
295 [developerDir | |
stuartmorgan
2012/07/30 21:12:25
Same
TVL
2012/07/30 21:17:28
Done.
| |
296 stringByAppendingPathComponent:kDevToolsFoundationRelativePath]; | |
297 NSBundle* devToolsFoundationBundle = | |
298 [NSBundle bundleWithPath:devToolsFoundationPath]; | |
299 if (![devToolsFoundationBundle load]) | |
300 return nil; | |
301 NSString* simBundlePath = | |
302 [developerDir | |
stuartmorgan
2012/07/30 21:12:25
Same
TVL
2012/07/30 21:17:28
Done.
| |
303 stringByAppendingPathComponent:kSimulatorFrameworkRelativePath]; | |
304 NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath]; | |
305 if (![simBundle load]) | |
306 return nil; | |
307 return simBundle; | |
308 } | |
309 | |
310 // Helper to find a class by name and die if it isn't found. | |
311 Class FindClassByName(NSString* nameOfClass) { | |
312 Class theClass = NSClassFromString(nameOfClass); | |
313 if (!theClass) { | |
314 LogError(@"Failed to find class %@ at runtime.", nameOfClass); | |
315 exit(EXIT_FAILURE); | |
316 } | |
317 return theClass; | |
318 } | |
319 | |
251 // Converts the given app path to an application spec, which requires an | 320 // Converts the given app path to an application spec, which requires an |
252 // absolute path. | 321 // absolute path. |
253 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { | 322 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { |
323 Class applicationSpecifierClass = | |
324 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier"); | |
254 if (![appPath isAbsolutePath]) { | 325 if (![appPath isAbsolutePath]) { |
255 NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath]; | 326 NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath]; |
256 appPath = [cwd stringByAppendingPathComponent:appPath]; | 327 appPath = [cwd stringByAppendingPathComponent:appPath]; |
257 } | 328 } |
258 appPath = [appPath stringByStandardizingPath]; | 329 appPath = [appPath stringByStandardizingPath]; |
259 return [DTiPhoneSimulatorApplicationSpecifier | 330 return [applicationSpecifierClass specifierWithApplicationPath:appPath]; |
260 specifierWithApplicationPath:appPath]; | |
261 } | 331 } |
262 | 332 |
263 // Returns the system root for the given SDK version. If sdkVersion is nil, the | 333 // Returns the system root for the given SDK version. If sdkVersion is nil, the |
264 // default system root is returned. Will return nil if the sdkVersion is not | 334 // default system root is returned. Will return nil if the sdkVersion is not |
265 // valid. | 335 // valid. |
266 DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) { | 336 DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) { |
267 DTiPhoneSimulatorSystemRoot* systemRoot = | 337 Class systemRootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot"); |
268 [DTiPhoneSimulatorSystemRoot defaultRoot]; | 338 DTiPhoneSimulatorSystemRoot* systemRoot = [systemRootClass defaultRoot]; |
269 if (sdkVersion) | 339 if (sdkVersion) |
270 systemRoot = [DTiPhoneSimulatorSystemRoot rootWithSDKVersion:sdkVersion]; | 340 systemRoot = [systemRootClass rootWithSDKVersion:sdkVersion]; |
271 | 341 |
272 return systemRoot; | 342 return systemRoot; |
273 } | 343 } |
274 | 344 |
275 // Builds a config object for starting the specified app. | 345 // Builds a config object for starting the specified app. |
276 DTiPhoneSimulatorSessionConfig* BuildSessionConfig( | 346 DTiPhoneSimulatorSessionConfig* BuildSessionConfig( |
277 DTiPhoneSimulatorApplicationSpecifier* appSpec, | 347 DTiPhoneSimulatorApplicationSpecifier* appSpec, |
278 DTiPhoneSimulatorSystemRoot* systemRoot, | 348 DTiPhoneSimulatorSystemRoot* systemRoot, |
279 NSString* stdoutPath, | 349 NSString* stdoutPath, |
280 NSString* stderrPath, | 350 NSString* stderrPath, |
281 NSArray* appArgs, | 351 NSArray* appArgs, |
282 NSDictionary* appEnv, | 352 NSDictionary* appEnv, |
283 NSNumber* deviceFamily) { | 353 NSNumber* deviceFamily) { |
354 Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig"); | |
284 DTiPhoneSimulatorSessionConfig* sessionConfig = | 355 DTiPhoneSimulatorSessionConfig* sessionConfig = |
285 [[[DTiPhoneSimulatorSessionConfig alloc] init] autorelease]; | 356 [[[sessionConfigClass alloc] init] autorelease]; |
286 sessionConfig.applicationToSimulateOnStart = appSpec; | 357 sessionConfig.applicationToSimulateOnStart = appSpec; |
287 sessionConfig.simulatedSystemRoot = systemRoot; | 358 sessionConfig.simulatedSystemRoot = systemRoot; |
288 sessionConfig.localizedClientName = @"chromium"; | 359 sessionConfig.localizedClientName = @"chromium"; |
289 sessionConfig.simulatedApplicationStdErrPath = stderrPath; | 360 sessionConfig.simulatedApplicationStdErrPath = stderrPath; |
290 sessionConfig.simulatedApplicationStdOutPath = stdoutPath; | 361 sessionConfig.simulatedApplicationStdOutPath = stdoutPath; |
291 sessionConfig.simulatedApplicationLaunchArgs = appArgs; | 362 sessionConfig.simulatedApplicationLaunchArgs = appArgs; |
292 sessionConfig.simulatedApplicationLaunchEnvironment = appEnv; | 363 sessionConfig.simulatedApplicationLaunchEnvironment = appEnv; |
293 sessionConfig.simulatedDeviceFamily = deviceFamily; | 364 sessionConfig.simulatedDeviceFamily = deviceFamily; |
294 return sessionConfig; | 365 return sessionConfig; |
295 } | 366 } |
296 | 367 |
297 // Builds a simulator session that will use the given delegate. | 368 // Builds a simulator session that will use the given delegate. |
298 DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) { | 369 DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) { |
370 Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession"); | |
299 DTiPhoneSimulatorSession* session = | 371 DTiPhoneSimulatorSession* session = |
300 [[[DTiPhoneSimulatorSession alloc] init] autorelease]; | 372 [[[sessionClass alloc] init] autorelease]; |
301 session.delegate = delegate; | 373 session.delegate = delegate; |
302 return session; | 374 return session; |
303 } | 375 } |
304 | 376 |
305 // Creates a temporary directory with a unique name based on the provided | 377 // Creates a temporary directory with a unique name based on the provided |
306 // template. The template should not contain any path separators and be suffixed | 378 // template. The template should not contain any path separators and be suffixed |
307 // with X's, which will be substituted with a unique alphanumeric string (see | 379 // with X's, which will be substituted with a unique alphanumeric string (see |
308 // 'man mkdtemp' for details). The directory will be created as a subdirectory | 380 // 'man mkdtemp' for details). The directory will be created as a subdirectory |
309 // of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX', | 381 // of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX', |
310 // this method would return something like '/path/to/tempdir/test-3n2'. | 382 // this method would return something like '/path/to/tempdir/test-3n2'. |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
385 " Will create a new directory if not specified.\n" | 457 " Will create a new directory if not specified.\n" |
386 " -e Specifies an environment key=value pair that will be" | 458 " -e Specifies an environment key=value pair that will be" |
387 " set in the simulated application's environment.\n"); | 459 " set in the simulated application's environment.\n"); |
388 } | 460 } |
389 | 461 |
390 } // namespace | 462 } // namespace |
391 | 463 |
392 int main(int argc, char* const argv[]) { | 464 int main(int argc, char* const argv[]) { |
393 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; | 465 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
394 | 466 |
395 char* toolName = basename(argv[0]); | 467 // basename() may modify the passed in string and it returns a pointer to an |
396 if (toolName != NULL) | 468 // internal buffer. Give it a copy to modify, and copy what it returns. |
397 gToolName = toolName; | 469 char* worker = strdup(argv[0]); |
470 char* toolName = basename(worker); | |
471 if (toolName != NULL) { | |
472 toolName = strdup(toolName); | |
473 if (toolName != NULL) | |
474 gToolName = toolName; | |
475 } | |
476 if (worker != NULL) | |
477 free(worker); | |
398 | 478 |
399 NSString* appPath = nil; | 479 NSString* appPath = nil; |
400 NSString* appName = nil; | 480 NSString* appName = nil; |
401 NSString* sdkVersion = nil; | 481 NSString* sdkVersion = nil; |
402 NSString* deviceName = @"iPhone"; | 482 NSString* deviceName = @"iPhone"; |
403 NSString* simHomePath = nil; | 483 NSString* simHomePath = nil; |
404 NSMutableArray* appArgs = [NSMutableArray array]; | 484 NSMutableArray* appArgs = [NSMutableArray array]; |
405 NSMutableDictionary* appEnv = [NSMutableDictionary dictionary]; | 485 NSMutableDictionary* appEnv = [NSMutableDictionary dictionary]; |
406 | 486 |
407 // Parse the optional arguments | 487 // Parse the optional arguments |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
449 appName = [appPath lastPathComponent]; | 529 appName = [appPath lastPathComponent]; |
450 while (++optind < argc) { | 530 while (++optind < argc) { |
451 [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]]; | 531 [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]]; |
452 } | 532 } |
453 } else { | 533 } else { |
454 LogError(@"Unable to parse command line arguments."); | 534 LogError(@"Unable to parse command line arguments."); |
455 PrintUsage(); | 535 PrintUsage(); |
456 exit(EXIT_FAILURE); | 536 exit(EXIT_FAILURE); |
457 } | 537 } |
458 | 538 |
539 NSString* developerDir = FindDeveloperDir(); | |
540 if (!developerDir) { | |
541 LogError(@"Unable to find developer directory."); | |
542 exit(EXIT_FAILURE); | |
543 } | |
544 | |
545 NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir); | |
546 if (!simulatorFramework) { | |
547 LogError(@"Failed to load the Simulator Framework."); | |
548 exit(EXIT_FAILURE); | |
549 } | |
550 | |
459 // Make sure the app path provided is legit. | 551 // Make sure the app path provided is legit. |
460 DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath); | 552 DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath); |
461 if (!appSpec) { | 553 if (!appSpec) { |
462 LogError(@"Invalid app path: %@", appPath); | 554 LogError(@"Invalid app path: %@", appPath); |
463 exit(EXIT_FAILURE); | 555 exit(EXIT_FAILURE); |
464 } | 556 } |
465 | 557 |
466 // Make sure the SDK path provided is legit (or nil). | 558 // Make sure the SDK path provided is legit (or nil). |
467 DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion); | 559 DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion); |
468 if (!systemRoot) { | 560 if (!systemRoot) { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
506 | 598 |
507 // Create the config and simulator session. | 599 // Create the config and simulator session. |
508 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, | 600 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, |
509 systemRoot, | 601 systemRoot, |
510 stdioPath, | 602 stdioPath, |
511 stdioPath, | 603 stdioPath, |
512 appArgs, | 604 appArgs, |
513 appEnv, | 605 appEnv, |
514 deviceFamily); | 606 deviceFamily); |
515 SimulatorDelegate* delegate = | 607 SimulatorDelegate* delegate = |
516 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath] autorelease]; | 608 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath] autorelease]; |
517 DTiPhoneSimulatorSession* session = BuildSession(delegate); | 609 DTiPhoneSimulatorSession* session = BuildSession(delegate); |
518 | 610 |
519 // Start the simulator session. | 611 // Start the simulator session. |
520 NSError* error; | 612 NSError* error; |
521 BOOL started = [session requestStartWithConfig:config | 613 BOOL started = [session requestStartWithConfig:config |
522 timeout:kSessionStartTimeoutSeconds | 614 timeout:kSessionStartTimeoutSeconds |
523 error:&error]; | 615 error:&error]; |
524 | 616 |
525 // Spin the runtime indefinitely. When the delegate gets the message that the | 617 // Spin the runtime indefinitely. When the delegate gets the message that the |
526 // app has quit it will exit this program. | 618 // app has quit it will exit this program. |
527 if (started) | 619 if (started) |
528 [[NSRunLoop mainRunLoop] run]; | 620 [[NSRunLoop mainRunLoop] run]; |
529 else | 621 else |
530 LogError(@"Simulator failed to start: %@", [error localizedDescription]); | 622 LogError(@"Simulator failed to start: %@", [error localizedDescription]); |
531 | 623 |
532 // Note that this code is only executed if the simulator fails to start | 624 // Note that this code is only executed if the simulator fails to start |
533 // because once the main run loop is started, only the delegate calling | 625 // because once the main run loop is started, only the delegate calling |
534 // exit() will end the program. | 626 // exit() will end the program. |
535 [pool drain]; | 627 [pool drain]; |
536 return EXIT_FAILURE; | 628 return EXIT_FAILURE; |
537 } | 629 } |
OLD | NEW |