Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
15aea80
Python 3.15 Linux builds
Avasam Jun 10, 2026
6d35e4e
Linux 3.15 builds
Avasam Jun 11, 2026
7229002
Add lazy imports
Avasam Jun 11, 2026
99e0804
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 11, 2026
31c8a16
Merge branch 'main' into 3.15-linux-builds
Avasam Jun 11, 2026
42c2ca7
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 11, 2026
7ee9bed
Apply suggestion from @Avasam
Avasam Jun 11, 2026
8e737e7
pickup local wheels
Avasam Jun 11, 2026
125988c
Move CMAKE_ARGS to shared local + CI install script
Avasam Jun 11, 2026
9e3ae66
Verbose uv sync
Avasam Jun 11, 2026
b610b8a
pyinstaller 3.15
Avasam Jun 12, 2026
bcb9c96
Fix build script
Avasam Jun 12, 2026
0542ae5
Add Windows 3.15 prebuilds
Avasam Jun 12, 2026
85a4230
Use lazy filter
Avasam Jun 12, 2026
4ba7e1c
fix build
Avasam Jun 12, 2026
4243a1c
uv lock
Avasam Jun 12, 2026
59ed074
ignore pyright error
Avasam Jun 12, 2026
1192586
Replicate tcltk_info.available check
Avasam Jun 12, 2026
1201d7f
Use numpy from index
Avasam Jun 12, 2026
5215ad3
Deprecated numpy methods
Avasam Jun 12, 2026
a092f54
Add UV_PYTHON ot even local script
Avasam Jun 12, 2026
0d15239
Try python-version fixes
Avasam Jun 12, 2026
e1ad4d8
fixed accidentally removed line
Avasam Jun 12, 2026
977d24a
Avoid SupportsSplashScreen check on CI
Avasam Jun 12, 2026
989a5e0
Fix UV_PYTHON
Avasam Jun 12, 2026
bb4c264
let skip splash on 3.15 on ci
Avasam Jun 12, 2026
11255b4
Attempt a fix for numpy 3.15 arm
Avasam Jun 12, 2026
8e93a74
correct arch
Avasam Jun 12, 2026
19c76d4
Merge branch 'main' of https://github.com/Toufool/AutoSplit into 3.15…
Avasam Jun 12, 2026
a22409d
resync
Avasam Jun 12, 2026
9196b04
additional todo comment
Avasam Jun 12, 2026
64615d3
Extract splash screen check script
Avasam Jun 12, 2026
ba2e3d4
Make own imports lazy
Avasam Jun 12, 2026
279218e
Much faster startup with lazy imports
Avasam Jun 12, 2026
1e22413
add ref https://github.com/python/cpython/issues/151208
Avasam Jun 12, 2026
8bf8fc9
Make `shell: pwsh` the default
Avasam Jun 12, 2026
6785091
Add import tests
Avasam Jun 12, 2026
742c702
Also install x11-xserver-utils
Avasam Jun 12, 2026
4480c2c
Avoid marking already installed packages as manual
Avasam Jun 12, 2026
ade00a1
Merge branch 'main' of https://github.com/Toufool/AutoSplit into 3.15…
Avasam Jun 13, 2026
631eae5
tool.uv.exclude-newer-packag.pyinstaller-hooks-contrib = false # pyin…
Avasam Jun 13, 2026
35af0a7
Merge branch 'main' into 3.15-linux-builds
Avasam Jun 15, 2026
0f12c1b
fix lockfile
Avasam Jun 15, 2026
3563338
Add Levenshtein exclusion
Avasam Jun 15, 2026
aebb397
numpy
Avasam Jun 15, 2026
820f095
Inverted condition
Avasam Jun 15, 2026
d6823fd
pyinstaller better hash
Avasam Jun 15, 2026
cf7748e
unecessary comment
Avasam Jun 15, 2026
2b9905b
Add more 3.15 markers
Avasam Jun 15, 2026
1773eca
Add more
Avasam Jun 15, 2026
fa7f24a
UV_NO_BUILD conditional
Avasam Jun 15, 2026
9a08d90
Bump pyinstaller 3.15 release
Avasam Jun 15, 2026
1091d2b
Merge branch 'main' into 3.15-linux-builds
Avasam Jun 21, 2026
b410718
Apply suggestion from @Avasam
Avasam Jun 22, 2026
77d9526
tcl/tk issue fixed in 3.5 beta 3
Avasam Jun 24, 2026
7ae33ab
Merge branch 'main' of https://github.com/Toufool/AutoSplit into 3.15…
Avasam Jun 29, 2026
4ebad74
numpy now has 3.15 dev wheels
Avasam Jun 29, 2026
4406e80
removed hacky action step
Avasam Jun 29, 2026
5d1dbee
opencv-contrib-python-headless match runtime numpy
Avasam Jun 29, 2026
765e253
try fixing opencv building numpy on 3.15
Avasam Jun 29, 2026
3d9643d
Merge branch 'main' into 3.15-linux-builds
Avasam Jun 29, 2026
9fee980
Back to explicit = true
Avasam Jun 29, 2026
4b34b3d
Merge branch '3.15-linux-builds' of https://github.com/Toufool/AutoSp…
Avasam Jun 29, 2026
31d73ab
Restored MSVC hack
Avasam Jun 29, 2026
4f3111f
Can mypy be python version unpinned
Avasam Jun 30, 2026
0a6db8c
lock
Avasam Jun 30, 2026
7004f48
Add back mypy >=2.1; python_version < '3.15'
Avasam Jun 30, 2026
a5e1d84
let lockfile fix itself
Avasam Jun 30, 2026
18b9a1e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 30, 2026
1f686cb
Merge branch 'main' into 3.15-linux-builds
Avasam Jul 3, 2026
b4d1540
Merge branch 'main' of https://github.com/Toufool/AutoSplit into 3.15…
Avasam Jul 3, 2026
0d892c3
OpenCV 5
Avasam Jul 3, 2026
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
23 changes: 18 additions & 5 deletions .github/workflows/lint-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,21 @@ jobs:
env:
# Prevent accidentally slower type-checking due to missing arm wheels.
# Fail rather than accidentally compile C/binary extensions from sdist.
UV_NO_BUILD: true
UV_NO_BUILD: ${{ !(matrix.python-version == '3.15' && matrix.os == 'windows-latest') }} # winrt-Windows no wheels for 3.15 yet
# Per-package no-binary overrides no-build, allowing
# known pure-Python source-only dependencies to still build.
# The Build job intentionally builds some binary packages from source.
UV_NO_BINARY_PACKAGE: keyboard PyAutoGUI beslogic-ruff-config
UV_NO_BINARY_PACKAGE: >-
keyboard PyAutoGUI beslogic-ruff-config
${{ matrix.python-version == '3.15' && 'RapidFuzz Levenshtein' || '' }}
# TODO: ^ Remove these exceptions once 3.15 wheels are released
strategy:
fail-fast: false
# Pyright is version and platform sensible
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: ["3.14", "3.15"]
steps:
- uses: actions/checkout@v6
- name: Set up uv for Python ${{ matrix.python-version }}
Expand Down Expand Up @@ -92,12 +95,15 @@ jobs:
# 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: ["3.14", "3.15"]
wine-compat: [""]
include:
- os: windows-latest
python-version: "3.14"
wine-compat: "-WineCompat"
- os: windows-latest
python-version: "3.15"
wine-compat: "-WineCompat"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -121,8 +127,15 @@ jobs:
&& format('{0}-{1}', matrix.python-version, endsWith(matrix.os, 'arm') && 'aarch64' || 'x86_64'))
|| null }}
# endregion
# MinGW (cc) fails SIZEOF_PY_INTPTR_T detection on ARM64; force MSVC for numpy sdist
# TODO: Remove this action once we use numpy 3.15 wheels
# - name: Set up MSVC ARM64 environment
# if: ${{ matrix.os == 'windows-11-arm' && matrix.python-version == '3.15' }}
# uses: ilammy/msvc-dev-cmd@v1
# with:
# arch: arm64
- run: scripts/install.ps1 ${{ matrix.wine-compat }}
- run: "scripts/build.ps1 ${{ matrix.wine-compat }}"
- run: scripts/build.ps1 ${{ matrix.wine-compat }}
- name: Run test suite
# pywinctl/pymonctl connect to the X display at import-time, hence xvfb
run: >-
Expand Down
18 changes: 13 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dependencies = [
"PyWinCtl >=0.0.42", # py.typed
"keyboard @ git+https://github.com/boppreh/keyboard.git", # Fix install on macos and linux-ci https://github.com/boppreh/keyboard/pull/568
"numpy >=2.3.2", # Python 3.14 support # TODO: Bump when 3.15 wheels
"opencv-contrib-python-headless >=4.13", # glibc 2.28 wheels (for aarch) # TODO: Bump when 3.15 wheels # TODO: Bump to 5.0 after a week for restored sdist
"opencv-contrib-python-headless >=5.0", # Restored sdist
"packaging >=20.0", # py.typed
"tomli-w >=1.1.0", # Typing fixes

Expand Down Expand Up @@ -49,7 +49,7 @@ formatters = [
dev = [
#
# Linters & Formatters
"mypy >=2.1", # TODO: Bump when 3.15 wheels
"mypy >=2.1; python_version < '3.15'", # TODO: Bump when 3.15 wheels
"pyright[nodejs] >=1.1.400", # reportPrivateImportUsage behaviour change
{ include-group = "formatters" },
#
Expand Down Expand Up @@ -85,16 +85,24 @@ find-links = ["./scripts"] # Discover local wheels
exclude-newer = "1 week"
[tool.uv.exclude-newer-package]
typed-D3DShot = false # I own it
opencv-contrib-python-headless = "1 day"

###
# Development channels
###
[tool.uv.sources]
beslogic-ruff-config = { git = "https://github.com/Beslogic/Beslogic-Ruff-Config", rev = "312cfab8a1e2653639a2ef665e99eac6c7412ba7" }
# pywin32 = { git = "https://github.com/mhammond/pywin32.git", marker = "python_version >= '3.15'" }
# pyinstaller = { url = "https://github.com/pyinstaller/pyinstaller/archive/develop.zip", marker = "python_version >= '3.15'" }
# numpy = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }
# pillow = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }
# pyinstaller = { git = "https://github.com/pyinstaller/pyinstaller.git", marker = "python_version >= '3.15'" }
numpy = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }
pillow = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }

[tool.uv.extra-build-dependencies]
# When opencv builds from sdist, its isolated build env ignores the numpy source
# mapping above. match-runtime reuses our installed numpy instead of building it.
opencv-contrib-python-headless = [
{ requirement = "numpy", match-runtime = true },
]

[[tool.uv.index]]
exclude-newer = false # Anaconda index doesn't have upload dates
Expand Down
12 changes: 12 additions & 0 deletions scripts/compile_resources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,21 @@ if (-not $GITHUB_REPOSITORY) {
$GITHUB_REPOSITORY = 'Toufool/AutoSplit'
}

# Our own top-level modules and packages, used by AutoSplit.py's lazy imports
# filter (Python 3.15+). Generated because PyInstaller-frozen builds can't
# discover pure modules on disk: they live inside the PYZ archive.
$SRC_ROOT_MODULES = (
@('"__main__"') + (
Get-ChildItem ./src |
Where-Object { ($_.Extension -eq '.py' -or $_.PSIsContainer) -and $_.Name -notlike '__*' } |
ForEach-Object { "`"$($_.BaseName)`"" }
)
) -join ', '

New-Item $build_vars_path -ItemType File -Force | Out-Null
Add-Content $build_vars_path "AUTOSPLIT_BUILD_NUMBER = `"$BUILD_NUMBER`""
Add-Content $build_vars_path "AUTOSPLIT_GITHUB_REPOSITORY = `"$GITHUB_REPOSITORY`""
Add-Content $build_vars_path "SRC_ROOT_MODULES = frozenset(($SRC_ROOT_MODULES))"
Write-Host "Generated build number: `"$BUILD_NUMBER`""
Write-Host "Set repository to `"$GITHUB_REPOSITORY`""

Expand Down
15 changes: 6 additions & 9 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

param([switch]$WineCompat)

# Validating user groups on Linux
if ($IsLinux) {
# Validating user groups on Linux
$groups = groups
if ($groups.Contains('input') -and $groups.Contains('tty')) {
Write-Host "User $Env:USER is already part of groups input and tty. No actions taken."
Expand All @@ -20,18 +20,15 @@ if ($IsLinux) {
Write-Output 'SUBSYSTEM=="input", MODE="0666" GROUP="plugdev"' | sudo tee /etc/udev/rules.d/12-input.rules
Write-Output 'SUBSYSTEM=="misc", MODE="0666" GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/12-input.rules
Write-Output 'SUBSYSTEM=="tty", MODE="0666" GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/12-input.rules
}
Write-Host 'You have been added automatically,' `
"but still need to manually terminate your session with 'loginctl terminate-user $Env:USER'" `
'for the changes to take effect outside of this script.'
if (-not $Env:GITHUB_JOB) {

Write-Host 'You have been added automatically,' `
"but still need to manually terminate your session with 'loginctl terminate-user $Env:USER'" `
'for the changes to take effect outside of this script.'
Write-Host -NoNewline 'Press any key to continue...'
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
}
}

if ($IsLinux) {
if (-not $Env:GITHUB_JOB -or $Env:GITHUB_JOB -eq 'Build') {
# System dependencies
if ((Get-Command apt-get, dpkg-query -ErrorAction SilentlyContinue).Count -eq 2) {
Expand Down Expand Up @@ -116,6 +113,6 @@ Write-Output "Installing Python dependencies with: uv $uvSyncArgs"
uv @uvSyncArgs

# Don't compile resources on the Build CI job as it'll do so in build script
if (-not $prod) {
if ($Env:GITHUB_JOB -ne 'Build') {
& "$PSScriptRoot/compile_resources.ps1"
}
3 changes: 2 additions & 1 deletion src/AutoControlledThread.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override

from PySide6 import QtCore

Expand All @@ -16,6 +16,7 @@ def __init__(self, autosplit: AutoSplit):
self._autosplit_ref = autosplit
super().__init__()

@override
@QtCore.Slot()
def run(self):
while True:
Expand Down
48 changes: 48 additions & 0 deletions src/AutoSplit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,54 @@
# Enable all warnings, which Python hides by default outside __main__
warnings.simplefilter("default")

if sys.version_info >= (3, 15):
# Packages whose *internal* imports must stay eager
# (importing them from elsewhere is still lazy):
# - stdlib: gains nothing (loads at startup anyway),
# and its lazy proxies fail to reify under shiboken6's patched __import__
# (e.g. typing.Union: "'lazy_import' object is not subscriptable").
# - shiboken6/shibokensupport: patch __import__ (feature_import) and exec
# embedded modules behind aliased spec names
# (PySide6.support.signature.* vs shibokensupport.*).
# - numpy: its self-check raises a bogus version conflict when imported
# through shiboken6's patched __import__.
_EAGER_INTERNALS = (
frozenset({
"PySide6",
"shiboken6",
"shibokensupport",
"numpy",
})
| sys.stdlib_module_names
)

def _lazy_imports_filter(
importing: str | None,
imported: str,
fromlist: tuple[str, ...] | None = None, # noqa: ARG001
/,
) -> bool:
# No importer means exec'd/embedded code (e.g. shiboken6's signature
# bootstrap), which can't be trusted to resolve lazy proxies.
if not importing:
return False
# Imports within the same top-level package must stay eager:
# - A package importing its own submodule rebinds the submodule name on
# the parent package, clobbering any same-named lazy proxy in the
# package's namespace
# (e.g. "from capture_method.ScrotCaptureMethod import ScrotCaptureMethod").
# https://github.com/python/cpython/issues/151208
# - A relative "from . import x" binds a lazy proxy that fails to reify
# on attribute access (e.g. PIL/__init__.py's
# "__version__ = _version.__version__" sees the raw proxy).
# TODO: Report upstream, possibly same root cause as the issue above.
if importing.partition(".")[0] == imported.partition(".")[0]:
return False
return importing.partition(".")[0] not in _EAGER_INTERNALS

sys.set_lazy_imports_filter(_lazy_imports_filter)
sys.set_lazy_imports("all")

# Prevent PyAutoGUI and pywinctl from setting Process DPI Awareness,
# which Qt tries to do then throws warnings about it.
# The unittest workaround significantly increases
Expand Down
46 changes: 42 additions & 4 deletions tests/test_import_all_modules.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"""
Smoke test: import every one of our own modules, including submodules.

Catches import-time errors early: syntax errors, missing dependencies
and broken platform guards in module-level code.
Catches import-time errors early: syntax errors, missing dependencies,
broken platform guards and lazy import issues in module-level code.
"""

import importlib
import operator
import pkgutil
import subprocess # noqa: S404
import sys
import textwrap
import unittest
from pathlib import Path

Expand Down Expand Up @@ -55,8 +57,44 @@ def test_import_all_modules(self):
if module_name in EXPECTED_OS_ERRORS:
with self.assertRaises(OSError):
importlib.import_module(module_name)
else:
importlib.import_module(module_name)
continue
module = importlib.import_module(module_name)
# Force every lazy import proxy to resolve. eval's LOAD_NAME
# on the module's namespace triggers reification
# (plain getattr does not).
for attr_name in [
k for k, v in vars(module).items() if type(v).__name__ == "lazy_import"
]:
eval(attr_name, vars(module)) # noqa: S307

def test_app_entrypoint_in_fresh_interpreter(self):
"""
The test runner itself has already imported most of the stdlib, which
masks lazy import issues that only occur with a clean sys.modules
(e.g. shiboken6's bootstrap importing stdlib modules through lazy
proxies). Mimic a real app launch instead.

Also probes shiboken6's signature support: its bootstrap swallows
errors and only logs them, and it isn't otherwise guaranteed to be
exercised by mere imports.
"""
code = textwrap.dedent("""
import AutoSplit
import inspect
from PySide6 import QtCore
signature = inspect.signature(QtCore.QObject.objectName)
assert isinstance(signature, inspect.Signature), signature
""")
# Trusted, hardcoded code string ran with our own interpreter
result = subprocess.run( # noqa: S603
[sys.executable, "-c", code],
capture_output=True,
text=True,
check=False,
cwd=SRC_DIR,
timeout=120,
)
self.assertEqual(result.returncode, 0, msg=result.stderr)


if __name__ == "__main__":
Expand Down
Loading
Loading