Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Fix getting info for Android tools #906

Merged
merged 2 commits into from
Mar 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,8 +852,8 @@ interface ISysInfoData {
adbVer: string;
/** Whether iTunes is installed on the machine */
itunesInstalled: boolean;
/** Whether `android` executable can be run */
androidInstalled: boolean;
/** Whether `emulator` executable can be run */
emulatorInstalled: boolean;
/** mono version, relevant on Mac only **/
monoVer: string;
/** git version string, as returned by `git --version` **/
Expand All @@ -875,7 +875,7 @@ interface ISysInfo {
* @param {any} androidToolsInfo Defines paths to adb and android executables.
* @return {Promise<ISysInfoData>} Object containing information for current system.
*/
getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string, pathToAndroid: string }): Promise<ISysInfoData>;
getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string }): Promise<ISysInfoData>;

/** Returns Java version. **/
getJavaVersion(): Promise<string>;
Expand Down
3 changes: 3 additions & 0 deletions definitions/mobile.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,9 @@ declare module Mobile {

interface IAndroidEmulatorServices extends IEmulatorPlatformServices {
getAllRunningEmulators(): Promise<string[]>;
pathToEmulatorExecutable: string;
getInfoFromAvd(avdName: string): Mobile.IAvdInfo;
getAvds(): string[];
}

interface IiSimDevice {
Expand Down
70 changes: 45 additions & 25 deletions mobile/android/android-emulator-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class AndroidEmulatorServices implements Mobile.IAndroidEmulatorServices {
private $logcatHelper: Mobile.ILogcatHelper,
private $options: ICommonOptions,
private $utils: IUtils,
private $injector: IInjector) {
private $injector: IInjector,
private $hostInfo: IHostInfo) {
iconv.extendNodeEncodings();
}

Expand All @@ -44,11 +45,27 @@ class AndroidEmulatorServices implements Mobile.IAndroidEmulatorServices {
this.adbFilePath = await this.$staticConfig.getAdbFilePath();
}

private get pathToEmulatorExecutable(): string {
public get pathToEmulatorExecutable(): string {
if (!this._pathToEmulatorExecutable) {
let androidHome = process.env.ANDROID_HOME;
let emulatorExecutableName = "emulator";
this._pathToEmulatorExecutable = androidHome ? path.join(androidHome, "tools", emulatorExecutableName) : emulatorExecutableName;
const androidHome = process.env.ANDROID_HOME;
const emulatorExecutableName = "emulator";

this._pathToEmulatorExecutable = emulatorExecutableName;

if (androidHome) {
// Check https://developer.android.com/studio/releases/sdk-tools.html (25.3.0)
// Since this version of SDK tools, the emulator is a separate package.
// However the emulator executable still exists in the "tools" dir.
const pathToEmulatorFromAndroidStudio = path.join(androidHome, emulatorExecutableName, emulatorExecutableName);

const realFilePath = this.$hostInfo.isWindows ? `${pathToEmulatorFromAndroidStudio}.exe` : pathToEmulatorFromAndroidStudio;

if (this.$fs.exists(realFilePath)) {
this._pathToEmulatorExecutable = pathToEmulatorFromAndroidStudio;
} else {
this._pathToEmulatorExecutable = path.join(androidHome, "tools", emulatorExecutableName);
}
}
}

return this._pathToEmulatorExecutable;
Expand Down Expand Up @@ -371,26 +388,39 @@ class AndroidEmulatorServices implements Mobile.IAndroidEmulatorServices {
}
}

public getInfoFromAvd(avdName: string): Mobile.IAvdInfo {
let iniFile = path.join(this.avdDir, avdName + ".ini"),
avdInfo: Mobile.IAvdInfo = this.parseAvdFile(avdName, iniFile);

if (avdInfo.path && this.$fs.exists(avdInfo.path)) {
iniFile = path.join(avdInfo.path, "config.ini");
avdInfo = this.parseAvdFile(avdName, iniFile, avdInfo);
}

return avdInfo;
}

public getAvds(): string[] {
let result: string[] = [];
if (this.$fs.exists(this.avdDir)) {
let entries = this.$fs.readDirectory(this.avdDir);
result = _.filter(entries, (e: string) => e.match(AndroidEmulatorServices.INI_FILES_MASK) !== null)
.map((e) => e.match(AndroidEmulatorServices.INI_FILES_MASK)[1]);
}
return result;
}

private getBestFit(): string {
let minVersion = this.$emulatorSettingsService.minVersion;

let best = _(this.getAvds())
.map(avd => this.getInfoFromAvd(avd))
.filter(avd => !!avd)
.maxBy(avd => avd.targetNum);

return (best && best.targetNum >= minVersion) ? best.name : null;
}

private getInfoFromAvd(avdName: string): Mobile.IAvdInfo {
let iniFile = path.join(this.avdDir, avdName + ".ini");
let avdInfo: Mobile.IAvdInfo = this.parseAvdFile(avdName, iniFile);
if (avdInfo.path && this.$fs.exists(avdInfo.path)) {
iniFile = path.join(avdInfo.path, "config.ini");
avdInfo = this.parseAvdFile(avdName, iniFile, avdInfo);
}
return avdInfo;
}

private parseAvdFile(avdName: string, avdFileName: string, avdInfo?: Mobile.IAvdInfo): Mobile.IAvdInfo {
if (!this.$fs.exists(avdFileName)) {
return null;
Expand Down Expand Up @@ -465,16 +495,6 @@ class AndroidEmulatorServices implements Mobile.IAndroidEmulatorServices {
return path.join(this.androidHomeDir, AndroidEmulatorServices.AVD_DIR_NAME);
}

private getAvds(): string[] {
let result: string[] = [];
if (this.$fs.exists(this.avdDir)) {
let entries = this.$fs.readDirectory(this.avdDir);
result = _.filter(entries, (e: string) => e.match(AndroidEmulatorServices.INI_FILES_MASK) !== null)
.map((e) => e.match(AndroidEmulatorServices.INI_FILES_MASK)[1]);
}
return result;
}

private async waitForEmulatorBootToComplete(emulatorId: string): Promise<void> {
this.$logger.printInfoMessageOnSameLine("Waiting for emulator device initialization...");

Expand Down
26 changes: 11 additions & 15 deletions sys-info-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export class SysInfoBase implements ISysInfo {
protected $hostInfo: IHostInfo,
protected $iTunesValidator: Mobile.IiTunesValidator,
protected $logger: ILogger,
protected $winreg: IWinReg) { }
protected $winreg: IWinReg,
protected $androidEmulatorServices: Mobile.IAndroidEmulatorServices) { }

private monoVerRegExp = /version (\d+[.]\d+[.]\d+) /gm;
private sysInfoCache: ISysInfoData = undefined;
Expand Down Expand Up @@ -134,7 +135,7 @@ export class SysInfoBase implements ISysInfo {
return this.cocoapodVersionCache;
}

public async getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string, pathToAndroid: string }): Promise<ISysInfoData> {
public async getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string }): Promise<ISysInfoData> {
if (!this.sysInfoCache) {
let res: ISysInfoData = Object.create(null);
let procOutput: string;
Expand Down Expand Up @@ -167,7 +168,6 @@ export class SysInfoBase implements ISysInfo {

res.cocoapodVer = await this.getCocoapodVersion();
let pathToAdb = androidToolsInfo ? androidToolsInfo.pathToAdb : "adb";
let pathToAndroid = androidToolsInfo ? androidToolsInfo.pathToAndroid : "android";

if (!androidToolsInfo) {
this.$logger.trace("'adb' and 'android' will be checked from PATH environment variable.");
Expand All @@ -176,7 +176,7 @@ export class SysInfoBase implements ISysInfo {
procOutput = await this.exec(`${quoteString(pathToAdb)} version`);
res.adbVer = procOutput ? procOutput.split(os.EOL)[0] : null;

res.androidInstalled = await this.checkAndroid(pathToAndroid);
res.emulatorInstalled = await this.checkEmulator();

procOutput = await this.exec("mono --version");
if (!!procOutput) {
Expand Down Expand Up @@ -212,17 +212,13 @@ export class SysInfoBase implements ISysInfo {
return null;
}

// `android -h` returns exit code 1 on successful invocation (Mac OS X for now, possibly Linux). Therefore, we cannot use $childProcess
private async checkAndroid(pathToAndroid: string): Promise<boolean> {
let result = false;
try {
if (pathToAndroid) {
let androidChildProcess = await this.$childProcess.spawnFromEvent(pathToAndroid, ["-h"], "close", {}, { throwError: false });
result = androidChildProcess && androidChildProcess.stdout && _.includes(androidChildProcess.stdout, "android");
}
} catch (err) {
this.$logger.trace(`Error while checking is ${pathToAndroid} installed. Error is: ${err.messge}`);
}
private async checkEmulator(): Promise<boolean> {
// emulator -help exits with code 1 on Windows, so we should parse the output.
// First line of it should be:
// Android Emulator usage: emulator [options] [-qemu args]
const emulatorHelp = await this.$childProcess.spawnFromEvent(this.$androidEmulatorServices.pathToEmulatorExecutable, ["-help"], "close", {}, { throwError: false });
const result = !!(emulatorHelp && emulatorHelp.stdout && emulatorHelp.stdout.indexOf("usage: emulator") !== -1);
this.$logger.trace(`The result of checking is Android Emulator installed is:${os.EOL}- stdout: ${emulatorHelp && emulatorHelp.stdout}${os.EOL}- stderr: ${emulatorHelp && emulatorHelp.stderr}`);

return result;
}
Expand Down
42 changes: 27 additions & 15 deletions test/unit-tests/sys-info-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,24 @@ interface IChildProcessResults {
nodeGypVersion: IChildProcessResultDescription;
xCodeVersion: IChildProcessResultDescription;
adbVersion: IChildProcessResultDescription;
androidInstalled: IChildProcessResultDescription;
emulatorInstalled: IChildProcessResultDescription;
monoVersion: IChildProcessResultDescription;
gradleVersion: IChildProcessResultDescription;
gitVersion: IChildProcessResultDescription;
podVersion: IChildProcessResultDescription;
}

function getResultFromChildProcess(childProcessResultDescription: IChildProcessResultDescription): any {
function getResultFromChildProcess(childProcessResultDescription: IChildProcessResultDescription, spawnFromEventOpts?: { throwError: boolean }): any {
if (childProcessResultDescription.shouldThrowError) {
throw new Error("This one throws error.");
if (spawnFromEventOpts && !spawnFromEventOpts.throwError) {
return {
stderr: "This one throws error.",
code: 1,
stdout: null
};
} else {
throw new Error("This one throws error.");
}
}

return childProcessResultDescription.result;
Expand All @@ -50,7 +58,7 @@ function createChildProcessResults(childProcessResult: IChildProcessResults): ID
"pod --version": childProcessResult.podVersion,
'"adb" version': childProcessResult.adbVersion,
"'adb' version": childProcessResult.adbVersion, // for Mac and Linux
'android': childProcessResult.androidInstalled,
'emulator': childProcessResult.emulatorInstalled,
"mono --version": childProcessResult.monoVersion,
"git --version": childProcessResult.gitVersion,
"gradle -v": childProcessResult.gradleVersion
Expand All @@ -65,8 +73,8 @@ function createTestInjector(childProcessResult: IChildProcessResults, hostInfoDa
return getResultFromChildProcess(childProcessResultDictionary[command]);
},

spawnFromEvent: (command: string, args: string[], event: string) => {
return getResultFromChildProcess(childProcessResultDictionary[command]);
spawnFromEvent: (command: string, args: string[], event: string, opts: any, spawnFromEventOpts?: { throwError: boolean }) => {
return getResultFromChildProcess(childProcessResultDictionary[command], spawnFromEventOpts);
}
});

Expand All @@ -93,6 +101,10 @@ function createTestInjector(childProcessResult: IChildProcessResults, hostInfoDa

injector.register("sysInfoBase", SysInfoBase);

injector.register("androidEmulatorServices", {
pathToEmulatorExecutable: "emulator"
});

return injector;
}

Expand All @@ -116,7 +128,7 @@ describe("sysInfoBase", () => {
nodeGypVersion: { result: "2.0.0" },
xCodeVersion: { result: "6.4.0" },
adbVersion: { result: "Android Debug Bridge version 1.0.32" },
androidInstalled: { result: { stdout: "android" } },
emulatorInstalled: { result: { stdout: "Android Emulator usage: emulator [options] [-qemu args]" } },
monoVersion: { result: "version 1.0.6 " },
gradleVersion: { result: "Gradle 2.8" },
gitVersion: { result: "git version 1.9.5" },
Expand All @@ -134,7 +146,7 @@ describe("sysInfoBase", () => {
assert.deepEqual(result.javacVersion, "1.8.0_60");
assert.deepEqual(result.nodeGypVer, childProcessResult.nodeGypVersion.result);
assert.deepEqual(result.adbVer, childProcessResult.adbVersion.result);
assert.deepEqual(result.androidInstalled, true);
assert.deepEqual(result.emulatorInstalled, true);
assert.deepEqual(result.monoVer, "1.0.6");
assert.deepEqual(result.gradleVer, "2.8");
assert.deepEqual(result.gitVer, "1.9.5");
Expand Down Expand Up @@ -212,7 +224,7 @@ describe("sysInfoBase", () => {
nodeGypVersion: { shouldThrowError: true },
xCodeVersion: { shouldThrowError: true },
adbVersion: { shouldThrowError: true },
androidInstalled: { shouldThrowError: true },
emulatorInstalled: { shouldThrowError: true },
monoVersion: { shouldThrowError: true },
gradleVersion: { shouldThrowError: true },
gitVersion: { shouldThrowError: true },
Expand All @@ -221,18 +233,18 @@ describe("sysInfoBase", () => {
});

describe("when android info is incorrect", () => {
it("pathToAdb and pathToAndroid are null", async () => {
it("pathToAdb is null", async () => {
childProcessResult.adbVersion = {
result: null
};
childProcessResult.androidInstalled = {
result: false
childProcessResult.emulatorInstalled = {
result: null
};
testInjector = createTestInjector(childProcessResult, { isWindows: false, isDarwin: false, dotNetVersion: "4.5.1" }, null);
sysInfoBase = testInjector.resolve("sysInfoBase");
let result = await sysInfoBase.getSysInfo(toolsPackageJson, { pathToAdb: null, pathToAndroid: null });
let result = await sysInfoBase.getSysInfo(toolsPackageJson, { pathToAdb: null });
assert.deepEqual(result.adbVer, null);
assert.deepEqual(result.androidInstalled, false);
assert.deepEqual(result.emulatorInstalled, false);
});
});

Expand All @@ -246,7 +258,7 @@ describe("sysInfoBase", () => {
assert.deepEqual(result.nodeGypVer, null);
assert.deepEqual(result.xcodeVer, null);
assert.deepEqual(result.adbVer, null);
assert.deepEqual(result.androidInstalled, false);
assert.deepEqual(result.emulatorInstalled, false);
assert.deepEqual(result.monoVer, null);
assert.deepEqual(result.gradleVer, null);
assert.deepEqual(result.gitVer, null);
Expand Down