返回 CodeWhale
preflight-glibc.js
根目录 / npm / codewhale / scripts / preflight-glibc.js
1 const fs = require("fs");
2 const { execFileSync } = require("child_process");
3
4 const GLIBC_VERSION_RE = /GLIBC_(\d+)\.(\d+)(?:\.(\d+))?/g;
5
6 function isLinux() {
7 return process.platform === "linux";
8 }
9
10 function parseVersion(text) {
11 const match = String(text || "").match(/(\d+)\.(\d+)(?:\.(\d+))?/);
12 if (!match) return null;
13 return [Number(match[1]), Number(match[2]), Number(match[3] || 0)];
14 }
15
16 function compareVersion(a, b) {
17 for (let i = 0; i < 3; i += 1) {
18 if (a[i] !== b[i]) return a[i] - b[i];
19 }
20 return 0;
21 }
22
23 function formatVersion(version) {
24 return version[2] ? `${version[0]}.${version[1]}.${version[2]}` : `${version[0]}.${version[1]}`;
25 }
26
27 function detectHostGlibc() {
28 try {
29 const out = execFileSync("getconf", ["GNU_LIBC_VERSION"], {
30 encoding: "utf8",
31 stdio: ["ignore", "pipe", "ignore"],
32 });
33 const version = parseVersion(out);
34 if (version) return version;
35 } catch {
36 // fall through to ldd
37 }
38 try {
39 const out = execFileSync("ldd", ["--version"], {
40 encoding: "utf8",
41 stdio: ["ignore", "pipe", "ignore"],
42 });
43 const firstLine = out.split("\n", 1)[0];
44 const version = parseVersion(firstLine);
45 if (version) return version;
46 } catch {
47 // glibc not present (e.g. musl / Alpine)
48 }
49 return null;
50 }
51
52 function detectBinaryRequiredGlibc(filePath) {
53 const buf = fs.readFileSync(filePath);
54 const text = buf.toString("latin1");
55 let highest = null;
56 GLIBC_VERSION_RE.lastIndex = 0;
57 let match;
58 while ((match = GLIBC_VERSION_RE.exec(text)) !== null) {
59 const version = [Number(match[1]), Number(match[2]), Number(match[3] || 0)];
60 if (!highest || compareVersion(version, highest) > 0) {
61 highest = version;
62 }
63 }
64 return highest;
65 }
66
67 function buildFromSourceHint() {
68 return [
69 "You can still run codewhale by building from source with Cargo:",
70 "",
71 " # Requires Rust 1.88+ (https://rustup.rs)",
72 " cargo install codewhale-cli --locked # provides `codewhale` and `codew`",
73 " cargo install codewhale-tui --locked # provides `codewhale-tui`",
74 "",
75 "Or build from a checkout:",
76 "",
77 " git clone https://github.com/Hmbown/CodeWhale.git",
78 " cd CodeWhale",
79 " cargo install --path crates/cli --locked",
80 " cargo install --path crates/tui --locked",
81 "",
82 "See https://github.com/Hmbown/CodeWhale/blob/main/docs/INSTALL.md",
83 ].join("\n");
84 }
85
86 function skipGlibcCheck() {
87 return (
88 process.env.CODEWHALE_SKIP_GLIBC_CHECK === "1" ||
89 process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK === "1" ||
90 process.env.DEEPSEEK_SKIP_GLIBC_CHECK === "1"
91 );
92 }
93
94 function glibcCompatibilityMessage(required, host) {
95 const hostLine = host
96 ? `this system has glibc ${formatVersion(host)}, which is too old for that asset.`
97 : "this system does not appear to provide GNU libc.";
98 return [
99 `Prebuilt Codewhale Linux binaries require GLIBC_${formatVersion(required)}, but ${hostLine}`,
100 "",
101 "The Linux x64 release asset is a static (musl) build that runs on any glibc,",
102 "but the Linux arm64 asset is a GNU libc build linked against",
103 "Ubuntu 24.04/glibc 2.39, which Ubuntu 22.04 (glibc 2.35) cannot run.",
104 "",
105 buildFromSourceHint(),
106 "",
107 "Set CODEWHALE_SKIP_GLIBC_CHECK=1 to bypass this check at your own risk.",
108 ].join("\n");
109 }
110
111 function preflightGlibc(filePath) {
112 if (!isLinux()) return;
113 if (skipGlibcCheck()) {
114 return;
115 }
116
117 const required = detectBinaryRequiredGlibc(filePath);
118 if (!required) {
119 // Statically linked / musl binary, or no GLIBC_* version dependencies present.
120 return;
121 }
122
123 const host = detectHostGlibc();
124 if (!host) {
125 throw new Error(glibcCompatibilityMessage(required, null));
126 }
127
128 if (compareVersion(host, required) < 0) {
129 throw new Error(glibcCompatibilityMessage(required, host));
130 }
131 }
132
133 module.exports = {
134 preflightGlibc,
135 detectHostGlibc,
136 detectBinaryRequiredGlibc,
137 // exported for tests
138 _internal: {
139 parseVersion,
140 compareVersion,
141 formatVersion,
142 glibcCompatibilityMessage,
143 skipGlibcCheck,
144 },
145 };
146
146 lines JAVASCRIPT