Skip to content
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

[TIMOB-24829] Improve android build performance #205

Merged
merged 6 commits into from Sep 25, 2017

Conversation

janvennemann
Copy link
Contributor

@janvennemann janvennemann commented Sep 1, 2017

JIRA: https://jira.appcelerator.org/browse/TIMOB-24829

This is the reworked version of #189, using the appc-tasks module and complete with unit tests backing the refactored code.

Change overview:

  • Moved the Hyperloop wrapper sources from being processed by the normal Android build process. This was the main reason for slow performance because it required the minifyJs flag being forced to true to work properly, slowing down the JS processing. It will also no longer regenerate all wrappers on every build.
  • Hyperloop wrappers will no longer be copied to the Resources directory which caused some confusion in classic apps. The hook will now generate all files under the build directory.
  • All important build steps are now being able to work incrementally. This will allow the Hyperloop processing to only trigger for changed or deleted files instead all of them.

To properly work in master, this has to be tested together with #206

}
// Special case for android-support-v4.jar and android-support-v13.jar
if (basenames.hasOwnProperty('android-support-v4.jar') && basenames.hasOwnProperty('android-support-v13.jar')) {
var specialCase = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

uniqueJars = uniqueJars.filter(function (jarFile) {
  return path.basename(jarFile) !== 'android-support-v4.jar';
});

canDoIncrementalRun() {
let buildManifest = this._builder.buildManifest;

if (buildManifest.deployType !== this._builder.deployType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really care about deploy type? Only in that it changes the default setting for magnification, right? Does that not get reflected int he check below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only the deploy type is stored in the build manifest so i use that to indirectly check wether the minify JS settings changed. The other check is for the --skip-minify-js flag which can be changed regardless of deploy type.

minifyJsAndWrite(sourcePath, destinationPath) {
return new Promise(
(resolve, reject) => {
babel.transformFile(sourcePath, {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should really have a built in "magnification" task/library whatever to call here. We have found that sometimes plugins in babili result in busted JS that some engines can't handle, and have had to tweak the minification before. I would hope we could just ask the CLI build process to minify these files as some build step and not have to have babili in hyperloop again and call it with possibly differing options/plugin versions/etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, totally agree on that! I didn't feel comfortable using babili directly but it was the only way to get minification as i explicitly avoid the default build pipeline as that was the performance bottle neck. A way to call the minification from node-titanium-sdk without the overhead of running the JS analyzation would be nice so we could drop the direct dependency to babili in Hyperloop again.

/**
* Constructs a new task for metabase generation
*
* @param {Obejct} taskInfo
Copy link
Contributor

Choose a reason for hiding this comment

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

Object

* @return {Promise}
*/
runTaskAction() {
let inputFiles = Array.from(this.inputFiles);
Copy link
Contributor

Choose a reason for hiding this comment

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

const?

return this.doFullTaskRun();
}

let referencedClasses = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

refactor out a method to re-use same code as in full run to gather the full set of referenced classes?

});

let expandedClassList = metabase.generate.expandDependencies(this.metabase, referencedClasses);
let classesToGenerate = expandedClassList.filter(className => !this._generatedClasses.has(className));
Copy link
Contributor

Choose a reason for hiding this comment

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

This plus the following code is a good use case for https://lodash.com/docs/#partition if we're using lodash... Looks like you're basically looping through this._generatedClasses. If it's in expandedClassesList, we should generate it, if not, we should remove it (then do a loop on the set to remove and actually delete them, preferably async).


changedFiles.forEach((state, pathAndFilename) => {
if (state === 'created' || state === 'changed') {
let hyperloopUsed = this.scanFileForHyperloopRequires(pathAndFilename);
Copy link
Contributor

Choose a reason for hiding this comment

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

const

this._references.forEach((fileInfo, pathAndFilename) => {
referencesObj[pathAndFilename] = fileInfo;
});
let referencesJson = JSON.stringify(referencesObj);
Copy link
Contributor

Choose a reason for hiding this comment

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

const

*/
scanFileForHyperloopRequires(pathAndFilename) {
var result = this.extractAndReplaceHyperloopRequires(pathAndFilename);
if (result !== null && result.usedClasses.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we care if result is null or undefined? Why not justif (result && result.usedClasses.length > 0) {?

return null;
}

var contents = fs.readFileSync(file, 'UTF-8'),
Copy link
Contributor

Choose a reason for hiding this comment

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

can these all be const?

requireRegex = /require\s*\(\s*[\\"']+([\w_/-\\.\\*]+)[\\"']+\s*\)/ig;
this._logger.trace('Searching for hyperloop requires in: ' + file);
(contents.match(requireRegex) || []).forEach(m => {
var re = /require\s*\(\s*[\\"']+([\w_/-\\.\\*]+)[\\"']+\s*\)/i.exec(m),
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this the same regexp from above? I think this should be removed, and className = re[1], should be const classname = m[1],

(contents.match(requireRegex) || []).forEach(m => {
var re = /require\s*\(\s*[\\"']+([\w_/-\\.\\*]+)[\\"']+\s*\)/i.exec(m),
className = re[1],
lastIndex,
Copy link
Contributor

Choose a reason for hiding this comment

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

Move to using let/const in the proper scopes below, please...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing!

* @param {String} replaceStr String used to replace all occurrences of needle
* @return {String} New string which has all occurrences of needle replaced
*/
replaceAll(haystack, needle, replaceStr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I wrote this horrible code :( Maybe improve it a little bit...

replaceAll(haystack, needle, replaceStr) {
  const length = needle.length;
  let newBuffer = haystack,
    index = -1;
  while ((index = newBuffer.indexOf(needle)) >= 0) {
    const before = newBuffer.substring(0, index);
    const after = newBuffer.substring(index + length);
    newBuffer = before + replaceStr + after;
  }
   return newBuffer;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha, i didn't want to touch any of the existing code and rather focused on moving into a task for now. I'll implement this improvement

},
"nyc": {
"reporter": [
"lcov",
Copy link
Contributor

Choose a reason for hiding this comment

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

So if we produce a 'cobertura' coverage report, Jenkins can consume that...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add that!

@sgtcoolguy
Copy link
Contributor

Yay for tests! Do these actually run as part of the build? I'm guessing not. Can we please hook them up in the Jenkinsfile? Ideally we'd model this like many of the other npm packages, and have a Gruntfile that can do linting/testing of the JS code. If you'd like I can get us started in that direction and then we can hook the nyc/test stuff into it?

Proper usage of const and let, refactoring of the scan references task to optimize some old code pieces and async removal of unused wrappers in the generate surces task.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants