-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcronstable.spec
More file actions
148 lines (134 loc) · 5.73 KB
/
Copy pathcronstable.spec
File metadata and controls
148 lines (134 loc) · 5.73 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
# -*- mode: python ; coding: utf-8 -*-
import sys
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None
# strip is a Unix concept (ELF/Mach-O). On Windows the GNU `strip` that ships
# with git bash WILL corrupt the bundled PE DLLs (notably pythonXY.dll) if
# PyInstaller is allowed to run it -- the resulting .exe then fails to load the
# Python DLL ("Invalid access to memory location"). So strip only off Windows.
STRIP = sys.platform != "win32"
# bundle the single-page web UI (cronstable/web/index.html) so the binary
# serves it without needing any files on disk, plus the third-party license
# notices (cronstable/licenses/*.txt) that `--third-party-licenses` prints:
# the LGPL notice for bundled zeroconf must travel inside the binary itself.
datas = collect_data_files("cronstable")
# uvloop and orjson are optional runtime accelerators, each imported behind a
# try/except with a stdlib fallback: uvloop by cronstable/__main__._new_event_loop
# (else asyncio) and orjson by cronstable/_json (else stdlib json). A frozen binary
# only contains what is importable in the build environment, so bundle each (as
# a hidden import, since the guarded import is easy for the analysis to miss)
# exactly when the build env actually has it -- the binary CI jobs best-effort
# install a wheel, or source-build it, before freezing (see install_orjson.sh /
# the uvloop steps). Absent (an arch with no wheel and no working source build)
# the list stays empty and the binary simply runs on the stdlib equivalents.
hiddenimports = []
try:
import uvloop # noqa: F401
hiddenimports.append("uvloop")
except ImportError:
pass
try:
import orjson # noqa: F401
hiddenimports.append("orjson")
except ImportError:
pass
# pynacl (the `push` extra): cronstable/push guards `from nacl.public
# import ...` in a try/except, the pattern the analysis is most likely
# to drop. Name the exact module we import, AND cffi's `_cffi_backend`
# extension: nacl's compiled `_sodium` module imports it from generated
# code the static analysis cannot see, so without the explicit entry the
# frozen import dies with "No module named '_cffi_backend'" while the
# libsodium .so sits uselessly in the bundle. The binary lanes verify a
# real sealed-box round-trip before freezing and a push-enabled
# --validate-config after, so a miss here fails CI.
try:
import nacl.public # noqa: F401
hiddenimports.extend(["nacl.public", "_cffi_backend"])
except ImportError:
pass
# zeroconf (the `discovery` extra, behind web.bonjour): same guarded-import
# pattern as pynacl above. It is LGPL-2.1; bundling is deliberate and paired
# with the compliance kit (the in-binary notice behind --third-party-licenses,
# the source archive attached to every GitHub Release, and the public build
# recipe as the relink path). See LICENSING.md before changing anything here.
try:
import zeroconf # noqa: F401
import zeroconf.asyncio # noqa: F401
hiddenimports.extend(["zeroconf", "zeroconf.asyncio"])
except ImportError:
pass
# Modules that are never reachable at runtime but that the analysis (or a
# dependency's optional `try: import ...` probe) could otherwise rake into the
# bundle. cronstable is a headless daemon with an ANSI TUI (raw termios/tty on
# POSIX, msvcrt on Windows) and an HTML/JSON web UI, so no GUI toolkit is ever
# imported; the TUI reads raw keypresses itself and never uses readline; durable
# state is JSON (orjson / stdlib json), never sqlite. Excluding a module that was
# never going to be collected is a harmless no-op, so this list is insurance
# against dead weight sneaking in. Verified against the tree and the runtime deps
# (aiohttp / jinja2 / strictyaml / sentry-sdk / aiosmtplib / psutil / tzdata);
# the per-arch `--version` smoke test is the build-time backstop.
excludes = [
# GUI toolkits: never imported by a headless daemon.
"tkinter",
"_tkinter",
"turtle",
"turtledemo",
"idlelib",
# curses: the TUI drives the terminal through termios/tty directly.
"curses",
"_curses",
"_curses_panel",
# the TUI reads raw keypresses; nothing uses readline's line editor.
"readline",
# no sqlite anywhere in cronstable or its runtime deps.
"sqlite3",
"_sqlite3",
# dev/tooling stdlib that never runs inside the frozen daemon.
"test",
"lib2to3",
"ensurepip",
"pydoc_data",
]
# optimize=2 compiles every bundled module at -OO: it strips assert statements
# AND docstrings from the frozen bytecode. cronstable's modules are deliberately
# docstring-dense (the rationale lives next to the code), and those strings
# otherwise ship in the archive and sit in resident memory for the life of the
# daemon; dropping them shrinks the binary and lowers RSS. Every assert in the
# tree is a type-narrowing / internal-invariant check (`x is not None`,
# `isinstance`, `not in`) with no side effects and no untrusted-input
# validation, so removing them does not change behavior on the correct path.
# The source-run test suite does not exercise the frozen -OO build; CI's
# per-arch `--version` smoke test is the backstop for a dependency that might
# misbehave without its docstrings/asserts.
a = Analysis(
["cronstable"],
pathex=["."],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
runtime_hooks=[],
excludes=excludes,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
optimize=2,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name="cronstable",
debug=False,
bootloader_ignore_signals=False,
strip=STRIP,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
)