UNPKG

makit

Version:

Make in JavaScript done right!

171 lines (170 loc) 6.77 kB
"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;