const NSTimeInterval kOutputPollIntervalSeconds = 0.1;
// The path within the developer dir of the private Simulator frameworks.
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
#if defined(IOSSIM_USE_XCODE_6)
NSString* const kSimulatorFrameworkRelativePath =
@"../SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework";
+NSString* const kCoreSimulatorRelativePath =
+ @"Library/PrivateFrameworks/CoreSimulator.framework";
#else
NSString* const kSimulatorFrameworkRelativePath =
@"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/"
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;
+}
+
+// Prints supported devices and SDKs.
+void PrintSupportedDevices() {
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ printf("Supported device/SDK combinations:\n");
+ Class simDeviceSetClass = FindClassByName(@"SimDeviceSet");
+ id deviceSet =
+ [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]];
+ for (id simDevice in [deviceSet availableDevices]) {
+ NSString* deviceInfo =
+ [NSString stringWithFormat:@" -d '%@' -s '%@'\n",
+ [simDevice name], [[simDevice runtime] versionString]];
+ printf("%s", [deviceInfo UTF8String]);
+ }
+#else
+ printf("Supported SDK versions:\n");
+ Class rootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot");
+ for (id root in [rootClass knownRoots]) {
+ printf(" '%s'\n", [[root sdkVersion] UTF8String]);
+ }
+ printf("Supported devices:\n");
+ printf(" 'iPhone'\n");
+ printf(" 'iPhone Retina (3.5-inch)'\n");
+ printf(" 'iPhone Retina (4-inch)'\n");
+ printf(" 'iPhone Retina (4-inch 64-bit)'\n");
+ printf(" 'iPad'\n");
+ printf(" 'iPad Retina'\n");
+ printf(" 'iPad Retina (64-bit)'\n");
+#endif // defined(IOSSIM_USE_XCODE_6)
+}
} // namespace
// A delegate that is called when the simulated app is started or ended in the
// 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)tailOutput {
+- (void)tailOutputForSession:(DTiPhoneSimulatorSession*)session {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
- // Copy data to stdout/stderr while the app is running.
NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_];
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ // With iOS 8 simulators on Xcode 6, the app output is relative to the
+ // simulator's data directory.
+ if ([session.sessionConfig.simulatedSystemRoot.sdkVersion isEqual:@"8.0"]) {
+ NSString* dataPath = session.sessionConfig.device.dataPath;
+ NSString* appOutput = [dataPath stringByAppendingPathComponent:stdioPath_];
+ simio = [NSFileHandle fileHandleForReadingAtPath:appOutput];
+ }
+#endif
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]];
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:stdioPath_]) {
appRunning_ = NO;
- [self tailOutput];
+ [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
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(tailOutput)
- object:nil];
+ outputThread_ =
+ [[NSThread alloc] initWithTarget:self
+ selector:@selector(tailOutputForSession:)
+ object:session];
[outputThread_ start];
}
} else {
// Otherwise, the iOS Simulator's system logging is sandboxed, so parse the
// sandboxed system.log file for known errors.
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ NSString* dataPath = session.sessionConfig.device.dataPath;
+ NSString* path =
+ [dataPath stringByAppendingPathComponent:@"Library/Logs/system.log"];
+#else
NSString* relativePathToSystemLog =
[NSString stringWithFormat:
@"Library/Logs/iOS Simulator/%@/system.log", versionString];
NSString* path =
[simulatorHome_ stringByAppendingPathComponent:relativePathToSystemLog];
+#endif // defined(IOSSIM_USE_XCODE_6)
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path]) {
NSString* content =
error:NULL];
NSArray* lines = [content componentsSeparatedByCharactersInSet:
[NSCharacterSet newlineCharacterSet]];
+ NSString* simulatedAppPID =
+ [NSString stringWithFormat:@"%d", session.simulatedApplicationPID];
for (NSString* line in lines) {
NSString* const kErrorString = @"Service exited with abnormal code:";
- if ([line rangeOfString:kErrorString].location != NSNotFound) {
+ if ([line rangeOfString:kErrorString].location != NSNotFound &&
+ [line rangeOfString:simulatedAppPID].location != NSNotFound) {
LogWarning(@"Console message: %@", line);
badEntryFound = YES;
break;
// looking at stale logs.
remove([path fileSystemRepresentation]);
} else {
- LogWarning(@"Unable to find sandboxed system log.");
+ LogWarning(@"Unable to find system log at '%@'.", path);
}
}
return output;
}
-// 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;
-}
-
// Loads the Simulator framework from the given developer dir.
NSBundle* LoadSimulatorFramework(NSString* developerDir) {
// The Simulator framework depends on some of the other Xcode private
return nil;
}
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ NSString* coreSimulatorPath = [developerDir
+ stringByAppendingPathComponent:kCoreSimulatorRelativePath];
+ NSBundle* coreSimulatorBundle =
+ [NSBundle bundleWithPath:coreSimulatorPath];
+ if (![coreSimulatorBundle load])
+ return nil;
+#endif // defined(IOSSIM_USE_XCODE_6)
+
NSString* simBundlePath = [developerDir
stringByAppendingPathComponent:kSimulatorFrameworkRelativePath];
NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath];
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];
}
sessionConfig.simulatedApplicationLaunchEnvironment = appEnv;
sessionConfig.simulatedDeviceInfoName = deviceName;
sessionConfig.simulatedDeviceFamily = deviceFamily;
+
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
+ id simDeviceType =
+ [simDeviceTypeClass supportedDeviceTypesByName][deviceName];
+ Class simRuntimeClass = FindClassByName(@"SimRuntime");
+ NSString* identifier = systemRoot.runtime.identifier;
+ id simRuntime = [simRuntimeClass supportedRuntimesByIdentifier][identifier];
+
+ // Attempt to use an existing device, but create one if a suitable match can't
+ // be found. For example, if the simulator is running with a non-default home
+ // directory (e.g. via iossim's -u command line arg) then there won't be any
+ // devices so one will have to be created.
+ Class simDeviceSetClass = FindClassByName(@"SimDeviceSet");
+ id deviceSet =
+ [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]];
+ id simDevice = nil;
+ for (id device in [deviceSet availableDevices]) {
+ if ([device runtime] == simRuntime &&
+ [device deviceType] == simDeviceType) {
+ simDevice = device;
+ break;
+ }
+ }
+ if (!simDevice) {
+ NSError* error = nil;
+ // n.b. only the device name is necessary because the iOS Simulator menu
+ // already splits devices by runtime version.
+ NSString* name = [NSString stringWithFormat:@"iossim - %@ ", deviceName];
+ simDevice = [deviceSet createDeviceWithType:simDeviceType
+ runtime:simRuntime
+ name:name
+ error:&error];
+ if (error) {
+ LogError(@"Failed to create device: %@", error);
+ exit(kExitInitializationFailure);
+ }
+ }
+ sessionConfig.device = simDevice;
+#endif
return sessionConfig;
}
" -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",
+ " Defaults to %d.\n"
+ " -l List supported devices and iOS versions.\n",
static_cast<int>(kDefaultSessionStartTimeoutSeconds));
}
-
} // namespace
int main(int argc, char* const argv[]) {
NSString* appPath = nil;
NSString* appName = nil;
NSString* sdkVersion = nil;
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ NSString* deviceName = @"iPhone 5";
+#else
NSString* deviceName = @"iPhone";
+#endif
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:")) != -1) {
+ while ((c = getopt(argc, argv, "hs:d:u:e:t:l")) != -1) {
switch (c) {
case 's':
sdkVersion = [NSString stringWithUTF8String:optarg];
}
}
break;
+ case 'l':
+ PrintSupportedDevices();
+ exit(kExitSuccess);
+ break;
case 'h':
PrintUsage();
exit(kExitSuccess);
exit(kExitInvalidArguments);
}
- 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);
- }
-
// Make sure the app path provided is legit.
DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath);
if (!appSpec) {
DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion);
if (!systemRoot) {
LogError(@"Invalid SDK version: %@", sdkVersion);
+ PrintSupportedDevices();
exit(kExitInitializationFailure);
}
// Determine the deviceFamily based on the deviceName
NSNumber* deviceFamily = nil;
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+ Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
+ if ([simDeviceTypeClass supportedDeviceTypesByName][deviceName] == nil) {
+ LogError(@"Invalid device name: %@.", deviceName);
+ PrintSupportedDevices();
+ exit(kExitInvalidArguments);
+ }
+#else
if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) {
deviceFamily = [NSNumber numberWithInt:kIPhoneFamily];
} else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) {
deviceFamily = [NSNumber numberWithInt:kIPadFamily];
- } else {
+ }
+ else {
LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'",
deviceName);
exit(kExitInvalidArguments);
}
-
- // Set up the user home directory for the simulator
- if (!simHomePath) {
- NSString* dirNameTemplate =
- [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName];
- simHomePath = CreateTempDirectory(dirNameTemplate);
- if (!simHomePath) {
- LogError(@"Unable to create unique directory for template %@",
- dirNameTemplate);
+#endif // !defined(IOSSIM_USE_XCODE_6)
+
+ // 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);
}
- }
-
- if (!InitializeSimulatorUserHome(simHomePath)) {
- LogError(@"Unable to initialize home directory for simulator: %@",
- simHomePath);
- exit(kExitInitializationFailure);
+ } else {
+ simHomePath = NSHomeDirectory();
}
// Create the config and simulator session.