@andersbakken/fisk 4.0.71 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/daemon/fisk-daemon.js +222 -23
- package/package.json +2 -2
package/daemon/fisk-daemon.js
CHANGED
|
@@ -1,30 +1,194 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var child_process = require('child_process');
|
|
5
6
|
var require$$1 = require('fs');
|
|
7
|
+
var require$$4 = require('util');
|
|
8
|
+
var path$h = require('path');
|
|
9
|
+
var EventEmitter = require('events');
|
|
6
10
|
var require$$0 = require('constants');
|
|
7
11
|
var require$$0$1 = require('stream');
|
|
8
|
-
var require$$4 = require('util');
|
|
9
12
|
var assert$1 = require('assert');
|
|
10
|
-
var path$h = require('path');
|
|
11
13
|
var os$1 = require('os');
|
|
12
14
|
var net = require('net');
|
|
13
15
|
var require$$1$1 = require('module');
|
|
14
16
|
|
|
15
17
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
16
18
|
|
|
17
|
-
var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
|
|
18
19
|
var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
|
|
20
|
+
var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
|
|
21
|
+
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$h);
|
|
22
|
+
var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
|
|
19
23
|
var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
|
|
20
24
|
var require$$0__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$0$1);
|
|
21
|
-
var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
|
|
22
25
|
var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert$1);
|
|
23
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$h);
|
|
24
26
|
var os__default = /*#__PURE__*/_interopDefaultLegacy(os$1);
|
|
25
27
|
var net__default = /*#__PURE__*/_interopDefaultLegacy(net);
|
|
26
28
|
var require$$1__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$1$1);
|
|
27
29
|
|
|
30
|
+
/*! *****************************************************************************
|
|
31
|
+
Copyright (c) Microsoft Corporation.
|
|
32
|
+
|
|
33
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
34
|
+
purpose with or without fee is hereby granted.
|
|
35
|
+
|
|
36
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
37
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
38
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
39
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
40
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
41
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
42
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
43
|
+
***************************************************************************** */
|
|
44
|
+
|
|
45
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
46
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
47
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
48
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
49
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
50
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
51
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const execFileAsync = require$$4.promisify(child_process.execFile);
|
|
56
|
+
// Prefixes removed from a compiler's `-v` output before hashing / parsing.
|
|
57
|
+
// Must match filter() in src/client/Client.cpp (lines 234-257).
|
|
58
|
+
const FILTER_PREFIXES = [
|
|
59
|
+
"COLLECT_",
|
|
60
|
+
"InstalledDir: ",
|
|
61
|
+
"Found candidate GCC installation: ",
|
|
62
|
+
"Selected GCC installation: "
|
|
63
|
+
];
|
|
64
|
+
// Remove any line that starts with one of the filter prefixes.
|
|
65
|
+
// Equivalent to the C++ filter() loop that removes needle from line-starts only.
|
|
66
|
+
function filterOutput(output) {
|
|
67
|
+
let result = output;
|
|
68
|
+
for (const needle of FILTER_PREFIXES) {
|
|
69
|
+
const lines = result.split("\n");
|
|
70
|
+
const kept = [];
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
if (!line.startsWith(needle)) {
|
|
73
|
+
kept.push(line);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
result = kept.join("\n");
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
// Emulate sscanf cascade "%d.%d.%d" -> "%d.%d" -> "%d".
|
|
81
|
+
function parseVersion(suffix) {
|
|
82
|
+
const three = /^(\d+)\.(\d+)\.(\d+)/.exec(suffix);
|
|
83
|
+
if (three) {
|
|
84
|
+
return {
|
|
85
|
+
major: parseInt(three[1], 10),
|
|
86
|
+
minor: parseInt(three[2], 10),
|
|
87
|
+
patch: parseInt(three[3], 10)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const two = /^(\d+)\.(\d+)/.exec(suffix);
|
|
91
|
+
if (two) {
|
|
92
|
+
return { major: parseInt(two[1], 10), minor: parseInt(two[2], 10), patch: 0 };
|
|
93
|
+
}
|
|
94
|
+
const one = /^(\d+)/.exec(suffix);
|
|
95
|
+
if (one) {
|
|
96
|
+
return { major: parseInt(one[1], 10), minor: 0, patch: 0 };
|
|
97
|
+
}
|
|
98
|
+
return { major: 0, minor: 0, patch: 0 };
|
|
99
|
+
}
|
|
100
|
+
// Byte-for-byte port of createCompilerInfo() in src/client/Client.cpp (lines 290-341).
|
|
101
|
+
function createCompilerInfo(exec, versionInfo) {
|
|
102
|
+
let type = "unknown";
|
|
103
|
+
let input = "";
|
|
104
|
+
let version = { major: 0, minor: 0, patch: 0 };
|
|
105
|
+
let foundVersion = false;
|
|
106
|
+
const lines = versionInfo.split("\n");
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (line.startsWith("gcc version ")) {
|
|
109
|
+
type = "gcc";
|
|
110
|
+
const suffix = line.substring(12);
|
|
111
|
+
input += suffix;
|
|
112
|
+
version = parseVersion(suffix);
|
|
113
|
+
foundVersion = true;
|
|
114
|
+
}
|
|
115
|
+
else if (line.startsWith("clang version ")) {
|
|
116
|
+
type = "clang";
|
|
117
|
+
const suffix = line.substring(14);
|
|
118
|
+
input += suffix;
|
|
119
|
+
version = parseVersion(suffix);
|
|
120
|
+
foundVersion = true;
|
|
121
|
+
}
|
|
122
|
+
else if (line.startsWith("Target: ")) {
|
|
123
|
+
const suffix = line.substring(8);
|
|
124
|
+
input += suffix;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!foundVersion) {
|
|
128
|
+
const lower = exec.toLowerCase();
|
|
129
|
+
if (lower.indexOf("clang") !== -1) {
|
|
130
|
+
type = "clang";
|
|
131
|
+
}
|
|
132
|
+
else if (lower.indexOf("gcc") !== -1) {
|
|
133
|
+
type = "gcc";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const hash = crypto.createHash("sha1").update(input).digest("hex").toUpperCase();
|
|
137
|
+
return { hash, input, type, version };
|
|
138
|
+
}
|
|
139
|
+
class CompilerInfoCache {
|
|
140
|
+
constructor() {
|
|
141
|
+
this.cache = new Map();
|
|
142
|
+
this.pending = new Map();
|
|
143
|
+
}
|
|
144
|
+
get(compilerPath) {
|
|
145
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
146
|
+
if (typeof compilerPath !== "string" || compilerPath.length === 0) {
|
|
147
|
+
throw new Error("CompilerInfoCache.get: compilerPath must be a non-empty string");
|
|
148
|
+
}
|
|
149
|
+
const absPath = path__default["default"].resolve(compilerPath);
|
|
150
|
+
const stat = yield require$$1.promises.stat(absPath);
|
|
151
|
+
const key = `${absPath}:${stat.mtimeMs}`;
|
|
152
|
+
const cached = this.cache.get(key);
|
|
153
|
+
if (cached) {
|
|
154
|
+
return cached;
|
|
155
|
+
}
|
|
156
|
+
const inflight = this.pending.get(key);
|
|
157
|
+
if (inflight) {
|
|
158
|
+
return inflight;
|
|
159
|
+
}
|
|
160
|
+
const compute = CompilerInfoCache.compute(absPath).then((info) => {
|
|
161
|
+
this.cache.set(key, info);
|
|
162
|
+
return info;
|
|
163
|
+
});
|
|
164
|
+
this.pending.set(key, compute);
|
|
165
|
+
// Clean up the pending map on both success and failure so a failed
|
|
166
|
+
// lookup doesn't wedge the key forever. We attach a no-op catch on
|
|
167
|
+
// the cleanup chain because the original rejection is already
|
|
168
|
+
// surfaced through the returned `compute` promise.
|
|
169
|
+
compute
|
|
170
|
+
.finally(() => {
|
|
171
|
+
this.pending.delete(key);
|
|
172
|
+
})
|
|
173
|
+
.catch(() => {
|
|
174
|
+
/* rejection observed by caller via the returned `compute` */
|
|
175
|
+
});
|
|
176
|
+
return compute;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
static compute(absPath) {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
const { stdout, stderr } = yield execFileAsync(absPath, ["-v"], {
|
|
182
|
+
timeout: 30000,
|
|
183
|
+
maxBuffer: 4 * 1024 * 1024
|
|
184
|
+
});
|
|
185
|
+
const combined = `${stdout}${stderr}`;
|
|
186
|
+
const filtered = filterOutput(combined);
|
|
187
|
+
return createCompilerInfo(absPath, filtered);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
28
192
|
const Constants = {
|
|
29
193
|
// client codes
|
|
30
194
|
get AcquireCppSlot() {
|
|
@@ -4199,6 +4363,7 @@ const compileSlots = new Slots(option.int("slots", Math.max(os__default["default
|
|
|
4199
4363
|
const localSlotCount = option.int("local-slots", 0);
|
|
4200
4364
|
const localSlots = new Slots(localSlotCount, "local", debug);
|
|
4201
4365
|
const localSlotsMaxLoad = option("local-slots-max-load") || 0;
|
|
4366
|
+
const compilerInfoCache = new CompilerInfoCache();
|
|
4202
4367
|
const slotSubscribers = [];
|
|
4203
4368
|
function slotsInfo() {
|
|
4204
4369
|
return {
|
|
@@ -4277,6 +4442,7 @@ server.on("compile", (compile) => {
|
|
|
4277
4442
|
});
|
|
4278
4443
|
let requestedCppSlot = false;
|
|
4279
4444
|
let requestedLocalSlot = false;
|
|
4445
|
+
let compileClosed = false;
|
|
4280
4446
|
compile.on("acquireCppSlot", () => {
|
|
4281
4447
|
if (debug) {
|
|
4282
4448
|
console.log("acquireCppSlot");
|
|
@@ -4320,27 +4486,58 @@ server.on("compile", (compile) => {
|
|
|
4320
4486
|
compileSlots.release(compile.id);
|
|
4321
4487
|
}
|
|
4322
4488
|
});
|
|
4323
|
-
compile.on("acquireSlot", () => {
|
|
4489
|
+
compile.on("acquireSlot", (msg) => {
|
|
4324
4490
|
if (debug) {
|
|
4325
|
-
console.log("acquireSlot");
|
|
4491
|
+
console.log("acquireSlot", msg);
|
|
4326
4492
|
}
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4493
|
+
const compilerPath = msg && typeof msg.compiler === "string" && msg.compiler.length > 0 ? msg.compiler : null;
|
|
4494
|
+
const infoResult = compilerPath
|
|
4495
|
+
? compilerInfoCache.get(compilerPath).then((info) => ({ info, error: null }), (err) => {
|
|
4496
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4497
|
+
if (debug) {
|
|
4498
|
+
console.log("acquireSlot -> compilerInfoCache failed", compilerPath, message);
|
|
4499
|
+
}
|
|
4500
|
+
return { info: null, error: message };
|
|
4501
|
+
})
|
|
4502
|
+
: Promise.resolve({ info: null, error: "acquireSlot missing compiler path" });
|
|
4503
|
+
infoResult
|
|
4504
|
+
.then(({ info, error }) => {
|
|
4505
|
+
if (compileClosed) {
|
|
4506
|
+
return;
|
|
4330
4507
|
}
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4508
|
+
const respond = (slot) => {
|
|
4509
|
+
const response = {
|
|
4510
|
+
type: "slotAcquired",
|
|
4511
|
+
slot,
|
|
4512
|
+
compilerInfo: info
|
|
4513
|
+
};
|
|
4514
|
+
if (error) {
|
|
4515
|
+
response.error = error;
|
|
4516
|
+
}
|
|
4517
|
+
compile.send(response);
|
|
4518
|
+
};
|
|
4519
|
+
if (canAcquireLocalSlot() && localSlots.tryAcquire(compile.id, { pid: compile.pid })) {
|
|
4520
|
+
if (debug) {
|
|
4521
|
+
console.log("acquireSlot -> local slot granted");
|
|
4522
|
+
}
|
|
4523
|
+
requestedLocalSlot = true;
|
|
4524
|
+
respond("local");
|
|
4337
4525
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4526
|
+
else {
|
|
4527
|
+
if (debug) {
|
|
4528
|
+
console.log("acquireSlot -> falling back to cpp slot");
|
|
4529
|
+
}
|
|
4530
|
+
assert__default["default"](!requestedCppSlot);
|
|
4531
|
+
requestedCppSlot = true;
|
|
4532
|
+
cppSlots.acquire(compile.id, { pid: compile.pid }, () => {
|
|
4533
|
+
respond("cpp");
|
|
4534
|
+
});
|
|
4535
|
+
}
|
|
4536
|
+
})
|
|
4537
|
+
.catch((err) => {
|
|
4538
|
+
// Defensive: the process-wide unhandledRejection handler calls process.exit().
|
|
4539
|
+
console.error("acquireSlot handler failed unexpectedly", err);
|
|
4540
|
+
});
|
|
4344
4541
|
});
|
|
4345
4542
|
compile.on("releaseLocalSlot", () => {
|
|
4346
4543
|
if (debug) {
|
|
@@ -4356,6 +4553,7 @@ server.on("compile", (compile) => {
|
|
|
4356
4553
|
if (debug) {
|
|
4357
4554
|
console.error("Got error from fiskc", compile.id, compile.pid, err);
|
|
4358
4555
|
}
|
|
4556
|
+
compileClosed = true;
|
|
4359
4557
|
if (requestedCppSlot) {
|
|
4360
4558
|
requestedCppSlot = false;
|
|
4361
4559
|
cppSlots.release(compile.id);
|
|
@@ -4373,6 +4571,7 @@ server.on("compile", (compile) => {
|
|
|
4373
4571
|
if (debug) {
|
|
4374
4572
|
console.log("got end from", compile.id, compile.pid);
|
|
4375
4573
|
}
|
|
4574
|
+
compileClosed = true;
|
|
4376
4575
|
if (requestedCppSlot) {
|
|
4377
4576
|
requestedCppSlot = false;
|
|
4378
4577
|
cppSlots.release(compile.id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andersbakken/fisk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Fisk, a distributed compile system",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint . --ext .ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@andersbakken/blessed": "^0.1.82",
|
|
36
|
-
"@andersbakken/fisk-native": "^
|
|
36
|
+
"@andersbakken/fisk-native": "^21.0.0",
|
|
37
37
|
"@jhanssen/options": "^10.0.0",
|
|
38
38
|
"axios": "^0.21.1",
|
|
39
39
|
"bufferutil": "^4.0.7",
|