UNPKG

appium-espresso-driver

Version:
203 lines (177 loc) 6.66 kB
import { SubProcess } from 'teen_process'; import { fs, system } from 'appium/support'; import _ from 'lodash'; import path from 'path'; import { EOL } from 'os'; import { updateDependencyLines } from './utils'; const GRADLE_VERSION_KEY = 'gradle'; const GRADLE_URL_PREFIX = 'distributionUrl='; const GRADLE_URL_TEMPLATE = 'https\\://services.gradle.org/distributions/gradle-VERSION-all.zip'; const DEPENDENCY_PROP_NAMES = ['additionalAppDependencies', 'additionalAndroidTestDependencies']; const VERSION_KEYS = [ GRADLE_VERSION_KEY, 'androidGradlePlugin', 'compileSdk', 'buildTools', 'minSdk', 'targetSdk', 'kotlin', 'sourceCompatibility', 'targetCompatibility', 'jvmTarget', 'composeVersion', 'espressoVersion', 'annotationVersion' ]; function buildServerSigningConfig (args) { return { zipAlign: true, keystoreFile: args.keystoreFile, keystorePassword: args.keystorePassword, keyAlias: args.keyAlias, keyPassword: args.keyPassword }; } class ServerBuilder { constructor (log, args = {}) { this.log = log; this.serverPath = args.serverPath; this.showGradleLog = args.showGradleLog; const buildConfiguration = args.buildConfiguration || {}; const versionConfiguration = buildConfiguration.toolsVersions || {}; this.serverVersions = _.reduce(versionConfiguration, (acc, value, key) => { if (VERSION_KEYS.includes(key)) { acc[key] = value; } else { log.warn(`Got unexpected '${key}' in toolsVersion block of the build configuration`); } return acc; }, {}); this.testAppPackage = args.testAppPackage; this.signingConfig = args.signingConfig; for (const propName of DEPENDENCY_PROP_NAMES) { this[propName] = buildConfiguration[propName] || []; } } async build () { if (this.serverVersions[GRADLE_VERSION_KEY]) { await this.setGradleWrapperVersion(this.serverVersions[GRADLE_VERSION_KEY]); } await this.insertAdditionalDependencies(); await this.runBuildProcess(); } /** * @returns {{cmd: string, args: string[]}} */ getCommand () { const cmd = system.isWindows() ? 'gradlew.bat' : path.resolve(this.serverPath, 'gradlew'); const buildProperty = (key, value) => value ? `-P${key}=${value}` : null; /** @type {string[]} */ // @ts-ignore Typescript does not understand filter(Boolean) const args = VERSION_KEYS .filter((key) => key !== GRADLE_VERSION_KEY) .map((key) => { const serverVersion = this.serverVersions[key]; const gradleProperty = `appium${key.charAt(0).toUpperCase()}${key.slice(1)}`; return buildProperty(gradleProperty, serverVersion); }) .filter(Boolean); if (this.signingConfig) { // @ts-ignore Typescript does not understand filter(Boolean) args.push(...( _.keys(this.signingConfig) .map((key) => [`appium${_.upperFirst(key)}`, this.signingConfig[key]]) .map(([k, v]) => buildProperty(k, v)) .filter(Boolean) )); } if (this.testAppPackage) { // @ts-ignore Typescript does not understand filter(Boolean) args.push(buildProperty('appiumTargetPackage', this.testAppPackage)); } args.push('app:assembleAndroidTest'); return {cmd, args}; } async setGradleWrapperVersion (version) { const propertiesPath = path.resolve(this.serverPath, 'gradle', 'wrapper', 'gradle-wrapper.properties'); const originalProperties = await fs.readFile(propertiesPath, 'utf8'); const newProperties = this.updateGradleDistUrl(originalProperties, version); await fs.writeFile(propertiesPath, newProperties, 'utf8'); } updateGradleDistUrl (propertiesContent, version) { return propertiesContent.replace( new RegExp(`^(${_.escapeRegExp(GRADLE_URL_PREFIX)}).+$`, 'gm'), `$1${GRADLE_URL_TEMPLATE.replace('VERSION', version)}` ); } async insertAdditionalDependencies () { let hasAdditionalDeps = false; for (const propName of DEPENDENCY_PROP_NAMES) { if (!_.isArray(this[propName])) { throw new Error(`'${propName}' must be an array`); } if (_.isEmpty(this[propName].filter((line) => _.trim(line)))) { continue; } for (const dep of this[propName]) { if (/[\s'\\$]/.test(dep)) { throw new Error('Single quotes, dollar characters and whitespace characters' + ` are disallowed in additional dependencies: ${dep}`); } } hasAdditionalDeps = true; } if (!hasAdditionalDeps) { return; } const buildPath = path.resolve(this.serverPath, 'app', 'build.gradle.kts'); let configuration = await fs.readFile(buildPath, 'utf8'); for (const propName of DEPENDENCY_PROP_NAMES) { const prefix = propName === DEPENDENCY_PROP_NAMES[0] ? 'api' : 'androidTestImplementation'; const deps = this[propName] .filter((line) => _.trim(line)) .map((line) => `${prefix}("${line}")`); if (_.isEmpty(deps)) { continue; } this.log.info(`Adding the following ${propName} to build.gradle.kts: ${deps}`); configuration = updateDependencyLines(configuration, propName, deps); } await fs.writeFile(buildPath, configuration, 'utf8'); } async runBuildProcess () { const {cmd, args} = this.getCommand(); this.log.debug(`Beginning build with command '${cmd} ${args.join(' ')}' ` + `in directory '${this.serverPath}'`); const gradlebuild = new SubProcess(cmd, args, { cwd: this.serverPath, stdio: ['ignore', 'pipe', 'pipe'], // https://github.com/nodejs/node/issues/52572 shell: system.isWindows(), windowsVerbatimArguments: true }); /** @type {string[]} */ let gradleError = []; const logMsg = `Output from Gradle ${this.showGradleLog ? 'will' : 'will not'} be logged`; this.log.debug(`${logMsg}. To change this, use 'showGradleLog' desired capability`); gradlebuild.on('line-stderr', (line) => { this.log.warn(`[Gradle] ${line}`); gradleError.push(line); }); gradlebuild.on('line-stdout', (line) => this.log.info(`[Gradle] ${line}`)); try { await gradlebuild.start(); await gradlebuild.join(); } catch (err) { const msg = `Unable to build Espresso server - ${err.message}\n` + `Gradle error message:${EOL}${gradleError.join('\n')}`; throw this.log.errorWithException(msg); } finally { gradlebuild.removeAllListeners(); } } } export { ServerBuilder, VERSION_KEYS, GRADLE_URL_TEMPLATE, buildServerSigningConfig }; export default ServerBuilder;