'use strict'; const crypto = require('node:crypto'); const fs = require('node:fs'); const path = require('node:path'); const SOURCE_PACKAGES = Object.freeze( [ { id: 'freetype', version: '2.13.3', sourceKind: 'archive', sourceUrl: 'https://download.savannah.gnu.org/releases/freetype/freetype-2.13.3.tar.xz', expectedSha256: '0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289', license: 'FreeType License (FTL)', }, { id: 'fribidi', version: '1.0.16', sourceKind: 'archive', sourceUrl: 'https://github.com/fribidi/fribidi/releases/download/v1.0.16/fribidi-1.0.16.tar.xz', expectedSha256: '1b1cde5b235d40479e91be2f0e88a309e3214c8ab470ec8a2744d82a5a9ea05c', license: 'LGPL-2.1-or-later', }, { id: 'harfbuzz', version: '8.5.0', sourceKind: 'archive', sourceUrl: 'https://github.com/harfbuzz/harfbuzz/releases/download/8.5.0/harfbuzz-8.5.0.tar.xz', expectedSha256: '77e4f7f98f3d86bf8788b53e6832fb96279956e1c3961988ea3d4b7ca41ddc27', license: 'MIT', }, { id: 'expat', version: '2.8.2', sourceKind: 'archive', sourceUrl: 'https://github.com/libexpat/libexpat/releases/download/R_2_8_2/expat-2.8.2.tar.xz', expectedSha256: '3ad89b8588e6644bd4e49981480d48b21289eebbcd4f0a1a4afb1c29f99b6ab4', license: 'MIT', }, { id: 'fontconfig', version: '2.16.0', sourceKind: 'archive', sourceUrl: 'https://www.freedesktop.org/software/fontconfig/release/fontconfig-2.16.0.tar.xz', expectedSha256: '6a33dc555cc9ba8b10caf7695878ef134eeb36d0af366041f639b1da9b6ed220', license: 'MIT', }, { id: 'libass', version: '0.17.3', sourceKind: 'archive', sourceUrl: 'https://github.com/libass/libass/releases/download/0.17.3/libass-0.17.3.tar.xz', expectedSha256: 'eae425da50f0015c21f7b3a9c7262a910f0218af469e22e2931462fed3c50959', license: 'ISC', }, { id: 'openssl', version: '3.5.7', sourceKind: 'archive', sourceUrl: 'https://github.com/openssl/openssl/releases/download/openssl-3.5.7/openssl-3.5.7.tar.gz', expectedSha256: 'a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8', license: 'Apache-2.0', }, { id: 'ffmpeg', version: '8.1', sourceKind: 'archive', sourceUrl: 'https://ffmpeg.org/releases/ffmpeg-8.1.tar.xz', expectedSha256: 'b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a', license: 'LGPL-2.1-or-later', }, { id: 'libplacebo', version: '7.360.1', sourceKind: 'git', sourceUrl: 'https://github.com/haasn/libplacebo.git', sourceTag: 'v7.360.1', expectedGitCommit: 'cee9b076f2c63104ccfd497fa79c39a867293ec4', license: 'LGPL-2.1-or-later', }, { id: 'mpv', version: '0.41.0', sourceKind: 'archive', sourceUrl: 'https://github.com/mpv-player/mpv/archive/refs/tags/v0.41.0.tar.gz', expectedSha256: 'ee21092a5ee427353392360929dc64645c54479aefdb5babc5cfbb5fad626209', license: 'LGPL-2.1-or-later with -Dgpl=false', }, ].map((sourcePackage) => Object.freeze(sourcePackage)) ); const BUILD_ORDER = Object.freeze( SOURCE_PACKAGES.map((sourcePackage) => sourcePackage.id) ); const FFMPEG_CONFIGURE_FLAGS = Object.freeze([ '--enable-shared', '--disable-static', '--disable-programs', '--disable-doc', '--disable-debug', '--disable-autodetect', '--disable-gpl', '--disable-nonfree', '--disable-version3', '--enable-pic', '--enable-pthreads', '--enable-openssl', '--disable-gnutls', '--disable-mbedtls', '--disable-libtls', '--enable-network', '--disable-protocols', '--enable-protocol=file', '--enable-protocol=http', '--enable-protocol=https', '--enable-protocol=httpproxy', '--enable-protocol=tcp', '--enable-protocol=tls', '--enable-protocol=udp', '--enable-protocol=crypto', '--enable-protocol=data', '--enable-demuxer=hls', '--enable-vaapi', '--disable-vdpau', '--disable-vulkan', '--disable-libdrm', '--disable-cuda-llvm', '--disable-cuvid', '--disable-nvdec', '--disable-nvenc', '--disable-xlib', '--disable-sdl2', '--disable-openal', ]); const MPV_MESON_FLAGS = Object.freeze([ '-Dgpl=false', '-Dlibmpv=true', '-Dcplayer=false', '-Dbuild-date=false', '-Dtests=false', '-Dfuzzers=false', '-Ddisable-packet-pool=false', '-Dcdda=disabled', '-Dcplugins=disabled', '-Ddvbin=disabled', '-Ddvdnav=disabled', '-Diconv=enabled', '-Djavascript=disabled', '-Djpeg=disabled', '-Dlcms2=disabled', '-Dlibarchive=disabled', '-Dlibavdevice=disabled', '-Dlibbluray=disabled', '-Dlua=disabled', '-Dpthread-debug=disabled', '-Drubberband=disabled', '-Dsdl2-gamepad=disabled', '-Duchardet=disabled', '-Duwp=disabled', '-Dvapoursynth=disabled', '-Dvector=enabled', '-Dwin32-threads=disabled', '-Dx11-clipboard=disabled', '-Dzimg=disabled', '-Dzlib=disabled', '-Dalsa=enabled', '-Daudiounit=disabled', '-Dcoreaudio=disabled', '-Davfoundation=disabled', '-Djack=disabled', '-Dopenal=disabled', '-Daudiotrack=disabled', '-Daaudio=disabled', '-Dopensles=disabled', '-Doss-audio=disabled', '-Dpipewire=disabled', '-Dpulse=enabled', '-Dsdl2-audio=disabled', '-Dsndio=disabled', '-Dwasapi=disabled', '-Dcaca=disabled', '-Dcocoa=disabled', '-Dd3d11=disabled', '-Ddirect3d=disabled', '-Ddmabuf-wayland=disabled', '-Ddrm=disabled', '-Degl=enabled', '-Degl-android=disabled', '-Degl-angle=disabled', '-Degl-angle-lib=disabled', '-Degl-angle-win32=disabled', '-Degl-drm=disabled', '-Degl-wayland=disabled', '-Degl-x11=disabled', '-Dgbm=enabled', '-Dgl=enabled', '-Dgl-cocoa=disabled', '-Dgl-dxinterop=disabled', '-Dgl-win32=disabled', '-Dgl-x11=disabled', '-Dsdl2-video=disabled', '-Dshaderc=disabled', '-Dsixel=disabled', '-Dspirv-cross=disabled', '-Dplain-gl=enabled', '-Dvdpau=disabled', '-Dvdpau-gl-x11=disabled', '-Dvaapi=enabled', '-Dvaapi-drm=enabled', '-Dvaapi-wayland=disabled', '-Dvaapi-win32=disabled', '-Dvaapi-x11=disabled', '-Dvulkan=disabled', '-Dwayland=disabled', '-Dx11=disabled', '-Dxv=disabled', '-Dandroid-media-ndk=disabled', '-Dcuda-hwaccel=disabled', '-Dcuda-interop=disabled', '-Dd3d-hwaccel=disabled', '-Dd3d9-hwaccel=disabled', '-Dgl-dxinterop-d3d9=disabled', '-Dios-gl=disabled', '-Dvideotoolbox-gl=disabled', '-Dvideotoolbox-pl=disabled', '-Dmacos-10-15-4-features=disabled', '-Dmacos-11-features=disabled', '-Dmacos-11-3-features=disabled', '-Dmacos-12-features=disabled', '-Dmacos-cocoa-cb=disabled', '-Dmacos-media-player=disabled', '-Dmacos-touchbar=disabled', '-Dswift-build=disabled', '-Dwin32-smtc=disabled', '-Dhtml-build=disabled', '-Dmanpage-build=disabled', '-Dpdf-build=disabled', ]); const BUILD_RECIPES = Object.freeze({ freetype: Object.freeze({ buildSystem: 'configure', sharedOnly: true, args: Object.freeze([ '--enable-shared', '--disable-static', '--without-brotli', '--without-bzip2', '--without-harfbuzz', '--without-png', '--without-zlib', ]), }), fribidi: Object.freeze({ buildSystem: 'configure', sharedOnly: true, args: Object.freeze([ '--enable-shared', '--disable-static', '--disable-docs', '--disable-bin', ]), }), harfbuzz: Object.freeze({ buildSystem: 'meson', sharedOnly: true, args: Object.freeze([ '-Dglib=disabled', '-Dgobject=disabled', '-Dcairo=disabled', '-Dchafa=disabled', '-Dicu=disabled', '-Dfreetype=enabled', '-Dtests=disabled', '-Dintrospection=disabled', '-Ddocs=disabled', '-Dutilities=disabled', '-Dbenchmark=disabled', ]), }), expat: Object.freeze({ buildSystem: 'cmake', sharedOnly: true, args: Object.freeze([ '-DEXPAT_SHARED_LIBS=ON', '-DEXPAT_BUILD_TOOLS=OFF', '-DEXPAT_BUILD_EXAMPLES=OFF', '-DEXPAT_BUILD_TESTS=OFF', '-DEXPAT_BUILD_DOCS=OFF', ]), }), fontconfig: Object.freeze({ buildSystem: 'meson', sharedOnly: true, args: Object.freeze([ '-Ddoc=disabled', '-Dtests=disabled', '-Dtools=disabled', '-Dcache-build=disabled', '-Dnls=disabled', ]), }), libass: Object.freeze({ buildSystem: 'configure', sharedOnly: true, args: Object.freeze([ '--enable-shared', '--disable-static', '--enable-fontconfig', '--disable-coretext', '--disable-directwrite', '--disable-libunibreak', ]), }), openssl: Object.freeze({ buildSystem: 'openssl', sharedOnly: true, args: Object.freeze([ 'shared', 'no-apps', 'no-docs', 'no-tests', 'no-engine', 'no-legacy', 'no-module', 'no-weak-ssl-ciphers', ]), }), ffmpeg: Object.freeze({ buildSystem: 'ffmpeg', sharedOnly: true, args: FFMPEG_CONFIGURE_FLAGS, }), libplacebo: Object.freeze({ buildSystem: 'meson', sharedOnly: true, args: Object.freeze([ '-Dopengl=enabled', '-Dvulkan=disabled', '-Dvk-proc-addr=disabled', '-Dglslang=disabled', '-Dshaderc=disabled', '-Dlcms=disabled', '-Ddovi=disabled', '-Dlibdovi=disabled', '-Ddemos=false', '-Dtests=false', '-Dbench=false', '-Dfuzz=false', '-Dunwind=disabled', '-Dxxhash=disabled', ]), }), mpv: Object.freeze({ buildSystem: 'meson', sharedOnly: true, args: MPV_MESON_FLAGS, }), }); const REQUIRED_TOOLS = Object.freeze([ 'cc', 'cmake', 'curl', 'git', 'make', 'meson', 'ninja', 'patchelf', 'perl', 'pkg-config', 'python3', 'readelf', 'tar', ]); const DEFAULT_SYSTEM_PKG_CONFIG_DIRS = Object.freeze([ '/usr/lib/x86_64-linux-gnu/pkgconfig', '/usr/lib64/pkgconfig', '/usr/lib/pkgconfig', '/usr/share/pkgconfig', ]); const EXPECTED_SYSTEM_PKG_CONFIG_PACKAGES = Object.freeze([ 'alsa', 'egl', 'gbm', 'gl', 'libpulse', 'libva', 'libva-drm', ]); const GLIBC_TOOLCHAIN_ALLOWLIST = Object.freeze([ 'ld-linux-x86-64.so.2', 'libc.so.6', 'libdl.so.2', 'libgcc_s.so.1', 'libm.so.6', 'libpthread.so.0', 'librt.so.1', 'libstdc++.so.6', ]); const EXTERNAL_SYSTEM_LIBRARIES = Object.freeze( [ { name: 'libEGL.so.1', interface: 'EGL', reason: 'System graphics-driver interface used by the frame-copy helper.', }, { name: 'libGL.so.1', interface: 'OpenGL', reason: 'System OpenGL compatibility interface supplied by the graphics stack.', }, { name: 'libGLX.so.0', interface: 'OpenGL', reason: 'GLVND OpenGL dispatch interface supplied by the graphics stack.', }, { name: 'libOpenGL.so.0', interface: 'OpenGL', reason: 'GLVND OpenGL interface supplied by the graphics stack.', }, { name: 'libasound.so.2', interface: 'ALSA', reason: 'Linux system audio interface intentionally used by libmpv.', }, { name: 'libdrm.so.2', interface: 'DRM', reason: 'Kernel graphics interface used by system GBM and VA-API drivers.', }, { name: 'libgbm.so.1', interface: 'GBM', reason: 'System graphics-buffer interface used by headless EGL rendering.', }, { name: 'libpulse.so.0', interface: 'PulseAudio', reason: 'Linux desktop audio interface intentionally used by libmpv.', }, { name: 'libva-drm.so.2', interface: 'VA-API DRM', reason: 'System VA-API DRM interface used for hardware decoding.', }, { name: 'libva.so.2', interface: 'VA-API', reason: 'System video-acceleration interface used for hardware decoding.', }, ].map((externalLibrary) => Object.freeze(externalLibrary)) ); const SHARED_LIBRARY_PATTERN = /\.so(?:\.\d+)*$/; const SHA256_PATTERN = /^[a-f0-9]{64}$/; const externalSystemLibraryNames = new Set( EXTERNAL_SYSTEM_LIBRARIES.map(({ name }) => name) ); const allowedExternalLibraryNames = new Set([ ...GLIBC_TOOLCHAIN_ALLOWLIST, ...externalSystemLibraryNames, ]); function assertArchiveMatchesPin(sourcePackage, actualSha256) { if (actualSha256 !== sourcePackage.expectedSha256) { throw new Error( `${sourcePackage.id} archive SHA-256 mismatch: expected ${sourcePackage.expectedSha256}, received ${actualSha256}.` ); } } function assertGitCommitMatchesPin(sourcePackage, actualGitCommit) { if (actualGitCommit !== sourcePackage.expectedGitCommit) { throw new Error( `${sourcePackage.id} git commit mismatch: expected ${sourcePackage.expectedGitCommit}, received ${actualGitCommit}.` ); } } function joinEnvironmentParts(parts, separator = ' ') { return parts.filter((value) => value && value.trim()).join(separator); } function resolveSystemPkgConfigDirs(environment = {}) { const explicitDirectories = environment.IPTVNATOR_EMBEDDED_MPV_SYSTEM_PKG_CONFIG_DIRS; if (!explicitDirectories) { return [...DEFAULT_SYSTEM_PKG_CONFIG_DIRS]; } const directories = explicitDirectories .split(path.delimiter) .map((directory) => directory.trim()) .filter(Boolean); if ( directories.length === 0 || directories.some((directory) => !path.isAbsolute(directory)) ) { throw new Error( 'IPTVNATOR_EMBEDDED_MPV_SYSTEM_PKG_CONFIG_DIRS must contain only absolute paths.' ); } return [ ...new Set(directories.map((directory) => path.normalize(directory))), ]; } function createBuildEnvironment({ prefix, baseEnv = process.env, systemPkgConfigDirs = [], }) { const prefixPkgConfigDirs = [ path.join(prefix, 'lib', 'pkgconfig'), path.join(prefix, 'share', 'pkgconfig'), ]; const pkgConfigLibDirs = [ ...new Set([ ...prefixPkgConfigDirs, ...systemPkgConfigDirs.filter(Boolean), ]), ]; const prefixLibDir = path.join(prefix, 'lib'); const ignoredVariables = new Set([ 'CFLAGS', 'CPPFLAGS', 'CXXFLAGS', 'LDFLAGS', 'LD_LIBRARY_PATH', 'LIBRARY_PATH', 'CPATH', 'C_INCLUDE_PATH', 'CPLUS_INCLUDE_PATH', 'CMAKE_PREFIX_PATH', 'CMAKE_LIBRARY_PATH', 'CMAKE_INCLUDE_PATH', 'FONTCONFIG_PATH', 'OPENSSL_MODULES', ]); const inheritedEnvironment = Object.fromEntries( Object.entries(baseEnv).filter( ([name]) => !ignoredVariables.has(name) && !name.startsWith('PKG_CONFIG') ) ); return { ...inheritedEnvironment, PATH: joinEnvironmentParts( [path.join(prefix, 'bin'), baseEnv.PATH], path.delimiter ), PKG_CONFIG_PATH: prefixPkgConfigDirs.join(path.delimiter), PKG_CONFIG_LIBDIR: pkgConfigLibDirs.join(path.delimiter), CMAKE_PREFIX_PATH: prefix, CPPFLAGS: `-I${path.join(prefix, 'include')}`, CFLAGS: joinEnvironmentParts([ '-fPIC', `-I${path.join(prefix, 'include')}`, ]), CXXFLAGS: joinEnvironmentParts([ '-fPIC', `-I${path.join(prefix, 'include')}`, ]), LDFLAGS: joinEnvironmentParts([ `-L${prefixLibDir}`, `-Wl,-rpath-link,${prefixLibDir}`, ]), LD_LIBRARY_PATH: prefixLibDir, FONTCONFIG_PATH: path.join(prefix, 'etc', 'fonts'), OPENSSL_MODULES: path.join(prefixLibDir, 'ossl-modules'), }; } function sha256Buffer(contents) { return crypto.createHash('sha256').update(contents).digest('hex'); } function runtimeLibraryNames(libDir) { return fs .readdirSync(libDir, { withFileTypes: true }) .filter( (entry) => (entry.isFile() || entry.isSymbolicLink()) && SHARED_LIBRARY_PATTERN.test(entry.name) ) .map((entry) => entry.name) .sort(); } function assertPathInside(parentPath, candidatePath, label) { const relativePath = path.relative(parentPath, candidatePath); if ( relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath) ) { throw new Error(`${label} resolves outside ${parentPath}.`); } } function materializeLibrarySymlinks(libDir) { const realLibDir = fs.realpathSync(libDir); for (const name of runtimeLibraryNames(libDir)) { const libraryPath = path.join(libDir, name); const stat = fs.lstatSync(libraryPath); if (!stat.isSymbolicLink()) { continue; } const realLibraryPath = fs.realpathSync(libraryPath); assertPathInside( realLibDir, realLibraryPath, `Runtime library alias ${name}` ); const targetStat = fs.statSync(realLibraryPath); if (!targetStat.isFile()) { throw new Error( `Runtime library alias ${name} does not resolve to a regular file.` ); } const contents = fs.readFileSync(realLibraryPath); fs.unlinkSync(libraryPath); fs.writeFileSync(libraryPath, contents, { mode: targetStat.mode & 0o777, }); } } function createRuntimeFileRecords(libDir) { return runtimeLibraryNames(libDir).map((name) => { const libraryPath = path.join(libDir, name); const stat = fs.lstatSync(libraryPath); if (!stat.isFile() || stat.isSymbolicLink()) { throw new Error( `Runtime library ${name} must be a materialized regular file.` ); } const contents = fs.readFileSync(libraryPath); return { name, size: contents.length, sha256: sha256Buffer(contents), }; }); } function parseReadelfDynamic(output) { const dynamic = { needed: [], rpath: [], runpath: [], }; const dynamicEntryPattern = /\((NEEDED|RPATH|RUNPATH)\)[^[]*\[([^\]]*)\]/g; for (const match of output.matchAll(dynamicEntryPattern)) { const [, tag, value] = match; if (tag === 'NEEDED') { dynamic.needed.push(value); continue; } const field = tag.toLowerCase(); dynamic[field].push( ...value.split(':').filter((pathEntry) => pathEntry.length > 0) ); } for (const field of Object.keys(dynamic)) { dynamic[field] = [...new Set(dynamic[field])].sort(); } return dynamic; } function validateRuntimeDependencyClosure({ entries, runtimeFileNames, buildPrefix, }) { if (!Array.isArray(entries) || !Array.isArray(runtimeFileNames)) { throw new TypeError( 'Runtime closure entries and runtime file names must be arrays.' ); } const bundledNames = new Set(runtimeFileNames); const entryNames = new Set(); const externalDependencies = new Set(); const normalizedEntries = [...entries] .map((entry) => ({ name: entry.name, needed: [...new Set(entry.needed ?? [])].sort(), rpath: [...new Set(entry.rpath ?? [])].sort(), runpath: [...new Set(entry.runpath ?? [])].sort(), })) .sort((left, right) => left.name.localeCompare(right.name)); for (const entry of normalizedEntries) { if (!bundledNames.has(entry.name)) { throw new Error( `Dynamic closure contains undeclared runtime file ${entry.name}.` ); } if (entryNames.has(entry.name)) { throw new Error( `Dynamic closure contains duplicate runtime file ${entry.name}.` ); } entryNames.add(entry.name); if (entry.rpath.length > 0) { throw new Error( `${entry.name} has forbidden RPATH ${entry.rpath.join(':')}.` ); } if (entry.runpath.length !== 1 || entry.runpath[0] !== '$ORIGIN') { const renderedRunpath = entry.runpath.length > 0 ? entry.runpath.join(':') : ''; throw new Error( `${entry.name} RUNPATH must be exactly $ORIGIN; got ${renderedRunpath}.` ); } if ( buildPrefix && [...entry.rpath, ...entry.runpath].some((value) => value.includes(buildPrefix) ) ) { throw new Error( `${entry.name} RPATH/RUNPATH contains build prefix ${buildPrefix}.` ); } for (const dependencyName of entry.needed) { if (bundledNames.has(dependencyName)) { continue; } if (!allowedExternalLibraryNames.has(dependencyName)) { throw new Error( `Runtime dependency is not bundled or allowlisted: ${entry.name} -> ${dependencyName}.` ); } externalDependencies.add(dependencyName); } } for (const runtimeFileName of bundledNames) { if (!entryNames.has(runtimeFileName)) { throw new Error( `Runtime library ${runtimeFileName} is missing from the dynamic closure.` ); } } return { entries: normalizedEntries, externalDependencies: [...externalDependencies].sort(), }; } function parseCliInvocation({ platform, arch, argv, cwd }) { if (platform !== 'linux' || arch !== 'x64') { throw new Error( `Embedded MPV runtime source builds are supported on Linux x64 only; received ${platform}/${arch}.` ); } const args = argv[0] === '--' ? argv.slice(1) : argv; if (args.length !== 1 || !args[0]) { throw new Error( [ 'Usage: node tools/embedded-mpv/build-linux-runtime.mjs ', '', 'Builds the pinned LGPL-compatible Linux x64 libmpv runtime from source.', ].join('\n') ); } return { prefix: path.resolve(cwd, args[0]) }; } function sourceManifestMetadata(sourceRecord) { const metadata = { version: sourceRecord.version, sourceUrl: sourceRecord.sourceUrl, ...(sourceRecord.sourceTag ? { sourceTag: sourceRecord.sourceTag } : {}), ...(sourceRecord.sourceSha256 ? { sourceSha256: sourceRecord.sourceSha256 } : {}), ...(sourceRecord.sourceGitCommit ? { sourceGitCommit: sourceRecord.sourceGitCommit } : {}), ...(sourceRecord.sourceSubmodules ? { sourceSubmodules: [...sourceRecord.sourceSubmodules] } : {}), license: sourceRecord.license, }; if ( sourceRecord.sourceKind === 'archive' && !SHA256_PATTERN.test(sourceRecord.sourceSha256 ?? '') ) { throw new Error( `Archive source ${sourceRecord.id} is missing its downloaded SHA-256 digest.` ); } if ( sourceRecord.sourceKind === 'git' && !/^[a-f0-9]{40,64}$/.test(sourceRecord.sourceGitCommit ?? '') ) { throw new Error( `Git source ${sourceRecord.id} is missing its exact commit digest.` ); } return metadata; } function createLinuxRuntimeManifest({ sourceRecords, runtimeFiles, dependencyClosure, buildHost, generatedAt = new Date().toISOString(), ffmpegConfigureFlags = FFMPEG_CONFIGURE_FLAGS, mpvMesonFlags = MPV_MESON_FLAGS, }) { const packages = {}; for (const sourcePackage of SOURCE_PACKAGES) { const sourceRecord = sourceRecords[sourcePackage.id]; if (!sourceRecord) { throw new Error(`Missing source metadata for ${sourcePackage.id}.`); } if (sourcePackage.sourceKind === 'archive') { assertArchiveMatchesPin(sourcePackage, sourceRecord.sourceSha256); } else { assertGitCommitMatchesPin( sourcePackage, sourceRecord.sourceGitCommit ); } packages[sourcePackage.id] = sourceManifestMetadata(sourceRecord); } const runtimeTotalBytes = runtimeFiles.reduce( (total, runtimeFile) => total + runtimeFile.size, 0 ); return { schemaVersion: 1, origin: 'vendored-lgpl-source-build', platform: 'linux', arch: 'x64', generatedAt, packages, ffmpeg: { ...packages.ffmpeg, licensePolicy: 'LGPL, built with OpenSSL and without GPL, nonfree, or version-3-only components.', configureFlags: [...ffmpegConfigureFlags], }, mpv: { ...packages.mpv, licensePolicy: 'LGPL-compatible libmpv built with -Dgpl=false and without the CLI player.', mesonFlags: [...mpvMesonFlags], }, runtimeFiles: runtimeFiles.map((runtimeFile) => ({ ...runtimeFile })), runtimeTotalBytes, runtimeDependencyClosure: { entries: dependencyClosure.entries.map((entry) => ({ name: entry.name, needed: [...entry.needed], rpath: [...entry.rpath], runpath: [...entry.runpath], })), externalDependencies: [...dependencyClosure.externalDependencies], }, externalSystemLibraries: EXTERNAL_SYSTEM_LIBRARIES.map( (externalLibrary) => ({ ...externalLibrary }) ), 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.', }; } module.exports = { BUILD_RECIPES, BUILD_ORDER, DEFAULT_SYSTEM_PKG_CONFIG_DIRS, EXTERNAL_SYSTEM_LIBRARIES, EXPECTED_SYSTEM_PKG_CONFIG_PACKAGES, FFMPEG_CONFIGURE_FLAGS, GLIBC_TOOLCHAIN_ALLOWLIST, MPV_MESON_FLAGS, REQUIRED_TOOLS, SOURCE_PACKAGES, assertArchiveMatchesPin, assertGitCommitMatchesPin, createBuildEnvironment, createLinuxRuntimeManifest, createRuntimeFileRecords, materializeLibrarySymlinks, parseCliInvocation, parseReadelfDynamic, resolveSystemPkgConfigDirs, runtimeLibraryNames, sha256Buffer, validateRuntimeDependencyClosure, };