返回 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`",
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/Hmbown/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/Hmbown/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 GNU libc.";
100 return [
101 `Prebuilt Codewhale Linux binaries require GLIBC_${formatVersion(required)}, but ${hostLine}`,
102 "",
103 "The Linux x64 release asset is a static (musl) build that runs on any glibc,",
104 "but the Linux arm64 asset is a GNU libc build linked against",
105 "Ubuntu 24.04/glibc 2.39, which Ubuntu 22.04 (glibc 2.35) cannot run.",
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