mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-14 18:28:11 +02:00
Bundle libsecret for systems without it; first launch keeps Flet's message off the client path
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
61c992f959
commit
5890b52da6
4 files changed
+122
-1
No files matched your search
@@ -38,6 +38,23 @@ else:
|
||||
_MenuLabel = "Start menu" if IsWindows else "app menu"
|
||||
|
||||
|
||||
def _KeepFallbackLibraries() -> None:
|
||||
"""Linux AppImage: copy the bundled libsecret (and what it needs) to
|
||||
~/.local/share/SaraSuite/Libraries, for FletIdentity.py to give the Flet
|
||||
client of every installed program on systems without it."""
|
||||
Bundled = Path(getattr(sys, "_MEIPASS", "")) / "FallbackLibs"
|
||||
if IsWindows or not Bundled.is_dir():
|
||||
return
|
||||
Kept = DataDir / "Libraries"
|
||||
try:
|
||||
Kept.mkdir(parents=True, exist_ok=True)
|
||||
for Library in Bundled.iterdir():
|
||||
if not (Kept / Library.name).exists() or (Kept / Library.name).stat().st_size != Library.stat().st_size:
|
||||
shutil.copy2(Library, Kept / Library.name)
|
||||
except OSError:
|
||||
pass # only a fallback
|
||||
|
||||
|
||||
def _LoadFletIdentity():
|
||||
# Lives in Programs/ (bundled as data) because installed programs get a
|
||||
# copy of the same file next to them.
|
||||
@@ -324,6 +341,7 @@ def Main(Page: ft.Page) -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_KeepFallbackLibraries() # also before a command (an install from the command line)
|
||||
if len(sys.argv) > 1:
|
||||
if IsWindows and getattr(sys, "frozen", False):
|
||||
# The .exe is built windowed (no console of its own); write the
|
||||
|
||||
@@ -18,8 +18,17 @@ podman run --rm --security-opt label=disable -v "$PWD:/src" -w /src \
|
||||
quay.io/pypa/manylinux_2_28_x86_64 bash -euo pipefail -c '
|
||||
uv venv --quiet --allow-existing --managed-python --python 3.14 build/Container/venv
|
||||
uv pip install --quiet --python build/Container/venv -r requirements.txt flet-desktop pyinstaller
|
||||
# The Flet window needs libsecret, which some systems lack: bundled as a
|
||||
# fallback (RestoreLibraryPath.py uses only what the system lacks).
|
||||
dnf -y -q install libsecret >/dev/null
|
||||
mkdir -p build/FallbackLibs
|
||||
for Library in libsecret-1.so.0 libgcrypt.so.20 libgpg-error.so.0; do cp -L /usr/lib64/$Library build/FallbackLibs/; done
|
||||
# Each finds the others beside it, wherever they are put (a client lib folder).
|
||||
patchelf --set-rpath "\$ORIGIN" build/FallbackLibs/libsecret-1.so.0 build/FallbackLibs/libgcrypt.so.20
|
||||
build/Container/venv/bin/pyinstaller --noconfirm --clean --log-level WARN \
|
||||
--onefile --windowed --name SaraSuite --collect-data flet \
|
||||
--runtime-hook "$PWD/Packaging/RestoreLibraryPath.py" \
|
||||
--add-data "$PWD/build/FallbackLibs:FallbackLibs" \
|
||||
--add-data "$PWD/assets:assets" --add-data "$PWD/Programs:Programs" \
|
||||
--distpath build/PyInstaller/dist --workpath build/PyInstaller/work --specpath build/PyInstaller \
|
||||
"$PWD/Main.py"'
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# PyInstaller runtime hook. The frozen app runs with LD_LIBRARY_PATH pointing
|
||||
# at its bundled libraries, and programs it starts inherit that: Flet's
|
||||
# desktop client then loads the bundled (older) libstdc++ instead of the
|
||||
# system's and won't start ("GLIBCXX_3.4.29 not found"). Give them the
|
||||
# original value back; the app itself has already loaded what it needs.
|
||||
#
|
||||
# Flet's window also needs libsecret, which isn't part of GTK and some
|
||||
# systems don't install ("libsecret-1.so.0: cannot open shared object file").
|
||||
# The build bundles it in FallbackLibs, with libgcrypt and libgpg-error, which
|
||||
# it needs; the ones this system lacks (only those) are offered to what the
|
||||
# app starts, after the original path.
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def SystemHas(Name):
|
||||
"""Whether the system itself has a library: asked of its linker cache
|
||||
and standard folders, not dlopen, which would also find this bundle's
|
||||
own copies (PyInstaller adds what bundled libraries need)."""
|
||||
for Folder in ("/usr/lib64", "/usr/lib", "/lib64", "/lib", "/usr/lib/x86_64-linux-gnu", "/lib/x86_64-linux-gnu"):
|
||||
if os.path.exists(os.path.join(Folder, Name)):
|
||||
return True
|
||||
try:
|
||||
Cache = subprocess.run(["ldconfig", "-p"], capture_output=True, text=True, timeout=10,
|
||||
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"}).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return False
|
||||
return any(Line.strip().startswith(Name + " ") for Line in Cache.splitlines())
|
||||
|
||||
Paths = [os.environ["LD_LIBRARY_PATH_ORIG"]] if os.environ.get("LD_LIBRARY_PATH_ORIG") else []
|
||||
|
||||
Bundle = getattr(sys, "_MEIPASS", "")
|
||||
Fallback = os.path.join(Bundle, "FallbackLibs")
|
||||
if sys.platform.startswith("linux") and os.path.isdir(Fallback):
|
||||
InUse = os.path.join(Bundle, "FallbackLibsInUse")
|
||||
os.makedirs(InUse, exist_ok=True)
|
||||
for Name in ("libgpg-error.so.0", "libgcrypt.so.20", "libsecret-1.so.0"):
|
||||
if not SystemHas(Name) and not os.path.lexists(os.path.join(InUse, Name)):
|
||||
os.symlink(os.path.join(Fallback, Name), os.path.join(InUse, Name))
|
||||
if os.listdir(InUse):
|
||||
Paths.append(InUse)
|
||||
|
||||
if Paths:
|
||||
os.environ["LD_LIBRARY_PATH"] = os.pathsep.join(Paths)
|
||||
else:
|
||||
os.environ.pop("LD_LIBRARY_PATH", None)
|
||||
@@ -18,13 +18,47 @@ Used two ways:
|
||||
case Flet just uses its normal client.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
OriginalId = b"com.appveyor.flet"
|
||||
# Flet's window needs libsecret, which isn't part of GTK and some systems don't
|
||||
# install. SaraSuite keeps copies of it and what it needs (from its AppImage)
|
||||
# here; MissingLibraries puts the ones this system lacks beside the client.
|
||||
LibrariesDir = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share") / "SaraSuite" / "Libraries"
|
||||
FallbackLibraries = ("libgpg-error.so.0", "libgcrypt.so.20", "libsecret-1.so.0")
|
||||
CopiesDir = Path.home() / ".cache" / "SaraSuite" / "FletClient"
|
||||
|
||||
|
||||
def _SystemHas(Name: str) -> bool:
|
||||
"""Whether the system has a library (its standard folders and linker
|
||||
cache; the same check as the AppImages' RestoreLibraryPath.py)."""
|
||||
import subprocess
|
||||
|
||||
for Folder in ("/usr/lib64", "/usr/lib", "/lib64", "/lib", "/usr/lib/x86_64-linux-gnu", "/lib/x86_64-linux-gnu"):
|
||||
if (Path(Folder) / Name).exists():
|
||||
return True
|
||||
try:
|
||||
Cache = subprocess.run(["ldconfig", "-p"], capture_output=True, text=True, timeout=10,
|
||||
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"}).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return False
|
||||
return any(Line.strip().startswith(Name + " ") for Line in Cache.splitlines())
|
||||
|
||||
|
||||
def AddMissingLibraries(ClientLib: Path) -> None:
|
||||
"""Copies SaraSuite's fallback libraries the system doesn't have into the
|
||||
client's lib folder (it looks there first: its RUNPATH is $ORIGIN/lib)."""
|
||||
if not sys.platform.startswith("linux") or not LibrariesDir.is_dir() or not ClientLib.is_dir():
|
||||
return
|
||||
for Name in FallbackLibraries:
|
||||
Source = LibrariesDir / Name
|
||||
if Source.is_file() and not (ClientLib / Name).exists() and not _SystemHas(Name):
|
||||
shutil.copy2(Source, ClientLib / Name)
|
||||
|
||||
|
||||
def ClientFor(AppId: str) -> str:
|
||||
"""Folder of a client copy whose app ID is AppId ("" if not possible).
|
||||
AppId needs a dot (a GTK rule) and at most 17 characters, the length of
|
||||
@@ -36,6 +70,7 @@ def ClientFor(AppId: str) -> str:
|
||||
from flet_desktop import ensure_client_cached # the program's own Flet version
|
||||
|
||||
Original = Path(ensure_client_cached()) / "flet"
|
||||
AddMissingLibraries(Original / "lib")
|
||||
Binary = (Original / "flet").read_bytes()
|
||||
Copy = CopiesDir / f"{Original.parent.name}-{AppId}" / "flet"
|
||||
Patched = Binary.replace(
|
||||
@@ -64,4 +99,16 @@ def ClientFor(AppId: str) -> str:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(ClientFor(sys.argv[1]) if len(sys.argv) > 1 else "")
|
||||
# Only the folder goes to stdout, which the launcher reads: Flet's own
|
||||
# messages ("Preparing Flet ... for the first use", written straight to
|
||||
# the file descriptor) go to stderr.
|
||||
sys.stdout.flush()
|
||||
Stdout = os.dup(1)
|
||||
os.dup2(2, 1)
|
||||
try:
|
||||
Folder = ClientFor(sys.argv[1]) if len(sys.argv) > 1 else ""
|
||||
finally:
|
||||
sys.stdout.flush()
|
||||
os.dup2(Stdout, 1)
|
||||
os.close(Stdout)
|
||||
print(Folder)
|
||||
Reference in new issue
Block a user