Skip to content

gh-112984 Update Windows build for free-threaded builds #113129

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

Merged
merged 24 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
00478ff
gh-112984 Update Windows build for free-threaded builds
zooba Dec 14, 2023
2a1ef3b
Fix rt script
zooba Dec 14, 2023
7912ba4
Add suffix
zooba Dec 14, 2023
87ba7ac
Some test fixes and new venv launcher
zooba Dec 14, 2023
ff83554
Finish venv launcher, update venv
zooba Dec 15, 2023
e6211cb
Fix pyvenv path calculation
zooba Dec 15, 2023
3a7425b
[WIP] Update installer to add optional freethreaded install
zooba Dec 15, 2023
a59e2fc
Merge remote-tracking branch 'upstream/main' into gh-112984
zooba Dec 18, 2023
0528976
Add visible option to MSI
zooba Dec 18, 2023
198082e
Build freethreaded properly in tools/msi/build.bat
zooba Dec 18, 2023
65efd4e
Test fixes
zooba Dec 19, 2023
55205fa
Fix launcher tests
zooba Dec 19, 2023
9d9dd9b
NEWS and whitespace
zooba Dec 19, 2023
d027b93
Update venv and tests
zooba Dec 20, 2023
5e7cf0a
Avoid comparing bytes and str
zooba Dec 20, 2023
6e38ca1
Ensure Py_GIL_DISABLED is consistent
zooba Dec 20, 2023
011478b
Update PC/layout script for free-threaded
zooba Dec 20, 2023
903c168
Merge remote-tracking branch 'upstream/main' into gh-112984
zooba Dec 21, 2023
b6cc6ca
Merge remote-tracking branch 'upstream/main' into gh-112984
zooba Jan 4, 2024
524edf9
Merge remote-tracking branch 'upstream/main' into gh-112984
zooba Jan 5, 2024
8217689
Update docs
zooba Jan 5, 2024
1255715
Update variable in build script
zooba Jan 5, 2024
baa6549
Merge remote-tracking branch 'upstream/main' into gh-112984
zooba Jan 17, 2024
8d54a59
Add experimental label to installer
zooba Jan 17, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/build_msi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
strategy:
matrix:
type: [x86, x64, arm64]
env:
IncludeFreethreaded: true
steps:
- uses: actions/checkout@v4
- name: Build CPython installer
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/reusable-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Display build info
run: .\python.bat -m test.pythoninfo
- name: Tests
run: .\PCbuild\rt.bat -p Win32 -d -q --fast-ci
run: .\PCbuild\rt.bat -p Win32 -d -q --fast-ci ${{ inputs.free-threading && '--disable-gil' || '' }}

build_win_amd64:
name: 'build and test (x64)'
Expand All @@ -37,7 +37,7 @@ jobs:
- name: Display build info
run: .\python.bat -m test.pythoninfo
- name: Tests
run: .\PCbuild\rt.bat -p x64 -d -q --fast-ci
run: .\PCbuild\rt.bat -p x64 -d -q --fast-ci ${{ inputs.free-threading && '--disable-gil' || '' }}

build_win_arm64:
name: 'build (arm64)'
Expand Down
Binary file added Doc/using/win_install_freethreaded.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 62 additions & 2 deletions Doc/using/windows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,46 @@ settings and replace any that have been removed or modified.
"Uninstall" will remove Python entirely, with the exception of the
:ref:`launcher`, which has its own entry in Programs and Features.

.. _install-freethreaded-windows:

Installing Free-threaded Binaries
---------------------------------

.. versionadded:: 3.13 (Experimental)

.. note::

Everything described in this section is considered experimental,
and should be expected to change in future releases.

To install pre-built binaries with free-threading enabled (see :pep:`703`), you
should select "Customize installation". The second page of options includes the
"Download free-threaded binaries" checkbox.

.. image:: win_install_freethreaded.png

Selecting this option will download and install additional binaries to the same
location as the main Python install. The main executable is called
``python3.13t.exe``, and other binaries either receive a ``t`` suffix or a full
ABI suffix. Python source files and bundled third-party dependencies are shared
with the main install.

The free-threaded version is registered as a regular Python install with the
tag ``3.13t`` (with a ``-32`` or ``-arm64`` suffix as normal for those
platforms). This allows tools to discover it, and for the :ref:`launcher` to
support ``py.exe -3.13t``. Note that the launcher will interpret ``py.exe -3``
(or a ``python3`` shebang) as "the latest 3.x install", which will prefer the
free-threaded binaries over the regular ones, while ``py.exe -3.13`` will not.
If you use the short style of option, you may prefer to not install the
free-threaded binaries at this time.

To specify the install option at the command line, use
``Include_freethreaded=1``. See :ref:`install-layout-option` for instructions on
pre-emptively downloading the additional binaries for offline install. The
options to include debug symbols and binaries also apply to the free-threaded
builds.

Free-threaded binaries are also available :ref:`on nuget.org <windows-nuget>`.

.. _windows-store:

Expand Down Expand Up @@ -450,9 +490,29 @@ automatically use the headers and import libraries in your build.

The package information pages on nuget.org are
`www.nuget.org/packages/python <https://www.nuget.org/packages/python>`_
for the 64-bit version and `www.nuget.org/packages/pythonx86
<https://www.nuget.org/packages/pythonx86>`_ for the 32-bit version.
for the 64-bit version, `www.nuget.org/packages/pythonx86
<https://www.nuget.org/packages/pythonx86>`_ for the 32-bit version, and
`www.nuget.org/packages/pythonarm64
<https://www.nuget.org/packages/pythonarm64>`_ for the ARM64 version

Free-threaded packages
----------------------

.. versionadded:: 3.13 (Experimental)

.. note::

Everything described in this section is considered experimental,
and should be expected to change in future releases.

Packages containing free-threaded binaries are named
`python-freethreaded <https://www.nuget.org/packages/python-freethreaded>`_
for the 64-bit version, `pythonx86-freethreaded
<https://www.nuget.org/packages/pythonx86-freethreaded>`_ for the 32-bit
version, and `pythonarm64-freethreaded
<https://www.nuget.org/packages/pythonarm64-freethreaded>`_ for the ARM64
version. These packages contain both the ``python3.13t.exe`` and
``python.exe`` entry points, both of which run free threaded.

.. _windows-embeddable:

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_ctypes/test_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def test_load_hasattr(self):
def test_load_dll_with_flags(self):
_sqlite3 = import_helper.import_module("_sqlite3")
src = _sqlite3.__file__
if src.lower().endswith("_d.pyd"):
if src.partition(".")[0].lower().endswith("_d"):
ext = "_d.dll"
else:
ext = ".dll"
Expand Down
40 changes: 26 additions & 14 deletions Lib/test/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@


PY_EXE = "py.exe"
DEBUG_BUILD = False
if sys.executable.casefold().endswith("_d.exe".casefold()):
PY_EXE = "py_d.exe"
DEBUG_BUILD = True

# Registry data to create. On removal, everything beneath top-level names will
# be deleted.
Expand Down Expand Up @@ -232,7 +234,7 @@ def run_py(self, args, env=None, allow_fail=False, expect_returncode=0, argv=Non
p.stdin.close()
p.wait(10)
out = p.stdout.read().decode("utf-8", "replace")
err = p.stderr.read().decode("ascii", "replace")
err = p.stderr.read().decode("ascii", "replace").replace("\uFFFD", "?")
if p.returncode != expect_returncode and support.verbose and not allow_fail:
print("++ COMMAND ++")
print([self.py_exe, *args])
Expand Down Expand Up @@ -273,7 +275,7 @@ def script(self, content, encoding="utf-8"):
def fake_venv(self):
venv = Path.cwd() / "Scripts"
venv.mkdir(exist_ok=True, parents=True)
venv_exe = (venv / Path(sys.executable).name)
venv_exe = (venv / ("python_d.exe" if DEBUG_BUILD else "python.exe"))
venv_exe.touch()
try:
yield venv_exe, {"VIRTUAL_ENV": str(venv.parent)}
Expand Down Expand Up @@ -521,6 +523,9 @@ def test_virtualenv_in_list(self):
self.assertEqual(str(venv_exe), m.group(1))
break
else:
if support.verbose:
print(data["stdout"])
print(data["stderr"])
self.fail("did not find active venv path")

data = self.run_py(["-0"], env=env)
Expand Down Expand Up @@ -616,25 +621,29 @@ def test_py_handle_64_in_ini(self):
self.assertEqual("True", data["SearchInfo.oldStyleTag"])

def test_search_path(self):
stem = Path(sys.executable).stem
exe = Path("arbitrary-exe-name.exe").absolute()
exe.touch()
self.addCleanup(exe.unlink)
with self.py_ini(TEST_PY_DEFAULTS):
with self.script(f"#! /usr/bin/env {stem} -prearg") as script:
with self.script(f"#! /usr/bin/env {exe.stem} -prearg") as script:
data = self.run_py(
[script, "-postarg"],
env={"PATH": f"{Path(sys.executable).parent};{os.getenv('PATH')}"},
env={"PATH": f"{exe.parent};{os.getenv('PATH')}"},
)
self.assertEqual(f"{sys.executable} -prearg {script} -postarg", data["stdout"].strip())
self.assertEqual(f"{exe} -prearg {script} -postarg", data["stdout"].strip())

def test_search_path_exe(self):
# Leave the .exe on the name to ensure we don't add it a second time
name = Path(sys.executable).name
exe = Path("arbitrary-exe-name.exe").absolute()
exe.touch()
self.addCleanup(exe.unlink)
with self.py_ini(TEST_PY_DEFAULTS):
with self.script(f"#! /usr/bin/env {name} -prearg") as script:
with self.script(f"#! /usr/bin/env {exe.name} -prearg") as script:
data = self.run_py(
[script, "-postarg"],
env={"PATH": f"{Path(sys.executable).parent};{os.getenv('PATH')}"},
env={"PATH": f"{exe.parent};{os.getenv('PATH')}"},
)
self.assertEqual(f"{sys.executable} -prearg {script} -postarg", data["stdout"].strip())
self.assertEqual(f"{exe} -prearg {script} -postarg", data["stdout"].strip())

def test_recursive_search_path(self):
stem = self.get_py_exe().stem
Expand Down Expand Up @@ -727,15 +736,18 @@ def test_shebang_command_in_venv(self):
data = self.run_py([script], expect_returncode=103)

with self.fake_venv() as (venv_exe, env):
# Put a real Python (ourselves) on PATH as a distraction.
# Put a "normal" Python on PATH as a distraction.
# The active VIRTUAL_ENV should be preferred when the name isn't an
# exact match.
env["PATH"] = f"{Path(sys.executable).parent};{os.environ['PATH']}"
exe = Path(Path(venv_exe).name).absolute()
exe.touch()
self.addCleanup(exe.unlink)
env["PATH"] = f"{exe.parent};{os.environ['PATH']}"

with self.script(f'#! /usr/bin/env {stem} arg1') as script:
data = self.run_py([script], env=env)
self.assertEqual(data["stdout"].strip(), f"{venv_exe} arg1 {script}")

with self.script(f'#! /usr/bin/env {Path(sys.executable).stem} arg1') as script:
with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script:
data = self.run_py([script], env=env)
self.assertEqual(data["stdout"].strip(), f"{sys.executable} arg1 {script}")
self.assertEqual(data["stdout"].strip(), f"{exe} arg1 {script}")
4 changes: 4 additions & 0 deletions Lib/test/test_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,8 @@ def test_tools_buildbot_test(self):
test_args.append('-x64') # 64-bit build
if not support.Py_DEBUG:
test_args.append('+d') # Release build, use python.exe
if sysconfig.get_config_var("Py_GIL_DISABLED"):
test_args.append('--disable-gil')
self.run_batch(script, *test_args, *self.tests)

@unittest.skipUnless(sys.platform == 'win32', 'Windows only')
Expand All @@ -862,6 +864,8 @@ def test_pcbuild_rt(self):
rt_args.append('-x64') # 64-bit build
if support.Py_DEBUG:
rt_args.append('-d') # Debug build, use python_d.exe
if sysconfig.get_config_var("Py_GIL_DISABLED"):
rt_args.append('--disable-gil')
self.run_batch(script, *rt_args, *self.regrtest_args, *self.tests)


Expand Down
16 changes: 13 additions & 3 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,14 @@ def test_prompt(self):

def test_upgrade_dependencies(self):
builder = venv.EnvBuilder()
bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
bin_path = 'bin'
python_exe = os.path.split(sys.executable)[1]
if sys.platform == 'win32':
bin_path = 'Scripts'
if os.path.normcase(os.path.splitext(python_exe)[0]).endswith('_d'):
python_exe = 'python_d.exe'
else:
python_exe = 'python.exe'
with tempfile.TemporaryDirectory() as fake_env_dir:
expect_exe = os.path.normcase(
os.path.join(fake_env_dir, bin_path, python_exe)
Expand Down Expand Up @@ -283,7 +289,9 @@ def test_sysconfig(self):
# build environment
('is_python_build()', str(sysconfig.is_python_build())),
('get_makefile_filename()', sysconfig.get_makefile_filename()),
('get_config_h_filename()', sysconfig.get_config_h_filename())):
('get_config_h_filename()', sysconfig.get_config_h_filename()),
('get_config_var("Py_GIL_DISABLED")',
str(sysconfig.get_config_var("Py_GIL_DISABLED")))):
with self.subTest(call):
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
out, err = check_output(cmd, encoding='utf-8')
Expand Down Expand Up @@ -315,7 +323,9 @@ def test_sysconfig_symlinks(self):
# build environment
('is_python_build()', str(sysconfig.is_python_build())),
('get_makefile_filename()', sysconfig.get_makefile_filename()),
('get_config_h_filename()', sysconfig.get_config_h_filename())):
('get_config_h_filename()', sysconfig.get_config_h_filename()),
('get_config_var("Py_GIL_DISABLED")',
str(sysconfig.get_config_var("Py_GIL_DISABLED")))):
with self.subTest(call):
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
out, err = check_output(cmd, encoding='utf-8')
Expand Down
Loading