mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
fix(embedded-mpv): pin Linux display data
This commit is contained in:
1 parent
e8903c86fb
commit
f9207a1884
7 files changed
+441
-54
No files matched your search
@@ -15,74 +15,74 @@
|
||||
### Runtime contracts and staging
|
||||
|
||||
- Create `tools/embedded-mpv/linux-runtime-manifest.cjs`
|
||||
- Parse, normalize, and validate Linux frame-copy runtime manifests.
|
||||
- Parse, normalize, and validate Linux frame-copy runtime manifests.
|
||||
- Create `tools/embedded-mpv/build-linux-runtime.mjs`
|
||||
- Build the pinned LGPL-compatible FFmpeg/libass/libplacebo/libmpv prefix.
|
||||
- Build the pinned LGPL-compatible FFmpeg/libass/libplacebo/libmpv prefix.
|
||||
- Modify `tools/embedded-mpv/stage-runtime.mjs`
|
||||
- Stage Linux shared libraries and reject incomplete release manifests.
|
||||
- Stage Linux shared libraries and reject incomplete release manifests.
|
||||
- Modify `apps/electron-backend/build-embedded-mpv.js`
|
||||
- Build against staged Linux libmpv, copy the bundled closure, and emit the
|
||||
profile-neutral build manifest.
|
||||
- Build against staged Linux libmpv, copy the bundled closure, and emit the
|
||||
profile-neutral build manifest.
|
||||
- Modify `apps/electron-backend/native/binding.gyp`
|
||||
- Keep helper RPATH relative and remove build-host RPATH.
|
||||
- Keep helper RPATH relative and remove build-host RPATH.
|
||||
- Modify `package.json`
|
||||
- Expose the Linux runtime build command.
|
||||
- Expose the Linux runtime build command.
|
||||
|
||||
### Packaging profiles and validation
|
||||
|
||||
- Create `tools/packaging/linux-frame-copy-profile.cjs`
|
||||
- Own profile names, target sets, manifest origins, and package dependencies.
|
||||
- Own profile names, target sets, manifest origins, and package dependencies.
|
||||
- Create `tools/packaging/linux-frame-copy-profile.test.mjs`
|
||||
- Verify profile/target/dependency mapping and invalid combinations.
|
||||
- Verify profile/target/dependency mapping and invalid combinations.
|
||||
- Modify `electron-builder.json`
|
||||
- Declare DEB/RPM/Pacman libmpv dependencies.
|
||||
- Declare DEB/RPM/Pacman libmpv dependencies.
|
||||
- Modify `tools/packaging/embedded-mpv-frame-copy-files.cjs`
|
||||
- Package Linux helper/reader and select/remove private runtime by profile.
|
||||
- Package Linux helper/reader and select/remove private runtime by profile.
|
||||
- Modify `tools/packaging/embedded-mpv-packaging.cjs`
|
||||
- Validate Linux ELF linkage, manifest, files, modes, RPATH, and isolation.
|
||||
- Validate Linux ELF linkage, manifest, files, modes, RPATH, and isolation.
|
||||
- Modify `tools/packaging/embedded-mpv-arch.test.mjs`
|
||||
- Cover system, bundled, malformed, and foreign-architecture layouts.
|
||||
- Cover system, bundled, malformed, and foreign-architecture layouts.
|
||||
- Modify `tools/packaging/electron-after-pack.cjs`
|
||||
- Pass the required Linux profile into preparation and validation.
|
||||
- Pass the required Linux profile into preparation and validation.
|
||||
- Modify `tools/packaging/verify-electron-package-layout.mjs`
|
||||
- Verify the expected profile for every unpacked layout.
|
||||
- Verify the expected profile for every unpacked layout.
|
||||
- Modify `tools/packaging/project.json`
|
||||
- Add new tests and source inputs.
|
||||
- Add new tests and source inputs.
|
||||
|
||||
### Runtime capability probe
|
||||
|
||||
- Modify `apps/electron-backend/native/helper/frame_helper_gl.h`
|
||||
- Provide a context-only probe that does not create a playback session.
|
||||
- Provide a context-only probe that does not create a playback session.
|
||||
- Modify `apps/electron-backend/native/helper/mpv_frame_helper.cpp`
|
||||
- Implement the versioned `--runtime-probe` JSON protocol.
|
||||
- Implement the versioned `--runtime-probe` JSON protocol.
|
||||
- Create `apps/electron-backend/src/app/services/embedded-mpv-frame-copy-runtime.ts`
|
||||
- Validate manifest/files and run/cache the bounded helper probe.
|
||||
- Validate manifest/files and run/cache the bounded helper probe.
|
||||
- Create `apps/electron-backend/src/app/services/embedded-mpv-frame-copy-runtime.spec.ts`
|
||||
- Cover all fail-closed paths and success caching.
|
||||
- Cover all fail-closed paths and success caching.
|
||||
- Modify `apps/electron-backend/src/app/services/embedded-mpv-frame-copy-platform.util.ts`
|
||||
- Resolve an artifact set and delegate usability to the runtime probe.
|
||||
- Resolve an artifact set and delegate usability to the runtime probe.
|
||||
- Modify `apps/electron-backend/src/app/services/embedded-mpv-frame-copy-platform.util.spec.ts`
|
||||
- Keep path/security coverage and add manifest/probe integration cases.
|
||||
- Keep path/security coverage and add manifest/probe integration cases.
|
||||
- Modify `apps/electron-backend/src/app/services/embedded-mpv-native.service.ts`
|
||||
- Surface stable fallback diagnostics without changing native-view safety.
|
||||
- Surface stable fallback diagnostics without changing native-view safety.
|
||||
|
||||
### Linux CI, package smoke, and documentation
|
||||
|
||||
- Create `tools/packaging/verify-linux-frame-copy-runtime.mjs`
|
||||
- Inspect real package payload ELF/modes/manifest and invoke the helper probe.
|
||||
- Inspect real package payload ELF/modes/manifest and invoke the helper probe.
|
||||
- Create `tools/packaging/verify-linux-frame-copy-runtime.test.mjs`
|
||||
- Unit-test verifier parsing and failure reporting with fixtures.
|
||||
- Unit-test verifier parsing and failure reporting with fixtures.
|
||||
- Create `apps/electron-backend-e2e/src/embedded-mpv-frame-copy-packaged.e2e.ts`
|
||||
- Exercise packaged capability, a deterministic media frame, and fallback.
|
||||
- Exercise packaged capability, a deterministic media frame, and fallback.
|
||||
- Modify `.github/workflows/build-and-make.yaml`
|
||||
- Build/cache runtime, split profiles, inspect every format, and run sandbox
|
||||
and container smoke coverage.
|
||||
- Build/cache runtime, split profiles, inspect every format, and run sandbox
|
||||
and container smoke coverage.
|
||||
- Modify `docs/architecture/embedded-mpv-native.md`
|
||||
- Modify `tools/embedded-mpv/README.md`
|
||||
- Modify `vendor/embedded-mpv/README.md`
|
||||
- Modify `AGENTS.md`
|
||||
- Modify `CLAUDE.md`
|
||||
- Document the final contract and verification matrix.
|
||||
- Document the final contract and verification matrix.
|
||||
|
||||
## Task 1: Define Linux Packaging Profiles
|
||||
|
||||
@@ -115,10 +115,9 @@ assert.deepEqual(LINUX_SYSTEM_PACKAGE_DEPENDENCIES, {
|
||||
rpm: 'mpv-libs',
|
||||
pacman: 'mpv',
|
||||
});
|
||||
assert.deepEqual(
|
||||
validateLinuxProfileTargets('system', ['deb', 'AppImage']),
|
||||
['Linux frame-copy profile "system" cannot build target "appimage".']
|
||||
);
|
||||
assert.deepEqual(validateLinuxProfileTargets('system', ['deb', 'AppImage']), [
|
||||
'Linux frame-copy profile "system" cannot build target "appimage".',
|
||||
]);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run RED**
|
||||
@@ -220,7 +219,13 @@ Linux mpv flags must include:
|
||||
```
|
||||
|
||||
Record downloaded SHA-256 values, git commits/submodules, exact flags, runtime
|
||||
file names/sizes, and source-distribution obligations.
|
||||
file names/sizes, and source-distribution obligations. Pin the hwdata v0.409
|
||||
archive and record its `pnp.ids` as a build input to libdisplay-info 0.1.1.
|
||||
Stage private `hwdata.pc` metadata and run libdisplay-info's Meson setup with a
|
||||
prefix-only pkg-config environment so the upstream
|
||||
`/usr/share/hwdata/pnp.ids` fallback is unreachable. Include the exact hwdata
|
||||
archive and its `GPL-2.0-or-later OR XFree86-1.0` notice in the release source
|
||||
bundle.
|
||||
|
||||
- [ ] **Step 4: Implement Linux staging**
|
||||
|
||||
@@ -422,7 +427,7 @@ Expected: FAIL because the runtime probe module does not exist.
|
||||
Emit exactly one line:
|
||||
|
||||
```json
|
||||
{"protocol":1,"usable":true,"libmpv":"2.x","renderApi":"egl"}
|
||||
{ "protocol": 1, "usable": true, "libmpv": "2.x", "renderApi": "egl" }
|
||||
```
|
||||
|
||||
Exit nonzero with a JSON `reason` when `mpv_create`, `mpv_initialize`, or the
|
||||
@@ -651,4 +656,3 @@ Report separately:
|
||||
|
||||
Do not push, open a PR, publish artifacts, or merge. Leave the fully checked
|
||||
local branch ready for explicit user confirmation in a new task.
|
||||
|
||||
+16
-6
@@ -43,11 +43,11 @@ carry an explicit unavailable marker and must not contain x64 native binaries.
|
||||
|
||||
Linux packaging is split into explicit profiles:
|
||||
|
||||
| Profile | Formats | libmpv strategy |
|
||||
| --- | --- | --- |
|
||||
| `system` | DEB, RPM, Pacman | Depend on the distribution package and resolve `libmpv.so.2` from the host |
|
||||
| `portable` | AppImage, Snap | Bundle the pinned LGPL-compatible runtime closure under `native/lib` |
|
||||
| `flatpak` | Flatpak | Bundle the same pinned LGPL-compatible runtime closure under `native/lib` |
|
||||
| Profile | Formats | libmpv strategy |
|
||||
| ---------- | ---------------- | -------------------------------------------------------------------------- |
|
||||
| `system` | DEB, RPM, Pacman | Depend on the distribution package and resolve `libmpv.so.2` from the host |
|
||||
| `portable` | AppImage, Snap | Bundle the pinned LGPL-compatible runtime closure under `native/lib` |
|
||||
| `flatpak` | Flatpak | Bundle the same pinned LGPL-compatible runtime closure under `native/lib` |
|
||||
|
||||
The official CI matrix must run these profiles independently. The packaging
|
||||
hook receives the profile through a required environment value and validates
|
||||
@@ -75,6 +75,16 @@ demuxers, protocols, and software scaling/resampling plus libass text
|
||||
subtitles. Hardware decoding remains opportunistic through host Mesa/driver
|
||||
interfaces and must fall back to software decoding.
|
||||
|
||||
The source build also pins the `hwdata` v0.409 archive and its SHA-256 because
|
||||
libdisplay-info 0.1.1 compiles `pnp.ids` into its generated vendor lookup
|
||||
table. The builder stages that file with private `hwdata.pc` metadata and
|
||||
restricts libdisplay-info's native pkg-config search to the staged prefix, so
|
||||
Meson's `/usr/share/hwdata/pnp.ids` fallback cannot make the runtime depend on
|
||||
unrecorded host data. The runtime manifest records this build-input
|
||||
relationship. Release source bundles must include the exact hwdata archive and
|
||||
its dual-license notice (`GPL-2.0-or-later OR XFree86-1.0`) alongside the
|
||||
MIT-licensed libdisplay-info source.
|
||||
|
||||
## Runtime Layout And Linkage
|
||||
|
||||
The x64 packaged native directory is:
|
||||
@@ -115,6 +125,7 @@ The manifest records:
|
||||
- libmpv SONAME and either system package requirements or bundled files;
|
||||
- source package versions, URLs/checksums, license identifiers, and exact
|
||||
FFmpeg/mpv build flags for bundled profiles;
|
||||
- the pinned hwdata `pnp.ids` build input consumed by libdisplay-info;
|
||||
- runtime closure and total byte size;
|
||||
- the native-view backend contract and the fact that only the helper links
|
||||
libmpv.
|
||||
@@ -203,4 +214,3 @@ status.
|
||||
Release artifacts must publish the generated runtime manifest and exact source
|
||||
archives/metadata required by the recorded LGPL source-distribution statement.
|
||||
No publication, push, pull request, or merge is part of this task.
|
||||
|
||||
@@ -4,6 +4,12 @@ const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const HWDATA_BUILD_INPUT = Object.freeze({
|
||||
consumer: 'libdisplay-info',
|
||||
relativePath: 'pnp.ids',
|
||||
purpose: 'PNP vendor lookup table compiled into libdisplay-info.',
|
||||
});
|
||||
|
||||
const SOURCE_PACKAGES = Object.freeze(
|
||||
[
|
||||
{
|
||||
@@ -94,6 +100,17 @@ const SOURCE_PACKAGES = Object.freeze(
|
||||
expectedGitCommit: 'cee9b076f2c63104ccfd497fa79c39a867293ec4',
|
||||
license: 'LGPL-2.1-or-later',
|
||||
},
|
||||
{
|
||||
id: 'hwdata',
|
||||
version: '0.409',
|
||||
sourceKind: 'archive',
|
||||
sourceUrl:
|
||||
'https://github.com/vcrhonek/hwdata/archive/refs/tags/v0.409.tar.gz',
|
||||
expectedSha256:
|
||||
'23006accc0f931dd5187d0307a57d0744e2b8feb85e73c37bc0f5229fb31eadd',
|
||||
license: 'GPL-2.0-or-later OR XFree86-1.0',
|
||||
buildInput: HWDATA_BUILD_INPUT,
|
||||
},
|
||||
{
|
||||
id: 'libdisplay-info',
|
||||
version: '0.1.1',
|
||||
@@ -388,6 +405,11 @@ const BUILD_RECIPES = Object.freeze({
|
||||
'-Dxxhash=disabled',
|
||||
]),
|
||||
}),
|
||||
hwdata: Object.freeze({
|
||||
buildSystem: 'data',
|
||||
sharedOnly: false,
|
||||
args: Object.freeze([]),
|
||||
}),
|
||||
'libdisplay-info': Object.freeze({
|
||||
buildSystem: 'meson',
|
||||
sharedOnly: true,
|
||||
@@ -875,6 +897,148 @@ function createBuildEnvironment({
|
||||
};
|
||||
}
|
||||
|
||||
function createPinnedHwdataPkgConfigEnvironment({ buildEnvironment, prefix }) {
|
||||
if (!buildEnvironment || typeof buildEnvironment !== 'object') {
|
||||
throw new TypeError(
|
||||
'Pinned hwdata requires the Linux runtime build environment.'
|
||||
);
|
||||
}
|
||||
const prefixPkgConfigDirs = [
|
||||
path.join(prefix, 'lib', 'pkgconfig'),
|
||||
path.join(prefix, 'share', 'pkgconfig'),
|
||||
];
|
||||
const pinnedPkgConfigPath = prefixPkgConfigDirs.join(path.delimiter);
|
||||
return {
|
||||
...buildEnvironment,
|
||||
PKG_CONFIG_PATH: pinnedPkgConfigPath,
|
||||
PKG_CONFIG_LIBDIR: pinnedPkgConfigPath,
|
||||
};
|
||||
}
|
||||
|
||||
function assertPinnedHwdataResolution({
|
||||
pcFileDir,
|
||||
pkgDataDir,
|
||||
prefix,
|
||||
version,
|
||||
}) {
|
||||
const expectedPcFileDir = path.join(prefix, 'share', 'pkgconfig');
|
||||
const expectedPkgDataDir = path.join(prefix, 'share', 'hwdata');
|
||||
if (path.resolve(pcFileDir) !== path.resolve(expectedPcFileDir)) {
|
||||
throw new Error(
|
||||
`Pinned hwdata pkg-config metadata resolved outside the staged prefix: ${pcFileDir}.`
|
||||
);
|
||||
}
|
||||
if (path.resolve(pkgDataDir) !== path.resolve(expectedPkgDataDir)) {
|
||||
throw new Error(
|
||||
`Pinned hwdata data resolved outside the staged prefix: ${pkgDataDir}.`
|
||||
);
|
||||
}
|
||||
const hwdataPackage = SOURCE_PACKAGES.find(({ id }) => id === 'hwdata');
|
||||
if (version !== hwdataPackage.version) {
|
||||
throw new Error(
|
||||
`Pinned hwdata version mismatch: expected ${hwdataPackage.version}, received ${version}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function preparePinnedHwdataBuildInput({
|
||||
buildEnvironment,
|
||||
fileSystem = fs,
|
||||
prefix,
|
||||
runCapture,
|
||||
sourcePath,
|
||||
}) {
|
||||
if (typeof runCapture !== 'function') {
|
||||
throw new TypeError(
|
||||
'Pinned hwdata preparation requires a command capture function.'
|
||||
);
|
||||
}
|
||||
const hwdataPackage = SOURCE_PACKAGES.find(({ id }) => id === 'hwdata');
|
||||
const sourceRoot = fileSystem.realpathSync(sourcePath);
|
||||
const sourceInputPath = path.join(
|
||||
sourcePath,
|
||||
hwdataPackage.buildInput.relativePath
|
||||
);
|
||||
const sourceInputStat = fileSystem.lstatSync(sourceInputPath);
|
||||
if (!sourceInputStat.isFile() || sourceInputStat.isSymbolicLink()) {
|
||||
throw new Error(
|
||||
`Pinned hwdata build input must be a regular file: ${sourceInputPath}.`
|
||||
);
|
||||
}
|
||||
const realSourceInputPath = fileSystem.realpathSync(sourceInputPath);
|
||||
assertPathInside(
|
||||
sourceRoot,
|
||||
realSourceInputPath,
|
||||
'Pinned hwdata build input'
|
||||
);
|
||||
const pnpIds = fileSystem.readFileSync(realSourceInputPath);
|
||||
if (pnpIds.length === 0) {
|
||||
throw new Error('Pinned hwdata pnp.ids build input must not be empty.');
|
||||
}
|
||||
|
||||
const pkgDataDir = path.join(prefix, 'share', 'hwdata');
|
||||
const pcFileDir = path.join(prefix, 'share', 'pkgconfig');
|
||||
fileSystem.mkdirSync(pkgDataDir, { recursive: true });
|
||||
fileSystem.mkdirSync(pcFileDir, { recursive: true });
|
||||
fileSystem.writeFileSync(path.join(pkgDataDir, 'pnp.ids'), pnpIds, {
|
||||
mode: 0o644,
|
||||
});
|
||||
fileSystem.writeFileSync(
|
||||
path.join(pcFileDir, 'hwdata.pc'),
|
||||
[
|
||||
`prefix=${prefix}`,
|
||||
'datadir=${prefix}/share',
|
||||
`pkgdatadir=${pkgDataDir}`,
|
||||
'',
|
||||
'Name: hwdata',
|
||||
'Description: Pinned PNP hardware identification data',
|
||||
`Version: ${hwdataPackage.version}`,
|
||||
'',
|
||||
].join('\n'),
|
||||
{ mode: 0o644 }
|
||||
);
|
||||
|
||||
const pinnedEnvironment = createPinnedHwdataPkgConfigEnvironment({
|
||||
buildEnvironment,
|
||||
prefix,
|
||||
});
|
||||
const captureOptions = { env: pinnedEnvironment };
|
||||
const resolvedPcFileDir = runCapture(
|
||||
'pkg-config',
|
||||
['--variable=pcfiledir', 'hwdata'],
|
||||
captureOptions
|
||||
);
|
||||
const resolvedPkgDataDir = runCapture(
|
||||
'pkg-config',
|
||||
['--variable=pkgdatadir', 'hwdata'],
|
||||
captureOptions
|
||||
);
|
||||
const resolvedVersion = runCapture(
|
||||
'pkg-config',
|
||||
['--modversion', 'hwdata'],
|
||||
captureOptions
|
||||
);
|
||||
assertPinnedHwdataResolution({
|
||||
pcFileDir: resolvedPcFileDir,
|
||||
pkgDataDir: resolvedPkgDataDir,
|
||||
prefix,
|
||||
version: resolvedVersion,
|
||||
});
|
||||
return pinnedEnvironment;
|
||||
}
|
||||
|
||||
function resolveLinuxPackageBuildEnvironment(packageId, context) {
|
||||
if (packageId !== 'libdisplay-info') {
|
||||
return context.buildEnvironment;
|
||||
}
|
||||
if (!context.hwdataBuildEnvironment) {
|
||||
throw new Error(
|
||||
'libdisplay-info requires the staged pinned hwdata build environment.'
|
||||
);
|
||||
}
|
||||
return context.hwdataBuildEnvironment;
|
||||
}
|
||||
|
||||
function sha256Buffer(contents) {
|
||||
return crypto.createHash('sha256').update(contents).digest('hex');
|
||||
}
|
||||
@@ -1294,6 +1458,9 @@ function sourceManifestMetadata(sourceRecord) {
|
||||
...(sourceRecord.sourceSubmodules
|
||||
? { sourceSubmodules: [...sourceRecord.sourceSubmodules] }
|
||||
: {}),
|
||||
...(sourceRecord.buildInput
|
||||
? { buildInput: { ...sourceRecord.buildInput } }
|
||||
: {}),
|
||||
license: sourceRecord.license,
|
||||
};
|
||||
|
||||
@@ -1392,7 +1559,7 @@ function createLinuxRuntimeManifest({
|
||||
),
|
||||
buildHost,
|
||||
sourceDistribution:
|
||||
'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.',
|
||||
'Attach a source archive to the corresponding Linux binary release containing the exact downloaded source archives, including the pinned dual-licensed hwdata archive whose pnp.ids is compiled into the 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.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1429,7 +1596,9 @@ module.exports = {
|
||||
parseReadelfDynamic,
|
||||
parseReadelfVersionInfo,
|
||||
parseVersion,
|
||||
preparePinnedHwdataBuildInput,
|
||||
resolveSystemPkgConfigDirs,
|
||||
resolveLinuxPackageBuildEnvironment,
|
||||
runtimeLibraryNames,
|
||||
sha256Buffer,
|
||||
publishOwnedOutput,
|
||||
|
||||
@@ -32,7 +32,9 @@ const {
|
||||
parseCliInvocation,
|
||||
parseReadelfDynamic,
|
||||
parseReadelfVersionInfo,
|
||||
preparePinnedHwdataBuildInput,
|
||||
retainRuntimeLibraries,
|
||||
resolveLinuxPackageBuildEnvironment,
|
||||
resolveSystemPkgConfigDirs,
|
||||
runtimeLibraryNames,
|
||||
selectReachableRuntimeLibraryNames,
|
||||
@@ -342,18 +344,35 @@ function mesonSetupArgs(prefix, recipeArgs) {
|
||||
function mesonInstall(packageId, recipe, context) {
|
||||
const sourcePath = sourcePathFor(packageId, context.sourceRoot);
|
||||
const buildPath = path.join(sourcePath, 'build-iptvnator');
|
||||
const buildEnvironment = resolveLinuxPackageBuildEnvironment(
|
||||
packageId,
|
||||
context
|
||||
);
|
||||
fs.rmSync(buildPath, { recursive: true, force: true });
|
||||
context.run(
|
||||
'meson',
|
||||
['setup', buildPath, ...mesonSetupArgs(context.prefix, recipe.args)],
|
||||
{ cwd: sourcePath }
|
||||
{ cwd: sourcePath, env: buildEnvironment }
|
||||
);
|
||||
context.run(
|
||||
'meson',
|
||||
['compile', '--jobs', context.parallelism, '-C', buildPath],
|
||||
{ cwd: sourcePath }
|
||||
{ cwd: sourcePath, env: buildEnvironment }
|
||||
);
|
||||
context.run('meson', ['install', '-C', buildPath], { cwd: sourcePath });
|
||||
context.run('meson', ['install', '-C', buildPath], {
|
||||
cwd: sourcePath,
|
||||
env: buildEnvironment,
|
||||
});
|
||||
}
|
||||
|
||||
function prepareHwdata(context) {
|
||||
context.hwdataBuildEnvironment = preparePinnedHwdataBuildInput({
|
||||
buildEnvironment: context.buildEnvironment,
|
||||
prefix: context.prefix,
|
||||
runCapture: (command, args, options) =>
|
||||
context.runCapture(command, args, options),
|
||||
sourcePath: sourcePathFor('hwdata', context.sourceRoot),
|
||||
});
|
||||
}
|
||||
|
||||
function pathInsideDestdir(destdir, absolutePath) {
|
||||
@@ -506,6 +525,9 @@ function buildRuntime(context) {
|
||||
continue;
|
||||
}
|
||||
switch (recipe.buildSystem) {
|
||||
case 'data':
|
||||
prepareHwdata(context);
|
||||
break;
|
||||
case 'configure':
|
||||
configureInstall(packageId, recipe, context);
|
||||
break;
|
||||
|
||||
@@ -42,8 +42,10 @@ const {
|
||||
parseCliInvocation,
|
||||
parseReadelfDynamic,
|
||||
parseReadelfVersionInfo,
|
||||
preparePinnedHwdataBuildInput,
|
||||
publishOwnedOutput,
|
||||
retainRuntimeLibraries,
|
||||
resolveLinuxPackageBuildEnvironment,
|
||||
resolveSystemPkgConfigDirs,
|
||||
selectReachableRuntimeLibraryNames,
|
||||
validateRuntimeDependencyClosure,
|
||||
@@ -91,6 +93,7 @@ test('pins the complete source stack and preserves dependency build order', () =
|
||||
{ id: 'openssl', version: '3.5.7' },
|
||||
{ id: 'ffmpeg', version: '8.1' },
|
||||
{ id: 'libplacebo', version: '7.360.1' },
|
||||
{ id: 'hwdata', version: '0.409' },
|
||||
{ id: 'libdisplay-info', version: '0.1.1' },
|
||||
{ id: 'mpv', version: '0.41.0' },
|
||||
]
|
||||
@@ -129,6 +132,21 @@ test('pins the complete source stack and preserves dependency build order', () =
|
||||
byId.get('openssl').sourceUrl,
|
||||
'https://github.com/openssl/openssl/releases/download/openssl-3.5.7/openssl-3.5.7.tar.gz'
|
||||
);
|
||||
assert.deepEqual(byId.get('hwdata'), {
|
||||
id: 'hwdata',
|
||||
version: '0.409',
|
||||
sourceKind: 'archive',
|
||||
sourceUrl:
|
||||
'https://github.com/vcrhonek/hwdata/archive/refs/tags/v0.409.tar.gz',
|
||||
expectedSha256:
|
||||
'23006accc0f931dd5187d0307a57d0744e2b8feb85e73c37bc0f5229fb31eadd',
|
||||
license: 'GPL-2.0-or-later OR XFree86-1.0',
|
||||
buildInput: {
|
||||
consumer: 'libdisplay-info',
|
||||
relativePath: 'pnp.ids',
|
||||
purpose: 'PNP vendor lookup table compiled into libdisplay-info.',
|
||||
},
|
||||
});
|
||||
assert.deepEqual(byId.get('libdisplay-info'), {
|
||||
id: 'libdisplay-info',
|
||||
version: '0.1.1',
|
||||
@@ -139,6 +157,9 @@ test('pins the complete source stack and preserves dependency build order', () =
|
||||
'0d8731588e9f82a9cac96324a3d7c82e2ba5b1b5e006143fefe692c74069fb60',
|
||||
license: 'MIT',
|
||||
});
|
||||
assert.ok(
|
||||
BUILD_ORDER.indexOf('hwdata') < BUILD_ORDER.indexOf('libdisplay-info')
|
||||
);
|
||||
assert.ok(
|
||||
BUILD_ORDER.indexOf('libdisplay-info') < BUILD_ORDER.indexOf('mpv')
|
||||
);
|
||||
@@ -156,6 +177,7 @@ test('hardcodes the verified official archive digests and libplacebo commit', ()
|
||||
'1b1cde5b235d40479e91be2f0e88a309e3214c8ab470ec8a2744d82a5a9ea05c',
|
||||
harfbuzz:
|
||||
'77e4f7f98f3d86bf8788b53e6832fb96279956e1c3961988ea3d4b7ca41ddc27',
|
||||
hwdata: '23006accc0f931dd5187d0307a57d0744e2b8feb85e73c37bc0f5229fb31eadd',
|
||||
libass: 'eae425da50f0015c21f7b3a9c7262a910f0218af469e22e2931462fed3c50959',
|
||||
'libdisplay-info':
|
||||
'0d8731588e9f82a9cac96324a3d7c82e2ba5b1b5e006143fefe692c74069fb60',
|
||||
@@ -303,12 +325,125 @@ test('defines shared-only source recipes with font discovery before playback', (
|
||||
BUILD_ORDER.indexOf('fontconfig') < BUILD_ORDER.indexOf('libass')
|
||||
);
|
||||
assert.ok(BUILD_ORDER.indexOf('openssl') < BUILD_ORDER.indexOf('ffmpeg'));
|
||||
assert.equal(BUILD_RECIPES.hwdata.buildSystem, 'data');
|
||||
assert.equal(BUILD_RECIPES.hwdata.sharedOnly, false);
|
||||
assert.equal(BUILD_RECIPES['libdisplay-info'].buildSystem, 'meson');
|
||||
for (const packageId of BUILD_ORDER) {
|
||||
for (const packageId of BUILD_ORDER.filter(
|
||||
(packageId) => packageId !== 'hwdata'
|
||||
)) {
|
||||
assert.equal(BUILD_RECIPES[packageId].sharedOnly, true, packageId);
|
||||
}
|
||||
});
|
||||
|
||||
test('stages pinned hwdata and excludes host pkg-config fallback', (t) => {
|
||||
const root = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'iptvnator-pinned-hwdata-')
|
||||
);
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const sourcePath = path.join(root, 'sources', 'hwdata');
|
||||
const prefix = path.join(root, 'runtime');
|
||||
fs.mkdirSync(sourcePath, { recursive: true });
|
||||
const pnpIds = 'ABC Example Display Vendor\n';
|
||||
fs.writeFileSync(path.join(sourcePath, 'pnp.ids'), pnpIds);
|
||||
|
||||
const invocations = [];
|
||||
const buildEnvironment = {
|
||||
PATH: '/usr/bin',
|
||||
PKG_CONFIG_PATH: '/host/pkgconfig',
|
||||
PKG_CONFIG_LIBDIR:
|
||||
'/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig',
|
||||
};
|
||||
const pinnedEnvironment = preparePinnedHwdataBuildInput({
|
||||
buildEnvironment,
|
||||
prefix,
|
||||
runCapture(command, args, options) {
|
||||
invocations.push({ args, command, env: options.env });
|
||||
assert.equal(options.env.PKG_CONFIG_PATH.includes('/host'), false);
|
||||
assert.equal(options.env.PKG_CONFIG_LIBDIR.includes('/usr'), false);
|
||||
if (args[0] === '--variable=pcfiledir') {
|
||||
return path.join(prefix, 'share', 'pkgconfig');
|
||||
}
|
||||
if (args[0] === '--variable=pkgdatadir') {
|
||||
return path.join(prefix, 'share', 'hwdata');
|
||||
}
|
||||
if (args[0] === '--modversion') {
|
||||
return '0.409';
|
||||
}
|
||||
throw new Error(`Unexpected pkg-config query: ${args.join(' ')}`);
|
||||
},
|
||||
sourcePath,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
fs.readFileSync(
|
||||
path.join(prefix, 'share', 'hwdata', 'pnp.ids'),
|
||||
'utf8'
|
||||
),
|
||||
pnpIds
|
||||
);
|
||||
const pkgConfig = fs.readFileSync(
|
||||
path.join(prefix, 'share', 'pkgconfig', 'hwdata.pc'),
|
||||
'utf8'
|
||||
);
|
||||
assert.match(pkgConfig, /^Version: 0\.409$/m);
|
||||
assert.match(
|
||||
pkgConfig,
|
||||
new RegExp(
|
||||
`^pkgdatadir=${path
|
||||
.join(prefix, 'share', 'hwdata')
|
||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`,
|
||||
'm'
|
||||
)
|
||||
);
|
||||
assert.doesNotMatch(pkgConfig, /\/usr\/share\/hwdata/);
|
||||
assert.equal(pinnedEnvironment.PKG_CONFIG_PATH.includes('/host'), false);
|
||||
assert.equal(pinnedEnvironment.PKG_CONFIG_LIBDIR.includes('/usr'), false);
|
||||
assert.equal(
|
||||
resolveLinuxPackageBuildEnvironment('libdisplay-info', {
|
||||
buildEnvironment,
|
||||
hwdataBuildEnvironment: pinnedEnvironment,
|
||||
}),
|
||||
pinnedEnvironment
|
||||
);
|
||||
assert.equal(
|
||||
resolveLinuxPackageBuildEnvironment('mpv', {
|
||||
buildEnvironment,
|
||||
hwdataBuildEnvironment: pinnedEnvironment,
|
||||
}),
|
||||
buildEnvironment
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
resolveLinuxPackageBuildEnvironment('libdisplay-info', {
|
||||
buildEnvironment,
|
||||
}),
|
||||
/libdisplay-info.*pinned hwdata/i
|
||||
);
|
||||
assert.deepEqual(
|
||||
invocations.map(({ args, command }) => [command, ...args]),
|
||||
[
|
||||
['pkg-config', '--variable=pcfiledir', 'hwdata'],
|
||||
['pkg-config', '--variable=pkgdatadir', 'hwdata'],
|
||||
['pkg-config', '--modversion', 'hwdata'],
|
||||
]
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
preparePinnedHwdataBuildInput({
|
||||
buildEnvironment,
|
||||
prefix: path.join(root, 'rejected-runtime'),
|
||||
runCapture(_command, args) {
|
||||
return args[0] === '--modversion'
|
||||
? '0.409'
|
||||
: '/usr/share/hwdata';
|
||||
},
|
||||
sourcePath,
|
||||
}),
|
||||
/pinned hwdata.*host|host.*hwdata|resolved outside/i
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects duplicate option assignments in every Meson recipe', () => {
|
||||
assert.doesNotThrow(() =>
|
||||
assertUniqueMesonOptionAssignments(BUILD_RECIPES)
|
||||
@@ -1309,6 +1444,13 @@ test('generates a hash-complete manifest accepted by the Linux validator', (t) =
|
||||
assert.doesNotMatch(manifest.sourceDistribution, /TBD|TODO/i);
|
||||
assert.match(manifest.sourceDistribution, /source archives/i);
|
||||
assert.match(manifest.sourceDistribution, /libdisplay-info/i);
|
||||
assert.match(manifest.sourceDistribution, /hwdata/i);
|
||||
assert.match(manifest.sourceDistribution, /pnp\.ids/i);
|
||||
assert.deepEqual(manifest.packages.hwdata.buildInput, {
|
||||
consumer: 'libdisplay-info',
|
||||
relativePath: 'pnp.ids',
|
||||
purpose: 'PNP vendor lookup table compiled into libdisplay-info.',
|
||||
});
|
||||
});
|
||||
|
||||
test('guards the CLI to Linux x64 and requires exactly one output prefix', () => {
|
||||
|
||||
@@ -192,6 +192,24 @@ function validatePackages(errors, packages) {
|
||||
);
|
||||
}
|
||||
|
||||
if (pinnedPackage.buildInput) {
|
||||
if (
|
||||
!isObject(packageMetadata.buildInput) ||
|
||||
!isDeepStrictEqual(
|
||||
packageMetadata.buildInput,
|
||||
pinnedPackage.buildInput
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
`Linux runtime manifest ${label}.buildInput must equal the pinned build input.`
|
||||
);
|
||||
}
|
||||
} else if (packageMetadata.buildInput !== undefined) {
|
||||
errors.push(
|
||||
`Linux runtime manifest ${label} must not include buildInput.`
|
||||
);
|
||||
}
|
||||
|
||||
if (pinnedPackage.sourceKind === 'archive') {
|
||||
if (
|
||||
typeof packageMetadata.sourceSha256 === 'string' &&
|
||||
@@ -880,10 +898,18 @@ function validateLinuxRuntimeManifest(manifest) {
|
||||
errors.push(
|
||||
'Linux runtime manifest sourceDistribution must be a non-empty string.'
|
||||
);
|
||||
} else if (!/\blibdisplay-info\b/i.test(manifest.sourceDistribution)) {
|
||||
errors.push(
|
||||
'Linux runtime manifest sourceDistribution must explicitly include libdisplay-info.'
|
||||
);
|
||||
} else {
|
||||
for (const [sourceName, sourcePattern] of [
|
||||
['hwdata', /\bhwdata\b/i],
|
||||
['pnp.ids', /\bpnp\.ids\b/i],
|
||||
['libdisplay-info', /\blibdisplay-info\b/i],
|
||||
]) {
|
||||
if (!sourcePattern.test(manifest.sourceDistribution)) {
|
||||
errors.push(
|
||||
`Linux runtime manifest sourceDistribution must explicitly include ${sourceName}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateRuntimeFiles(errors, manifest.runtimeFiles);
|
||||
|
||||
@@ -54,6 +54,9 @@ function sourcePackageRecord(sourcePackage) {
|
||||
...(sourcePackage.sourceTag
|
||||
? { sourceTag: sourcePackage.sourceTag }
|
||||
: {}),
|
||||
...(sourcePackage.buildInput
|
||||
? { buildInput: structuredClone(sourcePackage.buildInput) }
|
||||
: {}),
|
||||
...(sourcePackage.sourceKind === 'archive'
|
||||
? { sourceSha256: sourcePackage.expectedSha256 }
|
||||
: {
|
||||
@@ -131,7 +134,7 @@ function createValidManifest(
|
||||
mesonFlags: ['-Dlibmpv=true', '-Dgpl=false'],
|
||||
},
|
||||
sourceDistribution:
|
||||
'Publish the exact source archives, including the pinned MIT-licensed libdisplay-info source archive.',
|
||||
'Publish the exact source archives, including pinned hwdata pnp.ids and the MIT-licensed libdisplay-info source archive.',
|
||||
runtimeFiles,
|
||||
runtimeTotalBytes: runtimeFiles.reduce(
|
||||
(total, runtimeEntry) => total + (runtimeEntry?.size ?? 0),
|
||||
@@ -322,6 +325,14 @@ test('requires pinned archive hashes and exact libplacebo git provenance', () =>
|
||||
/packages\.libplacebo\.sourceGitCommit must equal the pinned commit/
|
||||
);
|
||||
|
||||
const hostHwdataInput = createValidManifest();
|
||||
hostHwdataInput.packages.hwdata.buildInput.relativePath =
|
||||
'/usr/share/hwdata/pnp.ids';
|
||||
assert.match(
|
||||
validateLinuxRuntimeManifest(hostHwdataInput).join('\n'),
|
||||
/packages\.hwdata\.buildInput must equal the pinned build input/
|
||||
);
|
||||
|
||||
const validSubmoduleRecord = `${'a'.repeat(40)} 3rdparty/example`;
|
||||
for (const sourceSubmodules of [
|
||||
[],
|
||||
@@ -583,12 +594,15 @@ test('requires exact portable ABI and external configuration records', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('requires the source-distribution statement to name libdisplay-info', () => {
|
||||
test('requires the source-distribution statement to name pinned display data', () => {
|
||||
const manifest = createValidManifest();
|
||||
manifest.sourceDistribution =
|
||||
'Publish all of the other source archives with the binary release.';
|
||||
const errors = validateLinuxRuntimeManifest(manifest).join('\n');
|
||||
assert.match(errors, /sourceDistribution must explicitly include hwdata/);
|
||||
assert.match(errors, /sourceDistribution must explicitly include pnp\.ids/);
|
||||
assert.match(
|
||||
validateLinuxRuntimeManifest(manifest).join('\n'),
|
||||
errors,
|
||||
/sourceDistribution must explicitly include libdisplay-info/
|
||||
);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user