| Index: testing/iossim/iossim.mm
|
| diff --git a/testing/iossim/iossim.mm b/testing/iossim/iossim.mm
|
| index 22b97a6fde8186b706610d223c110a599bc13d04..09473b732ca1587c3d042dd3ad5730e32bf82b95 100644
|
| --- a/testing/iossim/iossim.mm
|
| +++ b/testing/iossim/iossim.mm
|
| @@ -2,7 +2,6 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
| #import <Foundation/Foundation.h>
|
| -#if defined(__MAC_10_12) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_12
|
| #include <getopt.h>
|
| #include <string>
|
|
|
| @@ -422,871 +421,3 @@ int main(int argc, char* const argv[]) {
|
| KillSimulator();
|
| return kExitSuccess;
|
| }
|
| -#else
|
| -#import <Appkit/Appkit.h>
|
| -#include <asl.h>
|
| -#import <Foundation/Foundation.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.
|
| -
|
| -@class DVTStackBacktrace;
|
| -#import "DVTFoundation.h"
|
| -
|
| -@protocol SimBridge;
|
| -@class DVTSimulatorApplication;
|
| -@class SimDeviceSet;
|
| -@class SimDeviceType;
|
| -@class SimProfilesPathMonitor;
|
| -@class SimRuntime;
|
| -@class SimServiceConnectionManager;
|
| -#import "CoreSimulator.h"
|
| -
|
| -@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";
|
| -
|
| -// 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;
|
| -
|
| -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];
|
| -}
|
| -
|
| -// Prints supported devices and SDKs.
|
| -void PrintSupportedDevices() {
|
| - 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]);
|
| - }
|
| -}
|
| -} // 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_];
|
| - 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];
|
| - }
|
| - 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;
|
| -
|
| - // The iOS Simulator's system logging is sandboxed, so parse the sandboxed
|
| - // system.log file for known errors.
|
| - NSString* path;
|
| - NSString* dataPath = session.sessionConfig.device.dataPath;
|
| - path =
|
| - [dataPath stringByAppendingPathComponent:@"Library/Logs/system.log"];
|
| - 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;
|
| - 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;
|
| - 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");
|
| - Class simRuntimeClass = FindClassByName(@"SimRuntime");
|
| - NSArray* sorted =
|
| - [[simRuntimeClass supportedRuntimes] sortedArrayUsingDescriptors:@[
|
| - [NSSortDescriptor sortDescriptorWithKey:@"version" ascending:YES]
|
| - ]];
|
| - NSString* versionString = [[sorted lastObject] versionString];
|
| - DTiPhoneSimulatorSystemRoot* systemRoot =
|
| - [systemRootClass rootWithSDKVersion:versionString];
|
| - 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;
|
| -
|
| - Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
|
| - id simDeviceType =
|
| - [simDeviceTypeClass supportedDeviceTypesByAlias][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;
|
| - 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;
|
| -}
|
| -
|
| -// 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
|
| -
|
| -int main(int argc, char* const argv[]) {
|
| - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
| -
|
| - // 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 = @"iPhone 5s";
|
| - 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;
|
| - Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
|
| - if ([simDeviceTypeClass supportedDeviceTypesByAlias][deviceName] == nil) {
|
| - LogError(@"Invalid device name: %@.", deviceName);
|
| - PrintSupportedDevices();
|
| - 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;
|
| -}
|
| -
|
| -#endif
|
|
|