Projekt

Obecné

Profil

Stáhnout (7.23 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
"use strict";
6

    
7
const { Tapable, SyncHook, MultiHook } = require("tapable");
8
const asyncLib = require("neo-async");
9
const MultiWatching = require("./MultiWatching");
10
const MultiStats = require("./MultiStats");
11
const ConcurrentCompilationError = require("./ConcurrentCompilationError");
12

    
13
module.exports = class MultiCompiler extends Tapable {
14
	constructor(compilers) {
15
		super();
16
		this.hooks = {
17
			done: new SyncHook(["stats"]),
18
			invalid: new MultiHook(compilers.map(c => c.hooks.invalid)),
19
			run: new MultiHook(compilers.map(c => c.hooks.run)),
20
			watchClose: new SyncHook([]),
21
			watchRun: new MultiHook(compilers.map(c => c.hooks.watchRun)),
22
			infrastructureLog: new MultiHook(
23
				compilers.map(c => c.hooks.infrastructureLog)
24
			)
25
		};
26
		if (!Array.isArray(compilers)) {
27
			compilers = Object.keys(compilers).map(name => {
28
				compilers[name].name = name;
29
				return compilers[name];
30
			});
31
		}
32
		this.compilers = compilers;
33
		let doneCompilers = 0;
34
		let compilerStats = [];
35
		let index = 0;
36
		for (const compiler of this.compilers) {
37
			let compilerDone = false;
38
			const compilerIndex = index++;
39
			// eslint-disable-next-line no-loop-func
40
			compiler.hooks.done.tap("MultiCompiler", stats => {
41
				if (!compilerDone) {
42
					compilerDone = true;
43
					doneCompilers++;
44
				}
45
				compilerStats[compilerIndex] = stats;
46
				if (doneCompilers === this.compilers.length) {
47
					this.hooks.done.call(new MultiStats(compilerStats));
48
				}
49
			});
50
			// eslint-disable-next-line no-loop-func
51
			compiler.hooks.invalid.tap("MultiCompiler", () => {
52
				if (compilerDone) {
53
					compilerDone = false;
54
					doneCompilers--;
55
				}
56
			});
57
		}
58
		this.running = false;
59
	}
60

    
61
	get outputPath() {
62
		let commonPath = this.compilers[0].outputPath;
63
		for (const compiler of this.compilers) {
64
			while (
65
				compiler.outputPath.indexOf(commonPath) !== 0 &&
66
				/[/\\]/.test(commonPath)
67
			) {
68
				commonPath = commonPath.replace(/[/\\][^/\\]*$/, "");
69
			}
70
		}
71

    
72
		if (!commonPath && this.compilers[0].outputPath[0] === "/") return "/";
73
		return commonPath;
74
	}
75

    
76
	get inputFileSystem() {
77
		throw new Error("Cannot read inputFileSystem of a MultiCompiler");
78
	}
79

    
80
	get outputFileSystem() {
81
		throw new Error("Cannot read outputFileSystem of a MultiCompiler");
82
	}
83

    
84
	set inputFileSystem(value) {
85
		for (const compiler of this.compilers) {
86
			compiler.inputFileSystem = value;
87
		}
88
	}
89

    
90
	set outputFileSystem(value) {
91
		for (const compiler of this.compilers) {
92
			compiler.outputFileSystem = value;
93
		}
94
	}
95

    
96
	getInfrastructureLogger(name) {
97
		return this.compilers[0].getInfrastructureLogger(name);
98
	}
99

    
100
	validateDependencies(callback) {
101
		const edges = new Set();
102
		const missing = [];
103
		const targetFound = compiler => {
104
			for (const edge of edges) {
105
				if (edge.target === compiler) {
106
					return true;
107
				}
108
			}
109
			return false;
110
		};
111
		const sortEdges = (e1, e2) => {
112
			return (
113
				e1.source.name.localeCompare(e2.source.name) ||
114
				e1.target.name.localeCompare(e2.target.name)
115
			);
116
		};
117
		for (const source of this.compilers) {
118
			if (source.dependencies) {
119
				for (const dep of source.dependencies) {
120
					const target = this.compilers.find(c => c.name === dep);
121
					if (!target) {
122
						missing.push(dep);
123
					} else {
124
						edges.add({
125
							source,
126
							target
127
						});
128
					}
129
				}
130
			}
131
		}
132
		const errors = missing.map(m => `Compiler dependency \`${m}\` not found.`);
133
		const stack = this.compilers.filter(c => !targetFound(c));
134
		while (stack.length > 0) {
135
			const current = stack.pop();
136
			for (const edge of edges) {
137
				if (edge.source === current) {
138
					edges.delete(edge);
139
					const target = edge.target;
140
					if (!targetFound(target)) {
141
						stack.push(target);
142
					}
143
				}
144
			}
145
		}
146
		if (edges.size > 0) {
147
			const lines = Array.from(edges)
148
				.sort(sortEdges)
149
				.map(edge => `${edge.source.name} -> ${edge.target.name}`);
150
			lines.unshift("Circular dependency found in compiler dependencies.");
151
			errors.unshift(lines.join("\n"));
152
		}
153
		if (errors.length > 0) {
154
			const message = errors.join("\n");
155
			callback(new Error(message));
156
			return false;
157
		}
158
		return true;
159
	}
160

    
161
	runWithDependencies(compilers, fn, callback) {
162
		const fulfilledNames = new Set();
163
		let remainingCompilers = compilers;
164
		const isDependencyFulfilled = d => fulfilledNames.has(d);
165
		const getReadyCompilers = () => {
166
			let readyCompilers = [];
167
			let list = remainingCompilers;
168
			remainingCompilers = [];
169
			for (const c of list) {
170
				const ready =
171
					!c.dependencies || c.dependencies.every(isDependencyFulfilled);
172
				if (ready) {
173
					readyCompilers.push(c);
174
				} else {
175
					remainingCompilers.push(c);
176
				}
177
			}
178
			return readyCompilers;
179
		};
180
		const runCompilers = callback => {
181
			if (remainingCompilers.length === 0) return callback();
182
			asyncLib.map(
183
				getReadyCompilers(),
184
				(compiler, callback) => {
185
					fn(compiler, err => {
186
						if (err) return callback(err);
187
						fulfilledNames.add(compiler.name);
188
						runCompilers(callback);
189
					});
190
				},
191
				callback
192
			);
193
		};
194
		runCompilers(callback);
195
	}
196

    
197
	watch(watchOptions, handler) {
198
		if (this.running) return handler(new ConcurrentCompilationError());
199

    
200
		let watchings = [];
201
		let allStats = this.compilers.map(() => null);
202
		let compilerStatus = this.compilers.map(() => false);
203
		if (this.validateDependencies(handler)) {
204
			this.running = true;
205
			this.runWithDependencies(
206
				this.compilers,
207
				(compiler, callback) => {
208
					const compilerIdx = this.compilers.indexOf(compiler);
209
					let firstRun = true;
210
					let watching = compiler.watch(
211
						Array.isArray(watchOptions)
212
							? watchOptions[compilerIdx]
213
							: watchOptions,
214
						(err, stats) => {
215
							if (err) handler(err);
216
							if (stats) {
217
								allStats[compilerIdx] = stats;
218
								compilerStatus[compilerIdx] = "new";
219
								if (compilerStatus.every(Boolean)) {
220
									const freshStats = allStats.filter((s, idx) => {
221
										return compilerStatus[idx] === "new";
222
									});
223
									compilerStatus.fill(true);
224
									const multiStats = new MultiStats(freshStats);
225
									handler(null, multiStats);
226
								}
227
							}
228
							if (firstRun && !err) {
229
								firstRun = false;
230
								callback();
231
							}
232
						}
233
					);
234
					watchings.push(watching);
235
				},
236
				() => {
237
					// ignore
238
				}
239
			);
240
		}
241

    
242
		return new MultiWatching(watchings, this);
243
	}
244

    
245
	run(callback) {
246
		if (this.running) {
247
			return callback(new ConcurrentCompilationError());
248
		}
249

    
250
		const finalCallback = (err, stats) => {
251
			this.running = false;
252

    
253
			if (callback !== undefined) {
254
				return callback(err, stats);
255
			}
256
		};
257

    
258
		const allStats = this.compilers.map(() => null);
259
		if (this.validateDependencies(callback)) {
260
			this.running = true;
261
			this.runWithDependencies(
262
				this.compilers,
263
				(compiler, callback) => {
264
					const compilerIdx = this.compilers.indexOf(compiler);
265
					compiler.run((err, stats) => {
266
						if (err) {
267
							return callback(err);
268
						}
269
						allStats[compilerIdx] = stats;
270
						callback();
271
					});
272
				},
273
				err => {
274
					if (err) {
275
						return finalCallback(err);
276
					}
277
					finalCallback(null, new MultiStats(allStats));
278
				}
279
			);
280
		}
281
	}
282

    
283
	purgeInputFileSystem() {
284
		for (const compiler of this.compilers) {
285
			if (compiler.inputFileSystem && compiler.inputFileSystem.purge) {
286
				compiler.inputFileSystem.purge();
287
			}
288
		}
289
	}
290
};
(90-90/145)