diff --git a/.gitmodules b/.gitmodules index e1e82e2c2..47332214b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,9 +13,6 @@ [submodule "submodules/io.appium.gappium.sampleapp"] path = submodules/io.appium.gappium.sampleapp url = https://github.com/appium/io.appium.gappium.sampleapp.git -[submodule "submodules/udidetect"] - path = submodules/udidetect - url = https://github.com/vaskas/udidetect.git [submodule "submodules/unlock_apk"] path = submodules/unlock_apk url = https://github.com/appium/unlock_apk.git diff --git a/grunt-helpers.js b/grunt-helpers.js index 3946ed1a7..f6d2d105a 100644 --- a/grunt-helpers.js +++ b/grunt-helpers.js @@ -16,7 +16,7 @@ var _ = require("underscore") , parseXmlString = require('xml2js').parseString , appiumVer = require('./package.json').version , fs = require('fs') - , xcode = require('./lib/devices/ios/xcode.js') + , xcode = require('./lib/future.js').xcode , isWindows = require('appium-support').system.isWindows() , MAX_BUFFER_SIZE = 524288 , SELENDROID_MAX_BUFFER_SIZE = 4 * MAX_BUFFER_SIZE; diff --git a/lib/devices/ios/ios-log.js b/lib/devices/ios/ios-log.js index e1a75efde..9c6c76576 100644 --- a/lib/devices/ios/ios-log.js +++ b/lib/devices/ios/ios-log.js @@ -5,11 +5,13 @@ var spawn = require('win-spawn') , path = require('path') , fs = require('fs') , _ = require('underscore') + , which = require('which') , glob = require('glob') , logger = require('../../server/logger.js').get('appium') , async = require('async') , mkdirp = require('mkdirp') - , touch = require('touch'); + , touch = require('touch') + , xcode = require('../../future.js').xcode; // Date-Utils: Polyfills for the Date object require('date-utils'); @@ -19,7 +21,6 @@ var START_TIMEOUT = 10000; var IosLog = function (opts) { this.udid = opts.udid; this.simUdid = opts.simUdid; - this.xcodeVersion = opts.xcodeVersion; this.showLogs = opts.showLogs; this.proc = null; this.onIosLogStart = null; @@ -32,11 +33,6 @@ var IosLog = function (opts) { this.logsSinceLastRequest = []; this.startTimer = null; - if (this.xcodeVersion === null) { - logger.warn("Xcode version passed into log capture code as null, assuming Xcode 5"); - this.xcodeVersion = '5.0'; - } - }; IosLog.prototype.startCapture = function (cb) { @@ -44,53 +40,75 @@ IosLog.prototype.startCapture = function (cb) { this.onIosLogStart = cb; // Select cmd for log capture if (this.udid) { - var spawnEnv = _.clone(process.env); - var limdDir = path.resolve(__dirname, - "../../../build/deviceconsole"); - spawnEnv.PATH = process.env.PATH + ":" + limdDir; - spawnEnv.DYLD_LIBRARY_PATH = limdDir + ":" + process.env.DYLD_LIBRARY_PATH; - logger.debug("Starting iOS device log capture via deviceconsole"); - // deviceconsole retrieves many old device log lines that came before it was - // started so filter those out until we encounter new log lines. this.loggingModeOn = false; - this.proc = spawn("deviceconsole", ["-u", this.udid], {env: spawnEnv}); - this.finishStartingLogCapture(cb); - } else { - var ver = parseInt(this.xcodeVersion.split(".")[0], 10); - if (ver >= 5) { - var logsPath; - if (ver >= 6) { - logger.debug("Starting iOS 8.* simulator log capture"); - if (_.isUndefined(this.simUdid)) { - return cb(new Error("iOS8 log capture requires a sim udid")); + var spawnEnv = _.clone(process.env); + + logger.debug("Attempting iOS device log capture via libimobiledevice idevicesyslog"); + which("idevicesyslog", function (err) { + if (!err) { + try { + this.proc = spawn("idevicesyslog", {env: spawnEnv}); + } catch (e) { + cb(e); } - logsPath = path.resolve(process.env.HOME, "Library", "Logs", - "CoreSimulator", this.simUdid); } else { - logger.debug("Starting iOS 7.* simulator log capture"); - logsPath = path.resolve(process.env.HOME, "Library", "Logs", - "iOS Simulator", "7.*"); + logger.warn("Could not capture device log using libimobiledevice idevicesyslog. Libimobiledevice probably isn't installed"); + logger.debug("Attempting iOS device log capture via deviceconsole"); + var limdDir = path.resolve(__dirname, + "../../../build/deviceconsole"); + spawnEnv.PATH = process.env.PATH + ":" + limdDir; + spawnEnv.DYLD_LIBRARY_PATH = limdDir + ":" + process.env.DYLD_LIBRARY_PATH; + + // deviceconsole retrieves many old device log lines that came before it was + // started so filter those out until we encounter new log lines. + try { + this.proc = spawn("deviceconsole", ["-u", this.udid], {env: spawnEnv}); + } catch (e) { + cb(e); + } } - var errOnlyCb = function (cb) { return function (err) { cb(err); }; }; // makes waterfall safer - var touchLog = function (cb) { - async.series([ - function (cb) { mkdirp(logsPath, errOnlyCb(cb)); }, - function (cb) { touch(path.resolve(logsPath, "system.log"), errOnlyCb(cb)); }, - function (cb) { - require('fs').appendFile( - path.resolve(logsPath, "system.log"), - 'A new simulator is about to start!', - errOnlyCb(cb)); + + this.finishStartingLogCapture(cb); + }.bind(this)); + + } else { + xcode.getVersion(function (err, xcodeVersion) { + if (err) return cb(err); + + var ver = parseInt(xcodeVersion.split(".")[0], 10); + if (ver >= 5) { + var logsPath; + if (ver >= 6) { + logger.debug("Starting iOS 8.* simulator log capture"); + if (_.isUndefined(this.simUdid)) { + return cb(new Error("iOS8 log capture requires a sim udid")); } - ], function (err) { cb(err); }); - }; - async.waterfall([ - function (cb) { - if (logsPath.indexOf('*') < 0) { - // there is no doubt on system.log location - touchLog(cb); - } else cb(); - }, function (cb) { + logsPath = path.resolve(process.env.HOME, "Library", "Logs", + "CoreSimulator", this.simUdid); + } else { + logger.debug("Starting iOS 7.* simulator log capture"); + logsPath = path.resolve(process.env.HOME, "Library", "Logs", + "iOS Simulator", "7.*"); + } + var errOnlyCb = function (cb) { return function (err) { cb(err); }; }; // makes waterfall safer + + var touchLog = function (cb) { + if (logsPath.indexOf('*') >= 0) { + return cb(); + } + async.series([ + function (cb) { mkdirp(logsPath, errOnlyCb(cb)); }, + function (cb) { touch(path.resolve(logsPath, "system.log"), errOnlyCb(cb)); }, + function (cb) { + fs.appendFile( + path.resolve(logsPath, "system.log"), + 'A new simulator is about to start!', + errOnlyCb(cb)); + } + ], errOnlyCb(cb)); + }; + + var getLogFiles = function (cb) { glob(path.resolve(logsPath, "system.log"), function (err, files) { if (err || files.length < 1) { logger.error("Could not start log capture because no iOS " + @@ -100,7 +118,9 @@ IosLog.prototype.startCapture = function (cb) { } cb(null, files); }); - }, function (files, cb) { + }; + + var tailLogs = function (files, cb) { var lastModifiedLogPath = files[0] , lastModifiedLogTime = fs.statSync(files[0]).mtime; _.each(files, function (file) { @@ -114,21 +134,30 @@ IosLog.prototype.startCapture = function (cb) { // -n 1 is used so that tail returns a line that lets this process know that the // first line returned wasn't from stderr so it can tell the tailing was successful this.finishStartingLogCapture(cb); - }.bind(this) - ], function () { - logger.warn('System log capture failed.'); - // if system log fails this is no big deal - cb(); - }); - } else { - logger.debug("Starting iOS 6.* simulator log capture"); - this.proc = spawn("tail", ["-f", "-n", "1", "/var/log/system.log"]); - this.finishStartingLogCapture(cb); - } + }.bind(this); + + async.waterfall([touchLog, getLogFiles, tailLogs], function (err) { + if (err) { + logger.warn('System log capture failed.'); + } + cb(); + }); + } else { + logger.debug("Starting iOS 6.* simulator log capture"); + this.proc = spawn("tail", ["-f", "-n", "1", "/var/log/system.log"]); + this.finishStartingLogCapture(cb); + } + }.bind(this)); } }; IosLog.prototype.finishStartingLogCapture = function (cb) { + if (!this.proc) { + var msg = "Could not capture device log"; + logger.warn(msg); + return cb(new Error(msg)); + } + this.startTimer = setTimeout(function () { var msg = "Log capture did not start in a reasonable amount of time"; logger.error(msg); diff --git a/lib/devices/ios/ios.js b/lib/devices/ios/ios.js index 12eecaf5e..3bd96e555 100644 --- a/lib/devices/ios/ios.js +++ b/lib/devices/ios/ios.js @@ -4,6 +4,7 @@ var path = require('path') , ncp = require('ncp').ncp , fs = require('fs') , _ = require('underscore') + , which = require('which') , logger = require('../../server/logger.js').get('appium') , exec = require('child_process').exec , spawn = require('child_process').spawn @@ -12,7 +13,7 @@ var path = require('path') , xmlplist = require('plist') , Device = require('../device.js') , Instruments = require('./instruments.js') - , xcode = require('./xcode.js') + , xcode = require('../../future.js').xcode , errors = require('../../server/errors.js') , deviceCommon = require('../common.js') , iOSLog = require('./ios-log.js') @@ -28,7 +29,8 @@ var path = require('path') , CommandProxy = require('./uiauto').CommandProxy , UnknownError = errors.UnknownError , binaryPlist = true - , Args = require("vargs").Constructor; + , Args = require("vargs").Constructor + ; // XML Plist library helper var parseXmlPlistFile = function (plistFilename, cb) { @@ -88,7 +90,6 @@ IOS.prototype.init = function () { , takesScreenshot: true , networkConnectionEnabled: false }; - this.xcodeFolder = null; this.xcodeVersion = null; this.iOSSDKVersion = null; this.iosSimProcess = null; @@ -210,7 +211,7 @@ IOS.prototype.configureApp = function (cb) { } }; -IOS.prototype.preCleanup = function (cb) { +IOS.prototype.removeInstrumentsSocket = function (cb) { var removeSocket = function (innerCb) { logger.debug("Removing any remaining instruments sockets"); rimraf(this.sock, function (err) { @@ -226,26 +227,35 @@ IOS.prototype.getNumericVersion = function () { return parseFloat(this.args.platformVersion); }; -IOS.prototype.start = function (cb, onDie) { - if (this.instruments !== null) { - var msg = "Trying to start a session but instruments is still around"; - logger.error(msg); - return cb(new Error(msg)); - } - - if (typeof onDie === "function") { - this.onInstrumentsDie = onDie; - } - +IOS.prototype.startRealDevice = function (cb) { async.series([ - this.preCleanup.bind(this), - this.setXcodeFolder.bind(this), + this.removeInstrumentsSocket.bind(this), + this.detectUdid.bind(this), + this.parseLocalizableStrings.bind(this), + this.setBundleIdFromApp.bind(this), + this.createInstruments.bind(this), + this.startLogCapture.bind(this), + this.installToRealDevice.bind(this), + this.startInstruments.bind(this), + this.onInstrumentsLaunch.bind(this), + this.configureBootstrap.bind(this), + this.setBundleId.bind(this), + this.setInitialOrientation.bind(this), + this.initAutoWebview.bind(this), + this.waitForAppLaunched.bind(this), + ], function (err) { + cb(err); + }); +}; + +IOS.prototype.startSimulator = function (cb) { + async.series([ + this.removeInstrumentsSocket.bind(this), this.setXcodeVersion.bind(this), this.setiOSSDKVersion.bind(this), - this.checkDeviceAvailable.bind(this), + this.checkSimAvailable.bind(this), this.createSimulator.bind(this), this.moveBuiltInApp.bind(this), - this.detectTraceTemplate.bind(this), this.detectUdid.bind(this), this.parseLocalizableStrings.bind(this), this.setBundleIdFromApp.bind(this), @@ -258,7 +268,6 @@ IOS.prototype.start = function (cb, onDie) { this.setPreferences.bind(this), this.startLogCapture.bind(this), this.prelaunchSimulator.bind(this), - this.installToRealDevice.bind(this), this.startInstruments.bind(this), this.onInstrumentsLaunch.bind(this), this.configureBootstrap.bind(this), @@ -271,6 +280,24 @@ IOS.prototype.start = function (cb, onDie) { }); }; +IOS.prototype.start = function (cb, onDie) { + if (this.instruments !== null) { + var msg = "Trying to start a session but instruments is still around"; + logger.error(msg); + return cb(new Error(msg)); + } + + if (typeof onDie === "function") { + this.onInstrumentsDie = onDie; + } + + if (this.args.udid) { + this.startRealDevice(cb); + } else { + this.startSimulator(cb); + } +}; + IOS.prototype.createInstruments = function (cb) { logger.debug("Creating instruments"); this.commandProxy = new CommandProxy({ sock: this.sock }); @@ -339,23 +366,19 @@ IOS.prototype.startInstruments = function (cb) { }; IOS.prototype.makeInstruments = function (cb) { - if (this.xcodeVerNumeric >= 6 && this.args.withoutDelay) { - logger.info("On some xcode 6 platforms, instruments-without-delay does " + - "not work. If you experience this, you will need to re-run " + - "appium with the --native-instruments-lib flag"); - } // at the moment all the logging in uiauto is at debug level // TODO: be able to use info in appium-uiauto - prepareBootstrap({ + var bootstrap = prepareBootstrap({ sock: this.sock, interKeyDelay: this.args.interKeyDelay, justLoopInfinitely: this.shouldIgnoreInstrumentsExit(), autoAcceptAlerts: !(!this.args.autoAcceptAlerts || this.args.autoAcceptAlerts === 'false'), autoDismissAlerts: !(!this.args.autoDismissAlerts || this.args.autoDismissAlerts === 'false'), sendKeyStrategy: this.args.sendKeyStrategy || (this.args.udid ? 'grouped' : 'oneByOne') - }).then( - function (bootstrapPath) { + }); + + bootstrap.then(function (bootstrapPath) { var instruments = new Instruments({ // on real devices bundleId is always used app: (!this.args.udid ? this.args.app : null) || this.args.bundleId @@ -366,7 +389,6 @@ IOS.prototype.makeInstruments = function (cb) { , template: this.args.automationTraceTemplatePath , withoutDelay: this.args.withoutDelay , platformVersion: this.args.platformVersion - , xcodeVersion: this.xcodeVersion , webSocket: this.args.webSocket , launchTimeout: this.args.launchTimeout , flakeyRetries: this.args.backendRetries @@ -375,8 +397,7 @@ IOS.prototype.makeInstruments = function (cb) { , traceDir: this.args.traceDir }); cb(null, instruments); - }.bind(this), function (err) { cb(err); } - ); + }.bind(this), cb).fail(cb); }; IOS.prototype.shouldIgnoreInstrumentsExit = function () { @@ -516,17 +537,6 @@ IOS.prototype.onUnexpectedInstrumentsExit = function (code) { } }; -IOS.prototype.setXcodeFolder = function (cb) { - logger.debug("Setting Xcode folder"); - xcode.getPath(function (err, xcodeFolder) { - if (err) { - logger.error("Could not determine Xcode folder:" + err.message); - } - this.xcodeFolder = xcodeFolder; - cb(); - }.bind(this)); -}; - IOS.prototype.setXcodeVersion = function (cb) { logger.debug("Setting Xcode version"); xcode.getVersion(function (err, versionNumber) { @@ -534,19 +544,13 @@ IOS.prototype.setXcodeVersion = function (cb) { logger.error("Could not determine Xcode version:" + err.message); } this.xcodeVersion = versionNumber; - this.xcodeVerNumeric = parseFloat(versionNumber); - if (this.xcodeVersion === "5.0.1") { - cb(new Error("Xcode 5.0.1 ships with a broken version of " + - "Instruments. please upgrade to 5.0.2")); - } else { - cb(); - } + cb(); }.bind(this)); }; IOS.prototype.setiOSSDKVersion = function (cb) { logger.debug("Setting iOS SDK Version"); - xcode.getiOSSDKVersionWithRetry(3, function (err, versionNumber) { + xcode.getMaxIOSSDK(function (err, versionNumber) { if (err) { logger.error("Could not determine iOS SDK version"); return cb(err); @@ -592,7 +596,7 @@ IOS.prototype.setLocale = function (cb) { logger.debug("Locale was set"); if (needSimRestart) { logger.debug("First time setting locale, or locale changed, killing existing Instruments and Sim procs."); - Instruments.killAllSim(this.xcodeVersion); + Instruments.killAllSim(); Instruments.killAll(); setTimeout(cb, 250); } else { @@ -778,57 +782,38 @@ IOS.prototype.setSafariPrefs = function () { } }; -IOS.prototype.detectTraceTemplate = function (cb) { - logger.debug("Detecting automation tracetemplate"); +IOS.prototype.detectUdid = function (cb) { var msg; - if (!this.args.automationTraceTemplatePath) { - xcode.getPath(function (err, xcodeFolderPath) { - if (err) return cb(err); - if (xcodeFolderPath !== null) { - var instExt = this.iOSSDKVersion >= 8 ? 'xrplugin' : 'bundle'; - var xcodeTraceTemplatePath = path.resolve(xcodeFolderPath, - "../Applications/Instruments.app/Contents/PlugIns", - "AutomationInstrument." + instExt + "/Contents/Resources", - "Automation.tracetemplate"); - if (fs.existsSync(xcodeTraceTemplatePath)) { - this.args.automationTraceTemplatePath = xcodeTraceTemplatePath; + logger.debug("Auto-detecting iOS udid..."); + if (this.args.udid !== null && this.args.udid === "auto") { + which('idevice_id', function (notFound, cmdPath) { + + var udidetectPath; + if (notFound) { + udidetectPath = require.resolve('udidetect'); + } else { + udidetectPath = cmdPath + " -l"; + } + + exec(udidetectPath, { maxBuffer: 524288, timeout: 3000 }, function (err, stdout) { + if (err) { + msg = "Error detecting udid: " + err.message; + logger.error(msg); + cb(err); + } + + if (stdout && stdout.length > 2) { + this.args.udid = stdout.replace("\n", ""); + logger.debug("Detected udid as " + this.args.udid); cb(); } else { - msg = "Could not find Automation.tracetemplate in " + - xcodeTraceTemplatePath; + msg = "Could not detect udid."; logger.error(msg); cb(new Error(msg)); } - } else { - msg = "Could not find Automation.tracetemplate because XCode " + - "could not be found. Try setting the path with xcode-select."; - logger.error(msg); - cb(new Error(msg)); - } - }.bind(this)); - } else { - cb(); - } -}; + }.bind(this)); -IOS.prototype.detectUdid = function (cb) { - if (this.args.udid !== null && this.args.udid === "auto") { - logger.debug("Auto-detecting iOS udid..."); - var udidetectPath = path.resolve(__dirname, "../../../build/udidetect/udidetect"); - var udiddetectProc = exec(udidetectPath, { maxBuffer: 524288, timeout: 3000 }, function (err, stdout) { - if (stdout && stdout.length > 2) { - this.args.udid = stdout.replace("\n", ""); - logger.debug("Detected udid as " + this.args.udid); - cb(); - } else { - logger.error("Could not detect udid."); - cb(new Error("Could not detect udid.")); - } }.bind(this)); - udiddetectProc.on('timeout', function () { - logger.error("Timed out trying to detect udid."); - cb(new Error("Timed out trying to detect udid.")); - }); } else { logger.debug("Not auto-detecting udid, running on sim"); cb(); @@ -1097,7 +1082,7 @@ IOS.getSimForDeviceString = function (dString, availDevices) { return [matchedDevice, matchedUdid]; }; -IOS.prototype.checkDeviceAvailable = function (cb) { +IOS.prototype.checkSimAvailable = function (cb) { if (this.args.udid) { logger.debug("Not checking whether simulator is available since we're on " + "a real device"); @@ -1203,35 +1188,42 @@ IOS.prototype.prelaunchSimulator = function (cb) { return cb(); } - logger.debug("Pre-launching simulator"); - var iosSimPath = path.resolve(this.xcodeFolder, - "Platforms/iPhoneSimulator.platform/Developer/Applications" + - "/iPhone Simulator.app/Contents/MacOS/iPhone Simulator"); - if (!fs.existsSync(iosSimPath)) { - msg = "Could not find ios simulator binary at " + iosSimPath; - logger.error(msg); - return cb(new Error(msg)); - } - this.endSimulator(function (err) { - if (err) return cb(err); - logger.debug("Launching device: " + this.getDeviceString()); - var iosSimArgs = ["-SimulateDevice", this.getDeviceString()]; - this.iosSimProcess = spawn(iosSimPath, iosSimArgs); - var waitForSimulatorLogs = function (countdown) { - if (countdown <= 0 || - (this.logs.syslog && (this.logs.syslog.getAllLogs().length > 0 || - (this.logs.crashlog && this.logs.crashlog.getAllLogs().length > 0)))) { - logger.debug(countdown > 0 ? "Simulator is now ready." : - "Waited 10 seconds for simulator to start."); - cb(); - } else { - setTimeout(function () { - waitForSimulatorLogs(countdown - 1); - }, 1000); - } - }.bind(this); - waitForSimulatorLogs(10); - }.bind(this)); + xcode.getPath(function (err, xcodePath) { + if (err) { + return cb(new Error('Could not find xcode folder. Needed to start simulator. ' + err.message)); + } + + logger.debug("Pre-launching simulator"); + var iosSimPath = path.resolve(xcodePath, + "Platforms/iPhoneSimulator.platform/Developer/Applications" + + "/iPhone Simulator.app/Contents/MacOS/iPhone Simulator"); + if (!fs.existsSync(iosSimPath)) { + msg = "Could not find ios simulator binary at " + iosSimPath; + logger.error(msg); + return cb(new Error(msg)); + } + this.endSimulator(function (err) { + if (err) return cb(err); + logger.debug("Launching device: " + this.getDeviceString()); + var iosSimArgs = ["-SimulateDevice", this.getDeviceString()]; + this.iosSimProcess = spawn(iosSimPath, iosSimArgs); + var waitForSimulatorLogs = function (countdown) { + if (countdown <= 0 || + (this.logs.syslog && (this.logs.syslog.getAllLogs().length > 0 || + (this.logs.crashlog && this.logs.crashlog.getAllLogs().length > 0)))) { + logger.debug(countdown > 0 ? "Simulator is now ready." : + "Waited 10 seconds for simulator to start."); + cb(); + } else { + setTimeout(function () { + waitForSimulatorLogs(countdown - 1); + }, 1000); + } + }.bind(this); + waitForSimulatorLogs(10); + }.bind(this)); + + }).bind(this); }; IOS.prototype.parseLocalizableStrings = function (/* language, stringFile, cb */) { @@ -1379,7 +1371,7 @@ IOS.prototype.endSimulator = function (cb) { this.iosSimProcess.kill("SIGHUP"); this.iosSimProcess = null; } else { - Instruments.killAllSim(this.xcodeVersion); + Instruments.killAllSim(); } this.endSimulatorDaemons(cb); }; @@ -1517,11 +1509,13 @@ IOS.prototype.startLogCapture = function (cb) { this.logs.syslog = new iOSLog({ udid: this.args.udid , simUdid: this.iOSSimUdid - , xcodeVersion: this.xcodeVersion , showLogs: this.args.showSimulatorLog || this.args.showIOSLog }); this.logs.syslog.startCapture(function (err) { - if (err) cb(err); + if (err) { + logger.warn("Could not capture logs from device. Continuing without capturing logs."); + return cb(); + } this.logs.crashlog.startCapture(cb); }.bind(this)); }; diff --git a/lib/devices/ios/simulator.js b/lib/devices/ios/simulator.js index 19090ea27..2f0cae615 100644 --- a/lib/devices/ios/simulator.js +++ b/lib/devices/ios/simulator.js @@ -8,7 +8,7 @@ var logger = require('../../server/logger.js').get('appium') , path = require('path') , ncp = require('ncp') , mkdirp = require('mkdirp') - , xcode = require('./xcode.js') + , xcode = require('../../future.js').xcode , simctl = require('../../future').simctl , multiResolve = require('../../helpers.js').multiResolve , rimraf = require('rimraf') diff --git a/lib/devices/ios/xcode.js b/lib/devices/ios/xcode.js deleted file mode 100644 index 4c3ef3c17..000000000 --- a/lib/devices/ios/xcode.js +++ /dev/null @@ -1,152 +0,0 @@ -"use strict"; - -var logger = require('../../server/logger.js').get('appium') - , fs = require('fs') - , async = require('async') - , exec = require('child_process').exec - , util = require('appium-support').util - , env = process.env - , helpers = require('../../helpers.js') - , escapeSpace = helpers.escapeSpace; - -var XCODE_SUBDIR = "/Contents/Developer"; -var xcode = {}; - -var _hasExpectedSubDir = function (path) { - return path.substring(path.length - XCODE_SUBDIR.length) === XCODE_SUBDIR; -}; - -var _getPathFromSymlink = function (stdout, cb) { - // Node's invocation of xcode-select sometimes flakes and returns an empty string. - // Not clear why. As a workaround, Appium can reliably deduce the version in use by checking - // the locations xcode-select uses to store the selected version's path. This should be 100% - // reliable so long as the link locations remain the same. However, since we're relying on - // hardcoded paths, this approach will break the next time Apple changes the symlink location. - var symlnkPath = "/var/db/xcode_select_link"; - var legacySymlnkPath = "/usr/share/xcode-select/xcode_dir_path"; // Xcode < 5.x (?) - - // xcode-select allows users to override its settings with the DEVELOPER_DIR env var, - // so check that first - if (util.hasContent(env.DEVELOPER_DIR)) { - var dir = _hasExpectedSubDir(env.DEVELOPER_DIR) ? - env.DEVELOPER_DIR : - env.DEVELOPER_DIR + XCODE_SUBDIR; - cb(null, dir); - } else if (fs.existsSync(symlnkPath)) { - fs.readlink(symlnkPath, cb); - } else if (fs.existsSync(legacySymlnkPath)) { - fs.readlink(legacySymlnkPath, cb); - } else { - // We should only get here is we failed to capture xcode-select's stdout and our - // other checks failed. Either Apple has moved the symlink to a new location or the user - // is using the default install. 99.999% chance it's the latter, so issue a warning - // should we ever hit the edge case. - logger.warn("xcode-select returned an invalid path: " + stdout + - ". No links to alternate versions were detected. Using Xcode's " + - "default location."); - var path = "/Applications/Xcode.app" + XCODE_SUBDIR; - var err = fs.existsSync(path) ? null : new Error("No Xcode installation found."); - cb(err, path); - } -}; - -xcode.getPath = function (cb) { - exec('xcode-select --print-path', { maxBuffer: 524288, timeout: 3000 }, function (err, stdout, stderr) { - if (!err) { - var xcodeFolderPath = stdout.replace("\n", ""); - xcodeFolderPath = xcodeFolderPath.replace(new RegExp("/$"), ""); - - if (util.hasContent(xcodeFolderPath) && fs.existsSync(xcodeFolderPath)) { - cb(null, xcodeFolderPath); - } else if (!util.hasContent(xcodeFolderPath)) { - _getPathFromSymlink(xcodeFolderPath, cb); - } else { - var msg = "xcode-select could not find xcode. Path: " + xcodeFolderPath + " does not exist."; - logger.error(msg); - cb(new Error(msg), null); - } - } else { - logger.error("xcode-select threw error " + err); - logger.error("Stderr: " + stderr); - logger.error("Stdout: " + stdout); - cb(new Error("xcode-select threw an error"), null); - } - }); -}; - -xcode.getVersion = function (cb) { - xcode.getPath(function (err, xcodePath) { - if (!err && !_hasExpectedSubDir(xcodePath)) { - err = new Error("Expected a path ending with '" + XCODE_SUBDIR + "', but got " + xcodePath); - } - if (err) { - return cb(err, null); - } - // we want to read the CFBundleShortVersionString from Xcode's plist. - // It should be in /[root]/XCode.app/Contents/ - var plistPath = xcodePath.replace(XCODE_SUBDIR, "/Contents/Info.plist"); - if (!fs.existsSync(plistPath)) { - return cb(new Error("Could not get Xcode version: " + plistPath + " does not exist on disk."), null); - } else { - var cmd; - try { - cmd = "/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' " + - escapeSpace(plistPath); - } catch (e) { - return cb(e); - } - exec(cmd, { maxBuffer: 524288, timeout: 3000 }, function (err, stdout) { - if (err !== null) return cb(err, null); - var versionPattern = /\d\.\d\.*\d*/; - // need to use string#match here; previous code used regexp#exec, which does not return null - var match = stdout.match(versionPattern); - if (match === null || !util.hasContent(match[0])) { - cb(new Error("Could not parse Xcode version (xcodebuild output was: " + stdout + ")."), null); - } else { - var versionNumber = match[0]; - cb(null, versionNumber); - } - }); - } - }); -}; - -xcode.getiOSSDKVersion = function (cb) { - var msg; - xcode.getVersion(function (err, versionNumber) { - if (err) { - msg = "Could not get the iOS SDK version because the Xcode version could not be determined."; - logger.error(msg); - cb(new Error(msg), null); - } else if (versionNumber[0] === '4') { - cb(null, '6.1'); - } else { - logger.debug("Getting sdk version from xcrun with a timeout"); - exec('xcrun --sdk iphonesimulator --show-sdk-version', { maxBuffer: 524288, timeout: 3000 }, function (err, stdout, stderr) { - if (!err) { - var iosSDKVersion = stdout.replace("\n", ""); - var match = /\d.\d/.exec(iosSDKVersion); - if (match) { - cb(null, iosSDKVersion); - } else { - msg = "xcrun returned a non-numeric iOS SDK version: " + iosSDKVersion; - logger.error(msg); - cb(new Error(msg), null); - } - } else { - msg = "xcrun threw an error " + err; - logger.error(msg); - logger.error("Stderr: " + stderr); - logger.error("Stdout: " + stdout); - cb(new Error(msg), null); - } - }); - } - }); -}; - -xcode.getiOSSDKVersionWithRetry = function (times, cb) { - async.retry(times, xcode.getiOSSDKVersion, cb); -}; - -module.exports = xcode; diff --git a/lib/future.js b/lib/future.js index c839e36ec..399414478 100644 --- a/lib/future.js +++ b/lib/future.js @@ -3,7 +3,8 @@ var _ = require('underscore') , Q = require('q') - , simctl = require("node-simctl"); + , simctl = require('node-simctl') + , xcode = require('appium-xcode'); var nodeify = function (obj) { if (typeof obj !== "function") { @@ -45,5 +46,6 @@ if (typeof Promise === "function") { module.exports = { simctl: nodeify(simctl), + xcode: nodeify(xcode), nodeify: nodeify }; diff --git a/package.json b/package.json index ed2223678..0216e4c65 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,10 @@ "appium-adb": "=1.7.5", "appium-atoms": "=0.0.5", "appium-chromedriver": "=1.0.0", - "appium-instruments": "=1.5.4", - "appium-support": "=0.1.1", + "appium-instruments": "=1.7.3", + "appium-support": "=1.0.2", "appium-uiauto": "=1.10.7", + "appium-xcode": "=1.0.2", "argparse": "~1.0.1", "async": "~0.9.0", "binary-cookies": "~0.1.1", @@ -88,12 +89,14 @@ "temp": "~0.8.1", "through": "~2.3.6", "touch": "0.0.3", + "traceur": "0.0.87", "underscore": "~1.8.1", "underscore.string": "~3.0.3", "unzip": "~0.1.11", "utf7": "~1.0.0", "uuid-js": "~0.7.5", "vargs": "~0.1.0", + "which": "^1.0.9", "win-spawn": "~2.0.0", "winston": "~0.9.0", "ws": "~0.7.1", @@ -142,5 +145,8 @@ "yargs": "^3.0.4", "yiewd": "~0.6.0", "rewire": "^2.3.1" + }, + "optionalDependencies": { + "udidetect": "^1.0.7" } } diff --git a/reset.sh b/reset.sh index a64d358a8..ea0571021 100755 --- a/reset.sh +++ b/reset.sh @@ -167,16 +167,6 @@ reset_ios() { set -e echo "* Setting iOS config to Appium's version" run_cmd "$grunt" setConfigVer:ios - echo "* Cloning/updating udidetect" - run_cmd git submodule update --init submodules/udidetect - echo "* Building udidetect" - run_cmd pushd submodules/udidetect - run_cmd make - run_cmd popd - echo "* Moving udidetect into build/udidetect" - run_cmd rm -rf build/udidetect - run_cmd mkdir build/udidetect - run_cmd cp -R submodules/udidetect/udidetect build/udidetect/ if $include_dev ; then if $npmlink ; then echo "* Cloning/npm linking appium-atoms" diff --git a/submodules/appium-instruments b/submodules/appium-instruments index 29331a4c1..91c0ba808 160000 --- a/submodules/appium-instruments +++ b/submodules/appium-instruments @@ -1 +1 @@ -Subproject commit 29331a4c178a45c2d0a2c875b2ce35b572cc206f +Subproject commit 91c0ba80869ab872d0255bcaea066c98bce5bddc diff --git a/submodules/udidetect b/submodules/udidetect deleted file mode 160000 index 0654ad8d7..000000000 --- a/submodules/udidetect +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0654ad8d79b6c404dd0034b827cfecfef224c575 diff --git a/test/functional/ios/file-movement-specs.js b/test/functional/ios/file-movement-specs.js index b48acb975..743439ab9 100644 --- a/test/functional/ios/file-movement-specs.js +++ b/test/functional/ios/file-movement-specs.js @@ -7,7 +7,7 @@ var setup = require("../common/setup-base") , path = require('path') , Readable = require('stream').Readable , Simulator = require('../../../lib/devices/ios/simulator.js') - , xcode = require('../../../lib/devices/ios/xcode.js') + , xcode = require('../../../lib/future.js').xcode , exec = require('child_process').exec , getSimUdid = require('../../helpers/sim-udid.js').getSimUdid , Unzip = require('unzip'); @@ -66,7 +66,7 @@ describe('file movements - pullFile and pushFile', function () { before(function (done) { var pv = env.CAPS.platformVersion || '7.1'; var ios8 = parseFloat(pv) >= 8; - xcode.getiOSSDKVersion(function (err, sdk) { + xcode.getMaxIOSSDK(function (err, sdk) { if (err) return done(err); var next = function (udid) { var sim = new Simulator({ diff --git a/test/functional/ios/prefs/check-safari-settings.js b/test/functional/ios/prefs/check-safari-settings.js index 93a125c4d..0e08ab4ec 100644 --- a/test/functional/ios/prefs/check-safari-settings.js +++ b/test/functional/ios/prefs/check-safari-settings.js @@ -2,7 +2,7 @@ var _ = require('underscore') , env = require('../../../helpers/env.js') - , xcode = require('../../../../lib/devices/ios/xcode.js') + , xcode = require('../../../../lib/future.js').xcode , Simulator = require('../../../../lib/devices/ios/simulator.js') , settingsPlists = require('../../../../lib/devices/ios/settings.js') , getSimUdid = require('../../../helpers/sim-udid').getSimUdid; @@ -28,7 +28,7 @@ exports.ios6 = function (driver, setting, expected, cb) { var ios7up = function (version, udid, setting, expected, cb) { var settingsSets; var foundSettings; - xcode.getiOSSDKVersion(function (err, sdk) { + xcode.getMaxIOSSDK(function (err, sdk) { var sim = new Simulator({ platformVer: env.CAPS.platformVersion, sdkVer: sdk, @@ -60,7 +60,7 @@ var ios7up = function (version, udid, setting, expected, cb) { }; exports.ios7up = function (desired, setting, expected, cb) { - xcode.getiOSSDKVersion(function (err, sdk) { + xcode.getMaxIOSSDK(function (err, sdk) { if (parseFloat(sdk) >= 8) { getSimUdid('6', sdk, desired, function (err, udid) { if (err) return cb(err); diff --git a/test/functional/ios/testapp/device-specs.js b/test/functional/ios/testapp/device-specs.js index ade491af0..3bcdc79df 100644 --- a/test/functional/ios/testapp/device-specs.js +++ b/test/functional/ios/testapp/device-specs.js @@ -4,7 +4,7 @@ var _ = require('underscore'), initSession = require('../../../helpers/session').initSession, should = require('chai').should(), expect = require('chai').expect, - XCODE = require('../../../../lib/devices/ios/xcode.js'), + XCODE = require('../../../../lib/future.js').xcode, fs = require('fs'), env = process.env, desired = require('./desired'); @@ -39,6 +39,8 @@ describe('testapp - device', function () { it('should fail with a bad path', function (done) { env.DEVELOPER_DIR = "/foo/bar"; + XCODE.clearInternalCache(); + XCODE.getPath(function (err) { env.DEVELOPER_DIR = ""; expect(err).not.to.be.null;