UNPKG

11.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _promise = require('babel-runtime/core-js/promise');
8
9var _promise2 = _interopRequireDefault(_promise);
10
11var _maxSafeInteger = require('babel-runtime/core-js/number/max-safe-integer');
12
13var _maxSafeInteger2 = _interopRequireDefault(_maxSafeInteger);
14
15var _from = require('babel-runtime/core-js/array/from');
16
17var _from2 = _interopRequireDefault(_from);
18
19var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
20
21var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
22
23var _lodash = require('lodash');
24
25var _lodash2 = _interopRequireDefault(_lodash);
26
27var _path = require('path');
28
29var _path2 = _interopRequireDefault(_path);
30
31var _readdirEnhanced = require('readdir-enhanced');
32
33var _readdirEnhanced2 = _interopRequireDefault(_readdirEnhanced);
34
35var _State = require('./State');
36
37var _State2 = _interopRequireDefault(_State);
38
39var _StateConsumer = require('./StateConsumer');
40
41var _StateConsumer2 = _interopRequireDefault(_StateConsumer);
42
43var _File = require('./File');
44
45var _File2 = _interopRequireDefault(_File);
46
47var _Rule = require('./Rule');
48
49var _Rule2 = _interopRequireDefault(_Rule);
50
51function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
52
53const VALID_COMMAND_PATTERN = /^(build|clean|graph|load|log|save|scrub)$/;
54
55class DiCy extends _StateConsumer2.default {
56 static create(filePath, options = {}) {
57 return (0, _asyncToGenerator3.default)(function* () {
58 const schema = yield DiCy.getOptionDefinitions();
59 const state = new _State2.default(filePath, schema);
60 const builder = new DiCy(state, state.getJobOptions());
61
62 yield builder.initialize();
63 yield builder.setInstanceOptions(options);
64
65 state.assignOptions(options);
66
67 return builder;
68 })();
69 }
70
71 initialize() {
72 var _this = this;
73
74 return (0, _asyncToGenerator3.default)(function* () {
75 const ruleClassPath = _path2.default.join(__dirname, 'Rules');
76 const entries = yield _readdirEnhanced2.default.async(ruleClassPath);
77 _this.state.ruleClasses = entries.map(function (entry) {
78 return require(_path2.default.join(ruleClassPath, entry)).default;
79 });
80 })();
81 }
82
83 setInstanceOptions(options = {}) {
84 var _this2 = this;
85
86 return (0, _asyncToGenerator3.default)(function* () {
87 const instance = yield _this2.getFile('dicy-instance.yaml-ParsedYAML');
88 if (instance) instance.value = options;
89 })();
90 }
91
92 analyzePhase(command, phase) {
93 var _this3 = this;
94
95 return (0, _asyncToGenerator3.default)(function* () {
96 _this3.checkForKill();
97
98 for (const ruleClass of _this3.ruleClasses) {
99 const jobNames = ruleClass.ignoreJobName ? [null] : _this3.options.jobNames;
100 for (const jobName of jobNames) {
101 const rule = yield ruleClass.analyzePhase(_this3.state, command, phase, _this3.state.getJobOptions(jobName));
102 if (rule) {
103 yield _this3.addRule(rule);
104 }
105 }
106 }
107 })();
108 }
109
110 analyzeFiles(command, phase) {
111 var _this4 = this;
112
113 return (0, _asyncToGenerator3.default)(function* () {
114 _this4.checkForKill();
115
116 while (true) {
117 const file = (0, _from2.default)(_this4.files).find(function (file) {
118 return !file.analyzed;
119 });
120
121 if (!file) break;
122
123 for (const ruleClass of _this4.ruleClasses) {
124 const jobNames = file.jobNames.size === 0 ? [null] : (0, _from2.default)(file.jobNames.values());
125 for (const jobName of jobNames) {
126 const rules = yield ruleClass.analyzeFile(_this4.state, command, phase, _this4.state.getJobOptions(jobName), file);
127 for (const rule of rules) {
128 yield _this4.addRule(rule);
129 }
130 }
131 }
132
133 file.analyzed = true;
134 }
135 })();
136 }
137
138 getAvailableRules(command) {
139 return this.ruleClasses.filter(rule => !command || rule.commands.has(command)).map(rule => ({ name: rule.name, description: rule.description }));
140 }
141
142 evaluateRule(rule, action) {
143 var _this5 = this;
144
145 return (0, _asyncToGenerator3.default)(function* () {
146 _this5.checkForKill();
147
148 if (rule.failures.has(action)) {
149 _this5.info(`Skipping rule ${rule.id} because of previous failure.`);
150 return false;
151 }
152
153 yield rule.evaluate(action);
154 return true;
155 })();
156 }
157
158 evaluate(command, phase, action) {
159 var _this6 = this;
160
161 return (0, _asyncToGenerator3.default)(function* () {
162 _this6.checkForKill();
163
164 const primaryCount = function (ruleGroup) {
165 return ruleGroup.reduce(function (total, rule) {
166 return total + rule.parameters.reduce(function (count, parameter) {
167 return parameter.filePath === _this6.filePath ? count + 1 : count;
168 }, 0);
169 }, 0);
170 };
171 const evaluationNeeded = function (rule) {
172 return rule.actions.has(action) && rule.command === command && rule.phase === phase;
173 };
174
175 // First separate the rule graph into connected components. For each
176 // component only retain rules that are pertinent to the current command,
177 // phase and action. Rank the rules in the component by the number of other
178 // rules that it is directly dependent on within the same component. Only
179 // retain those that have the lowest dependency rank. Sort the remaining
180 // rules by number of inputs in an ascending order. Finally sort the
181 // components by number of primaries in an ascending order. A rule is
182 // considered a primary is it has as an input the main source file for that
183 // job. Note: we are using lodash's sortBy because the standard sort is
184 // not guaranteed to be a stable sort.
185 const rules = _lodash2.default.flatten(_lodash2.default.sortBy(_this6.components.map(function (component) {
186 const ruleGroup = _lodash2.default.sortBy(component.filter(evaluationNeeded), [function (rule) {
187 return rule.inputs.length;
188 }]);
189
190 return ruleGroup.reduce(function (current, rule) {
191 // Rank the rule by how many other rules it is directly dependent on.
192 const rank = ruleGroup.reduce(function (count, otherRule) {
193 return _this6.isGrandparentOf(rule, otherRule) ? count + 1 : count;
194 }, 0);
195
196 // The rank is lower than the current rank so start a new list.
197 if (rank < current.rank) return { rank, rules: [rule]
198 // The ranks is the same as the current rank so just add us to the
199 };if (rank === current.rank) current.rules.push(rule);
200 // list.
201
202 return current;
203 }, { rank: _maxSafeInteger2.default, rules: [] }).rules;
204 }), [primaryCount]));
205
206 if (rules.length === 0) return false;
207
208 let didEvaluation = false;
209
210 for (const rule of rules) {
211 yield _this6.checkUpdates(command, phase);
212 didEvaluation = (yield _this6.evaluateRule(rule, action)) || didEvaluation;
213 }
214
215 yield _this6.checkUpdates(command, phase);
216
217 return didEvaluation;
218 })();
219 }
220
221 checkUpdates(command, phase) {
222 var _this7 = this;
223
224 return (0, _asyncToGenerator3.default)(function* () {
225 _this7.checkForKill();
226
227 for (const file of _this7.files) {
228 for (const rule of _this7.state.getInputRules(file)) {
229 yield rule.addFileActions(file, command, phase);
230 }
231 file.hasBeenUpdated = false;
232 }
233 })();
234 }
235
236 kill(message = 'Build was killed.') {
237 if (!this.killToken) return _promise2.default.resolve();
238
239 if (!this.killToken.promise) {
240 this.killToken.error = new Error(message);
241 this.killToken.promise = new _promise2.default(resolve => {
242 // $FlowIgnore
243 this.killToken.resolve = resolve;
244 this.killChildProcesses();
245 });
246 }
247
248 return this.killToken.promise;
249 }
250
251 run(...commands) {
252 var _this8 = this;
253
254 return (0, _asyncToGenerator3.default)(function* () {
255 if (_this8.killToken) {
256 _this8.error('Build currently in progress');
257 return false;
258 }
259
260 const invalidCommands = commands.filter(function (command) {
261 return !VALID_COMMAND_PATTERN.test(command);
262 });
263 if (invalidCommands.length !== 0) {
264 _this8.error(`Aborting due to receiving the following invalid commands: ${invalidCommands.join(', ')}`);
265 return false;
266 }
267
268 _this8.killToken = {};
269
270 let success = true;
271
272 try {
273 for (const command of commands) {
274 for (const phase of ['initialize', 'execute', 'finalize']) {
275 yield _this8.runPhase(command, phase);
276 }
277 }
278 success = (0, _from2.default)(_this8.rules).every(function (rule) {
279 return rule.failures.size === 0;
280 });
281 } catch (error) {
282 success = false;
283 _this8.error(error.stack);
284 } finally {
285 if (_this8.killToken && _this8.killToken.resolve) {
286 success = false;
287 _this8.killToken.resolve();
288 }
289 _this8.killToken = null;
290 }
291
292 return success;
293 })();
294 }
295
296 runPhase(command, phase) {
297 var _this9 = this;
298
299 return (0, _asyncToGenerator3.default)(function* () {
300 _this9.checkForKill();
301
302 for (const file of _this9.files) {
303 file.hasBeenUpdated = file.hasBeenUpdatedCache;
304 file.analyzed = false;
305 }
306
307 for (const rule of _this9.rules) {
308 yield rule.phaseInitialize(command, phase);
309 }
310
311 yield _this9.analyzePhase(command, phase);
312
313 for (let cycle = 0; cycle < _this9.options.phaseCycles; cycle++) {
314 let didEvaluation = false;
315
316 for (const action of ['parse', 'updateDependencies', 'run']) {
317 yield _this9.analyzeFiles(command, phase);
318 didEvaluation = (yield _this9.evaluate(command, phase, action)) || didEvaluation;
319 }
320
321 if ((0, _from2.default)(_this9.files).every(function (file) {
322 return file.analyzed;
323 }) && (0, _from2.default)(_this9.rules).every(function (rule) {
324 return rule.command !== command || rule.phase !== phase || !rule.needsEvaluation;
325 })) break;
326
327 if (!didEvaluation) break;
328 }
329 })();
330 }
331
332 static getOptionDefinitions() {
333 return (0, _asyncToGenerator3.default)(function* () {
334 const filePath = _path2.default.resolve(__dirname, '..', 'resources', 'option-schema.yaml');
335 const schema = yield _File2.default.load(filePath);
336 const options = [];
337 for (const name in schema) {
338 const option = schema[name];
339 option.name = name;
340 options.push(option);
341 }
342 return options;
343 })();
344 }
345
346 updateOptions(options = {}, user = false) {
347 var _this10 = this;
348
349 return (0, _asyncToGenerator3.default)(function* () {
350 const normalizedOptions = {};
351 const filePath = _this10.resolvePath(user ? '$HOME/.dicy.yaml' : '$ROOTDIR/$NAME.yaml');
352
353 if (yield _File2.default.canRead(filePath)) {
354 const currentOptions = yield _File2.default.safeLoad(filePath);
355 _this10.state.assignSubOptions(normalizedOptions, currentOptions);
356 }
357
358 _this10.state.assignSubOptions(normalizedOptions, options);
359 yield _File2.default.safeDump(filePath, normalizedOptions);
360
361 return normalizedOptions;
362 })();
363 }
364}
365exports.default = DiCy;
\No newline at end of file