Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 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
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
21 changes: 17 additions & 4 deletions .github/workflows/lint-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,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 pyinstaller PyAutoGUI beslogic-ruff-config
UV_NO_BINARY_PACKAGE: >-
keyboard PyAutoGUI beslogic-ruff-config
${{ matrix.python-version == '3.15' && 'RapidFuzz Levenshtein numpy' || '' }}
# 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 @@ -90,12 +93,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 @@ -119,6 +125,13 @@ 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 }}"
- name: Run test suite
Expand Down
11 changes: 7 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies = [

#
# Build and compile resources
"pyinstaller >=6.15.0", # Python 3.14 support
"pyinstaller >=6.21.0", # Python 3.15 support

#
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers
Expand Down Expand Up @@ -49,7 +49,7 @@ dev = [
#
# Linters & Formatters
"dprint-py >=0.50.0.0",
"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 = "ruff" },
#
Expand Down Expand Up @@ -84,16 +84,19 @@ find-links = ["./scripts"] # Discover local wheels
exclude-newer = "1 week"
[tool.uv.exclude-newer-package]
typed-D3DShot = false # I own it
# pyinstaller-hooks-contrib = false # pyinstaller develop build
pyinstaller = "2 days" # TODO: REMOVE BEFORE MERGING
pyinstaller-hooks-contrib = "6 days" # TODO: REMOVE BEFORE MERGING

###
# 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 = { 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'" }
# pyinstaller = { url = "https://github.com/pyinstaller/pyinstaller/archive/develop.zip", marker = "python_version >= '3.15'" }
pillow = { index = "scientific-python-nightly-wheels", marker = "python_version >= '3.15'" }

[[tool.uv.index]]
exclude-newer = false # Anaconda index doesn't have upload dates
Expand Down
6 changes: 4 additions & 2 deletions scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ Push-Location "$PSScriptRoot/.." # Avoid issues with space in path
try {
& 'scripts/compile_resources.ps1'

# TODO: Remove UV_PYTHON check once tcl/tk is fixed on Python 3.15
# CI not allowed to skip splash screen, it MUST build (will fail when calling PyInstaller)
$SupportsSplashScreen = $Env:GITHUB_JOB -or [System.Convert]::ToBoolean(
$(uv run --active scripts/check_splash_support.py))
$SupportsSplashScreen = ($Env:UV_PYTHON -notlike '3.15*') -and (
$Env:GITHUB_JOB -or [System.Convert]::ToBoolean(
$(uv run --active scripts/check_splash_support.py)))

$arguments = @(
'src/AutoSplit.py',
Expand Down
9 changes: 8 additions & 1 deletion scripts/check_splash_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@
from PyInstaller.building.splash import Splash

try:
Splash._check_tcl_tk_compatibility() # noqa: SLF001
from PyInstaller.utils.hooks.tcl_tk import tcltk_info

if not tcltk_info.available:
raise SystemExit( # noqa: TRY301 # Copies source
"ERROR: Your platform does not support the splash screen feature, "
+ "since tkinter is not installed. Please install tkinter and try again."
)
Splash._check_tcl_tk_compatibility(tcltk_info) # noqa: SLF001
print(True)
except SystemExit as e:
print(e, file=sys.stderr)
Expand Down
12 changes: 12 additions & 0 deletions scripts/compile_resources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,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
48 changes: 48 additions & 0 deletions src/AutoSplit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@
import os
import sys

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
5 changes: 1 addition & 4 deletions src/capture_method/BitBltCaptureMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@ def get_frame(self) -> MatLike | None:
# Invalid handle or the window was closed while it was being manipulated
return None

if is_blank(image):
image = None
else:
image.shape = (height, width, BGRA_CHANNEL_COUNT)
image = None if is_blank(image) else image.reshape((height, width, BGRA_CHANNEL_COUNT))

# Cleanup DC and handle
try_delete_dc(dc_object)
Expand Down
2 changes: 1 addition & 1 deletion src/capture_method/WindowsGraphicsCaptureMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def get_frame(self) -> MatLike | None:
raise ValueError("Unable to obtain the BitmapBuffer from SoftwareBitmap.")
reference = bitmap_buffer.create_reference()
image = np.frombuffer(cast("bytes", reference), dtype=np.uint8)
image.shape = (self.size.height, self.size.width, BGRA_CHANNEL_COUNT)
image = image.reshape((self.size.height, self.size.width, BGRA_CHANNEL_COUNT))
image = image[
selection["y"] : selection["y"] + selection["height"],
selection["x"] : selection["x"] + selection["width"],
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