-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Automated Windows .exe Compilation with GitHub Actions
Problem Description
Currently, the GUI requires users to:
- Install Python 3.10+
- Install dependencies (customtkinter, pillow, etc.)
- Run
wareflow-guicommand
This creates a barrier to adoption for non-technical users who simply want to download and run an executable.
Proposed Solution
Implement automated compilation of Windows .exe using GitHub Actions that:
- Compiles on every push to main/feature branches
- Stores executable as artifact for download
- Creates GitHub releases with .exe attachment on version tags
- Generates checksums for integrity verification
- Optimizes file size through UPX compression
- Tests the executable in CI environment before release
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ GitHub Repository │
├─────────────────────────────────────────────────────────────┤
│ │
│ Push / Pull Request / Tag │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ .github/workflows/build-exe.yml │ │
│ │ - Triggered on push to main/* │ │
│ │ - Triggered on pull requests │ │
│ │ - Triggered on version tags (v*) │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Windows Runner (ubuntu-latest or windows) │ │
│ │ 1. Setup Python 3.10+ │ │
│ │ 2. Install dependencies │ │
│ │ 3. Run PyInstaller │ │
│ │ 4. Test executable │ │
│ │ 5. Generate checksums │ │
│ │ 6. Upload artifacts │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ├─────────────────┬─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Artifact │ │ Release │ │ Check │ │
│ │ (PR/Push)│ │ (Tags) │ │ Sums │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Implementation Plan
Phase 1: PyInstaller Configuration (1 day)
Tasks:
-
Create
build/gui.specfile- One-file executable configuration
- Include all data files and assets
- Configure hidden imports
- Set up UPX compression
- Disable console (windowed mode)
-
Create
build/__init__.pyfor build utilities -
Create
build/icon.icoapplication icon- Use a warehouse/box icon
- Multiple sizes for Windows
-
Create
build/directory structure
Deliverables:
- Complete PyInstaller spec file
- Application icon
- Build documentation
Phase 2: GitHub Actions Workflow (1 day)
Tasks:
-
Create
.github/workflows/build-exe.ymlworkflow -
Job: Build Windows Executable
- Trigger: Push to
main,feature/*,develop - OS:
windows-latestorubuntu-latest(with Wine) - Steps:
- Checkout code - Setup Python 3.10 - Install dependencies - Install PyInstaller - Build .exe with PyInstaller - Run smoke tests on .exe - Generate SHA256 checksums - Upload artifact (wareflow-gui-win.exe)
- Trigger: Push to
-
Job: Create Release (on tags only)
- Trigger: Version tags (v*..)
- Steps:
- Download artifacts - Create GitHub release - Upload .exe to release - Upload checksums - Generate release notes
Deliverables:
- Complete GitHub Actions workflow
- Automated artifact generation
- Automated release creation
Phase 3: Build Testing (0.5 day)
Tasks:
-
Create smoke tests for compiled .exe
- Test application launches
- Test basic navigation
- Test project loading
- Test database connection
-
Add tests to CI workflow
- Run .exe with
--helpflag - Run .exe with test project
- Validate no crashes on startup
- Run .exe with
-
Create
tests/integration/test_exeSmokeTests.py
Deliverables:
- Smoke test suite
- CI integration
- Test documentation
Phase 4: Build Optimization (0.5 day)
Tasks:
-
Optimize PyInstaller spec
- Exclude unnecessary dependencies
- Use UPX compression
- Optimize imports
-
Reduce file size
- Target: < 100 MB compressed
- Test different PyInstaller options
-
Improve startup time
- Lazy load modules
- Optimize imports
- Profile and benchmark
Deliverables:
- Optimized build configuration
- Size/performance benchmarks
Phase 5: Documentation (0.5 day)
Tasks:
- Update README with download instructions
- Create
docs/BUILD.mdfor manual builds - Create
docs/INSTALL.mdfor end users - Add GitHub releases template
- Document artifact locations
Deliverables:
- Complete user documentation
- Developer build documentation
- Release process documentation
Detailed GitHub Actions Workflow
File Structure
.github/
└── workflows/
├── build-exe.yml # Main build workflow
├── release.yml # Release creation workflow
└── test-exe.yml # Smoke test workflow
build/
├── gui.spec # PyInstaller configuration
├── icon.ico # Application icon
└── metadata/ # Version info, etc.
Workflow: build-exe.yml
name: Build Windows Executable
on:
push:
branches: [ main, develop, 'feature/**' ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller
pip install -e .
- name: Build executable
run: |
pyinstaller build/gui.spec --clean --noconfirm
- name: Run smoke tests
run: |
$exePath = "dist\Wareflow-GUI.exe"
Start-Process -FilePath $exePath -ArgumentList "--help" -Wait
- name: Generate checksums
run: |
Get-FileHash dist\Wareflow-GUI.exe -Algorithm SHA256 | Select-Object -ExpandProperty Hash > dist\Wareflow-GUI.exe.sha256
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: wareflow-gui-windows
path: |
dist/Warehouse-GUI.exe
dist/Warehouse-GUI.exe.sha256
retention-days: 30
- name: Upload build info
uses: actions/upload-artifact@v4
with:
name: build-info
path: |
dist/*.txt
retention-days: 30Workflow: release.yml (on tags)
name: Create Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: wareflow-gui-windows
path: ./artifacts
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
artifacts/Warehouse-GUI.exe
artifacts/Warehouse-GUI.exe.sha256
generate_release_notes: true
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}PyInstaller Configuration
gui.spec
# -*- mode: python ; coding: utf-8 -*-
import sys
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
block_cipher = None
a = Analysis(
['src/wareflow_analysis/gui/__main__.py'],
pathex=[],
binaries=[],
datas=[
# Source code
('src/wareflow_analysis', 'wareflow_analysis'),
# Templates
('src/wareflow_analysis/templates', 'wareflow_analysis/templates'),
],
hiddenimports=[
'customtkinter',
'tkinter',
'_tkinter',
'pandas',
'pandas._libs.tslibs.base',
'pandas._libs.tslibs.dtypes',
'pandas._libs.tslibs.np_datetime',
'pandas._libs.tslibs.nattype',
'openpyxl',
'openpyxl.cell._writer',
'yaml',
'sqlite3',
'PIL',
'PIL._tkinter_finder',
'darkdetect',
'excel_to_sql',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[
# Exclude test modules
'pytest',
'tests',
'unittest',
# Exclude development tools
'ruff',
'black',
'mypy',
# Exclude unnecessary stdlib
'email',
'html',
'http',
'urllib3',
],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
# Remove duplicate files
for src in a.datas:
if '__pycache__' in src[0]:
a.datas.remove(src)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='Warehouse-GUI',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False, # Windowed mode (no console)
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='build/icon.ico',
version_file='build/version_info.txt',
)version_info.txt
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(0, 6, 0, 0),
prodvers=(0, 6, 0, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[
StringStruct(u'CompanyName', u'Wareflow'),
StringStruct(u'FileDescription', u'Warehouse Data Analysis GUI'),
StringStruct(u'FileVersion', u'0.6.0.0'),
StringStruct(u'InternalName', u'Warehouse-GUI'),
StringStruct(u'LegalCopyright', u'MIT License'),
StringStruct(u'OriginalFilename', u'Warehouse-GUI.exe'),
StringStruct(u'ProductName', u'Wareflow Analysis'),
StringStruct(u'ProductVersion', u'0.6.0.0')
]
)
]
),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)
Smoke Tests
tests/integration/test_exeSmokeTests.py
"""Smoke tests for compiled executable."""
import subprocess
import time
from pathlib import Path
class TestExeSmokeTests:
"""Basic smoke tests for the compiled .exe."""
@staticmethod
def get_exe_path() -> Path:
"""Get path to compiled executable."""
# Check dist directory
exe_paths = [
Path("dist/Warehouse-GUI.exe"),
Path("artifacts/Warehouse-GUI.exe"),
]
for path in exe_paths:
if path.exists():
return path
raise FileNotFoundError("Executable not found")
def test_exe_exists(self):
"""Test that executable was built."""
exe_path = self.get_exe_path()
assert exe_path.exists()
assert exe_path.stat().st_size > 1_000_000 # At least 1 MB
def test_exe_runs(self):
"""Test that executable launches without crashing."""
exe_path = self.get_exe_path()
# Run with timeout
result = subprocess.run(
[str(exe_path)],
timeout=5,
capture_output=True
)
# Should either run successfully or show window
# Exit code 0 or 1 is acceptable (user closed window)
assert result.returncode in [0, 1]
def test_exe_version(self):
"""Test executable version info."""
exe_path = self.get_exe_path()
# Try to read version info
result = subprocess.run(
["powershell", "(Get-Item '').VersionInfo"],
capture_output=True
)
# Should have version info
assert result.returncode == 0
def test_checksum_exists(self):
"""Test that checksum file exists and is valid."""
exe_path = self.get_exe_path()
checksum_path = exe_path.with_suffix('.exe.sha256')
assert checksum_path.exists()
content = checksum_path.read_text()
assert len(content) == 64 # SHA256 hash length
def test_exe_imports_work(self):
"""Test that all imports work in frozen environment."""
# This would be run inside the exe with special test mode
passBuild Optimization
Exclusion Strategy
excludes=[
# Testing
'pytest', 'unittest', 'mock', 'coverage',
# Development
'ruff', 'black', 'mypy', 'flake8', 'pylint',
# Unused stdlib
'email', 'smtplib', 'email.mime',
'html', 'html.parser',
'http', 'http.server', 'urllib3', 'requests',
'xml', 'xmlrpc',
# Database servers
'psycopg2', 'pymysql', 'cx_oracle',
'redis', 'pymongo',
]Optimization Goals
| Metric | Target | Strategy |
|---|---|---|
| File Size | < 100 MB | UPX compression, excludes |
| Startup Time | < 3 sec | Lazy imports, optimization |
| Memory Usage | < 200 MB idle | Cleanup, proper garbage collection |
| First Render | < 2 sec | Optimized imports |
Release Process
Automated Release Flow
1. Developer merges feature to main
↓
2. Version bump in pyproject.toml (0.6.0 → 0.7.0)
↓
3. Create git tag: git tag v0.7.0
↓
4. Push tag: git push origin v0.7.0
↓
5. GitHub Actions triggers
↓
6. Builds .exe (10-15 minutes)
↓
7. Runs smoke tests
↓
8. Creates GitHub Release
↓
9. Uploads artifacts:
- Warehouse-GUI.exe
- Warehouse-GUI.exe.sha256
- Release notes
↓
10. Users download from Releases page
Version Bumping Strategy
# Update version in pyproject.toml
version = "0.7.0"
# Commit version bump
git add pyproject.toml
git commit -m "chore: bump version to 0.7.0"
# Create and push tag
git tag v0.7.0
git push origin main --tagsSuccess Criteria
Functionality
- .exe launches without Python installed
- All GUI features work in compiled version
- No console window appears
- Application icon displays correctly
- Version info shows in file properties
Performance
- File size < 100 MB
- Startup time < 3 seconds
- Memory usage < 200 MB idle
- No memory leaks detected
CI/CD
- Build completes successfully on every push
- Artifacts uploaded and downloadable
- Releases created automatically on tags
- Checksums generated correctly
- Smoke tests pass in CI
Documentation
- User can download and run .exe without issues
- Documentation updated with download instructions
- Developer build documentation complete
- Release process documented
Risks & Mitigation
| Risk | Impact | Mitigation |
|---|---|---|
| Large file size | Medium | UPX compression, exclude unnecessary deps |
| Build fails on CI | High | Test builds locally first, use stable Python version |
| False positive antivirus | Medium | Code signing certificate (future), reputation building |
| Missing dependencies | High | Comprehensive hidden imports list, thorough testing |
| Windows-only | Low | Document clearly, offer Mac/Linux builds later |
| Slow builds | Low | Cache dependencies, parallel jobs if needed |
Future Enhancements
- Code Signing - Purchase certificate to sign .exe
- MacOS Build - Create .dmg for macOS
- Linux Build - Create .AppImage for Linux
- Auto-update - Implement in-app update checking
- Installer - Create NSIS installer with shortcuts
- Portable Version - ZIP version without installation
- Smaller Size - Try PyInstaller alternatives (Nuitka, cx_Freeze)
- Faster Builds - Use caching, build matrix
Timeline Estimation
Total Effort: 3.5 days
- Phase 1: PyInstaller Config (1 day)
- Phase 2: GitHub Actions (1 day)
- Phase 3: Build Testing (0.5 day)
- Phase 4: Optimization (0.5 day)
- Phase 5: Documentation (0.5 day)
Dependencies:
- PyInstaller expertise
- GitHub Actions knowledge
- Windows machine for testing
- Icon design assets
Priority: Medium-High
- User value: High (easier adoption)
- Technical risk: Low (proven tools)
- Effort: Moderate
Issue ID: 003
Created: 2025-01-26
Last Updated: 2025-01-26
Status: Open - Planning
Assignee: TBD
Labels: ci-cd, build, windows, enhancement
Milestone: v0.7.0 or v1.0.0