makit
Version:
Make in JavaScript done right!
171 lines (170 loc) • 6.77 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Make = void 0;
const target_1 = require("./target");
const number_1 = require("./utils/number");
const io_1 = require("./io");
const mtime_1 = require("./fs/mtime");
const queue_1 = require("./utils/queue");
const task_1 = require("./task");
const graph_1 = require("./utils/graph");
const logger_1 = require("./utils/logger");
const l = logger_1.Logger.getOrCreate();
/**
* 一个 Make 对象表示一次 make
* 每次 make 的入口 target 是唯一的,其依赖图是一个有序图,用 checkCircular 来确保这一点
*/
class Make {
constructor({ root = process.cwd(), matchRule, disableCheckCircular, reporter }) {
this.dependencyGraph = new graph_1.DirectedGraph();
this.targets = new Map();
this.tasks = new Map();
this.isMaking = false;
// ES Set 是有序集合(按照 add 顺序),在此用作队列顺便帮助去重
this.targetQueue = new queue_1.Queue();
this.root = root;
this.matchRule = matchRule;
this.reporter = reporter;
this.disableCheckCircular = disableCheckCircular || false;
}
async make(targetName, parent) {
this.buildDependencyGraph(targetName, parent);
for (const node of this.dependencyGraph.preOrder(targetName)) {
const target = this.targets.get(node);
if (target.isReady())
this.scheduleTask(target);
}
l.verbose('GRAF', '0-indegree:', this.targetQueue);
return new Promise((resolve, reject) => {
const target = this.targets.get(targetName);
target.addPromise(resolve, reject);
this.startMake();
});
}
startMake() {
if (this.isMaking)
return;
this.isMaking = true;
while (this.targetQueue.size) {
const task = this.targetQueue.pop();
if (task.isCanceled())
continue;
this.doMake(task.target)
.then(() => {
if (task.isCanceled())
return;
task.target.resolve();
this.notifyDependants(task.target.name);
})
.catch((err) => {
if (task.isCanceled())
return;
// 让 target 以及依赖 target 的目标对应的 make promise 失败
const dependants = this.dependencyGraph.getInVerticesRecursively(task.target.name);
err['target'] = task.target.name;
for (const dependant of dependants) {
this.targets.get(dependant).reject(err);
}
});
}
this.isMaking = false;
}
invalidate(targetName) {
const target = this.targets.get(targetName);
// 还没编译到这个文件,或者这个文件根本不在依赖树里
if (!target)
return;
// 更新它的时间(用严格递增的虚拟时间来替代文件系统时间)
target.updateMtime();
const queue = new Set([target]);
for (const node of queue) {
for (const parent of this.dependencyGraph.getInVertices(node.name)) {
const ptarget = this.targets.get(parent);
// 已经成功 make,意味着 parent 对 node 的依赖已经移除
// 注意:不可用 isFinished,因为 isRejected() 的情况依赖并未移除
if (node.isResolved())
ptarget.pendingDependencyCount++;
queue.add(ptarget);
}
node.reset();
}
this.scheduleTask(target);
this.startMake();
}
buildDependencyGraph(node, parent) {
l.verbose('GRAF', 'node:', node, 'parent:', parent);
this.dependencyGraph.addVertex(node);
// 边 (parent, node) 不存在时才添加
if (parent && !this.dependencyGraph.hasEdge(parent, node)) {
this.dependencyGraph.addEdge(parent, node);
if (!this.isResolved(node)) {
this.targets.get(parent).pendingDependencyCount++;
}
}
if (!this.disableCheckCircular)
this.dependencyGraph.checkCircular(node);
if (this.targets.has(node))
return;
const result = this.matchRule(node);
const [rule, match] = result || [undefined, null];
const target = this.createTarget({ target: node, match, rule });
this.targets.set(node, target);
l.debug('DEPS', logger_1.hlTarget(node), target.getDependencies());
for (const dep of target.getDependencies())
this.buildDependencyGraph(dep, node);
}
isResolved(targetName) {
const target = this.targets.get(targetName);
return target && target.isResolved();
}
async doMake(target) {
target.start();
this.reporter.make(target);
let dmtime = mtime_1.MTIME_EMPTY_DEPENDENCY;
for (const dep of this.dependencyGraph.getOutVerticies(target.name)) {
dmtime = Math.max(dmtime, this.targets.get(dep).mtime);
}
l.debug('TIME', logger_1.hlTarget(target.name), () => `mtime(${target.mtime}) ${number_1.relation(target.mtime, dmtime)} dmtime(${dmtime})`);
if (dmtime < target.mtime) {
this.reporter.skip(target);
}
else {
if (!target.rule)
throw new Error(`no rule matched target: "${target.name}"`);
await target.rule.recipe.make(target.ctx);
if (target.rule.hasDynamicDependencies)
await target.writeDependency();
await target.updateMtime();
this.reporter.made(target);
}
}
notifyDependants(targetName) {
for (const dependant of this.dependencyGraph.getInVertices(targetName)) {
const dependantTarget = this.targets.get(dependant);
--dependantTarget.pendingDependencyCount;
if (dependantTarget.isReady()) {
this.scheduleTask(dependantTarget);
this.startMake();
}
}
}
scheduleTask(target) {
const task = new task_1.Task(target);
if (this.tasks.has(target.name)) {
this.tasks.get(target.name).cancel();
}
this.tasks.set(target.name, task);
this.targetQueue.push(task);
}
createTarget({ target, match, rule }) {
return target_1.Target.create({
target,
match,
fs: io_1.IO.getFileSystem(),
root: this.root,
rule,
make: (child) => this.make(child, target)
});
}
}
exports.Make = Make;