@qooxdoo/framework
Version:
The JS Framework for Coders
283 lines (250 loc) • 8.75 kB
JavaScript
/* ************************************************************************
*
* qooxdoo-compiler - node.js based replacement for the Qooxdoo python
* toolchain
*
* https://github.com/qooxdoo/qooxdoo
*
* Copyright:
* 2011-2017 Zenesis Limited, http://www.zenesis.com
*
* License:
* MIT: https://opensource.org/licenses/MIT
*
* This software is provided under the same licensing terms as Qooxdoo,
* please see the LICENSE file in the Qooxdoo project's top-level directory
* for details.
*
* Authors:
* * John Spackman (john.spackman@zenesis.com, @johnspackman)
*
* *********************************************************************** */
var log = qx.tool.utils.LogManager.createLog("analyser");
/**
* Application maker; supports multiple applications to compile against a single
* target
*/
qx.Class.define("qx.tool.compiler.makers.AppMaker", {
extend: qx.tool.compiler.makers.AbstractAppMaker,
/**
* Constructor
* @param className {String|String[]} classname(s) to generate
* @param theme {String} the theme classname
*/
construct(className, theme) {
super();
this.__applications = [];
if (className) {
var app = new qx.tool.compiler.app.Application(className);
if (theme) {
app.setTheme(theme);
}
this.addApplication(app);
}
},
members: {
__applications: null,
/**
* Adds an Application to be made
* @param app
*/
addApplication(app) {
this.__applications.push(app);
},
/**
* Returns the array of applications
* @returns {qx.tool.compiler.app.Application[]}
*/
getApplications() {
return this.__applications;
},
/*
* @Override
*/
async make() {
var analyser = this.getAnalyser();
let target = this.getTarget();
await this.fireEventAsync("making");
this.setSuccess(null);
this.setHasWarnings(null);
let success = true;
let hasWarnings = false;
// merge all environment settings for the analyser
const compileEnv = qx.tool.utils.Values.merge(
{},
qx.tool.compiler.ClassFile.ENVIRONMENT_CONSTANTS,
{
"qx.compiler": true,
"qx.compiler.version": qx.tool.config.Utils.getCompilerVersion()
},
this.getEnvironment(),
target.getDefaultEnvironment(),
target.getEnvironment()
);
let preserve = target.getPreserveEnvironment();
if (preserve) {
let tmp = {};
preserve.forEach(key => (tmp[key] = true));
preserve = tmp;
} else {
preserve = {};
}
let appEnvironments = {};
this.getApplications().forEach(app => {
appEnvironments[app.toHashCode()] = qx.tool.utils.Values.merge(
{},
compileEnv,
app.getCalculatedEnvironment()
);
});
// Analyze the list of environment variables, detect which are shared between all apps
let allAppEnv = {};
this.getApplications().forEach(app => {
let env = appEnvironments[app.toHashCode()];
Object.keys(env).forEach(key => {
if (!allAppEnv[key]) {
allAppEnv[key] = {
value: env[key],
same: true
};
} else if (allAppEnv[key].value !== env[key]) {
allAppEnv[key].same = false;
}
});
});
// If an env setting is the same for all apps, move it to the target for code elimination; similarly,
// if it varies between apps, then remove it from the target and make each app specify it individually
this.getApplications().forEach(app => {
let env = appEnvironments[app.toHashCode()];
Object.keys(allAppEnv).forEach(key => {
if (preserve[key]) {
env[key] = compileEnv[key];
} else if (allAppEnv[key].same) {
delete env[key];
} else if (env[key] === undefined) {
env[key] = compileEnv[key];
}
});
});
// Cleanup to remove env that have been moved to the app
Object.keys(allAppEnv).forEach(key => {
if (!preserve[key] && allAppEnv[key].same) {
compileEnv[key] = allAppEnv[key].value;
} else {
delete compileEnv[key];
}
});
await analyser.open();
analyser.setEnvironment(compileEnv);
if (!this.isNoErase() && analyser.isContextChanged()) {
log.log("enviroment changed - delete output dir");
await this.eraseOutputDir();
await qx.tool.utils.Utils.makeParentDir(this.getOutputDir());
await analyser.resetDatabase();
}
await qx.tool.utils.Utils.promisifyThis(analyser.initialScan, analyser);
await analyser.updateEnvironmentData();
target.setAnalyser(analyser);
this.__applications.forEach(app => app.setAnalyser(analyser));
await target.open();
for (let library of analyser.getLibraries()) {
let fontsData = library.getFontsData();
for (let fontName in fontsData) {
let fontData = fontsData[fontName];
let font = analyser.getFont(fontName);
if (!font) {
font = analyser.getFont(fontName, true);
await font.updateFromManifest(fontData, library);
}
}
}
this.__applications.forEach(function (app) {
app.getRequiredClasses().forEach(function (className) {
analyser.addClass(className);
});
if (app.getTheme()) {
analyser.addClass(app.getTheme());
}
});
await analyser.analyseClasses();
await analyser.saveDatabase();
await this.fireEventAsync("writingApplications");
// Detect which applications need to be recompiled by looking for classes recently compiled
// which is on the application's dependency list. The first time `.make()` is called there
// will be no dependencies so we just compile anyway, but `qx compile --watch` will call it
// multiple times
let compiledClasses = this.getRecentlyCompiledClasses(true);
let db = analyser.getDatabase();
var appsThisTime = await this.__applications.filter(async app => {
let loadDeps = app.getDependencies();
if (!loadDeps || !loadDeps.length) {
return true;
}
let res = loadDeps.some(name => Boolean(compiledClasses[name]));
let localModules = app.getLocalModules();
for (let requireName in localModules) {
let stat = await qx.tool.utils.files.Utils.safeStat(
localModules[requireName]
);
res ||=
stat.mtime.getTime() >
(db?.modulesInfo?.localModules[requireName] || 0);
}
return res;
});
let allAppInfos = [];
for (let i = 0; i < appsThisTime.length; i++) {
let application = appsThisTime[i];
if (application.getType() != "browser" && !compileEnv["qx.headless"]) {
qx.tool.compiler.Console.print(
"qx.tool.compiler.maker.appNotHeadless",
application.getName()
);
}
var appEnv = qx.tool.utils.Values.merge(
{},
compileEnv,
appEnvironments[application.toHashCode()]
);
application.calcDependencies();
if (application.getFatalCompileErrors()) {
qx.tool.compiler.Console.print(
"qx.tool.compiler.maker.appFatalError",
application.getName()
);
success = false;
continue;
}
if (!hasWarnings) {
application.getDependencies().forEach(classname => {
if (!db.classInfo[classname] || !db.classInfo[classname].markers) {
return;
}
db.classInfo[classname].markers.forEach(marker => {
let type = qx.tool.compiler.Console.getInstance().getMessageType(
marker.msgId
);
if (type == "warning") {
hasWarnings = true;
}
});
});
}
let appInfo = {
application,
analyser,
maker: this
};
allAppInfos.push(appInfo);
await this.fireDataEventAsync("writingApplication", appInfo);
await target.generateApplication(application, appEnv);
await this.fireDataEventAsync("writtenApplication", appInfo);
}
await this.fireDataEventAsync("writtenApplications", allAppInfos);
await analyser.saveDatabase();
await this.fireEventAsync("made");
this.setSuccess(success);
this.setHasWarnings(hasWarnings);
}
}
});