Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
push:
tags:
- "v*"
workflow_dispatch: {}

jobs:
build:
Expand Down Expand Up @@ -38,6 +39,7 @@ jobs:
publish:
name: Publish to PyPI
needs: build
if: github.event_name == 'push'
runs-on: ubuntu-latest
environment:
name: pypi
Expand All @@ -53,3 +55,124 @@ jobs:

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

windows-build:
name: Build Windows executable
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install project and PyInstaller
run: python -m pip install ".[pyinstaller]"

- name: Build executable
run: pyinstaller packaging/windows/rixsviewer.spec

- name: Rename executable with version
shell: bash
run: mv dist/rixsviewer.exe "dist/rixsviewer-${{ github.ref_name }}-windows.exe"

- name: Smoke test executable launches
shell: pwsh
env:
QT_QPA_PLATFORM: offscreen
run: |
$exe = Get-ChildItem dist -Filter "rixsviewer-*-windows.exe" | Select-Object -First 1
$proc = Start-Process -FilePath $exe.FullName -PassThru
Start-Sleep -Seconds 8
if ($proc.HasExited) {
Write-Error "Executable exited early with code $($proc.ExitCode)"
exit 1
}
Write-Host "Executable is running after 8s, smoke test passed"
Stop-Process -Id $proc.Id -Force

- name: Upload Windows executable artifact
uses: actions/upload-artifact@v4
with:
name: windows-exe
path: dist/rixsviewer-*-windows.exe

linux-appimage-build:
name: Build Linux AppImage
runs-on: ubuntu-latest
container: rockylinux:9
steps:
- name: Install git
run: dnf install -y git

- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install system dependencies
run: |
dnf install -y python3.11 python3.11-pip \
mesa-libGL mesa-libEGL libxkbcommon libxkbcommon-x11 \
xcb-util-wm xcb-util-image xcb-util-keysyms xcb-util-renderutil \
dbus-libs fontconfig freetype file

- name: Install project and PyInstaller
run: python3.11 -m pip install ".[pyinstaller]"

- name: Build AppImage
run: bash packaging/linux/build-appimage.sh "${{ github.ref_name }}"

- name: Smoke test AppImage launches
env:
QT_QPA_PLATFORM: offscreen
run: |
APPIMAGE=$(ls dist/rixsviewer-*-x86_64.AppImage)
chmod +x "$APPIMAGE"
timeout 8 "$APPIMAGE" --appimage-extract-and-run > /tmp/run.log 2>&1 &
RPID=$!
sleep 6
if kill -0 $RPID 2>/dev/null; then
echo "Smoke test passed: AppImage is running after 6s"
kill $RPID
else
wait $RPID || true
echo "Smoke test FAILED: AppImage exited early"
cat /tmp/run.log
exit 1
fi

- name: Upload Linux AppImage artifact
uses: actions/upload-artifact@v4
with:
name: linux-appimage
path: dist/rixsviewer-*-x86_64.AppImage

github-release:
name: Publish GitHub Release
needs: [windows-build, linux-appimage-build]
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download Windows executable artifact
uses: actions/download-artifact@v4
with:
name: windows-exe
path: dist/

- name: Download Linux AppImage artifact
uses: actions/download-artifact@v4
with:
name: linux-appimage
path: dist/

- name: Create/update GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
dist/rixsviewer-*-windows.exe
dist/rixsviewer-*-x86_64.AppImage
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ MANIFEST
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
!packaging/windows/*.spec
!packaging/linux/*.spec

# Installer logs
pip-log.txt
Expand Down
3 changes: 3 additions & 0 deletions packaging/linux/AppRun
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"
exec "${HERE}/usr/bin/rixsviewer/rixsviewer" "$@"
20 changes: 20 additions & 0 deletions packaging/linux/build-appimage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# packaging/linux/build-appimage.sh
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")/../.."
VERSION="${1:?Usage: build-appimage.sh <version-tag>}"

rm -rf build dist/rixsviewer dist/AppDir
python3.11 -m PyInstaller packaging/linux/rixsviewer.spec

mkdir -p dist/AppDir/usr/bin
cp -r dist/rixsviewer dist/AppDir/usr/bin/rixsviewer
install -m 755 packaging/linux/AppRun dist/AppDir/AppRun
cp packaging/linux/rixsviewer.desktop dist/AppDir/rixsviewer.desktop
cp src/rixsviewer/assets/icon.png dist/AppDir/rixsviewer.png

curl -sL -o dist/appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x dist/appimagetool

dist/appimagetool --appimage-extract-and-run dist/AppDir "dist/rixsviewer-${VERSION}-x86_64.AppImage"
4 changes: 4 additions & 0 deletions packaging/linux/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from rixsviewer.rixsviewer_gui import main

if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions packaging/linux/rixsviewer.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=RixsViewer
Exec=rixsviewer
Icon=rixsviewer
Categories=Science;
Terminal=false
50 changes: 50 additions & 0 deletions packaging/linux/rixsviewer.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# packaging/linux/rixsviewer.spec
# -*- mode: python ; coding: utf-8 -*-
from pathlib import Path

spec_dir = Path(SPECPATH)
repo_root = spec_dir.parent.parent

a = Analysis(
[str(spec_dir / 'entrypoint.py')],
pathex=[],
binaries=[],
datas=[
(str(repo_root / 'src' / 'rixsviewer' / 'assets'), 'rixsviewer/assets'),
],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['epics'],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='rixsviewer',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name='rixsviewer',
)
4 changes: 4 additions & 0 deletions packaging/windows/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from rixsviewer.rixsviewer_gui import main

if __name__ == "__main__":
main()
Binary file added packaging/windows/icon.ico
Binary file not shown.
45 changes: 45 additions & 0 deletions packaging/windows/rixsviewer.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# packaging/windows/rixsviewer.spec
# -*- mode: python ; coding: utf-8 -*-
from pathlib import Path

spec_dir = Path(SPECPATH)
repo_root = spec_dir.parent.parent

a = Analysis(
[str(spec_dir / 'entrypoint.py')],
pathex=[],
binaries=[],
datas=[
(str(repo_root / 'src' / 'rixsviewer' / 'assets'), 'rixsviewer/assets'),
],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['epics'],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='rixsviewer',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(spec_dir / 'icon.ico'),
)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dependencies = [
epics = ["pyepics"]
dev = ["pytest>=7.0.0", "black>=22.0.0", "flake8>=4.0.0"]
docs = ["sphinx>=6.0.0", "sphinx-rtd-theme>=1.0.0", "numpydoc>=1.5.0"]
pyinstaller = ["pyinstaller>=6.21"]

[project.scripts]
rixsviewer = "rixsviewer.rixsviewer_gui:main"
Expand Down
5 changes: 5 additions & 0 deletions src/rixsviewer/rixsviewer_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,11 @@ def main():
# Create QApplication with remaining arguments
app = QApplication([sys.argv[0]] + qt_args)

if sys.platform == "darwin":
pass # keep native macOS style
else:
app.setStyle("Fusion") # Windows / Linux

icon_path = Path(__file__).parent / "assets" / "icon.png"
if icon_path.exists():
app.setWindowIcon(QIcon(str(icon_path)))
Expand Down
Loading