返回 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.89+ (https://rustup.rs)",
72 " cargo install codewhale-cli --locked # provides `codewhale`",
73 " bin=$(dirname \"$(command -v codewhale)\")",
74 " ln -sf \"$bin/codewhale\" \"$bin/codew\" # optional short alias",
75 "",
76 "Or build from a checkout:",
77 "",
78 " git clone https://github.com/codewhale-hq/CodeWhale.git",
79 " cd CodeWhale",
80 " cargo install --path crates/cli --locked",
81 " bin=$(dirname \"$(command -v codewhale)\")",
82 " ln -sf \"$bin/codewhale\" \"$bin/codew\"",
83 "",
84 "See https://github.com/codewhale-hq/CodeWhale/blob/main/docs/INSTALL.md",
85 ].join("\n");
86 }
87
88 function skipGlibcCheck() {
89 return (
90 process.env.CODEWHALE_SKIP_GLIBC_CHECK === "1" ||
91 process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK === "1" ||
92 process.env.DEEPSEEK_SKIP_GLIBC_CHECK === "1"
93 );
94 }
95
96 function glibcCompatibilityMessage(required, host) {
97 const hostLine = host
98 ? `this system has glibc ${formatVersion(host)}, which is too old for that asset.`
99 : "this system does not appear to provide glibc.";
100 return [
101 `This Codewhale binary requires GLIBC_${formatVersion(required)}, but ${hostLine}`,
102 "",
103 "Official Codewhale Linux release assets (x64 and arm64) are static musl builds",
104 "with no glibc dependency, so this binary is not an official release asset.",
105 "Check where it came from, or build from source on this host.",
106 "",
107 buildFromSourceHint(),
108 "",
109 "Set CODEWHALE_SKIP_GLIBC_CHECK=1 to bypass this check at your own risk.",
110 ].join("\n");
111 }
112
113 function preflightGlibc(filePath) {
114 if (!isLinux()) return;
115 if (skipGlibcCheck()) {
116 return;
117 }
118
119 const required = detectBinaryRequiredGlibc(filePath);
120 if (!required) {
121 // Statically linked / musl binary, or no GLIBC_* version dependencies present.
122 return;
123 }
124
125 const host = detectHostGlibc();
126 if (!host) {
127 throw new Error(glibcCompatibilityMessage(required, null));
128 }
129
130 if (compareVersion(host, required) < 0) {
131 throw new Error(glibcCompatibilityMessage(required, host));
132 }
133 }
134
135 module.exports = {
136 preflightGlibc,
137 detectHostGlibc,
138 detectBinaryRequiredGlibc,
139 // exported for tests
140 _internal: {
141 parseVersion,
142 compareVersion,
143 formatVersion,
144 glibcCompatibilityMessage,
145 skipGlibcCheck,
146 },
147 };
148
148 lines JAVASCRIPT