@andersbakken/fisk 5.0.4 → 5.0.7
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 +172 -75
- package/package.json +1 -1
- package/scheduler/fisk-scheduler.js +1 -1
package/daemon/fisk-daemon.js
CHANGED
|
@@ -58,88 +58,191 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
const execFileAsync = require$$4.promisify(child_process.execFile);
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
// Fingerprinting strategy:
|
|
62
|
+
//
|
|
63
|
+
// A compiler's "identity" for distributed-compile purposes is the set of
|
|
64
|
+
// behaviours that determine what code the frontend accepts and what the
|
|
65
|
+
// backend produces. It is NOT the bytes of the driver executable, because
|
|
66
|
+
// GCC and Clang bake absolute install paths into the driver at build time
|
|
67
|
+
// (STANDARD_EXEC_PREFIX / GCC_INSTALL_PREFIX / CLANG_RESOURCE_DIR /
|
|
68
|
+
// DEFAULT_SYSROOT / ...). Two machines that installed the same conan
|
|
69
|
+
// package for llvm end up with byte-different driver binaries whose paths
|
|
70
|
+
// point into per-machine conan caches, but the compilers are functionally
|
|
71
|
+
// identical. A file hash would say they are different; the scheduler would
|
|
72
|
+
// then be unable to match clients to builders.
|
|
73
|
+
//
|
|
74
|
+
// Instead, we hash the compiler's answers to a small set of probes that
|
|
75
|
+
// (a) are switch-independent, (b) do not embed absolute paths, and
|
|
76
|
+
// (c) fully determine frontend behaviour:
|
|
77
|
+
//
|
|
78
|
+
// -dumpmachine default target triple
|
|
79
|
+
// -dumpversion version number
|
|
80
|
+
// -x c -E -dM /dev/null all builtin macros for C
|
|
81
|
+
// -x c++ -E -dM /dev/null all builtin macros for C++
|
|
82
|
+
//
|
|
83
|
+
// The macro dumps include __clang_version__ / __GNUC__ / __GNUC_MINOR__ /
|
|
84
|
+
// __GNUC_PATCHLEVEL__ / __VERSION__ / target width macros / feature-test
|
|
85
|
+
// macros. Those strings are frozen at compiler-build time, not install
|
|
86
|
+
// time, so they are identical across machines that installed the same
|
|
87
|
+
// compiler package.
|
|
88
|
+
const PROBE_TIMEOUT_MS = 10000;
|
|
89
|
+
const PROBE_MAX_BUFFER = 4 * 1024 * 1024;
|
|
90
|
+
const PROBES = [
|
|
91
|
+
{ label: "dumpmachine", args: ["-dumpmachine"], required: true },
|
|
92
|
+
{ label: "dumpversion", args: ["-dumpversion"], required: true },
|
|
93
|
+
{ label: "dumpfullversion", args: ["-dumpfullversion"], required: false },
|
|
94
|
+
{ label: "builtins-c", args: ["-x", "c", "-E", "-dM", "/dev/null"], required: true },
|
|
95
|
+
{ label: "builtins-cxx", args: ["-x", "c++", "-E", "-dM", "/dev/null"], required: true }
|
|
68
96
|
];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
function runProbe(exec, probe) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
try {
|
|
100
|
+
const { stdout, stderr } = yield execFileAsync(exec, [...probe.args], {
|
|
101
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
102
|
+
maxBuffer: PROBE_MAX_BUFFER
|
|
103
|
+
});
|
|
104
|
+
return `${stdout}${stderr}`;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (probe.required) {
|
|
108
|
+
throw new Error(`Probe '${probe.label}' failed for ${exec}: ${err instanceof Error ? err.message : String(err)}`);
|
|
79
109
|
}
|
|
110
|
+
return null;
|
|
80
111
|
}
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
return result;
|
|
112
|
+
});
|
|
84
113
|
}
|
|
85
|
-
// Emulate sscanf cascade "%d.%d.%d" -> "%d.%d" -> "%d".
|
|
86
|
-
function parseVersion(
|
|
87
|
-
const three = /^(\d+)\.(\d+)\.(\d+)/.exec(
|
|
114
|
+
// Emulate the C++ sscanf cascade "%d.%d.%d" -> "%d.%d" -> "%d".
|
|
115
|
+
function parseVersion(text) {
|
|
116
|
+
const three = /^(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
88
117
|
if (three) {
|
|
89
|
-
return {
|
|
90
|
-
major: parseInt(three[1], 10),
|
|
91
|
-
minor: parseInt(three[2], 10),
|
|
92
|
-
patch: parseInt(three[3], 10)
|
|
93
|
-
};
|
|
118
|
+
return { major: parseInt(three[1], 10), minor: parseInt(three[2], 10), patch: parseInt(three[3], 10) };
|
|
94
119
|
}
|
|
95
|
-
const two = /^(\d+)\.(\d+)/.exec(
|
|
120
|
+
const two = /^(\d+)\.(\d+)/.exec(text);
|
|
96
121
|
if (two) {
|
|
97
122
|
return { major: parseInt(two[1], 10), minor: parseInt(two[2], 10), patch: 0 };
|
|
98
123
|
}
|
|
99
|
-
const one = /^(\d+)/.exec(
|
|
124
|
+
const one = /^(\d+)/.exec(text);
|
|
100
125
|
if (one) {
|
|
101
126
|
return { major: parseInt(one[1], 10), minor: 0, patch: 0 };
|
|
102
127
|
}
|
|
103
128
|
return { major: 0, minor: 0, patch: 0 };
|
|
104
129
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
function detectTypeFromMacros(macros) {
|
|
131
|
+
// clang defines __clang__ even under GCC compatibility mode.
|
|
132
|
+
if (/^#define __clang__ /m.test(macros)) {
|
|
133
|
+
return "clang";
|
|
134
|
+
}
|
|
135
|
+
// GCC defines __GNUC__ but so does clang; require __GNUC__ *without* __clang__.
|
|
136
|
+
if (/^#define __GNUC__ /m.test(macros)) {
|
|
137
|
+
return "gcc";
|
|
138
|
+
}
|
|
139
|
+
return "unknown";
|
|
140
|
+
}
|
|
141
|
+
function macroValue(macros, name) {
|
|
142
|
+
const m = new RegExp(`^#define ${name} (.*)$`, "m").exec(macros);
|
|
143
|
+
return m ? m[1].trim() : null;
|
|
144
|
+
}
|
|
145
|
+
function stripQuotes(s) {
|
|
146
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
|
|
147
|
+
return s.substring(1, s.length - 1);
|
|
148
|
+
}
|
|
149
|
+
return s;
|
|
150
|
+
}
|
|
151
|
+
// Extract a version tuple from the compiler's own macros. This is stable
|
|
152
|
+
// across install locations because these macros are frozen at compiler
|
|
153
|
+
// build time.
|
|
154
|
+
function versionFromMacros(macros, type) {
|
|
155
|
+
if (type === "clang") {
|
|
156
|
+
const v = macroValue(macros, "__clang_version__");
|
|
157
|
+
if (v) {
|
|
158
|
+
return parseVersion(stripQuotes(v));
|
|
126
159
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
160
|
+
const major = macroValue(macros, "__clang_major__");
|
|
161
|
+
const minor = macroValue(macros, "__clang_minor__");
|
|
162
|
+
const patch = macroValue(macros, "__clang_patchlevel__");
|
|
163
|
+
if (major !== null) {
|
|
164
|
+
return {
|
|
165
|
+
major: parseInt(major, 10) || 0,
|
|
166
|
+
minor: minor !== null ? parseInt(minor, 10) || 0 : 0,
|
|
167
|
+
patch: patch !== null ? parseInt(patch, 10) || 0 : 0
|
|
168
|
+
};
|
|
130
169
|
}
|
|
131
170
|
}
|
|
132
|
-
if (
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
if (type === "gcc") {
|
|
172
|
+
const major = macroValue(macros, "__GNUC__");
|
|
173
|
+
const minor = macroValue(macros, "__GNUC_MINOR__");
|
|
174
|
+
const patch = macroValue(macros, "__GNUC_PATCHLEVEL__");
|
|
175
|
+
if (major !== null) {
|
|
176
|
+
return {
|
|
177
|
+
major: parseInt(major, 10) || 0,
|
|
178
|
+
minor: minor !== null ? parseInt(minor, 10) || 0 : 0,
|
|
179
|
+
patch: patch !== null ? parseInt(patch, 10) || 0 : 0
|
|
180
|
+
};
|
|
139
181
|
}
|
|
140
182
|
}
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
return { major: 0, minor: 0, patch: 0 };
|
|
184
|
+
}
|
|
185
|
+
function gatherProbes(exec) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
const results = yield Promise.all(PROBES.map((p) => runProbe(exec, p)));
|
|
188
|
+
const [dumpmachine, dumpversion, dumpfullversion, builtinsC, builtinsCxx] = results;
|
|
189
|
+
// The required probes cannot be null because runProbe would have thrown.
|
|
190
|
+
return {
|
|
191
|
+
dumpmachine: (dumpmachine !== null && dumpmachine !== void 0 ? dumpmachine : "").trim(),
|
|
192
|
+
dumpversion: (dumpversion !== null && dumpversion !== void 0 ? dumpversion : "").trim(),
|
|
193
|
+
dumpfullversion: dumpfullversion === null ? null : dumpfullversion.trim(),
|
|
194
|
+
builtinsC: builtinsC !== null && builtinsC !== void 0 ? builtinsC : "",
|
|
195
|
+
builtinsCxx: builtinsCxx !== null && builtinsCxx !== void 0 ? builtinsCxx : ""
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// Build the canonical fingerprint blob whose SHA becomes the compiler hash.
|
|
200
|
+
// Fields are separated by NUL to avoid ambiguity if any probe output
|
|
201
|
+
// contains our field label as a substring. Field labels are included so
|
|
202
|
+
// that adding a new probe in a later version deterministically changes the
|
|
203
|
+
// hash for the same compiler (the label acts as a schema version bump).
|
|
204
|
+
function canonicalFingerprint(p) {
|
|
205
|
+
var _a;
|
|
206
|
+
const parts = [
|
|
207
|
+
"fisk-compiler-fingerprint-v1",
|
|
208
|
+
"dumpmachine",
|
|
209
|
+
p.dumpmachine,
|
|
210
|
+
"dumpversion",
|
|
211
|
+
p.dumpversion,
|
|
212
|
+
"dumpfullversion",
|
|
213
|
+
(_a = p.dumpfullversion) !== null && _a !== void 0 ? _a : "",
|
|
214
|
+
"builtins-c",
|
|
215
|
+
p.builtinsC,
|
|
216
|
+
"builtins-cxx",
|
|
217
|
+
p.builtinsCxx
|
|
218
|
+
];
|
|
219
|
+
return Buffer.from(parts.join("\0"), "utf8");
|
|
220
|
+
}
|
|
221
|
+
function createCompilerInfo(exec) {
|
|
222
|
+
var _a;
|
|
223
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
const probes = yield gatherProbes(exec);
|
|
225
|
+
const type = detectTypeFromMacros(probes.builtinsC);
|
|
226
|
+
const versionFromMac = versionFromMacros(probes.builtinsC, type);
|
|
227
|
+
const version = versionFromMac.major !== 0
|
|
228
|
+
? versionFromMac
|
|
229
|
+
: parseVersion((_a = probes.dumpfullversion) !== null && _a !== void 0 ? _a : probes.dumpversion);
|
|
230
|
+
const blob = canonicalFingerprint(probes);
|
|
231
|
+
const hash = crypto.createHash("sha1").update(blob).digest("hex").toUpperCase();
|
|
232
|
+
// `input` is retained for debug/traceability: it lets a human see what
|
|
233
|
+
// went into the hash without needing to re-probe the compiler. Keep it
|
|
234
|
+
// small: just the identifying strings, not the full macro dumps.
|
|
235
|
+
const input = [
|
|
236
|
+
`type=${type}`,
|
|
237
|
+
`target=${probes.dumpmachine}`,
|
|
238
|
+
`version=${version.major}.${version.minor}.${version.patch}`,
|
|
239
|
+
`dumpversion=${probes.dumpversion}`,
|
|
240
|
+
probes.dumpfullversion ? `dumpfullversion=${probes.dumpfullversion}` : ""
|
|
241
|
+
]
|
|
242
|
+
.filter((s) => s.length > 0)
|
|
243
|
+
.join("\n");
|
|
244
|
+
return { hash, input, type, version };
|
|
245
|
+
});
|
|
143
246
|
}
|
|
144
247
|
class CompilerInfoCache {
|
|
145
248
|
constructor() {
|
|
@@ -151,7 +254,9 @@ class CompilerInfoCache {
|
|
|
151
254
|
if (typeof compilerPath !== "string" || compilerPath.length === 0) {
|
|
152
255
|
throw new Error("CompilerInfoCache.get: compilerPath must be a non-empty string");
|
|
153
256
|
}
|
|
154
|
-
|
|
257
|
+
// Resolve symlinks so that /usr/bin/clang and /usr/bin/clang-18
|
|
258
|
+
// (when the former is a symlink to the latter) share a cache entry.
|
|
259
|
+
const absPath = yield require$$1.promises.realpath(path__default["default"].resolve(compilerPath));
|
|
155
260
|
const stat = yield require$$1.promises.stat(absPath);
|
|
156
261
|
const key = `${absPath}:${stat.mtimeMs}`;
|
|
157
262
|
const cached = this.cache.get(key);
|
|
@@ -168,28 +273,20 @@ class CompilerInfoCache {
|
|
|
168
273
|
});
|
|
169
274
|
this.pending.set(key, compute);
|
|
170
275
|
// Clean up the pending map on both success and failure so a failed
|
|
171
|
-
// lookup doesn't wedge the key forever.
|
|
172
|
-
// the cleanup chain because the original rejection is already
|
|
173
|
-
// surfaced through the returned `compute` promise.
|
|
276
|
+
// lookup doesn't wedge the key forever.
|
|
174
277
|
compute
|
|
175
278
|
.finally(() => {
|
|
176
279
|
this.pending.delete(key);
|
|
177
280
|
})
|
|
178
281
|
.catch(() => {
|
|
179
|
-
/* rejection observed by caller via the returned
|
|
282
|
+
/* rejection observed by caller via the returned promise */
|
|
180
283
|
});
|
|
181
284
|
return compute;
|
|
182
285
|
});
|
|
183
286
|
}
|
|
184
287
|
static compute(absPath) {
|
|
185
288
|
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
-
|
|
187
|
-
timeout: 30000,
|
|
188
|
-
maxBuffer: 4 * 1024 * 1024
|
|
189
|
-
});
|
|
190
|
-
const combined = `${stdout}${stderr}`;
|
|
191
|
-
const filtered = filterOutput(combined);
|
|
192
|
-
return createCompilerInfo(absPath, filtered);
|
|
289
|
+
return createCompilerInfo(absPath);
|
|
193
290
|
});
|
|
194
291
|
}
|
|
195
292
|
}
|
package/package.json
CHANGED
|
@@ -57995,7 +57995,7 @@ const option = options({
|
|
|
57995
57995
|
const common = common$1(option);
|
|
57996
57996
|
let nextCommandId = 0;
|
|
57997
57997
|
const server = new Server(option, common.Version);
|
|
57998
|
-
const clientMinimumVersion = "
|
|
57998
|
+
const clientMinimumVersion = "5.0.6";
|
|
57999
57999
|
const serverStartTime = Date.now();
|
|
58000
58000
|
process.on("unhandledRejection", (reason, p) => {
|
|
58001
58001
|
console.error("Unhandled Rejection at: Promise", p, "reason:", reason === null || reason === void 0 ? void 0 : reason.stack);
|