Skip to content

Commit 5b1bf29

Browse files
committed
Merge branch 'next' of https://github.com/webpack/webpack into next
2 parents 814c894 + 8f30d83 commit 5b1bf29

2 files changed

Lines changed: 260 additions & 50 deletions

File tree

lib/MultiCompiler.js

Lines changed: 95 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,63 @@ module.exports = class MultiCompiler extends Tapable {
7575
});
7676
}
7777

78+
validateDependencies(callback) {
79+
const edges = new Set();
80+
const missing = [];
81+
const targetFound = (compiler) => {
82+
for(const edge of edges) {
83+
if(edge.target === compiler) {
84+
return true;
85+
}
86+
}
87+
return false;
88+
};
89+
const sortEdges = (e1, e2) => {
90+
return e1.source.name.localeCompare(e2.source.name) ||
91+
e1.target.name.localeCompare(e2.target.name);
92+
};
93+
for(const source of this.compilers) {
94+
if(source.dependencies) {
95+
for(const dep of source.dependencies) {
96+
const target = this.compilers.find((c) => c.name === dep);
97+
if(!target) {
98+
missing.push(dep);
99+
} else {
100+
edges.add({
101+
source,
102+
target
103+
});
104+
}
105+
}
106+
}
107+
}
108+
const errors = missing.map((m) => `Compiler dependency \`${m}\` not found.`);
109+
const stack = this.compilers.filter((c) => !targetFound(c));
110+
while(stack.length > 0) {
111+
const current = stack.pop();
112+
for(const edge of edges) {
113+
if(edge.source === current) {
114+
edges.delete(edge);
115+
const target = edge.target;
116+
if(!targetFound(target)) {
117+
stack.push(target);
118+
}
119+
}
120+
}
121+
}
122+
if(edges.size > 0) {
123+
const lines = Array.from(edges).sort(sortEdges).map(edge => `${edge.source.name} -> ${edge.target.name}`);
124+
lines.unshift("Circular dependency found in compiler dependencies.");
125+
errors.unshift(lines.join("\n"));
126+
}
127+
if(errors.length > 0) {
128+
const message = errors.join("\n");
129+
callback(new Error(message));
130+
return false;
131+
}
132+
return true;
133+
}
134+
78135
runWithDependencies(compilers, fn, callback) {
79136
let fulfilledNames = {};
80137
let remainingCompilers = compilers;
@@ -109,50 +166,54 @@ module.exports = class MultiCompiler extends Tapable {
109166
let watchings = [];
110167
let allStats = this.compilers.map(() => null);
111168
let compilerStatus = this.compilers.map(() => false);
112-
this.runWithDependencies(this.compilers, (compiler, callback) => {
113-
const compilerIdx = this.compilers.indexOf(compiler);
114-
let firstRun = true;
115-
let watching = compiler.watch(Array.isArray(watchOptions) ? watchOptions[compilerIdx] : watchOptions, (err, stats) => {
116-
if(err)
117-
handler(err);
118-
if(stats) {
119-
allStats[compilerIdx] = stats;
120-
compilerStatus[compilerIdx] = "new";
121-
if(compilerStatus.every(Boolean)) {
122-
const freshStats = allStats.filter((s, idx) => {
123-
return compilerStatus[idx] === "new";
124-
});
125-
compilerStatus.fill(true);
126-
const multiStats = new MultiStats(freshStats);
127-
handler(null, multiStats);
169+
if(this.validateDependencies(handler)) {
170+
this.runWithDependencies(this.compilers, (compiler, callback) => {
171+
const compilerIdx = this.compilers.indexOf(compiler);
172+
let firstRun = true;
173+
let watching = compiler.watch(Array.isArray(watchOptions) ? watchOptions[compilerIdx] : watchOptions, (err, stats) => {
174+
if(err)
175+
handler(err);
176+
if(stats) {
177+
allStats[compilerIdx] = stats;
178+
compilerStatus[compilerIdx] = "new";
179+
if(compilerStatus.every(Boolean)) {
180+
const freshStats = allStats.filter((s, idx) => {
181+
return compilerStatus[idx] === "new";
182+
});
183+
compilerStatus.fill(true);
184+
const multiStats = new MultiStats(freshStats);
185+
handler(null, multiStats);
186+
}
128187
}
129-
}
130-
if(firstRun && !err) {
131-
firstRun = false;
132-
callback();
133-
}
188+
if(firstRun && !err) {
189+
firstRun = false;
190+
callback();
191+
}
192+
});
193+
watchings.push(watching);
194+
}, () => {
195+
// ignore
134196
});
135-
watchings.push(watching);
136-
}, () => {
137-
// ignore
138-
});
197+
}
139198

140199
return new MultiWatching(watchings, this);
141200
}
142201

143202
run(callback) {
144203
const allStats = this.compilers.map(() => null);
145-
this.runWithDependencies(this.compilers, ((compiler, callback) => {
146-
const compilerIdx = this.compilers.indexOf(compiler);
147-
compiler.run((err, stats) => {
204+
if(this.validateDependencies(callback)) {
205+
this.runWithDependencies(this.compilers, ((compiler, callback) => {
206+
const compilerIdx = this.compilers.indexOf(compiler);
207+
compiler.run((err, stats) => {
208+
if(err) return callback(err);
209+
allStats[compilerIdx] = stats;
210+
callback();
211+
});
212+
}), (err) => {
148213
if(err) return callback(err);
149-
allStats[compilerIdx] = stats;
150-
callback();
214+
callback(null, new MultiStats(allStats));
151215
});
152-
}), (err) => {
153-
if(err) return callback(err);
154-
callback(null, new MultiStats(allStats));
155-
});
216+
}
156217
}
157218

158219
purgeInputFileSystem() {

test/MultiCompiler.test.js

Lines changed: 165 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,21 @@ const createCompiler = function(overrides) {
4747
};
4848

4949
const setupTwoCompilerEnvironment = function(env, compiler1Values, compiler2Values) {
50-
const compilerEnvironment1 = new CompilerEnvironment();
51-
const compilerEnvironment2 = new CompilerEnvironment();
52-
const compilers = [
53-
Object.assign({
54-
name: "compiler1"
55-
}, (compiler1Values || {}), compilerEnvironment1.getCompilerStub()),
56-
Object.assign({
57-
name: "compiler2"
58-
}, (compiler2Values || {}), compilerEnvironment2.getCompilerStub())
59-
];
50+
return setupMutliCompilerEnvironment(env, 2, [compiler1Values, compiler2Values]);
51+
};
52+
53+
const setupMutliCompilerEnvironment = function(env, count, compilerValues) {
54+
const values = Array.isArray(compilerValues) ? compilerValues : new Array(count);
55+
const environments = values.map(() => new CompilerEnvironment());
56+
const compilers = environments.map((e, i) => Object.assign({
57+
name: `compiler${i + 1}`
58+
}, (values[i] || {}), e.getCompilerStub()));
6059
env.myMultiCompiler = new MultiCompiler(compilers);
61-
env.compiler1EventBindings = compilerEnvironment1.getPluginEventBindings();
62-
env.compiler2EventBindings = compilerEnvironment2.getPluginEventBindings();
63-
env.compiler1WatchCallbacks = compilerEnvironment1.getWatchCallbacks();
64-
env.compiler2WatchCallbacks = compilerEnvironment2.getWatchCallbacks();
65-
env.compiler1RunCallbacks = compilerEnvironment1.getRunCallbacks();
66-
env.compiler2RunCallbacks = compilerEnvironment2.getRunCallbacks();
60+
environments.forEach((compilerEnvironment, i) => {
61+
env[`compiler${i + 1}EventBindings`] = compilerEnvironment.getPluginEventBindings();
62+
env[`compiler${i + 1}WatchCallbacks`] = compilerEnvironment.getWatchCallbacks();
63+
env[`compiler${i + 1}RunCallbacks`] = compilerEnvironment.getRunCallbacks();
64+
});
6765
};
6866

6967
describe("MultiCompiler", () => {
@@ -397,6 +395,89 @@ describe("MultiCompiler", () => {
397395
env.callback.callCount.should.be.exactly(1);
398396
});
399397
});
398+
399+
describe("with missing compiler dependencies", () => {
400+
beforeEach(() => {
401+
setupTwoCompilerEnvironment(env, {
402+
name: "compiler1",
403+
dependencies: ["compiler2"]
404+
}, {
405+
name: "compiler3"
406+
});
407+
env.callback = sinon.spy();
408+
env.options = [{
409+
testWatchOptions: true
410+
}, {
411+
testWatchOptions2: true
412+
}];
413+
env.result = env.myMultiCompiler.watch(env.options, env.callback);
414+
});
415+
416+
it("should call the callback with an error message", () => {
417+
env.compiler1WatchCallbacks.length.should.be.exactly(0);
418+
env.compiler2WatchCallbacks.length.should.be.exactly(0);
419+
env.callback.callCount.should.be.exactly(1);
420+
env.callback.getCall(0).args[0].should.be.Error();
421+
should(env.callback.getCall(0).args[1]).be.undefined();
422+
});
423+
});
424+
425+
describe("with circular compiler dependencies", () => {
426+
beforeEach(() => {
427+
setupTwoCompilerEnvironment(env, {
428+
name: "compiler1",
429+
dependencies: ["compiler2"]
430+
}, {
431+
name: "compiler2",
432+
dependencies: ["compiler1"]
433+
});
434+
env.callback = sinon.spy();
435+
env.options = [{
436+
testWatchOptions: true
437+
}, {
438+
testWatchOptions2: true
439+
}];
440+
env.result = env.myMultiCompiler.watch(env.options, env.callback);
441+
});
442+
443+
it("should call the callback with an error message", () => {
444+
env.compiler1WatchCallbacks.length.should.be.exactly(0);
445+
env.compiler2WatchCallbacks.length.should.be.exactly(0);
446+
env.callback.callCount.should.be.exactly(1);
447+
env.callback.getCall(0).args[0].should.be.Error();
448+
should(env.callback.getCall(0).args[1]).be.undefined();
449+
});
450+
});
451+
452+
describe("with no root compiler", () => {
453+
beforeEach(() => {
454+
setupMutliCompilerEnvironment(env, 3, [{
455+
name: "a",
456+
}, {
457+
name: "b",
458+
dependencies: ["a", "c"]
459+
}, {
460+
name: "c",
461+
dependencies: ["b"]
462+
}]);
463+
env.callback = sinon.spy();
464+
env.options = [{
465+
testWatchOptions: true
466+
}, {
467+
testWatchOptions2: true
468+
}];
469+
env.result = env.myMultiCompiler.watch(env.options, env.callback);
470+
});
471+
472+
it("should call the callback with an error message", () => {
473+
env.compiler1RunCallbacks.length.should.be.exactly(0);
474+
env.compiler2RunCallbacks.length.should.be.exactly(0);
475+
env.compiler3RunCallbacks.length.should.be.exactly(0);
476+
env.callback.callCount.should.be.exactly(1);
477+
env.callback.getCall(0).args[0].should.be.Error();
478+
should(env.callback.getCall(0).args[1]).be.undefined();
479+
});
480+
});
400481
});
401482

402483
describe("run", () => {
@@ -489,6 +570,74 @@ describe("MultiCompiler", () => {
489570
env.callback.callCount.should.be.exactly(1);
490571
});
491572
});
573+
574+
describe("with missing compiler dependencies", () => {
575+
beforeEach(() => {
576+
setupTwoCompilerEnvironment(env, {
577+
name: "compiler1",
578+
dependencies: ["compiler2"]
579+
}, {
580+
name: "compiler3"
581+
});
582+
env.callback = sinon.spy();
583+
env.myMultiCompiler.run(env.callback);
584+
});
585+
586+
it("should call the callback with an error message", () => {
587+
env.compiler1RunCallbacks.length.should.be.exactly(0);
588+
env.compiler2RunCallbacks.length.should.be.exactly(0);
589+
env.callback.callCount.should.be.exactly(1);
590+
env.callback.getCall(0).args[0].should.be.Error();
591+
should(env.callback.getCall(0).args[1]).be.undefined();
592+
});
593+
});
594+
595+
describe("with circular compiler dependencies", () => {
596+
beforeEach(() => {
597+
setupTwoCompilerEnvironment(env, {
598+
name: "compiler1",
599+
dependencies: ["compiler2"]
600+
}, {
601+
name: "compiler2",
602+
dependencies: ["compiler1"]
603+
});
604+
env.callback = sinon.spy();
605+
env.myMultiCompiler.run(env.callback);
606+
});
607+
608+
it("should call the callback with an error message", () => {
609+
env.compiler1RunCallbacks.length.should.be.exactly(0);
610+
env.compiler2RunCallbacks.length.should.be.exactly(0);
611+
env.callback.callCount.should.be.exactly(1);
612+
env.callback.getCall(0).args[0].should.be.Error();
613+
should(env.callback.getCall(0).args[1]).be.undefined();
614+
});
615+
});
616+
617+
describe("with no root compiler", () => {
618+
beforeEach(() => {
619+
setupMutliCompilerEnvironment(env, 3, [{
620+
name: "a",
621+
}, {
622+
name: "b",
623+
dependencies: ["a", "c"]
624+
}, {
625+
name: "c",
626+
dependencies: ["b"]
627+
}]);
628+
env.callback = sinon.spy();
629+
env.myMultiCompiler.run(env.callback);
630+
});
631+
632+
it("should call the callback with an error message", () => {
633+
env.compiler1RunCallbacks.length.should.be.exactly(0);
634+
env.compiler2RunCallbacks.length.should.be.exactly(0);
635+
env.compiler3RunCallbacks.length.should.be.exactly(0);
636+
env.callback.callCount.should.be.exactly(1);
637+
env.callback.getCall(0).args[0].should.be.Error();
638+
should(env.callback.getCall(0).args[1]).be.undefined();
639+
});
640+
});
492641
});
493642

494643
describe("purgeInputFileSystem", () => {

0 commit comments

Comments
 (0)