Skip to content

feat: support incremental builds #927

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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
123 changes: 42 additions & 81 deletions Alloy/commands/compile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ var ejs = require('ejs'),
BuildLog = require('./BuildLog'),
Orphanage = require('./Orphanage');

const MvcCompileTask = require('./tasks/mvc-compile-task');

var alloyRoot = path.join(__dirname, '..', '..'),
viewRegex = new RegExp('\\.' + CONST.FILE_EXT.VIEW + '$'),
controllerRegex = new RegExp('\\.' + CONST.FILE_EXT.CONTROLLER + '$'),
Expand Down Expand Up @@ -418,93 +420,52 @@ module.exports = function(args, program) {
CU.models.push(m);
});

// Create a regex for determining which platform-specific
// folders should be used in the compile process
var filteredPlatforms = _.reject(CONST.PLATFORM_FOLDERS_ALLOY, function(p) {
return p === buildPlatform;
});
filteredPlatforms = _.map(filteredPlatforms, function(p) { return p + '[\\\\\\/]'; });
var filterRegex = new RegExp('^(?:(?!' + filteredPlatforms.join('|') + '))');

// don't process XML/controller files inside .svn folders (ALOY-839)
var excludeRegex = new RegExp('(?:^|[\\/\\\\])(?:' + CONST.EXCLUDED_FILES.join('|') + ')(?:$|[\\/\\\\])');

// Process all views/controllers and generate their runtime
// commonjs modules and source maps.
var tracker = {};
_.each(widgetDirs, function(collection) {
// generate runtime controllers from views
var theViewDir = path.join(collection.dir, CONST.DIR.VIEW);
if (fs.existsSync(theViewDir)) {
_.each(walkSync(theViewDir), function(view) {
view = path.normalize(view);
if (viewRegex.test(view) && filterRegex.test(view) && !excludeRegex.test(view)) {
// make sure this controller is only generated once
var theFile = view.substring(0, view.lastIndexOf('.'));
var theKey = theFile.replace(new RegExp('^' + buildPlatform + '[\\/\\\\]'), '');
var fp = path.join(collection.dir, theKey);
if (tracker[fp]) { return; }

// generate runtime controller
logger.info('[' + view + '] ' + (collection.manifest ? collection.manifest.id +
' ' : '') + 'view processing...');
parseAlloyComponent(view, collection.dir, collection.manifest, null, restrictionPath);
tracker[fp] = true;
}
});
}

// generate runtime controllers from any controller code that has no
// corresponding view markup
var theControllerDir = path.join(collection.dir, CONST.DIR.CONTROLLER);
if (fs.existsSync(theControllerDir)) {
_.each(walkSync(theControllerDir), function(controller) {
controller = path.normalize(controller);
if (controllerRegex.test(controller) && filterRegex.test(controller) && !excludeRegex.test(controller)) {
// make sure this controller is only generated once
var theFile = controller.substring(0, controller.lastIndexOf('.'));
var theKey = theFile.replace(new RegExp('^' + buildPlatform + '[\\/\\\\]'), '');
var fp = path.join(collection.dir, theKey);
if (tracker[fp]) { return; }

// generate runtime controller
logger.info('[' + controller + '] ' + (collection.manifest ?
collection.manifest.id + ' ' : '') + 'controller processing...');
parseAlloyComponent(controller, collection.dir, collection.manifest, true, restrictionPath);
tracker[fp] = true;
}
return Promise.resolve()
.then(() => {
const mvcCompileTask = new MvcCompileTask({
incrementalDirectory: path.join(compileConfig.dir.project, 'build', 'alloy', 'incremental', 'mvc'),
logger,
compileConfig,
restrictionPath,
parseAlloyComponent,
sourceCollections: widgetDirs,
targetPlatform: buildPlatform
});
}
});
logger.info('');

generateAppJs(paths, compileConfig, restrictionPath, compilerMakeFile);

// ALOY-905: workaround TiSDK < 3.2.0 iOS device build bug where it can't reference app.js
// in platform-specific folders, so we just copy the platform-specific one to
// the Resources folder.
if (buildPlatform === 'ios' && tiapp.version.lt('3.2.0')) {
U.copyFileSync(path.join(paths.resources, titaniumFolder, 'app.js'), path.join(paths.resources, 'app.js'));
}
return mvcCompileTask.run();
})
.then(() => {
generateAppJs(paths, compileConfig, restrictionPath, compilerMakeFile);

// ALOY-905: workaround TiSDK < 3.2.0 iOS device build bug where it can't reference app.js
// in platform-specific folders, so we just copy the platform-specific one to
// the Resources folder.
if (buildPlatform === 'ios' && tiapp.version.lt('3.2.0')) {
U.copyFileSync(path.join(paths.resources, titaniumFolder, 'app.js'), path.join(paths.resources, 'app.js'));
}

// optimize code
logger.info('----- OPTIMIZING -----');
// optimize code
logger.info('----- OPTIMIZING -----');

if (restrictionSkipOptimize) {
logger.info('Skipping optimize due to file restriction.');
} else {
optimizeCompiledCode(alloyConfig, paths);
}
if (restrictionSkipOptimize) {
logger.info('Skipping optimize due to file restriction.');
} else {
optimizeCompiledCode(alloyConfig, paths);
}

// trigger our custom compiler makefile
if (compilerMakeFile.isActive) {
compilerMakeFile.trigger('post:compile', _.clone(compileConfig));
}
// trigger our custom compiler makefile
if (compilerMakeFile.isActive) {
compilerMakeFile.trigger('post:compile', _.clone(compileConfig));
}

// write out the log for this build
buildLog.write();
// write out the log for this build
buildLog.write();

BENCHMARK('TOTAL', true);
BENCHMARK('TOTAL', true);
})
.catch(e => {
logger.error(e);
U.die(e.message);
});
};


Expand Down
142 changes: 142 additions & 0 deletions Alloy/commands/compile/tasks/mvc-compile-task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const _ = require('lodash');
const { IncrementalFileTask } = require('appc-tasks');
const fs = require('fs');
const path = require('path');
const walkSync = require('walk-sync');

const CONST = require('../../../common/constants');

const viewRegex = new RegExp('\\.' + CONST.FILE_EXT.VIEW + '$');
const controllerRegex = new RegExp('\\.' + CONST.FILE_EXT.CONTROLLER + '$');
const excludeRegex = new RegExp('(?:^|[\\/\\\\])(?:' + CONST.EXCLUDED_FILES.join('|') + ')(?:$|[\\/\\\\])');

/**
* Task to compile controllers/views and their styles
*/
class MvcCompileTask extends IncrementalFileTask {
/**
* Constructs a new MVC compile task.
*
* @param {Object} options Configuration object for this task
*/
constructor(options) {
options.name = options.name || 'mvc-compile';
super(options);

this.targetPlatform = options.targetPlatform;
this.restrictionPath = options.restrictionPath;
this.parseAlloyComponent = options.parseAlloyComponent;

// Create a regex for determining which platform-specific
// folders should be used in the compile process
let filteredPlatforms = _.reject(CONST.PLATFORM_FOLDERS_ALLOY, p => {
return p === this.targetPlatform;
});
filteredPlatforms = _.map(filteredPlatforms, function(p) { return p + '[\\\\\\/]'; });
this.filterRegex = new RegExp('^(?:(?!' + filteredPlatforms.join('|') + '))');

this.processed = {};
this.rawSourceCollections = [];

if (options.sourceCollections) {
this.sourceCollections = options.sourceCollections;
}
}

set sourceCollections(sourceCollections) {
this.inputFiles = new Set();
this.rawSourceCollections = sourceCollections;
const candidateSourcePaths = [CONST.DIR.VIEW, CONST.DIR.CONTROLLER];
_.each(sourceCollections, collection => {
for (const candidateSourcePath of candidateSourcePaths) {
var sourcePath = path.join(collection.dir, candidateSourcePath);
if (!fs.existsSync(sourcePath)) {
continue;
}

_.each(walkSync(sourcePath), componentFilename => {
componentFilename = path.normalize(componentFilename);
const componentRegex = candidateSourcePath === CONST.DIR.VIEW ? viewRegex : controllerRegex;
if (componentRegex.test(componentFilename) && this.filterRegex.test(componentFilename) && !excludeRegex.test(componentFilename)) {
this.addInputFile(path.join(sourcePath, componentFilename));
}
});
}
});
}

/**
* Does a full task run, processing every input file.
*
* @return {Promise}
*/
doFullTaskRun() {
// @todo make this parallel?
for (const inputFile of this.inputFiles) {
this.compileAlloyComponent(inputFile);
}

return Promise.resolve();
}

/**
* Does an incremental task run, processing only changed files.
*
* @param {Map} changedFiles Map of file paths and their current file state (created, changed, deleted)
* @return {Promise}
*/
doIncrementalTaskRun(changedFiles) {
changedFiles.forEach((fileState, filePath) => {
if (fileState === 'deleted') {
// @todo delete associated controller, view and styles
} else {
this.compileAlloyComponent(filePath);
}
});

return Promise.resolve();
}

/**
* Finds the source collection a file part of.
*
* @param {String} filePath Full path to the file for which to look up the source collection.
*/
findSourceCollection(filePath) {
for (const collection of this.rawSourceCollections) {
if (filePath.startsWith(collection.dir)) {
return collection;
}
}

throw new Error(`Source collection not found for ${filePath}.`);
}

/**
* Compiles an Alloy component (controller/view).
*
* Note that this only needs to be done once for either the view OR the controller.
*
* @param {*} inputFile
*/
compileAlloyComponent(inputFile) {
const collection = this.findSourceCollection(inputFile);
const parseAsController = controllerRegex.test(inputFile);
let relativeComponentPath = inputFile.replace(collection.dir, '');
relativeComponentPath = relativeComponentPath.replace(new RegExp(`^${path.sep}?(views|controllers)${path.sep}?`), '');

var relativeComponentPathWithoutExtension = relativeComponentPath.substring(0, relativeComponentPath.lastIndexOf('.'));
var componentIdentifier = relativeComponentPathWithoutExtension.replace(new RegExp('^' + this.targetPlatform + '[\\/\\\\]'), '');
var fullComponentIdentifier = path.join(collection.dir, componentIdentifier);
if (this.processed[fullComponentIdentifier]) {
return;
}

this.logger.info('[' + relativeComponentPath + '] ' + (collection.manifest ? collection.manifest.id +
' ' : '') + `${parseAsController ? 'controller' : 'view'} processing...`);
this.parseAlloyComponent(relativeComponentPath, collection.dir, collection.manifest, parseAsController, this.restrictionPath);
this.processed[fullComponentIdentifier] = true;
}
}

module.exports = MvcCompileTask;
41 changes: 41 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@babel/parser": "^7.4.5",
"@babel/traverse": "^7.4.5",
"@babel/types": "^7.4.4",
"appc-tasks": "^1.0.1",
"async": "^2.4.0",
"chmodr": "^1.0.2",
"colors": "^1.1.2",
Expand Down