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