diff --git a/lib/xcode.js b/lib/xcode.js index c2205ce..f2e44e0 100644 --- a/lib/xcode.js +++ b/lib/xcode.js @@ -23,6 +23,7 @@ const fs = require('fs'), path = require('path'), readPlist = require('./utilities').readPlist, + sqlite3 = require('sqlite3'), __ = appc.i18n(__dirname).__; var cache, @@ -88,6 +89,59 @@ exports.detect = function detect(options, callback) { return runtimes; } + function findSDKs(dir, nameRegExp, minVersion) { + var vers = []; + + fs.existsSync(dir) && fs.readdirSync(dir).forEach(function (name) { + var file = path.join(dir, name); + if (!fs.existsSync(file) || !fs.statSync(file).isDirectory()) return; + var m = name.match(nameRegExp); + if (m && (!minVersion || appc.version.gte(m[1], minVersion))) { + var ver = m[1]; + file = path.join(file, 'System', 'Library', 'CoreServices', 'SystemVersion.plist'); + if (fs.existsSync(file)) { + var p = new appc.plist(file); + if (p.ProductVersion) { + ver = p.ProductVersion; + } + } + vers.push(ver); + } + }); + + return vers.sort().reverse(); + } + + function findIosSims(dir, xcodeVer) { + var vers = findSDKs(dir, /^iPhoneSimulator(.+)\.sdk$/), + simRuntimesDir = '/Library/Developer/CoreSimulator/Profiles/Runtimes'; + + // for Xcode >=6.2 <7.0, the simulators are in a global directory + if (fs.existsSync(simRuntimesDir) && appc.version.gte(xcodeVer, '6.2')) { + fs.readdirSync(simRuntimesDir).forEach(function (name) { + var file = path.join(simRuntimesDir, name); + if (!fs.existsSync(file) || !fs.statSync(file).isDirectory()) return; + + var m = name.match(/^iOS (.+)\.simruntime$/); + if (m && (!options.minIosVersion || appc.version.gte(m[1], options.minIosVersion))) { + var ver = m[1]; + file = path.join(file, 'Contents', 'Resources', 'RuntimeRoot', 'System', 'Library', 'CoreServices', 'SystemVersion.plist'); + if (fs.existsSync(file)) { + var p = new appc.plist(file); + if (p.ProductVersion) { + ver = p.ProductVersion; + } + } + if (vers.indexOf(ver) === -1) { + vers.push(ver); + } + } + }); + } + + return vers.sort().reverse(); + } + var searchPaths = { '/Applications': 1, '~/Applications': 1 @@ -98,14 +152,15 @@ exports.detect = function detect(options, callback) { issues: [] }, selectedXcodePath = null, - globalSimRuntimes = findSimRuntimes('/Library/Developer/CoreSimulator/Profiles/Runtimes'); + globalSimRuntimes = findSimRuntimes('/Library/Developer/CoreSimulator/Profiles/Runtimes'), + xcodes = []; // since we do not support Xcode 5 and below, weed them out options.supportedVersions = (options.supportedVersions ? options.supportedVersions + '||' : '') + '>=6.0.0'; async.series([ // build the list of searchPaths - function (next) { + function detectOSXenv(next) { env.detect(options, function (err, env) { (Array.isArray(options.searchPath) ? options.searchPath : [ options.searchPath ]).forEach(function (p) { p && (searchPaths[p] = 1); @@ -128,199 +183,178 @@ exports.detect = function detect(options, callback) { next(); }); }); - } - ], function () { - // scan all searchPaths for Xcode installs - var xcodes = []; - - // scan search paths for Xcodes - Object.keys(searchPaths).forEach(function (p) { - if (fs.existsSync(p) && fs.statSync(p).isDirectory()) { - // is this directory an Xcode dev dir? - if (/\/Contents\/Developer\/?$/.test(p) && fs.existsSync(path.join(p, 'usr', 'bin', 'xcodebuild')) && xcodes.indexOf(p) === -1) { - xcodes.push(p) - } else { - // is it the Xcode dir? - var devDir = path.join(p, 'Contents', 'Developer'); - if (fs.existsSync(path.join(devDir, 'usr', 'bin', 'xcodebuild')) && xcodes.indexOf(devDir) === -1) { - xcodes.push(devDir); - } else { - // possibly a parent folder, scan for Xcodes - fs.readdirSync(p).forEach(function (name) { - var dir = path.join(p, name, 'Contents', 'Developer'); - if (xcodes.indexOf(dir) === -1 && fs.existsSync(path.join(dir, 'usr', 'bin', 'xcodebuild'))) { - xcodes.push(dir); - } - }); - } - } - } - }); + }, - function findSDKs(dir, nameRegExp, minVersion) { - var vers = []; - fs.existsSync(dir) && fs.readdirSync(dir).forEach(function (name) { - var file = path.join(dir, name); - if (!fs.existsSync(file) || !fs.statSync(file).isDirectory()) return; - var m = name.match(nameRegExp); - if (m && (!minVersion || appc.version.gte(m[1], minVersion))) { - var ver = m[1]; - file = path.join(file, 'System', 'Library', 'CoreServices', 'SystemVersion.plist'); - if (fs.existsSync(file)) { - var p = new appc.plist(file); - if (p.ProductVersion) { - ver = p.ProductVersion; + function findXcodes(next) { + // scan all searchPaths for Xcode installs + Object.keys(searchPaths).forEach(function (p) { + if (fs.existsSync(p) && fs.statSync(p).isDirectory()) { + // is this directory an Xcode dev dir? + if (/\/Contents\/Developer\/?$/.test(p) && fs.existsSync(path.join(p, 'usr', 'bin', 'xcodebuild')) && xcodes.indexOf(p) === -1) { + xcodes.push(p) + } else { + // is it the Xcode dir? + var devDir = path.join(p, 'Contents', 'Developer'); + if (fs.existsSync(path.join(devDir, 'usr', 'bin', 'xcodebuild')) && xcodes.indexOf(devDir) === -1) { + xcodes.push(devDir); + } else { + // possibly a parent folder, scan for Xcodes + fs.readdirSync(p).forEach(function (name) { + var dir = path.join(p, name, 'Contents', 'Developer'); + if (xcodes.indexOf(dir) === -1 && fs.existsSync(path.join(dir, 'usr', 'bin', 'xcodebuild'))) { + xcodes.push(dir); + } + }); } } - vers.push(ver); } }); - return vers.sort().reverse(); - } + next(); + }, - function findIosSims(dir, xcodeVer) { - var vers = findSDKs(dir, /^iPhoneSimulator(.+)\.sdk$/), - simRuntimesDir = '/Library/Developer/CoreSimulator/Profiles/Runtimes'; - - // starting in Xcode 6.2, the simulators - if (fs.existsSync(simRuntimesDir) && appc.version.gte(xcodeVer, '6.2')) { - fs.readdirSync(simRuntimesDir).forEach(function (name) { - var file = path.join(simRuntimesDir, name); - if (!fs.existsSync(file) || !fs.statSync(file).isDirectory()) return; - - var m = name.match(/^iOS (.+)\.simruntime$/); - if (m && (!options.minIosVersion || appc.version.gte(m[1], options.minIosVersion))) { - var ver = m[1]; - file = path.join(file, 'Contents', 'Resources', 'RuntimeRoot', 'System', 'Library', 'CoreServices', 'SystemVersion.plist'); - if (fs.existsSync(file)) { - var p = new appc.plist(file); - if (p.ProductVersion) { - ver = p.ProductVersion; - } - } - if (vers.indexOf(ver) === -1) { - vers.push(ver); - } + function loadXcodeInfo(next) { + xcodes.forEach(function (dir) { + var p = new appc.plist(path.join(path.dirname(dir), 'version.plist')), + selected = dir == selectedXcodePath, + supported = options.supportedVersions ? appc.version.satisfies(p.CFBundleShortVersionString, options.supportedVersions, true) : true, + ver = p.CFBundleShortVersionString + ':' + p.ProductBuildVersion, + f; + + if (!results.xcode[ver] || selected || dir <= results.xcode[ver].path) { + var watchos = null; + if (appc.version.gte(p.CFBundleShortVersionString, '7.0')) { + watchos = { + sdks: findSDKs(path.join(dir, 'Platforms', 'WatchOS.platform', 'Developer', 'SDKs'), /^WatchOS(.+)\.sdk$/, options.minWatchosVersion), + sims: findSDKs(path.join(dir, 'Platforms', 'WatchSimulator.platform', 'Developer', 'SDKs'), /^WatchSimulator(.+)\.sdk$/, options.minWatchosVersion) + }; + } else if (appc.version.gte(p.CFBundleShortVersionString, '6.2')) { + watchos = { + sdks: ['1.0'], + sims: ['1.0'] + }; } - }); - } - return vers.sort().reverse(); - } - - xcodes.forEach(function (dir) { - var p = new appc.plist(path.join(path.dirname(dir), 'version.plist')), - selected = dir == selectedXcodePath, - supported = options.supportedVersions ? appc.version.satisfies(p.CFBundleShortVersionString, options.supportedVersions, true) : true, - ver = p.CFBundleShortVersionString + ':' + p.ProductBuildVersion, - f; - - if (!results.xcode[ver] || selected || dir <= results.xcode[ver].path) { - var watchos = null; - if (appc.version.gte(p.CFBundleShortVersionString, '7.0')) { - watchos = { - sdks: findSDKs(path.join(dir, 'Platforms', 'WatchOS.platform', 'Developer', 'SDKs'), /^WatchOS(.+)\.sdk$/, options.minWatchosVersion), - sims: findSDKs(path.join(dir, 'Platforms', 'WatchSimulator.platform', 'Developer', 'SDKs'), /^WatchSimulator(.+)\.sdk$/, options.minWatchosVersion) - }; - } else if (appc.version.gte(p.CFBundleShortVersionString, '6.2')) { - watchos = { - sdks: ['1.0'], - sims: ['1.0'] + var xc = results.xcode[ver] = { + xcodeapp: dir.replace(/\/Contents\/Developer\/?$/, ''), + path: dir, + selected: selected, + version: p.CFBundleShortVersionString, + build: p.ProductBuildVersion, + supported: supported, + sdks: findSDKs(path.join(dir, 'Platforms', 'iPhoneOS.platform', 'Developer', 'SDKs'), /^iPhoneOS(.+)\.sdk$/, options.minIosVersion), + sims: findIosSims(path.join(dir, 'Platforms', 'iPhoneSimulator.platform', 'Developer', 'SDKs'), p.CFBundleShortVersionString), + simDeviceTypes: {}, + simRuntimes: appc.util.mix({}, globalSimRuntimes), + watchos: watchos, + teams: {}, + executables: { + xcodebuild: fs.existsSync(f = path.join(dir, 'usr', 'bin', 'xcodebuild')) ? f : null, + clang: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'clang')) ? f : null, + clang_xx: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'clang++')) ? f : null, + libtool: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'libtool')) ? f : null, + lipo: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'lipo')) ? f : null, + otool: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'otool')) ? f : null, + pngcrush: fs.existsSync(f = path.join(dir, 'Platforms', 'iPhoneOS.platform', 'Developer', 'usr', 'bin', 'pngcrush')) ? f : null, + simulator: null, + watchsimulator: null, + simctl: fs.existsSync(f = path.join(dir, 'usr', 'bin', 'simctl')) ? f : null + } }; - } - - var xc = results.xcode[ver] = { - xcodeapp: dir.replace(/\/Contents\/Developer\/?$/, ''), - path: dir, - selected: selected, - version: p.CFBundleShortVersionString, - build: p.ProductBuildVersion, - supported: supported, - sdks: findSDKs(path.join(dir, 'Platforms', 'iPhoneOS.platform', 'Developer', 'SDKs'), /^iPhoneOS(.+)\.sdk$/, options.minIosVersion), - sims: findIosSims(path.join(dir, 'Platforms', 'iPhoneSimulator.platform', 'Developer', 'SDKs'), p.CFBundleShortVersionString), - simDeviceTypes: {}, - simRuntimes: appc.util.mix({}, globalSimRuntimes), - watchos: watchos, - executables: { - xcodebuild: fs.existsSync(f = path.join(dir, 'usr', 'bin', 'xcodebuild')) ? f : null, - clang: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'clang')) ? f : null, - clang_xx: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'clang++')) ? f : null, - libtool: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'libtool')) ? f : null, - lipo: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'lipo')) ? f : null, - otool: fs.existsSync(f = path.join(dir, 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'otool')) ? f : null, - pngcrush: fs.existsSync(f = path.join(dir, 'Platforms', 'iPhoneOS.platform', 'Developer', 'usr', 'bin', 'pngcrush')) ? f : null, - simulator: null, - watchsimulator: null, - simctl: fs.existsSync(f = path.join(dir, 'usr', 'bin', 'simctl')) ? f : null - } - }; - ['iPhoneSimulator.platform', 'WatchSimulator.platform'].forEach(function (platform) { - // read in the device types - var deviceTypesDir = path.join(xc.path, 'Platforms', platform, 'Developer', 'Library', 'CoreSimulator', 'Profiles', 'DeviceTypes'); - fs.existsSync(deviceTypesDir) && fs.readdirSync(deviceTypesDir).forEach(function (name) { - var plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Info.plist')), - devId = plist && plist.CFBundleIdentifier; - if (plist) { - var deviceType = xc.simDeviceTypes[devId] = { - name: plist.CFBundleName, - model: 'unknown', - supportsWatch: false - }; - - plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Resources', 'profile.plist')); + ['iPhoneSimulator.platform', 'WatchSimulator.platform'].forEach(function (platform) { + // read in the device types + var deviceTypesDir = path.join(xc.path, 'Platforms', platform, 'Developer', 'Library', 'CoreSimulator', 'Profiles', 'DeviceTypes'); + fs.existsSync(deviceTypesDir) && fs.readdirSync(deviceTypesDir).forEach(function (name) { + var plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Info.plist')), + devId = plist && plist.CFBundleIdentifier; if (plist) { - deviceType.model = plist.modelIdentifier; + var deviceType = xc.simDeviceTypes[devId] = { + name: plist.CFBundleName, + model: 'unknown', + supportsWatch: false + }; + + plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Resources', 'profile.plist')); + if (plist) { + deviceType.model = plist.modelIdentifier; + } + + plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Resources', 'capabilities.plist')); + if (plist) { + deviceType.supportsWatch = !!plist.capabilities['watch-companion']; + } } + }); - plist = readPlist(path.join(deviceTypesDir, name, 'Contents', 'Resources', 'capabilities.plist')); - if (plist) { - deviceType.supportsWatch = !!plist.capabilities['watch-companion']; - } - } + // read in the runtimes + appc.util.mix(xc.simRuntimes, findSimRuntimes(path.join(xc.path, 'Platforms', platform, 'Developer', 'Library', 'CoreSimulator', 'Profiles', 'Runtimes'))); }); - // read in the runtimes - appc.util.mix(xc.simRuntimes, findSimRuntimes(path.join(xc.path, 'Platforms', platform, 'Developer', 'Library', 'CoreSimulator', 'Profiles', 'Runtimes'))); - }); + ['Simulator', 'iOS Simulator'].some(function (name) { + var p = path.join(dir, 'Applications', name + '.app', 'Contents', 'MacOS', name); + if (fs.existsSync(p)) { + xc.executables.simulator = p; + return true; + } + }); - ['Simulator', 'iOS Simulator'].some(function (name) { - var p = path.join(dir, 'Applications', name + '.app', 'Contents', 'MacOS', name); - if (fs.existsSync(p)) { - xc.executables.simulator = p; - return true; + var watchsim = path.join(dir, 'Applications', 'Simulator (Watch).app', 'Contents', 'MacOS', 'Simulator (Watch)'); + if (fs.existsSync(watchsim)) { + xc.executables.watchsimulator = watchsim; } - }); - var watchsim = path.join(dir, 'Applications', 'Simulator (Watch).app', 'Contents', 'MacOS', 'Simulator (Watch)'); - if (fs.existsSync(watchsim)) { - xc.executables.watchsimulator = watchsim; + selected && (results.selectedXcode = xc); + + if (supported === false) { + results.issues.push({ + id: 'IOS_XCODE_TOO_OLD', + type: 'warning', + message: __('Xcode %s is too old and is no longer supported.', '__' + p.CFBundleShortVersionString + '__') + '\n' + + __('The minimum supported Xcode version is Xcode %s.', appc.version.parseMin(options.supportedVersions)), + xcodeVer: p.CFBundleShortVersionString, + minSupportedVer: appc.version.parseMin(options.supportedVersions) + }); + } else if (supported === 'maybe') { + results.issues.push({ + id: 'IOS_XCODE_TOO_NEW', + type: 'warning', + message: __('Xcode %s may or may not work as expected.', '__' + p.CFBundleShortVersionString + '__') + '\n' + + __('The maximum supported Xcode version is Xcode %s.', appc.version.parseMax(options.supportedVersions, true)), + xcodeVer: p.CFBundleShortVersionString, + maxSupportedVer: appc.version.parseMax(options.supportedVersions, true) + }); + } } + }); + next(); + }, - selected && (results.selectedXcode = xc); + function findTeams(next) { + async.each(Object.keys(results.xcode), function (id, cb) { + var xc = results.xcode[id], + dbFile = appc.fs.resolvePath('~/Library/Developer/Xcode/DeveloperPortal ' + xc.version + '.db'); - if (supported === false) { - results.issues.push({ - id: 'IOS_XCODE_TOO_OLD', - type: 'warning', - message: __('Xcode %s is too old and is no longer supported.', '__' + p.CFBundleShortVersionString + '__') + '\n' + - __('The minimum supported Xcode version is Xcode %s.', appc.version.parseMin(options.supportedVersions)), - xcodeVer: p.CFBundleShortVersionString, - minSupportedVer: appc.version.parseMin(options.supportedVersions) - }); - } else if (supported === 'maybe') { - results.issues.push({ - id: 'IOS_XCODE_TOO_NEW', - type: 'warning', - message: __('Xcode %s may or may not work as expected.', '__' + p.CFBundleShortVersionString + '__') + '\n' + - __('The maximum supported Xcode version is Xcode %s.', appc.version.parseMax(options.supportedVersions, true)), - xcodeVer: p.CFBundleShortVersionString, - maxSupportedVer: appc.version.parseMax(options.supportedVersions, true) - }); + if (!fs.existsSync(dbFile)) { + return cb(); } - } - }); + var db = new sqlite3.Database(dbFile); + db.all('SELECT ZNAME, ZSTATUS, ZTEAMID, ZTYPE FROM ZTEAM', function (err, rows) { + err || rows.forEach(function (row) { + if (row.ZTEAMID) { + xc.teams[row.ZTEAMID] = { + name: row.ZNAME, + status: row.ZSTATUS || 'unknown', + type: row.ZTYPE + }; + } + }); + db.close(); + cb(); + }); + }, next); + } + ], function () { if (Object.keys(results.xcode).length) { var validXcodes = 0, sdkCounter = 0, diff --git a/package.json b/package.json index 81a10c5..769ccea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ioslib", - "version": "0.4.7", + "version": "0.5.0", "description": "iOS Utility Library", "keywords": [ "appcelerator", @@ -42,7 +42,8 @@ "bplist-parser": "^0.1.0", "mkdirp": "^0.5.1", "node-appc": "0.2.28", - "node-ios-device": "0.4.1" + "node-ios-device": "0.4.1", + "sqlite3": "^3.0.10" }, "devDependencies": { "mocha": "^2.2.5", diff --git a/test/test-xcode.js b/test/test-xcode.js index cced8d1..ce3ec4e 100644 --- a/test/test-xcode.js +++ b/test/test-xcode.js @@ -15,7 +15,7 @@ const function checkXcode(xcode) { should(xcode).be.an.Object; - should(xcode).have.keys('xcodeapp', 'path', 'selected', 'version', 'build', 'supported', 'sdks', 'sims', 'simDeviceTypes', 'simRuntimes', 'watchos', 'executables'); + should(xcode).have.keys('xcodeapp', 'path', 'selected', 'version', 'build', 'supported', 'sdks', 'sims', 'simDeviceTypes', 'simRuntimes', 'watchos', 'teams', 'executables'); should(xcode.xcodeapp).be.a.String; should(xcode.xcodeapp).not.equal(''); @@ -84,6 +84,18 @@ function checkXcode(xcode) { }); } + should(xcode.teams).be.an.Object; + Object.keys(xcode.teams).forEach(function (teamId) { + should(xcode.teams[teamId]).be.an.Object; + should(xcode.teams[teamId]).have.keys('name', 'status', 'type'); + should(xcode.teams[teamId].name).be.a.String(); + should(xcode.teams[teamId].name).not.equal(''); + should(xcode.teams[teamId].status).be.a.String(); + should(xcode.teams[teamId].status).not.equal(''); + should(xcode.teams[teamId].type).be.a.String(); + should(xcode.teams[teamId].type).not.equal(''); + }); + var keys = ['xcodebuild', 'clang', 'clang_xx', 'libtool', 'lipo', 'otool', 'pngcrush', 'simulator', 'watchsimulator', 'simctl']; should(xcode.executables).be.an.Object; keys.forEach(function (key) {