Skip to content

Commit 528c962

Browse files
feat: Validate Javac version with Android runtime version
In NativeScript 4.1.0 we add support for Java 10. However, older versions of the Android runtime cannot work with Java 10, so verification for the current environment and local builds depends on the current Android runtime version. In order to handle this, add projectDir parameter to the API. In case it is passed, we'll check if Android Runtime version is specified in the package.json and we'll verify if it is supported for the current Java version.
1 parent 5d964fc commit 528c962

File tree

11 files changed

+247
-27
lines changed

11 files changed

+247
-27
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
}

0 commit comments

Comments
 (0)