@oxiaom/adoremix 1.0.22 → 1.0.24

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/package.json +1 -1
  2. package/src/doctor.js +115 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxiaom/adoremix",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "AdoreMix broadcast server - cross-platform installer, runner and service manager",
5
5
  "bin": {
6
6
  "adoremix": "./bin/adoremix.js"
package/src/doctor.js CHANGED
@@ -77,6 +77,65 @@ function tryFixIcu70() {
77
77
  }
78
78
  }
79
79
 
80
+ // ---- 版本兼容性检查辅助 ----
81
+
82
+ // "3.4.29" -> [3,4,29](数值比较用,避免 "2.34" < "2.3.4" 之类的字符串排序陷阱)
83
+ function parseVer(str) {
84
+ if (typeof str !== 'string') return null;
85
+ const m = str.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
86
+ if (!m) return null;
87
+ return [Number(m[1]), Number(m[2] || 0), Number(m[3] || 0)];
88
+ }
89
+
90
+ // a>b→1, a<b→-1, 相等→0
91
+ function compareVer(a, b) {
92
+ const va = parseVer(a), vb = parseVer(b);
93
+ if (!va || !vb) return 0;
94
+ for (let i = 0; i < 3; i++) {
95
+ if (va[i] > vb[i]) return 1;
96
+ if (va[i] < vb[i]) return -1;
97
+ }
98
+ return 0;
99
+ }
100
+
101
+ // 读文件找出引用的最高符号版本,如 maxSymVer(bin, 'GLIBCXX') -> '3.4.29'。
102
+ // 纯 node 实现(latin1 读 + 正则扫 .dynstr),不依赖 strings/grep/sort 等外部工具。
103
+ function maxSymVer(file, prefix) {
104
+ try {
105
+ const s = fs.readFileSync(file).toString('latin1');
106
+ const re = new RegExp(prefix + '_\\d+(?:\\.\\d+){1,2}', 'g');
107
+ const found = [];
108
+ let m;
109
+ while ((m = re.exec(s)) !== null) found.push(m[0].slice(prefix.length + 1));
110
+ if (!found.length) return null;
111
+ found.sort(compareVer);
112
+ return found[found.length - 1];
113
+ } catch (e) {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ // 一组文件(二进制 + 捆绑 Qt 库)中引用的最高符号版本 = 整包对该符号版本的最低需求
119
+ function requiredSymVers(files, prefix) {
120
+ let max = null;
121
+ for (const f of files) {
122
+ if (!f || !fs.existsSync(f)) continue;
123
+ const v = maxSymVer(f, prefix);
124
+ if (v && (!max || compareVer(v, max) > 0)) max = v;
125
+ }
126
+ return max;
127
+ }
128
+
129
+ // 解析 ldd 输出:lib名 -> 绝对路径(not found 记为 null)
130
+ function parseLdd(lddOut) {
131
+ const resolved = {};
132
+ for (const line of lddOut.split('\n')) {
133
+ const m = line.match(/^\s*(\S+)\s+=>\s+(\S+)/);
134
+ if (m) resolved[m[1]] = m[2] === 'not found' ? null : m[2];
135
+ }
136
+ return resolved;
137
+ }
138
+
80
139
  function runDoctor(workdir, opts) {
81
140
  opts = opts || {};
82
141
  logger.log('=== AdoreMix 健康检查 ===');
@@ -135,11 +194,8 @@ function runDoctor(workdir, opts) {
135
194
  const checkBin = fs.existsSync(binPath) ? binPath : nativeBinPath;
136
195
  try {
137
196
  const ldd = execSync(`ldd "${checkBin}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
138
- const missing = [];
139
- for (const line of ldd.split('\n')) {
140
- const m = line.match(/^\s*(\S+)\s*=>\s*not found/);
141
- if (m) missing.push(m[1]);
142
- }
197
+ const resolved = parseLdd(ldd);
198
+ const missing = Object.keys(resolved).filter(k => resolved[k] === null);
143
199
  if (missing.length === 0) {
144
200
  logger.ok(`✓ 动态库依赖完整`);
145
201
  } else {
@@ -208,6 +264,60 @@ function runDoctor(workdir, opts) {
208
264
  issues.push({ type: 'libs-unknown', severity: 'error', msg: unknown.join(', ') });
209
265
  }
210
266
  }
267
+ // ---- 版本兼容性检查(所有依赖库的版本与匹配)----
268
+ const libDir = path.join(path.dirname(checkBin), 'lib');
269
+ const qtLibs = [];
270
+ try {
271
+ for (const f of fs.readdirSync(libDir)) {
272
+ if (/^libQt5.*\.so\.\d/.test(f)) qtLibs.push(path.join(libDir, f));
273
+ }
274
+ } catch (e) { /* lib 目录不存在则跳过捆绑 Qt 扫描 */ }
275
+ const scanFiles = [checkBin].concat(qtLibs);
276
+
277
+ // A. libstdc++ GLIBCXX 版本(捆绑 Qt 5.15 需 GCC 9 时代符号,实测 GLIBCXX_3.4.29)
278
+ const reqCxx = requiredSymVers(scanFiles, 'GLIBCXX');
279
+ const stdcppPath = resolved['libstdc++.so.6'];
280
+ if (reqCxx && stdcppPath) {
281
+ const provCxx = maxSymVer(stdcppPath, 'GLIBCXX');
282
+ if (provCxx && compareVer(provCxx, reqCxx) < 0) {
283
+ issues.push({ type: 'libstdcxx-version', severity: 'error', msg: `系统 libstdc++ 太旧:需 GLIBCXX_${reqCxx},实际只有 GLIBCXX_${provCxx}。运行会 undefined symbol 崩溃,需 Ubuntu 20.04+(libstdc++6 升级),无法自动降级。` });
284
+ logger.error(`❌ libstdc++ 版本不兼容:需 GLIBCXX_${reqCxx},实际 GLIBCXX_${provCxx}`);
285
+ } else if (provCxx) {
286
+ logger.ok(`✓ libstdc++ GLIBCXX_${provCxx}(需 >= ${reqCxx})`);
287
+ }
288
+ }
289
+
290
+ // B. glibc GLIBC 版本(二进制在 Ubuntu 22.04 编译,实测需 GLIBC_2.34)
291
+ const reqGlibc = requiredSymVers(scanFiles, 'GLIBC');
292
+ const libcPath = resolved['libc.so.6'];
293
+ if (reqGlibc && libcPath) {
294
+ const provGlibc = maxSymVer(libcPath, 'GLIBC');
295
+ if (provGlibc && compareVer(provGlibc, reqGlibc) < 0) {
296
+ issues.push({ type: 'glibc-version', severity: 'error', msg: `系统 glibc 太旧:需 GLIBC_${reqGlibc},实际 GLIBC_${provGlibc}。二进制在 Ubuntu 22.04 编译,需 Ubuntu 22.04+。` });
297
+ logger.error(`❌ glibc 版本不兼容:需 GLIBC_${reqGlibc},实际 GLIBC_${provGlibc}`);
298
+ } else if (provGlibc) {
299
+ logger.ok(`✓ glibc GLIBC_${provGlibc}(需 >= ${reqGlibc})`);
300
+ }
301
+ }
302
+
303
+ // C. Qt 捆绑一致性:必须加载捆绑 lib/ 里的 Qt,不能加载系统 Qt(版本可能不匹配)
304
+ const qtSonames = Object.keys(resolved).filter(k => /^libQt5.*\.so\.5$/.test(k));
305
+ const libDirNorm = path.normalize(libDir);
306
+ for (const so of qtSonames) {
307
+ const rp = resolved[so];
308
+ if (!rp) {
309
+ issues.push({ type: 'qt-missing', severity: 'error', msg: `${so} 未能解析,捆绑 Qt 加载失败` });
310
+ logger.error(`❌ ${so} 未解析(捆绑 Qt 加载失败)`);
311
+ } else if (!path.normalize(rp).startsWith(libDirNorm + path.sep)) {
312
+ issues.push({ type: 'qt-bundle', severity: 'error', msg: `${so} 加载系统 Qt:${rp}(应为捆绑 ${libDir}),版本可能不匹配` });
313
+ logger.error(`❌ ${so} => ${rp}(非捆绑 Qt)`);
314
+ } else {
315
+ logger.ok(`✓ ${so} => 捆绑 ${path.basename(rp)}`);
316
+ }
317
+ }
318
+
319
+ // D. 依赖解析统计(信息性)
320
+ logger.log(` 已解析依赖 ${Object.keys(resolved).length} 项,缺库 ${missing.length} 项`);
211
321
  } catch (e) {
212
322
  logger.warn(`⚠ ldd 检查失败(非 Linux 或权限问题):${e.message.split('\n')[0]}`);
213
323
  }