Skip to content

feat: introduce resources update command #3347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/man_pages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Command | Description
[platform list](project/configuration/platform.html) | Lists all platforms that the project currently targets.
[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.
[platform update `<Platform>`](project/configuration/platform-update.html) | Updates the NativeScript runtime for the specified platform.
[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.
[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.
[build `<Platform>`](project/testing/build.html) | Builds the project for the selected target platform and produces an application package or an emulator package.
[deploy `<Platform>`](project/testing/deploy.html) | Deploys the project to a connected physical or virtual device.
Expand Down
31 changes: 31 additions & 0 deletions docs/man_pages/project/configuration/resources/resources-update.md
Original file line number Diff line number Diff line change
@@ -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/<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:
- `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.
3 changes: 2 additions & 1 deletion docs/man_pages/project/configuration/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
[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
3 changes: 3 additions & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
34 changes: 34 additions & 0 deletions lib/commands/resources/resources-update.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await this.$androidResourcesMigrationService.migrate(this.$projectData.getAppResourcesDirectoryPath());
}

public async canExecute(args: string[]): Promise<boolean> {
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);
6 changes: 6 additions & 0 deletions lib/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@ interface IInfoService {
printComponentsInfo(): Promise<void>;
}

interface IAndroidResourcesMigrationService {
canMigrate(platformString: string): boolean;
hasMigrated(appResourcesDir: string): boolean;
migrate(appResourcesDir: string): Promise<void>;
}

/**
* Describes properties needed for uploading a package to iTunes Connect
*/
Expand Down
151 changes: 88 additions & 63 deletions lib/services/android-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<void> {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 => {
Expand All @@ -259,8 +255,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise<void> {
// 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);

Expand Down Expand Up @@ -316,33 +321,28 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
}

public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise<void> {
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<string> {
Expand Down Expand Up @@ -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);

Expand All @@ -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<void> {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Loading