@andersbakken/fisk 4.0.70 → 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.
Files changed (2) hide show
  1. package/daemon/fisk-daemon.js +222 -23
  2. package/package.json +2 -2
@@ -1,30 +1,194 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var EventEmitter = require('events');
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
- if (canAcquireLocalSlot() && localSlots.tryAcquire(compile.id, { pid: compile.pid })) {
4328
- if (debug) {
4329
- console.log("acquireSlot -> local slot granted");
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
- requestedLocalSlot = true;
4332
- compile.send(Constants.LocalSlotAcquired);
4333
- }
4334
- else {
4335
- if (debug) {
4336
- console.log("acquireSlot -> falling back to cpp slot");
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
- assert__default["default"](!requestedCppSlot);
4339
- requestedCppSlot = true;
4340
- cppSlots.acquire(compile.id, { pid: compile.pid }, () => {
4341
- compile.send(Constants.CppSlotAcquired);
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": "4.0.70",
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": "^20.0.0",
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",