Skip to content

Commit 9a11aac

Browse files
Add the new checks for Android SDK Tools
1 parent 5f3c4e7 commit 9a11aac

File tree

7 files changed

+184
-52
lines changed

7 files changed

+184
-52
lines changed

lib/android-tools-info.ts

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import * as path from "path";
2-
import * as semver from "semver";
3-
import { EOL } from "os";
41
import { ChildProcess } from "./wrappers/child-process";
52
import { FileSystem } from "./wrappers/file-system";
63
import { HostInfo } from "./host-info";
4+
import { Constants } from "./constants";
5+
import { EOL } from "os";
6+
import * as semver from "semver";
7+
import * as path from "path";
78

89
export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
910
private static ANDROID_TARGET_PREFIX = "android";
@@ -15,6 +16,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
1516

1617
private toolsInfo: NativeScriptDoctor.IAndroidToolsInfoData;
1718
private androidHome = process.env["ANDROID_HOME"];
19+
private pathToEmulatorExecutable: string;
1820

1921
constructor(private childProcess: ChildProcess,
2022
private fs: FileSystem,
@@ -34,13 +36,16 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
3436
return this.toolsInfo;
3537
}
3638

37-
public validateInfo(): string[] {
38-
const errors: string[] = [];
39+
public validateInfo(): NativeScriptDoctor.IWarning[] {
40+
const errors: NativeScriptDoctor.IWarning[] = [];
3941
const toolsInfoData = this.getToolsInfo();
4042
const isAndroidHomeValid = this.validateAndroidHomeEnvVariable();
4143
if (!toolsInfoData.compileSdkVersion) {
42-
errors.push(`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.`,
43-
`Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`);
44+
errors.push({
45+
warning: `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.`,
46+
additionalInformation: `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`,
47+
platforms: [Constants.ANDROID_PLATFORM_NAME]
48+
});
4449
}
4550

4651
if (!toolsInfoData.buildToolsVersion) {
@@ -58,33 +63,50 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
5863
invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.';
5964
}
6065

61-
errors.push("You need to have the Android SDK Build-tools installed on your system. " + message, invalidBuildToolsAdditionalMsg);
66+
errors.push({
67+
warning: "You need to have the Android SDK Build-tools installed on your system. " + message,
68+
additionalInformation: invalidBuildToolsAdditionalMsg,
69+
platforms: [Constants.ANDROID_PLATFORM_NAME]
70+
});
6271
}
6372

6473
if (!toolsInfoData.supportRepositoryVersion) {
6574
let invalidSupportLibAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage the Android Support Repository.`;
6675
if (!isAndroidHomeValid) {
6776
invalidSupportLibAdditionalMsg += ' In case you already have it installed, make sure `ANDROID_HOME` environment variable is set correctly.';
6877
}
69-
errors.push(`You need to have Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later and the latest Android Support Repository installed on your system.`, invalidSupportLibAdditionalMsg);
78+
79+
errors.push({
80+
warning: `You need to have Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later and the latest Android Support Repository installed on your system.`,
81+
additionalInformation: invalidSupportLibAdditionalMsg,
82+
platforms: [Constants.ANDROID_PLATFORM_NAME]
83+
});
7084
}
7185

7286
return errors;
7387
}
7488

75-
public validateJavacVersion(installedJavaVersion: string): string[] {
76-
const errors: string[] = [];
89+
public validateJavacVersion(installedJavaVersion: string): NativeScriptDoctor.IWarning[] {
90+
const errors: NativeScriptDoctor.IWarning[] = [];
7791

7892
let additionalMessage = "You will not be able to build your projects for Android." + EOL
7993
+ "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 +
8094
" described in " + this.getSystemRequirementsLink();
8195
let matchingVersion = (installedJavaVersion || "").match(AndroidToolsInfo.VERSION_REGEX);
8296
if (matchingVersion && matchingVersion[1]) {
8397
if (semver.lt(matchingVersion[1], AndroidToolsInfo.MIN_JAVA_VERSION)) {
84-
errors.push(`Javac version ${installedJavaVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`, additionalMessage);
98+
errors.push({
99+
warning: `Javac version ${installedJavaVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`,
100+
additionalInformation: additionalMessage,
101+
platforms: [Constants.ANDROID_PLATFORM_NAME]
102+
});
85103
}
86104
} else {
87-
errors.push("Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.", additionalMessage);
105+
errors.push({
106+
warning: "Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.",
107+
additionalInformation: additionalMessage,
108+
platforms: [Constants.ANDROID_PLATFORM_NAME]
109+
});
88110
}
89111

90112
return errors;
@@ -104,22 +126,51 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
104126
return null;
105127
}
106128

107-
public validateAndroidHomeEnvVariable(): string[] {
108-
const errors: string[] = [];
129+
public validateAndroidHomeEnvVariable(): NativeScriptDoctor.IWarning[] {
130+
const errors: NativeScriptDoctor.IWarning[] = [];
109131
const expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"];
110132

111133
if (!this.androidHome || !this.fs.exists(this.androidHome)) {
112-
errors.push("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.",
113-
"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.");
134+
errors.push({
135+
warning: "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.",
136+
additionalInformation: "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.",
137+
platforms: [Constants.ANDROID_PLATFORM_NAME]
138+
});
114139
} else if (expectedDirectoriesInAndroidHome.map(dir => this.fs.exists(path.join(this.androidHome, dir))).length === 0) {
115-
errors.push("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.",
116-
"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, " +
117-
"where you will find `tools` and `platform-tools` directories.");
140+
errors.push({
141+
warning: "The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.",
142+
additionalInformation: "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, " +
143+
"where you will find `tools` and `platform-tools` directories.",
144+
platforms: [Constants.ANDROID_PLATFORM_NAME]
145+
});
118146
}
119147

120148
return errors;
121149
}
122150

151+
public getPathToEmulatorExecutable(): string {
152+
if (!this.pathToEmulatorExecutable) {
153+
const emulatorExecutableName = "emulator";
154+
155+
this.pathToEmulatorExecutable = emulatorExecutableName;
156+
157+
if (this.androidHome) {
158+
// Check https://developer.android.com/studio/releases/sdk-tools.html (25.3.0)
159+
// Since this version of SDK tools, the emulator is a separate package.
160+
// However the emulator executable still exists in the "tools" dir.
161+
const pathToEmulatorFromAndroidStudio = path.join(this.androidHome, emulatorExecutableName, emulatorExecutableName);
162+
163+
if (this.fs.exists(pathToEmulatorFromAndroidStudio)) {
164+
this.pathToEmulatorExecutable = pathToEmulatorFromAndroidStudio;
165+
} else {
166+
this.pathToEmulatorExecutable = path.join(this.androidHome, "tools", emulatorExecutableName);
167+
}
168+
}
169+
}
170+
171+
return this.pathToEmulatorExecutable;
172+
}
173+
123174
private getPathToSdkManagementTool(): string {
124175
const sdkmanagerName = "sdkmanager";
125176
let sdkManagementToolPath = sdkmanagerName;
@@ -217,7 +268,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
217268
const sortedAndroidToolsInfo = AndroidToolsInfo.SUPPORTED_TARGETS.sort();
218269

219270
sortedAndroidToolsInfo.forEach(s => {
220-
if (installedTargets.includes(s)) {
271+
if (installedTargets.indexOf(s) >= 0) {
221272
latestValidAndroidTarget = s;
222273
}
223274
});

lib/doctor.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
1313
private helpers: Helpers,
1414
private hostInfo: HostInfo,
1515
private iOSLocalBuildRequirements: IosLocalBuildRequirements,
16-
private sysInfo: NativeScriptDoctor.ISysInfo) { }
16+
private sysInfo: NativeScriptDoctor.ISysInfo,
17+
private androidToolsInfo: NativeScriptDoctor.IAndroidToolsInfo) { }
1718

1819
public async canExecuteLocalBuild(platform: string): Promise<boolean> {
1920
this.validatePlatform(platform);
@@ -28,30 +29,47 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
2829
}
2930

3031
public async getWarnings(): Promise<NativeScriptDoctor.IWarning[]> {
31-
const result: NativeScriptDoctor.IWarning[] = [];
32+
let result: NativeScriptDoctor.IWarning[] = [];
3233
const sysInfoData = await this.sysInfo.getSysInfo();
3334

35+
const androidHomeValidationErrors = this.androidToolsInfo.validateAndroidHomeEnvVariable();
36+
if (androidHomeValidationErrors.length > 0) {
37+
result = result.concat(androidHomeValidationErrors);
38+
}
39+
3440
if (!sysInfoData.adbVer) {
3541
result.push({
3642
warning: "WARNING: adb from the Android SDK is not installed or is not configured properly. ",
37-
additionalInformation: "For Android-related operations, the AppBuilder CLI will use a built-in version of adb." + EOL
43+
additionalInformation: "For Android-related operations, the NativeScript CLI will use a built-in version of adb." + EOL
3844
+ "To avoid possible issues with the native Android emulator, Genymotion or connected" + EOL
3945
+ "Android devices, verify that you have installed the latest Android SDK and" + EOL
40-
+ "its dependencies as described in http://developer.android.com/sdk/index.html#Requirements" + EOL,
46+
+ "its dependencies as described in http://developer.android.com/sdk/index.html#Requirements" + EOL
47+
+ this.getPackageManagerTip(),
4148
platforms: [Constants.ANDROID_PLATFORM_NAME]
4249
});
4350
}
4451

45-
if (!sysInfoData.androidInstalled) {
52+
if (!sysInfoData.isAndroidSdkConfiguredCorrectly) {
4653
result.push({
4754
warning: "WARNING: The Android SDK is not installed or is not configured properly.",
4855
additionalInformation: "You will not be able to run your apps in the native emulator. To be able to run apps" + EOL
4956
+ "in the native Android emulator, verify that you have installed the latest Android SDK " + EOL
50-
+ "and its dependencies as described in http://developer.android.com/sdk/index.html#Requirements" + EOL,
57+
+ "and its dependencies as described in http://developer.android.com/sdk/index.html#Requirements" + EOL
58+
+ this.getPackageManagerTip(),
5159
platforms: [Constants.ANDROID_PLATFORM_NAME]
5260
});
5361
}
5462

63+
const androidToolsInfoValidationErrors = this.androidToolsInfo.validateInfo();
64+
if (androidToolsInfoValidationErrors.length > 0) {
65+
result = result.concat(androidToolsInfoValidationErrors);
66+
}
67+
68+
const javacValidationErrors = this.androidToolsInfo.validateJavacVersion(sysInfoData.javacVersion);
69+
if (javacValidationErrors.length > 0) {
70+
result = result.concat(javacValidationErrors);
71+
}
72+
5573
if (this.hostInfo.isDarwin) {
5674
if (!sysInfoData.xcodeVer) {
5775
result.push({
@@ -153,4 +171,12 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
153171
throw new Error(`Platform ${platform} is not supported.The supported platforms are: ${Constants.SUPPORTED_PLATFORMS.join(", ")} `);
154172
}
155173
}
174+
175+
private getPackageManagerTip(): string {
176+
if (this.hostInfo.isWindows) {
177+
return "TIP: To avoid setting up the necessary environment variables, you can use the chocolatey package manager to install the Android SDK and its dependencies." + EOL;
178+
} else if (this.hostInfo.isDarwin) {
179+
return "TIP: To avoid setting up the necessary environment variables, you can use the Homebrew package manager to install the Android SDK and its dependencies." + EOL;
180+
}
181+
}
156182
}

lib/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ChildProcess } from "./wrappers/child-process";
22
import { FileSystem } from "./wrappers/file-system";
33
import { SysInfo } from "./sys-info";
44
import { HostInfo } from "./host-info";
5+
import { AndroidToolsInfo } from "./android-tools-info";
56
import { WinReg } from "./winreg";
67
import { Helpers } from "./helpers";
78
import { Doctor } from "./doctor";
@@ -14,19 +15,21 @@ const winReg = new WinReg();
1415
const hostInfo = new HostInfo(winReg);
1516
const fileSystem = new FileSystem();
1617
const helpers = new Helpers();
18+
const androidToolsInfo = new AndroidToolsInfo(childProcess, fileSystem, hostInfo);
1719

18-
const sysInfo: NativeScriptDoctor.ISysInfo = new SysInfo(childProcess, fileSystem, helpers, hostInfo, winReg);
20+
const sysInfo: NativeScriptDoctor.ISysInfo = new SysInfo(childProcess, fileSystem, helpers, hostInfo, winReg, androidToolsInfo);
1921

2022
const androidLocalBuildRequirements = new AndroidLocalBuildRequirements(sysInfo);
2123
const iOSLocalBuildRequirements = new IosLocalBuildRequirements(sysInfo, hostInfo);
2224

23-
const doctor: NativeScriptDoctor.IDoctor = new Doctor(androidLocalBuildRequirements, helpers, hostInfo, iOSLocalBuildRequirements, sysInfo);
25+
const doctor: NativeScriptDoctor.IDoctor = new Doctor(androidLocalBuildRequirements, helpers, hostInfo, iOSLocalBuildRequirements, sysInfo, androidToolsInfo);
2426

2527
const setShouldCacheSysInfo = sysInfo.setShouldCacheSysInfo.bind(sysInfo);
2628

2729
export {
2830
sysInfo,
2931
doctor,
3032
constants,
31-
setShouldCacheSysInfo
33+
setShouldCacheSysInfo,
34+
androidToolsInfo
3235
};

lib/sys-info.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ export class SysInfo implements NativeScriptDoctor.ISysInfo {
4242
private xcprojInfoCache: NativeScriptDoctor.IXcprojInfo;
4343
private isCocoaPodsUpdateRequiredCache: boolean = null;
4444
private shouldCache: boolean = true;
45+
private isAndroidSdkConfiguredCorrectlyCache: boolean = null;
4546

4647
constructor(private childProcess: ChildProcess,
4748
private fileSystem: FileSystem,
4849
private helpers: Helpers,
4950
private hostInfo: HostInfo,
50-
private winReg: WinReg) { }
51+
private winReg: WinReg,
52+
private androidToolsInfo: NativeScriptDoctor.IAndroidToolsInfo) { }
5153

5254
public getJavaVersion(): Promise<string> {
5355
return this.getValueForProperty(() => this.javaVerCache, async (): Promise<string> => {
@@ -158,29 +160,31 @@ export class SysInfo implements NativeScriptDoctor.ISysInfo {
158160

159161
public getAdbVersion(): Promise<string> {
160162
return this.getValueForProperty(() => this.adbVerCache, async (): Promise<string> => {
161-
const output = await this.execCommand("adb version");
163+
const output = await this.execCommand(`${await this.androidToolsInfo.getPathToAdbFromAndroidHome()} version`);
162164
return output ? this.getVersionFromString(output) : null;
163165
});
164166
}
165167

166-
// `android -h` returns exit code 1 on successful invocation (Mac OS X for now, possibly Linux).
167168
public isAndroidInstalled(): Promise<boolean> {
168169
return this.getValueForProperty(() => this.androidInstalledCache, async (): Promise<boolean> => {
169-
let pathToAndroid = "android";
170-
if (this.hostInfo.isWindows) {
171-
pathToAndroid = `${pathToAndroid}.bat`;
170+
try {
171+
const errors = this.androidToolsInfo.validateAndroidHomeEnvVariable();
172+
return errors.length === 0;
173+
} catch (err) {
174+
return false;
172175
}
176+
});
177+
}
173178

179+
public async isAndroidSdkConfiguredCorrectly(): Promise<boolean> {
180+
return this.getValueForProperty(() => this.isAndroidSdkConfiguredCorrectlyCache, async (): Promise<boolean> => {
174181
try {
175-
// On mac android -h returns exit code 1. That's why we need to ignore the error.
176-
const output = await this.childProcess.spawnFromEvent(pathToAndroid, ["-h"], "close", { ignoreError: true });
177-
if (output) {
178-
output.stdout = output.stdout || '';
179-
return output.stdout.indexOf("android") >= 0;
180-
}
182+
await this.childProcess.execFile(this.androidToolsInfo.getPathToEmulatorExecutable(), ['-help']);
181183
} catch (err) {
182-
return null;
184+
return false;
183185
}
186+
187+
return true;
184188
});
185189
}
186190

@@ -240,6 +244,7 @@ export class SysInfo implements NativeScriptDoctor.ISysInfo {
240244
result.isCocoaPodsWorkingCorrectly = await this.isCocoaPodsWorkingCorrectly();
241245
result.nativeScriptCliVersion = await this.getNativeScriptCliVersion();
242246
result.isCocoaPodsUpdateRequired = await this.isCocoaPodsUpdateRequired();
247+
result.isAndroidSdkConfiguredCorrectly = await this.isAndroidSdkConfiguredCorrectly();
243248

244249
return result;
245250
});

0 commit comments

Comments
 (0)