@@ -28,10 +28,13 @@ jobs:
2828 print(f"tag=v{v}")
2929 PY
3030
31- - uses : softprops/action-gh-release@v2
31+ - name : Create GitHub release
32+ uses : softprops/action-gh-release@v2
3233 with :
3334 tag_name : ${{ steps.meta.outputs.tag }}
3435 name : ${{ steps.meta.outputs.tag }}
36+ draft : false
37+ prerelease : false
3538
3639 build :
3740 needs : metadata
@@ -42,43 +45,121 @@ jobs:
4245 include :
4346 - os : ubuntu-latest
4447 platform : linux-x86_64
45- exe : bangen
48+ extra_build_flags : --clang
49+
4650 - os : macos-latest
4751 platform : macos-universal2
48- exe : bangen
52+ extra_build_flags : --clang --macos-target-arch=universal
53+
4954 - os : windows-latest
5055 platform : windows-x86_64
51- exe : bangen.exe
56+ extra_build_flags : " "
5257
5358 steps :
5459 - uses : actions/checkout@v4
5560
5661 - uses : actions/setup-python@v5
5762 with :
5863 python-version : " 3.11"
59- cache : " pip"
64+ # No cache: pip — we cache the entire installed env in Layer 1,
65+ # which is strictly better (zero pip activity on a cache hit).
66+
67+ # ── Layer 1: installed Python environment ────────────────────────────
68+ # Caches the full Python + site-packages directory. On a hit, pip is
69+ # never invoked at all. Keyed on pyproject.toml only — bangen itself
70+ # is installed with -e (editable) so source changes are always live
71+ # without busting this cache.
72+ - name : Cache pip environment
73+ id : pip-cache
74+ uses : actions/cache@v4
75+ with :
76+ path : ${{ env.pythonLocation }}
77+ key : pip-env-${{ runner.os }}-py3.11-${{ hashFiles('pyproject.toml') }}
78+ # Intentionally no restore-keys: a partial env restore causes subtle
79+ # import errors that are harder to debug than a clean install.
6080
61- # 🔥 Cache Nuitka + compiler artifacts
62- - name : Cache Nuitka + compiler
81+ - name : Install dependencies
82+ if : steps.pip-cache.outputs.cache-hit != 'true'
83+ run : |
84+ python -m pip install --upgrade pip
85+ pip install -e . nuitka
86+
87+ # ── Layer 2: sccache (C compiler output cache) ───────────────────────
88+ # Nuitka translates Python → C, then compiles the C with clang/MSVC.
89+ # sccache caches the compiled C object files keyed on content hash,
90+ # so unchanged modules are never recompiled.
91+ #
92+ # Linux / macOS: we shim the clang binary so every compiler call Nuitka
93+ # makes goes through sccache transparently. The shim must land before
94+ # /usr/bin in PATH — /usr/local/bin satisfies this on both runners.
95+ #
96+ # Windows: Nuitka calls cl.exe (MSVC) through its own Scons backend in
97+ # a way that cannot be intercepted with a shim. Layer 3 carries Windows.
98+ - uses : mozilla-actions/sccache-action@v0.0.5
99+
100+ - name : Configure sccache
101+ shell : bash
102+ run : |
103+ echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
104+ echo "SCCACHE_CACHE_SIZE=2G" >> "$GITHUB_ENV"
105+
106+ if [ "${{ runner.os }}" = "Linux" ]; then
107+ REAL_CLANG="$(which clang)"
108+ REAL_CLANGXX="$(which clang++)"
109+ printf '#!/bin/sh\nexec sccache %s "$@"\n' "$REAL_CLANG" | sudo tee /usr/local/bin/clang > /dev/null
110+ printf '#!/bin/sh\nexec sccache %s "$@"\n' "$REAL_CLANGXX" | sudo tee /usr/local/bin/clang++ > /dev/null
111+ sudo chmod +x /usr/local/bin/clang /usr/local/bin/clang++
112+
113+ elif [ "${{ runner.os }}" = "macOS" ]; then
114+ REAL_CLANG="$(xcrun -f clang)"
115+ REAL_CLANGXX="$(xcrun -f clang++)"
116+ printf '#!/bin/sh\nexec sccache %s "$@"\n' "$REAL_CLANG" | sudo tee /usr/local/bin/clang > /dev/null
117+ printf '#!/bin/sh\nexec sccache %s "$@"\n' "$REAL_CLANGXX" | sudo tee /usr/local/bin/clang++ > /dev/null
118+ sudo chmod +x /usr/local/bin/clang /usr/local/bin/clang++
119+ fi
120+
121+ # ── Layer 3: Nuitka bytecode + download cache ─────────────────────────
122+ # Caches two things inside Nuitka's cache dir:
123+ # • Python→C translation results (expensive, source-sensitive)
124+ # • Onefile bootstrap downloads (AppImage on Linux, etc. — one-time)
125+ #
126+ # Three-tier restore-key cascade:
127+ # 1. Exact hit — source + deps unchanged, fully warm
128+ # 2. Deps hit — only deps unchanged; translation cache partially warm
129+ # 3. OS hit — bootstrap downloads reused; compilation starts cold
130+ - name : Set Nuitka cache dir
131+ shell : bash
132+ run : |
133+ if [ "${{ runner.os }}" = "Windows" ]; then
134+ echo "NUITKA_CACHE_DIR=${LOCALAPPDATA}/Nuitka" >> "$GITHUB_ENV"
135+ else
136+ echo "NUITKA_CACHE_DIR=${HOME}/.cache/Nuitka" >> "$GITHUB_ENV"
137+ fi
138+
139+ - name : Cache Nuitka artifacts
63140 uses : actions/cache@v4
64141 with :
65142 path : |
66143 ~/.cache/Nuitka
67- ~/.ccache
68144 ~/AppData/Local/Nuitka
69- ~/AppData/Local/ccache
70- key : nuitka-${{ runner.os }}-3.11-${{ hashFiles('pyproject.toml', 'bangen/**/*.py') }}
145+ key : >-
146+ nuitka-${{ runner.os }}-py3.11-
147+ ${{ hashFiles('pyproject.toml') }}-
148+ ${{ hashFiles('bangen/**/*.py', '_entry.py') }}
71149 restore-keys : |
72- nuitka-${{ runner.os }}-3.11-
150+ nuitka-${{ runner.os }}-py3.11-${{ hashFiles('pyproject.toml') }}-
151+ nuitka-${{ runner.os }}-py3.11-
73152
74- - name : Install dependencies
75- run : |
76- python -m pip install --upgrade pip
77- pip install . "nuitka[onefile]"
153+ # ─────────────────────────────────────────────────────────────────────
78154
79- # Ensure Nuitka uses cache path
80- - name : Set Nuitka cache dir
81- run : echo "NUITKA_CACHE_DIR=$HOME/.cache/Nuitka" >> $GITHUB_ENV
155+ - name : Generate entry script
156+ shell : bash
157+ run : |
158+ cat > _entry.py << 'EOF'
159+ from bangen.app import main
160+ if __name__ == "__main__":
161+ main()
162+ EOF
82163
83164 - name : Build (Nuitka onefile optimized)
84165 shell : bash
@@ -89,14 +170,34 @@ jobs:
89170 --output-dir=build \
90171 --output-filename=bangen \
91172 --follow-imports \
173+ \
174+ --include-package=bangen \
175+ \
176+ --include-package=rich \
177+ --include-package-data=rich \
178+ \
179+ --include-package=pyfiglet \
180+ --include-package-data=pyfiglet \
181+ \
182+ --include-package=PIL \
183+ \
184+ --include-package=typer \
185+ --include-package=click \
186+ \
187+ --nofollow-import-to=tkinter \
188+ --nofollow-import-to=unittest \
189+ --nofollow-import-to=test \
190+ --nofollow-import-to=distutils \
191+ --nofollow-import-to=setuptools \
192+ --nofollow-import-to=pkg_resources \
193+ \
92194 --lto=yes \
93- --jobs=2 \
94195 --python-flag=no_asserts \
95196 --python-flag=no_docstrings \
96197 --python-flag=isolated \
97198 --onefile-tempdir-spec="{CACHE_DIR}/bangen/${{ needs.metadata.outputs.version }}" \
98- --clang \
99- bangen/__main__ .py
199+ ${{ matrix.extra_build_flags }} \
200+ _entry .py
100201
101202 - name : Prepare release asset
102203 shell : bash
@@ -105,13 +206,15 @@ jobs:
105206
106207 if [ "${{ runner.os }}" = "Windows" ]; then
107208 BIN="build/bangen.exe"
209+ EXT=".exe"
108210 else
109211 BIN="build/bangen"
212+ EXT=""
110213 chmod +x "$BIN"
111214 fi
112215
113216 cp "$BIN" \
114- "release-assets/bangen-${{ needs.metadata.outputs.tag }}-${{ matrix.platform }}"
217+ "release-assets/bangen-${{ needs.metadata.outputs.tag }}-${{ matrix.platform }}${EXT} "
115218
116219 - name : Upload artifact
117220 uses : actions/upload-artifact@v4
@@ -123,4 +226,4 @@ jobs:
123226 uses : softprops/action-gh-release@v2
124227 with :
125228 tag_name : ${{ needs.metadata.outputs.tag }}
126- files : release-assets/*
229+ files : release-assets/*
0 commit comments