Repository navigation
fix(linux): give Ghostscript a clean library path in the frozen build - #211
Merged
Merged
Conversation
On Linux the PyInstaller one-file bootloader prepends its extraction directory (sys._MEIPASS, /tmp/_MEI...) to LD_LIBRARY_PATH and keeps the previous value, if there was one, in LD_LIBRARY_PATH_ORIG. Every child inherits that, so the system gs started by Compress resolved its shared libraries against the bundle first. The new module docstring records the measurement: with the v1.15.1 PDFApps-Linux.tar.gz on Ubuntu 26.04 the system gs 10.06.0 loaded the bundle's libstdc++.so.6, exited 1 with "GLIBCXX_3.4.32 not found", and Compress dropped its Ghostscript pass without a message. app/_subprocess_env.py (new, no Qt and no app.* imports) adds system_tool_env(environ=None, *, frozen=None, platform=None, meipass=None). It returns None (inherit unchanged) outside a frozen build, on any platform other than Linux, with no _MEIPASS, or when there is nothing to undo. Otherwise it returns a copy of the environment with the bundle taken out of LD_LIBRARY_PATH: when the first entry is inside _MEIPASS and LD_LIBRARY_PATH_ORIG exists, the value from before launch is put back, even when empty; otherwise every entry inside _MEIPASS is dropped and the variable removed when nothing is left. LD_LIBRARY_PATH_ORIG is removed in both cases. A sibling directory such as /tmp/_MEIabcd is not treated as inside /tmp/_MEIabc. Neither the input mapping nor os.environ is modified. app/_gs_paths.py threads an environ argument through _gs_lib_dirs and _derive_gs_lib_dirs down to _gs_version, so the "gs --version" probe runs with the same environment as the gs child; a probe run with the inherited LD_LIBRARY_PATH would fail and stay cached for the session. app/utils.py (_compress_pdf, pass A) builds the gs environment in two layers: base_env = system_tool_env(), then GS_LIB on top of it with _gs_subprocess_env(gs, environ=base_env), falling back to base_env when there is no GS_LIB to add. The imports of both helpers move out of the try block, so an ImportError is no longer swallowed by its except Exception and pass A cannot be skipped silently for that reason. Tests: tests/test_subprocess_env.py covers the helper (platforms, restore from _ORIG, sibling directories, path normalisation, no mutation, defaults read from sys at call time, and an import allowlist). tests/test_gs_lib_paths.py checks that the version probe inherits by default and otherwise gets the child's environment, that pass A builds GS_LIB on the system-tool environment and still passes that environment when there is no GS_LIB, and that a missing helper module raises instead of being swallowed. Full suite at verification time: 1057 passed. This is step 1. Still to do: the tesseract calls, xdg-open in base.py, CONTRIBUTING, the relaunch on a language change (which hands the old _MEI directory on in LD_LIBRARY_PATH_ORIG), and the test for an empty meipass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Step 1 of keeping the frozen Linux build's bundled libraries away from the system tools it starts. This step covers Ghostscript in Compress.
On Linux the PyInstaller one-file bootloader prepends its extraction directory (
sys._MEIPASS,/tmp/_MEI...) toLD_LIBRARY_PATHand keeps the previous value, if there was one, inLD_LIBRARY_PATH_ORIG. Every child inherits that, so the system gs started by Compress resolved its shared libraries against the bundle first. The new module docstring records the measurement: with the v1.15.1PDFApps-Linux.tar.gzon Ubuntu 26.04 the system gs 10.06.0 loaded the bundle'slibstdc++.so.6, exited 1 with "GLIBCXX_3.4.32 not found", and Compress dropped its Ghostscript pass without a message.Changes
app/_subprocess_env.py(new, no Qt and noapp.*imports):system_tool_env(environ=None, *, frozen=None, platform=None, meipass=None).None(inherit unchanged) outside a frozen build, on any platform other than Linux, with no_MEIPASS, or when there is nothing to undo.LD_LIBRARY_PATH: when the first entry is inside_MEIPASSandLD_LIBRARY_PATH_ORIGexists, the value from before launch is put back, even when empty; otherwise every entry inside_MEIPASSis dropped, and the variable is removed when nothing is left.LD_LIBRARY_PATH_ORIGis removed in both cases./tmp/_MEIabcdis not treated as inside/tmp/_MEIabc. Neither the input mapping noros.environis modified.app/_gs_paths.py: anenvironargument goes through_gs_lib_dirsand_derive_gs_lib_dirsdown to_gs_version, so thegs --versionprobe runs with the same environment as the gs child. A probe run with the inheritedLD_LIBRARY_PATHwould fail and stay cached for the session.app/utils.py(_compress_pdf, pass A):base_env = system_tool_env(), thenGS_LIBon top of it with_gs_subprocess_env(gs, environ=base_env), falling back tobase_envwhen there is noGS_LIBto add. The imports of both helpers move out of thetryblock, so anImportErroris no longer swallowed by itsexcept Exception.Tests
tests/test_subprocess_env.py(new): platforms, restore from_ORIG, sibling directories, path normalisation, no mutation, defaults read fromsysat call time, and an import allowlist for the module.tests/test_gs_lib_paths.py: the version probe inherits by default and otherwise gets the child's environment; pass A buildsGS_LIBon the system-tool environment and still passes that environment when there is noGS_LIB; a missing helper module raises instead of being swallowed.Not in this PR (later steps)
xdg-openinapp/base.py;_MEIdirectory on inLD_LIBRARY_PATH_ORIG;meipass.🤖 Generated with Claude Code