-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathTideway-win.spec
More file actions
151 lines (136 loc) · 4.61 KB
/
Copy pathTideway-win.spec
File metadata and controls
151 lines (136 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# -*- mode: python ; coding: utf-8 -*-
#
# Windows PyInstaller spec. Build with:
# pyinstaller Tideway-win.spec --noconfirm
#
# Produces dist/Tideway/Tideway.exe (onedir). Onedir is
# preferred over onefile here because a user opening 20 tracks in quick
# succession shouldn't incur the onefile unpack tax on each child
# process, and AV scanners flag onefile executables more often.
#
# Prerequisite: run `npm --prefix web run build` first so web/dist/
# exists. PyInstaller bundles whatever is on disk at spec-load time.
from pathlib import Path
from PyInstaller.utils.hooks import collect_all
block_cipher = None
repo_root = Path(SPECPATH).resolve()
dist_dir = repo_root / "web" / "dist"
_version_file = repo_root / "VERSION"
APP_VERSION = _version_file.read_text().strip() if _version_file.is_file() else "0.0.0"
if not dist_dir.is_dir():
raise SystemExit(
f"web/dist not found at {dist_dir}. Run "
f"`npm --prefix web run build` first."
)
datas = [
# (source, dest-inside-bundle)
(str(dist_dir), "web/dist"),
]
# Ship the VERSION file so the server can read it at runtime.
if _version_file.is_file():
datas.append((str(_version_file), "."))
# Audio + video I/O is handled entirely by PyAV (libav), which
# ships its own libav binaries in its wheel — no external ffmpeg
# needed anywhere in the app.
binaries: list[tuple[str, str]] = []
# pydantic v2's core is a Rust extension (`pydantic_core._pydantic_core`)
# plus a metadata dir. PyInstaller's default static analysis misses the
# compiled .pyd; collect_all grabs data + binaries + submodules in one
# call. Same pattern for any other package with a native extension that
# the default hook doesn't cover.
#
# `tls_client` is deliberately excluded: spotapi imports it but we
# replace the transport with `app.spotify_curl_session` (curl-cffi)
# at runtime, and the bundled `tls-client-64.dll` panics on Windows.
# Skipping the collect_all keeps the broken DLL out of the install.
for pkg in ("pydantic", "pydantic_core", "curl_cffi"):
try:
_d, _b, _h = collect_all(pkg)
datas += _d
binaries += _b
except Exception:
# curl-cffi is best-effort; if it isn't installed in the
# build environment the app falls back to plain requests at
# runtime instead of failing the build.
pass
# tidalapi and uvicorn use dynamic imports that PyInstaller's static
# analysis misses. Collect their submodules explicitly.
hiddenimports = [
"tidalapi",
"tidalapi.album",
"tidalapi.artist",
"tidalapi.media",
"tidalapi.page",
"tidalapi.playlist",
"tidalapi.user",
"uvicorn.logging",
"uvicorn.loops.auto",
"uvicorn.protocols.http.auto",
"uvicorn.protocols.websockets.auto",
"uvicorn.lifespan.on",
"sse_starlette",
"webview.platforms.edgechromium",
# Global media-key listener. pynput's Windows backend pulls Win32
# APIs dynamically; declare the submodule explicitly.
"pynput",
"pynput.keyboard",
"pynput.keyboard._win32",
# curl-cffi-backed transport for spotapi. Imported lazily inside
# app.spotify_public._ensure_client; declare explicitly so
# PyInstaller's static analysis picks it up regardless of whether
# the function-body import is traced.
"app.spotify_curl_session",
]
a = Analysis(
["desktop.py"],
pathex=[str(repo_root)],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
# tkinter isn't imported by anything we ship, but Python's stdlib
# _includes_ it, so PyInstaller will happily bundle it unless we
# say otherwise. Exclude to keep the Windows bundle lean.
excludes=[
"tkinter",
],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
# Icon file is optional — drop a 256x256 .ico at assets/icon.ico to
# customize. Falls back to the generic PyInstaller icon if missing.
icon_path = repo_root / "assets" / "icon.ico"
icon_arg = str(icon_path) if icon_path.is_file() else None
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="Tideway",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False, # GUI app — no console window
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=icon_arg,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="Tideway",
)