Skip to content

Commit 0af290d

Browse files
feat: Implement standalone executable builds with a new build script, PyInstaller configuration, and GitHub Actions workflow, updating the roadmap status.
1 parent 1456b44 commit 0af290d

File tree

4 files changed

+219
-1
lines changed

4 files changed

+219
-1
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Build Executables
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
8+
jobs:
9+
build:
10+
strategy:
11+
matrix:
12+
include:
13+
- os: ubuntu-latest
14+
platform: linux
15+
- os: macos-latest
16+
platform: macos
17+
- os: windows-latest
18+
platform: windows
19+
20+
runs-on: ${{ matrix.os }}
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Set up Python
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: "3.11"
29+
30+
- name: Install dependencies
31+
run: |
32+
python -m pip install --upgrade pip
33+
pip install -r requirements.txt
34+
pip install pyinstaller
35+
36+
- name: Build executable
37+
run: python packaging/build_executable.py
38+
39+
- name: Upload artifact
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: git-chronoscope-${{ matrix.platform }}
43+
path: dist/git-chronoscope-*
44+
45+
release:
46+
needs: build
47+
runs-on: ubuntu-latest
48+
if: github.event_name == 'release'
49+
permissions:
50+
contents: write
51+
52+
steps:
53+
- name: Download all artifacts
54+
uses: actions/download-artifact@v4
55+
with:
56+
path: artifacts
57+
58+
- name: Upload to release
59+
uses: softprops/action-gh-release@v1
60+
with:
61+
files: artifacts/**/*

packaging/build_executable.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Build script for creating standalone executables.
4+
Usage: python packaging/build_executable.py
5+
"""
6+
import os
7+
import sys
8+
import subprocess
9+
import platform
10+
import shutil
11+
12+
13+
def get_platform_suffix():
14+
"""Get platform-specific suffix for executable."""
15+
system = platform.system().lower()
16+
if system == 'windows':
17+
return 'windows.exe'
18+
elif system == 'darwin':
19+
return 'macos'
20+
else:
21+
return 'linux'
22+
23+
24+
def build_executable():
25+
"""Build the standalone executable using PyInstaller."""
26+
# Get project root
27+
script_dir = os.path.dirname(os.path.abspath(__file__))
28+
project_root = os.path.dirname(script_dir)
29+
30+
print(f"Building git-chronoscope executable for {platform.system()}...")
31+
32+
# Install PyInstaller if needed
33+
try:
34+
import PyInstaller
35+
except ImportError:
36+
print("Installing PyInstaller...")
37+
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'pyinstaller'])
38+
39+
# Change to project root
40+
os.chdir(project_root)
41+
42+
# Build command
43+
cmd = [
44+
sys.executable, '-m', 'PyInstaller',
45+
'--onefile',
46+
'--name', 'git-chronoscope',
47+
'--console',
48+
'--clean',
49+
'--noconfirm',
50+
# Hidden imports for Pygments
51+
'--collect-submodules', 'pygments.lexers',
52+
'--collect-submodules', 'pygments.styles',
53+
# Exclude unnecessary modules
54+
'--exclude-module', 'tkinter',
55+
'--exclude-module', 'matplotlib',
56+
'--exclude-module', 'scipy',
57+
'--exclude-module', 'pytest',
58+
# Entry point
59+
'src/main.py',
60+
]
61+
62+
print(f"Running: {' '.join(cmd)}")
63+
subprocess.check_call(cmd)
64+
65+
# Rename output with platform suffix
66+
dist_dir = os.path.join(project_root, 'dist')
67+
suffix = get_platform_suffix()
68+
69+
if platform.system() == 'Windows':
70+
src = os.path.join(dist_dir, 'git-chronoscope.exe')
71+
dst = os.path.join(dist_dir, f'git-chronoscope-{suffix}')
72+
else:
73+
src = os.path.join(dist_dir, 'git-chronoscope')
74+
dst = os.path.join(dist_dir, f'git-chronoscope-{suffix}')
75+
76+
if os.path.exists(src):
77+
shutil.move(src, dst)
78+
print(f"Created: {dst}")
79+
80+
print("Build complete!")
81+
return dst
82+
83+
84+
if __name__ == '__main__':
85+
build_executable()

packaging/pyinstaller_config.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# PyInstaller Configuration for git-chronoscope
2+
#
3+
# Build with:
4+
# cd git-chronoscope
5+
# pip install pyinstaller
6+
# pyinstaller packaging/pyinstaller_config.py
7+
#
8+
# Or use the helper script:
9+
# python packaging/build_executable.py
10+
11+
import sys
12+
import os
13+
14+
# Add parent directory to path for imports
15+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
16+
17+
from PyInstaller.utils.hooks import collect_submodules
18+
19+
block_cipher = None
20+
21+
# Collect all Pygments submodules for syntax highlighting
22+
hiddenimports = collect_submodules('pygments.lexers') + collect_submodules('pygments.styles')
23+
24+
a = Analysis(
25+
[os.path.join('..', 'src', 'main.py')],
26+
pathex=[os.path.dirname(os.path.dirname(os.path.abspath(__file__)))],
27+
binaries=[],
28+
datas=[
29+
(os.path.join('..', 'README.md'), '.'),
30+
],
31+
hiddenimports=hiddenimports,
32+
hookspath=[],
33+
hooksconfig={},
34+
runtime_hooks=[],
35+
excludes=[
36+
'tkinter',
37+
'matplotlib',
38+
'scipy',
39+
'numpy.testing',
40+
'pytest',
41+
],
42+
win_no_prefer_redirects=False,
43+
win_private_assemblies=False,
44+
cipher=block_cipher,
45+
noarchive=False,
46+
)
47+
48+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
49+
50+
exe = EXE(
51+
pyz,
52+
a.scripts,
53+
a.binaries,
54+
a.zipfiles,
55+
a.datas,
56+
[],
57+
name='git-chronoscope',
58+
debug=False,
59+
bootloader_ignore_signals=False,
60+
strip=False,
61+
upx=True,
62+
upx_exclude=[],
63+
runtime_tmpdir=None,
64+
console=True,
65+
disable_windowed_traceback=False,
66+
argv_emulation=False,
67+
target_arch=None,
68+
codesign_identity=None,
69+
entitlements_file=None,
70+
icon=None,
71+
)

roadmap/distribution/03-standalone-executable.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Standalone Executable
22

33
## Implementation Status
4-
**⏳ NOT IMPLEMENTED**
4+
**✅ IMPLEMENTED**
5+
- Implementation: `packaging/build_executable.py`, `.github/workflows/build-executables.yml`
56

67
## 1. Feature Description
78

0 commit comments

Comments
 (0)