Skip to content

Commit 81dd5d0

Browse files
Fix getting info for Android tools (#2611)
* 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. * Fix `emulate android --available-devices` The command `tns emulate android --available-devices` should list the available Android Virtual Devices on the current machine. It uses `android list avd` command which is no longer available. The alternative is to use the new `$ANDROID_HOME/tools/bin/avdmanager` executable or use the way we already have in the mobile-cli-lib - parse the `.ini` files of each device. Use the methods from `androidEmulatorServices` so the code will work with both old and new SDKs. * Fix getting installed Android SDKs
1 parent e1c81eb commit 81dd5d0

8 files changed

+262
-306
lines changed

lib/android-tools-info.ts

+61-87
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as path from "path";
22
import * as semver from "semver";
3-
import {EOL} from "os";
3+
import { EOL } from "os";
44

55
export class AndroidToolsInfo implements IAndroidToolsInfo {
66
private static ANDROID_TARGET_PREFIX = "android";
@@ -15,69 +15,15 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
1515
private selectedCompileSdk: number;
1616
private installedTargetsCache: string[] = null;
1717
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-
}
3018

3119
constructor(private $childProcess: IChildProcess,
3220
private $errors: IErrors,
3321
private $fs: IFileSystem,
3422
private $hostInfo: IHostInfo,
3523
private $logger: ILogger,
3624
private $options: IOptions,
37-
private $adb: Mobile.IAndroidDebugBridge,
3825
protected $staticConfig: Config.IStaticConfig) { }
3926

40-
public getPathToAndroidExecutable(options?: { showWarningsAsErrors: boolean }): IFuture<string> {
41-
return ((): string => {
42-
if (options) {
43-
this.showWarningsAsErrors = options.showWarningsAsErrors;
44-
}
45-
if (!this.pathToAndroidExecutable) {
46-
if (this.validateAndroidHomeEnvVariable(this.androidHome)) {
47-
let androidPath = path.join(this.androidHome, "tools", this.androidExecutableName);
48-
if (!this.trySetAndroidPath(androidPath).wait() && !this.trySetAndroidPath(this.androidExecutableName).wait()) {
49-
this.printMessage(`Unable to find "${this.androidExecutableName}" executable file. Make sure you have set ANDROID_HOME environment variable correctly.`);
50-
}
51-
} else {
52-
this.$logger.trace("ANDROID_HOME environment variable is not set correctly.");
53-
}
54-
}
55-
56-
return this.pathToAndroidExecutable;
57-
}).future<string>()();
58-
}
59-
60-
private trySetAndroidPath(androidPath: string): IFuture<boolean> {
61-
return ((): boolean => {
62-
let isAndroidPathCorrect = true;
63-
try {
64-
let result = this.$adb.executeCommand(["--help"], { returnChildProcess: true }).wait();
65-
if (result && result.stdout) {
66-
this.$logger.trace(result.stdout);
67-
this.pathToAndroidExecutable = androidPath;
68-
} else {
69-
this.$logger.trace(`Unable to find android executable from '${androidPath}'.`);
70-
isAndroidPathCorrect = false;
71-
}
72-
} catch (err) {
73-
this.$logger.trace(`Error occurred while checking androidExecutable from '${androidPath}'. ${err.message}`);
74-
isAndroidPathCorrect = false;
75-
}
76-
77-
return isAndroidPathCorrect;
78-
}).future<boolean>()();
79-
}
80-
8127
public getToolsInfo(): IFuture<IAndroidToolsInfoData> {
8228
return ((): IAndroidToolsInfoData => {
8329
if (!this.toolsInfo) {
@@ -101,10 +47,10 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
10147
let detectedErrors = false;
10248
this.showWarningsAsErrors = options && options.showWarningsAsErrors;
10349
let toolsInfoData = this.getToolsInfo().wait();
104-
let isAndroidHomeValid = this.validateAndroidHomeEnvVariable(toolsInfoData.androidHomeEnvVar);
50+
let isAndroidHomeValid = this.validateAndroidHomeEnvVariable();
10551
if (!toolsInfoData.compileSdkVersion) {
10652
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.`,
107-
"Run `$ android` to manage your Android SDK versions.");
53+
`Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`);
10854
detectedErrors = true;
10955
}
11056

@@ -118,7 +64,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
11864
message = `You have to install version ${versionRangeMatches[1]}.`;
11965
}
12066

121-
let invalidBuildToolsAdditionalMsg = 'Run `android` from your command-line to install required `Android Build Tools`.';
67+
let invalidBuildToolsAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` from your command-line to install required \`Android Build Tools\`.`;
12268
if (!isAndroidHomeValid) {
12369
invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.';
12470
}
@@ -128,7 +74,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
12874
}
12975

13076
if (!toolsInfoData.supportRepositoryVersion) {
131-
let invalidSupportLibAdditionalMsg = 'Run `$ android` to manage the Android Support Repository.';
77+
let invalidSupportLibAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage the Android Support Repository.`;
13278
if (!isAndroidHomeValid) {
13379
invalidSupportLibAdditionalMsg += ' In case you already have it installed, make sure `ANDROID_HOME` environment variable is set correctly.';
13480
}
@@ -198,6 +144,54 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
198144
}).future<string>()();
199145
}
200146

147+
private _cachedAndroidHomeValidationResult: boolean = null;
148+
public validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean {
149+
if (this._cachedAndroidHomeValidationResult === null) {
150+
if (options) {
151+
this.showWarningsAsErrors = options.showWarningsAsErrors;
152+
}
153+
154+
this._cachedAndroidHomeValidationResult = true;
155+
let expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
156+
if (!this.androidHome || !this.$fs.exists(this.androidHome)) {
157+
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.",
158+
"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.");
159+
this._cachedAndroidHomeValidationResult = false;
160+
} else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(this.androidHome, dir))))) {
161+
this.printMessage("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.",
162+
"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, " +
163+
"where you will find `tools` and `platform-tools` directories.");
164+
this._cachedAndroidHomeValidationResult = false;
165+
}
166+
}
167+
168+
return this._cachedAndroidHomeValidationResult;
169+
}
170+
171+
private _cachedPathToSdkManagementTool: string;
172+
private getPathToSdkManagementTool(): string {
173+
if (!this._cachedPathToSdkManagementTool) {
174+
const sdkmanagerName = "sdkmanager";
175+
this._cachedPathToSdkManagementTool = sdkmanagerName;
176+
177+
const isAndroidHomeValid = this.validateAndroidHomeEnvVariable();
178+
179+
if (isAndroidHomeValid) {
180+
// In case ANDROID_HOME is correct, check if sdkmanager exists and if not it means the SDK has not been updated.
181+
// In this case user shoud use `android` from the command-line instead of sdkmanager.
182+
const pathToSdkmanager = path.join(this.androidHome, "tools", "bin", sdkmanagerName);
183+
const pathToAndroidExecutable = path.join(this.androidHome, "tools", "android");
184+
const pathToExecutable = this.$fs.exists(pathToSdkmanager) ? pathToSdkmanager : pathToAndroidExecutable;
185+
186+
this.$logger.trace(`Path to Android SDK Management tool is: ${pathToExecutable}`);
187+
188+
this._cachedPathToSdkManagementTool = pathToExecutable.replace(this.androidHome, this.$hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME");
189+
}
190+
}
191+
192+
return this._cachedPathToSdkManagementTool;
193+
}
194+
201195
private shouldGenerateTypings(): boolean {
202196
return this.$options.androidTypings;
203197
}
@@ -343,45 +337,25 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
343337
return (() => {
344338
if (!this.installedTargetsCache) {
345339
try {
346-
let pathToAndroidExecutable = this.getPathToAndroidExecutable().wait();
347-
if (pathToAndroidExecutable) {
348-
let result = this.$childProcess.spawnFromEvent(pathToAndroidExecutable, ["list", "targets"], "close", {}, { throwError: false }).wait();
349-
if (result && result.stdout) {
350-
this.$logger.trace(result.stdout);
351-
this.installedTargetsCache = [];
352-
result.stdout.replace(/id: \d+ or "(.+)"/g, (m: string, p1: string) => (this.installedTargetsCache.push(p1), m));
353-
}
340+
this.installedTargetsCache = [];
341+
const pathToInstalledTargets = path.join(this.androidHome, "platforms");
342+
if (this.$fs.exists(pathToInstalledTargets)) {
343+
this.installedTargetsCache = this.$fs.readDirectory(pathToInstalledTargets);
344+
this.$logger.trace("Installed Android Targets are: ", this.installedTargetsCache);
354345
}
346+
347+
this.$logger.trace("Installed Android Targets are: ", this.installedTargetsCache);
355348
} catch (err) {
356349
this.$logger.trace("Unable to get Android targets. Error is: " + err);
357350
}
358351
}
352+
359353
return this.installedTargetsCache;
360354
}).future<string[]>()();
361355
}
362356

363357
private getMaxSupportedVersion(): number {
364358
return this.parseAndroidSdkString(_.last(AndroidToolsInfo.SUPPORTED_TARGETS.sort()));
365359
}
366-
367-
private _cachedAndroidHomeValidationResult: boolean = null;
368-
private validateAndroidHomeEnvVariable(androidHomeEnvVar: string): boolean {
369-
if (this._cachedAndroidHomeValidationResult === null) {
370-
this._cachedAndroidHomeValidationResult = true;
371-
let expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
372-
if (!androidHomeEnvVar || !this.$fs.exists(androidHomeEnvVar)) {
373-
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.",
374-
"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.");
375-
this._cachedAndroidHomeValidationResult = false;
376-
} else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(androidHomeEnvVar, dir))))) {
377-
this.printMessage("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.",
378-
"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, " +
379-
"where you will find `tools` and `platform-tools` directories.");
380-
this._cachedAndroidHomeValidationResult = false;
381-
}
382-
}
383-
384-
return this._cachedAndroidHomeValidationResult;
385-
}
386360
}
387361
$injector.register("androidToolsInfo", AndroidToolsInfo);

lib/declarations.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,11 @@ interface IAndroidToolsInfo {
176176
validateJavacVersion(installedJavaVersion: string, options?: { showWarningsAsErrors: boolean }): IFuture<boolean>;
177177

178178
/**
179-
* Returns the path to `android` executable. It should be `$ANDROID_HOME/tools/android`.
180-
* In case ANDROID_HOME is not defined, check if `android` is part of $PATH.
179+
* Validates if ANDROID_HOME environment variable is set correctly.
181180
* @param {any} options Defines if the warning messages should treated as error.
182-
* @return {string} Path to the `android` executable.
181+
* @returns {boolean} true in case ANDROID_HOME is correctly set, false otherwise.
183182
*/
184-
getPathToAndroidExecutable(options?: { showWarningsAsErrors: boolean }): IFuture<string>;
183+
validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean;
185184

186185
/**
187186
* 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
@@ -92,8 +92,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
9292
this.validatePackageName(this.$projectData.projectId);
9393
this.validateProjectName(this.$projectData.projectName);
9494

95-
// this call will fail in case `android` is not set correctly.
96-
this.$androidToolsInfo.getPathToAndroidExecutable({ showWarningsAsErrors: true }).wait();
95+
this.$androidToolsInfo.validateAndroidHomeEnvVariable({ showWarningsAsErrors: true });
9796
let javaCompilerVersion = this.$sysInfo.getJavaCompilerVersion().wait();
9897
this.$androidToolsInfo.validateJavacVersion(javaCompilerVersion, { showWarningsAsErrors: true }).wait();
9998
}).future<void>()();

lib/services/doctor-service.ts

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

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

0 commit comments

Comments
 (0)