Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,26 @@ distributed by their own registries, not redistributed inside our binaries.
- The MediaPipe **JavaScript** solution and its two ~5.6 MB WASM builds are no longer
bundled: inference moved into the native compositor, and nothing loaded them.

## Microsoft OpenMP runtime — `vcomp140.dll` (Windows only)
## Microsoft Visual C++ runtime — `vcomp140.dll`, `msvcp140*.dll`, `vcruntime140*.dll` (Windows only)

- **Component**: `resources/electron/native/bin/win32-x64/vcomp140.dll`.
- **Components**: under `resources/electron/native/bin/win32-x64/` —
`vcomp140.dll`, `msvcp140.dll`, `msvcp140_1.dll`, `vcruntime140.dll`,
`vcruntime140_1.dll`.
- **License**: redistributable under the Microsoft Visual C++ Redistributable
terms accompanying Visual Studio; the copy shipped is taken from the
`VC\Redist\MSVC\<version>\x64\Microsoft.VC<nnn>.OpenMP\` directory of the
Visual Studio installation that builds the release, never from `System32`.
- **Why it ships**: the ggml backends above are compiled with OpenMP and import
it. It is **not** part of Windows, so without it `whisper-stt-server` dies in
the loader before `main()` on any machine that has no Visual C++
Redistributable, and transcription and captions fail with no usable error.
Staged by `scripts/stage-vcomp-runtime.mjs`; `scripts/before-pack.cjs` refuses
to package if it is missing while anything still imports it.
terms accompanying Visual Studio; the copies shipped are taken from the
`VC\Redist\MSVC\<version>\x64\Microsoft.VC<nnn>.OpenMP\` and
`…\Microsoft.VC<nnn>.CRT\` directories of the Visual Studio installation that
builds the release, never from `System32`.
- **Why they ship**: two prebuilt binaries in the payload import them, and
neither is ours to recompile against the static CRT. The ggml backends above
are compiled with OpenMP and import `vcomp140.dll`; the vendored ONNX Runtime
imports the CRT proper. None of these are **part of Windows**, so without them
`whisper-stt-server` dies in the loader before `main()` on any machine that has
no Visual C++ Redistributable — transcription and captions fail with no usable
error — and `onnxruntime.dll` fails to load, leaving the camera background
silently inert. Staged by `scripts/stage-vcomp-runtime.mjs`;
`scripts/before-pack.cjs` refuses to package if any is missing while something
still imports it.

## PipeWire — headers (Linux only)

Expand Down
94 changes: 61 additions & 33 deletions scripts/stage-vcomp-runtime.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Stages vcomp140.dll beside the whisper/ggml payload it is loaded by.
// Stages the Visual C++ runtime DLLs that the prebuilt payload imports, beside it.
//
// Two independent binaries need this, for the same reason and with the same fix.
//
// ggml-base.dll and ggml-cpu.dll are compiled with OpenMP, so they import
// vcomp140.dll — Microsoft's OpenMP runtime, which ships with the Visual C++
Expand All @@ -8,16 +10,26 @@
// and transcription fail with the unactionable timeout described in
// scripts/before-pack.cjs.
//
// onnxruntime.dll — vendored by scripts/fetch-onnxruntime.mjs for the camera
// background segmentation — imports the CRT proper: msvcp140, msvcp140_1,
// vcruntime140, vcruntime140_1. It arrives as an upstream release binary, so
// `-C target-feature=+crt-static` is not available the way it is for our own Rust
// addon; the only remedy left is the one before-pack names, which is this file.
// Without it `checkWinNoRedistDependency` refuses to pack at all, and the Windows
// installer cannot be built — while `WIN_REQUIRED` in the same hook refuses to pack
// *without* onnxruntime.dll, so dropping it is not an escape either.
//
// This is the same class of failure that Store certification rejected 1.9.1 for,
// and it survived that fix because the guard only looked for msvcp/vcruntime/concrt
// prefixes — `vcomp` matches none of them. The guard now covers the whole family
// and, more usefully, only objects when the DLL is not shipped alongside.
//
// Shipping the DLL rather than rebuilding whisper without OpenMP is deliberate:
// Shipping the DLLs rather than rebuilding without them is deliberate. For whisper
// it leaves the computation byte-for-byte identical, where -DGGML_OPENMP=OFF would
// swap OpenMP's scheduler for ggml's own and change transcription throughput by an
// amount nobody has measured. 200 KB against that unknown is a cheap trade. If the
// dependency ever becomes inconvenient, measure first, then switch.
// amount nobody has measured. For ONNX Runtime there is nothing to rebuild. ~1 MB
// against that is a cheap trade. If the dependency ever becomes inconvenient,
// measure first, then switch.

import fs from "node:fs";
import path from "node:path";
Expand All @@ -27,10 +39,19 @@ import { findVcVarsAll } from "./msvcEnv.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "..");
const DEST_DIR = path.join(ROOT, "electron", "native", "bin", "win32-x64");
const DLL = "vcomp140.dll";
// Lower-case, because that is how they are compared against `readdirSync` names.
// vcomp140 lives in Microsoft.VC<nnn>.OpenMP, the other four in Microsoft.VC<nnn>.CRT —
// sibling directories under the same Redist tree, so one walk finds them all.
const DLLS = [
"vcomp140.dll",
"msvcp140.dll",
"msvcp140_1.dll",
"vcruntime140.dll",
"vcruntime140_1.dll",
];

if (process.platform !== "win32") {
console.log("Skipping OpenMP runtime staging: Windows-only.");
console.log("Skipping Visual C++ runtime staging: Windows-only.");
process.exit(0);
}

Expand Down Expand Up @@ -65,8 +86,11 @@ function searchRoots() {
];
}

/** Candidate paths per DLL name, from ONE walk — the trees are large enough that
* walking them once per name would be the slowest part of the build. */
function findRedistCopies() {
const found = [];
const wanted = new Set(DLLS);
const found = new Map(DLLS.map((name) => [name, []]));
const walk = (dir, depth) => {
if (depth > 8) return;
let entries;
Expand All @@ -77,52 +101,56 @@ function findRedistCopies() {
}
for (const entry of entries) {
const full = path.join(dir, entry.name);
const lower = entry.name.toLowerCase();
if (entry.isDirectory()) {
walk(full, depth + 1);
} else if (
entry.name.toLowerCase() === DLL &&
/\\Redist\\/i.test(full) &&
/\\x64\\/i.test(full)
) {
} else if (wanted.has(lower) && /\\Redist\\/i.test(full) && /\\x64\\/i.test(full)) {
// `onecore\x64` is a trimmed variant for Windows Core headless SKUs; the
// desktop app wants the ordinary one.
if (!/\\onecore\\/i.test(full)) found.push(full);
if (!/\\onecore\\/i.test(full)) found.get(lower).push(full);
}
}
};
for (const root of searchRoots()) walk(root, 0);
return found;
}

const candidates = findRedistCopies();
if (candidates.length === 0) {
throw new Error(
`Could not find a redistributable ${DLL} under any Visual Studio installation.\n\n` +
"It lives in VC\\Redist\\MSVC\\<version>\\x64\\Microsoft.VC<nnn>.OpenMP\\.\n" +
"Install the Visual Studio C++ workload, which is required to build the native\n" +
"helpers anyway. Without this file the shipped whisper/ggml libraries cannot load\n" +
"on a machine that has no Visual C++ Redistributable, and transcription fails there\n" +
"with no usable error.",
);
}

// Newest by file version, so a machine carrying several toolsets stages the latest.
const versionOf = (file) => {
const match = file.match(/MSVC\\(\d+(?:\.\d+)*)\\/i);
return match ? match[1].split(".").map(Number) : [0];
};
candidates.sort((a, b) => {
const newestFirst = (a, b) => {
const [x, y] = [versionOf(a), versionOf(b)];
for (let i = 0; i < Math.max(x.length, y.length); i++) {
if ((x[i] ?? 0) !== (y[i] ?? 0)) return (y[i] ?? 0) - (x[i] ?? 0);
}
return 0;
});
};

const source = candidates[0];
fs.mkdirSync(DEST_DIR, { recursive: true });
const dest = path.join(DEST_DIR, DLL);
fs.copyFileSync(source, dest);
const copies = findRedistCopies();

// Report every missing name at once. Staging four of five and failing on the fifth
// would send someone back through the same install-and-retry loop per DLL.
const missing = DLLS.filter((name) => copies.get(name).length === 0);
if (missing.length > 0) {
throw new Error(
`Could not find a redistributable ${missing.join(", ")} under any Visual Studio installation.\n\n` +
"They live in VC\\Redist\\MSVC\\<version>\\x64\\ — vcomp140.dll under\n" +
"Microsoft.VC<nnn>.OpenMP, the rest under Microsoft.VC<nnn>.CRT.\n" +
"Install the Visual Studio C++ workload, which is required to build the native\n" +
"helpers anyway. Without these files the shipped whisper/ggml libraries and the\n" +
"ONNX Runtime cannot load on a machine that has no Visual C++ Redistributable:\n" +
"transcription fails there with no usable error, and the camera background is\n" +
"silently inert. before-pack refuses to package either way.",
);
}

console.log(`Staged ${DLL} from ${source}`);
console.log(` -> ${path.relative(ROOT, dest)}`);
fs.mkdirSync(DEST_DIR, { recursive: true });
for (const name of DLLS) {
const source = copies.get(name).sort(newestFirst)[0];
const dest = path.join(DEST_DIR, name);
fs.copyFileSync(source, dest);
console.log(`Staged ${name} from ${source}`);
console.log(` -> ${path.relative(ROOT, dest)}`);
}
6 changes: 6 additions & 0 deletions technical-documentation/engineering/build-and-packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ Two lessons, and the second is the useful one:

The remedy here is to ship it: `scripts/stage-vcomp-runtime.mjs` copies `vcomp140.dll` out of the Visual Studio redistributable directory into the payload, and `win.extraResources` carries it like everything else in that folder. Shipping rather than rebuilding whisper with `-DGGML_OPENMP=OFF` is deliberate — the DLL leaves the computation identical, where dropping OpenMP swaps its scheduler for ggml's own and changes transcription throughput by an amount nobody has measured. 200 KB against that unknown is a cheap trade; measure before revisiting it.

#### The CRT proper, via ONNX Runtime

The camera-background feature vendors `onnxruntime.dll` (`scripts/fetch-onnxruntime.mjs`), an upstream release binary that imports `msvcp140`, `msvcp140_1`, `vcruntime140` and `vcruntime140_1`. It is not ours to rebuild, so `-C target-feature=+crt-static` — the answer for our own Rust addon — does not apply, and the guard's other exit does not either: `WIN_REQUIRED` in the same hook refuses to package *without* `onnxruntime.dll`, because a build missing it degrades cleanly and silently into a camera-background control that does nothing. Requiring the DLL and refusing its imports left the Windows installer unbuildable from the moment the feature landed, and nothing noticed, because `build.yml` only runs on dispatch or a tag and no Windows build had been dispatched since.

So the same remedy covers both: `stage-vcomp-runtime.mjs` stages the CRT set alongside `vcomp140.dll`, all five from the redistributable directory of the building toolchain, and the guard stops objecting because the imports now ship. **The check that this is complete is the build itself** — `before-pack` reads the real import table of the real payload and refuses on anything still unshipped, which is a stronger statement than any unit test of the staging script could make.

**Local testing cannot confirm this class of fix.** This machine has the redistributable and always will, so a successful run here proves the build is not broken — it says nothing about the clean-machine behaviour. The import table is the only evidence for that half, which is why the guard reads it rather than running anything.

Everything the payload needs from outside itself is now either shipped beside it or present on every Windows edition — with one exception worth knowing: `wgc-capture.exe` imports `mf.dll`, `mfplat.dll` and `mfreadwrite.dll`, and **Media Foundation is absent from Windows N editions** unless the user installs the Media Feature Pack. Recording would fail there with the same `0xC0000135` as above. Untested and unhandled; N editions are sold in Europe.
Expand Down
Loading
Loading