@openmixer/plugin-qualify 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/plugin-qualify-records-package.mjs +9 -7
- package/bin/plugin-qualify.mjs +34 -0
- package/dist/distributed-records.d.ts +16 -3
- package/dist/distributed-records.js +38 -4
- package/dist/distributed-records.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/judged-layer.d.ts +91 -0
- package/dist/judged-layer.js +144 -0
- package/dist/judged-layer.js.map +1 -0
- package/dist/judgement-line.d.ts +6 -0
- package/dist/judgement-line.js +20 -0
- package/dist/judgement-line.js.map +1 -0
- package/dist/measurement/cli.d.ts +2 -0
- package/dist/measurement/cli.js +25 -2
- package/dist/measurement/cli.js.map +1 -1
- package/dist/measurement/format.d.ts +2 -0
- package/dist/measurement/format.js +5 -0
- package/dist/measurement/format.js.map +1 -1
- package/dist/qualification-record.d.ts +8 -0
- package/dist/qualification-record.js +12 -2
- package/dist/qualification-record.js.map +1 -1
- package/dist/qualify-isolated.d.ts +5 -3
- package/dist/qualify-isolated.js +6 -4
- package/dist/qualify-isolated.js.map +1 -1
- package/dist/qualify-sync.d.ts +7 -2
- package/dist/qualify-sync.js +21 -11
- package/dist/qualify-sync.js.map +1 -1
- package/dist/records-package.d.ts +31 -4
- package/dist/records-package.js +149 -24
- package/dist/records-package.js.map +1 -1
- package/fixtures/records-rpi5-0.19.1/http___drumkv1.sourceforge.net_lv2@0.2-7d179bdcf9e5.default.probe.json +58 -0
- package/fixtures/records-rpi5-0.19.1/http___drumkv1.sourceforge.net_lv2@0.2-7d179bdcf9e5.default.qualification.json +305 -0
- package/fixtures/records-rpi5-0.19.1/http___samplv1.sourceforge.net_lv2@0.2-c9cd8fb3102f.default.probe.json +58 -0
- package/fixtures/records-rpi5-0.19.1/http___samplv1.sourceforge.net_lv2@0.2-c9cd8fb3102f.default.qualification.json +301 -0
- package/package.json +2 -2
- package/src/distributed-records.ts +41 -4
- package/src/index.ts +2 -0
- package/src/judged-layer.ts +197 -0
- package/src/judgement-line.ts +22 -0
- package/src/measurement/cli.ts +26 -2
- package/src/measurement/format.ts +8 -0
- package/src/qualification-record.ts +19 -2
- package/src/qualify-isolated.ts +6 -4
- package/src/qualify-sync.ts +22 -10
- package/src/records-package.ts +156 -25
- package/tools/lv2-measure.mjs +21 -2
package/src/records-package.ts
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* `plugin-qualify-records-package`: the Debian package of a device's qualification records, so other devices of the
|
|
5
5
|
* machine class install them as the distributed layer instead of measuring the same plugin set again.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* A record set is per machine class, rate and quantum (#317). In: the qualification directories of a run, one per
|
|
8
|
+
* cell it was measured at, as plugin-qualify-sync leaves them, or the run's root holding `b<rate>-<quantum>/qualification`.
|
|
9
|
+
* Out: one `plugin-db-qualification-<class>-<set>-<rate>-<quantum>_<version>_all.deb` per cell, a package of plugin-db's
|
|
10
|
+
* qualification layer, installing every record of that cell and the probe output it judged under
|
|
11
|
+
* `/usr/share/omx/qualification/<class>/<set>-<rate>-<quantum>/`, beside the set's `provenance.json`, which names the
|
|
12
|
+
* cell. A directory's cell is the one its base is named for (`b48000-512`), else `--cell`, else the one cell every
|
|
13
|
+
* record's grid holds; a record whose grid does not hold its set's cell stays out, named. A record goes in only with its probe output, byte for byte the copy the record names;
|
|
11
14
|
* an unmeasured record never applies and stays out. The archive is written here, `ar` and two `tar.xz` members, so a
|
|
12
15
|
* machine without dpkg builds it too; the tar members are GNU tar's, owned by root, in name order, at one time.
|
|
13
16
|
*/
|
|
@@ -15,14 +18,18 @@ import { createHash } from "node:crypto";
|
|
|
15
18
|
import { spawnSync } from "node:child_process";
|
|
16
19
|
import { closeSync, copyFileSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
17
20
|
import { tmpdir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
21
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
19
22
|
|
|
20
|
-
import {
|
|
23
|
+
import { cellKey, type GridCell } from "./measurement-grid.js";
|
|
24
|
+
import { DISTRIBUTED_DIR_SYSTEM, recordCells, DISTRIBUTED_PROVENANCE_FILE, DISTRIBUTED_PROVENANCE_SCHEMA, type DistributedProvenance } from "./distributed-records.js";
|
|
21
25
|
import { sha256Hex } from "./probe-output.js";
|
|
22
26
|
import { QUALIFICATION_RECORD_SCHEMA } from "./qualification-record.js";
|
|
23
27
|
|
|
24
28
|
export interface RecordsPackageArgs {
|
|
25
|
-
|
|
29
|
+
/** Qualification directories, or run roots holding `b<rate>-<quantum>/qualification`: one set per cell. */
|
|
30
|
+
readonly sources: readonly string[];
|
|
31
|
+
/** The cell of a lone directory whose name says none. */
|
|
32
|
+
readonly cell?: GridCell;
|
|
26
33
|
readonly hostClass: string;
|
|
27
34
|
readonly set: string;
|
|
28
35
|
/** A JSON file, or the JSON itself. */
|
|
@@ -44,18 +51,22 @@ export function recordsPackageHelp(): string {
|
|
|
44
51
|
return [
|
|
45
52
|
"plugin-qualify-records-package: the Debian package of a device's qualification records, for the distributed layer",
|
|
46
53
|
"",
|
|
47
|
-
" plugin-qualify-records-package <qualification dir
|
|
54
|
+
" plugin-qualify-records-package <qualification dir|run root>... --host-class <class> --set <set> --provenance <json> [options]",
|
|
55
|
+
"",
|
|
56
|
+
" one package per cell (rate and quantum): each qualification directory is a cell, named by its base",
|
|
57
|
+
" (<run>/b48000-512/qualification); a run root stands for every b<rate>-<quantum>/qualification under it",
|
|
48
58
|
"",
|
|
49
59
|
" --host-class <class> the machine class the records were measured on (rpi5): the directory they install in",
|
|
50
|
-
" --set <set> the plugin set they cover (zynthian-vangelis)",
|
|
60
|
+
" --set <set> the plugin set they cover (zynthian-vangelis); each package's set is <set>-<rate>-<quantum>",
|
|
61
|
+
" --cell <rate>/<quantum> the cell of a lone directory not named b<rate>-<quantum> whose records hold several",
|
|
51
62
|
" --provenance <json> a JSON file, or the JSON itself: the image, the package versions, the source revisions",
|
|
52
63
|
" --version <version> the package's version (default: today, 2026.10.29)",
|
|
53
64
|
" --out <dir> where the .deb is written (default: .)",
|
|
54
65
|
" --maintainer <who> the package's Maintainer field",
|
|
55
66
|
" --help this text",
|
|
56
67
|
"",
|
|
57
|
-
`
|
|
58
|
-
"exit 0
|
|
68
|
+
`each package installs ${DISTRIBUTED_DIR_SYSTEM}/<class>/<set>-<rate>-<quantum>/ and depends on this plugin-qualify's minor, up to the next one`,
|
|
69
|
+
"exit 0 every package was written, 2 one could not be",
|
|
59
70
|
"",
|
|
60
71
|
].join("\n");
|
|
61
72
|
}
|
|
@@ -63,7 +74,7 @@ export function recordsPackageHelp(): string {
|
|
|
63
74
|
export function parseRecordsPackageArgs(argv: readonly string[]): { ok: true; args: RecordsPackageArgs } | { ok: true; help: true } | { ok: false; error: string } {
|
|
64
75
|
const values = new Map<string, string>();
|
|
65
76
|
const positional: string[] = [];
|
|
66
|
-
const valued = new Set(["--host-class", "--set", "--provenance", "--version", "--out", "--maintainer"]);
|
|
77
|
+
const valued = new Set(["--host-class", "--set", "--cell", "--provenance", "--version", "--out", "--maintainer"]);
|
|
67
78
|
for (let i = 0; i < argv.length; i++) {
|
|
68
79
|
const a = argv[i]!;
|
|
69
80
|
if (a === "--help") return { ok: true, help: true };
|
|
@@ -74,19 +85,24 @@ export function parseRecordsPackageArgs(argv: readonly string[]): { ok: true; ar
|
|
|
74
85
|
} else if (a.startsWith("--")) return { ok: false, error: `unknown argument ${a}` };
|
|
75
86
|
else positional.push(a);
|
|
76
87
|
}
|
|
77
|
-
if (positional.length
|
|
88
|
+
if (positional.length === 0) return { ok: false, error: "name a qualification directory or a run root" };
|
|
78
89
|
for (const flag of ["--host-class", "--set", "--provenance"]) if (!values.has(flag)) return { ok: false, error: `${flag} is needed` };
|
|
79
90
|
const hostClass = values.get("--host-class")!;
|
|
80
91
|
const set = values.get("--set")!;
|
|
81
92
|
for (const [what, v] of [["--host-class", hostClass], ["--set", set]] as const) {
|
|
82
93
|
if (!DEB_WORD.test(v)) return { ok: false, error: `${what} ${v}: a package name holds lowercase letters, digits, + - and . only` };
|
|
83
94
|
}
|
|
95
|
+
const cellText = values.get("--cell");
|
|
96
|
+
const cell = cellText === undefined ? undefined : parseCell(cellText);
|
|
97
|
+
if (cellText !== undefined && cell === undefined) return { ok: false, error: `--cell ${cellText} is not <rate>/<quantum>` };
|
|
98
|
+
if (cell !== undefined && positional.length !== 1) return { ok: false, error: "--cell names the cell of one directory; name one" };
|
|
84
99
|
const version = values.get("--version");
|
|
85
100
|
if (version !== undefined && !DEB_VERSION.test(version)) return { ok: false, error: `--version ${version} is not a Debian version` };
|
|
86
101
|
return {
|
|
87
102
|
ok: true,
|
|
88
103
|
args: {
|
|
89
|
-
|
|
104
|
+
sources: positional,
|
|
105
|
+
...(cell === undefined ? {} : { cell }),
|
|
90
106
|
hostClass,
|
|
91
107
|
set,
|
|
92
108
|
provenance: values.get("--provenance")!,
|
|
@@ -97,6 +113,68 @@ export function parseRecordsPackageArgs(argv: readonly string[]): { ok: true; ar
|
|
|
97
113
|
};
|
|
98
114
|
}
|
|
99
115
|
|
|
116
|
+
function parseCell(text: string): GridCell | undefined {
|
|
117
|
+
const m = /^(\d+)\/(\d+)$/.exec(text.trim());
|
|
118
|
+
if (m === null) return undefined;
|
|
119
|
+
const [rate, quantum] = [Number(m[1]), Number(m[2])];
|
|
120
|
+
return rate > 0 && quantum > 0 && Number.isSafeInteger(rate) && Number.isSafeInteger(quantum) ? { rate, quantum } : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A run's base directory names its cell: `b48000-512`. */
|
|
124
|
+
const BASE_NAME = /^b(\d+)-(\d+)$/;
|
|
125
|
+
|
|
126
|
+
/** The set of a cell: `<set>-<rate>-<quantum>`. */
|
|
127
|
+
export const recordsSetName = (set: string, cell: GridCell): string => `${set}-${String(cell.rate)}-${String(cell.quantum)}`;
|
|
128
|
+
|
|
129
|
+
/** One qualification directory of a run and the cell its name says, when it says one. */
|
|
130
|
+
export interface RecordsSource {
|
|
131
|
+
readonly dir: string;
|
|
132
|
+
readonly named?: GridCell;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The qualification directories the arguments stand for, in name order: a directory holding records is one; a run
|
|
137
|
+
* root stands for every `b<rate>-<quantum>/qualification` (or `b<rate>-<quantum>` holding records) under it.
|
|
138
|
+
*/
|
|
139
|
+
export function recordsSources(args: readonly string[]): RecordsSource[] {
|
|
140
|
+
const holdsRecords = (d: string): boolean => {
|
|
141
|
+
try {
|
|
142
|
+
return readdirSync(d).some((n) => n.endsWith(".qualification.json") || n.endsWith(".unmeasured.json"));
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const namedBy = (d: string): GridCell | undefined => {
|
|
148
|
+
for (const part of [basename(d), basename(dirname(d))]) {
|
|
149
|
+
const m = BASE_NAME.exec(part);
|
|
150
|
+
if (m !== null) return { rate: Number(m[1]), quantum: Number(m[2]) };
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
};
|
|
154
|
+
const out: RecordsSource[] = [];
|
|
155
|
+
for (const a of args) {
|
|
156
|
+
const dir = resolve(a);
|
|
157
|
+
statSync(dir);
|
|
158
|
+
if (holdsRecords(dir)) {
|
|
159
|
+
const named = namedBy(dir);
|
|
160
|
+
out.push({ dir, ...(named === undefined ? {} : { named }) });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const bases = readdirSync(dir).filter((n) => BASE_NAME.test(n)).sort();
|
|
164
|
+
let found = 0;
|
|
165
|
+
for (const b of bases) {
|
|
166
|
+
for (const d of [join(dir, b, "qualification"), join(dir, b)]) {
|
|
167
|
+
if (!holdsRecords(d)) continue;
|
|
168
|
+
out.push({ dir: d, named: namedBy(d)! });
|
|
169
|
+
found++;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (found === 0) throw new Error(`${dir} holds no record and no b<rate>-<quantum> base with records`);
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
100
178
|
/** The package of a class and a set. */
|
|
101
179
|
export const recordsPackageName = (hostClass: string, set: string): string => `plugin-db-qualification-${hostClass}-${set}`;
|
|
102
180
|
|
|
@@ -122,7 +200,7 @@ export function today(now = new Date()): { version: string; date: string } {
|
|
|
122
200
|
}
|
|
123
201
|
|
|
124
202
|
/** The files of a qualification directory that go in the package: each record with its probe output, and what stays out, by name and why. */
|
|
125
|
-
export function collectRecords(stateDir: string): { records: string[]; files: string[]; left: { name: string; why: string }[] } {
|
|
203
|
+
export function collectRecords(stateDir: string, cell?: GridCell): { records: string[]; files: string[]; left: { name: string; why: string }[] } {
|
|
126
204
|
const names = readdirSync(stateDir).sort();
|
|
127
205
|
const records: string[] = [];
|
|
128
206
|
const files: string[] = [];
|
|
@@ -133,7 +211,7 @@ export function collectRecords(stateDir: string): { records: string[]; files: st
|
|
|
133
211
|
continue;
|
|
134
212
|
}
|
|
135
213
|
if (!name.endsWith(".qualification.json")) continue;
|
|
136
|
-
let record: { schema?: unknown; probe?: { file?: unknown; sha256?: unknown } };
|
|
214
|
+
let record: { schema?: unknown; probe?: { file?: unknown; sha256?: unknown }; measurements?: { grid?: readonly GridCell[] } };
|
|
137
215
|
try {
|
|
138
216
|
record = JSON.parse(readFileSync(join(stateDir, name), "utf8"));
|
|
139
217
|
} catch {
|
|
@@ -144,6 +222,11 @@ export function collectRecords(stateDir: string): { records: string[]; files: st
|
|
|
144
222
|
left.push({ name, why: `not a ${QUALIFICATION_RECORD_SCHEMA} naming its probe output` });
|
|
145
223
|
continue;
|
|
146
224
|
}
|
|
225
|
+
const cells = gridCellsOf(record);
|
|
226
|
+
if (cell !== undefined && !cells.includes(cellKey(cell))) {
|
|
227
|
+
left.push({ name, why: `measured at ${cells.length === 0 ? "no cell" : cells.join(", ")}, not at its set's ${cellKey(cell)}` });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
147
230
|
let probe: Buffer;
|
|
148
231
|
try {
|
|
149
232
|
probe = readFileSync(join(stateDir, record.probe.file));
|
|
@@ -161,6 +244,29 @@ export function collectRecords(stateDir: string): { records: string[]; files: st
|
|
|
161
244
|
return { records, files: [...new Set(files)].sort(), left };
|
|
162
245
|
}
|
|
163
246
|
|
|
247
|
+
/** The cells of a record's grid, `rate/quantum`, by rate then quantum: `recordCells`, keyed. */
|
|
248
|
+
const gridCellsOf = (record: { measurements?: { grid?: readonly GridCell[] } }): string[] =>
|
|
249
|
+
recordCells({ measurements: { grid: Array.isArray(record.measurements?.grid) ? record.measurements.grid : [] } } as unknown as Parameters<typeof recordCells>[0]).map(cellKey);
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The cell of a source: its base's name, else the one given, else the one cell every record of it holds alone.
|
|
253
|
+
* Throws, naming the cells, when none decides it.
|
|
254
|
+
*/
|
|
255
|
+
export function cellOfSource(source: RecordsSource, given?: GridCell): GridCell {
|
|
256
|
+
if (source.named !== undefined) return source.named;
|
|
257
|
+
if (given !== undefined) return given;
|
|
258
|
+
const seen = new Set<string>();
|
|
259
|
+
for (const name of readdirSync(source.dir).filter((n) => n.endsWith(".qualification.json"))) {
|
|
260
|
+
try {
|
|
261
|
+
for (const c of gridCellsOf(JSON.parse(readFileSync(join(source.dir, name), "utf8")))) seen.add(c);
|
|
262
|
+
} catch {
|
|
263
|
+
// a record that does not read is named by the collection, and decides no cell
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (seen.size === 1) return parseCell([...seen][0]!)!;
|
|
267
|
+
throw new Error(`${source.dir}: its records are measured at ${seen.size === 0 ? "no cell" : [...seen].join(", ")}; a set is one cell: name the base b<rate>-<quantum> or give --cell`);
|
|
268
|
+
}
|
|
269
|
+
|
|
164
270
|
/** The provenance argument read: a file's JSON, or the JSON itself. */
|
|
165
271
|
function readProvenanceArg(arg: string): Record<string, unknown> {
|
|
166
272
|
const text = arg.trimStart().startsWith("{") ? arg : readFileSync(arg, "utf8");
|
|
@@ -203,6 +309,9 @@ function tarXz(tree: string, out: string, mtime: number): void {
|
|
|
203
309
|
|
|
204
310
|
export interface BuiltRecordsPackage {
|
|
205
311
|
readonly path: string;
|
|
312
|
+
/** The set, `<set>-<rate>-<quantum>`, and its cell. */
|
|
313
|
+
readonly set: string;
|
|
314
|
+
readonly cell: GridCell;
|
|
206
315
|
readonly name: string;
|
|
207
316
|
readonly version: string;
|
|
208
317
|
readonly records: number;
|
|
@@ -211,28 +320,49 @@ export interface BuiltRecordsPackage {
|
|
|
211
320
|
readonly left: readonly { name: string; why: string }[];
|
|
212
321
|
}
|
|
213
322
|
|
|
214
|
-
/**
|
|
215
|
-
|
|
216
|
-
|
|
323
|
+
/**
|
|
324
|
+
* One package per cell of `args.sources`, written into `args.out`, in the order of the cells. Every source's cell is
|
|
325
|
+
* decided and no two may share one before any package is written; throws, naming why, when one cannot be built.
|
|
326
|
+
*/
|
|
327
|
+
export function buildRecordsPackages(args: RecordsPackageArgs, now = new Date()): BuiltRecordsPackage[] {
|
|
328
|
+
const sources = recordsSources(args.sources);
|
|
329
|
+
if (args.cell !== undefined && sources.length !== 1) throw new Error("--cell names the cell of one directory, and these are several");
|
|
330
|
+
const cells = sources.map((s) => ({ source: s, cell: cellOfSource(s, args.cell) }));
|
|
331
|
+
const seen = new Map<string, string>();
|
|
332
|
+
for (const { source, cell } of cells) {
|
|
333
|
+
const other = seen.get(cellKey(cell));
|
|
334
|
+
if (other !== undefined) throw new Error(`${source.dir} and ${other} are both measured at ${cellKey(cell)}: one set per cell`);
|
|
335
|
+
seen.set(cellKey(cell), source.dir);
|
|
336
|
+
}
|
|
337
|
+
return cells
|
|
338
|
+
.sort((a, b) => a.cell.rate - b.cell.rate || a.cell.quantum - b.cell.quantum)
|
|
339
|
+
.map(({ source, cell }) => buildRecordsPackage({ ...args, stateDir: source.dir, cell }, now));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** The package of one cell's directory, written into `args.out`. Throws, naming why, when it cannot be built. */
|
|
343
|
+
function buildRecordsPackage(args: RecordsPackageArgs & { readonly stateDir: string; readonly cell: GridCell }, now: Date): BuiltRecordsPackage {
|
|
344
|
+
const set = recordsSetName(args.set, args.cell);
|
|
345
|
+
const name = recordsPackageName(args.hostClass, set);
|
|
217
346
|
const range = recordsDependsRange(ownVersion());
|
|
218
347
|
if (range === undefined) throw new Error("this plugin-qualify names no version, so the package cannot say which one reads it");
|
|
219
348
|
const day = today(now);
|
|
220
349
|
const version = args.version ?? day.version;
|
|
221
350
|
statSync(args.stateDir);
|
|
222
|
-
const { records, files, left } = collectRecords(args.stateDir);
|
|
351
|
+
const { records, files, left } = collectRecords(args.stateDir, args.cell);
|
|
223
352
|
if (records.length === 0) throw new Error(`${args.stateDir} holds no record that can go in the package`);
|
|
224
353
|
const provenance: DistributedProvenance = {
|
|
225
354
|
...readProvenanceArg(args.provenance),
|
|
226
355
|
schema: DISTRIBUTED_PROVENANCE_SCHEMA,
|
|
227
|
-
set
|
|
356
|
+
set,
|
|
228
357
|
hostClass: args.hostClass,
|
|
358
|
+
cell: { rate: args.cell.rate, quantum: args.cell.quantum },
|
|
229
359
|
package: { name, version },
|
|
230
360
|
date: day.date,
|
|
231
361
|
};
|
|
232
362
|
const work = mkdtempSync(join(tmpdir(), "records-package-"));
|
|
233
363
|
try {
|
|
234
364
|
const data = join(work, "data");
|
|
235
|
-
const rel = join("usr", "share", "omx", "qualification", args.hostClass,
|
|
365
|
+
const rel = join("usr", "share", "omx", "qualification", args.hostClass, set);
|
|
236
366
|
const dir = join(data, rel);
|
|
237
367
|
mkdirSync(dir, { recursive: true });
|
|
238
368
|
for (const f of files) copyFileSync(join(args.stateDir, f), join(dir, f));
|
|
@@ -259,9 +389,10 @@ export function buildRecordsPackage(args: RecordsPackageArgs, now = new Date()):
|
|
|
259
389
|
"Section: sound",
|
|
260
390
|
"Priority: optional",
|
|
261
391
|
"Multi-Arch: foreign",
|
|
262
|
-
`Description: plugin-db qualification layer: ${args.set} set for ${args.hostClass}`,
|
|
392
|
+
`Description: plugin-db qualification layer: ${args.set} set for ${args.hostClass} at ${String(args.cell.rate)} Hz, ${String(args.cell.quantum)} frames`,
|
|
263
393
|
` The qualification layer of the plugin database: the plugin-qualify records`,
|
|
264
|
-
` of the ${args.set} plugin set, measured on a ${args.hostClass} device
|
|
394
|
+
` of the ${args.set} plugin set, measured on a ${args.hostClass} device at ${String(args.cell.rate)} Hz`,
|
|
395
|
+
` and a quantum of ${String(args.cell.quantum)} frames. plugin-qualify-sync takes a plugin from them instead of`,
|
|
265
396
|
" measuring it, and only when every file of the installed plugin hashes as the",
|
|
266
397
|
" measured one did; any other plugin is measured on the device as before.",
|
|
267
398
|
"",
|
|
@@ -282,7 +413,7 @@ export function buildRecordsPackage(args: RecordsPackageArgs, now = new Date()):
|
|
|
282
413
|
],
|
|
283
414
|
mtime,
|
|
284
415
|
);
|
|
285
|
-
return { path, name, version, records: records.length, files: installed.length, left };
|
|
416
|
+
return { path, set, cell: args.cell, name, version, records: records.length, files: installed.length, left };
|
|
286
417
|
} finally {
|
|
287
418
|
rmSync(work, { recursive: true, force: true });
|
|
288
419
|
}
|
package/tools/lv2-measure.mjs
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
buildPipeline,
|
|
51
51
|
collectHostFacts,
|
|
52
52
|
HOSTING_PASS_DIMENSIONS,
|
|
53
|
+
PROBED_DIMENSIONS,
|
|
53
54
|
hostingLine,
|
|
54
55
|
INITIAL_PROGRESS,
|
|
55
56
|
parseMeasureArgs,
|
|
@@ -131,6 +132,20 @@ function hostingTools() {
|
|
|
131
132
|
return { tool, interposer };
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The native measurer for the pin step of a run with no hosting pass: absent, the run still measures and says on stderr
|
|
137
|
+
* that its plugins carry no integrity block (it does not need the interposer, so a half-populated pair is not refused here).
|
|
138
|
+
*/
|
|
139
|
+
function pinTools() {
|
|
140
|
+
const { tools } = locateNativeTools([LV2_MEASURE_TOOL_NAME, RT_INTERPOSE_NAME], process.env, existsSync);
|
|
141
|
+
const [tool, interposer] = tools;
|
|
142
|
+
if (tool?.path === undefined) {
|
|
143
|
+
process.stderr.write(`${TOOL_NAME}: no ${LV2_MEASURE_TOOL_NAME} at ${tool?.looked.join(", ") ?? "its places"}: the plugins are written with no integrity block (build it with make -C native or set ${tool?.setting})\n`);
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
return { tool: tool.path, interposer: interposer?.path ?? "" };
|
|
147
|
+
}
|
|
148
|
+
|
|
134
149
|
/** The hosting pass: the measurer once per plugin, each in its own process, its lines written as JSONL. */
|
|
135
150
|
function runHosting(step, uris, onProgress) {
|
|
136
151
|
const lines = [];
|
|
@@ -201,7 +216,8 @@ async function main() {
|
|
|
201
216
|
const options = parsed.options;
|
|
202
217
|
|
|
203
218
|
const hosting = options.dimensions.some((d) => HOSTING_PASS_DIMENSIONS.includes(d));
|
|
204
|
-
const
|
|
219
|
+
const probedOnly = !hosting && options.dimensions.some((d) => PROBED_DIMENSIONS.includes(d));
|
|
220
|
+
const pipeline = buildPipeline(options, hosting ? hostingTools() : probedOnly ? pinTools() : undefined);
|
|
205
221
|
|
|
206
222
|
if (options.dryRun) {
|
|
207
223
|
process.stderr.write(
|
|
@@ -242,11 +258,13 @@ async function main() {
|
|
|
242
258
|
const measuredAt = new Date().toISOString();
|
|
243
259
|
emit({ step: "start", steps: pipeline.steps.map((s) => s.id) });
|
|
244
260
|
let hostingLines;
|
|
261
|
+
let pinLines;
|
|
245
262
|
for (const step of pipeline.steps) {
|
|
246
263
|
process.stderr.write(`== ${step.id}\n`);
|
|
247
264
|
if (step.perPlugin) {
|
|
248
265
|
const uris = options.plugins ?? urisFromScan(readJson(pipeline.hostingScan));
|
|
249
|
-
|
|
266
|
+
if (step.id === "pin") pinLines = runHosting(step, uris, onProgress);
|
|
267
|
+
else hostingLines = runHosting(step, uris, onProgress);
|
|
250
268
|
} else {
|
|
251
269
|
await runStep(step, onProgress);
|
|
252
270
|
}
|
|
@@ -257,6 +275,7 @@ async function main() {
|
|
|
257
275
|
options,
|
|
258
276
|
...(pipeline.finalCatalog === undefined ? {} : { catalog: readJson(pipeline.finalCatalog) }),
|
|
259
277
|
...(hostingLines === undefined ? {} : { hosting: hostingLines }),
|
|
278
|
+
...(pinLines === undefined ? {} : { pins: pinLines }),
|
|
260
279
|
costProvenance:
|
|
261
280
|
pipeline.costProvenance !== undefined && existsSync(pipeline.costProvenance)
|
|
262
281
|
? readJson(pipeline.costProvenance)
|