fix(embedded-mpv): pin Linux display data

This commit is contained in:
4gray committed 2026-07-17 22:42:36 +02:00
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.
@@ -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.
+170 -1
View File
@@ -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,
+25 -3
View File
@@ -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;
+143 -1
View File
@@ -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', () => {
+30 -4
View File
@@ -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/
);
});