Skip to content

Commit 80d5929

Browse files
committed
Add Windows release workflow and PyInstaller spec
Introduces a GitHub Actions workflow to build and publish Windows executables on version changes. Adds a PyInstaller spec file for building the executable with version metadata. Updates __main__.py to support both source and frozen (PyInstaller) imports.
1 parent 27555be commit 80d5929

File tree

3 files changed

+198
-2
lines changed

3 files changed

+198
-2
lines changed

.github/workflows/release.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: Publish Executable
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
release:
10+
runs-on: windows-latest
11+
permissions:
12+
contents: write
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 2
18+
19+
- name: Detect version change
20+
id: detect
21+
shell: bash
22+
run: |
23+
if [ "$(git rev-list --count HEAD)" -le 1 ]; then
24+
echo "changed=true" >> "$GITHUB_OUTPUT"
25+
elif git diff --quiet HEAD^ HEAD -- glitter/__init__.py glitter.spec; then
26+
echo "changed=false" >> "$GITHUB_OUTPUT"
27+
else
28+
echo "changed=true" >> "$GITHUB_OUTPUT"
29+
fi
30+
31+
- name: Stop if version unchanged
32+
if: steps.detect.outputs.changed != 'true'
33+
run: |
34+
Write-Output "Version not changed; skipping release."
35+
exit 0
36+
37+
- name: Setup Python
38+
if: steps.detect.outputs.changed == 'true'
39+
uses: actions/setup-python@v5
40+
with:
41+
python-version: '3.10'
42+
43+
- name: Extract version
44+
if: steps.detect.outputs.changed == 'true'
45+
id: version
46+
shell: bash
47+
run: |
48+
python - <<'PY'
49+
import os
50+
import pathlib
51+
import re
52+
53+
init_file = pathlib.Path("glitter/__init__.py")
54+
text = init_file.read_text(encoding="utf-8")
55+
match = re.search(r"__version__\s*=\s*['\"]([^'\"]+)['\"]", text)
56+
if not match:
57+
raise SystemExit("Unable to find __version__ in glitter/__init__.py")
58+
version = match.group(1)
59+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as handle:
60+
handle.write(f"value={version}\n")
61+
PY
62+
63+
- name: Install build dependencies
64+
if: steps.detect.outputs.changed == 'true'
65+
shell: bash
66+
run: |
67+
python -m pip install --upgrade pip
68+
pip install -r requirements.txt pyinstaller
69+
70+
- name: Clean previous builds
71+
if: steps.detect.outputs.changed == 'true'
72+
shell: bash
73+
run: |
74+
rm -rf dist build
75+
76+
- name: Build executable
77+
if: steps.detect.outputs.changed == 'true'
78+
shell: bash
79+
run: |
80+
pyinstaller glitter.spec
81+
82+
- name: Prepare release asset
83+
if: steps.detect.outputs.changed == 'true'
84+
shell: bash
85+
run: |
86+
VERSION="${{ steps.version.outputs.value }}"
87+
mkdir -p release
88+
cp dist/glitter.exe "release/glitter-${VERSION}-windows.exe"
89+
90+
- name: Publish GitHub release
91+
if: steps.detect.outputs.changed == 'true'
92+
uses: softprops/action-gh-release@v1
93+
with:
94+
tag_name: v${{ steps.version.outputs.value }}
95+
name: Glitter v${{ steps.version.outputs.value }}
96+
draft: false
97+
prerelease: false
98+
files: release/*
99+
body: |
100+
Automated release for Glitter v${{ steps.version.outputs.value }}.

glitter.spec

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
from PyInstaller.utils.hooks import collect_submodules
3+
from PyInstaller.utils.win32.versioninfo import (
4+
FixedFileInfo,
5+
StringFileInfo,
6+
StringStruct,
7+
StringTable,
8+
VarFileInfo,
9+
VarStruct,
10+
VSVersionInfo,
11+
)
12+
13+
from glitter import __version__ as GLITTER_VERSION
14+
15+
16+
def _version_tuple(raw: str):
17+
parts = [int(bit) for bit in raw.split(".") if bit.isdigit()]
18+
padded = (parts + [0, 0, 0, 0])[:4]
19+
return tuple(padded)
20+
21+
22+
VERSION_INFO = VSVersionInfo(
23+
ffi=FixedFileInfo(
24+
filevers=_version_tuple(GLITTER_VERSION),
25+
prodvers=_version_tuple(GLITTER_VERSION),
26+
mask=0x3F,
27+
flags=0,
28+
OS=0x40004,
29+
fileType=0x1,
30+
subtype=0x0,
31+
date=(0, 0),
32+
),
33+
kids=[
34+
StringFileInfo(
35+
[
36+
StringTable(
37+
"040904B0",
38+
[
39+
StringStruct("CompanyName", "ScarletKc"),
40+
StringStruct("FileDescription", "A Simple LAN File Transfer CLI."),
41+
StringStruct("FileVersion", GLITTER_VERSION),
42+
StringStruct("InternalName", "glitter"),
43+
StringStruct("LegalCopyright", "Copyright (C) ScarletKc"),
44+
StringStruct("OriginalFilename", "glitter.exe"),
45+
StringStruct("ProductName", "Glitter"),
46+
StringStruct("ProductVersion", GLITTER_VERSION),
47+
],
48+
)
49+
]
50+
),
51+
VarFileInfo([VarStruct("Translation", [1033, 1200])]),
52+
],
53+
)
54+
55+
hiddenimports = []
56+
hiddenimports += collect_submodules('cryptography')
57+
58+
59+
a = Analysis(
60+
['glitter\\__main__.py'],
61+
pathex=[],
62+
binaries=[],
63+
datas=[],
64+
hiddenimports=hiddenimports,
65+
hookspath=[],
66+
hooksconfig={},
67+
runtime_hooks=[],
68+
excludes=[],
69+
noarchive=False,
70+
optimize=0,
71+
)
72+
pyz = PYZ(a.pure)
73+
74+
exe = EXE(
75+
pyz,
76+
a.scripts,
77+
a.binaries,
78+
a.datas,
79+
[],
80+
name='glitter',
81+
debug=False,
82+
bootloader_ignore_signals=False,
83+
strip=False,
84+
upx=True,
85+
upx_exclude=[],
86+
runtime_tmpdir=None,
87+
console=True,
88+
disable_windowed_traceback=False,
89+
argv_emulation=False,
90+
target_arch=None,
91+
codesign_identity=None,
92+
entitlements_file=None,
93+
version=VERSION_INFO,
94+
)

glitter/__main__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
from .cli import main
1+
try:
2+
from .cli import main
3+
except ImportError: # pragma: no cover - fallback for frozen builds
4+
from glitter.cli import main # type: ignore[import] - accessible in PyInstaller bundle
25

36
if __name__ == "__main__":
47
raise SystemExit(main())
5-

0 commit comments

Comments
 (0)