Commit 7f590bcf authored by lliabraa's avatar lliabraa Committed by Commit bot

Make iossim binary work on Xcode 5 and 6.

The CL changes most compile-time checks to be runtime-checks so that
an iossim binary built agains Xcode 6 will still work against Xcode 5.

BUG=None

Review URL: https://codereview.chromium.org/494583004

Cr-Commit-Position: refs/heads/master@{#291681}
parent 3d6b1ddb
......@@ -25,6 +25,13 @@
'defines': [
'IOSSIM_USE_XCODE_6',
],
'xcode_settings': {
# The CoreSimulator.h file generated by class-dump defines a
# property of type |NSString*| and a setter for the property
# that takes a parameter of type |id|. This type mismatch causes
# a compiler warning, so turn off -Werror.
'GCC_TREAT_WARNINGS_AS_ERRORS': 'NO',
},
'actions': [
{
'action_name': 'generate_dvt_foundation_header',
......
......@@ -101,19 +101,6 @@ const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30;
// polling interval.
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/"
@"DVTiPhoneSimulatorRemoteClient.framework";
#endif // IOSSIM_USE_XCODE_6
NSString* const kDVTFoundationRelativePath =
@"../SharedFrameworks/DVTFoundation.framework";
NSString* const kDevToolsFoundationRelativePath =
......@@ -135,6 +122,7 @@ const int kExitInvalidArguments = 2;
const int kExitInitializationFailure = 3;
const int kExitAppFailedToStart = 4;
const int kExitAppCrashed = 5;
const int kExitUnsupportedXcodeVersion = 6;
void LogError(NSString* format, ...) {
va_list list;
......@@ -172,36 +160,104 @@ Class FindClassByName(NSString* nameOfClass) {
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];
}
// Finds the Xcode version via xcodebuild -version. Output from xcodebuild is
// expected to look like:
// Xcode <version>
// Build version 5B130a
// where <version> is the string returned by this function (e.g. 6.0).
NSString* FindXcodeVersion() {
NSString* output = GetOutputFromTask(@"/usr/bin/xcodebuild",
@[ @"-version" ]);
// Scan past the "Xcode ", then scan the rest of the line into |version|.
NSScanner* scanner = [NSScanner scannerWithString:output];
BOOL valid = [scanner scanString:@"Xcode " intoString:NULL];
NSString* version;
valid =
[scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet]
intoString:&version];
if (!valid) {
LogError(@"Unable to find Xcode version. 'xcodebuild -version' "
@"returned \n%@", output);
return nil;
}
return version;
}
// Returns true if iossim is running with Xcode 6 or later installed on the
// host.
BOOL IsRunningWithXcode6OrLater() {
static NSString* xcodeVersion = FindXcodeVersion();
if (!xcodeVersion) {
return false;
}
NSArray* components = [xcodeVersion componentsSeparatedByString:@"."];
if ([components count] < 1) {
return false;
}
NSInteger majorVersion = [[components objectAtIndex:0] integerValue];
return majorVersion >= 6;
}
// 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 (IsRunningWithXcode6OrLater()) {
#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)
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]);
}
#endif // IOSSIM_USE_XCODE_6
} else {
printf("Supported SDK versions:\n");
Class rootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot");
for (id root in [rootClass knownRoots]) {
printf(" '%s'\n", [[root sdkVersion] UTF8String]);
}
// This is the list of devices supported on Xcode 5.1.x.
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");
}
}
} // namespace
......@@ -260,17 +316,18 @@ void PrintSupportedDevices() {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_];
// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
// (crbug.com/385030).
if (IsRunningWithXcode6OrLater()) {
#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
// 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 // IOSSIM_USE_XCODE_6
}
NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput];
// Copy data to stdout/stderr while the app is running.
while (appRunning_) {
......@@ -419,19 +476,20 @@ void PrintSupportedDevices() {
} 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).
NSString* path;
if (IsRunningWithXcode6OrLater()) {
#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)
NSString* dataPath = session.sessionConfig.device.dataPath;
path =
[dataPath stringByAppendingPathComponent:@"Library/Logs/system.log"];
#endif // IOSSIM_USE_XCODE_6
} else {
NSString* relativePathToSystemLog =
[NSString stringWithFormat:
@"Library/Logs/iOS Simulator/%@/system.log", versionString];
path = [simulatorHome_
stringByAppendingPathComponent:relativePathToSystemLog];
}
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path]) {
NSString* content =
......@@ -481,21 +539,8 @@ NSString* FindDeveloperDir() {
return developerDir;
// Go look for it via xcode-select.
NSTask* xcodeSelectTask = [[[NSTask alloc] init] autorelease];
[xcodeSelectTask setLaunchPath:@"/usr/bin/xcode-select"];
[xcodeSelectTask setArguments:[NSArray arrayWithObject:@"-print-path"]];
NSPipe* outputPipe = [NSPipe pipe];
[xcodeSelectTask setStandardOutput:outputPipe];
NSFileHandle* outputFile = [outputPipe fileHandleForReading];
[xcodeSelectTask launch];
NSData* outputData = [outputFile readDataToEndOfFile];
[xcodeSelectTask terminate];
NSString* output =
[[[NSString alloc] initWithData:outputData
encoding:NSUTF8StringEncoding] autorelease];
NSString* output = GetOutputFromTask(@"/usr/bin/xcode-select",
@[ @"-print-path" ]);
output = [output stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([output length] == 0)
......@@ -530,19 +575,26 @@ NSBundle* LoadSimulatorFramework(NSString* developerDir) {
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)
// The path within the developer dir of the private Simulator frameworks.
NSString* simulatorFrameworkRelativePath;
if (IsRunningWithXcode6OrLater()) {
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;
} else {
simulatorFrameworkRelativePath =
@"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/"
@"DVTiPhoneSimulatorRemoteClient.framework";
}
NSString* simBundlePath = [developerDir
stringByAppendingPathComponent:kSimulatorFrameworkRelativePath];
stringByAppendingPathComponent:simulatorFrameworkRelativePath];
NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath];
if (![simBundle load])
return nil;
......@@ -602,47 +654,47 @@ DTiPhoneSimulatorSessionConfig* BuildSessionConfig(
sessionConfig.simulatedDeviceInfoName = deviceName;
sessionConfig.simulatedDeviceFamily = deviceFamily;
// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
// (crbug.com/385030).
if (IsRunningWithXcode6OrLater()) {
#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;
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);
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 // IOSSIM_USE_XCODE_6
}
sessionConfig.device = simDevice;
#endif
return sessionConfig;
}
......@@ -756,9 +808,20 @@ void PrintUsage() {
}
} // namespace
void EnsureSupportForCurrentXcodeVersion() {
if (IsRunningWithXcode6OrLater()) {
#if !IOSSIM_USE_XCODE_6
LogError(@"Running on Xcode 6, but Xcode 6 support was not compiled in.");
exit(kExitUnsupportedXcodeVersion);
#endif // IOSSIM_USE_XCODE_6
}
}
int main(int argc, char* const argv[]) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
EnsureSupportForCurrentXcodeVersion();
// 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]);
......@@ -774,13 +837,7 @@ 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* deviceName = IsRunningWithXcode6OrLater() ? @"iPhone 5" : @"iPhone";
NSString* simHomePath = nil;
NSMutableArray* appArgs = [NSMutableArray array];
NSMutableDictionary* appEnv = [NSMutableDictionary dictionary];
......@@ -887,27 +944,27 @@ int main(int argc, char* const argv[]) {
// 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 (IsRunningWithXcode6OrLater()) {
#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 {
LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'",
deviceName);
exit(kExitInvalidArguments);
Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
if ([simDeviceTypeClass supportedDeviceTypesByName][deviceName] == nil) {
LogError(@"Invalid device name: %@.", deviceName);
PrintSupportedDevices();
exit(kExitInvalidArguments);
}
#endif // IOSSIM_USE_XCODE_6
} else {
if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) {
deviceFamily = [NSNumber numberWithInt:kIPhoneFamily];
} else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) {
deviceFamily = [NSNumber numberWithInt:kIPadFamily];
}
else {
LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'",
deviceName);
exit(kExitInvalidArguments);
}
}
#endif // !defined(IOSSIM_USE_XCODE_6)
// Set up the user home directory for the simulator only if a non-default
// value was specified.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment