appium-xcode
Version:
Interact with Xcode
281 lines • 12.3 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMaxTVOSSDK = exports.getMaxIOSSDK = exports.getPath = void 0;
exports.getVersion = getVersion;
exports.getMaxIOSSDKWithoutRetry = getMaxIOSSDKWithoutRetry;
exports.getMaxTVOSSDKWithoutRetry = getMaxTVOSSDKWithoutRetry;
exports.getClangVersion = getClangVersion;
exports.getPathFromDeveloperDir = getPathFromDeveloperDir;
exports.getPathFromXcodeSelect = getPathFromXcodeSelect;
const support_1 = require("@appium/support");
const path_1 = __importDefault(require("path"));
const asyncbox_1 = require("asyncbox");
const lodash_1 = __importDefault(require("lodash"));
const teen_process_1 = require("teen_process");
const semver = __importStar(require("semver"));
const helpers_1 = require("./helpers");
const DEFAULT_NUMBER_OF_RETRIES = 2;
const XCODE_BUNDLE_ID = 'com.apple.dt.Xcode';
const log = support_1.logger.getLogger('Xcode');
/**
* Retrieves the full path to Xcode Developer subfolder via xcode-select
*
* @param {number} timeout The maximum timeout for xcode-select execution
* @returns {Promise<string>} Full path to Xcode Developer subfolder
* @throws {Error} If it is not possible to retrieve a proper path
*/
async function getPathFromXcodeSelect(timeout = helpers_1.XCRUN_TIMEOUT) {
/**
* @param {string} prefix
* @returns {Promise<string>}
*/
const generateErrorMessage = async (prefix) => {
const xcodePaths = await (0, helpers_1.findAppPaths)(XCODE_BUNDLE_ID);
if (lodash_1.default.isEmpty(xcodePaths)) {
return `${prefix}. Consider installing Xcode to address this issue.`;
}
const proposals = xcodePaths.map((p) => ` sudo xcode-select -s "${path_1.default.join(p, 'Contents', 'Developer')}"`);
return `${prefix}. ` +
`Consider running${proposals.length > 1 ? ' any of' : ''}:\n${proposals.join('\n')}\nto address this issue.`;
};
let stdout;
try {
({ stdout } = await (0, teen_process_1.exec)('xcode-select', ['--print-path'], { timeout }));
}
catch (e) {
const msg = `Cannot determine the path to Xcode by running 'xcode-select -p' command. ` +
`Original error: ${e.stderr || e.message}`;
log.error(msg);
throw new Error(msg);
}
// trim and remove trailing slash
const developerRoot = String(stdout).replace(/\/$/, '').trim();
if (!developerRoot) {
const msg = await generateErrorMessage(`'xcode-select -p' returned an empty string`);
log.error(msg);
throw new Error(msg);
}
// xcode-select might also return a path to command line tools
const { CFBundleIdentifier } = await (0, helpers_1.readXcodePlist)(developerRoot);
if (CFBundleIdentifier === XCODE_BUNDLE_ID) {
return developerRoot;
}
const msg = await generateErrorMessage(`'${developerRoot}' is not a valid Xcode path`);
log.error(msg);
throw msg;
}
/**
* Retrieves the full path to Xcode Developer subfolder via `DEVELOPER_DIR` environment variable
*
* @returns {Promise<string>} Full path to Xcode Developer subfolder
* @throws {Error} If it is not possible to retrieve a proper path
* @privateRemarks This method assumes `DEVELOPER_DIR` is defined.
*/
async function getPathFromDeveloperDir() {
const developerRoot = /** @type {string} */ (process.env.DEVELOPER_DIR);
const { CFBundleIdentifier } = await (0, helpers_1.readXcodePlist)(developerRoot);
if (CFBundleIdentifier === XCODE_BUNDLE_ID) {
return developerRoot;
}
const msg = `The path to Xcode Developer dir '${developerRoot}' provided in DEVELOPER_DIR ` +
`environment variable is not a valid path`;
log.error(msg);
throw new Error(msg);
}
/**
* Retrieves the full path to Xcode Developer subfolder.
* If `DEVELOPER_DIR` environment variable is provided then its value has a priority.
* @param {number} timeout The maximum timeout for xcode-select execution
* @returns {Promise<string>} Full path to Xcode Developer subfolder timeout
* @throws {Error} If there was an error while retrieving the path.
*/
const getPath = lodash_1.default.memoize(
/**
* @param {number} timeout
* @returns {Promise<string>}
*/
(timeout = helpers_1.XCRUN_TIMEOUT) => process.env.DEVELOPER_DIR ? getPathFromDeveloperDir() : getPathFromXcodeSelect(timeout));
exports.getPath = getPath;
/**
* Retrieves Xcode version
*
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands.
* @returns {Promise<import("semver").SemVer | null>} Xcode version
* @throws {Error} If there was a failure while retrieving the version
*/
async function getVersionWithoutRetry(timeout = helpers_1.XCRUN_TIMEOUT) {
const developerPath = await getPath(timeout);
// we want to read the CFBundleShortVersionString from Xcode's plist.
const { CFBundleShortVersionString } = await (0, helpers_1.readXcodePlist)(developerPath);
return semver.coerce(CFBundleShortVersionString);
}
/**
* Retrieves Xcode version or the cached one if called more than once
*
* @param {number} retries How many retries to apply for version retrieval
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @returns {Promise<import("semver").SemVer | null>} Xcode version
* @throws {Error} If there was a failure while retrieving the version
*/
const getVersionMemoized = lodash_1.default.memoize(function getVersionMemoized(retries = DEFAULT_NUMBER_OF_RETRIES, timeout = helpers_1.XCRUN_TIMEOUT) {
return (0, asyncbox_1.retry)(retries, getVersionWithoutRetry, timeout);
});
/**
* @typedef {Object} XcodeVersion
* @property {string} versionString Xcode version as a string
* @property {number} versionFloat Xcode version as a float number
* @property {number} major Major number of Xcode version
* @property {number} minor Minor number of Xcode version
* @property {number} [patch] Patch number of Xcode version (if exists)
* @property {() => string} toString Returns Xcode version as a string
*/
/**
* Retrieves Xcode version
*
* @param {boolean} parse [false] Whether to parse the version to a XcodeVersion version
* @param {number} retries [2] How many retries to apply for getting the version number
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @returns {Promise<XcodeVersion | string>} Xcode version depending on the value of `parse` flag
* @throws {Error} If there was a failure while retrieving the version
*/
async function getVersion(parse = false, retries = DEFAULT_NUMBER_OF_RETRIES, timeout = helpers_1.XCRUN_TIMEOUT) {
const version = /** @type {import('semver').SemVer} */ (await getVersionMemoized(retries, timeout));
// xcode version strings are not exactly semver string: patch versions of 0
// are removed (e.g., '10.0.0' => '10.0')
const versionString = version.patch > 0 ? version.version : `${version.major}.${version.minor}`;
if (!parse) {
return versionString;
}
return {
versionString,
versionFloat: parseFloat(versionString),
major: version.major,
minor: version.minor,
patch: version.patch > 0 ? version.patch : undefined,
toString() {
return versionString;
},
};
}
/**
* Check https://trac.macports.org/wiki/XcodeVersionInfo
* to see the actual mapping between clang and other components.
*
* @returns {Promise<string|null>} The actual Clang version in x.x.x.x or x.x.x format,
* which is supplied with Command Line Tools. `null` is returned
* if CLT are not installed.
*/
async function getClangVersion() {
try {
await support_1.fs.which('clang');
}
catch {
log.info('Cannot find clang executable on the local system. ' +
'Are Xcode Command Line Tools installed?');
return null;
}
const { stdout } = await (0, teen_process_1.exec)('clang', ['--version']);
const match = /clang-([0-9.]+)/.exec(stdout);
if (!match) {
log.info(`Cannot parse clang version from ${stdout}`);
return null;
}
return match[1];
}
/**
* Retrieves the maximum version of iOS SDK supported by the installed Xcode
*
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @returns {Promise<string>} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
async function getMaxIOSSDKWithoutRetry(timeout = helpers_1.XCRUN_TIMEOUT) {
const args = ['--sdk', 'iphonesimulator', '--show-sdk-version'];
const { stdout } = await (0, helpers_1.runXcrunCommand)(args, timeout);
const sdkVersion = stdout.trim();
const match = /\d.\d/.exec(stdout);
if (!match) {
throw new Error(`xcrun returned a non-numeric iOS SDK version: '${sdkVersion}'`);
}
return sdkVersion;
}
/**
* Retrieves the maximum version of iOS SDK supported by the installed Xcode
*
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @param {number} retries The maximum number of retries
* @returns {string} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
const getMaxIOSSDK = lodash_1.default.memoize(function getMaxIOSSDK(retries = DEFAULT_NUMBER_OF_RETRIES, timeout = helpers_1.XCRUN_TIMEOUT) {
return (0, asyncbox_1.retry)(retries, getMaxIOSSDKWithoutRetry, timeout);
});
exports.getMaxIOSSDK = getMaxIOSSDK;
/**
* Retrieves the maximum version of tvOS SDK supported by the installed Xcode
*
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @returns {Promise<string>} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
async function getMaxTVOSSDKWithoutRetry(timeout = helpers_1.XCRUN_TIMEOUT) {
const args = ['--sdk', 'appletvsimulator', '--show-sdk-version'];
const { stdout } = await (0, helpers_1.runXcrunCommand)(args, timeout);
const sdkVersion = stdout.trim();
if (isNaN(parseFloat(sdkVersion))) {
throw new Error(`xcrun returned a non-numeric tvOS SDK version: '${sdkVersion}'`);
}
return sdkVersion;
}
/**
* Retrieves the maximum version of tvOS SDK supported by the installed Xcode
*
* @throws {Error} If the SDK version number cannot be determined
*/
const getMaxTVOSSDK = lodash_1.default.memoize(
/**
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @param {number} retries The maximum number of retries
* @returns {Promise<string>} The SDK version
*/
async function getMaxTVOSSDK(retries = DEFAULT_NUMBER_OF_RETRIES, timeout = helpers_1.XCRUN_TIMEOUT) {
return /** @type {string} */ (await (0, asyncbox_1.retry)(retries, getMaxTVOSSDKWithoutRetry, timeout));
});
exports.getMaxTVOSSDK = getMaxTVOSSDK;
//# sourceMappingURL=xcode.js.map
;