Skip to content

Plamen5kov/rebuild aar #3398

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 11 commits into from
Mar 8, 2018
Merged

Plamen5kov/rebuild aar #3398

merged 11 commits into from
Mar 8, 2018

Conversation

Plamen5kov
Copy link
Contributor

@Plamen5kov Plamen5kov commented Feb 27, 2018

Don't merge before: telerik/mobile-cli-lib#1055
this PR is based on branch: kddimitrov/nsconfig-app-folder

problem
the CLI doesn't build aar files for the native plugins

solution
build aar when:

  • there are AndroidManifest.xml or res folder in plugins' platforms/android/ folder
  • --syncAllFiles flag is passed (and a change is made in plugins' platforms/android/` folder

related issues
NativeScript/android#890
NativeScript/nativescript-plugin-seed#65

);
}

return this.getDirectories(source).filter(isAndroidSourceDirectory);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using the following approach

const directories = ["res", "java", "assets", "jniLibs"];
return this.$fs.enumerateFilesInDirectorySync(source, (file, stat) => stat.isDirectory() && _.includes(file, directoties));

return this.getDirectories(source).filter(isAndroidSourceDirectory);
}

private getManifest(platformsDir: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use platformsData.getPlatformData().configurationFilePath?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the plugin's platforms dir, not the project

return this.$fs.readDirectory(source).map(name => path.join(source, name));
}

private createDirIfDoesntExist(dirPath: string, { isRelativeToScript = false } = {}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use this.$fs.ensureDirectoryExists(dirPath)?

@rosen-vladimirov
Copy link
Contributor

@Plamen5kov can you please rebase the branch correctly, so it will be easier to check the new commits. Also currently it conflicts and cannot be merged.

@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch from e177efe to 10d60af Compare February 27, 2018 14:27
@Plamen5kov
Copy link
Contributor Author

@rosen-vladimirov I did

@Plamen5kov Plamen5kov self-assigned this Feb 28, 2018
@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch from 10d60af to d59dbe7 Compare February 28, 2018 11:45
@KristianDD KristianDD force-pushed the kddimitrov/nsconfig-app-folder branch from b96a8cc to 2b5ef94 Compare February 28, 2018 12:01
private getAndroidSourceDirectories(source: string): Array<string> {
const directories = ["res", "java", "assets", "jniLibs"];
const resultArr: Array<string> = [];
this.$fs.enumerateFilesInDirectorySync(source, (file, stat) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return this.$fs.enumerateFilesInDirectorySync(source, (file, stat) => stat.isDirectory() && _.some(directories, (element) => file.endsWith(element)))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't work, returns true or false i need the array

Copy link
Contributor

@rosen-vladimirov rosen-vladimirov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gradle-plugin is an external tool, so it should be placed in a separate location, not in services. A common place for such tools is a vendor directory at the root of the project. You can place the gradle-plugin there. Also note that using such tools directly in the package requires adding their LICENSE file as well, so can you please add it as well.

import * as path from "path";
import * as shell from "shelljs";

const xml2js = require("xml2js");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add @types/xml2js as devDependency and use import here;

import { Builder, parseString } from "xml2js";

return resultArr;
}

private getManifest(platformsDir: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method needs return type

return newManifestContent;
}

private createManifestContent(packageName: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method needs return type

return newManifestContent;
}

private async getXml(stringContent: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method needs return type

if (manifestFilePath) {
let androidManifestContent;
try {
androidManifestContent = this.$fs.readFile(manifestFilePath).toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use this.$fs.readText here. This way you'll not have to use toString()

}
}

private validateOptions(options: IBuildOptions) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method needs return type

}

// Do nothing, the Android Gradle script will configure itself based on the input dependencies.json
}

public async checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>> {
const detectedPlugins: Array<any> = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can introduce an interface in this class or at least inline the properties:

public async checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<{ platformsAndroidDirPath: string, pluginName: string }>> {
    const detectedPlugins: Array<{ platformsAndroidDirPath: string, pluginName: string }> = [];

return detectedPlugins;
}

private isAllowedFile(item: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method needs return type

const currentItemStat = this.$fs.getFsStats(item);
if (currentItemStat.mtime > aarStat.mtime) {
detectedPlugins.push({
platformsAndroidDirPath: platformsAndroidDirPath,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the short syntax here:

detectedPlugins.push({
    platformsAndroidDirPath,
    pluginName
});

});
} else if (nativeFiles.length > 0) {
detectedPlugins.push({
platformsAndroidDirPath: platformsAndroidDirPath,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above:
you can use the short syntax here:

detectedPlugins.push({
    platformsAndroidDirPath,
    pluginName
});

import * as path from "path";
import * as shell from "shelljs";

const xml2js = require("xml2js");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to add a separate service that is wrapper for xml2js package and to inject the new service in constructor. (It's not a merge stopper)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we can hold off on that because xml2js is used only in AndroidPluginBuildService and there's no publicly exposed API, that can be used when the CLI is required.

return resultArr;
}

private getManifest(platformsDir: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to traverse the platforms dir to find the manifest file. Shoudn't it be in platforms\android folder. If this is the case, should'n we just return

path.join(platfromsDir, "android", AndroidPluginBuildService.ANDROID_MANIFEST_XML);

Copy link
Contributor Author

@Plamen5kov Plamen5kov Mar 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin's platforms/android folder does not necessarily have an AndroidManifest.xml file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration tool started initially with the thought that it could also support flavors of the android libraries, in which cases there could be more than one manifests. Since I later realized that is a somewhat unrealistic expectation (that plugins would have additional flavors), I dropped the idea, but didn't modify the code as it kept working.

Yes, you can indeed use path.join followed by a check if fs.existsSync()


const xml2js = require("xml2js");

export class AndroidPluginBuildService implements IAndroidPluginBuildService {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider to add some unit tests for this service.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch 2 times, most recently from e620e83 to 9e44a11 Compare March 2, 2018 14:35
@Plamen5kov Plamen5kov changed the base branch from kddimitrov/nsconfig-app-folder to master March 5, 2018 07:33
@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch 3 times, most recently from 4ecee82 to 149bdb2 Compare March 5, 2018 08:31
@Plamen5kov
Copy link
Contributor Author

Plamen5kov commented Mar 5, 2018

Added unit tests and rebased on master branch.
ping @rosen-vladimirov

@Plamen5kov Plamen5kov added this to the 4.0.0 milestone Mar 5, 2018
private $hostInfo: IHostInfo,
private $androidToolsInfo: IAndroidToolsInfo) { }

private static ANDROID_MANIFEST_XML = "AndroidManifest.xml";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the MANIFEST_FILE_NAME constant instead

private $androidToolsInfo: IAndroidToolsInfo) { }

private static ANDROID_MANIFEST_XML = "AndroidManifest.xml";
private static INCLUDE_GRADLE = "include.gradle";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as the include.gradle string is already used in android-project-service.ts, maybe it is better to extract a constant in constants file and use it here and in AndroidProjectService

private static ANDROID_PLUGIN_GRADLE_TEMPLATE = "../../vendor/gradle-plugin";

private getAndroidSourceDirectories(source: string): Array<string> {
const directories = ["res", "java", "assets", "jniLibs"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use RESOURCES_DIR and ASSETS_DIR from constants


private async updateManifestContent(oldManifestContent: string, defaultPackageName: string): Promise<string> {
const content = oldManifestContent;
let newManifestContent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to declare the variable here. Just declare it where you will use it.

}

private async updateManifestContent(oldManifestContent: string, defaultPackageName: string): Promise<string> {
const content = oldManifestContent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need this variable?

package.json Outdated
@@ -79,6 +79,7 @@
"winreg": "0.0.17",
"ws": "2.2.0",
"xcode": "https://github.com/NativeScript/node-xcode/archive/1.4.0.tar.gz",
"xml2js": "^0.4.19",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use strict versions of both new dependencies.

@@ -47,7 +47,7 @@ describe("IOSEntitlements Service Tests", () => {
injector = createTestInjector();

platformsData = injector.resolve("platformsData");
projectData = injector.resolve("projectData");
projectData = $injector.resolve<IProjectData>("projectData");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this way you'll not use the local injector variable, but the global $injector one. It is a bad practice to use the global variable in the tests. Can you elaborate why you needed to change this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably a copy-paste error, but thanks for the explanation

@@ -32,20 +32,6 @@ describe("Plugin preparation", () => {
assert.deepEqual({}, pluginPrepare.preparedDependencies);
});

it("skips prepare if every plugin prepared", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it mean that now we'll never skip prepare even if all plugins are okay?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, if the JS part of the plugins is prepared we will skill prepare, but we will always check for changes in the native part of a plugin for changes that need rebuilding.

await androidBuildPluginService.buildAar(config);
const hasGeneratedAar = fs.exists(path.join(tempFolder, expectedAarName));

assert.equal(hasGeneratedAar, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.isTrue(hasGeneratedAar);


allprojects {
repositories {
jcenter()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we add the google() repository as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need, because we decide what dependencies to use, and we don't have any dependencies that would use google()

@Plamen5kov
Copy link
Contributor Author

run ci

private $fs: IFileSystem,
private $options: IOptions) {

if (this.$options.path) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can shorten the whole syntax:

this.pluginProjectPath = path.resolve(this.$options.path || ".");


const tempAndroidProject = path.join(platformsAndroidPath, "android-project");

const options: IBuildOptions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use short syntax here:

const options: IBuildOptions = {
	aarOutputDir: platformsAndroidPath,
	platformsAndroidDirPath,
	pluginName,
	tempPluginDirPath: tempAndroidProject
};

But this is just a preference, so definitely not a merge stopper

}

public async canExecute(args: string[]): Promise<boolean> {
if (!this.$fs.exists(path.join(this.pluginProjectPath, "platforms", "android"))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

platforms -> constants.PLATFORMS_DIR_NAME

@@ -0,0 +1,15 @@

interface IBuildOptions extends IAndroidBuildOptions{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface does not add anything to the IAndroidBuildOptions. What is the purpose of having it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In one of your previous comments, you mentioned the properties are android specific so I moved them to an android specific interface.

return buildOptions;
}

public getBuildOptions(configurationFilePath?: string): Array<string> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. As I cannot think of a better name, lets keep the current one.

filesToSync.push(filePath);
} else if (event === "unlink" || event === "unlinkDir") {
filesToRemove.push(filePath);
if (!isAllowedFinalFile(filePath)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I place .aar file in App_Resources? In case yes, what should happen here - we'll not include the files in filesToSync and... we should rebuild the application? Will this happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't talked about placing aar files in App_Resources so I wouldn't know what would happen.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rosen-vladimirov App_Resources/Android/libs (where aars can be placed) is watched separately and should force a rebuild

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rosen-vladimirov I checked it out and when you have an example.aar file in the App_Resources and the tns run android or tns run android --syncAllFiles is running and the example.aar is changed, the changes are being ignored.
When the command is stopped and ran again, the example.aar forces a rebuild.

@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch from ae61e5a to e5cf9a3 Compare March 7, 2018 13:08
@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch 3 times, most recently from 6d0786c to 7e192f9 Compare March 7, 2018 15:20
Copy link
Contributor

@rosen-vladimirov rosen-vladimirov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After green build

@Plamen5kov Plamen5kov force-pushed the plamen5kov/rebuild-aar branch from 7e192f9 to 1cda138 Compare March 7, 2018 16:08
@miroslavaivanova
Copy link
Contributor

run ci

@Plamen5kov Plamen5kov merged commit 283b31c into master Mar 8, 2018
@Plamen5kov Plamen5kov deleted the plamen5kov/rebuild-aar branch March 8, 2018 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants