signal-desktop/ts/scripts/check-min-os-version.ts

178 lines
4.6 KiB
TypeScript
Raw Normal View History

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { execFile as execFileCb } from 'node:child_process';
import { promisify } from 'node:util';
import { existsSync } from 'node:fs';
import { join, basename } from 'node:path';
import fastGlob from 'fast-glob';
import { gte } from 'semver';
// Note: because we don't run under electron - this is a path to binary
import ELECTRON_BINARY from 'electron';
import { drop } from '../util/drop.js';
import packageJson from '../util/packageJson.js';
const execFile = promisify(execFileCb);
// See https://en.wikipedia.org/wiki/Darwin_(operating_system)#Darwin_20_onwards
const MACOS_TO_DARWIN_VERSIONS = new Map([
// Big Sur
['11.0', '20.1.0'],
['11.1', '20.2.0'],
['11.2', '20.3.0'],
['11.3', '20.4.0'],
['11.4', '20.5.0'],
// Monterey
['12.0', '21.0.1'],
['12.0.1', '21.1.0'],
['12.1', '21.2.0'],
['12.2', '21.3.0'],
['12.3', '21.4.0'],
['12.4', '21.5.0'],
['12.5', '21.6.0'],
// Ventura
['13.0', '22.1.0'],
['13.1', '22.2.0'],
['13.2', '22.3.0'],
['13.3', '22.4.0'],
['13.4', '22.5.0'],
['13.5', '22.6.0'],
// Sonoma
['14.0', '23.0.0'],
['14.1', '23.1.0'],
['14.2', '23.2.0'],
['14.3', '23.3.0'],
['14.4', '23.4.0'],
['14.5', '23.5.0'],
// Sequoia
['15.0', '24.0.0'],
// Tahoe
['26.0', '25.0.0'],
]);
async function macosVersionCheck(file: string) {
console.log(`${file}: checking...`);
const { stdout } = await execFile('otool', ['-l', file]);
const match = stdout.match(/minos\s+([\d.]+)/);
if (match == null) {
throw new Error(`Failed to detect min OS version of ${file}`);
}
const [, macosVersion] = match;
const darwinVersion = MACOS_TO_DARWIN_VERSIONS.get(macosVersion);
if (darwinVersion == null) {
throw new Error(`No matching darwin version for macOS ${macosVersion}`);
}
const minSupported = packageJson.build.mac.releaseInfo.vendor.minOSVersion;
if (gte(minSupported, darwinVersion)) {
console.log(`${file}: required version ${darwinVersion}`);
return;
}
throw new Error(
`${basename(file)} minimum darwin version is ${darwinVersion} ` +
`(macOS ${macosVersion}), but package.json has ${minSupported}`
);
}
function padGlibcVersion(version: string) {
if (/^\d+\.\d+$/.test(version)) {
return `${version}.0`;
}
if (/^\d+\.\d+\.\d+$/.test(version)) {
return version;
}
throw new Error(`Unsupported glibc version: ${version}`);
}
async function linuxVersionCheck(file: string) {
if (!existsSync(file)) {
console.log(`${file}: skipping`);
return;
}
console.log(`${file}: checking...`);
const { stdout } = await execFile('objdump', ['-T', file], {
maxBuffer: 100 * 1024 * 1024,
});
let minGlibcVersion: string | undefined;
for (const [, unpaddedVersion] of stdout.matchAll(/GLIBC_([\d.]+)/g)) {
const glibcVersion = padGlibcVersion(unpaddedVersion);
if (minGlibcVersion == null || gte(glibcVersion, minGlibcVersion)) {
minGlibcVersion = glibcVersion;
}
}
if (minGlibcVersion == null) {
throw new Error(`Failed to detect glibc versions of ${file}`);
}
const libc6Dependency = packageJson.build.deb.depends.find(req =>
req.startsWith('libc6 ')
);
if (libc6Dependency == null) {
throw new Error('Missing libc6 dependency in package.json');
}
const match = libc6Dependency.match(/^libc6 \(>= ([\d.]+)\)$/);
if (match == null) {
throw new Error(
`Invalid libc6 dependency in package.json, ${libc6Dependency}`
);
}
const minSupported = padGlibcVersion(match[1]);
if (gte(minSupported, minGlibcVersion)) {
console.log(`${file}: required version ${minGlibcVersion}`);
return;
}
throw new Error(
`${basename(file)} minimum GLIBC version is ${minGlibcVersion} ` +
`but package.json has ${libc6Dependency}`
);
}
async function main() {
const BINARY_FILES = [
ELECTRON_BINARY as unknown as string,
...(await fastGlob(
packageJson.build.files
.filter((p: unknown): p is string => typeof p === 'string')
.filter(p => p.endsWith('.node'))
.map(p => p.replace(/\${platform}/, process.platform))
.map(p => p.replace(/\${arch}/, process.arch)),
{
absolute: true,
onlyFiles: true,
cwd: join(__dirname, '..', '..'),
}
)),
];
if (process.platform === 'darwin') {
for (const file of BINARY_FILES) {
// eslint-disable-next-line no-await-in-loop
await macosVersionCheck(file);
}
} else if (process.platform === 'linux') {
for (const file of BINARY_FILES) {
// eslint-disable-next-line no-await-in-loop
await linuxVersionCheck(file);
}
}
}
drop(main());