Skip to content

Commit 8f46639

Browse files
Check Android platform dependencies when adding platform
If the current project already have some of the Android platform dependencies installed the installed version should be checked. If the installed version satisfies the Android platform the CLI should not install this dependency, if not the CLI should throw error to inform the user about the current version and the required version.
1 parent f086d8c commit 8f46639

File tree

2 files changed

+61
-51
lines changed

2 files changed

+61
-51
lines changed

lib/commands/install.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
///<reference path="../.d.ts"/>
22
"use strict";
3+
import {EOL} from "os";
34

45
export class InstallCommand implements ICommand {
56
constructor(private $platformsData: IPlatformsData,
@@ -34,7 +35,7 @@ export class InstallCommand implements ICommand {
3435
try {
3536
this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`]).wait();
3637
} catch (err) {
37-
error += err;
38+
error = `${error}${EOL}${err}`;
3839
}
3940
}
4041
});

lib/services/android-project-service.ts

+59-50
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
1818
private static MIN_RUNTIME_VERSION_WITH_GRADLE = "1.3.0";
1919
private static MIN_REQUIRED_NODEJS_VERSION_FOR_STATIC_BINDINGS = "4.2.1";
2020
private static REQUIRED_DEV_DEPENDENCIES = [
21-
{ name: "babel-traverse", version: "^6.4.5"},
22-
{ name: "babel-types", version: "^6.4.5"},
23-
{ name: "babylon", version: "^6.4.5"},
24-
{ name: "filewalker", version: "^0.1.2"},
25-
{ name: "lazy", version: "^1.0.11"}
21+
{ name: "babel-traverse", version: "^6.4.5" },
22+
{ name: "babel-types", version: "^6.4.5" },
23+
{ name: "babylon", version: "^6.4.5" },
24+
{ name: "filewalker", version: "^0.1.2" },
25+
{ name: "lazy", version: "^1.0.11" }
2626
];
2727

2828
private get sysInfoData(): ISysInfoData {
@@ -50,8 +50,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
5050
private $projectTemplatesService: IProjectTemplatesService,
5151
private $xmlValidator: IXmlValidator,
5252
private $npm: INodePackageManager) {
53-
super($fs, $projectData, $projectDataService);
54-
this._androidProjectPropertiesManagers = Object.create(null);
53+
super($fs, $projectData, $projectDataService);
54+
this._androidProjectPropertiesManagers = Object.create(null);
5555
}
5656

5757
private _platformData: IPlatformData = null;
@@ -86,7 +86,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
8686

8787
public getAppResourcesDestinationDirectoryPath(frameworkVersion?: string): IFuture<string> {
8888
return (() => {
89-
if(this.canUseGradle(frameworkVersion).wait()) {
89+
if (this.canUseGradle(frameworkVersion).wait()) {
9090
return path.join(this.platformData.projectRoot, "src", "main", "res");
9191
}
9292

@@ -100,30 +100,30 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
100100
this.validateProjectName(this.$projectData.projectName);
101101

102102
// this call will fail in case `android` is not set correctly.
103-
this.$androidToolsInfo.getPathToAndroidExecutable({showWarningsAsErrors: true}).wait();
104-
this.$androidToolsInfo.validateJavacVersion(this.sysInfoData.javacVersion, {showWarningsAsErrors: true}).wait();
103+
this.$androidToolsInfo.getPathToAndroidExecutable({ showWarningsAsErrors: true }).wait();
104+
this.$androidToolsInfo.validateJavacVersion(this.sysInfoData.javacVersion, { showWarningsAsErrors: true }).wait();
105105
}).future<void>()();
106106
}
107107

108108
public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void> {
109109
return (() => {
110-
if(semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
110+
if (semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
111111
this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`);
112112
}
113113

114114
this.$fs.ensureDirectoryExists(this.platformData.projectRoot).wait();
115-
this.$androidToolsInfo.validateInfo({showWarningsAsErrors: true, validateTargetSdk: true}).wait();
115+
this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }).wait();
116116
let androidToolsInfo = this.$androidToolsInfo.getToolsInfo().wait();
117117
let targetSdkVersion = androidToolsInfo.targetSdkVersion;
118118
this.$logger.trace(`Using Android SDK '${targetSdkVersion}'.`);
119-
if(this.$options.symlink) {
119+
if (this.$options.symlink) {
120120
this.symlinkDirectory("libs", this.platformData.projectRoot, frameworkDir).wait();
121121
} else {
122122
this.copy(this.platformData.projectRoot, frameworkDir, "libs", "-R");
123123
}
124124

125125
// These files and directories should not be symlinked as CLI is modifying them and we'll change the original values as well.
126-
if(pathToTemplate) {
126+
if (pathToTemplate) {
127127
let mainPath = path.join(this.platformData.projectRoot, "src", "main");
128128
this.$fs.createDirectory(mainPath).wait();
129129
shell.cp("-R", path.join(path.resolve(pathToTemplate), "*"), mainPath);
@@ -138,17 +138,25 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
138138
}
139139

140140
this.cleanResValues(targetSdkVersion, frameworkVersion).wait();
141-
if(this.canUseStaticBindingGenerator()) {
141+
if (this.canUseStaticBindingGenerator()) {
142142
let npmConfig = {
143143
"save": true,
144144
"save-dev": true,
145145
"save-exact": true,
146146
"silent": true
147147
};
148148

149-
_.each(AndroidProjectService.REQUIRED_DEV_DEPENDENCIES, (dependency: any) =>
150-
this.$npm.install(`${dependency.name}@${dependency.version}`, this.$projectData.projectDir, npmConfig).wait()
151-
);
149+
let projectPackageJson: any = this.$fs.readJson(this.$projectData.projectFilePath).wait();
150+
151+
_.each(AndroidProjectService.REQUIRED_DEV_DEPENDENCIES, (dependency: any) => {
152+
let dependencyVersionInProject = projectPackageJson.dependencies[dependency.name] || projectPackageJson.devDependencies[dependency.name];
153+
154+
if (!dependencyVersionInProject) {
155+
this.$npm.install(`${dependency.name}@${dependency.version}`, this.$projectData.projectDir, npmConfig).wait();
156+
} else if (!semver.satisfies(dependencyVersionInProject, dependency.version)) {
157+
this.$errors.failWithoutHelp(`Your project have installed ${dependency.name} version ${dependencyVersionInProject} but Android platform requires version ${dependency.version}.`);
158+
}
159+
});
152160
} else {
153161
this.$logger.printMarkdown(` As you are using Node.js \`${this.sysInfoData.nodeVer}\` Static Binding Generator will be turned off.` +
154162
`Upgrade your Node.js to ${AndroidProjectService.MIN_REQUIRED_NODEJS_VERSION_FOR_STATIC_BINDINGS} or later, so you can use this feature.`);
@@ -170,14 +178,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
170178
let resDestinationDir = this.getAppResourcesDestinationDirectoryPath(frameworkVersion).wait();
171179
let directoriesInResFolder = this.$fs.readDirectory(resDestinationDir).wait();
172180
let directoriesToClean = directoriesInResFolder
173-
.map(dir => { return {
181+
.map(dir => {
182+
return {
174183
dirName: dir,
175184
sdkNum: parseInt(dir.substr(AndroidProjectService.VALUES_VERSION_DIRNAME_PREFIX.length))
176185
};
177186
})
178187
.filter(dir => dir.dirName.match(AndroidProjectService.VALUES_VERSION_DIRNAME_PREFIX)
179-
&& dir.sdkNum
180-
&& (!targetSdkVersion || (targetSdkVersion < dir.sdkNum)))
188+
&& dir.sdkNum
189+
&& (!targetSdkVersion || (targetSdkVersion < dir.sdkNum)))
181190
.map(dir => path.join(resDestinationDir, dir.dirName));
182191
this.$logger.trace("Directories to clean:");
183192
this.$logger.trace(directoriesToClean);
@@ -209,7 +218,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
209218

210219
private getProjectNameFromId(): string {
211220
let id: string;
212-
if(this.$projectData && this.$projectData.projectId) {
221+
if (this.$projectData && this.$projectData.projectId) {
213222
id = this.$projectData.projectId.split(".")[2];
214223
}
215224

@@ -226,7 +235,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
226235

227236
public updatePlatform(currentVersion: string, newVersion: string, canUpdate: boolean, addPlatform?: Function, removePlatforms?: (platforms: string[]) => IFuture<void>): IFuture<boolean> {
228237
return (() => {
229-
if(semver.eq(newVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
238+
if (semver.eq(newVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
230239
let platformLowercase = this.platformData.normalizedPlatformName.toLowerCase();
231240
removePlatforms([platformLowercase.split("@")[0]]).wait();
232241
addPlatform(platformLowercase).wait();
@@ -239,8 +248,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
239248

240249
public buildProject(projectRoot: string, buildConfig?: IBuildConfig): IFuture<void> {
241250
return (() => {
242-
if(this.canUseGradle().wait()) {
243-
this.$androidToolsInfo.validateInfo({showWarningsAsErrors: true, validateTargetSdk: true}).wait();
251+
if (this.canUseGradle().wait()) {
252+
this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }).wait();
244253
let androidToolsInfo = this.$androidToolsInfo.getToolsInfo().wait();
245254
let compileSdk = androidToolsInfo.compileSdkVersion;
246255
let targetSdk = this.getTargetFromAndroidManifest().wait() || compileSdk;
@@ -253,15 +262,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
253262
`-PsupportVersion=${appCompatVersion}`,
254263
];
255264

256-
if(this.$options.release) {
265+
if (this.$options.release) {
257266
buildOptions.push("-Prelease");
258267
buildOptions.push(`-PksPath=${path.resolve(this.$options.keyStorePath)}`);
259268
buildOptions.push(`-Palias=${this.$options.keyStoreAlias}`);
260269
buildOptions.push(`-Ppassword=${this.$options.keyStoreAliasPassword}`);
261270
buildOptions.push(`-PksPassword=${this.$options.keyStorePassword}`);
262271
}
263272

264-
if(!this.canUseStaticBindingGenerator()) {
273+
if (!this.canUseStaticBindingGenerator()) {
265274
buildOptions.push("-PdontRunSbg");
266275
}
267276

@@ -296,7 +305,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
296305

297306
// In case the file is not correct, looks like we are still using the default AndroidManifest.xml from runtime and the current file (in res dir)
298307
// should be merged with it.
299-
if(this.isAndroidManifestFileCorrect(androidManifestPath).wait()) {
308+
if (this.isAndroidManifestFileCorrect(androidManifestPath).wait()) {
300309
// Delete the AndroidManifest.xml file from res directory as the runtime will consider it as addition to the one in src/main and will try to merge them.
301310
// However now they are the same file.
302311
this.$fs.deleteFile(androidManifestPath).wait();
@@ -312,7 +321,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
312321

313322
// In case we should extract the manifest from default template, but for some reason we cannot, break the execution,
314323
// so the original file from Android runtime will be used.
315-
if(shouldExtractDefaultManifest && !this.extractAndroidManifestFromDefaultTemplate(originalAndroidManifestFilePath).wait()) {
324+
if (shouldExtractDefaultManifest && !this.extractAndroidManifestFromDefaultTemplate(originalAndroidManifestFilePath).wait()) {
316325
return;
317326
}
318327

@@ -366,7 +375,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
366375

367376
// Copy include.gradle file
368377
let includeGradleFilePath = path.join(pluginPlatformsFolderPath, "include.gradle");
369-
if(this.$fs.exists(includeGradleFilePath).wait()) {
378+
if (this.$fs.exists(includeGradleFilePath).wait()) {
370379
shell.cp("-f", includeGradleFilePath, pluginConfigurationDirectoryPath);
371380
}
372381
}).future<void>()();
@@ -377,7 +386,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
377386
try {
378387
this.$fs.deleteDirectory(path.join(this.platformData.projectRoot, "configurations", pluginData.name)).wait();
379388
this.$fs.deleteDirectory(path.join(this.platformData.projectRoot, "src", pluginData.name)).wait();
380-
} catch(e) {
389+
} catch (e) {
381390
if (e.code === "ENOENT") {
382391
this.$logger.debug("No native code jars found: " + e.message);
383392
} else {
@@ -412,8 +421,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
412421
private _canUseGradle: boolean;
413422
private canUseGradle(frameworkVersion?: string): IFuture<boolean> {
414423
return (() => {
415-
if(!this._canUseGradle) {
416-
if(!frameworkVersion) {
424+
if (!this._canUseGradle) {
425+
if (!frameworkVersion) {
417426
this.$projectDataService.initialize(this.$projectData.projectDir);
418427
frameworkVersion = this.$projectDataService.getValue(this.platformData.frameworkPackageName).wait().version;
419428
}
@@ -431,7 +440,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
431440
}
432441

433442
private spawn(command: string, args: string[], opts?: any): IFuture<void> {
434-
return this.$childProcess.spawnFromEvent(command, args, "close", opts || { stdio: "inherit"});
443+
return this.$childProcess.spawnFromEvent(command, args, "close", opts || { stdio: "inherit" });
435444
}
436445

437446
private validatePackageName(packageName: string): void {
@@ -442,7 +451,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
442451
}
443452

444453
//Class is a reserved word
445-
if(/\b[Cc]lass\b/.test(packageName)) {
454+
if (/\b[Cc]lass\b/.test(packageName)) {
446455
this.$errors.fail("class is a reserved word");
447456
}
448457
}
@@ -463,9 +472,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
463472
let versionInManifest: string;
464473
if (this.$fs.exists(this.platformData.configurationFilePath).wait()) {
465474
let targetFromAndroidManifest: string = this.$fs.readText(this.platformData.configurationFilePath).wait();
466-
if(targetFromAndroidManifest) {
475+
if (targetFromAndroidManifest) {
467476
let match = targetFromAndroidManifest.match(/.*?android:targetSdkVersion=\"(.*?)\"/);
468-
if(match && match[1]) {
477+
if (match && match[1]) {
469478
versionInManifest = match[1];
470479
}
471480
}
@@ -483,7 +492,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
483492
_.each(directoryContent, (file: string) => {
484493
let sourceFilePath = path.join(frameworkDir, directoryName, file);
485494
let destinationFilePath = path.join(projectRoot, directoryName, file);
486-
if(this.$fs.getFsStats(sourceFilePath).wait().isFile()) {
495+
if (this.$fs.getFsStats(sourceFilePath).wait().isFile()) {
487496
this.$fs.symlink(sourceFilePath, destinationFilePath).wait();
488497
} else {
489498
this.$fs.symlink(sourceFilePath, destinationFilePath, "dir").wait();
@@ -500,15 +509,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
500509
// Use a real magic to detect if this is the correct file, by checking some mandatory strings.
501510
let fileContent = this.$fs.readText(pathToAndroidManifest).wait(),
502511
isFileCorrect = !!(~fileContent.indexOf("android:minSdkVersion") && ~fileContent.indexOf("android:targetSdkVersion")
503-
&& ~fileContent.indexOf("uses-permission") && ~fileContent.indexOf("<application")
504-
&& ~fileContent.indexOf("<activity") && ~fileContent.indexOf("<intent-filter>")
505-
&& ~fileContent.indexOf("android.intent.action.MAIN") && ~fileContent.indexOf("com.tns.ErrorReportActivity")
506-
&& ~fileContent.indexOf("android:versionCode")
507-
&& !this.$xmlValidator.getXmlFileErrors(pathToAndroidManifest).wait());
512+
&& ~fileContent.indexOf("uses-permission") && ~fileContent.indexOf("<application")
513+
&& ~fileContent.indexOf("<activity") && ~fileContent.indexOf("<intent-filter>")
514+
&& ~fileContent.indexOf("android.intent.action.MAIN") && ~fileContent.indexOf("com.tns.ErrorReportActivity")
515+
&& ~fileContent.indexOf("android:versionCode")
516+
&& !this.$xmlValidator.getXmlFileErrors(pathToAndroidManifest).wait());
508517

509518
this.$logger.trace(`Existing ${this.platformData.configurationFileName} is ${isFileCorrect ? "" : "NOT "}correct.`);
510519
return isFileCorrect;
511-
} catch(err) {
520+
} catch (err) {
512521
this.$logger.trace(`Error while checking ${pathToAndroidManifest}: `, err);
513522
return false;
514523
}
@@ -519,9 +528,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
519528

520529
private getConfigurationFileBackupName(originalAndroidManifestFilePath: string): IFuture<string> {
521530
return (() => {
522-
if(!this._configurationFileBackupName) {
531+
if (!this._configurationFileBackupName) {
523532
let defaultBackupName = this.platformData.configurationFileName + ".backup";
524-
if(this.$fs.exists(path.join(path.dirname(originalAndroidManifestFilePath), defaultBackupName)).wait()) {
533+
if (this.$fs.exists(path.join(path.dirname(originalAndroidManifestFilePath), defaultBackupName)).wait()) {
525534
defaultBackupName += `_${createGUID(false)}`;
526535
}
527536
this._configurationFileBackupName = defaultBackupName;
@@ -541,7 +550,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
541550
private revertBackupOfOriginalAndroidManifest(originalAndroidManifestFilePath: string): IFuture<void> {
542551
return (() => {
543552
let pathToBackupFile = path.join(path.dirname(originalAndroidManifestFilePath), this.getConfigurationFileBackupName(originalAndroidManifestFilePath).wait());
544-
if(this.$fs.exists(pathToBackupFile).wait()) {
553+
if (this.$fs.exists(pathToBackupFile).wait()) {
545554
this.$logger.trace(`Could not extract ${this.platformData.configurationFileName} from default template. Reverting the change of your app/App_Resources/${this.platformData.configurationFileName}.`);
546555
shell.mv(pathToBackupFile, originalAndroidManifestFilePath);
547556
}
@@ -556,11 +565,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
556565
if (this.$fs.exists(templateAndroidManifest).wait()) {
557566
this.$logger.trace(`${originalAndroidManifestFilePath} is missing. Upgrading the source of the project with one from the new project template. Copy ${templateAndroidManifest} to ${originalAndroidManifestFilePath}`);
558567
try {
559-
if(alreadyHasAndroidManifest) {
568+
if (alreadyHasAndroidManifest) {
560569
this.backupOriginalAndroidManifest(originalAndroidManifestFilePath).wait();
561570
}
562571
this.$fs.copyFile(templateAndroidManifest, originalAndroidManifestFilePath).wait();
563-
} catch(e) {
572+
} catch (e) {
564573
this.$logger.trace(`Copying template's ${this.platformData.configurationFileName} failed. `, e);
565574
this.revertBackupOfOriginalAndroidManifest(originalAndroidManifestFilePath).wait();
566575
return false;
@@ -570,7 +579,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
570579
return false;
571580
}
572581

573-
if(alreadyHasAndroidManifest) {
582+
if (alreadyHasAndroidManifest) {
574583
this.$logger.warn(`Your ${this.platformData.configurationFileName} in app/App_Resources/Android will be replaced by the default one from hello-world template.`);
575584
this.$logger.printMarkdown(`The original file will be moved to \`${this.getConfigurationFileBackupName(originalAndroidManifestFilePath).wait()}\`. Merge it **manually** with the new \`${this.platformData.configurationFileName}\` in your app/App_Resources/Android.`);
576585
}

0 commit comments

Comments
 (0)