mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(embedded-mpv): make Linux runtime portable
This commit is contained in:
1 parent
84ff8de04a
commit
904fc6d3ec
5 files changed
+1584
-69
No files matched your search
@@ -94,6 +94,16 @@ const SOURCE_PACKAGES = Object.freeze(
|
||||
expectedGitCommit: 'cee9b076f2c63104ccfd497fa79c39a867293ec4',
|
||||
license: 'LGPL-2.1-or-later',
|
||||
},
|
||||
{
|
||||
id: 'libdisplay-info',
|
||||
version: '0.1.1',
|
||||
sourceKind: 'archive',
|
||||
sourceUrl:
|
||||
'https://gitlab.freedesktop.org/emersion/libdisplay-info/-/releases/0.1.1/downloads/libdisplay-info-0.1.1.tar.xz',
|
||||
expectedSha256:
|
||||
'0d8731588e9f82a9cac96324a3d7c82e2ba5b1b5e006143fefe692c74069fb60',
|
||||
license: 'MIT',
|
||||
},
|
||||
{
|
||||
id: 'mpv',
|
||||
version: '0.41.0',
|
||||
@@ -203,7 +213,7 @@ const MPV_MESON_FLAGS = Object.freeze([
|
||||
'-Dd3d11=disabled',
|
||||
'-Ddirect3d=disabled',
|
||||
'-Ddmabuf-wayland=disabled',
|
||||
'-Ddrm=disabled',
|
||||
'-Ddrm=enabled',
|
||||
'-Degl=enabled',
|
||||
'-Degl-android=disabled',
|
||||
'-Degl-angle=disabled',
|
||||
@@ -318,6 +328,12 @@ const BUILD_RECIPES = Object.freeze({
|
||||
'-Dtools=disabled',
|
||||
'-Dcache-build=disabled',
|
||||
'-Dnls=disabled',
|
||||
'-Dxml-backend=expat',
|
||||
'-Dbaseconfig-dir=/etc/fonts',
|
||||
'-Dconfig-dir=/etc/fonts/conf.d',
|
||||
'-Dtemplate-dir=/usr/share/fontconfig/conf.avail',
|
||||
'-Dcache-dir=/var/cache/fontconfig',
|
||||
'-Dxml-dir=/usr/share/xml/fontconfig',
|
||||
]),
|
||||
}),
|
||||
libass: Object.freeze({
|
||||
@@ -344,6 +360,7 @@ const BUILD_RECIPES = Object.freeze({
|
||||
'no-legacy',
|
||||
'no-module',
|
||||
'no-weak-ssl-ciphers',
|
||||
'--openssldir=/etc/ssl',
|
||||
]),
|
||||
}),
|
||||
ffmpeg: Object.freeze({
|
||||
@@ -371,6 +388,11 @@ const BUILD_RECIPES = Object.freeze({
|
||||
'-Dxxhash=disabled',
|
||||
]),
|
||||
}),
|
||||
'libdisplay-info': Object.freeze({
|
||||
buildSystem: 'meson',
|
||||
sharedOnly: true,
|
||||
args: Object.freeze([]),
|
||||
}),
|
||||
mpv: Object.freeze({
|
||||
buildSystem: 'meson',
|
||||
sharedOnly: true,
|
||||
@@ -385,6 +407,7 @@ const REQUIRED_TOOLS = Object.freeze([
|
||||
'git',
|
||||
'make',
|
||||
'meson',
|
||||
'nasm',
|
||||
'ninja',
|
||||
'patchelf',
|
||||
'perl',
|
||||
@@ -394,6 +417,23 @@ const REQUIRED_TOOLS = Object.freeze([
|
||||
'tar',
|
||||
]);
|
||||
|
||||
const MINIMUM_TOOL_VERSIONS = Object.freeze({
|
||||
cc: '9.0.0',
|
||||
cmake: '3.16.0',
|
||||
curl: '7.71.0',
|
||||
git: '2.30.0',
|
||||
make: '4.0.0',
|
||||
meson: '1.6.0',
|
||||
nasm: '2.15.05',
|
||||
ninja: '1.10.0',
|
||||
patchelf: '0.14.0',
|
||||
perl: '5.30.0',
|
||||
'pkg-config': '0.29.0',
|
||||
python3: '3.8.0',
|
||||
readelf: '2.35.0',
|
||||
tar: '1.30.0',
|
||||
});
|
||||
|
||||
const DEFAULT_SYSTEM_PKG_CONFIG_DIRS = Object.freeze([
|
||||
'/usr/lib/x86_64-linux-gnu/pkgconfig',
|
||||
'/usr/lib64/pkgconfig',
|
||||
@@ -406,6 +446,7 @@ const EXPECTED_SYSTEM_PKG_CONFIG_PACKAGES = Object.freeze([
|
||||
'egl',
|
||||
'gbm',
|
||||
'gl',
|
||||
'libdrm',
|
||||
'libpulse',
|
||||
'libva',
|
||||
'libva-drm',
|
||||
@@ -477,6 +518,31 @@ const EXTERNAL_SYSTEM_LIBRARIES = Object.freeze(
|
||||
].map((externalLibrary) => Object.freeze(externalLibrary))
|
||||
);
|
||||
|
||||
const RUNTIME_EXTERNAL_CONFIGURATION = Object.freeze({
|
||||
fontconfig: Object.freeze({
|
||||
configDirectory: '/etc/fonts',
|
||||
templateDirectory: '/usr/share/fontconfig',
|
||||
cacheDirectory: '/var/cache/fontconfig',
|
||||
ownership: 'system',
|
||||
}),
|
||||
openssl: Object.freeze({
|
||||
configFile: '/etc/ssl/openssl.cnf',
|
||||
certificateFile: '/etc/ssl/cert.pem',
|
||||
certificateDirectory: '/etc/ssl/certs',
|
||||
ownership: 'system',
|
||||
}),
|
||||
});
|
||||
|
||||
const OUTPUT_OWNERSHIP_MARKER = '.iptvnator-linux-runtime-owner';
|
||||
const OUTPUT_OWNERSHIP_MARKER_CONTENT =
|
||||
'iptvnator-embedded-mpv-linux-runtime-v1\n';
|
||||
|
||||
const PORTABLE_ABI_BASELINE = Object.freeze({
|
||||
distribution: 'Ubuntu 22.04',
|
||||
glibcMaximum: '2.35',
|
||||
glibcxxMaximum: '3.4.30',
|
||||
});
|
||||
|
||||
const SHARED_LIBRARY_PATTERN = /\.so(?:\.\d+)*$/;
|
||||
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
||||
const externalSystemLibraryNames = new Set(
|
||||
@@ -503,6 +569,217 @@ function assertGitCommitMatchesPin(sourcePackage, actualGitCommit) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseVersion(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const match = value.match(/\b(\d+\.\d+(?:\.\d+)*)\b/);
|
||||
return match?.[1] ?? null;
|
||||
}
|
||||
|
||||
function compareVersions(left, right) {
|
||||
const leftParts = left.split('.').map(Number);
|
||||
const rightParts = right.split('.').map(Number);
|
||||
const length = Math.max(leftParts.length, rightParts.length);
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
||||
if (difference !== 0) {
|
||||
return Math.sign(difference);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function assertMinimumToolVersions(toolVersions) {
|
||||
for (const tool of REQUIRED_TOOLS) {
|
||||
const declaredVersion = toolVersions?.[tool];
|
||||
if (typeof declaredVersion !== 'string' || !declaredVersion.trim()) {
|
||||
throw new Error(`Missing required tool version for ${tool}.`);
|
||||
}
|
||||
const actualVersion = parseVersion(declaredVersion);
|
||||
if (!actualVersion) {
|
||||
throw new Error(
|
||||
`Unable to parse required tool version for ${tool}: ${declaredVersion}.`
|
||||
);
|
||||
}
|
||||
const minimumVersion = MINIMUM_TOOL_VERSIONS[tool];
|
||||
if (compareVersions(actualVersion, minimumVersion) < 0) {
|
||||
throw new Error(
|
||||
`${tool} ${actualVersion} is unsupported; ${tool} requires ${minimumVersion} or newer for Linux runtime builds.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertUniqueMesonOptionAssignments(buildRecipes) {
|
||||
if (!buildRecipes || typeof buildRecipes !== 'object') {
|
||||
throw new TypeError('Linux runtime build recipes must be an object.');
|
||||
}
|
||||
for (const [packageId, recipe] of Object.entries(buildRecipes)) {
|
||||
if (recipe?.buildSystem !== 'meson') {
|
||||
continue;
|
||||
}
|
||||
if (!Array.isArray(recipe.args)) {
|
||||
throw new Error(
|
||||
`${packageId} Meson recipe must declare an argument array.`
|
||||
);
|
||||
}
|
||||
|
||||
const assignmentsByOption = new Map();
|
||||
for (const flag of recipe.args) {
|
||||
const match =
|
||||
typeof flag === 'string' ? flag.match(/^(-D[^=]+)=/) : null;
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
const option = match[1];
|
||||
const assignmentCount = (assignmentsByOption.get(option) ?? 0) + 1;
|
||||
assignmentsByOption.set(option, assignmentCount);
|
||||
if (assignmentCount > 1) {
|
||||
throw new Error(
|
||||
`${packageId} Meson recipe must assign ${option} exactly once.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lstatIfExists(fileSystem, filePath) {
|
||||
try {
|
||||
return fileSystem.lstatSync(filePath);
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function assertOwnedOutputDestination(outputPrefix, fileSystem = fs) {
|
||||
const outputStat = lstatIfExists(fileSystem, outputPrefix);
|
||||
if (!outputStat) {
|
||||
return;
|
||||
}
|
||||
if (!outputStat.isDirectory() || outputStat.isSymbolicLink()) {
|
||||
throw new Error(
|
||||
`Existing output ${outputPrefix} must be a non-symbolic-link directory carrying the IPTVnator ownership marker.`
|
||||
);
|
||||
}
|
||||
|
||||
const markerPath = path.join(outputPrefix, OUTPUT_OWNERSHIP_MARKER);
|
||||
const markerStat = lstatIfExists(fileSystem, markerPath);
|
||||
if (
|
||||
!markerStat ||
|
||||
!markerStat.isFile() ||
|
||||
markerStat.isSymbolicLink() ||
|
||||
fileSystem.readFileSync(markerPath, 'utf8') !==
|
||||
OUTPUT_OWNERSHIP_MARKER_CONTENT
|
||||
) {
|
||||
throw new Error(
|
||||
`Existing output ${outputPrefix} is missing the valid IPTVnator ownership marker.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function ownedStagingPrefixPath(outputPrefix, token) {
|
||||
return path.join(
|
||||
path.dirname(outputPrefix),
|
||||
`.${path.basename(outputPrefix)}.iptvnator-stage-${token}`
|
||||
);
|
||||
}
|
||||
|
||||
function createOwnedStagingPrefix(
|
||||
outputPrefix,
|
||||
{ fileSystem = fs, token = crypto.randomBytes(8).toString('hex') } = {}
|
||||
) {
|
||||
assertOwnedOutputDestination(outputPrefix, fileSystem);
|
||||
const outputParent = path.dirname(outputPrefix);
|
||||
const stagingPrefix = ownedStagingPrefixPath(outputPrefix, token);
|
||||
if (lstatIfExists(fileSystem, stagingPrefix)) {
|
||||
throw new Error(
|
||||
`Refusing to reuse existing Linux runtime staging path ${stagingPrefix}.`
|
||||
);
|
||||
}
|
||||
|
||||
fileSystem.mkdirSync(outputParent, { recursive: true });
|
||||
fileSystem.mkdirSync(stagingPrefix);
|
||||
try {
|
||||
fileSystem.writeFileSync(
|
||||
path.join(stagingPrefix, OUTPUT_OWNERSHIP_MARKER),
|
||||
OUTPUT_OWNERSHIP_MARKER_CONTENT,
|
||||
{ mode: 0o644 }
|
||||
);
|
||||
} catch (error) {
|
||||
fileSystem.rmSync(stagingPrefix, { recursive: true, force: true });
|
||||
throw error;
|
||||
}
|
||||
return stagingPrefix;
|
||||
}
|
||||
|
||||
function publishOwnedOutput({
|
||||
outputPrefix,
|
||||
stagingPrefix,
|
||||
fileSystem = fs,
|
||||
token = crypto.randomBytes(8).toString('hex'),
|
||||
}) {
|
||||
assertOwnedOutputDestination(outputPrefix, fileSystem);
|
||||
assertOwnedOutputDestination(stagingPrefix, fileSystem);
|
||||
if (!lstatIfExists(fileSystem, stagingPrefix)) {
|
||||
throw new Error(
|
||||
`Linux runtime staging prefix does not exist: ${stagingPrefix}.`
|
||||
);
|
||||
}
|
||||
|
||||
const backupPrefix = path.join(
|
||||
path.dirname(outputPrefix),
|
||||
`.${path.basename(outputPrefix)}.iptvnator-backup-${token}`
|
||||
);
|
||||
if (lstatIfExists(fileSystem, backupPrefix)) {
|
||||
throw new Error(
|
||||
`Refusing to reuse existing Linux runtime backup path ${backupPrefix}.`
|
||||
);
|
||||
}
|
||||
|
||||
let movedPreviousOutput = false;
|
||||
let published = false;
|
||||
try {
|
||||
if (lstatIfExists(fileSystem, outputPrefix)) {
|
||||
fileSystem.renameSync(outputPrefix, backupPrefix);
|
||||
movedPreviousOutput = true;
|
||||
}
|
||||
fileSystem.renameSync(stagingPrefix, outputPrefix);
|
||||
published = true;
|
||||
if (movedPreviousOutput) {
|
||||
fileSystem.rmSync(backupPrefix, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
movedPreviousOutput &&
|
||||
!lstatIfExists(fileSystem, outputPrefix) &&
|
||||
lstatIfExists(fileSystem, backupPrefix)
|
||||
) {
|
||||
fileSystem.renameSync(backupPrefix, outputPrefix);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
if (lstatIfExists(fileSystem, stagingPrefix)) {
|
||||
fileSystem.rmSync(stagingPrefix, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
if (published && lstatIfExists(fileSystem, backupPrefix)) {
|
||||
fileSystem.rmSync(backupPrefix, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function joinEnvironmentParts(parts, separator = ' ') {
|
||||
return parts.filter((value) => value && value.trim()).join(separator);
|
||||
}
|
||||
@@ -625,9 +902,12 @@ function assertPathInside(parentPath, candidatePath, label) {
|
||||
}
|
||||
}
|
||||
|
||||
function materializeLibrarySymlinks(libDir) {
|
||||
function materializeLibrarySymlinks(libDir, selectedNames = null) {
|
||||
const realLibDir = fs.realpathSync(libDir);
|
||||
for (const name of runtimeLibraryNames(libDir)) {
|
||||
if (selectedNames && !selectedNames.has(name)) {
|
||||
continue;
|
||||
}
|
||||
const libraryPath = path.join(libDir, name);
|
||||
const stat = fs.lstatSync(libraryPath);
|
||||
if (!stat.isSymbolicLink()) {
|
||||
@@ -654,6 +934,109 @@ function materializeLibrarySymlinks(libDir) {
|
||||
}
|
||||
}
|
||||
|
||||
function selectReachableRuntimeLibraryNames(entries) {
|
||||
if (!Array.isArray(entries)) {
|
||||
throw new TypeError('Runtime dynamic entries must be an array.');
|
||||
}
|
||||
|
||||
const entriesByName = new Map();
|
||||
for (const entry of entries) {
|
||||
if (
|
||||
!entry ||
|
||||
typeof entry.name !== 'string' ||
|
||||
!SHARED_LIBRARY_PATTERN.test(entry.name)
|
||||
) {
|
||||
throw new Error(
|
||||
'Runtime dynamic entry has an invalid library name.'
|
||||
);
|
||||
}
|
||||
if (entriesByName.has(entry.name)) {
|
||||
throw new Error(
|
||||
`Runtime dynamic entries contain duplicate library ${entry.name}.`
|
||||
);
|
||||
}
|
||||
entriesByName.set(entry.name, entry);
|
||||
}
|
||||
|
||||
const linkerAlias = entriesByName.get('libmpv.so');
|
||||
if (!linkerAlias) {
|
||||
throw new Error(
|
||||
'Linux runtime must contain the libmpv.so linker alias.'
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof linkerAlias.soname !== 'string' ||
|
||||
!SHARED_LIBRARY_PATTERN.test(linkerAlias.soname) ||
|
||||
!entriesByName.has(linkerAlias.soname)
|
||||
) {
|
||||
throw new Error(
|
||||
'libmpv.so must declare a bundled SONAME before runtime pruning.'
|
||||
);
|
||||
}
|
||||
|
||||
const reachableNames = new Set(['libmpv.so']);
|
||||
const pendingNames = [linkerAlias.soname];
|
||||
while (pendingNames.length > 0) {
|
||||
const libraryName = pendingNames.shift();
|
||||
if (reachableNames.has(libraryName)) {
|
||||
continue;
|
||||
}
|
||||
reachableNames.add(libraryName);
|
||||
const entry = entriesByName.get(libraryName);
|
||||
if (!entry) {
|
||||
throw new Error(
|
||||
`Reachable runtime library ${libraryName} is missing its dynamic entry.`
|
||||
);
|
||||
}
|
||||
for (const neededName of entry.needed ?? []) {
|
||||
if (
|
||||
entriesByName.has(neededName) &&
|
||||
!reachableNames.has(neededName)
|
||||
) {
|
||||
pendingNames.push(neededName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...reachableNames].sort();
|
||||
}
|
||||
|
||||
function retainRuntimeLibraries(libDir, retainedNames) {
|
||||
if (!Array.isArray(retainedNames) || retainedNames.length === 0) {
|
||||
throw new Error('Runtime retention list must be a non-empty array.');
|
||||
}
|
||||
const retainedNameSet = new Set(retainedNames);
|
||||
if (retainedNameSet.size !== retainedNames.length) {
|
||||
throw new Error('Runtime retention list contains duplicate libraries.');
|
||||
}
|
||||
|
||||
const availableNames = runtimeLibraryNames(libDir);
|
||||
for (const retainedName of retainedNameSet) {
|
||||
if (!availableNames.includes(retainedName)) {
|
||||
throw new Error(
|
||||
`Retained runtime library does not exist: ${retainedName}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
materializeLibrarySymlinks(libDir, retainedNameSet);
|
||||
for (const libraryName of availableNames) {
|
||||
if (!retainedNameSet.has(libraryName)) {
|
||||
fs.rmSync(path.join(libDir, libraryName));
|
||||
}
|
||||
}
|
||||
|
||||
for (const retainedName of retainedNameSet) {
|
||||
const retainedPath = path.join(libDir, retainedName);
|
||||
const stat = fs.lstatSync(retainedPath);
|
||||
if (!stat.isFile() || stat.isSymbolicLink()) {
|
||||
throw new Error(
|
||||
`Retained runtime library ${retainedName} must be a materialized regular file.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createRuntimeFileRecords(libDir) {
|
||||
return runtimeLibraryNames(libDir).map((name) => {
|
||||
const libraryPath = path.join(libDir, name);
|
||||
@@ -677,10 +1060,16 @@ function parseReadelfDynamic(output) {
|
||||
needed: [],
|
||||
rpath: [],
|
||||
runpath: [],
|
||||
soname: null,
|
||||
};
|
||||
const dynamicEntryPattern = /\((NEEDED|RPATH|RUNPATH)\)[^[]*\[([^\]]*)\]/g;
|
||||
const dynamicEntryPattern =
|
||||
/\((NEEDED|RPATH|RUNPATH|SONAME)\)[^[]*\[([^\]]*)\]/g;
|
||||
for (const match of output.matchAll(dynamicEntryPattern)) {
|
||||
const [, tag, value] = match;
|
||||
if (tag === 'SONAME') {
|
||||
dynamic.soname = value;
|
||||
continue;
|
||||
}
|
||||
if (tag === 'NEEDED') {
|
||||
dynamic.needed.push(value);
|
||||
continue;
|
||||
@@ -691,12 +1080,75 @@ function parseReadelfDynamic(output) {
|
||||
);
|
||||
}
|
||||
|
||||
for (const field of Object.keys(dynamic)) {
|
||||
for (const field of ['needed', 'rpath', 'runpath']) {
|
||||
dynamic[field] = [...new Set(dynamic[field])].sort();
|
||||
}
|
||||
return dynamic;
|
||||
}
|
||||
|
||||
function parseReadelfVersionInfo(output, name) {
|
||||
if (typeof output !== 'string' || typeof name !== 'string' || !name) {
|
||||
throw new TypeError(
|
||||
'readelf version output and runtime library name are required.'
|
||||
);
|
||||
}
|
||||
|
||||
let requiredGlibc = null;
|
||||
let requiredGlibcxx = null;
|
||||
const versionPattern = /\b(GLIBCXX|GLIBC)_(\d+(?:\.\d+)+)\b/g;
|
||||
for (const [, namespace, version] of output.matchAll(versionPattern)) {
|
||||
if (
|
||||
namespace === 'GLIBC' &&
|
||||
(!requiredGlibc || compareVersions(version, requiredGlibc) > 0)
|
||||
) {
|
||||
requiredGlibc = version;
|
||||
}
|
||||
if (
|
||||
namespace === 'GLIBCXX' &&
|
||||
(!requiredGlibcxx || compareVersions(version, requiredGlibcxx) > 0)
|
||||
) {
|
||||
requiredGlibcxx = version;
|
||||
}
|
||||
}
|
||||
|
||||
return { name, requiredGlibc, requiredGlibcxx };
|
||||
}
|
||||
|
||||
function assertPortableAbiRecords(records) {
|
||||
if (!Array.isArray(records)) {
|
||||
throw new TypeError('Runtime ABI records must be an array.');
|
||||
}
|
||||
for (const record of records) {
|
||||
for (const [field, maximum] of [
|
||||
['requiredGlibc', PORTABLE_ABI_BASELINE.glibcMaximum],
|
||||
['requiredGlibcxx', PORTABLE_ABI_BASELINE.glibcxxMaximum],
|
||||
]) {
|
||||
const version = record?.[field];
|
||||
if (version && compareVersions(version, maximum) > 0) {
|
||||
throw new Error(
|
||||
`Portable ABI baseline ${PORTABLE_ABI_BASELINE.distribution} rejects newer symbol ${version} required by ${record.name}; maximum ${field} is ${maximum}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertPortableBuildHostGlibc(glibcVersion) {
|
||||
if (
|
||||
typeof glibcVersion !== 'string' ||
|
||||
!/^\d+(?:\.\d+)+$/.test(glibcVersion)
|
||||
) {
|
||||
throw new Error(
|
||||
'Unable to determine the Linux build host glibc version.'
|
||||
);
|
||||
}
|
||||
if (compareVersions(glibcVersion, PORTABLE_ABI_BASELINE.glibcMaximum) > 0) {
|
||||
throw new Error(
|
||||
`Build host glibc ${glibcVersion} exceeds the portable ABI baseline ${PORTABLE_ABI_BASELINE.distribution} maximum ${PORTABLE_ABI_BASELINE.glibcMaximum}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateRuntimeDependencyClosure({
|
||||
entries,
|
||||
runtimeFileNames,
|
||||
@@ -714,6 +1166,7 @@ function validateRuntimeDependencyClosure({
|
||||
const normalizedEntries = [...entries]
|
||||
.map((entry) => ({
|
||||
name: entry.name,
|
||||
soname: entry.soname ?? null,
|
||||
needed: [...new Set(entry.needed ?? [])].sort(),
|
||||
rpath: [...new Set(entry.rpath ?? [])].sort(),
|
||||
runpath: [...new Set(entry.runpath ?? [])].sort(),
|
||||
@@ -733,6 +1186,27 @@ function validateRuntimeDependencyClosure({
|
||||
}
|
||||
entryNames.add(entry.name);
|
||||
|
||||
if (
|
||||
entry.soname !== null &&
|
||||
(typeof entry.soname !== 'string' ||
|
||||
!SHARED_LIBRARY_PATTERN.test(entry.soname) ||
|
||||
path.basename(entry.soname) !== entry.soname)
|
||||
) {
|
||||
throw new Error(
|
||||
`${entry.name} SONAME must be null or a safe shared-library basename.`
|
||||
);
|
||||
}
|
||||
if (
|
||||
entry.name === 'libmpv.so' &&
|
||||
(typeof entry.soname !== 'string' ||
|
||||
!/^libmpv\.so\.\d+(?:\.\d+)*$/.test(entry.soname) ||
|
||||
!bundledNames.has(entry.soname))
|
||||
) {
|
||||
throw new Error(
|
||||
'libmpv.so must declare a versioned SONAME present in the runtime closure.'
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.rpath.length > 0) {
|
||||
throw new Error(
|
||||
`${entry.name} has forbidden RPATH ${entry.rpath.join(':')}.`
|
||||
@@ -845,6 +1319,7 @@ function sourceManifestMetadata(sourceRecord) {
|
||||
function createLinuxRuntimeManifest({
|
||||
sourceRecords,
|
||||
runtimeFiles,
|
||||
abiRecords,
|
||||
dependencyClosure,
|
||||
buildHost,
|
||||
generatedAt = new Date().toISOString(),
|
||||
@@ -867,6 +1342,7 @@ function createLinuxRuntimeManifest({
|
||||
}
|
||||
packages[sourcePackage.id] = sourceManifestMetadata(sourceRecord);
|
||||
}
|
||||
assertPortableAbiRecords(abiRecords);
|
||||
|
||||
const runtimeTotalBytes = runtimeFiles.reduce(
|
||||
(total, runtimeFile) => total + runtimeFile.size,
|
||||
@@ -893,9 +1369,18 @@ function createLinuxRuntimeManifest({
|
||||
},
|
||||
runtimeFiles: runtimeFiles.map((runtimeFile) => ({ ...runtimeFile })),
|
||||
runtimeTotalBytes,
|
||||
runtimeAbi: {
|
||||
baseline: { ...PORTABLE_ABI_BASELINE },
|
||||
files: abiRecords.map((record) => ({ ...record })),
|
||||
},
|
||||
runtimeExternalConfiguration: {
|
||||
fontconfig: { ...RUNTIME_EXTERNAL_CONFIGURATION.fontconfig },
|
||||
openssl: { ...RUNTIME_EXTERNAL_CONFIGURATION.openssl },
|
||||
},
|
||||
runtimeDependencyClosure: {
|
||||
entries: dependencyClosure.entries.map((entry) => ({
|
||||
name: entry.name,
|
||||
soname: entry.soname ?? null,
|
||||
needed: [...entry.needed],
|
||||
rpath: [...entry.rpath],
|
||||
runpath: [...entry.runpath],
|
||||
@@ -907,7 +1392,7 @@ function createLinuxRuntimeManifest({
|
||||
),
|
||||
buildHost,
|
||||
sourceDistribution:
|
||||
'Attach a source archive to the corresponding Linux binary release containing the exact downloaded source archives, a checkout or git bundle of the recorded libplacebo commit and submodules, tools/embedded-mpv/build-linux-runtime.mjs, tools/embedded-mpv/build-linux-runtime.cjs, this runtime manifest, and any local patches.',
|
||||
'Attach a source archive to the corresponding Linux binary release containing the exact downloaded source archives, including the pinned MIT-licensed libdisplay-info source archive, a checkout or git bundle of the recorded libplacebo commit and submodules, tools/embedded-mpv/build-linux-runtime.mjs, tools/embedded-mpv/build-linux-runtime.cjs, this runtime manifest, and any local patches.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -919,19 +1404,36 @@ module.exports = {
|
||||
EXPECTED_SYSTEM_PKG_CONFIG_PACKAGES,
|
||||
FFMPEG_CONFIGURE_FLAGS,
|
||||
GLIBC_TOOLCHAIN_ALLOWLIST,
|
||||
MINIMUM_TOOL_VERSIONS,
|
||||
MPV_MESON_FLAGS,
|
||||
OUTPUT_OWNERSHIP_MARKER,
|
||||
PORTABLE_ABI_BASELINE,
|
||||
REQUIRED_TOOLS,
|
||||
RUNTIME_EXTERNAL_CONFIGURATION,
|
||||
SOURCE_PACKAGES,
|
||||
assertArchiveMatchesPin,
|
||||
assertGitCommitMatchesPin,
|
||||
assertMinimumToolVersions,
|
||||
assertOwnedOutputDestination,
|
||||
assertPortableAbiRecords,
|
||||
assertPortableBuildHostGlibc,
|
||||
assertUniqueMesonOptionAssignments,
|
||||
compareVersions,
|
||||
createBuildEnvironment,
|
||||
createLinuxRuntimeManifest,
|
||||
createOwnedStagingPrefix,
|
||||
createRuntimeFileRecords,
|
||||
materializeLibrarySymlinks,
|
||||
ownedStagingPrefixPath,
|
||||
parseCliInvocation,
|
||||
parseReadelfDynamic,
|
||||
parseReadelfVersionInfo,
|
||||
parseVersion,
|
||||
resolveSystemPkgConfigDirs,
|
||||
runtimeLibraryNames,
|
||||
sha256Buffer,
|
||||
publishOwnedOutput,
|
||||
retainRuntimeLibraries,
|
||||
selectReachableRuntimeLibraryNames,
|
||||
validateRuntimeDependencyClosure,
|
||||
};
|
||||
Reference in new issue
Block a user