fix(desktop): keep dots in the app name when resolving the runtime library - #36006
Conversation
…brary `deno desktop --output my-app-2.9.2` produced an app that exited with "No runtime library found" on Linux. Two places treated the text after the last dot of the app name as a file extension. `get_desktop_specific_filepath` used `PathBuf::with_extension`, turning `my-app-2.9.2` into `my-app-2.9.so` and thereby also truncating the app dir and launcher names to `my-app-2.9`. The launcher then did the same thing to itself: laufey's `LaufeyFindColocatedRuntime` chops the executable's file name at the last dot and appends `.so`, so it looked for `my-app-2.so`. Append the dylib extension instead of replacing it, so the compiled library and the app name keep every dot, and name the shipped library on Linux the way the launcher resolves it, so a dotted app name loads the runtime it ships with. Refuse an app name whose resolved library name would land on the launcher or on one of the backend's own libraries, since that would silently overwrite them. Closes denoland#35971
|
Nice fix. One question on scope: the new clobber guard in Should we add the equivalent guard on the Windows side here, or is that intentionally out of scope for this PR? |
The clobber guard added for Linux was missing on the Windows path: `package_windows_app_dir` copies the compiled dylib as `<app>.dll` and renames the backend binary to `<app>.exe` into a directory that is a copy of the LAUFEY backend dir, so `deno desktop --target x86_64-pc-windows-msvc --output d3dcompiler_47` silently overwrote a CEF-shipped DLL and shipped an app that cannot start. Both platforms now share one `reject_backend_file_collision` helper, and both check the launcher name as well as the runtime library name — on Linux `--output chrome-sandbox` could eat a backend file through the rename the same way.
crowlbot
left a comment
There was a problem hiding this comment.
Diagnosis and fix both look right, and the two-sided nature of it (append instead of replace on the build side, mirror the launcher's own chop on the ship side) is the correct shape.
Worth noting the description undersells the scope: this fixes Windows and macOS too. app_name comes from dylib_parts(dylib_path).file_stem(), so before append_extension a --output my-app-2.9.2 silently produced a my-app-2.9.app bundle and a my-app-2.9.dll that the my-app-2.9.2.exe launcher would never find. Windows needs no linux_colocated_runtime_name equivalent because the launcher's chop removes a real .exe, so <app>.dll lines up naturally — that asymmetry is worth a line in the comment, since it looks like an omission otherwise.
linux_colocated_runtime_name mirrors a laufey bug across a repo boundary
Nothing ties the two sides together. If laufey ever fixes LaufeyFindColocatedRuntime to strip only a known extension, every app built by this version of Deno breaks: the launcher would start looking for my-app-2.9.2.so, which we deliberately don't ship. Right now that's an invisible coupling between a Rust function here and C++ behaviour in another repo, discoverable only by an app failing to start.
At minimum the doc comment should name the laufey version whose behaviour it mirrors, so a future bump has something to check against. If the size cost is tolerable, shipping the library under both names would make the app survive the change in either direction.
Relatedly, the dot > 0 carve-out:
// A dot at index 0 is part of the name (`.hidden`), not an extension — the
// launcher treats it that way too.That's asserted as fact and pinned by linux_colocated_runtime_name_leading_dot_is_not_an_extension, but nothing verifies laufey agrees — a plain strrchr(name, '.') would find the dot at index 0 and produce .so. Contrived enough not to matter in practice, but the test reads as if the behaviour were verified.
Test coverage stops short of the code that changed
linux_colocated_runtime_name and reject_backend_file_collision are both well covered in isolation, but nothing exercises the call sites this PR actually rewrote — the dest_dylib == launcher_path bail in package_linux_app_dir (reachable with an app name like myapp.so), or either of the new guards in package_windows_app_dir. Those are the paths where the ordering matters: the guards have to run before the fs::copy and the fs::rename, and that ordering is exactly what a refactor would break silently.
One thing I checked and it's fine: reserve_app_dir + copy_dir_all means the app dir contains only backend files when reject_backend_file_collision runs, so a rebuild can't produce a false "already part of the app" failure from its own previous output.
Addresses review feedback on denoland#36006: - The Linux and Windows packagers had grown near-identical guard sequences that had to run before the fs::copy and fs::rename, with nothing enforcing that ordering. Both now go through resolve_app_dir_targets, which returns the paths only once every check has passed - a caller can't reach a path without having passed them, so a refactor can't reorder them apart. Covered by seven tests exercising the call-site paths that had none: the library-lands-on-the-launcher bail, both Windows guards, the backend-binary-is-the-launcher no-op, and that a rejected name leaves the app dir untouched. - linux_colocated_runtime_name now records that it mirrors laufey's LaufeyFindColocatedRuntime as of 0.7.0, that the coupling is only verified by apps starting, and that a laufey change to that lookup breaks every app built by this version - so bumping laufey means re-checking it. - The leading-dot carve-out no longer claims the launcher agrees; it says outright that it is unverified and what to check if a dotfile-named app fails to start. - Note on the Windows path why it needs no truncation equivalent: its launcher has a real .exe for the chop to consume.
|
@bartlomieju — good catch, and not out of scope: the same guard is on the Windows path as of 3fe7bde, which predates this reply but postdates your question, so it may not have been visible when you asked.
Since your question was really "why does one platform have this and not the other", I've gone further in c743657: both packagers now resolve their paths through a single |
|
Addressing the review — all four in c743657, on top of a Scope was undersoldRight. The description now says outright that macOS and Windows were hit too — The Windows asymmetry now has a comment at the call site: no truncation equivalent is needed there because the launcher is The cross-repo couplingDocumented, with the version. Worth noting the merge made this concrete rather than hypothetical: the branch was pinned to laufey 0.5.0 while I did not ship the library under both names. A hard link makes the on-disk cost nil, but the duplicate lands in the The
|
crowlbot
left a comment
There was a problem hiding this comment.
Re-reviewed at c743657.
All three points are addressed, and resolve_app_dir_targets is a better answer than the one I suggested. Returning AppDirTargets only on success means a caller can't obtain the paths without having passed the checks, so the "guards must run before fs::copy/fs::rename" ordering is enforced by the type rather than by comment — and resolve_app_dir_targets_checks_before_returning_any_path pins exactly that. Extracting it also made the Windows call site testable, which is where the guard was previously unreachable from tests.
The laufey coupling section is the right shape: naming 0.7.0, saying it's verified only by apps starting, and spelling out the failure mode on a bump gives a future reader something concrete to check. And relabelling the .hidden carve-out as unverified — with the strrchr counterexample and how to recognise the symptom — is more useful than the confident version was.
One thing to keep an eye on rather than a review comment: test integration (2/2) debug macos-x86_64 is failing here and on #36573 as well. Same job on both, so it reads as unrelated to either change, but worth a glance before merge.
crowlbot
left a comment
There was a problem hiding this comment.
Following up on the CI question from my last comment — the failure is unrelated to this change:
failed tests:
integration::repl::pty_regex_literal_with_quote
panicked at tests/integration/repl_tests.rs:67:13:
Timed out.
A PTY REPL timeout on macos-x86_64, failing identically on #36573 and #36006, neither of which touches the REPL. Flake — safe to ignore or re-run.
crowlbot
left a comment
There was a problem hiding this comment.
Re-reviewed at the current head — this round is a merge of main with no changes of your own, so nothing new on the code.
Since that merge pulls in #36575 and #36574, which touch the same three desktop files this branch does, I checked the resolution rather than assuming: no conflict markers anywhere, this branch's own changes are intact, and both merged PRs' changes survived in runtime/ops/desktop.rs (op_desktop_alert_async + ERROR_DIALOG_SHOWING from #36575, the .backup gate on the update sentinel from #36574). Clean.
CI is still running; nothing red so far.
…brary (#36006) `deno desktop --output my-app-2.9.2` (or any output basename with a dot in it, e.g. `template-deno2.9.2-desktop-vue3-vite8.AppImage`) produced an app that exited immediately on Linux with: ``` No runtime library found. Set LAUFEY_RUNTIME_PATH or use --runtime <path> ``` Two places treated the text after the last dot of the app name as a file extension: - `get_desktop_specific_filepath` used `PathBuf::with_extension`, so the compiled library for `my-app-2.9.2` became `my-app-2.9.so` — which also truncated the app dir and launcher names to `my-app-2.9`, silently renaming the app. - The launcher then did the same thing to itself: laufey's `LaufeyFindColocatedRuntime` chops its own file name at the last dot and appends `.so`, so the `my-app-2.9` launcher looked for `my-app-2.so`, which doesn't exist. The fix appends the dylib extension instead of replacing it (so the library and the app name keep every dot), and names the library shipped on Linux the way the launcher resolves it. ## All three platforms are affected, not just Linux `app_name` comes from `dylib_parts(dylib_path).file_stem()`, so before `append_extension` a `--output my-app-2.9.2` also produced a `my-app-2.9.app` bundle on macOS, and on Windows a `my-app-2.9.dll` that the `my-app-2.9.2.exe` launcher would never find. Linux is only where it surfaced as a clean error message. Windows needs no `linux_colocated_runtime_name` equivalent: its launcher is `<app>.exe`, so the same chop-at-the-last-dot lookup consumes a real `.exe` and lands on `<app>.dll` for any name, dotted or not. Linux launchers have no extension for the chop to eat, which is why only that side pre-truncates. That asymmetry is now noted in the code so it doesn't read as an omission. ## Clobber guards An app name whose runtime library or launcher would land on a file the backend ships (`libcef.so`, `libcef.dll`, `d3dcompiler_47.dll`, the helper executables) — or, for a Linux name like `myapp.so`, on the launcher itself — is now rejected instead of silently overwriting it. Both `fs::copy` and `fs::rename` replace without complaint, so the result was an app that couldn't start with nothing in the build output to say why. Both packagers resolve their paths through `resolve_app_dir_targets`, which returns them only once every check has passed. The ordering (all guards before any write) is therefore structural rather than incidental. ## Coupling to laufey `linux_colocated_runtime_name` mirrors `LaufeyFindColocatedRuntime` as of **laufey 0.7.0**, and that coupling crosses a repo boundary with nothing to enforce it — the chop lives in laufey's C++ backend, so it is verified only by apps starting. If laufey ever changes that lookup to strip only a known extension, every app built by this version of Deno stops starting, because the launcher would begin looking for `my-app-2.9.2.so`, which we deliberately don't ship. The doc comment says so, and says to re-check on a laufey bump. ## Verification Verified on Linux with the issue's repro: `deno desktop --output ./out/my-app-2.9.2.AppImage main.ts` now builds an app dir with launcher `my-app-2.9.2` next to `my-app-2.9.so`, and both the app dir and the AppImage start and report `Runtime loaded successfully`. Renaming the launcher back to the old truncated `my-app-2.9` reproduces the original error. Closes #35971 --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
deno desktop --output my-app-2.9.2(or any output basename with a dot in it, e.g.template-deno2.9.2-desktop-vue3-vite8.AppImage) produced an app that exited immediately on Linux with:Two places treated the text after the last dot of the app name as a file extension:
get_desktop_specific_filepathusedPathBuf::with_extension, so the compiled library formy-app-2.9.2becamemy-app-2.9.so— which also truncated the app dir and launcher names tomy-app-2.9, silently renaming the app.LaufeyFindColocatedRuntimechops its own file name at the last dot and appends.so, so themy-app-2.9launcher looked formy-app-2.so, which doesn't exist.The fix appends the dylib extension instead of replacing it (so the library and the app name keep every dot), and names the library shipped on Linux the way the launcher resolves it.
All three platforms are affected, not just Linux
app_namecomes fromdylib_parts(dylib_path).file_stem(), so beforeappend_extensiona--output my-app-2.9.2also produced amy-app-2.9.appbundle on macOS, and on Windows amy-app-2.9.dllthat themy-app-2.9.2.exelauncher would never find. Linux is only where it surfaced as a clean error message.Windows needs no
linux_colocated_runtime_nameequivalent: its launcher is<app>.exe, so the same chop-at-the-last-dot lookup consumes a real.exeand lands on<app>.dllfor any name, dotted or not. Linux launchers have no extension for the chop to eat, which is why only that side pre-truncates. That asymmetry is now noted in the code so it doesn't read as an omission.Clobber guards
An app name whose runtime library or launcher would land on a file the backend ships (
libcef.so,libcef.dll,d3dcompiler_47.dll, the helper executables) — or, for a Linux name likemyapp.so, on the launcher itself — is now rejected instead of silently overwriting it. Bothfs::copyandfs::renamereplace without complaint, so the result was an app that couldn't start with nothing in the build output to say why.Both packagers resolve their paths through
resolve_app_dir_targets, which returns them only once every check has passed. The ordering (all guards before any write) is therefore structural rather than incidental.Coupling to laufey
linux_colocated_runtime_namemirrorsLaufeyFindColocatedRuntimeas of laufey 0.7.0, and that coupling crosses a repo boundary with nothing to enforce it — the chop lives in laufey's C++ backend, so it is verified only by apps starting. If laufey ever changes that lookup to strip only a known extension, every app built by this version of Deno stops starting, because the launcher would begin looking formy-app-2.9.2.so, which we deliberately don't ship. The doc comment says so, and says to re-check on a laufey bump.Verification
Verified on Linux with the issue's repro:
deno desktop --output ./out/my-app-2.9.2.AppImage main.tsnow builds an app dir with launchermy-app-2.9.2next tomy-app-2.9.so, and both the app dir and the AppImage start and reportRuntime loaded successfully. Renaming the launcher back to the old truncatedmy-app-2.9reproduces the original error.Closes #35971