Skip to content
Open
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
85 changes: 58 additions & 27 deletions .github/workflows/lint-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ on:
- "*.toml"
- "uv.lock"

permissions:
contents: read

env:
GITHUB_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
GITHUB_EXCLUDE_BUILD_NUMBER: ${{ inputs.excludeBuildNumber }}
Expand All @@ -44,7 +47,31 @@ defaults:
shell: pwsh

jobs:
PySentry:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- run: uvx pysentry-rs --sources=pypa,pypi,osv --forbid-unmaintained

# Single source of truth for the shipped Python version(s).
# GitHub Actions has no length() and `strategy` can't read `env`, so the matrix and the
# "more than one Python version" flag are both derived here and consumed via `needs`.
Setup:
runs-on: ubuntu-slim
outputs:
python-versions: ${{ steps.set.outputs.python-versions }}
include-python-version-tag: ${{ steps.set.outputs.include-python-version-tag }}
steps:
- id: set
# Only the Python version(s) we plan on shipping matter.
run: |
$pythonVersions = @('3.14')
"python-versions=$($pythonVersions | ConvertTo-Json -Compress -AsArray)" >> $Env:GITHUB_OUTPUT
"include-python-version-tag=$(if ($pythonVersions.Count -gt 1) { '-IncludePythonVersionTag' })" >> $Env:GITHUB_OUTPUT

Pyright:
needs: Setup
runs-on: ${{ matrix.os }}
env:
# Prevent accidentally slower type-checking due to missing arm wheels.
Expand All @@ -60,7 +87,7 @@ jobs:
matrix:
# windows arm runner slower as long as opencv doesn't provide windows arm64 wheels
os: [windows-latest, ubuntu-24.04-arm]
python-version: ["3.14"]
python-version: ${{ fromJson(needs.Setup.outputs.python-versions) }}
steps:
- uses: actions/checkout@v6
- name: Set up uv for Python ${{ matrix.python-version }}
Expand All @@ -76,23 +103,16 @@ jobs:
working-directory: src/
python-version: ${{ matrix.python-version }}

PySentry:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- run: uvx pysentry-rs --sources=pypa,pypi,osv --forbid-unmaintained

Build:
needs: Setup
runs-on: ${{ matrix.os }}
outputs:
AUTOSPLIT_VERSION: ${{ steps.artifact_vars.outputs.AUTOSPLIT_VERSION }}
strategy:
fail-fast: false
# Only the Python version we plan on shipping matters.
matrix:
os: [windows-latest, windows-11-arm, ubuntu-24.04, ubuntu-24.04-arm]
python-version: ["3.14"]
python-version: ${{ fromJson(needs.Setup.outputs.python-versions) }}
wine-compat: [""]
include:
- os: windows-latest
Expand Down Expand Up @@ -122,14 +142,12 @@ jobs:
|| null }}
# endregion
- run: scripts/install.ps1 ${{ matrix.wine-compat }}
- run: scripts/build.ps1 ${{ matrix.wine-compat }}
- run: scripts/build.ps1 ${{ matrix.wine-compat }} ${{ needs.Setup.outputs.include-python-version-tag }}
- name: Run test suite
# pywinctl/pymonctl connect to the X display at import-time, hence xvfb
run: >-
${{ startsWith(matrix.os, 'ubuntu') && 'xvfb-run --auto-servernum' || '' }}
uv run -m unittest discover --start-directory tests --verbose
- name: Add empty profile
run: echo "" > dist/settings.toml
- name: Extract AutoSplit version
id: artifact_vars
working-directory: src
Expand All @@ -138,41 +156,54 @@ jobs:
echo "AUTOSPLIT_VERSION=$Env:AUTOSPLIT_VERSION" >> $Env:GITHUB_OUTPUT
echo "OS=$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)" >> $Env:GITHUB_OUTPUT
- name: Upload Build Artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: >
AutoSplit v${{ steps.artifact_vars.outputs.AUTOSPLIT_VERSION }} for ${{
steps.artifact_vars.outputs.OS }}${{ matrix.wine-compat }} (Python ${{
matrix.python-version }})
path: |
dist/AutoSplit*
dist/settings.toml
path: dist/AutoSplit*
if-no-files-found: error
- name: Upload Build logs
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: >
Build logs for ${{ steps.artifact_vars.outputs.OS }}${{ matrix.wine-compat }} (Python
${{ matrix.python-version }})
path: |
build/AutoSplit/*.toc
build/AutoSplit/*.txt
build/AutoSplit/*.html
build/AutoSplit*/*.toc
build/AutoSplit*/*.txt
build/AutoSplit*/*.html
if-no-files-found: error

Release-Template:
Merge-Build-Artifacts:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
needs: Build
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Merge all executables into a single artifact
uses: actions/upload-artifact/merge@v7
with:
name: AutoSplit v${{ needs.Build.outputs.AUTOSPLIT_VERSION }}
# Only the executables, not the per-OS build logs. Keep the individual downloads
pattern: AutoSplit v*

Release-Template:
needs: [Setup, Build]
runs-on: ubuntu-slim
steps:
- name: Annotate release template URL
run: |
$version = "v${{ needs.Build.outputs.AUTOSPLIT_VERSION }}"
$body = [uri]::EscapeDataString(((@'
$pythonBullet = if ('${{ needs.Setup.outputs.include-python-version-tag }}') {
"- Python: This is the Python version bundled with AutoSplit. Try the newer version, it should be functionally identical, with a marginal performance boost. If you have any issue with it, please [report it here](https://github.com/Toufool/AutoSplit/issues) or on the Discord server and use an older Python version in the mean time."
}
else { '' }
$body = [uri]::EscapeDataString((@'
# Which Asset should I download?

- Python: This is the Python version bundled with AutoSplit. Try the newer version, it should be functionally identical, with a marginal performance boost. If you have any issue with it, please [report it here](https://github.com/Toufool/AutoSplit/issues) or on the Discord server and use an older Python version in the mean time.
- `arm64` vs `x64`: [Check your Processor Platform Architecture](https://www.checkadevice.com/tests/system/) (note that `x86_64` and `x64` means the same). If you're still unsure, `x64` will work either way. `arm64` should be more efficient.
{{PYTHON}}
- `arm64` vs `x64`: [Check your Processor Platform Architecture](https://www.checkadevice.com/tests/system/) (note that `x86_64`==`x64` and `aarch64`==`arm64`). If you're still unsure, `x64` will work either way. `arm64` should be more efficient.
- WineCompat: This is for running the Windows executable under Wine on Linux
'@).Trim() -replace "`n", ''))
'@).Replace('{{PYTHON}}', $pythonBullet).Trim() -replace "`n", '')
# ^ Removing newline from template because GitHub will actually decode %0A
echo "::notice::${{ github.server_url }}/${{ github.repository }}/releases/new?target=main&tag=$version&title=$version&body=$body"
2 changes: 1 addition & 1 deletion res/design.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ ClickableLabel:hover { background-color: palette(midlight); }</string>
<string>Toggle Logs</string>
</property>
<property name="shortcut">
<string>Ctrl+L</string>
<string>Ctrl+J</string>
</property>
<property name="shortcutContext">
<enum>Qt::ShortcutContext::ApplicationShortcut</enum>
Expand Down
58 changes: 49 additions & 9 deletions scripts/build.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#! /usr/bin/pwsh

param([switch]$WineCompat)
param(
[switch]$WineCompat,
[switch]$IncludePythonVersionTag
)

$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Expand All @@ -10,8 +13,15 @@ Push-Location "$PSScriptRoot/.." # Avoid issues with space in path
try {
& 'scripts/compile_resources.ps1'

$version = (Select-String 'pyproject.toml' -Pattern '^version = "(.+)"').Matches.Groups[1].Value
# Semver-compliant Python version tag
$pythonVersionTag = if ($IncludePythonVersionTag) {
(uv run --active python --version) -replace '^Python (\d+\.\d+).*', '+Python$1'
}
else { '' }

# CI not allowed to skip splash screen, it MUST build (will fail when calling PyInstaller)
$SupportsSplashScreen = $Env:GITHUB_JOB -or [System.Convert]::ToBoolean(
$supportsSplashScreen = $Env:GITHUB_JOB -or [System.Convert]::ToBoolean(
$(uv run --active scripts/check_splash_support.py))

$arguments = @(
Expand All @@ -30,13 +40,15 @@ try {
# Missing upx executable should be enough, but let's be explicit
$arguments += '--noupx'
}
if ($SupportsSplashScreen) {
if ($supportsSplashScreen) {
# https://github.com/pyinstaller/pyinstaller/issues/9022
$arguments += @('--splash=res/splash.png')
}
if ($IsWindows) {
$arch = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)".ToLower()
$arguments += @(
'--onefile',
"--name=AutoSplit-$version$pythonVersionTag-$arch$(if ($WineCompat) {'-WineCompat'} else {''})"
# Hidden import by winrt.windows.graphics.imaging.SoftwareBitmap.create_copy_from_surface_async
'--hidden-import=winrt.windows.foundation')
}
Expand All @@ -58,7 +70,13 @@ try {
Move-Item build/AppDir/AutoSplit/_internal build/AppDir/_internal
Remove-Item build/AppDir/AutoSplit

if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'X64') {
$arch = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
'X64' { 'x86_64' }
'Arm64' { 'aarch64' }
default { throw "Unsupported arch: $_" }
}

if ($arch -eq 'x86_64') {
# Technically UPX works for Linux executables, but trying to compress .so can still result in Segmentation fault
# https://github.com/orgs/pyinstaller/discussions/8922#discussioncomment-13185670
# https://github.com/pyinstaller/pyinstaller/blob/4d28a528f8ab8632f7cfa7662fc6fcc45881e741/PyInstaller/building/utils.py#L281-L288
Expand Down Expand Up @@ -87,8 +105,12 @@ try {
# Create AppImage
###
Copy-Item res/AutoSplit.desktop build/AppDir/AutoSplit.desktop
Copy-Item res/splash.png build/AppDir/AutoSplit.png
$version = (Select-String 'pyproject.toml' -Pattern '^version = "(.+)"').Matches.Groups[1].Value
# Icon as PNG (freedesktop doesn't support .ico), converted from res/icon.ico.
# Not splash.png, which uses hard transparency for the Tcl/Tk splash.
New-Item -ItemType Directory -Path build/AppDir/usr/share/icons/hicolor/256x256/apps -Force | Out-Null
uv run --active python -c "from PIL import Image; Image.open('res/icon.ico').save('build/AppDir/AutoSplit.png')"
# Top-level -> .DirIcon (file thumbnail); hicolor copy -> desktop integration (menu/taskbar).
Copy-Item build/AppDir/AutoSplit.png build/AppDir/usr/share/icons/hicolor/256x256/apps/AutoSplit.png
$date = Get-Date -Format 'yyyy-MM-dd'

New-Item -ItemType Directory -Path build/AppDir/usr/share/metainfo -Force | Out-Null
Expand All @@ -99,10 +121,28 @@ try {
if (Test-Path dist) { Remove-Item dist -Recurse -Force }
New-Item -ItemType Directory -Path dist | Out-Null

& 'scripts/appimagetool.AppImage' build/AppDir dist/AutoSplit.AppImage
chmod +x dist/AutoSplit.AppImage
# AppImage naming nomenclature:
# - https://github.com/AppImage/AppImageSpec/blob/master/draft.md#type-2-image-format
# - https://github.com/AppImage/appimage.github.io#:~:text=Standard%20nomenclature
$appImageName = "AutoSplit-$version$pythonVersionTag-$arch.AppImage"
$arguments = @('build/AppDir', "dist/$appImageName")
# Update information
# https://docs.appimage.org/packaging-guide/optional/updates.html#using-appimagetool
# https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
if ($Env:GITHUB_REPOSITORY) {
# Skip update information if not doing a GitHub build
$owner, $repo = $Env:GITHUB_REPOSITORY -split '/'
$arguments += @('-u', "gh-releases-zsync|$owner|$repo|latest|AutoSplit-*-$arch.AppImage.zsync")
}
& 'scripts/appimagetool.AppImage' @arguments

# appimagetool writes the .zsync file to the working directory (repo root) as the AppImage
# basename, not next to the AppImage. Move it into dist/.
if (Test-Path "$appImageName.zsync") {
Move-Item "$appImageName.zsync" "dist/$appImageName.zsync" -Force
}

Write-Host 'Created dist/AutoSplit.AppImage'
Write-Host "Created dist/$appImageName"
}
}
finally {
Expand Down
2 changes: 1 addition & 1 deletion scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ if ($IsLinux) {

Write-Output 'Installing appimagetool'
Invoke-WebRequest `
-Uri "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage" `
-Uri "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage" `
-OutFile "$PSScriptRoot/appimagetool.AppImage"
chmod +x "$PSScriptRoot/appimagetool.AppImage"
}
Expand Down
3 changes: 3 additions & 0 deletions src/AutoSplit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,9 @@ def main():
myappid = f"Toufool.AutoSplit.v{AUTOSPLIT_VERSION}"
shell32.SetCurrentProcessExplicitAppUserModelID(myappid)

# Decouple from the executable basename (which varies per build)
app.setApplicationName("AutoSplit")
app.setApplicationVersion(AUTOSPLIT_VERSION)
app.setWindowIcon(QtGui.QIcon(":/resources/icon.ico"))

if is_already_open():
Expand Down
6 changes: 3 additions & 3 deletions src/error_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def _set_text_message(
):
# Also surface the error message in the logs
plain_message = QtGui.QTextDocumentFragment.fromHtml(message).toPlainText()
sys.stderr.write(f"{plain_message}\n{details}\n" if details else f"{plain_message}\n")
print(f"{plain_message}\n{details}\n" if details else f"{plain_message}", sys.stderr)

message_box = QtWidgets.QMessageBox()
message_box.setWindowTitle("Error")
Expand Down Expand Up @@ -144,9 +144,9 @@ def invalid_hotkey(hotkey_name: str):


def no_settings_file_on_open():
_set_text_message(
print(
"No settings file found. "
+ "One can be loaded on open if placed in the same folder as the AutoSplit executable."
+ "One can be loaded on open if placed in the same folder as the AutoSplit executable.",
)


Expand Down
Loading