mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-06-17 19:29:44 +00:00
386 lines
14 KiB
JavaScript
386 lines
14 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.fixParamsWithDefines = exports.anyTargetChanged = exports.staleFiles = exports.populateExtraFiles = exports.populateFiles = exports.getPrefix = exports.gatherFiles = exports.populateEntry = exports.getWorkFileAsString = exports.putWorkFile = exports.endtime = exports.starttime = exports.builder = exports.Builder = exports.errorResult = exports.store = exports.FileWorkingStore = exports.PWORKER = void 0;
|
|
const util_1 = require("../common/util");
|
|
const platforms_1 = require("./platforms");
|
|
const workertools_1 = require("./workertools");
|
|
/// working file store and build steps
|
|
const PSRC = "../../src/";
|
|
exports.PWORKER = PSRC + "worker/";
|
|
;
|
|
///
|
|
class FileWorkingStore {
|
|
constructor() {
|
|
this.workfs = {};
|
|
this.workerseq = 0;
|
|
this.reset();
|
|
}
|
|
reset() {
|
|
this.workfs = {};
|
|
this.newVersion();
|
|
}
|
|
currentVersion() {
|
|
return this.workerseq;
|
|
}
|
|
newVersion() {
|
|
let ts = new Date().getTime();
|
|
if (ts <= this.workerseq)
|
|
ts = ++this.workerseq;
|
|
return ts;
|
|
}
|
|
putFile(path, data) {
|
|
var encoding = (typeof data === 'string') ? 'utf8' : 'binary';
|
|
var entry = this.workfs[path];
|
|
if (!entry || !compareData(entry.data, data) || entry.encoding != encoding) {
|
|
this.workfs[path] = entry = { path: path, data: data, encoding: encoding, ts: this.newVersion() };
|
|
console.log('+++', entry.path, entry.encoding, entry.data.length, entry.ts);
|
|
}
|
|
return entry;
|
|
}
|
|
hasFile(path) {
|
|
return this.workfs[path] != null;
|
|
}
|
|
getFileData(path) {
|
|
return this.workfs[path] && this.workfs[path].data;
|
|
}
|
|
getFileAsString(path) {
|
|
let data = this.getFileData(path);
|
|
if (data != null && typeof data !== 'string')
|
|
throw new Error(`${path}: expected string`);
|
|
return data; // TODO
|
|
}
|
|
getFileEntry(path) {
|
|
return this.workfs[path];
|
|
}
|
|
setItem(key, value) {
|
|
this.items[key] = value;
|
|
}
|
|
}
|
|
exports.FileWorkingStore = FileWorkingStore;
|
|
exports.store = new FileWorkingStore();
|
|
///
|
|
function errorResult(msg) {
|
|
return { errors: [{ line: 0, msg: msg }] };
|
|
}
|
|
exports.errorResult = errorResult;
|
|
class Builder {
|
|
constructor() {
|
|
this.steps = [];
|
|
this.startseq = 0;
|
|
}
|
|
// returns true if file changed during this build step
|
|
wasChanged(entry) {
|
|
return entry.ts > this.startseq;
|
|
}
|
|
async executeBuildSteps() {
|
|
this.startseq = exports.store.currentVersion();
|
|
var linkstep = null;
|
|
while (this.steps.length) {
|
|
var step = this.steps.shift(); // get top of array
|
|
var platform = step.platform;
|
|
var [tool, remoteTool] = step.tool.split(':', 2);
|
|
var toolfn = workertools_1.TOOLS[tool];
|
|
if (!toolfn) {
|
|
throw Error(`no tool named "${tool}"`);
|
|
}
|
|
if (remoteTool) {
|
|
step.tool = remoteTool;
|
|
}
|
|
step.params = platforms_1.PLATFORM_PARAMS[(0, util_1.getBasePlatform)(platform)];
|
|
try {
|
|
step.result = await toolfn(step);
|
|
}
|
|
catch (e) {
|
|
console.log("EXCEPTION", e, e.stack);
|
|
return errorResult(e + ""); // TODO: catch errors already generated?
|
|
}
|
|
if (step.result) {
|
|
step.result.params = step.params; // TODO: type check
|
|
if (step.debuginfo) {
|
|
let r = step.result; // TODO
|
|
if (!r.debuginfo)
|
|
r.debuginfo = {};
|
|
Object.assign(r.debuginfo, step.debuginfo);
|
|
}
|
|
// errors? return them
|
|
if ('errors' in step.result && step.result.errors.length) {
|
|
applyDefaultErrorPath(step.result.errors, step.path);
|
|
return step.result;
|
|
}
|
|
// if we got some output, return it immediately
|
|
if ('output' in step.result && step.result.output) {
|
|
return step.result;
|
|
}
|
|
// combine files with a link tool?
|
|
if ('linktool' in step.result) {
|
|
// add to existing link step
|
|
if (linkstep) {
|
|
linkstep.files = linkstep.files.concat(step.result.files);
|
|
linkstep.args = linkstep.args.concat(step.result.args);
|
|
}
|
|
else {
|
|
linkstep = {
|
|
tool: step.result.linktool,
|
|
platform: platform,
|
|
files: step.result.files,
|
|
args: step.result.args
|
|
};
|
|
}
|
|
linkstep.debuginfo = step.debuginfo; // TODO: multiple debuginfos
|
|
}
|
|
// process with another tool?
|
|
if ('nexttool' in step.result) {
|
|
var asmstep = Object.assign({ tool: step.result.nexttool, platform: platform }, step.result);
|
|
this.steps.push(asmstep);
|
|
}
|
|
// process final step?
|
|
if (this.steps.length == 0 && linkstep) {
|
|
this.steps.push(linkstep);
|
|
linkstep = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
async handleMessage(data) {
|
|
this.steps = [];
|
|
// file updates
|
|
if (data.updates) {
|
|
data.updates.forEach((u) => exports.store.putFile(u.path, u.data));
|
|
}
|
|
// object update
|
|
if (data.setitems) {
|
|
data.setitems.forEach((i) => exports.store.setItem(i.key, i.value));
|
|
}
|
|
// build steps
|
|
if (data.buildsteps) {
|
|
this.steps.push.apply(this.steps, data.buildsteps);
|
|
}
|
|
// single-file
|
|
if (data.code) {
|
|
this.steps.push(data); // TODO: remove cast
|
|
}
|
|
// execute build steps
|
|
if (this.steps.length) {
|
|
var result = await this.executeBuildSteps();
|
|
return result ? result : { unchanged: true };
|
|
}
|
|
// TODO: cache results
|
|
// message not recognized
|
|
console.log("Unknown message", data);
|
|
}
|
|
}
|
|
exports.Builder = Builder;
|
|
function applyDefaultErrorPath(errors, path) {
|
|
if (!path)
|
|
return;
|
|
for (var i = 0; i < errors.length; i++) {
|
|
var err = errors[i];
|
|
if (!err.path && err.line)
|
|
err.path = path;
|
|
}
|
|
}
|
|
function compareData(a, b) {
|
|
if (a.length != b.length)
|
|
return false;
|
|
if (typeof a === 'string' && typeof b === 'string') {
|
|
return a == b;
|
|
}
|
|
else {
|
|
for (var i = 0; i < a.length; i++) {
|
|
//if (a[i] != b[i]) console.log('differ at byte',i,a[i],b[i]);
|
|
if (a[i] != b[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
exports.builder = new Builder();
|
|
var _t1;
|
|
function starttime() { _t1 = new Date(); }
|
|
exports.starttime = starttime;
|
|
function endtime(msg) { var _t2 = new Date(); console.log(msg, _t2.getTime() - _t1.getTime(), "ms"); }
|
|
exports.endtime = endtime;
|
|
///
|
|
function putWorkFile(path, data) {
|
|
return exports.store.putFile(path, data);
|
|
}
|
|
exports.putWorkFile = putWorkFile;
|
|
function getWorkFileAsString(path) {
|
|
return exports.store.getFileAsString(path);
|
|
}
|
|
exports.getWorkFileAsString = getWorkFileAsString;
|
|
function populateEntry(fs, path, entry, options) {
|
|
var data = entry.data;
|
|
if (options && options.processFn) {
|
|
data = options.processFn(path, data);
|
|
}
|
|
// create subfolders
|
|
var toks = path.split('/');
|
|
if (toks.length > 1) {
|
|
for (var i = 0; i < toks.length - 1; i++)
|
|
try {
|
|
fs.mkdir(toks[i]);
|
|
}
|
|
catch (e) { }
|
|
}
|
|
// write file
|
|
fs.writeFile(path, data, { encoding: entry.encoding });
|
|
var time = new Date(entry.ts);
|
|
fs.utime(path, time, time);
|
|
console.log("<<<", path, entry.data.length);
|
|
}
|
|
exports.populateEntry = populateEntry;
|
|
// can call multiple times (from populateFiles)
|
|
function gatherFiles(step, options) {
|
|
var maxts = 0;
|
|
if (step.files) {
|
|
for (var i = 0; i < step.files.length; i++) {
|
|
var path = step.files[i];
|
|
var entry = exports.store.workfs[path];
|
|
if (!entry) {
|
|
throw new Error("No entry for path '" + path + "'");
|
|
}
|
|
else {
|
|
maxts = Math.max(maxts, entry.ts);
|
|
}
|
|
}
|
|
}
|
|
else if (step.code) {
|
|
var path = step.path ? step.path : options.mainFilePath; // TODO: what if options null
|
|
if (!path)
|
|
throw Error("need path or mainFilePath");
|
|
var code = step.code;
|
|
var entry = putWorkFile(path, code);
|
|
step.path = path;
|
|
step.files = [path];
|
|
maxts = entry.ts;
|
|
}
|
|
else if (step.path) {
|
|
var path = step.path;
|
|
var entry = exports.store.workfs[path];
|
|
maxts = entry.ts;
|
|
step.files = [path];
|
|
}
|
|
if (step.path && !step.prefix) {
|
|
step.prefix = getPrefix(step.path);
|
|
}
|
|
step.maxts = maxts;
|
|
return maxts;
|
|
}
|
|
exports.gatherFiles = gatherFiles;
|
|
function getPrefix(s) {
|
|
var pos = s.lastIndexOf('.');
|
|
return (pos > 0) ? s.substring(0, pos) : s;
|
|
}
|
|
exports.getPrefix = getPrefix;
|
|
function populateFiles(step, fs, options) {
|
|
gatherFiles(step, options);
|
|
if (!step.files)
|
|
throw Error("call gatherFiles() first");
|
|
for (var i = 0; i < step.files.length; i++) {
|
|
var path = step.files[i];
|
|
populateEntry(fs, path, exports.store.workfs[path], options);
|
|
}
|
|
}
|
|
exports.populateFiles = populateFiles;
|
|
function populateExtraFiles(step, fs, extrafiles) {
|
|
if (extrafiles) {
|
|
for (var i = 0; i < extrafiles.length; i++) {
|
|
var xfn = extrafiles[i];
|
|
// is this file cached?
|
|
if (exports.store.workfs[xfn]) {
|
|
fs.writeFile(xfn, exports.store.workfs[xfn].data, { encoding: 'binary' });
|
|
continue;
|
|
}
|
|
// fetch from network
|
|
var xpath = "lib/" + (0, util_1.getBasePlatform)(step.platform) + "/" + xfn;
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.open("GET", exports.PWORKER + xpath, false); // synchronous request
|
|
xhr.send(null);
|
|
if (xhr.response && xhr.status == 200) {
|
|
var data = new Uint8Array(xhr.response);
|
|
fs.writeFile(xfn, data, { encoding: 'binary' });
|
|
putWorkFile(xfn, data);
|
|
console.log(":::", xfn, data.length);
|
|
}
|
|
else {
|
|
throw Error("Could not load extra file " + xpath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exports.populateExtraFiles = populateExtraFiles;
|
|
function staleFiles(step, targets) {
|
|
if (!step.maxts)
|
|
throw Error("call populateFiles() first");
|
|
// see if any target files are more recent than inputs
|
|
for (var i = 0; i < targets.length; i++) {
|
|
var entry = exports.store.workfs[targets[i]];
|
|
if (!entry || step.maxts > entry.ts)
|
|
return true;
|
|
}
|
|
console.log("unchanged", step.maxts, targets);
|
|
return false;
|
|
}
|
|
exports.staleFiles = staleFiles;
|
|
function anyTargetChanged(step, targets) {
|
|
if (!step.maxts)
|
|
throw Error("call populateFiles() first");
|
|
// see if any target files are more recent than inputs
|
|
for (var i = 0; i < targets.length; i++) {
|
|
var entry = exports.store.workfs[targets[i]];
|
|
if (!entry || entry.ts > step.maxts)
|
|
return true;
|
|
}
|
|
console.log("unchanged", step.maxts, targets);
|
|
return false;
|
|
}
|
|
exports.anyTargetChanged = anyTargetChanged;
|
|
function fixParamsWithDefines(path, params) {
|
|
var libargs = params.libargs;
|
|
if (path && libargs) {
|
|
var code = getWorkFileAsString(path);
|
|
if (code) {
|
|
var oldcfgfile = params.cfgfile;
|
|
var ident2index = {};
|
|
// find all lib args "IDENT=VALUE"
|
|
for (var i = 0; i < libargs.length; i++) {
|
|
var toks = libargs[i].split('=');
|
|
if (toks.length == 2) {
|
|
ident2index[toks[0]] = i;
|
|
}
|
|
}
|
|
// find #defines and replace them
|
|
var re = /^[;]?#define\s+(\w+)\s+(\S+)/gmi; // TODO: empty string?
|
|
var m;
|
|
while (m = re.exec(code)) {
|
|
var ident = m[1];
|
|
var value = m[2];
|
|
var index = ident2index[ident];
|
|
if (index >= 0) {
|
|
libargs[index] = ident + "=" + value;
|
|
console.log('Using libargs', index, libargs[index]);
|
|
// TODO: MMC3 mapper switch
|
|
if (ident == 'NES_MAPPER' && value == '4') {
|
|
params.cfgfile = 'nesbanked.cfg';
|
|
console.log("using config file", params.cfgfile);
|
|
}
|
|
}
|
|
else if (ident == 'CFGFILE' && value) {
|
|
params.cfgfile = value;
|
|
}
|
|
else if (ident == 'LIBARGS' && value) {
|
|
params.libargs = value.split(',').filter((s) => { return s != ''; });
|
|
console.log('Using libargs', params.libargs);
|
|
}
|
|
else if (ident == 'CC65_FLAGS' && value) {
|
|
params.extra_compiler_args = value.split(',').filter((s) => { return s != ''; });
|
|
console.log('Using compiler flags', params.extra_compiler_args);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exports.fixParamsWithDefines = fixParamsWithDefines;
|
|
//# sourceMappingURL=builder.js.map
|