Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: enable experimental free-threaded python 3.13 on unix #2759

Merged
merged 1 commit into from
Jan 8, 2025
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
68 changes: 66 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,13 @@ jobs:
shell: bash
env:
UV_SYSTEM_PYTHON: true
UV_NO_PROGRESS: true
steps:

- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get install -qy alien fakeroot rpm

- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -98,19 +103,78 @@ jobs:
pattern: cx-freeze-whl-${{ matrix.os }}*
path: wheelhouse

- name: Install dependencies to test
run: |
uv pip install -r requirements.txt -r tests/requirements.txt
uv pip install cx_Freeze --no-index --no-deps -f wheelhouse --reinstall

- name: Generate coverage report
env:
COVERAGE_FILE: ".coverage.${{ matrix.python-version }}.${{ matrix.os }}"
run: pytest -nauto --cov="cx_Freeze"

- name: Upload coverage reports
uses: actions/upload-artifact@v4
with:
name: cov-${{ matrix.python-version }}.${{ matrix.os }}
path: .coverage.*
include-hidden-files: true

test_free_threaded:
needs:
- build_wheel
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-14]
python-version: ['3.13t']
fail-fast: false
defaults:
run:
shell: bash
env:
UV_NO_PROGRESS: true
steps:

- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get install -qy alien fakeroot rpm

- uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
repository: marcelotduarte/cx_Freeze

- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: |
requirements.txt
tests/requirements.txt
python-version: ${{ matrix.python-version }}

- name: Env
run: env | sort

- name: Sysconfig
run: uv run --no-project -m sysconfig

- name: Download the wheel
uses: actions/download-artifact@v4
with:
merge-multiple: true
pattern: cx-freeze-whl-${{ matrix.os }}*
path: wheelhouse

- name: Install dependencies to test
run: |
uv pip install -r requirements.txt -r tests/requirements.txt
uv sync --no-install-project --extra tests
uv pip install cx_Freeze --no-index --no-deps -f wheelhouse --reinstall

- name: Generate coverage report
env:
COVERAGE_FILE: ".coverage.${{ matrix.python-version }}.${{ matrix.os }}"
run: pytest -nauto --cov="cx_Freeze"
run: uv run --no-project pytest -nauto --cov="cx_Freeze"

- name: Upload coverage reports
uses: actions/upload-artifact@v4
Expand Down
9 changes: 5 additions & 4 deletions ci/build-wheel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ PY_PLATFORM=$($PYTHON -c "import sysconfig; print(sysconfig.get_platform(), end=
PY_VERSION=$($PYTHON -c "import sysconfig; print(sysconfig.get_python_version(), end='')")
PY_VERSION_FULL=$($PYTHON -c "import sysconfig; print(sysconfig.get_config_var('py_version'), end='')")
PY_VERSION_NODOT=$($PYTHON -c "import sysconfig; print(sysconfig.get_config_var('py_version_nodot'), end='')")
PY_ABI_THREAD=$($PYTHON -c "import sysconfig; print(sysconfig.get_config_var('abi_thread') or '', end='')")

PYTHON_TAG=cp$PY_VERSION_NODOT
PYTHON_TAG=cp$PY_VERSION_NODOT$PY_ABI_THREAD
if [[ $PY_PLATFORM == linux* ]]; then
PLATFORM_TAG=many$(echo $PY_PLATFORM | sed 's/\-/_/')
PLATFORM_TAG_MASK="$(echo $PLATFORM_TAG | sed 's/_/*_/')"
Expand Down Expand Up @@ -81,8 +82,8 @@ _bump_my_version () {

_cibuildwheel () {
local args=$*
# Use python >= 3.11
local py_version=$(_vergte $PY_VERSION 3.11)
# Use python >= 3.11 (python 3.13t is supported)
local py_version=$(_vergte $PY_VERSION 3.11)$PY_ABI_THREAD
# Do not export UV_* to avoid conflict with uv in cibuildwheel macOS/Windows
unset UV_SYSTEM_PYTHON
uvx -p $py_version cibuildwheel $args
Expand All @@ -101,7 +102,7 @@ echo "::endgroup::"
mkdir -p wheelhouse >/dev/null
if [[ $PY_PLATFORM == linux* ]]; then
echo "::group::Build sdist"
uv build -p $PY_VERSION --no-build-isolation --sdist -o wheelhouse
uv build -p $PY_VERSION$PY_ABI_THREAD --sdist -o wheelhouse
echo "::endgroup::"
fi
echo "::group::Build wheel(s)"
Expand Down
4 changes: 3 additions & 1 deletion cx_Freeze/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

__all__ = [
"ABI_THREAD",
"BUILD_EXE_DIR",
"EXE_SUFFIX",
"EXT_SUFFIX",
Expand All @@ -21,8 +22,9 @@

PLATFORM = sysconfig.get_platform()
PYTHON_VERSION = sysconfig.get_python_version()
ABI_THREAD = sysconfig.get_config_var("abi_thread") or ""

BUILD_EXE_DIR = Path(f"build/exe.{PLATFORM}-{PYTHON_VERSION}")
BUILD_EXE_DIR = Path(f"build/exe.{PLATFORM}-{PYTHON_VERSION}{ABI_THREAD}")
EXE_SUFFIX = sysconfig.get_config_var("EXE")
EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX")

Expand Down
14 changes: 10 additions & 4 deletions cx_Freeze/executable.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import TYPE_CHECKING

from cx_Freeze._compat import (
ABI_THREAD,
EXE_SUFFIX,
IS_MACOS,
IS_MINGW,
Expand Down Expand Up @@ -74,12 +75,17 @@ def base(self) -> Path:

@base.setter
def base(self, name: str | Path | None) -> None:
# The default base is the legacy console, except for
# The default base is the legacy console, except for Python 3.13t and
# Python 3.13 on macOS, that supports only the new console
if IS_MACOS and sys.version_info[:2] >= (3, 13):
name = name or "console"
else:
version = sys.version_info[:2]
if (
version <= (3, 13)
and ABI_THREAD == ""
and not (IS_MACOS and version == (3, 13))
):
name = name or "console_legacy"
else:
name = name or "console"
# silently ignore gui and service on non-windows systems
if not (IS_WINDOWS or IS_MINGW) and name in ("gui", "service"):
name = "console"
Expand Down
7 changes: 5 additions & 2 deletions cx_Freeze/freezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
from setuptools import Distribution

from cx_Freeze._compat import (
ABI_THREAD,
BUILD_EXE_DIR,
IS_CONDA,
IS_MACOS,
IS_MINGW,
IS_WINDOWS,
PYTHON_VERSION,
)
from cx_Freeze.common import get_resource_file_path, process_path_specs
from cx_Freeze.exception import FileError, OptionError
Expand Down Expand Up @@ -1035,9 +1037,10 @@ def _default_bin_includes(self) -> list[str]:
# MSYS2 python returns a static library.
names = [name.replace(".dll.a", ".dll")]
else:
py_version = f"{PYTHON_VERSION}{ABI_THREAD}"
names = [
f"python{sys.version_info[0]}.dll",
f"python{sys.version_info[0]}{sys.version_info[1]}.dll",
f"python{py_version.replace('.','')}.dll",
]
python_shared_libs: list[Path] = []
for name in names:
Expand Down Expand Up @@ -1113,7 +1116,7 @@ def _default_bin_excludes(self) -> list[str]:
def _default_bin_includes(self) -> list[str]:
python_shared_libs: list[Path] = []
# Check for distributed "cx_Freeze/bases/lib/Python"
name = "Python"
name = f"Python{ABI_THREAD.upper()}"
for bin_path in self._default_bin_path_includes():
fullname = Path(bin_path, name).resolve()
if fullname.is_file():
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dynamic = ["version"]

[project.optional-dependencies]
dev = [
"bump-my-version==0.29.0",
"bump-my-version==0.29.0 ;python_version < '3.13'",
"cibuildwheel==2.22.0",
"pre-commit==4.0.1", # python_version >= 3.9
]
Expand Down Expand Up @@ -164,9 +164,11 @@ optional_value = "final"
before-build = "uv pip install -r requirements.txt"
build-frontend = "build[uv]"
build-verbosity = 1
enable = ["cpython-freethreading"]
skip = [
"cp3{9,10,13}-musllinux_*",
"cp3{9,10,13}-manylinux_ppc64le",
"cp313t-win*",
]

[tool.cibuildwheel.linux]
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
bump-my-version==0.29.0
bump-my-version==0.29.0 ;python_version < '3.13'
cibuildwheel==2.22.0
pre-commit==4.0.1
57 changes: 34 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def build_extension(self, ext) -> None:
library_dirs.append(get_config_var("LIBPL"))
if not ENABLE_SHARED or IS_CONDA:
library_dirs.append(get_config_var("LIBDIR"))
libraries.append(f"python{get_python_version()}")
abi_thread = get_config_var("abi_thread") or ""
libraries.append(f"python{get_python_version()}{abi_thread}")
if get_config_var("LIBS"):
extra_args.extend(get_config_var("LIBS").split())
if get_config_var("LIBM"):
Expand Down Expand Up @@ -275,38 +276,48 @@ def get_extensions() -> list[Extension]:
os.environ.get("CI", "") != "true"
or os.environ.get("CIBUILDWHEEL", "0") != "1"
)
abi_thread = get_config_var("abi_thread") or ""
version = sys.version_info[:2]
extensions = [
Extension(
"cx_Freeze.bases.console",
["source/bases/console.c", "source/bases/_common.c"],
optional=optional,
),
Extension(
"cx_Freeze.bases.console_legacy",
["source/legacy/console.c"],
depends=["source/legacy/common.c"],
optional=optional
or (sys.version_info[:2] >= (3, 13) and IS_MACOS),
),
)
]

if IS_MINGW or IS_WINDOWS:
if (
version <= (3, 13)
and abi_thread == ""
and not (IS_MACOS and version == (3, 13))
):
extensions += [
Extension(
"cx_Freeze.bases.Win32GUI",
["source/legacy/Win32GUI.c"],
"cx_Freeze.bases.console_legacy",
["source/legacy/console.c"],
depends=["source/legacy/common.c"],
libraries=["user32"],
optional=optional,
),
Extension(
"cx_Freeze.bases.Win32Service",
["source/legacy/Win32Service.c"],
depends=["source/legacy/common.c"],
extra_link_args=["/DELAYLOAD:cx_Logging"],
libraries=["advapi32"],
optional=optional,
),
)
]
if IS_MINGW or IS_WINDOWS:
if version <= (3, 13) and abi_thread == "":
extensions += [
Extension(
"cx_Freeze.bases.Win32GUI",
["source/legacy/Win32GUI.c"],
depends=["source/legacy/common.c"],
libraries=["user32"],
optional=optional,
),
Extension(
"cx_Freeze.bases.Win32Service",
["source/legacy/Win32Service.c"],
depends=["source/legacy/common.c"],
extra_link_args=["/DELAYLOAD:cx_Logging"],
libraries=["advapi32"],
optional=optional,
),
]
extensions += [
Extension(
"cx_Freeze.bases.gui",
["source/bases/Win32GUI.c", "source/bases/_common.c"],
Expand Down
13 changes: 9 additions & 4 deletions tests/test_executables.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from cx_Freeze import Executable
from cx_Freeze._compat import (
ABI_THREAD,
BUILD_EXE_DIR,
EXE_SUFFIX,
IS_MACOS,
Expand Down Expand Up @@ -241,14 +242,18 @@ def test_executables(
("icon.ico", "icon.icns", "icon.png", "icon.svg"),
),
]
if IS_MACOS and sys.version_info[:2] >= (3, 13):
if (
sys.version_info[:2] <= (3, 13)
and ABI_THREAD == ""
and not (IS_MACOS and sys.version_info[:2] == (3, 13))
):
TEST_VALID_PARAMETERS += [
("base", None, "console-"),
("base", None, "console_legacy-"),
("base", "console_legacy", "console_legacy-"),
]
else:
TEST_VALID_PARAMETERS += [
("base", None, "console_legacy-"),
("base", "console_legacy", "console_legacy-"),
("base", None, "console-"),
]
if IS_WINDOWS or IS_MINGW:
TEST_VALID_PARAMETERS += [
Expand Down
14 changes: 8 additions & 6 deletions tests/test_freezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from cx_Freeze import Freezer
from cx_Freeze._compat import (
ABI_THREAD,
BUILD_EXE_DIR,
EXE_SUFFIX,
IS_CONDA,
Expand Down Expand Up @@ -99,19 +100,20 @@ def test_freezer_default_bin_includes(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)

freezer = Freezer(executables=["hello.py"])
py_version = f"{PYTHON_VERSION}{ABI_THREAD}"
if IS_MINGW:
expected = f"libpython{PYTHON_VERSION}.dll"
expected = f"libpython{py_version}.dll"
elif IS_WINDOWS:
expected = f"python{PYTHON_VERSION.replace('.','')}.dll"
expected = f"python{py_version.replace('.','')}.dll"
elif IS_CONDA: # macOS or Linux
if IS_MACOS:
expected = f"libpython{PYTHON_VERSION}.dylib"
expected = f"libpython{py_version}.dylib"
else:
expected = f"libpython{PYTHON_VERSION}.so*"
expected = f"libpython{py_version}.so*"
elif IS_MACOS:
expected = "Python"
expected = f"Python{ABI_THREAD.upper()}"
elif ENABLE_SHARED: # Linux
expected = f"libpython{PYTHON_VERSION}.so*"
expected = f"libpython{py_version}.so*"
else:
assert freezer.default_bin_includes == []
return
Expand Down
Loading