Skip to content

Commit 8e59db0

Browse files
Merge pull request #1736 from NativeScript/milanov/check-android-platform-dependencies-when-adding-platform-to-project
Check Android platform dependencies when adding platform
2 parents 99fcb40 + 8f46639 commit 8e59db0

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)