Skip to content

Commit 9dd55f8

Browse files
committed
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
1 parent c640f95 commit 9dd55f8

14 files changed

+249
-68
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&nbsp;remove&nbsp;`<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 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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
resources-update
2+
==========
3+
4+
Usage | Synopsis
5+
------|-------
6+
`$ tns resources-update` | Defaults to executing `$ tns resources-update android`. Updates the App_Resources/Android's folder structure.
7+
`$ tns resources-update android` | Updates the App_Resources/Android's folder structure.
8+
9+
Updates the App_Resources/<platform>'s internal folder structure to conform to that of an Android project. Android resource files and directories will be located at the following paths:
10+
- `drawable-*`, `values`, `raw`, etc. can be found at `App_Resources/Android/src/main/res`
11+
- `AndroidManifest.xml` can be found at `App_Resources/Android/src/main/AndroidManifest.xml`
12+
- Java source files can be dropped in at `App_Resources/Android/src/main/java` after creating the proper package subdirectory structure
13+
- Additional arbitrary assets can be dropped in at `App_Resources/Android/src/main/assets`
14+
15+
### Command Limitations
16+
17+
* The command works only for the directory structure under `App_Resources/Android`. Running `$ tns resources-update ios` will have no effect.
18+
19+
### Related Commands
20+
21+
Command | Description
22+
----------|----------
23+
[install](install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory.
24+
[platform add](platform-add.html) | Configures the current project to target the selected platform.
25+
[platform remove](platform-remove.html) | Removes the selected platform from the platforms that the project currently targets.
26+
[platform](platform.html) | Lists all platforms that the project currently targets.
27+
[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
@@ -102,6 +102,9 @@ $injector.requireCommand("init", "./commands/init");
102102
$injector.require("infoService", "./services/info-service");
103103
$injector.requireCommand("info", "./commands/info");
104104

105+
$injector.require("projectV4MigrationService", "./services/project-v4-migration-service");
106+
$injector.requireCommand("resources-update", "./commands/resources-update");
107+
105108
$injector.require("androidToolsInfo", "./android-tools-info");
106109
$injector.require("devicePathProvider", "./device-path-provider");
107110

lib/commands/resources-update.ts

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 $projectV4MigrationService: IProjectV4MigrationService) {
7+
this.$projectData.initializeProjectData();
8+
}
9+
10+
public async execute(args: string[]): Promise<void> {
11+
await this.$projectV4MigrationService.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.$projectV4MigrationService.canMigrate(platform)) {
22+
this.$errors.failWithoutHelp("The iOS platform does not need to have its resources updated.");
23+
}
24+
25+
if (this.$projectV4MigrationService.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 IProjectV4MigrationService {
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: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ 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";
77
import { attachAwaitDetach } from "../common/helpers";
8-
import { EOL } from "os";
98
import { Configurations } from "../common/constants";
109
import { SpawnOptions } from "child_process";
1110

@@ -36,7 +35,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
3635
private $injector: IInjector,
3736
private $pluginVariablesService: IPluginVariablesService,
3837
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
39-
private $npm: INodePackageManager) {
38+
private $npm: INodePackageManager,
39+
private $projectV4MigrationService: IProjectV4MigrationService) {
4040
super($fs, $projectDataService);
4141
this._androidProjectPropertiesManagers = Object.create(null);
4242
this.isAndroidStudioTemplate = false;
@@ -133,18 +133,32 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
133133
return Promise.resolve(true);
134134
}
135135

136-
public getAppResourcesDestinationDirectoryPath(projectData: IProjectData, frameworkVersion?: string): string {
137-
if (this.canUseGradle(projectData, frameworkVersion)) {
138-
const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR];
139-
if (this.isAndroidStudioTemplate) {
140-
resourcePath.unshift(constants.APP_FOLDER_NAME);
141-
}
136+
public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string {
137+
const appResourcesDirStructureHasMigrated = this.$projectV4MigrationService.hasMigrated(projectData.getAppResourcesDirectoryPath());
138+
139+
if (appResourcesDirStructureHasMigrated) {
140+
return this.getAppResourcesDestinationDirectoryPathUpdatedAppResourcesDirStructure(projectData);
141+
} else {
142+
return this.getAppResourcesDestinationDirectoryPathOldAppResourcesDirStructure(projectData);
143+
}
144+
}
145+
146+
private getAppResourcesDestinationDirectoryPathOldAppResourcesDirStructure(projectData: IProjectData): string {
147+
const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR];
148+
if (this.isAndroidStudioTemplate) {
149+
resourcePath.unshift(constants.APP_FOLDER_NAME);
150+
}
142151

143-
return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
152+
return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
153+
}
144154

155+
private getAppResourcesDestinationDirectoryPathUpdatedAppResourcesDirStructure(projectData: IProjectData): string {
156+
const resourcePath: string[] = [constants.SRC_DIR];
157+
if (this.isAndroidStudioTemplate) {
158+
resourcePath.unshift(constants.APP_FOLDER_NAME);
145159
}
146160

147-
return path.join(this.getPlatformData(projectData).projectRoot, constants.RESOURCES_DIR);
161+
return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath);
148162
}
149163

150164
public async validate(projectData: IProjectData): Promise<void> {
@@ -199,7 +213,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
199213
this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "gradlew gradlew.bat", "-f");
200214
}
201215

202-
this.cleanResValues(targetSdkVersion, projectData, frameworkVersion);
216+
this.cleanResValues(targetSdkVersion, projectData);
203217

204218
const npmConfig: INodePackageManagerInstallOptions = {
205219
save: true,
@@ -235,8 +249,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
235249
}
236250
}
237251

238-
private cleanResValues(targetSdkVersion: number, projectData: IProjectData, frameworkVersion: string): void {
239-
const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData, frameworkVersion);
252+
private cleanResValues(targetSdkVersion: number, projectData: IProjectData): void {
253+
const resDestinationDir = this.getAppResourcesDestinationDirectoryPath(projectData);
240254
const directoriesInResFolder = this.$fs.readDirectory(resDestinationDir);
241255
const directoriesToClean = directoriesInResFolder
242256
.map(dir => {
@@ -260,8 +274,16 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
260274
public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise<void> {
261275
// Interpolate the apilevel and package
262276
this.interpolateConfigurationFile(projectData, platformSpecificData);
277+
const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
278+
279+
let stringsFilePath: string;
280+
281+
if (!this.$projectV4MigrationService.hasMigrated(appResourcesDirectoryPath)) {
282+
stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), 'values', 'strings.xml');
283+
} else {
284+
stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), "main", "res", 'values', 'strings.xml');
285+
}
263286

264-
const stringsFilePath = path.join(this.getAppResourcesDestinationDirectoryPath(projectData), 'values', 'strings.xml');
265287
shell.sed('-i', /__NAME__/, projectData.projectName, stringsFilePath);
266288
shell.sed('-i', /__TITLE_ACTIVITY__/, projectData.projectName, stringsFilePath);
267289

@@ -317,33 +339,28 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
317339
}
318340

319341
public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise<void> {
320-
if (this.canUseGradle(projectData)) {
321-
const buildOptions = this.getBuildOptions(buildConfig, projectData);
322-
if (this.$logger.getLevel() === "TRACE") {
323-
buildOptions.unshift("--stacktrace");
324-
buildOptions.unshift("--debug");
325-
}
326-
if (buildConfig.release) {
327-
buildOptions.unshift("assembleRelease");
328-
} else {
329-
buildOptions.unshift("assembleDebug");
330-
}
331-
332-
const handler = (data: any) => {
333-
this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
334-
};
335-
336-
await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME,
337-
this.$childProcess,
338-
handler,
339-
this.executeGradleCommand(this.getPlatformData(projectData).projectRoot,
340-
buildOptions,
341-
{ stdio: buildConfig.buildOutputStdio || "inherit" },
342-
{ emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }));
342+
const buildOptions = this.getBuildOptions(buildConfig, projectData);
343+
if (this.$logger.getLevel() === "TRACE") {
344+
buildOptions.unshift("--stacktrace");
345+
buildOptions.unshift("--debug");
346+
}
347+
if (buildConfig.release) {
348+
buildOptions.unshift("assembleRelease");
343349
} else {
344-
this.$errors.failWithoutHelp("Cannot complete build because this project is ANT-based." + EOL +
345-
"Run `tns platform remove android && tns platform add android` to switch to Gradle and try again.");
350+
buildOptions.unshift("assembleDebug");
346351
}
352+
353+
const handler = (data: any) => {
354+
this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
355+
};
356+
357+
await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME,
358+
this.$childProcess,
359+
handler,
360+
this.executeGradleCommand(this.getPlatformData(projectData).projectRoot,
361+
buildOptions,
362+
{ stdio: buildConfig.buildOutputStdio || "inherit" },
363+
{ emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }));
347364
}
348365

349366
private getBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array<string> {
@@ -391,7 +408,15 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
391408
}
392409

393410
public ensureConfigurationFileInAppResources(projectData: IProjectData): void {
394-
const originalAndroidManifestFilePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName);
411+
const appResourcesDirectoryPath = projectData.appResourcesDirectoryPath;
412+
const appResourcesDirStructureHasMigrated = this.$projectV4MigrationService.hasMigrated(appResourcesDirectoryPath);
413+
let originalAndroidManifestFilePath;
414+
415+
if (appResourcesDirStructureHasMigrated) {
416+
originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, "src", "main", this.getPlatformData(projectData).configurationFileName);
417+
} else {
418+
originalAndroidManifestFilePath = path.join(appResourcesDirectoryPath, this.$devicePlatformsConstants.Android, this.getPlatformData(projectData).configurationFileName);
419+
}
395420

396421
const manifestExists = this.$fs.exists(originalAndroidManifestFilePath);
397422

@@ -400,16 +425,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
400425
return;
401426
}
402427
// Overwrite the AndroidManifest from runtime.
403-
this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath);
428+
if (!appResourcesDirStructureHasMigrated) {
429+
this.$fs.copyFile(originalAndroidManifestFilePath, this.getPlatformData(projectData).configurationFilePath);
430+
}
404431
}
405432

406433
public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void {
407-
const resourcesDirPath = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName);
408-
const valuesDirRegExp = /^values/;
409-
const resourcesDirs = this.$fs.readDirectory(resourcesDirPath).filter(resDir => !resDir.match(valuesDirRegExp));
410-
_.each(resourcesDirs, resourceDir => {
411-
this.$fs.deleteDirectory(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), resourceDir));
412-
});
434+
// Intentionally left empty
413435
}
414436

415437
public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
@@ -559,20 +581,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
559581
// Nothing android specific to check yet.
560582
}
561583

562-
private _canUseGradle: boolean;
563-
private canUseGradle(projectData: IProjectData, frameworkVersion?: string): boolean {
564-
if (!this._canUseGradle) {
565-
if (!frameworkVersion) {
566-
const frameworkInfoInProjectFile = this.$projectDataService.getNSValue(projectData.projectDir, this.getPlatformData(projectData).frameworkPackageName);
567-
frameworkVersion = frameworkInfoInProjectFile && frameworkInfoInProjectFile.version;
568-
}
569-
570-
this._canUseGradle = !frameworkVersion || semver.gte(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE);
571-
}
572-
573-
return this._canUseGradle;
574-
}
575-
576584
private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): void {
577585
const paths = files.split(' ').map(p => path.join(frameworkDir, p));
578586
shell.cp(cpArg, paths, projectRoot);

lib/services/prepare-platform-native-service.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme
1010
$hooksService: IHooksService,
1111
private $nodeModulesBuilder: INodeModulesBuilder,
1212
private $pluginsService: IPluginsService,
13-
private $projectChangesService: IProjectChangesService) {
13+
private $projectChangesService: IProjectChangesService,
14+
private $projectV4MigrationService: IProjectV4MigrationService) {
1415
super($fs, $hooksService, $xmlValidator);
1516
}
1617

@@ -62,13 +63,36 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme
6263

6364
private copyAppResources(platformData: IPlatformData, projectData: IProjectData): void {
6465
const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
65-
const appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME);
66-
if (this.$fs.exists(appResourcesDirectoryPath)) {
67-
platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath, projectData);
66+
const appResourcesDestinationDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME);
67+
if (this.$fs.exists(appResourcesDestinationDirectoryPath)) {
68+
platformData.platformProjectService.prepareAppResources(appResourcesDestinationDirectoryPath, projectData);
6869
const appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData);
6970
this.$fs.ensureDirectoryExists(appResourcesDestination);
70-
shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination);
71-
this.$fs.deleteDirectory(appResourcesDirectoryPath);
71+
72+
if (platformData.normalizedPlatformName.toLowerCase() === "android") {
73+
const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
74+
const appResourcesDirStructureHasMigrated = this.$projectV4MigrationService.hasMigrated(appResourcesDirectoryPath);
75+
const appResourcesAndroid = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName);
76+
77+
if (appResourcesDirStructureHasMigrated) {
78+
shell.cp("-Rf", path.join(appResourcesAndroid, "src", "main"), appResourcesDestination);
79+
80+
return;
81+
}
82+
83+
// https://github.com/NativeScript/android-runtime/issues/899
84+
// App_Resources/Android/libs is reserved to user's aars and jars, but they should not be copied as resources
85+
shell.cp("-Rf", path.join(appResourcesDestinationDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination);
86+
shell.rm("-Rf", path.join(appResourcesDestination, "libs"));
87+
88+
this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath);
89+
90+
return;
91+
}
92+
93+
shell.cp("-Rf", path.join(appResourcesDestinationDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination);
94+
95+
this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath);
7296
}
7397
}
7498

0 commit comments

Comments
 (0)