Skip to content

Commit 5f2d77b

Browse files
authored
Merge pull request #3347 from NativeScript/pete/introduce-resources-update-command
feat: introduce resources update command
2 parents 8a6b640 + 2031a8c commit 5f2d77b

File tree

14 files changed

+289
-73
lines changed

14 files changed

+289
-73
lines changed

docs/man_pages/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Command | Description
3232
[platform list](project/configuration/platform.html) | Lists all platforms that the project currently targets.
3333
[platform remove `<Platform>`](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.
3434
[platform update `<Platform>`](project/configuration/platform-update.html) | Updates the NativeScript runtime for the specified platform.
35+
[resources update](project/configuration/resources-update.html) | Updates the App_Resources/<platform>'s internal folder structure to conform to that of an Android Studio project.
3536
[prepare `<Platform>`](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.
3637
[build `<Platform>`](project/testing/build.html) | Builds the project for the selected target platform and produces an application package or an emulator package.
3738
[deploy `<Platform>`](project/testing/deploy.html) | Deploys the project to a connected physical or virtual device.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<% if (isJekyll) { %>---
2+
title: tns resources update
3+
position: 9
4+
---<% } %>
5+
#tns resources update
6+
==========
7+
8+
Usage | Synopsis
9+
------|-------
10+
`$ tns resources update` | Defaults to executing `$ tns resources update android`. Updates the App_Resources/Android's folder structure.
11+
`$ tns resources update android` | Updates the App_Resources/Android's folder structure.
12+
13+
Updates the App_Resources/<platform>'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:
14+
- `drawable-*`, `values`, `raw`, etc. can be found at `App_Resources/Android/src/main/res`
15+
- `AndroidManifest.xml` can be found at `App_Resources/Android/src/main/AndroidManifest.xml`
16+
- Java source files can be dropped in at `App_Resources/Android/src/main/java` after creating the proper package subdirectory structure
17+
- Additional arbitrary assets can be dropped in at `App_Resources/Android/src/main/assets`
18+
19+
### Command Limitations
20+
21+
* The command works only for the directory structure under `App_Resources/Android`. Running `$ tns resources-update ios` will have no effect.
22+
23+
### Related Commands
24+
25+
Command | Description
26+
----------|----------
27+
[install](install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory.
28+
[platform add](platform-add.html) | Configures the current project to target the selected platform.
29+
[platform remove](platform-remove.html) | Removes the selected platform from the platforms that the project currently targets.
30+
[platform](platform.html) | Lists all platforms that the project currently targets.
31+
[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.

docs/man_pages/project/configuration/update.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ Command | Description
2121
[platform remove](platform-remove.html) | Removes the selected platform from the platforms that the project currently targets.
2222
[platform](platform.html) | Lists all platforms that the project currently targets.
2323
[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.
24-
[platform update](platform-update.html) | Updates the NativeScript runtime for the specified platform.
24+
[platform update](platform-update.html) | Updates the NativeScript runtime for the specified platform.
25+
[resources update android](resources-update.html) | Updates the App_Resources/Android directory to the new v4.0 directory structure

lib/bootstrap.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ $injector.requireCommand("init", "./commands/init");
107107
$injector.require("infoService", "./services/info-service");
108108
$injector.requireCommand("info", "./commands/info");
109109

110+
$injector.require("androidResourcesMigrationService", "./services/android-resources-migration-service");
111+
$injector.requireCommand("resources|update", "./commands/resources/resources-update");
112+
110113
$injector.require("androidToolsInfo", "./android-tools-info");
111114
$injector.require("devicePathProvider", "./device-path-provider");
112115

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export class ResourcesUpdateCommand implements ICommand {
2+
public allowedParameters: ICommandParameter[] = [];
3+
4+
constructor(private $projectData: IProjectData,
5+
private $errors: IErrors,
6+
private $androidResourcesMigrationService: IAndroidResourcesMigrationService) {
7+
this.$projectData.initializeProjectData();
8+
}
9+
10+
public async execute(args: string[]): Promise<void> {
11+
await this.$androidResourcesMigrationService.migrate(this.$projectData.getAppResourcesDirectoryPath());
12+
}
13+
14+
public async canExecute(args: string[]): Promise<boolean> {
15+
if (!args || args.length === 0) {
16+
// Command defaults to migrating the Android App_Resources, unless explicitly specified
17+
args = ["android"];
18+
}
19+
20+
for (const platform of args) {
21+
if (!this.$androidResourcesMigrationService.canMigrate(platform)) {
22+
this.$errors.failWithoutHelp(`The ${platform} does not need to have its resources updated.`);
23+
}
24+
25+
if (this.$androidResourcesMigrationService.hasMigrated(this.$projectData.getAppResourcesDirectoryPath())) {
26+
this.$errors.failWithoutHelp("The App_Resources have already been updated for the Android platform.");
27+
}
28+
}
29+
30+
return true;
31+
}
32+
}
33+
34+
$injector.registerCommand("resources|update", ResourcesUpdateCommand);

lib/declarations.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,12 @@ interface IInfoService {
497497
printComponentsInfo(): Promise<void>;
498498
}
499499

500+
interface IAndroidResourcesMigrationService {
501+
canMigrate(platformString: string): boolean;
502+
hasMigrated(appResourcesDir: string): boolean;
503+
migrate(appResourcesDir: string): Promise<void>;
504+
}
505+
500506
/**
501507
* Describes properties needed for uploading a package to iTunes Connect
502508
*/

lib/services/android-project-service.ts

Lines changed: 88 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as constants from "../constants";
44
import * as semver from "semver";
55
import * as projectServiceBaseLib from "./platform-project-service-base";
66
import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge";
7-
import { EOL } from "os";
87
import { attachAwaitDetach, isRecommendedAarFile } from "../common/helpers";
98
import { Configurations } from "../common/constants";
109
import { SpawnOptions } from "child_process";
@@ -37,7 +36,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
3736
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
3837
private $npm: INodePackageManager,
3938
private $androidPluginBuildService: IAndroidPluginBuildService,
40-
private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) {
39+
private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements,
40+
private $androidResourcesMigrationService: IAndroidResourcesMigrationService) {
4141
super($fs, $projectDataService);
4242
this._androidProjectPropertiesManagers = Object.create(null);
4343
this.isAndroidStudioTemplate = false;
@@ -137,18 +137,14 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
137137
return Promise.resolve(true);
138138
}
139139

140-
public getAppResourcesDestinationDirectoryPath(projectData: IProjectData, frameworkVersion?: string): string {
141-
if (this.canUseGradle(projectData, frameworkVersion)) {
142-
const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR];
143-
if (this.isAndroidStudioTemplate) {
144-
resourcePath.unshift(constants.APP_FOLDER_NAME);
145-
}
146-
147-
return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
140+
public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string {
141+
const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectData.getAppResourcesDirectoryPath());
148142

143+
if (appResourcesDirStructureHasMigrated) {
144+
return this.getUpdatedAppResourcesDestinationDirPath(projectData);
145+
} else {
146+
return this.getLegacyAppResourcesDestinationDirPath(projectData);
149147
}
150-
151-
return path.join(this.getPlatformData(projectData).projectRoot, constants.RESOURCES_DIR);
152148
}
153149

154150
public async validate(projectData: IProjectData): Promise<void> {
@@ -198,7 +194,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
198194
this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "gradlew gradlew.bat", "-f");
199195
}
200196

201-
this.cleanResValues(targetSdkVersion, projectData, frameworkVersion);
197+
this.cleanResValues(targetSdkVersion, projectData);
202198

203199
const npmConfig: INodePackageManagerInstallOptions = {
204200
save: true,
@@ -234,8 +230,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
234230
}
235231
}
236232

237-
private cleanResValues(targetSdkVersion: number, projectData: IProjectData, frameworkVersion: string): void {
238-
const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData, frameworkVersion);
233+
private cleanResValues(targetSdkVersion: number, projectData: IProjectData): void {
234+
const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData);
239235
const directoriesInResFolder = this.$fs.readDirectory(resDestinationDir);
240236
const directoriesToClean = directoriesInResFolder
241237
.map(dir => {
@@ -259,8 +255,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
259255
public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise<void> {
260256
// Interpolate the apilevel and package
261257
this.interpolateConfigurationFile(projectData, platformSpecificData);
258+
const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
259+
260+
let stringsFilePath: string;
261+
262+
const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath(projectData);
263+
if (this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath)) {
264+
stringsFilePath = path.join(appResourcesDestinationDirectoryPath, constants.MAIN_DIR, constants.RESOURCES_DIR, 'values', 'strings.xml');
265+
} else {
266+
stringsFilePath = path.join(appResourcesDestinationDirectoryPath, 'values', 'strings.xml');
267+
}
262268

263-
const stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), 'values', 'strings.xml');
264269
shell.sed('-i', /__NAME__/, projectData.projectName, stringsFilePath);
265270
shell.sed('-i', /__TITLE_ACTIVITY__/, projectData.projectName, stringsFilePath);
266271

@@ -316,33 +321,28 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
316321
}
317322

318323
public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise<void> {
319-
if (this.canUseGradle(projectData)) {
320-
const buildOptions = this.getGradleBuildOptions(buildConfig, projectData);
321-
if (this.$logger.getLevel() === "TRACE") {
322-
buildOptions.unshift("--stacktrace");
323-
buildOptions.unshift("--debug");
324-
}
325-
if (buildConfig.release) {
326-
buildOptions.unshift("assembleRelease");
327-
} else {
328-
buildOptions.unshift("assembleDebug");
329-
}
330-
331-
const handler = (data: any) => {
332-
this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
333-
};
334-
335-
await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME,
336-
this.$childProcess,
337-
handler,
338-
this.executeCommand(this.getPlatformData(projectData).projectRoot,
339-
buildOptions,
340-
{ stdio: buildConfig.buildOutputStdio || "inherit" },
341-
{ emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }));
324+
const buildOptions = this.getGradleBuildOptions(buildConfig, projectData);
325+
if (this.$logger.getLevel() === "TRACE") {
326+
buildOptions.unshift("--stacktrace");
327+
buildOptions.unshift("--debug");
328+
}
329+
if (buildConfig.release) {
330+
buildOptions.unshift("assembleRelease");
342331
} else {
343-
this.$errors.failWithoutHelp("Cannot complete build because this project is ANT-based." + EOL +
344-
"Run `tns platform remove android && tns platform add android` to switch to Gradle and try again.");
332+
buildOptions.unshift("assembleDebug");
345333
}
334+
335+
const handler = (data: any) => {
336+
this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
337+
};
338+
339+
await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME,
340+
this.$childProcess,
341+
handler,
342+
this.executeCommand(this.getPlatformData(projectData).projectRoot,
343+
buildOptions,
344+
{ stdio: buildConfig.buildOutputStdio || "inherit" },
345+
{ emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }));
346346
}
347347

348348
private getGradleBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array<string> {
@@ -398,7 +398,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
398398
}
399399

400400
public ensureConfigurationFileInAppResources(projectData: IProjectData): void {
401-
const originalAndroidManifestFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName);
401+
const appResourcesDirectoryPath = projectData.appResourcesDirectoryPath;
402+
const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath);
403+
let originalAndroidManifestFilePath;
404+
405+
if (appResourcesDirStructureHasMigrated) {
406+
originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, "src", "main", this.getPlatformData(projectData).configurationFileName);
407+
} else {
408+
originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName);
409+
}
402410

403411
const manifestExists = this.$fs.exists(originalAndroidManifestFilePath);
404412

@@ -407,16 +415,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
407415
return;
408416
}
409417
// Overwrite the AndroidManifest from runtime.
410-
this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath);
418+
if (!appResourcesDirStructureHasMigrated) {
419+
this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath);
420+
}
411421
}
412422

413423
public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void {
414-
const resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName);
415-
const valuesDirRegExp = /^values/;
416-
const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp));
417-
_.each(resourcesDirs, resourceDir => {
418-
this.$fs.deleteDirectory(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), resourceDir));
419-
});
424+
this.cleanUpPreparedResources(appResourcesDirectoryPath, projectData);
420425
}
421426

422427
public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
@@ -641,20 +646,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
641646
// Nothing android specific to check yet.
642647
}
643648

644-
private _canUseGradle: boolean;
645-
private canUseGradle(projectData: IProjectData, frameworkVersion?: string): boolean {
646-
if (!this._canUseGradle) {
647-
if (!frameworkVersion) {
648-
const frameworkInfoInProjectFile = this.$projectDataService.getNSValue(projectData.projectDir, this.getPlatformData(projectData).frameworkPackageName);
649-
frameworkVersion = frameworkInfoInProjectFile && frameworkInfoInProjectFile.version;
650-
}
651-
652-
this._canUseGradle = !frameworkVersion || semver.gte(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE);
653-
}
654-
655-
return this._canUseGradle;
656-
}
657-
658649
private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): void {
659650
const paths = files.split(' ').map(p => path.join(frameworkDir, p));
660651
shell.cp(cpArg, paths, projectRoot);
@@ -759,6 +750,40 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
759750
const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`;
760751
return semver.gte(normalizedPlatformVersion, versionString);
761752
}
753+
754+
private getLegacyAppResourcesDestinationDirPath(projectData: IProjectData): string {
755+
const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR];
756+
if (this.isAndroidStudioTemplate) {
757+
resourcePath.unshift(constants.APP_FOLDER_NAME);
758+
}
759+
760+
return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
761+
}
762+
763+
private getUpdatedAppResourcesDestinationDirPath(projectData: IProjectData): string {
764+
const resourcePath: string[] = [constants.SRC_DIR];
765+
if (this.isAndroidStudioTemplate) {
766+
resourcePath.unshift(constants.APP_FOLDER_NAME);
767+
}
768+
769+
return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
770+
}
771+
772+
private cleanUpPreparedResources(appResourcesDirectoryPath: string, projectData: IProjectData): void {
773+
let resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName);
774+
if (this.$androidResourcesMigrationService.hasMigrated(projectData.appResourcesDirectoryPath)) {
775+
resourcesDirPath = path.join(resourcesDirPath, constants.MAIN_DIR, constants.RESOURCES_DIR);
776+
}
777+
778+
const valuesDirRegExp = /^values/;
779+
if (this.$fs.exists(resourcesDirPath)) {
780+
const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp));
781+
const appResourcesDestinationDirectoryPath = this.getAppResourcesDestinationDirectoryPath(projectData);
782+
_.each(resourcesDirs, resourceDir => {
783+
this.$fs.deleteDirectory(path.join(appResourcesDestinationDirectoryPath, resourceDir));
784+
});
785+
}
786+
}
762787
}
763788

764789
$injector.register("androidProjectService", AndroidProjectService);

0 commit comments

Comments
 (0)