Skip to content

Commit 9f62ba9

Browse files
Merge pull request #36 from NativeScript/vladimirov/validate-java-by-runtime
feat: Validate Javac version with Android runtime version
2 parents 5d964fc + f235e17 commit 9f62ba9

File tree

12 files changed

+248
-28
lines changed

12 files changed

+248
-28
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ Library that helps identifying if the environment can be used for development of
137137
const pythonInfo = await sysInfo.getPythonInfo();
138138
console.log("python info: ", pythonInfo );
139139

140-
const sysInfoData = await sysInfo.getSysInfo();
140+
const sysInfoData = await sysInfo.getSysInfo({ projectDir: "/Users/username/myProject" });
141141
console.log("sysInfo: ", sysInfoData);
142142

143143
const gitPath = await sysInfo.getGitPath();
@@ -454,12 +454,13 @@ Library that helps identifying if the environment can be used for development of
454454
import { androidToolsInfo } from "nativescript-doctor"
455455

456456
function main() {
457+
const projectDir = "/Users/username/myProject";
457458
console.log("path to adb from android home: ", await androidToolsInfo.getPathToAdbFromAndroidHome());
458459
console.log("path to emulator executable: ", androidToolsInfo.getPathToEmulatorExecutable());
459460
console.log("android tools info: ", androidToolsInfo.getToolsInfo());
460461
console.log("ANROID_HOME validation errors: ", await androidToolsInfo.validateAndroidHomeEnvVariable());
461462
console.log("android tools info validation errors: ", await androidToolsInfo.validateInfo());
462-
console.log("javac validation errors: ", await androidToolsInfo.validateJavacVersion(await sysInfo.getJavaCompilerVersion()));
463+
console.log("javac validation errors: ", await androidToolsInfo.validateJavacVersion(await sysInfo.getJavaCompilerVersion(), projectDir));
463464
}
464465

465466
main();
@@ -478,16 +479,20 @@ Library that helps identifying if the environment can be used for development of
478479

479480
/**
480481
* Checks if the Android tools are valid.
482+
* @param {string} projectDir @optional The project directory. Used to determine the Android Runtime version and validate the Java compiler version against it.
483+
* If it is not passed or the project does not have Android runtime, this validation is skipped.
481484
* @return {NativeScriptDoctor.IWarning[]} An array of errors from the validation checks. If there are no errors will return [].
482485
*/
483-
validateInfo(): NativeScriptDoctor.IWarning[];
486+
validateInfo(projectDir?: string): NativeScriptDoctor.IWarning[];
484487

485488
/**
486489
* Checks if the current javac version is valid.
487490
* @param {string} installedJavaVersion The version of javac to check.
491+
* @param {string} projectDir @optional The project directory. Used to determine the Android Runtime version and validate the Java compiler version against it.
492+
* If it is not passed or the project does not have Android runtime, this validation is skipped.
488493
* @return {NativeScriptDoctor.IWarning[]} An array of errors from the validation checks. If there are no errors will return [].
489494
*/
490-
validateJavacVersion(installedJavaVersion: string): NativeScriptDoctor.IWarning[];
495+
validateJavacVersion(installedJavaVersion: string, projectDir?: string): NativeScriptDoctor.IWarning[];
491496

492497
/**
493498
* Returns the path to the adb which is located in ANDROID_HOME.

lib/android-tools-info.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
3838
return this.toolsInfo;
3939
}
4040

41-
public validateInfo(): NativeScriptDoctor.IWarning[] {
41+
public validateInfo(projectDir?: string): NativeScriptDoctor.IWarning[] {
4242
const errors: NativeScriptDoctor.IWarning[] = [];
4343
const toolsInfoData = this.getToolsInfo();
4444
const isAndroidHomeValid = this.isAndroidHomeValid();
@@ -88,7 +88,7 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
8888
return errors;
8989
}
9090

91-
public validateJavacVersion(installedJavaCompilerVersion: string): NativeScriptDoctor.IWarning[] {
91+
public validateJavacVersion(installedJavaCompilerVersion: string, projectDir?: string): NativeScriptDoctor.IWarning[] {
9292
const errors: NativeScriptDoctor.IWarning[] = [];
9393

9494
let additionalMessage = "You will not be able to build your projects for Android." + EOL
@@ -100,8 +100,31 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
100100
if (installedJavaCompilerSemverVersion) {
101101
let warning: string = null;
102102

103+
const supportedVersions: IDictionary<string> = {
104+
"^10.0.0": "4.1.0-2018.5.18.1"
105+
};
106+
103107
if (semver.lt(installedJavaCompilerSemverVersion, AndroidToolsInfo.MIN_JAVA_VERSION)) {
104108
warning = `Javac version ${installedJavaCompilerVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`;
109+
} else {
110+
const runtimeVersion = this.getAndroidRuntimeVersionFromProjectDir(projectDir);
111+
if (runtimeVersion) {
112+
// get the item from the dictionary that corresponds to our current Javac version:
113+
let runtimeMinVersion: string = null;
114+
Object.keys(supportedVersions)
115+
.forEach(javacRange => {
116+
if (semver.satisfies(installedJavaCompilerSemverVersion, javacRange)) {
117+
runtimeMinVersion = supportedVersions[javacRange];
118+
}
119+
});
120+
121+
if (runtimeMinVersion && semver.lt(runtimeVersion, runtimeMinVersion)) {
122+
warning = `The Java compiler version ${installedJavaCompilerVersion} is not compatible with the current Android runtime version ${runtimeVersion}. ` +
123+
`In order to use this Javac version, you need to update your Android runtime or downgrade your Java compiler version.`;
124+
additionalMessage = "You will not be able to build your projects for Android." + EOL +
125+
"To be able to build for Android, downgrade your Java compiler version or update your Android runtime.";
126+
}
127+
}
105128
}
106129

107130
if (warning) {
@@ -320,4 +343,36 @@ export class AndroidToolsInfo implements NativeScriptDoctor.IAndroidToolsInfo {
320343
const errors = this.validateAndroidHomeEnvVariable();
321344
return !errors && !errors.length;
322345
}
346+
347+
private getAndroidRuntimeVersionFromProjectDir(projectDir: string): string {
348+
let runtimeVersion: string = null;
349+
if (projectDir && this.fs.exists(projectDir)) {
350+
const pathToPackageJson = path.join(projectDir, Constants.PACKAGE_JSON);
351+
352+
if (this.fs.exists(pathToPackageJson)) {
353+
const content = this.fs.readJson<INativeScriptProjectPackageJson>(pathToPackageJson);
354+
runtimeVersion = content && content.nativescript && content.nativescript["tns-android"] && content.nativescript["tns-android"].version;
355+
}
356+
}
357+
358+
if (runtimeVersion) {
359+
// Check if the version is not "next" or "rc", i.e. tag from npm
360+
if (!semver.valid(runtimeVersion)) {
361+
try {
362+
const npmViewOutput = this.childProcess.execSync(`npm view ${Constants.ANDROID_RUNTIME} dist-tags --json`);
363+
const jsonNpmViewOutput = JSON.parse(npmViewOutput);
364+
runtimeVersion = jsonNpmViewOutput[runtimeVersion] || runtimeVersion;
365+
} catch (err) {
366+
// Maybe there's no npm here
367+
}
368+
}
369+
}
370+
371+
if (runtimeVersion && !semver.valid(runtimeVersion)) {
372+
// If we got here, something terribly wrong happened.
373+
throw new Error(`The determined Android runtime version ${runtimeVersion} based on project directory ${projectDir} is not valid. Unable to verify if the current system is setup for Android development.`);
374+
}
375+
376+
return runtimeVersion;
377+
}
323378
}

lib/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ export class Constants {
99
};
1010
public static INFO_TYPE_NAME = "info";
1111
public static WARNING_TYPE_NAME = "warning";
12+
13+
public static PACKAGE_JSON = "package.json";
14+
public static NATIVESCRIPT_KEY = "nativescript";
15+
public static ANDROID_RUNTIME = "tns-android";
16+
public static VERSION_PROPERTY_NAME = "version";
1217
}

lib/declarations.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,16 @@ interface IHiveIds {
6666
interface IDictionary<T> {
6767
[key: string]: T
6868
}
69+
70+
interface IVersion {
71+
version: string;
72+
}
73+
74+
interface INativeScriptNode {
75+
["tns-android"]: IVersion;
76+
["tns-ios"]: IVersion;
77+
}
78+
79+
interface INativeScriptProjectPackageJson {
80+
nativescript: INativeScriptNode;
81+
}

lib/doctor.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
1616
private sysInfo: NativeScriptDoctor.ISysInfo,
1717
private androidToolsInfo: NativeScriptDoctor.IAndroidToolsInfo) { }
1818

19-
public async canExecuteLocalBuild(platform: string): Promise<boolean> {
19+
public async canExecuteLocalBuild(platform: string, projectDir?: string): Promise<boolean> {
2020
this.validatePlatform(platform);
2121

2222
if (platform.toLowerCase() === Constants.ANDROID_PLATFORM_NAME.toLowerCase()) {
23-
return await this.androidLocalBuildRequirements.checkRequirements();
23+
return await this.androidLocalBuildRequirements.checkRequirements(projectDir);
2424
} else if (platform.toLowerCase() === Constants.IOS_PLATFORM_NAME.toLowerCase()) {
2525
return await this.iOSLocalBuildRequirements.checkRequirements();
2626
}
@@ -33,7 +33,7 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
3333
const sysInfoData = await this.sysInfo.getSysInfo(config);
3434

3535
if (!config || !config.platform || config.platform === Constants.ANDROID_PLATFORM_NAME) {
36-
result = result.concat(this.getAndroidInfos(sysInfoData));
36+
result = result.concat(this.getAndroidInfos(sysInfoData, config && config.projectDir));
3737
}
3838

3939
if (!config || !config.platform || config.platform === Constants.IOS_PLATFORM_NAME) {
@@ -58,7 +58,7 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
5858
.map(item => this.convertInfoToWarning(item));
5959
}
6060

61-
private getAndroidInfos(sysInfoData: NativeScriptDoctor.ISysInfoData): NativeScriptDoctor.IInfo[] {
61+
private getAndroidInfos(sysInfoData: NativeScriptDoctor.ISysInfoData, projectDir?: string): NativeScriptDoctor.IInfo[] {
6262
let result: NativeScriptDoctor.IInfo[] = [];
6363

6464
result = result.concat(
@@ -92,7 +92,7 @@ export class Doctor implements NativeScriptDoctor.IDoctor {
9292
platforms: [Constants.ANDROID_PLATFORM_NAME]
9393
}),
9494
this.processValidationErrors({
95-
warnings: this.androidToolsInfo.validateJavacVersion(sysInfoData.javacVersion),
95+
warnings: this.androidToolsInfo.validateJavacVersion(sysInfoData.javacVersion, projectDir),
9696
infoMessage: "Javac is installed and is configured properly.",
9797
platforms: [Constants.ANDROID_PLATFORM_NAME]
9898
}),

lib/local-build-requirements/android-local-build-requirements.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ export class AndroidLocalBuildRequirements {
22
constructor(private androidToolsInfo: NativeScriptDoctor.IAndroidToolsInfo,
33
private sysInfo: NativeScriptDoctor.ISysInfo) { }
44

5-
public async checkRequirements(): Promise<boolean> {
5+
public async checkRequirements(projectDir?: string): Promise<boolean> {
66
const androidToolsInfo = await this.androidToolsInfo.validateInfo();
7+
const javacVersion = await this.sysInfo.getJavaCompilerVersion();
78
if (androidToolsInfo.length ||
8-
!await this.sysInfo.getJavaCompilerVersion() ||
9-
!await this.sysInfo.getAdbVersion()) {
9+
!javacVersion ||
10+
!await this.sysInfo.getAdbVersion() ||
11+
!await this.androidToolsInfo.validateJavacVersion(javacVersion, projectDir)) {
1012
return false;
1113
}
1214

lib/wrappers/child-process.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export class ChildProcess {
7676
});
7777
}
7878

79+
public execSync(command: string, options?: childProcess.ExecSyncOptions): string {
80+
return childProcess.execSync(command, options).toString();
81+
}
82+
7983
public execFile(command: string, args: string[]): Promise<any> {
8084
return new Promise<any>((resolve, reject) => {
8185
childProcess.execFile(command, args, (error, stdout) => {

lib/wrappers/file-system.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ export class FileSystem {
1717
public readDirectory(path: string): string[] {
1818
return fs.readdirSync(path);
1919
}
20+
21+
public readJson<T>(path: string, options?: { encoding?: null; flag?: string; }): T {
22+
const content = fs.readFileSync(path, options);
23+
return JSON.parse(content.toString());
24+
}
2025
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nativescript-doctor",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "Library that helps identifying if the environment can be used for development of {N} apps.",
55
"main": "lib/index.js",
66
"types": "./typings/nativescript-doctor.d.ts",

0 commit comments

Comments
 (0)