From 87c4541f357ea1244792c963d3bc188f89a81e47 Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Mon, 29 Jan 2018 15:34:21 +0200 Subject: [PATCH 1/2] feat(resources-update-command): introduce resources-update command Introduce the 'tns resources-update android' command. By design, upon execution it should migrate the directory structure of App_Resources/Android to the new v4 structure - the one that supports inclusion of java source files, arbitrary assets, and any resource files in the App_Resources/Android/src/main directory structure. Additional, user-defined flavors can also be created taking advantage of the new dir structure. docs(resources-update-command): add documentation for the new resources-update command fix(resources-update-command): make prepare and run backward-compatible fix(resource-update-command-tests): inject the new service in tests chore: address PR comments chore: fix git rebase error --- docs/man_pages/index.md | 1 + .../project/configuration/resources-update.md | 31 ++++ .../man_pages/project/configuration/update.md | 3 +- lib/bootstrap.ts | 3 + lib/commands/resources/resources-update.ts | 34 ++++ lib/declarations.d.ts | 6 + lib/services/android-project-service.ts | 151 ++++++++++-------- .../android-resources-migration-service.ts | 75 +++++++++ .../prepare-platform-native-service.ts | 42 +++-- test/debug.ts | 1 + test/npm-support.ts | 1 + test/platform-service.ts | 1 + test/plugins-service.ts | 1 + test/stubs.ts | 12 ++ 14 files changed, 289 insertions(+), 73 deletions(-) create mode 100644 docs/man_pages/project/configuration/resources-update.md create mode 100644 lib/commands/resources/resources-update.ts create mode 100644 lib/services/android-resources-migration-service.ts diff --git a/docs/man_pages/index.md b/docs/man_pages/index.md index 54c6218068..dedc223d9b 100644 --- a/docs/man_pages/index.md +++ b/docs/man_pages/index.md @@ -32,6 +32,7 @@ Command | Description [platform list](project/configuration/platform.html) | Lists all platforms that the project currently targets. [platform remove ``](project/configuration/platform-remove.html) | Removes the selected platform from the platforms that the project currently targets. This operation deletes all platform-specific files and subdirectories from your project. [platform update ``](project/configuration/platform-update.html) | Updates the NativeScript runtime for the specified platform. +[resources-update](project/configuration/resources-update.html) | Updates the App_Resources/'s internal folder structure to conform to that of an Android project. [prepare ``](project/configuration/prepare.html) | Copies relevant content from the app directory to the subdirectory for the selected target platform to let you build the project. [build ``](project/testing/build.html) | Builds the project for the selected target platform and produces an application package or an emulator package. [deploy ``](project/testing/deploy.html) | Deploys the project to a connected physical or virtual device. diff --git a/docs/man_pages/project/configuration/resources-update.md b/docs/man_pages/project/configuration/resources-update.md new file mode 100644 index 0000000000..7bc2b6aed9 --- /dev/null +++ b/docs/man_pages/project/configuration/resources-update.md @@ -0,0 +1,31 @@ +<% if (isJekyll) { %>--- +title: tns resources-update +position: 9 +---<% } %> +#tns resources-update +========== + +Usage | Synopsis +------|------- +`$ tns resources-update` | Defaults to executing `$ tns resources-update android`. Updates the App_Resources/Android's folder structure. +`$ tns resources-update android` | Updates the App_Resources/Android's folder structure. + +Updates the App_Resources/'s internal folder structure to conform to that of an Android project. Android resource files and directories will be located at the following paths: +- `drawable-*`, `values`, `raw`, etc. can be found at `App_Resources/Android/src/main/res` +- `AndroidManifest.xml` can be found at `App_Resources/Android/src/main/AndroidManifest.xml` +- Java source files can be dropped in at `App_Resources/Android/src/main/java` after creating the proper package subdirectory structure +- Additional arbitrary assets can be dropped in at `App_Resources/Android/src/main/assets` + +### Command Limitations + +* The command works only for the directory structure under `App_Resources/Android`. Running `$ tns resources-update ios` will have no effect. + +### Related Commands + +Command | Description +----------|---------- +[install](install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory. +[platform add](platform-add.html) | Configures the current project to target the selected platform. +[platform remove](platform-remove.html) | Removes the selected platform from the platforms that the project currently targets. +[platform](platform.html) | Lists all platforms that the project currently targets. +[prepare](prepare.html) | Copies common and relevant platform-specific content from the app directory to the subdirectory for the selected target platform in the platforms directory. \ No newline at end of file diff --git a/docs/man_pages/project/configuration/update.md b/docs/man_pages/project/configuration/update.md index 2e19186976..a4f15f4701 100644 --- a/docs/man_pages/project/configuration/update.md +++ b/docs/man_pages/project/configuration/update.md @@ -21,4 +21,5 @@ Command | Description [platform remove](platform-remove.html) | Removes the selected platform from the platforms that the project currently targets. [platform](platform.html) | Lists all platforms that the project currently targets. [prepare](prepare.html) | Copies common and relevant platform-specific content from the app directory to the subdirectory for the selected target platform in the platforms directory. -[platform update](platform-update.html) | Updates the NativeScript runtime for the specified platform. \ No newline at end of file +[platform update](platform-update.html) | Updates the NativeScript runtime for the specified platform. +[resources-update android](resources-update.html) | Updates the App_Resources/Android directory to the new v4.0 directory structure \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 6711c80bab..29516775c7 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -107,6 +107,9 @@ $injector.requireCommand("init", "./commands/init"); $injector.require("infoService", "./services/info-service"); $injector.requireCommand("info", "./commands/info"); +$injector.require("androidResourcesMigrationService", "./services/android-resources-migration-service"); +$injector.requireCommand("resources|update", "./commands/resources/resources-update"); + $injector.require("androidToolsInfo", "./android-tools-info"); $injector.require("devicePathProvider", "./device-path-provider"); diff --git a/lib/commands/resources/resources-update.ts b/lib/commands/resources/resources-update.ts new file mode 100644 index 0000000000..4bc224aa8f --- /dev/null +++ b/lib/commands/resources/resources-update.ts @@ -0,0 +1,34 @@ +export class ResourcesUpdateCommand implements ICommand { + public allowedParameters: ICommandParameter[] = []; + + constructor(private $projectData: IProjectData, + private $errors: IErrors, + private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { + this.$projectData.initializeProjectData(); + } + + public async execute(args: string[]): Promise { + await this.$androidResourcesMigrationService.migrate(this.$projectData.getAppResourcesDirectoryPath()); + } + + public async canExecute(args: string[]): Promise { + if (!args || args.length === 0) { + // Command defaults to migrating the Android App_Resources, unless explicitly specified + args = ["android"]; + } + + for (const platform of args) { + if (!this.$androidResourcesMigrationService.canMigrate(platform)) { + this.$errors.failWithoutHelp(`The ${platform} does not need to have its resources updated.`); + } + + if (this.$androidResourcesMigrationService.hasMigrated(this.$projectData.getAppResourcesDirectoryPath())) { + this.$errors.failWithoutHelp("The App_Resources have already been updated for the Android platform."); + } + } + + return true; + } +} + +$injector.registerCommand("resources|update", ResourcesUpdateCommand); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index b3774f131f..8710a87150 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -497,6 +497,12 @@ interface IInfoService { printComponentsInfo(): Promise; } +interface IAndroidResourcesMigrationService { + canMigrate(platformString: string): boolean; + hasMigrated(appResourcesDir: string): boolean; + migrate(appResourcesDir: string): Promise; +} + /** * Describes properties needed for uploading a package to iTunes Connect */ diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 3330b6944a..caf3e4fe01 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -4,7 +4,6 @@ import * as constants from "../constants"; import * as semver from "semver"; import * as projectServiceBaseLib from "./platform-project-service-base"; import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge"; -import { EOL } from "os"; import { attachAwaitDetach, isRecommendedAarFile } from "../common/helpers"; import { Configurations } from "../common/constants"; import { SpawnOptions } from "child_process"; @@ -37,7 +36,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $npm: INodePackageManager, private $androidPluginBuildService: IAndroidPluginBuildService, - private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { + private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, + private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { super($fs, $projectDataService); this._androidProjectPropertiesManagers = Object.create(null); this.isAndroidStudioTemplate = false; @@ -137,18 +137,14 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return Promise.resolve(true); } - public getAppResourcesDestinationDirectoryPath(projectData: IProjectData, frameworkVersion?: string): string { - if (this.canUseGradle(projectData, frameworkVersion)) { - const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR]; - if (this.isAndroidStudioTemplate) { - resourcePath.unshift(constants.APP_FOLDER_NAME); - } - - return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath); + public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string { + const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectData.getAppResourcesDirectoryPath()); + if (appResourcesDirStructureHasMigrated) { + return this.getUpdatedAppResourcesDestinationDirPath(projectData); + } else { + return this.getLegacyAppResourcesDestinationDirPath(projectData); } - - return path.join(this.getPlatformData(projectData).projectRoot, constants.RESOURCES_DIR); } public async validate(projectData: IProjectData): Promise { @@ -198,7 +194,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "gradlew gradlew.bat", "-f"); } - this.cleanResValues(targetSdkVersion, projectData, frameworkVersion); + this.cleanResValues(targetSdkVersion, projectData); const npmConfig: INodePackageManagerInstallOptions = { save: true, @@ -234,8 +230,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - private cleanResValues(targetSdkVersion: number, projectData: IProjectData, frameworkVersion: string): void { - const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData, frameworkVersion); + private cleanResValues(targetSdkVersion: number, projectData: IProjectData): void { + const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData); const directoriesInResFolder = this.$fs.readDirectory(resDestinationDir); const directoriesToClean = directoriesInResFolder .map(dir => { @@ -259,8 +255,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { // Interpolate the apilevel and package this.interpolateConfigurationFile(projectData, platformSpecificData); + const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + + let stringsFilePath: string; + + const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath(projectData); + if (this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath)) { + stringsFilePath = path.join(appResourcesDestinationDirectoryPath, constants.MAIN_DIR, constants.RESOURCES_DIR, 'values', 'strings.xml'); + } else { + stringsFilePath = path.join(appResourcesDestinationDirectoryPath, 'values', 'strings.xml'); + } - const stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), 'values', 'strings.xml'); shell.sed('-i', /__NAME__/, projectData.projectName, stringsFilePath); shell.sed('-i', /__TITLE_ACTIVITY__/, projectData.projectName, stringsFilePath); @@ -316,33 +321,28 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { - if (this.canUseGradle(projectData)) { - const buildOptions = this.getGradleBuildOptions(buildConfig, projectData); - if (this.$logger.getLevel() === "TRACE") { - buildOptions.unshift("--stacktrace"); - buildOptions.unshift("--debug"); - } - if (buildConfig.release) { - buildOptions.unshift("assembleRelease"); - } else { - buildOptions.unshift("assembleDebug"); - } - - const handler = (data: any) => { - this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); - }; - - await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, - this.$childProcess, - handler, - this.executeCommand(this.getPlatformData(projectData).projectRoot, - buildOptions, - { stdio: buildConfig.buildOutputStdio || "inherit" }, - { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true })); + const buildOptions = this.getGradleBuildOptions(buildConfig, projectData); + if (this.$logger.getLevel() === "TRACE") { + buildOptions.unshift("--stacktrace"); + buildOptions.unshift("--debug"); + } + if (buildConfig.release) { + buildOptions.unshift("assembleRelease"); } else { - this.$errors.failWithoutHelp("Cannot complete build because this project is ANT-based." + EOL + - "Run `tns platform remove android && tns platform add android` to switch to Gradle and try again."); + buildOptions.unshift("assembleDebug"); } + + const handler = (data: any) => { + this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); + }; + + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, + this.$childProcess, + handler, + this.executeCommand(this.getPlatformData(projectData).projectRoot, + buildOptions, + { stdio: buildConfig.buildOutputStdio || "inherit" }, + { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true })); } private getGradleBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array { @@ -398,7 +398,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public ensureConfigurationFileInAppResources(projectData: IProjectData): void { - const originalAndroidManifestFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName); + const appResourcesDirectoryPath = projectData.appResourcesDirectoryPath; + const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath); + let originalAndroidManifestFilePath; + + if (appResourcesDirStructureHasMigrated) { + originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, "src", "main", this.getPlatformData(projectData).configurationFileName); + } else { + originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName); + } const manifestExists = this.$fs.exists(originalAndroidManifestFilePath); @@ -407,16 +415,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return; } // Overwrite the AndroidManifest from runtime. - this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath); + if (!appResourcesDirStructureHasMigrated) { + this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath); + } } public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { - const resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); - const valuesDirRegExp = /^values/; - const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp)); - _.each(resourcesDirs, resourceDir => { - this.$fs.deleteDirectory(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), resourceDir)); - }); + this.cleanUpPreparedResources(appResourcesDirectoryPath, projectData); } public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { @@ -641,20 +646,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject // Nothing android specific to check yet. } - private _canUseGradle: boolean; - private canUseGradle(projectData: IProjectData, frameworkVersion?: string): boolean { - if (!this._canUseGradle) { - if (!frameworkVersion) { - const frameworkInfoInProjectFile = this.$projectDataService.getNSValue(projectData.projectDir, this.getPlatformData(projectData).frameworkPackageName); - frameworkVersion = frameworkInfoInProjectFile && frameworkInfoInProjectFile.version; - } - - this._canUseGradle = !frameworkVersion || semver.gte(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE); - } - - return this._canUseGradle; - } - private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): void { const paths = files.split(' ').map(p => path.join(frameworkDir, p)); shell.cp(cpArg, paths, projectRoot); @@ -759,6 +750,40 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`; return semver.gte(normalizedPlatformVersion, versionString); } + + private getLegacyAppResourcesDestinationDirPath(projectData: IProjectData): string { + const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR]; + if (this.isAndroidStudioTemplate) { + resourcePath.unshift(constants.APP_FOLDER_NAME); + } + + return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath); + } + + private getUpdatedAppResourcesDestinationDirPath(projectData: IProjectData): string { + const resourcePath: string[] = [constants.SRC_DIR]; + if (this.isAndroidStudioTemplate) { + resourcePath.unshift(constants.APP_FOLDER_NAME); + } + + return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath); + } + + private cleanUpPreparedResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { + let resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); + if (this.$androidResourcesMigrationService.hasMigrated(projectData.appResourcesDirectoryPath)) { + resourcesDirPath = path.join(resourcesDirPath, constants.MAIN_DIR, constants.RESOURCES_DIR); + } + + const valuesDirRegExp = /^values/; + if (this.$fs.exists(resourcesDirPath)) { + const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp)); + const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath(projectData); + _.each(resourcesDirs, resourceDir => { + this.$fs.deleteDirectory(path.join(appResourcesDestinationDirectoryPath, resourceDir)); + }); + } + } } $injector.register("androidProjectService", AndroidProjectService); diff --git a/lib/services/android-resources-migration-service.ts b/lib/services/android-resources-migration-service.ts new file mode 100644 index 0000000000..75e6959673 --- /dev/null +++ b/lib/services/android-resources-migration-service.ts @@ -0,0 +1,75 @@ +import * as path from "path"; +import * as shell from "shelljs"; +import * as constants from "../constants"; + +export class AndroidResourcesMigrationService implements IAndroidResourcesMigrationService { + private static ANDROID_DIR = "Android"; + private static ANDROID_DIR_TEMP = "Android-Updated"; + private static ANDROID_DIR_OLD = "Android-Pre-v4"; + + constructor(private $fs: IFileSystem, + private $logger: ILogger, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { } + + public canMigrate(platformString: string): boolean { + return platformString.toLowerCase() === this.$devicePlatformsConstants.Android.toLowerCase(); + } + + public hasMigrated(appResourcesDir: string): boolean { + return this.$fs.exists(path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR, constants.SRC_DIR, constants.MAIN_DIR)); + } + + public async migrate(appResourcesDir: string): Promise { + const originalAppResources = path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR); + const appResourcesDestination = path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR_TEMP); + const appMainSourceSet = path.join(appResourcesDestination, constants.SRC_DIR, constants.MAIN_DIR); + const appResourcesMainSourceSetResourcesDestination = path.join(appMainSourceSet, constants.RESOURCES_DIR); + + this.$fs.ensureDirectoryExists(appResourcesDestination); + this.$fs.ensureDirectoryExists(appMainSourceSet); + // create /java, /res and /assets in the App_Resources/Android/src/main directory + this.$fs.ensureDirectoryExists(appResourcesMainSourceSetResourcesDestination); + this.$fs.ensureDirectoryExists(path.join(appMainSourceSet, "java")); + this.$fs.ensureDirectoryExists(path.join(appMainSourceSet, constants.ASSETS_DIR)); + + const isDirectory = (source: string) => this.$fs.getLsStats(source).isDirectory(); + const getAllFiles = (source: string) => this.$fs.readDirectory(source).map(name => path.join(source, name)); + const getDirectories = (files: string[]) => files.filter(isDirectory); + const getFiles = (files: string[]) => files.filter((file: string) => !isDirectory(file)); + + this.$fs.copyFile(path.join(originalAppResources, "app.gradle"), path.join(appResourcesDestination, "app.gradle")); + + const appResourcesFiles = getAllFiles(originalAppResources); + const resourceDirectories = getDirectories(appResourcesFiles); + const resourceFiles = getFiles(appResourcesFiles); + + resourceDirectories.forEach(dir => { + if (path.basename(dir) !== "libs") { + // don't copy /App_Resources/Android/libs into the src/main/res/libs directory + this.$fs.copyFile(dir, appResourcesMainSourceSetResourcesDestination); + } else { + // copy App_Resources/Android/libs to App_ResourcesNew/Android/libs + this.$fs.copyFile(dir, path.join(appResourcesDestination)); + } + }); + + resourceFiles.forEach(file => { + const fileName = path.basename(file); + if (fileName !== constants.MANIFEST_FILE_NAME) { + // don't copy AndroidManifest into /App_Resources/Android as it needs to be inside src/main/ + this.$fs.copyFile(file, path.join(appResourcesDestination, fileName)); + } + }); + + this.$fs.copyFile(path.join(originalAppResources, constants.MANIFEST_FILE_NAME), path.join(appMainSourceSet, constants.MANIFEST_FILE_NAME)); + + // rename the legacy app_resources to ANDROID_DIR_OLD + shell.mv(originalAppResources, path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR_OLD)); + // move the new, updated app_resources to App_Resources/Android, as the de facto resources + shell.mv(appResourcesDestination, originalAppResources); + + this.$logger.out(`Successfully updated your project's application resources '/Android' directory structure.\nThe previous version of your Android application resources has been renamed to '/${AndroidResourcesMigrationService.ANDROID_DIR_OLD}'`); + } +} + +$injector.register("androidResourcesMigrationService", AndroidResourcesMigrationService); diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index 380b2f0ae5..b016290a72 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -1,6 +1,5 @@ import * as constants from "../constants"; import * as path from "path"; -import * as shell from "shelljs"; import { PreparePlatformService } from "./prepare-platform-service"; export class PreparePlatformNativeService extends PreparePlatformService implements IPreparePlatformService { @@ -10,7 +9,8 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme $hooksService: IHooksService, private $nodeModulesBuilder: INodeModulesBuilder, private $pluginsService: IPluginsService, - private $projectChangesService: IProjectChangesService) { + private $projectChangesService: IProjectChangesService, + private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { super($fs, $hooksService, $xmlValidator); } @@ -27,7 +27,7 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme } if (!config.changesInfo || config.changesInfo.changesRequirePrepare || config.appFilesUpdaterOptions.bundle) { - this.copyAppResources(config.platformData, config.projectData); + this.prepareAppResources(config.platformData, config.projectData); await config.platformData.platformProjectService.prepareProject(config.projectData, config.platformSpecificData); } @@ -60,16 +60,40 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); } - private copyAppResources(platformData: IPlatformData, projectData: IProjectData): void { + private prepareAppResources(platformData: IPlatformData, projectData: IProjectData): void { const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); + const appResourcesDestinationDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); - if (this.$fs.exists(appResourcesDirectoryPath)) { - platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath, projectData); + if (this.$fs.exists(appResourcesDestinationDirectoryPath)) { + platformData.platformProjectService.prepareAppResources(appResourcesDestinationDirectoryPath, projectData); const appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData); this.$fs.ensureDirectoryExists(appResourcesDestination); - shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination); - this.$fs.deleteDirectory(appResourcesDirectoryPath); + + if (platformData.normalizedPlatformName.toLowerCase() === "android") { + const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath); + const appResourcesAndroid = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName); + + if (appResourcesDirStructureHasMigrated) { + this.$fs.copyFile(path.join(appResourcesAndroid, "src", "*"), appResourcesDestination); + + this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath); + return; + } + + // https://github.com/NativeScript/android-runtime/issues/899 + // App_Resources/Android/libs is reserved to user's aars and jars, but they should not be copied as resources + this.$fs.copyFile(path.join(appResourcesDestinationDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination); + this.$fs.deleteDirectory(path.join(appResourcesDestination, "libs")); + + this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath); + + return; + } + + this.$fs.copyFile(path.join(appResourcesDestinationDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination); + + this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath); } } diff --git a/test/debug.ts b/test/debug.ts index 8a892e6c7e..9ce6c7a2e6 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -78,6 +78,7 @@ function createTestInjector(): IInjector { testInjector.register("settingsService", SettingsService); testInjector.register("androidPluginBuildService", stubs.AndroidPluginBuildServiceStub); testInjector.register("platformEnvironmentRequirements", {}); + testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); return testInjector; } diff --git a/test/npm-support.ts b/test/npm-support.ts index 9f814dbc84..d2715b0f94 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -97,6 +97,7 @@ function createTestInjector(): IInjector { }) }); testInjector.register("httpClient", {}); + testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); return testInjector; } diff --git a/test/platform-service.ts b/test/platform-service.ts index 2f4c4c3291..855bb1e1ae 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -103,6 +103,7 @@ function createTestInjector() { message: (): void => undefined }) }); + testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); return testInjector; } diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 9fe3d0e1bd..d49dbebaef 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -116,6 +116,7 @@ function createTestInjector() { testInjector.register("analyticsSettingsService", { getPlaygroundInfo: () => Promise.resolve(null) }); + testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); testInjector.register("platformEnvironmentRequirements", {}); return testInjector; diff --git a/test/stubs.ts b/test/stubs.ts index 9e36009616..70f4e4b08d 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -811,3 +811,15 @@ export class EmulatorPlatformService implements IEmulatorPlatformService { return Promise.resolve(); } } + +export class AndroidResourcesMigrationServiceStub implements IAndroidResourcesMigrationService { + canMigrate(platformString: string): boolean { + return true; + } + hasMigrated(appResourcesDir: string): boolean { + return false; + } + migrate(appResourcesDir: string): Promise { + return Promise.resolve(); + } +} From 2031a8c47bd58a9862336a59e799d35f54faabba Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Mon, 12 Mar 2018 14:18:48 +0200 Subject: [PATCH 2/2] docs(resourdces|update): update resources update command doc --- docs/man_pages/index.md | 2 +- .../configuration/{ => resources}/resources-update.md | 10 +++++----- docs/man_pages/project/configuration/update.md | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename docs/man_pages/project/configuration/{ => resources}/resources-update.md (79%) diff --git a/docs/man_pages/index.md b/docs/man_pages/index.md index dedc223d9b..9519eec4c6 100644 --- a/docs/man_pages/index.md +++ b/docs/man_pages/index.md @@ -32,7 +32,7 @@ Command | Description [platform list](project/configuration/platform.html) | Lists all platforms that the project currently targets. [platform remove ``](project/configuration/platform-remove.html) | Removes the selected platform from the platforms that the project currently targets. This operation deletes all platform-specific files and subdirectories from your project. [platform update ``](project/configuration/platform-update.html) | Updates the NativeScript runtime for the specified platform. -[resources-update](project/configuration/resources-update.html) | Updates the App_Resources/'s internal folder structure to conform to that of an Android project. +[resources update](project/configuration/resources-update.html) | Updates the App_Resources/'s internal folder structure to conform to that of an Android Studio project. [prepare ``](project/configuration/prepare.html) | Copies relevant content from the app directory to the subdirectory for the selected target platform to let you build the project. [build ``](project/testing/build.html) | Builds the project for the selected target platform and produces an application package or an emulator package. [deploy ``](project/testing/deploy.html) | Deploys the project to a connected physical or virtual device. diff --git a/docs/man_pages/project/configuration/resources-update.md b/docs/man_pages/project/configuration/resources/resources-update.md similarity index 79% rename from docs/man_pages/project/configuration/resources-update.md rename to docs/man_pages/project/configuration/resources/resources-update.md index 7bc2b6aed9..128a885a32 100644 --- a/docs/man_pages/project/configuration/resources-update.md +++ b/docs/man_pages/project/configuration/resources/resources-update.md @@ -1,16 +1,16 @@ <% if (isJekyll) { %>--- -title: tns resources-update +title: tns resources update position: 9 ---<% } %> -#tns resources-update +#tns resources update ========== Usage | Synopsis ------|------- -`$ tns resources-update` | Defaults to executing `$ tns resources-update android`. Updates the App_Resources/Android's folder structure. -`$ tns resources-update android` | Updates the App_Resources/Android's folder structure. +`$ tns resources update` | Defaults to executing `$ tns resources update android`. Updates the App_Resources/Android's folder structure. +`$ tns resources update android` | Updates the App_Resources/Android's folder structure. -Updates the App_Resources/'s internal folder structure to conform to that of an Android project. Android resource files and directories will be located at the following paths: +Updates the App_Resources/'s internal folder structure to conform to that of an Android Studio project. Android resource files and directories will be located at the following paths: - `drawable-*`, `values`, `raw`, etc. can be found at `App_Resources/Android/src/main/res` - `AndroidManifest.xml` can be found at `App_Resources/Android/src/main/AndroidManifest.xml` - Java source files can be dropped in at `App_Resources/Android/src/main/java` after creating the proper package subdirectory structure diff --git a/docs/man_pages/project/configuration/update.md b/docs/man_pages/project/configuration/update.md index a4f15f4701..785462534e 100644 --- a/docs/man_pages/project/configuration/update.md +++ b/docs/man_pages/project/configuration/update.md @@ -22,4 +22,4 @@ Command | Description [platform](platform.html) | Lists all platforms that the project currently targets. [prepare](prepare.html) | Copies common and relevant platform-specific content from the app directory to the subdirectory for the selected target platform in the platforms directory. [platform update](platform-update.html) | Updates the NativeScript runtime for the specified platform. -[resources-update android](resources-update.html) | Updates the App_Resources/Android directory to the new v4.0 directory structure \ No newline at end of file +[resources update android](resources-update.html) | Updates the App_Resources/Android directory to the new v4.0 directory structure \ No newline at end of file