Skip to content

Commit

Permalink
pythongh-112984 Update Windows build and installer for free-threaded …
Browse files Browse the repository at this point in the history
…builds (pythonGH-113129)
  • Loading branch information
zooba authored and aisk committed Feb 11, 2024
1 parent a97d972 commit 9ad2072
Show file tree
Hide file tree
Showing 76 changed files with 1,436 additions and 244 deletions.
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

0 comments on commit 9ad2072

Please sign in to comment.