Skip to content

Commit 9ad2072

Browse files
zoobaaisk
authored andcommitted
pythongh-112984 Update Windows build and installer for free-threaded builds (pythonGH-113129)
1 parent a97d972 commit 9ad2072

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1436
-244
lines changed

.github/workflows/build_msi.yml

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
strategy:
3333
matrix:
3434
type: [x86, x64, arm64]
35+
env:
36+
IncludeFreethreaded: true
3537
steps:
3638
- uses: actions/checkout@v4
3739
- name: Build CPython installer

.github/workflows/reusable-windows.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Display build info
2121
run: .\python.bat -m test.pythoninfo
2222
- name: Tests
23-
run: .\PCbuild\rt.bat -p Win32 -d -q --fast-ci
23+
run: .\PCbuild\rt.bat -p Win32 -d -q --fast-ci ${{ inputs.free-threading && '--disable-gil' || '' }}
2424

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

4242
build_win_arm64:
4343
name: 'build (arm64)'
231 KB
Loading

Doc/using/windows.rst

+62-2
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,46 @@ settings and replace any that have been removed or modified.
307307
"Uninstall" will remove Python entirely, with the exception of the
308308
:ref:`launcher`, which has its own entry in Programs and Features.
309309

310+
.. _install-freethreaded-windows:
311+
312+
Installing Free-threaded Binaries
313+
---------------------------------
314+
315+
.. versionadded:: 3.13 (Experimental)
316+
317+
.. note::
318+
319+
Everything described in this section is considered experimental,
320+
and should be expected to change in future releases.
321+
322+
To install pre-built binaries with free-threading enabled (see :pep:`703`), you
323+
should select "Customize installation". The second page of options includes the
324+
"Download free-threaded binaries" checkbox.
325+
326+
.. image:: win_install_freethreaded.png
327+
328+
Selecting this option will download and install additional binaries to the same
329+
location as the main Python install. The main executable is called
330+
``python3.13t.exe``, and other binaries either receive a ``t`` suffix or a full
331+
ABI suffix. Python source files and bundled third-party dependencies are shared
332+
with the main install.
333+
334+
The free-threaded version is registered as a regular Python install with the
335+
tag ``3.13t`` (with a ``-32`` or ``-arm64`` suffix as normal for those
336+
platforms). This allows tools to discover it, and for the :ref:`launcher` to
337+
support ``py.exe -3.13t``. Note that the launcher will interpret ``py.exe -3``
338+
(or a ``python3`` shebang) as "the latest 3.x install", which will prefer the
339+
free-threaded binaries over the regular ones, while ``py.exe -3.13`` will not.
340+
If you use the short style of option, you may prefer to not install the
341+
free-threaded binaries at this time.
342+
343+
To specify the install option at the command line, use
344+
``Include_freethreaded=1``. See :ref:`install-layout-option` for instructions on
345+
pre-emptively downloading the additional binaries for offline install. The
346+
options to include debug symbols and binaries also apply to the free-threaded
347+
builds.
348+
349+
Free-threaded binaries are also available :ref:`on nuget.org <windows-nuget>`.
310350

311351
.. _windows-store:
312352

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

451491
The package information pages on nuget.org are
452492
`www.nuget.org/packages/python <https://www.nuget.org/packages/python>`_
453-
for the 64-bit version and `www.nuget.org/packages/pythonx86
454-
<https://www.nuget.org/packages/pythonx86>`_ for the 32-bit version.
493+
for the 64-bit version, `www.nuget.org/packages/pythonx86
494+
<https://www.nuget.org/packages/pythonx86>`_ for the 32-bit version, and
495+
`www.nuget.org/packages/pythonarm64
496+
<https://www.nuget.org/packages/pythonarm64>`_ for the ARM64 version
497+
498+
Free-threaded packages
499+
----------------------
500+
501+
.. versionadded:: 3.13 (Experimental)
502+
503+
.. note::
455504

505+
Everything described in this section is considered experimental,
506+
and should be expected to change in future releases.
507+
508+
Packages containing free-threaded binaries are named
509+
`python-freethreaded <https://www.nuget.org/packages/python-freethreaded>`_
510+
for the 64-bit version, `pythonx86-freethreaded
511+
<https://www.nuget.org/packages/pythonx86-freethreaded>`_ for the 32-bit
512+
version, and `pythonarm64-freethreaded
513+
<https://www.nuget.org/packages/pythonarm64-freethreaded>`_ for the ARM64
514+
version. These packages contain both the ``python3.13t.exe`` and
515+
``python.exe`` entry points, both of which run free threaded.
456516

457517
.. _windows-embeddable:
458518

Lib/test/test_ctypes/test_loading.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def test_load_hasattr(self):
141141
def test_load_dll_with_flags(self):
142142
_sqlite3 = import_helper.import_module("_sqlite3")
143143
src = _sqlite3.__file__
144-
if src.lower().endswith("_d.pyd"):
144+
if src.partition(".")[0].lower().endswith("_d"):
145145
ext = "_d.dll"
146146
else:
147147
ext = ".dll"

Lib/test/test_launcher.py

+26-14
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919

2020

2121
PY_EXE = "py.exe"
22+
DEBUG_BUILD = False
2223
if sys.executable.casefold().endswith("_d.exe".casefold()):
2324
PY_EXE = "py_d.exe"
25+
DEBUG_BUILD = True
2426

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

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

618623
def test_search_path(self):
619-
stem = Path(sys.executable).stem
624+
exe = Path("arbitrary-exe-name.exe").absolute()
625+
exe.touch()
626+
self.addCleanup(exe.unlink)
620627
with self.py_ini(TEST_PY_DEFAULTS):
621-
with self.script(f"#! /usr/bin/env {stem} -prearg") as script:
628+
with self.script(f"#! /usr/bin/env {exe.stem} -prearg") as script:
622629
data = self.run_py(
623630
[script, "-postarg"],
624-
env={"PATH": f"{Path(sys.executable).parent};{os.getenv('PATH')}"},
631+
env={"PATH": f"{exe.parent};{os.getenv('PATH')}"},
625632
)
626-
self.assertEqual(f"{sys.executable} -prearg {script} -postarg", data["stdout"].strip())
633+
self.assertEqual(f"{exe} -prearg {script} -postarg", data["stdout"].strip())
627634

628635
def test_search_path_exe(self):
629636
# Leave the .exe on the name to ensure we don't add it a second time
630-
name = Path(sys.executable).name
637+
exe = Path("arbitrary-exe-name.exe").absolute()
638+
exe.touch()
639+
self.addCleanup(exe.unlink)
631640
with self.py_ini(TEST_PY_DEFAULTS):
632-
with self.script(f"#! /usr/bin/env {name} -prearg") as script:
641+
with self.script(f"#! /usr/bin/env {exe.name} -prearg") as script:
633642
data = self.run_py(
634643
[script, "-postarg"],
635-
env={"PATH": f"{Path(sys.executable).parent};{os.getenv('PATH')}"},
644+
env={"PATH": f"{exe.parent};{os.getenv('PATH')}"},
636645
)
637-
self.assertEqual(f"{sys.executable} -prearg {script} -postarg", data["stdout"].strip())
646+
self.assertEqual(f"{exe} -prearg {script} -postarg", data["stdout"].strip())
638647

639648
def test_recursive_search_path(self):
640649
stem = self.get_py_exe().stem
@@ -727,15 +736,18 @@ def test_shebang_command_in_venv(self):
727736
data = self.run_py([script], expect_returncode=103)
728737

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

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

739-
with self.script(f'#! /usr/bin/env {Path(sys.executable).stem} arg1') as script:
751+
with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script:
740752
data = self.run_py([script], env=env)
741-
self.assertEqual(data["stdout"].strip(), f"{sys.executable} arg1 {script}")
753+
self.assertEqual(data["stdout"].strip(), f"{exe} arg1 {script}")

Lib/test/test_regrtest.py

+4
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,8 @@ def test_tools_buildbot_test(self):
845845
test_args.append('-x64') # 64-bit build
846846
if not support.Py_DEBUG:
847847
test_args.append('+d') # Release build, use python.exe
848+
if sysconfig.get_config_var("Py_GIL_DISABLED"):
849+
test_args.append('--disable-gil')
848850
self.run_batch(script, *test_args, *self.tests)
849851

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

867871

Lib/test/test_venv.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,14 @@ def test_prompt(self):
223223

224224
def test_upgrade_dependencies(self):
225225
builder = venv.EnvBuilder()
226-
bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
226+
bin_path = 'bin'
227227
python_exe = os.path.split(sys.executable)[1]
228+
if sys.platform == 'win32':
229+
bin_path = 'Scripts'
230+
if os.path.normcase(os.path.splitext(python_exe)[0]).endswith('_d'):
231+
python_exe = 'python_d.exe'
232+
else:
233+
python_exe = 'python.exe'
228234
with tempfile.TemporaryDirectory() as fake_env_dir:
229235
expect_exe = os.path.normcase(
230236
os.path.join(fake_env_dir, bin_path, python_exe)
@@ -283,7 +289,9 @@ def test_sysconfig(self):
283289
# build environment
284290
('is_python_build()', str(sysconfig.is_python_build())),
285291
('get_makefile_filename()', sysconfig.get_makefile_filename()),
286-
('get_config_h_filename()', sysconfig.get_config_h_filename())):
292+
('get_config_h_filename()', sysconfig.get_config_h_filename()),
293+
('get_config_var("Py_GIL_DISABLED")',
294+
str(sysconfig.get_config_var("Py_GIL_DISABLED")))):
287295
with self.subTest(call):
288296
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
289297
out, err = check_output(cmd, encoding='utf-8')
@@ -315,7 +323,9 @@ def test_sysconfig_symlinks(self):
315323
# build environment
316324
('is_python_build()', str(sysconfig.is_python_build())),
317325
('get_makefile_filename()', sysconfig.get_makefile_filename()),
318-
('get_config_h_filename()', sysconfig.get_config_h_filename())):
326+
('get_config_h_filename()', sysconfig.get_config_h_filename()),
327+
('get_config_var("Py_GIL_DISABLED")',
328+
str(sysconfig.get_config_var("Py_GIL_DISABLED")))):
319329
with self.subTest(call):
320330
cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call
321331
out, err = check_output(cmd, encoding='utf-8')

0 commit comments

Comments
 (0)