Skip to content

Commit 068d17c

Browse files
Fix getting info for Android tools (#2610)
* 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. * Make androidSysInfo calls sync As one of the methods in androidSysInfo no longer needs to be async, after removing its async signature, this lead to avalanche of methods that no longer needs to be async. So fix all of them. * 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. * Code styling fixes * Fix getting installed Android SDKs
1 parent 31781bb commit 068d17c

14 files changed

+134
-184
lines changed

lib/android-tools-info.ts

+83-110
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";
@@ -13,19 +14,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
1314
private showWarningsAsErrors: boolean;
1415
private toolsInfo: IAndroidToolsInfoData;
1516
private selectedCompileSdk: number;
16-
private installedTargetsCache: string[] = null;
17-
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;
17+
private get androidHome(): string {
18+
return process.env["ANDROID_HOME"];
2919
}
3020

3121
constructor(private $childProcess: IChildProcess,
@@ -34,54 +24,17 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
3424
private $hostInfo: IHostInfo,
3525
private $logger: ILogger,
3626
private $options: IOptions,
37-
private $adb: Mobile.IAndroidDebugBridge,
3827
protected $staticConfig: Config.IStaticConfig) { }
3928

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-
77-
public async getToolsInfo(): Promise<IAndroidToolsInfoData> {
29+
@cache()
30+
public getToolsInfo(): IAndroidToolsInfoData {
7831
if (!this.toolsInfo) {
7932
let infoData: IAndroidToolsInfoData = Object.create(null);
8033
infoData.androidHomeEnvVar = this.androidHome;
81-
infoData.compileSdkVersion = await this.getCompileSdk();
82-
infoData.buildToolsVersion = await this.getBuildToolsVersion();
83-
infoData.targetSdkVersion = await this.getTargetSdk();
84-
infoData.supportRepositoryVersion = await this.getAndroidSupportRepositoryVersion();
34+
infoData.compileSdkVersion = this.getCompileSdkVersion();
35+
infoData.buildToolsVersion = this.getBuildToolsVersion();
36+
infoData.targetSdkVersion = this.getTargetSdk();
37+
infoData.supportRepositoryVersion = this.getAndroidSupportRepositoryVersion();
8538
infoData.generateTypings = this.shouldGenerateTypings();
8639

8740
this.toolsInfo = infoData;
@@ -90,14 +43,14 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
9043
return this.toolsInfo;
9144
}
9245

93-
public async validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): Promise<boolean> {
46+
public validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): boolean {
9447
let detectedErrors = false;
9548
this.showWarningsAsErrors = options && options.showWarningsAsErrors;
96-
let toolsInfoData = await this.getToolsInfo();
97-
let isAndroidHomeValid = this.validateAndroidHomeEnvVariable(toolsInfoData.androidHomeEnvVar);
49+
let toolsInfoData = this.getToolsInfo();
50+
let isAndroidHomeValid = this.validateAndroidHomeEnvVariable();
9851
if (!toolsInfoData.compileSdkVersion) {
9952
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.");
53+
`Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`);
10154
detectedErrors = true;
10255
}
10356

@@ -111,7 +64,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
11164
message = `You have to install version ${versionRangeMatches[1]}.`;
11265
}
11366

114-
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\`.`;
11568
if (!isAndroidHomeValid) {
11669
invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.';
11770
}
@@ -121,7 +74,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
12174
}
12275

12376
if (!toolsInfoData.supportRepositoryVersion) {
124-
let invalidSupportLibAdditionalMsg = 'Run `$ android` to manage the Android Support Repository.';
77+
let invalidSupportLibAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage the Android Support Repository.`;
12578
if (!isAndroidHomeValid) {
12679
invalidSupportLibAdditionalMsg += ' In case you already have it installed, make sure `ANDROID_HOME` environment variable is set correctly.';
12780
}
@@ -153,6 +106,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
153106
if (options) {
154107
this.showWarningsAsErrors = options.showWarningsAsErrors;
155108
}
109+
156110
let additionalMessage = "You will not be able to build your projects for Android." + EOL
157111
+ "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 +
158112
" described in " + this.$staticConfig.SYS_REQUIREMENTS_LINK;
@@ -186,6 +140,51 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
186140
return null;
187141
}
188142

143+
@cache()
144+
public validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean {
145+
if (options) {
146+
this.showWarningsAsErrors = options.showWarningsAsErrors;
147+
}
148+
149+
const expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
150+
let androidHomeValidationResult = true;
151+
152+
if (!this.androidHome || !this.$fs.exists(this.androidHome)) {
153+
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.",
154+
"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.");
155+
androidHomeValidationResult = false;
156+
} else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(this.androidHome, dir))))) {
157+
this.printMessage("The ANDROID_HOME environment variable points to incorrect 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+
"where you will find `tools` and `platform-tools` directories.");
160+
androidHomeValidationResult = false;
161+
}
162+
163+
return androidHomeValidationResult;
164+
}
165+
166+
@cache()
167+
private getPathToSdkManagementTool(): string {
168+
const sdkManagerName = "sdkmanager";
169+
let sdkManagementToolPath = sdkManagerName;
170+
171+
const isAndroidHomeValid = this.validateAndroidHomeEnvVariable();
172+
173+
if (isAndroidHomeValid) {
174+
// In case ANDROID_HOME is correct, check if sdkmanager exists and if not it means the SDK has not been updated.
175+
// In this case user shoud use `android` from the command-line instead of sdkmanager.
176+
const pathToSdkManager = path.join(this.androidHome, "tools", "bin", sdkManagerName);
177+
const pathToAndroidExecutable = path.join(this.androidHome, "tools", "android");
178+
const pathToExecutable = this.$fs.exists(pathToSdkManager) ? pathToSdkManager : pathToAndroidExecutable;
179+
180+
this.$logger.trace(`Path to Android SDK Management tool is: ${pathToExecutable}`);
181+
182+
sdkManagementToolPath = pathToExecutable.replace(this.androidHome, this.$hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME");
183+
}
184+
185+
return sdkManagementToolPath;
186+
}
187+
189188
private shouldGenerateTypings(): boolean {
190189
return this.$options.androidTypings;
191190
}
@@ -211,19 +210,19 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
211210
}
212211
}
213212

214-
private async getCompileSdk(): Promise<number> {
213+
private getCompileSdkVersion(): number {
215214
if (!this.selectedCompileSdk) {
216215
let userSpecifiedCompileSdk = this.$options.compileSdk;
217216
if (userSpecifiedCompileSdk) {
218-
let installedTargets = await this.getInstalledTargets();
217+
let installedTargets = this.getInstalledTargets();
219218
let androidCompileSdk = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${userSpecifiedCompileSdk}`;
220219
if (!_.includes(installedTargets, androidCompileSdk)) {
221220
this.$errors.failWithoutHelp(`You have specified '${userSpecifiedCompileSdk}' for compile sdk, but it is not installed on your system.`);
222221
}
223222

224223
this.selectedCompileSdk = userSpecifiedCompileSdk;
225224
} else {
226-
let latestValidAndroidTarget = await this.getLatestValidAndroidTarget();
225+
let latestValidAndroidTarget = this.getLatestValidAndroidTarget();
227226
if (latestValidAndroidTarget) {
228227
let integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget);
229228

@@ -237,8 +236,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
237236
return this.selectedCompileSdk;
238237
}
239238

240-
private async getTargetSdk(): Promise<number> {
241-
let targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : await this.getCompileSdk();
239+
private getTargetSdk(): number {
240+
let targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : this.getCompileSdkVersion();
242241
this.$logger.trace(`Selected targetSdk is: ${targetSdk}`);
243242
return targetSdk;
244243
}
@@ -273,7 +272,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
273272
return `${AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX} <=${this.getMaxSupportedVersion()}`;
274273
}
275274

276-
private async getBuildToolsVersion(): Promise<string> {
275+
private getBuildToolsVersion(): string {
277276
let buildToolsVersion: string;
278277
if (this.androidHome) {
279278
let pathToBuildTools = path.join(this.androidHome, "build-tools");
@@ -284,8 +283,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
284283
return buildToolsVersion;
285284
}
286285

287-
private async getAppCompatRange(): Promise<string> {
288-
let compileSdkVersion = await this.getCompileSdk();
286+
private getAppCompatRange(): string {
287+
let compileSdkVersion = this.getCompileSdkVersion();
289288
let requiredAppCompatRange: string;
290289
if (compileSdkVersion) {
291290
requiredAppCompatRange = `>=${compileSdkVersion} <${compileSdkVersion + 1}`;
@@ -294,9 +293,9 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
294293
return requiredAppCompatRange;
295294
}
296295

297-
private async getAndroidSupportRepositoryVersion(): Promise<string> {
296+
private getAndroidSupportRepositoryVersion(): string {
298297
let selectedAppCompatVersion: string;
299-
let requiredAppCompatRange = await this.getAppCompatRange();
298+
let requiredAppCompatRange = this.getAppCompatRange();
300299
if (this.androidHome && requiredAppCompatRange) {
301300
let pathToAppCompat = path.join(this.androidHome, "extras", "android", "m2repository", "com", "android", "support", "appcompat-v7");
302301
selectedAppCompatVersion = this.getMatchingDir(pathToAppCompat, requiredAppCompatRange);
@@ -306,56 +305,30 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
306305
return selectedAppCompatVersion;
307306
}
308307

309-
private async getLatestValidAndroidTarget(): Promise<string> {
310-
let installedTargets = await this.getInstalledTargets();
308+
private getLatestValidAndroidTarget(): string {
309+
let installedTargets = this.getInstalledTargets();
311310
return _.findLast(AndroidToolsInfo.SUPPORTED_TARGETS.sort(), supportedTarget => _.includes(installedTargets, supportedTarget));
312311
}
313312

314313
private parseAndroidSdkString(androidSdkString: string): number {
315314
return parseInt(androidSdkString.replace(`${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-`, ""));
316315
}
317316

318-
private async getInstalledTargets(): Promise<string[]> {
319-
if (!this.installedTargetsCache) {
320-
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-
}
329-
}
330-
} catch (err) {
331-
this.$logger.trace("Unable to get Android targets. Error is: " + err);
332-
}
317+
@cache()
318+
private getInstalledTargets(): string[] {
319+
let installedTargets: string[] = [];
320+
const pathToInstalledTargets = path.join(this.androidHome, "platforms");
321+
if (this.$fs.exists(pathToInstalledTargets)) {
322+
installedTargets = this.$fs.readDirectory(pathToInstalledTargets);
333323
}
334-
return this.installedTargetsCache;
324+
325+
this.$logger.trace("Installed Android Targets are: ", installedTargets);
326+
327+
return installedTargets;
335328
}
336329

337330
private getMaxSupportedVersion(): number {
338331
return this.parseAndroidSdkString(_.last(AndroidToolsInfo.SUPPORTED_TARGETS.sort()));
339332
}
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-
}
360333
}
361334
$injector.register("androidToolsInfo", AndroidToolsInfo);

lib/declarations.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,14 @@ interface IAndroidToolsInfo {
220220
* and ANDROID_HOME environement variable.
221221
* @return {IAndroidToolsInfoData} Information about installed Android Tools and SDKs.
222222
*/
223-
getToolsInfo(): Promise<IAndroidToolsInfoData>;
223+
getToolsInfo(): IAndroidToolsInfoData;
224224

225225
/**
226226
* Validates the information about required Android tools and SDK versions.
227227
* @param {any} options Defines if the warning messages should treated as error and if the targetSdk value should be validated as well.
228228
* @return {boolean} True if there are detected issues, false otherwise.
229229
*/
230-
validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): Promise<boolean>;
230+
validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): boolean;
231231

232232
/**
233233
* Validates the information about required JAVA version.
@@ -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/definitions/emulator-platform-service.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ interface IEmulatorPlatformService {
1111
listAvailableEmulators(platform: string): Promise<void>;
1212
getEmulatorInfo(platform: string, nameOfId: string): Promise<IEmulatorInfo>;
1313
getiOSEmulators(): Promise<IEmulatorInfo[]>;
14-
getAndroidEmulators(): Promise<IEmulatorInfo[]>;
14+
getAndroidEmulators(): IEmulatorInfo[];
1515
startEmulator(info: IEmulatorInfo, projectData: IProjectData): Promise<void>;
1616
}

lib/definitions/project.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter {
160160
validate(projectData: IProjectData): Promise<void>;
161161
createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, pathToTemplate?: string): Promise<void>;
162162
interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise<void>;
163-
interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise<void>;
163+
interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void;
164164

165165
/**
166166
* Executes additional actions after native project is created.

0 commit comments

Comments
 (0)