appium-xcode 4.0.5 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +7 -29
- package/build/index.js +40 -33
- package/build/lib/helpers.js +55 -0
- package/build/lib/helpers.js.map +1 -0
- package/build/lib/xcode.js +57 -197
- package/build/lib/xcode.js.map +1 -1
- package/index.js +19 -15
- package/lib/helpers.js +72 -0
- package/lib/xcode.js +130 -263
- package/package.json +2 -2
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import B from 'bluebird';
|
|
3
|
+
import { exec } from 'teen_process';
|
|
4
|
+
import { fs, plist } from '@appium/support';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
export const XCRUN_TIMEOUT = 15000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Executes 'xcrun' command line utility
|
|
11
|
+
*
|
|
12
|
+
* @param {string[]} args xcrun arguments
|
|
13
|
+
* @param {number} timeout [15000] The maximum number of milliseconds to wait until xcrun exists
|
|
14
|
+
* @returns {Promise<import("teen_process").ExecResult>} The result of xcrun execution
|
|
15
|
+
* @throws {Error} If xcrun returned non-zero exit code or timed out
|
|
16
|
+
*/
|
|
17
|
+
export async function runXcrunCommand (args, timeout = XCRUN_TIMEOUT) {
|
|
18
|
+
try {
|
|
19
|
+
return await exec('xcrun', args, {timeout});
|
|
20
|
+
} catch (err) {
|
|
21
|
+
// the true error can be hidden within the stderr
|
|
22
|
+
if (err.stderr) {
|
|
23
|
+
err.message = `${err.message}: ${err.stderr}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Uses macOS Spotlight service to detect where the given app is installed
|
|
32
|
+
*
|
|
33
|
+
* @param {string} bundleId Bundle identifier of the target app
|
|
34
|
+
* @returns {Promise<string[]>} Full paths to where the app with the given bundle id is present.
|
|
35
|
+
*/
|
|
36
|
+
export async function findAppPaths (bundleId) {
|
|
37
|
+
let stdout;
|
|
38
|
+
try {
|
|
39
|
+
({stdout} = await exec('/usr/bin/mdfind', [
|
|
40
|
+
`kMDItemCFBundleIdentifier=${bundleId}`
|
|
41
|
+
]));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const matchedPaths = _.trim(stdout)
|
|
47
|
+
.split('\n')
|
|
48
|
+
.map(_.trim)
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
if (_.isEmpty(matchedPaths)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const results = matchedPaths.map((p) => (async () => {
|
|
54
|
+
if (await fs.exists(p)) {
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
})());
|
|
58
|
+
return (await B.all(results)).filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Finds and retrieves the content of the Xcode's Info.plist file
|
|
63
|
+
*
|
|
64
|
+
* @param {string} developerRoot Full path to the Contents/Developer folder under Xcode.app root
|
|
65
|
+
* @returns {Promise<object>} All plist entries as an object or an empty object if no plist was found
|
|
66
|
+
*/
|
|
67
|
+
export async function readXcodePlist (developerRoot) {
|
|
68
|
+
const plistPath = path.resolve(developerRoot, '..', 'Info.plist');
|
|
69
|
+
return await fs.exists(plistPath)
|
|
70
|
+
? await plist.parsePlistFile(plistPath)
|
|
71
|
+
: {};
|
|
72
|
+
}
|
package/lib/xcode.js
CHANGED
|
@@ -1,141 +1,133 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fs, logger } from '@appium/support';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { retry } from 'asyncbox';
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import { parse as parsePlistData } from 'plist';
|
|
6
5
|
import { exec } from 'teen_process';
|
|
7
6
|
import semver from 'semver';
|
|
7
|
+
import {
|
|
8
|
+
runXcrunCommand, findAppPaths, XCRUN_TIMEOUT, readXcodePlist
|
|
9
|
+
} from './helpers';
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const XCRUN_TIMEOUT = 15000;
|
|
13
|
-
const XCODE_SUBDIR = '/Contents/Developer';
|
|
14
|
-
const DEFAULT_NUMBER_OF_RETRIES = 3;
|
|
11
|
+
const DEFAULT_NUMBER_OF_RETRIES = 2;
|
|
12
|
+
const XCODE_BUNDLE_ID = 'com.apple.dt.Xcode';
|
|
15
13
|
|
|
16
14
|
const log = logger.getLogger('Xcode');
|
|
17
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves the full path to Xcode Developer subfolder via xcode-select
|
|
18
|
+
*
|
|
19
|
+
* @param {number} timeout The maximum timeout for xcode-select execution
|
|
20
|
+
* @returns {Promise<string>} Full path to Xcode Developer subfolder
|
|
21
|
+
* @throws {Error} If it is not possible to retrieve a proper path
|
|
22
|
+
*/
|
|
23
|
+
async function getPathFromXcodeSelect (timeout = XCRUN_TIMEOUT) {
|
|
24
|
+
const generateErrorMessage = async (prefix) => {
|
|
25
|
+
const xcodePaths = await findAppPaths(XCODE_BUNDLE_ID);
|
|
26
|
+
if (_.isEmpty(xcodePaths)) {
|
|
27
|
+
return `${prefix}. Consider installing Xcode to address this issue.`;
|
|
28
|
+
}
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
30
|
+
const proposals = xcodePaths.map((p) => ` sudo xcode-select -s "${path.join(p, 'Contents', 'Developer')}"`);
|
|
31
|
+
return `${prefix}. ` +
|
|
32
|
+
`Consider running${proposals.length > 1 ? ' any of' : ''}:\n${'\n'.join(proposals)}\nto address this issue.`;
|
|
33
|
+
};
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
let stdout;
|
|
24
36
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return res;
|
|
30
|
-
} catch (err) {
|
|
31
|
-
// the true error can be hidden within the stderr
|
|
32
|
-
if (err.stderr) {
|
|
33
|
-
err.message = `${err.message}: ${err.stderr}`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
throw err;
|
|
37
|
+
({stdout} = await exec('xcode-select', ['--print-path'], {timeout}));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
log.errorAndThrow(`Cannot determine the path to Xcode by running 'xcode-select -p' command. ` +
|
|
40
|
+
`Original error: ${e.stderr || e.message}`);
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Not clear why. As a workaround, Appium can reliably deduce the version in use by checking
|
|
43
|
-
// the locations xcode-select uses to store the selected version's path. This should be 100%
|
|
44
|
-
// reliable so long as the link locations remain the same. However, since we're relying on
|
|
45
|
-
// hardcoded paths, this approach will break the next time Apple changes the symlink location.
|
|
46
|
-
log.warn(`Finding XcodePath by symlink because ${failMessage}`);
|
|
47
|
-
|
|
48
|
-
const symlinkPath = '/var/db/xcode_select_link';
|
|
49
|
-
const legacySymlinkPath = '/usr/share/xcode-select/xcode_dir_link'; // Xcode < 5.x
|
|
50
|
-
let xcodePath = null;
|
|
51
|
-
|
|
52
|
-
// xcode-select allows users to override its settings with the DEVELOPER_DIR env var,
|
|
53
|
-
// so check that first
|
|
54
|
-
if (util.hasContent(env.DEVELOPER_DIR)) {
|
|
55
|
-
const customPath = hasExpectedSubDir(env.DEVELOPER_DIR)
|
|
56
|
-
? env.DEVELOPER_DIR
|
|
57
|
-
: env.DEVELOPER_DIR + XCODE_SUBDIR;
|
|
58
|
-
|
|
59
|
-
if (await fs.exists(customPath)) {
|
|
60
|
-
xcodePath = customPath;
|
|
61
|
-
} else {
|
|
62
|
-
let mesg = `Could not find path to Xcode, environment variable ` +
|
|
63
|
-
`DEVELOPER_DIR set to: ${env.DEVELOPER_DIR} ` +
|
|
64
|
-
`but no Xcode found`;
|
|
65
|
-
log.warn(mesg);
|
|
66
|
-
throw new Error(mesg);
|
|
67
|
-
}
|
|
68
|
-
} else if (await fs.exists(symlinkPath)) {
|
|
69
|
-
xcodePath = await fs.readlink(symlinkPath);
|
|
70
|
-
} else if (await fs.exists(legacySymlinkPath)) {
|
|
71
|
-
xcodePath = await fs.readlink(legacySymlinkPath);
|
|
42
|
+
// trim and remove trailing slash
|
|
43
|
+
const developerRoot = stdout.replace(/\/$/, '').trim();
|
|
44
|
+
if (!developerRoot) {
|
|
45
|
+
log.errorAndThrow(await generateErrorMessage(`'xcode-select -p' returned an empty string`));
|
|
72
46
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
47
|
+
// xcode-select might also return a path to command line tools
|
|
48
|
+
const {CFBundleIdentifier} = await readXcodePlist(developerRoot);
|
|
49
|
+
if (CFBundleIdentifier === XCODE_BUNDLE_ID) {
|
|
50
|
+
return developerRoot;
|
|
76
51
|
}
|
|
77
52
|
|
|
78
|
-
|
|
79
|
-
// other checks failed. Either Apple has moved the symlink to a new location or the user
|
|
80
|
-
// is not using the default install. 99.999% chance it's the latter, so issue a warning
|
|
81
|
-
// should we ever hit the edge case.
|
|
82
|
-
let msg = `Could not find path to Xcode by symlinks located in ${symlinkPath}, or ${legacySymlinkPath}`;
|
|
83
|
-
log.warn(msg);
|
|
84
|
-
throw new Error(msg);
|
|
53
|
+
log.errorAndThrow(await generateErrorMessage(`'${developerRoot}' is not a valid Xcode path`));
|
|
85
54
|
}
|
|
86
55
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Retrieves the full path to Xcode Developer subfolder via DEVELOPER_DIR environment variable
|
|
58
|
+
*
|
|
59
|
+
* @returns {Promise<string>} Full path to Xcode Developer subfolder
|
|
60
|
+
* @throws {Error} If it is not possible to retrieve a proper path
|
|
61
|
+
*/
|
|
62
|
+
async function getPathFromDeveloperDir () {
|
|
63
|
+
const developerRoot = process.env.DEVELOPER_DIR;
|
|
64
|
+
const {CFBundleIdentifier} = await readXcodePlist(developerRoot);
|
|
65
|
+
if (CFBundleIdentifier === XCODE_BUNDLE_ID) {
|
|
66
|
+
return developerRoot;
|
|
95
67
|
}
|
|
96
68
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} else {
|
|
100
|
-
const msg = `xcode-select could not find xcode. Path '${xcodeFolderPath}' does not exist.`;
|
|
101
|
-
log.errorAndThrow(msg);
|
|
102
|
-
}
|
|
69
|
+
log.errorAndThrow(`The path to Xcode Developer dir '${developerRoot}' provided in DEVELOPER_DIR ` +
|
|
70
|
+
`environment variable is not a valid path`);
|
|
103
71
|
}
|
|
104
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Retrieves the full path to Xcode Developer subfolder.
|
|
75
|
+
* If DEVELOPER_DIR environment variable is provided then its value has a priority.
|
|
76
|
+
*
|
|
77
|
+
* @property {number} timeout [15000] The maximum timeout for xcode-select execution
|
|
78
|
+
* @returns {string} Full path to Xcode Developer subfolder
|
|
79
|
+
* @throws {Error} If there was an error while retrieving the path.
|
|
80
|
+
*/
|
|
105
81
|
const getPath = _.memoize(function getPath (timeout = XCRUN_TIMEOUT) {
|
|
106
|
-
|
|
107
|
-
// then we try using the symlinks that Apple has by default
|
|
108
|
-
return (async () => {
|
|
109
|
-
try {
|
|
110
|
-
return await getPathFromXcodeSelect(timeout);
|
|
111
|
-
} catch (e) {
|
|
112
|
-
return await getPathFromSymlink(e.message);
|
|
113
|
-
}
|
|
114
|
-
})();
|
|
82
|
+
return process.env.DEVELOPER_DIR ? getPathFromDeveloperDir() : getPathFromXcodeSelect(timeout);
|
|
115
83
|
});
|
|
116
84
|
|
|
117
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves Xcode version
|
|
87
|
+
*
|
|
88
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands.
|
|
89
|
+
* @returns {Promise<import("semver").SemVer | null>} Xcode version
|
|
90
|
+
* @throws {Error} If there was a failure while retrieving the version
|
|
91
|
+
*/
|
|
118
92
|
async function getVersionWithoutRetry (timeout = XCRUN_TIMEOUT) {
|
|
119
|
-
const
|
|
120
|
-
|
|
93
|
+
const developerPath = await getPath(timeout);
|
|
121
94
|
// we want to read the CFBundleShortVersionString from Xcode's plist.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (!await fs.exists(plistPath)) {
|
|
126
|
-
throw new Error(`Could not get Xcode version. ${plistPath} does not exist on disk.`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const version = await plist.parsePlistFile(plistPath);
|
|
130
|
-
return semver.coerce(version.CFBundleShortVersionString);
|
|
95
|
+
const {CFBundleShortVersionString} = await readXcodePlist(developerPath);
|
|
96
|
+
return semver.coerce(CFBundleShortVersionString);
|
|
131
97
|
}
|
|
132
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Retrieves Xcode version or the cached one if called more than once
|
|
101
|
+
*
|
|
102
|
+
* @param {number} retries [2] How many retries to apply for version retrieval
|
|
103
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
|
|
104
|
+
* @returns {Promise<import("semver").SemVer | null>} Xcode version
|
|
105
|
+
* @throws {Error} If there was a failure while retrieving the version
|
|
106
|
+
*/
|
|
133
107
|
const getVersionMemoized = _.memoize(
|
|
134
108
|
function getVersionMemoized (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
|
|
135
109
|
return retry(retries, getVersionWithoutRetry, timeout);
|
|
136
110
|
}
|
|
137
111
|
);
|
|
138
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @typedef {Object} XcodeVersion
|
|
115
|
+
* @property {string} versionString Xcode version as a string
|
|
116
|
+
* @property {number} versionFloat Xcode version as a float number
|
|
117
|
+
* @property {number} major Major number of Xcode version
|
|
118
|
+
* @property {number} minor Minor number of Xcode version
|
|
119
|
+
* @property {number?} patch Patch number of Xcode version (if exists)
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Retrieves Xcode version
|
|
124
|
+
*
|
|
125
|
+
* @param {boolean} parse [false] Whether to parse the version to a XcodeVersion version
|
|
126
|
+
* @param {number} retries [2] How many retries to apply for getting the version number
|
|
127
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
|
|
128
|
+
* @returns {Promise<XcodeVersion | string>} Xcode version depending on the value of `parse` flag
|
|
129
|
+
* @throws {Error} If there was a failure while retrieving the version
|
|
130
|
+
*/
|
|
139
131
|
async function getVersion (parse = false, retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
|
|
140
132
|
const version = await getVersionMemoized(retries, timeout);
|
|
141
133
|
// xcode version strings are not exactly semver string: patch versions of 0
|
|
@@ -157,37 +149,11 @@ async function getVersion (parse = false, retries = DEFAULT_NUMBER_OF_RETRIES, t
|
|
|
157
149
|
};
|
|
158
150
|
}
|
|
159
151
|
|
|
160
|
-
async function getCommandLineToolsVersion () {
|
|
161
|
-
// there are a number of different ways that the CLI tools version has been
|
|
162
|
-
// represented. Try them from most reliable to least, falling down the chain
|
|
163
|
-
const getVersionFunctions = [
|
|
164
|
-
async () => {
|
|
165
|
-
let pkg = (await exec('pkgutil', ['--pkgs=com.apple.pkg.DevSDK_.*'])).stdout;
|
|
166
|
-
return (await exec('pkgutil', [`--pkg-info=${pkg.trim()}`])).stdout;
|
|
167
|
-
},
|
|
168
|
-
async () => (await exec('pkgutil', [`--pkg-info=com.apple.pkg.CLTools_Executables`])).stdout,
|
|
169
|
-
async () => (await exec('pkgutil', [`--pkg-info=com.apple.pkg.DeveloperToolsCLI`])).stdout,
|
|
170
|
-
];
|
|
171
|
-
let stdout;
|
|
172
|
-
for (let getVersion of getVersionFunctions) {
|
|
173
|
-
try {
|
|
174
|
-
stdout = await getVersion();
|
|
175
|
-
break;
|
|
176
|
-
} catch (ign) {
|
|
177
|
-
stdout = '';
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// stdout should have a line like `version: 8.0.0.0.1.1472435881`
|
|
182
|
-
let match = /^version: (.+)$/m.exec(stdout); // https://regex101.com/r/HV3x4d/1
|
|
183
|
-
return match ? match[1] : undefined;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
152
|
/**
|
|
187
153
|
* Check https://trac.macports.org/wiki/XcodeVersionInfo
|
|
188
154
|
* to see the actual mapping between clang and other components.
|
|
189
155
|
*
|
|
190
|
-
* @returns {
|
|
156
|
+
* @returns {Promise<string?>} The actual Clang version in x.x.x.x or x.x.x format,
|
|
191
157
|
* which is supplied with Command Line Tools. `null` is returned
|
|
192
158
|
* if CLT are not installed.
|
|
193
159
|
*/
|
|
@@ -208,170 +174,71 @@ async function getClangVersion () {
|
|
|
208
174
|
return match[1];
|
|
209
175
|
}
|
|
210
176
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const pathSuffix = 'Contents/Resources/Automation.tracetemplate';
|
|
219
|
-
let automationTraceTemplatePaths = [
|
|
220
|
-
path.resolve(pathPrefix, `AutomationInstrument.${extensions[0]}`, pathSuffix),
|
|
221
|
-
path.resolve(pathPrefix, `AutomationInstrument.${extensions[1]}`, pathSuffix)
|
|
222
|
-
];
|
|
223
|
-
|
|
224
|
-
if (await fs.exists(automationTraceTemplatePaths[0])) {
|
|
225
|
-
return automationTraceTemplatePaths[0];
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (await fs.exists(automationTraceTemplatePaths[1])) {
|
|
229
|
-
return automationTraceTemplatePaths[1];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const msg = 'Could not find Automation.tracetemplate in any of the following' +
|
|
233
|
-
`locations ${automationTraceTemplatePaths.toString()}`;
|
|
234
|
-
log.error(msg);
|
|
235
|
-
throw new Error(msg);
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const getAutomationTraceTemplatePath = _.memoize(
|
|
240
|
-
function getAutomationTraceTemplatePath (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
|
|
241
|
-
return retry(retries, getAutomationTraceTemplatePathWithoutRetry, timeout);
|
|
242
|
-
}
|
|
243
|
-
);
|
|
244
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Retrieves the maximum version of iOS SDK supported by the installed Xcode
|
|
179
|
+
*
|
|
180
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
|
|
181
|
+
* @returns {string} The SDK version
|
|
182
|
+
* @throws {Error} If the SDK version number cannot be determined
|
|
183
|
+
*/
|
|
245
184
|
async function getMaxIOSSDKWithoutRetry (timeout = XCRUN_TIMEOUT) {
|
|
246
|
-
const version = await getVersion(false, DEFAULT_NUMBER_OF_RETRIES, timeout);
|
|
247
|
-
if (version[0] === '4') {
|
|
248
|
-
return '6.1';
|
|
249
|
-
}
|
|
250
|
-
|
|
251
185
|
const args = ['--sdk', 'iphonesimulator', '--show-sdk-version'];
|
|
252
186
|
const {stdout} = await runXcrunCommand(args, timeout);
|
|
253
|
-
|
|
254
187
|
const sdkVersion = stdout.trim();
|
|
255
188
|
const match = /\d.\d/.exec(stdout);
|
|
256
|
-
|
|
257
189
|
if (!match) {
|
|
258
190
|
throw new Error(`xcrun returned a non-numeric iOS SDK version: '${sdkVersion}'`);
|
|
259
191
|
}
|
|
260
|
-
|
|
261
192
|
return sdkVersion;
|
|
262
193
|
}
|
|
263
194
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Retrieves the maximum version of iOS SDK supported by the installed Xcode
|
|
197
|
+
*
|
|
198
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
|
|
199
|
+
* @param {number} retries [2] The maximum number of retries
|
|
200
|
+
* @returns {string} The SDK version
|
|
201
|
+
* @throws {Error} If the SDK version number cannot be determined
|
|
202
|
+
*/
|
|
271
203
|
const getMaxIOSSDK = _.memoize(
|
|
272
204
|
function getMaxIOSSDK (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
|
|
273
|
-
|
|
274
|
-
return retry(retries, getMaxIOSSDKWithoutRetry, timeout);
|
|
275
|
-
} catch (err) {
|
|
276
|
-
log.warn(`Unable to retrieve maximum iOS version: ${err.message}`);
|
|
277
|
-
log.warn('Guessing from Xcode version');
|
|
278
|
-
return getMaxIOSSDKFromXcodeVersion(timeout);
|
|
279
|
-
}
|
|
205
|
+
return retry(retries, getMaxIOSSDKWithoutRetry, timeout);
|
|
280
206
|
}
|
|
281
207
|
);
|
|
282
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Retrieves the maximum version of tvOS SDK supported by the installed Xcode
|
|
211
|
+
*
|
|
212
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
|
|
213
|
+
* @returns {string} The SDK version
|
|
214
|
+
* @throws {Error} If the SDK version number cannot be determined
|
|
215
|
+
*/
|
|
283
216
|
async function getMaxTVOSSDKWithoutRetry (timeout = XCRUN_TIMEOUT) {
|
|
284
217
|
const args = ['--sdk', 'appletvsimulator', '--show-sdk-version'];
|
|
285
218
|
const {stdout} = await runXcrunCommand(args, timeout);
|
|
286
|
-
|
|
287
219
|
const sdkVersion = stdout.trim();
|
|
288
|
-
|
|
289
220
|
if (isNaN(parseFloat(sdkVersion))) {
|
|
290
221
|
throw new Error(`xcrun returned a non-numeric tvOS SDK version: '${sdkVersion}'`);
|
|
291
222
|
}
|
|
292
|
-
|
|
293
223
|
return sdkVersion;
|
|
294
224
|
}
|
|
295
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Retrieves the maximum version of tvOS SDK supported by the installed Xcode
|
|
228
|
+
*
|
|
229
|
+
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
|
|
230
|
+
* @param {number} retries [2] The maximum number of retries
|
|
231
|
+
* @returns {string} The SDK version
|
|
232
|
+
* @throws {Error} If the SDK version number cannot be determined
|
|
233
|
+
*/
|
|
296
234
|
const getMaxTVOSSDK = _.memoize(
|
|
297
235
|
function getMaxTVOSSDK (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
|
|
298
236
|
return retry(retries, getMaxTVOSSDKWithoutRetry, timeout);
|
|
299
237
|
}
|
|
300
238
|
);
|
|
301
239
|
|
|
302
|
-
async function getConnectedDevices (timeout = XCRUN_TIMEOUT) {
|
|
303
|
-
const cmd = '/usr/sbin/system_profiler';
|
|
304
|
-
const args = ['-xml', 'SPUSBDataType'];
|
|
305
|
-
let {stdout} = await exec(cmd, args, {timeout});
|
|
306
|
-
let plistContent = parsePlistData(stdout);
|
|
307
|
-
|
|
308
|
-
let devicesFound = [];
|
|
309
|
-
let entriesToSearch = [plistContent[0]];
|
|
310
|
-
while (entriesToSearch.length > 0) {
|
|
311
|
-
let currentEntry = entriesToSearch.pop();
|
|
312
|
-
if (currentEntry instanceof Array) {
|
|
313
|
-
entriesToSearch = entriesToSearch.concat(currentEntry);
|
|
314
|
-
} else if ((currentEntry._name &&
|
|
315
|
-
currentEntry._name.substring(0, 4) === 'iPad') ||
|
|
316
|
-
(currentEntry._name &&
|
|
317
|
-
currentEntry._name.substring(0, 6) === 'iPhone') ||
|
|
318
|
-
(currentEntry._name && _.includes(currentEntry._name, 'Apple TV'))) {
|
|
319
|
-
let deviceInfo = {
|
|
320
|
-
name: currentEntry._name,
|
|
321
|
-
udid: currentEntry.serial_num,
|
|
322
|
-
productId: currentEntry.product_id,
|
|
323
|
-
deviceVersion: currentEntry.bcd_device
|
|
324
|
-
};
|
|
325
|
-
devicesFound.push(deviceInfo);
|
|
326
|
-
} else if (currentEntry._items) {
|
|
327
|
-
entriesToSearch = entriesToSearch.concat(currentEntry._items);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return devicesFound;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
async function getInstrumentsPathWithoutRetry (timeout = XCRUN_TIMEOUT) {
|
|
334
|
-
const args = ['-find', 'instruments'];
|
|
335
|
-
let {stdout} = await runXcrunCommand(args, timeout);
|
|
336
|
-
|
|
337
|
-
if (!stdout) {
|
|
338
|
-
stdout = '';
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
let instrumentsPath = stdout.trim();
|
|
342
|
-
|
|
343
|
-
if (!instrumentsPath) {
|
|
344
|
-
throw new Error(`Could not find path to instruments binary using 'xcrun ${args.join(' ')}'`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return instrumentsPath;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const getInstrumentsPath = _.memoize(
|
|
351
|
-
function getInstrumentsPath (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
|
|
352
|
-
return retry(retries, getInstrumentsPathWithoutRetry, timeout);
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
function clearInternalCache () {
|
|
357
|
-
|
|
358
|
-
// memoized functions
|
|
359
|
-
const memoized = [
|
|
360
|
-
getPath, getVersionMemoized, getAutomationTraceTemplatePath, getMaxIOSSDK,
|
|
361
|
-
getMaxTVOSSDK, getInstrumentsPath,
|
|
362
|
-
];
|
|
363
|
-
|
|
364
|
-
memoized.forEach((f) => {
|
|
365
|
-
if (f.cache) {
|
|
366
|
-
f.cache = new _.memoize.Cache();
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
240
|
export {
|
|
372
|
-
getPath, getVersion,
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
getCommandLineToolsVersion, getMaxTVOSSDK, getMaxTVOSSDKWithoutRetry,
|
|
376
|
-
getClangVersion,
|
|
241
|
+
getPath, getVersion, getMaxIOSSDK, getMaxIOSSDKWithoutRetry,
|
|
242
|
+
getMaxTVOSSDK, getMaxTVOSSDKWithoutRetry, getClangVersion,
|
|
243
|
+
getPathFromDeveloperDir, getPathFromXcodeSelect,
|
|
377
244
|
};
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"ios",
|
|
7
7
|
"xcode"
|
|
8
8
|
],
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "5.0.1",
|
|
10
10
|
"author": "Appium Contributors",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"repository": {
|
|
@@ -90,6 +90,6 @@
|
|
|
90
90
|
"mocha": "^10.0.0",
|
|
91
91
|
"pre-commit": "^1.1.3",
|
|
92
92
|
"prettier": "^2.7.1",
|
|
93
|
-
"semantic-release": "^
|
|
93
|
+
"semantic-release": "^20.0.2"
|
|
94
94
|
}
|
|
95
95
|
}
|