Skip to content

Commit d8e2c83

Browse files
rosen-vladimirovrosen-vladimirov
rosen-vladimirov
authored andcommitted
Fix getting info for Android tools
Due to changes in Android SDK, we have to update the checks in CLI. While gathering system information, we check the android executable, which is no longer returning correct results. We use the android executable to find information about installed Android SDKs and to construct correct paths based on ANDROID_HOME. Fix this by listing directories inside ANDROID_HOME and find the information about installed SDKs from there. Fix messages pointing to `android` executable to point to `sdkmanager` (in case it exists). In order to fix this, we rely on the emulator executable, which is the real thing we need as it is the one that allows us to work with Android Emulators (this is changed in mobile-cli-lib). Fix sys-info checks and get correct path to emulator according to latest changes.
1 parent 242cd3b commit d8e2c83

File tree

7 files changed

+61
-95
lines changed

7 files changed

+61
-95
lines changed

lib/android-tools-info.ts

+46-82
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as path from "path";
22
import * as semver from "semver";
33
import { EOL } from "os";
4+
import { cache } from "./common/decorators";
45

56
export class AndroidToolsInfo implements IAndroidToolsInfo {
67
private static ANDROID_TARGET_PREFIX = "android";
@@ -15,65 +16,15 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
1516
private selectedCompileSdk: number;
1617
private installedTargetsCache: string[] = null;
1718
private androidHome = process.env["ANDROID_HOME"];
18-
private pathToAndroidExecutable: string;
19-
private _androidExecutableName: string;
20-
private get androidExecutableName(): string {
21-
if (!this._androidExecutableName) {
22-
this._androidExecutableName = "android";
23-
if (this.$hostInfo.isWindows) {
24-
this._androidExecutableName += ".bat";
25-
}
26-
}
27-
28-
return this._androidExecutableName;
29-
}
3019

3120
constructor(private $childProcess: IChildProcess,
3221
private $errors: IErrors,
3322
private $fs: IFileSystem,
3423
private $hostInfo: IHostInfo,
3524
private $logger: ILogger,
3625
private $options: IOptions,
37-
private $adb: Mobile.IAndroidDebugBridge,
3826
protected $staticConfig: Config.IStaticConfig) { }
3927

40-
public async getPathToAndroidExecutable(options?: { showWarningsAsErrors: boolean }): Promise<string> {
41-
if (options) {
42-
this.showWarningsAsErrors = options.showWarningsAsErrors;
43-
}
44-
if (!this.pathToAndroidExecutable) {
45-
if (this.validateAndroidHomeEnvVariable(this.androidHome)) {
46-
let androidPath = path.join(this.androidHome, "tools", this.androidExecutableName);
47-
if (!await this.trySetAndroidPath(androidPath) && !await this.trySetAndroidPath(this.androidExecutableName)) {
48-
this.printMessage(`Unable to find "${this.androidExecutableName}" executable file. Make sure you have set ANDROID_HOME environment variable correctly.`);
49-
}
50-
} else {
51-
this.$logger.trace("ANDROID_HOME environment variable is not set correctly.");
52-
}
53-
}
54-
55-
return this.pathToAndroidExecutable;
56-
}
57-
58-
private async trySetAndroidPath(androidPath: string): Promise<boolean> {
59-
let isAndroidPathCorrect = true;
60-
try {
61-
let result = await this.$adb.executeCommand(["--help"], { returnChildProcess: true });
62-
if (result && result.stdout) {
63-
this.$logger.trace(result.stdout);
64-
this.pathToAndroidExecutable = androidPath;
65-
} else {
66-
this.$logger.trace(`Unable to find android executable from '${androidPath}'.`);
67-
isAndroidPathCorrect = false;
68-
}
69-
} catch (err) {
70-
this.$logger.trace(`Error occurred while checking androidExecutable from '${androidPath}'. ${err.message}`);
71-
isAndroidPathCorrect = false;
72-
}
73-
74-
return isAndroidPathCorrect;
75-
}
76-
7728
public async getToolsInfo(): Promise<IAndroidToolsInfoData> {
7829
if (!this.toolsInfo) {
7930
let infoData: IAndroidToolsInfoData = Object.create(null);
@@ -90,14 +41,25 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
9041
return this.toolsInfo;
9142
}
9243

44+
@cache()
45+
private getPathToSdkManagementTool(): string {
46+
const pathToSdkmanager = path.join(this.androidHome, "tools", "bin", "sdkmanager") + (this.$hostInfo.isWindows ? ".bat": "");
47+
const pathToAndroidExecutable = path.join(this.androidHome, "tools", "android") + (this.$hostInfo.isWindows ? ".bat": "");
48+
const pathToExecutable = this.$fs.exists(pathToSdkmanager) ? pathToSdkmanager : pathToAndroidExecutable;
49+
50+
this.$logger.trace(`Path to Android SDK Management tool is: ${pathToExecutable}`);
51+
52+
return pathToExecutable.replace(this.androidHome, this.$hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME");
53+
}
54+
9355
public async validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): Promise<boolean> {
9456
let detectedErrors = false;
9557
this.showWarningsAsErrors = options && options.showWarningsAsErrors;
9658
let toolsInfoData = await this.getToolsInfo();
97-
let isAndroidHomeValid = this.validateAndroidHomeEnvVariable(toolsInfoData.androidHomeEnvVar);
59+
let isAndroidHomeValid = this.validateAndroidHomeEnvVariable();
9860
if (!toolsInfoData.compileSdkVersion) {
9961
this.printMessage(`Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later.`,
100-
"Run `$ android` to manage your Android SDK versions.");
62+
`Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`);
10163
detectedErrors = true;
10264
}
10365

@@ -111,7 +73,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
11173
message = `You have to install version ${versionRangeMatches[1]}.`;
11274
}
11375

114-
let invalidBuildToolsAdditionalMsg = 'Run `android` from your command-line to install required `Android Build Tools`.';
76+
let invalidBuildToolsAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` from your command-line to install required \`Android Build Tools\`.`;
11577
if (!isAndroidHomeValid) {
11678
invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.';
11779
}
@@ -121,7 +83,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
12183
}
12284

12385
if (!toolsInfoData.supportRepositoryVersion) {
124-
let invalidSupportLibAdditionalMsg = 'Run `$ android` to manage the Android Support Repository.';
86+
let invalidSupportLibAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage the Android Support Repository.`;
12587
if (!isAndroidHomeValid) {
12688
invalidSupportLibAdditionalMsg += ' In case you already have it installed, make sure `ANDROID_HOME` environment variable is set correctly.';
12789
}
@@ -153,6 +115,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
153115
if (options) {
154116
this.showWarningsAsErrors = options.showWarningsAsErrors;
155117
}
118+
156119
let additionalMessage = "You will not be able to build your projects for Android." + EOL
157120
+ "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + EOL +
158121
" described in " + this.$staticConfig.SYS_REQUIREMENTS_LINK;
@@ -186,6 +149,28 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
186149
return null;
187150
}
188151

152+
private _cachedAndroidHomeValidationResult: boolean = null;
153+
public validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean {
154+
if (this._cachedAndroidHomeValidationResult === null) {
155+
this.showWarningsAsErrors = options && options.showWarningsAsErrors;
156+
157+
this._cachedAndroidHomeValidationResult = true;
158+
let expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
159+
if (!this.androidHome || !this.$fs.exists(this.androidHome)) {
160+
this.printMessage("The ANDROID_HOME environment variable is not set or it points to a non-existent directory. You will not be able to perform any build-related operations for Android.",
161+
"To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory.");
162+
this._cachedAndroidHomeValidationResult = false;
163+
} else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(this.androidHome, dir))))) {
164+
this.printMessage("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.",
165+
"To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory, " +
166+
"where you will find `tools` and `platform-tools` directories.");
167+
this._cachedAndroidHomeValidationResult = false;
168+
}
169+
}
170+
171+
return this._cachedAndroidHomeValidationResult;
172+
}
173+
189174
private shouldGenerateTypings(): boolean {
190175
return this.$options.androidTypings;
191176
}
@@ -318,44 +303,23 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
318303
private async getInstalledTargets(): Promise<string[]> {
319304
if (!this.installedTargetsCache) {
320305
try {
321-
let pathToAndroidExecutable = await this.getPathToAndroidExecutable();
322-
if (pathToAndroidExecutable) {
323-
let result = await this.$childProcess.spawnFromEvent(pathToAndroidExecutable, ["list", "targets"], "close", {}, { throwError: false });
324-
if (result && result.stdout) {
325-
this.$logger.trace(result.stdout);
326-
this.installedTargetsCache = [];
327-
result.stdout.replace(/id: \d+ or "(.+)"/g, (m: string, p1: string) => (this.installedTargetsCache.push(p1), m));
328-
}
306+
const pathToInstalledTargets = path.join(this.androidHome, "platforms");
307+
if (!this.$fs.exists(pathToInstalledTargets)) {
308+
throw new Error("No Android Targets installed.");
329309
}
310+
311+
this.installedTargetsCache = this.$fs.readDirectory(pathToInstalledTargets);
312+
this.$logger.trace("Installed Android Targets are: ", this.installedTargetsCache); 1111
330313
} catch (err) {
331314
this.$logger.trace("Unable to get Android targets. Error is: " + err);
332315
}
333316
}
317+
334318
return this.installedTargetsCache;
335319
}
336320

337321
private getMaxSupportedVersion(): number {
338322
return this.parseAndroidSdkString(_.last(AndroidToolsInfo.SUPPORTED_TARGETS.sort()));
339323
}
340-
341-
private _cachedAndroidHomeValidationResult: boolean = null;
342-
private validateAndroidHomeEnvVariable(androidHomeEnvVar: string): boolean {
343-
if (this._cachedAndroidHomeValidationResult === null) {
344-
this._cachedAndroidHomeValidationResult = true;
345-
let expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
346-
if (!androidHomeEnvVar || !this.$fs.exists(androidHomeEnvVar)) {
347-
this.printMessage("The ANDROID_HOME environment variable is not set or it points to a non-existent directory. You will not be able to perform any build-related operations for Android.",
348-
"To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory.");
349-
this._cachedAndroidHomeValidationResult = false;
350-
} else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(androidHomeEnvVar, dir))))) {
351-
this.printMessage("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.",
352-
"To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory, " +
353-
"where you will find `tools` and `platform-tools` directories.");
354-
this._cachedAndroidHomeValidationResult = false;
355-
}
356-
}
357-
358-
return this._cachedAndroidHomeValidationResult;
359-
}
360324
}
361325
$injector.register("androidToolsInfo", AndroidToolsInfo);

lib/declarations.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,11 @@ interface IAndroidToolsInfo {
238238
validateJavacVersion(installedJavaVersion: string, options?: { showWarningsAsErrors: boolean }): Promise<boolean>;
239239

240240
/**
241-
* Returns the path to `android` executable. It should be `$ANDROID_HOME/tools/android`.
242-
* In case ANDROID_HOME is not defined, check if `android` is part of $PATH.
241+
* Validates if ANDROID_HOME environment variable is set correctly.
243242
* @param {any} options Defines if the warning messages should treated as error.
244-
* @return {string} Path to the `android` executable.
243+
* @returns {boolean} true in case ANDROID_HOME is correctly set, false otherwise.
245244
*/
246-
getPathToAndroidExecutable(options?: { showWarningsAsErrors: boolean }): Promise<string>;
245+
validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean;
247246

248247
/**
249248
* Gets the path to `adb` executable from ANDROID_HOME. It should be `$ANDROID_HOME/platform-tools/adb` in case it exists.

lib/services/android-project-service.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
9090
this.validatePackageName(projectData.projectId);
9191
this.validateProjectName(projectData.projectName);
9292

93-
// this call will fail in case `android` is not set correctly.
94-
await this.$androidToolsInfo.getPathToAndroidExecutable({ showWarningsAsErrors: true });
93+
this.$androidToolsInfo.validateAndroidHomeEnvVariable({ showWarningsAsErrors: true });
9594

9695
let javaCompilerVersion = await this.$sysInfo.getJavaCompilerVersion();
9796

lib/services/doctor-service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class DoctorService implements IDoctorService {
4646
result = true;
4747
}
4848

49-
if (!sysInfo.androidInstalled) {
49+
if (!sysInfo.emulatorInstalled) {
5050
this.$logger.warn("WARNING: The Android SDK is not installed or is not configured properly.");
5151
this.$logger.out("You will not be able to build your projects for Android and run them in the native emulator." + EOL
5252
+ "To be able to build for Android and run apps in the native emulator, verify that you have" + EOL

lib/sys-info.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ export class SysInfo extends SysInfoBase {
77
protected $iTunesValidator: Mobile.IiTunesValidator,
88
protected $logger: ILogger,
99
protected $winreg: IWinReg,
10+
protected $androidEmulatorServices: Mobile.IAndroidEmulatorServices,
1011
private $androidToolsInfo: IAndroidToolsInfo) {
11-
super($childProcess, $hostInfo, $iTunesValidator, $logger, $winreg);
12+
super($childProcess, $hostInfo, $iTunesValidator, $logger, $winreg, $androidEmulatorServices);
1213
}
1314

14-
public async getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string, pathToAndroid: string }): Promise<ISysInfoData> {
15+
public async getSysInfo(pathToPackageJson: string, androidToolsInfo?: { pathToAdb: string }): Promise<ISysInfoData> {
1516
let defaultAndroidToolsInfo = {
16-
pathToAdb: await this.$androidToolsInfo.getPathToAdbFromAndroidHome(),
17-
pathToAndroid: await this.$androidToolsInfo.getPathToAndroidExecutable()
17+
pathToAdb: await this.$androidToolsInfo.getPathToAdbFromAndroidHome()
1818
};
1919

2020
return super.getSysInfo(pathToPackageJson || await path.join(__dirname, "..", "package.json"), androidToolsInfo || defaultAndroidToolsInfo);

test/stubs.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,13 @@ export class AndroidToolsInfoStub implements IAndroidToolsInfo {
511511
return "";
512512
}
513513

514-
async getPathToAdbFromAndroidHome(): Promise<string> {
514+
public async getPathToAdbFromAndroidHome(): Promise<string> {
515515
return Promise.resolve("");
516516
}
517+
518+
public validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean {
519+
return false;
520+
}
517521
}
518522

519523
export class ChildProcessStub {

0 commit comments

Comments
 (0)