]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-112984 Update Windows build and installer for free-threaded builds (GH-113129)
authorSteve Dower <steve.dower@python.org>
Wed, 17 Jan 2024 21:52:23 +0000 (21:52 +0000)
committerGitHub <noreply@github.com>
Wed, 17 Jan 2024 21:52:23 +0000 (21:52 +0000)
76 files changed:
.github/workflows/build_msi.yml
.github/workflows/reusable-windows.yml
Doc/using/win_install_freethreaded.png [new file with mode: 0644]
Doc/using/windows.rst
Lib/test/test_ctypes/test_loading.py
Lib/test/test_launcher.py
Lib/test/test_regrtest.py
Lib/test/test_venv.py
Lib/venv/__init__.py
Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst [new file with mode: 0644]
PC/layout/main.py
PC/layout/support/constants.py
PC/layout/support/nuspec.py
PC/layout/support/options.py
PC/pyconfig.h.in
PC/venvlauncher.c [new file with mode: 0644]
PCbuild/_asyncio.vcxproj
PCbuild/_bz2.vcxproj
PCbuild/_ctypes.vcxproj
PCbuild/_ctypes_test.vcxproj
PCbuild/_decimal.vcxproj
PCbuild/_elementtree.vcxproj
PCbuild/_hashlib.vcxproj
PCbuild/_lzma.vcxproj
PCbuild/_multiprocessing.vcxproj
PCbuild/_overlapped.vcxproj
PCbuild/_queue.vcxproj
PCbuild/_socket.vcxproj
PCbuild/_sqlite3.vcxproj
PCbuild/_ssl.vcxproj
PCbuild/_testbuffer.vcxproj
PCbuild/_testcapi.vcxproj
PCbuild/_testclinic.vcxproj
PCbuild/_testclinic_limited.vcxproj
PCbuild/_testconsole.vcxproj
PCbuild/_testimportmultiple.vcxproj
PCbuild/_testinternalcapi.vcxproj
PCbuild/_testmultiphase.vcxproj
PCbuild/_testsinglephase.vcxproj
PCbuild/_tkinter.vcxproj
PCbuild/_uuid.vcxproj
PCbuild/_wmi.vcxproj
PCbuild/_zoneinfo.vcxproj
PCbuild/pyexpat.vcxproj
PCbuild/pyproject.props
PCbuild/python.props
PCbuild/python.vcxproj
PCbuild/python3dll.vcxproj
PCbuild/pythonw.vcxproj
PCbuild/rt.bat
PCbuild/select.vcxproj
PCbuild/sqlite3.vcxproj
PCbuild/unicodedata.vcxproj
PCbuild/venvlauncher.vcxproj
PCbuild/venvlauncher.vcxproj.filters
PCbuild/venvwlauncher.vcxproj
PCbuild/venvwlauncher.vcxproj.filters
PCbuild/winsound.vcxproj
PCbuild/xxlimited.vcxproj
PCbuild/xxlimited_35.vcxproj
Tools/msi/build.bat
Tools/msi/bundle/Default.thm
Tools/msi/bundle/Default.wxl
Tools/msi/bundle/bundle.targets
Tools/msi/bundle/bundle.wxs
Tools/msi/bundle/packagegroups/freethreaded.wxs [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded.wixproj [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded.wxs [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded_d.wixproj [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded_d.wxs [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded_en-US.wxl_template [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded_files.wxs [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded_pdb.wixproj [new file with mode: 0644]
Tools/msi/freethreaded/freethreaded_pdb.wxs [new file with mode: 0644]
Tools/msi/lib/lib_files.wxs
Tools/msi/msi.props

index 29282dffa37ec0dabc98342b2aac0f989af8178c..65d32c734e77455e314d7bdd4d369add787f52fb 100644 (file)
@@ -32,6 +32,8 @@ jobs:
     strategy:
       matrix:
         type: [x86, x64, arm64]
+    env:
+      IncludeFreethreaded: true
     steps:
     - uses: actions/checkout@v4
     - name: Build CPython installer
index ae27c108d8368cd94a5d39bf108b578cef071222..c0209e0e1c92e935b32973bf877a577928d0c590 100644 (file)
@@ -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)'
@@ -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)'
diff --git a/Doc/using/win_install_freethreaded.png b/Doc/using/win_install_freethreaded.png
new file mode 100644 (file)
index 0000000..0aa01c1
Binary files /dev/null and b/Doc/using/win_install_freethreaded.png differ
index 598bf3ca9bcc04ba6ad48151c092c3a5299c063e..2a0e7b4b06f58650660f1ccfdc6096287ad9d03d 100644 (file)
@@ -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:
 
@@ -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:
 
index 22db97b818c17ec875788a8b317c6e01afca0ffd..9f0547f4c955c05737b95a02a1b9cc24fa49a3a8 100644 (file)
@@ -141,7 +141,7 @@ class LoaderTest(unittest.TestCase):
     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"
index bcd4ed63bf25a0c16e96e6b3116c8d785b76ac6d..3da6173cfd3f1349d5a15c5401f40a1671be35aa 100644 (file)
@@ -19,8 +19,10 @@ import winreg
 
 
 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.
@@ -232,7 +234,7 @@ class RunPyMixin:
             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])
@@ -273,7 +275,7 @@ class RunPyMixin:
     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)}
@@ -521,6 +523,9 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
                     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)
@@ -616,25 +621,29 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
         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
@@ -727,15 +736,18 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
             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}")
index e828941f6c779df514bdc4e70defe1c2e3ce8ded..89562fa5eac62caa05f1bab5389665c10fb82ad4 100644 (file)
@@ -845,6 +845,8 @@ class ProgramsTestCase(BaseTestCase):
             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')
@@ -862,6 +864,8 @@ class ProgramsTestCase(BaseTestCase):
             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)
 
 
index 218e7560cff3f545d6db86ab0935eb5c29b18e5d..6852625c36c62bdf765532662cb25ee1bc403853 100644 (file)
@@ -223,8 +223,14 @@ class BasicTest(BaseTest):
 
     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)
@@ -283,7 +289,9 @@ class BasicTest(BaseTest):
             # 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')
@@ -315,7 +323,9 @@ class BasicTest(BaseTest):
             # 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')
index d960bf3bd82ac5db5171e5544e66566ec074f70e..f04ca8fafcc33ba6dc130b271cc17d537cd488b2 100644 (file)
@@ -139,6 +139,11 @@ class EnvBuilder:
                              'check that your PATH environment variable is '
                              'correctly set.')
         dirname, exename = os.path.split(os.path.abspath(executable))
+        if sys.platform == 'win32':
+            # Always create the simplest name in the venv. It will either be a
+            # link back to executable, or a copy of the appropriate launcher
+            _d = '_d' if os.path.splitext(exename)[0].endswith('_d') else ''
+            exename = f'python{_d}.exe'
         context.executable = executable
         context.python_dir = dirname
         context.python_exe = exename
@@ -222,67 +227,26 @@ class EnvBuilder:
             args = ' '.join(args)
             f.write(f'command = {sys.executable} -m venv {args}\n')
 
-    if os.name != 'nt':
-        def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
-            """
-            Try symlinking a file, and if that fails, fall back to copying.
-            """
-            force_copy = not self.symlinks
-            if not force_copy:
-                try:
-                    if not os.path.islink(dst):  # can't link to itself!
-                        if relative_symlinks_ok:
-                            assert os.path.dirname(src) == os.path.dirname(dst)
-                            os.symlink(os.path.basename(src), dst)
-                        else:
-                            os.symlink(src, dst)
-                except Exception:   # may need to use a more specific exception
-                    logger.warning('Unable to symlink %r to %r', src, dst)
-                    force_copy = True
-            if force_copy:
-                shutil.copyfile(src, dst)
-    else:
-        def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
-            """
-            Try symlinking a file, and if that fails, fall back to copying.
-            """
-            bad_src = os.path.lexists(src) and not os.path.exists(src)
-            if self.symlinks and not bad_src and not os.path.islink(dst):
-                try:
+    def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
+        """
+        Try symlinking a file, and if that fails, fall back to copying.
+        (Unused on Windows, because we can't just copy a failed symlink file: we
+        switch to a different set of files instead.)
+        """
+        assert os.name != 'nt'
+        force_copy = not self.symlinks
+        if not force_copy:
+            try:
+                if not os.path.islink(dst):  # can't link to itself!
                     if relative_symlinks_ok:
                         assert os.path.dirname(src) == os.path.dirname(dst)
                         os.symlink(os.path.basename(src), dst)
                     else:
                         os.symlink(src, dst)
-                    return
-                except Exception:   # may need to use a more specific exception
-                    logger.warning('Unable to symlink %r to %r', src, dst)
-
-            # On Windows, we rewrite symlinks to our base python.exe into
-            # copies of venvlauncher.exe
-            basename, ext = os.path.splitext(os.path.basename(src))
-            srcfn = os.path.join(os.path.dirname(__file__),
-                                 "scripts",
-                                 "nt",
-                                 basename + ext)
-            # Builds or venv's from builds need to remap source file
-            # locations, as we do not put them into Lib/venv/scripts
-            if sysconfig.is_python_build() or not os.path.isfile(srcfn):
-                if basename.endswith('_d'):
-                    ext = '_d' + ext
-                    basename = basename[:-2]
-                if basename == 'python':
-                    basename = 'venvlauncher'
-                elif basename == 'pythonw':
-                    basename = 'venvwlauncher'
-                src = os.path.join(os.path.dirname(src), basename + ext)
-            else:
-                src = srcfn
-            if not os.path.exists(src):
-                if not bad_src:
-                    logger.warning('Unable to copy %r', src)
-                return
-
+            except Exception:   # may need to use a more specific exception
+                logger.warning('Unable to symlink %r to %r', src, dst)
+                force_copy = True
+        if force_copy:
             shutil.copyfile(src, dst)
 
     def create_git_ignore_file(self, context):
@@ -298,22 +262,23 @@ class EnvBuilder:
                        'see https://docs.python.org/3/library/venv.html\n')
             file.write('*\n')
 
-    def setup_python(self, context):
-        """
-        Set up a Python executable in the environment.
+    if os.name != 'nt':
+        def setup_python(self, context):
+            """
+            Set up a Python executable in the environment.
 
-        :param context: The information for the environment creation request
-                        being processed.
-        """
-        binpath = context.bin_path
-        path = context.env_exe
-        copier = self.symlink_or_copy
-        dirname = context.python_dir
-        if os.name != 'nt':
+            :param context: The information for the environment creation request
+                            being processed.
+            """
+            binpath = context.bin_path
+            path = context.env_exe
+            copier = self.symlink_or_copy
+            dirname = context.python_dir
             copier(context.executable, path)
             if not os.path.islink(path):
                 os.chmod(path, 0o755)
-            for suffix in ('python', 'python3', f'python3.{sys.version_info[1]}'):
+            for suffix in ('python', 'python3',
+                           f'python3.{sys.version_info[1]}'):
                 path = os.path.join(binpath, suffix)
                 if not os.path.exists(path):
                     # Issue 18807: make copies if
@@ -321,30 +286,105 @@ class EnvBuilder:
                     copier(context.env_exe, path, relative_symlinks_ok=True)
                     if not os.path.islink(path):
                         os.chmod(path, 0o755)
-        else:
-            if self.symlinks:
-                # For symlinking, we need a complete copy of the root directory
-                # If symlinks fail, you'll get unnecessary copies of files, but
-                # we assume that if you've opted into symlinks on Windows then
-                # you know what you're doing.
-                suffixes = [
-                    f for f in os.listdir(dirname) if
-                    os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll')
-                ]
-                if sysconfig.is_python_build():
-                    suffixes = [
-                        f for f in suffixes if
-                        os.path.normcase(f).startswith(('python', 'vcruntime'))
-                    ]
+
+    else:
+        def setup_python(self, context):
+            """
+            Set up a Python executable in the environment.
+
+            :param context: The information for the environment creation request
+                            being processed.
+            """
+            binpath = context.bin_path
+            dirname = context.python_dir
+            exename = os.path.basename(context.env_exe)
+            exe_stem = os.path.splitext(exename)[0]
+            exe_d = '_d' if os.path.normcase(exe_stem).endswith('_d') else ''
+            if sysconfig.is_python_build():
+                scripts = dirname
+            else:
+                scripts = os.path.join(os.path.dirname(__file__),
+                                       'scripts', 'nt')
+            if not sysconfig.get_config_var("Py_GIL_DISABLED"):
+                python_exe = os.path.join(dirname, f'python{exe_d}.exe')
+                pythonw_exe = os.path.join(dirname, f'pythonw{exe_d}.exe')
+                link_sources = {
+                    'python.exe': python_exe,
+                    f'python{exe_d}.exe': python_exe,
+                    'pythonw.exe': pythonw_exe,
+                    f'pythonw{exe_d}.exe': pythonw_exe,
+                }
+                python_exe = os.path.join(scripts, f'venvlauncher{exe_d}.exe')
+                pythonw_exe = os.path.join(scripts, f'venvwlauncher{exe_d}.exe')
+                copy_sources = {
+                    'python.exe': python_exe,
+                    f'python{exe_d}.exe': python_exe,
+                    'pythonw.exe': pythonw_exe,
+                    f'pythonw{exe_d}.exe': pythonw_exe,
+                }
             else:
-                suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'}
-                base_exe = os.path.basename(context.env_exe)
-                suffixes.add(base_exe)
+                exe_t = f'3.{sys.version_info[1]}t'
+                python_exe = os.path.join(dirname, f'python{exe_t}{exe_d}.exe')
+                pythonw_exe = os.path.join(dirname, f'pythonw{exe_t}{exe_d}.exe')
+                link_sources = {
+                    'python.exe': python_exe,
+                    f'python{exe_d}.exe': python_exe,
+                    f'python{exe_t}.exe': python_exe,
+                    f'python{exe_t}{exe_d}.exe': python_exe,
+                    'pythonw.exe': pythonw_exe,
+                    f'pythonw{exe_d}.exe': pythonw_exe,
+                    f'pythonw{exe_t}.exe': pythonw_exe,
+                    f'pythonw{exe_t}{exe_d}.exe': pythonw_exe,
+                }
+                python_exe = os.path.join(scripts, f'venvlaunchert{exe_d}.exe')
+                pythonw_exe = os.path.join(scripts, f'venvwlaunchert{exe_d}.exe')
+                copy_sources = {
+                    'python.exe': python_exe,
+                    f'python{exe_d}.exe': python_exe,
+                    f'python{exe_t}.exe': python_exe,
+                    f'python{exe_t}{exe_d}.exe': python_exe,
+                    'pythonw.exe': pythonw_exe,
+                    f'pythonw{exe_d}.exe': pythonw_exe,
+                    f'pythonw{exe_t}.exe': pythonw_exe,
+                    f'pythonw{exe_t}{exe_d}.exe': pythonw_exe,
+                }
+
+            do_copies = True
+            if self.symlinks:
+                do_copies = False
+                # For symlinking, we need all the DLLs to be available alongside
+                # the executables.
+                link_sources.update({
+                    f: os.path.join(dirname, f) for f in os.listdir(dirname)
+                    if os.path.normcase(f).startswith(('python', 'vcruntime'))
+                    and os.path.normcase(os.path.splitext(f)[1]) == '.dll'
+                })
+
+                to_unlink = []
+                for dest, src in link_sources.items():
+                    dest = os.path.join(binpath, dest)
+                    try:
+                        os.symlink(src, dest)
+                        to_unlink.append(dest)
+                    except OSError:
+                        logger.warning('Unable to symlink %r to %r', src, dst)
+                        do_copies = True
+                        for f in to_unlink:
+                            try:
+                                os.unlink(f)
+                            except OSError:
+                                logger.warning('Failed to clean up symlink %r',
+                                               f)
+                        logger.warning('Retrying with copies')
+                        break
 
-            for suffix in suffixes:
-                src = os.path.join(dirname, suffix)
-                if os.path.lexists(src):
-                    copier(src, os.path.join(binpath, suffix))
+            if do_copies:
+                for dest, src in copy_sources.items():
+                    dest = os.path.join(binpath, dest)
+                    try:
+                        shutil.copy2(src, dest)
+                    except OSError:
+                        logger.warning('Unable to copy %r to %r', src, dest)
 
             if sysconfig.is_python_build():
                 # copy init.tcl
@@ -437,6 +477,14 @@ class EnvBuilder:
         """
         binpath = context.bin_path
         plen = len(path)
+        if os.name == 'nt':
+            def skip_file(f):
+                f = os.path.normcase(f)
+                return (f.startswith(('python', 'venv'))
+                        and f.endswith(('.exe', '.pdb')))
+        else:
+            def skip_file(f):
+                return False
         for root, dirs, files in os.walk(path):
             if root == path:  # at top-level, remove irrelevant dirs
                 for d in dirs[:]:
@@ -444,8 +492,7 @@ class EnvBuilder:
                         dirs.remove(d)
                 continue  # ignore files in top level
             for f in files:
-                if (os.name == 'nt' and f.startswith('python')
-                        and f.endswith(('.exe', '.pdb'))):
+                if skip_file(f):
                     continue
                 srcfile = os.path.join(root, f)
                 suffix = root[plen:].split(os.sep)[2:]
@@ -456,20 +503,25 @@ class EnvBuilder:
                 if not os.path.exists(dstdir):
                     os.makedirs(dstdir)
                 dstfile = os.path.join(dstdir, f)
+                if os.name == 'nt' and srcfile.endswith(('.exe', '.pdb')):
+                    shutil.copy2(srcfile, dstfile)
+                    continue
                 with open(srcfile, 'rb') as f:
                     data = f.read()
-                if not srcfile.endswith(('.exe', '.pdb')):
-                    try:
-                        data = data.decode('utf-8')
-                        data = self.replace_variables(data, context)
-                        data = data.encode('utf-8')
-                    except UnicodeError as e:
-                        data = None
-                        logger.warning('unable to copy script %r, '
-                                       'may be binary: %s', srcfile, e)
-                if data is not None:
+                try:
+                    new_data = (
+                        self.replace_variables(data.decode('utf-8'), context)
+                            .encode('utf-8')
+                    )
+                except UnicodeError as e:
+                    logger.warning('unable to copy script %r, '
+                                   'may be binary: %s', srcfile, e)
+                    continue
+                if new_data == data:
+                    shutil.copy2(srcfile, dstfile)
+                else:
                     with open(dstfile, 'wb') as f:
-                        f.write(data)
+                        f.write(new_data)
                     shutil.copymode(srcfile, dstfile)
 
     def upgrade_dependencies(self, context):
diff --git a/Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst b/Misc/NEWS.d/next/Windows/2023-12-19-22-32-28.gh-issue-112984.F7kFMl.rst
new file mode 100644 (file)
index 0000000..429cd5b
--- /dev/null
@@ -0,0 +1 @@
+Adds free-threaded binaries to Windows installer as an optional component.
index accfd51dd978fb95e59b305943733732c06e912f..d176b272f1c19da43d0113ecac33887035f33d5b 100644 (file)
@@ -41,7 +41,7 @@ TCLTK_FILES_ONLY = FileNameSet("turtle.py")
 
 VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip")
 
-EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext", "vcruntime*")
+EXCLUDE_FROM_DLLS = FileStemSet("python*", "pyshellext", "vcruntime*")
 EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle")
 EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt")
 EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*")
@@ -126,9 +126,9 @@ def get_layout(ns):
         n = new_name or n
         src = ns.build / f
         if ns.debug and src not in REQUIRED_DLLS:
-            if not src.stem.endswith("_d"):
+            if not "_d." in src.name:
                 src = src.parent / (src.stem + "_d" + src.suffix)
-            if not n.endswith("_d"):
+            if "_d." not in f:
                 n += "_d"
                 f = n + "." + x
         yield dest + n + "." + x, src
@@ -141,17 +141,45 @@ def get_layout(ns):
             if lib.is_file():
                 yield "libs/" + n + ".lib", lib
 
+    source = "python.exe"
+    sourcew = "pythonw.exe"
+    alias = [
+        "python",
+        "python{}".format(VER_MAJOR) if ns.include_alias3 else "",
+        "python{}".format(VER_DOT) if ns.include_alias3x else "",
+    ]
+    aliasw = [
+        "pythonw",
+        "pythonw{}".format(VER_MAJOR) if ns.include_alias3 else "",
+        "pythonw{}".format(VER_DOT) if ns.include_alias3x else "",
+    ]
     if ns.include_appxmanifest:
-        yield from in_build("python_uwp.exe", new_name="python{}".format(VER_DOT))
-        yield from in_build("pythonw_uwp.exe", new_name="pythonw{}".format(VER_DOT))
-        # For backwards compatibility, but we don't reference these ourselves.
-        yield from in_build("python_uwp.exe", new_name="python")
-        yield from in_build("pythonw_uwp.exe", new_name="pythonw")
+        source = "python_uwp.exe"
+        sourcew = "pythonw_uwp.exe"
+    elif ns.include_freethreaded:
+        source = "python{}t.exe".format(VER_DOT)
+        sourcew = "pythonw{}t.exe".format(VER_DOT)
+        if not ns.include_alias:
+            alias = []
+            aliasw = []
+        alias.extend([
+            "python{}t".format(VER_DOT),
+            "python{}t".format(VER_MAJOR) if ns.include_alias3 else None,
+        ])
+        aliasw.extend([
+            "pythonw{}t".format(VER_DOT),
+            "pythonw{}t".format(VER_MAJOR) if ns.include_alias3 else None,
+        ])
+
+    for a in filter(None, alias):
+        yield from in_build(source, new_name=a)
+    for a in filter(None, aliasw):
+        yield from in_build(sourcew, new_name=a)
+
+    if ns.include_freethreaded:
+        yield from in_build(FREETHREADED_PYTHON_DLL_NAME)
     else:
-        yield from in_build("python.exe", new_name="python")
-        yield from in_build("pythonw.exe", new_name="pythonw")
-
-    yield from in_build(PYTHON_DLL_NAME)
+        yield from in_build(PYTHON_DLL_NAME)
 
     if ns.include_launchers and ns.include_appxmanifest:
         if ns.include_pip:
@@ -160,7 +188,10 @@ def get_layout(ns):
             yield from in_build("pythonw_uwp.exe", new_name="idle{}".format(VER_DOT))
 
     if ns.include_stable:
-        yield from in_build(PYTHON_STABLE_DLL_NAME)
+        if ns.include_freethreaded:
+            yield from in_build(FREETHREADED_PYTHON_STABLE_DLL_NAME)
+        else:
+            yield from in_build(PYTHON_STABLE_DLL_NAME)
 
     found_any = False
     for dest, src in rglob(ns.build, "vcruntime*.dll"):
@@ -171,16 +202,28 @@ def get_layout(ns):
 
     yield "LICENSE.txt", ns.build / "LICENSE.txt"
 
-    for dest, src in rglob(ns.build, ("*.pyd", "*.dll")):
-        if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS:
-            continue
-        if src in EXCLUDE_FROM_PYDS:
-            continue
+    for dest, src in rglob(ns.build, "*.pyd"):
+        if ns.include_freethreaded:
+            if not src.match("*.cp*t-win*.pyd"):
+                continue
+            if bool(src.match("*_d.cp*.pyd")) != bool(ns.debug):
+                continue
+        else:
+            if src.match("*.cp*t-win*.pyd"):
+                continue
+            if bool(src.match("*_d.pyd")) != bool(ns.debug):
+                continue
         if src in TEST_PYDS_ONLY and not ns.include_tests:
             continue
         if src in TCLTK_PYDS_ONLY and not ns.include_tcltk:
             continue
+        yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/")
 
+    for dest, src in rglob(ns.build, "*.dll"):
+        if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS:
+            continue
+        if src in EXCLUDE_FROM_DLLS:
+            continue
         yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/")
 
     if ns.zip_lib:
@@ -191,8 +234,12 @@ def get_layout(ns):
             yield "Lib/{}".format(dest), src
 
         if ns.include_venv:
-            yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python")
-            yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw")
+            if ns.include_freethreaded:
+                yield from in_build("venvlaunchert.exe", "Lib/venv/scripts/nt/")
+                yield from in_build("venvwlaunchert.exe", "Lib/venv/scripts/nt/")
+            else:
+                yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/")
+                yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/")
 
     if ns.include_tools:
 
@@ -208,7 +255,6 @@ def get_layout(ns):
         yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME
 
     if ns.include_dev:
-
         for dest, src in rglob(ns.source / "Include", "**/*.h"):
             yield "include/{}".format(dest), src
         yield "include/pyconfig.h", ns.build / "pyconfig.h"
@@ -552,7 +598,6 @@ def main():
 
     ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent)
     ns.build = ns.build or Path(sys.executable).parent
-    ns.temp = ns.temp or Path(tempfile.mkdtemp())
     ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build")
     if not ns.source.is_absolute():
         ns.source = (Path.cwd() / ns.source).resolve()
@@ -565,7 +610,12 @@ def main():
     if ns.include_cat and not ns.include_cat.is_absolute():
         ns.include_cat = (Path.cwd() / ns.include_cat).resolve()
     if not ns.arch:
-        ns.arch = "amd64" if sys.maxsize > 2 ** 32 else "win32"
+        if sys.winver.endswith("-arm64"):
+            ns.arch = "arm64"
+        elif sys.winver.endswith("-32"):
+            ns.arch = "win32"
+        else:
+            ns.arch = "amd64"
 
     if ns.copy and not ns.copy.is_absolute():
         ns.copy = (Path.cwd() / ns.copy).resolve()
@@ -574,6 +624,14 @@ def main():
     if ns.catalog and not ns.catalog.is_absolute():
         ns.catalog = (Path.cwd() / ns.catalog).resolve()
 
+    if not ns.temp:
+        # Put temp on a Dev Drive for speed if we're copying to one.
+        # If not, the regular temp dir will have to do.
+        if ns.copy and getattr(os.path, "isdevdrive", lambda d: False)(ns.copy):
+            ns.temp = ns.copy.with_name(ns.copy.name + "_temp")
+        else:
+            ns.temp = Path(tempfile.mkdtemp())
+
     configure_logger(ns)
 
     log_info(
@@ -602,6 +660,12 @@ Catalog: {ns.catalog}""",
         log_warning("Assuming --include-tcltk to support --include-idle")
         ns.include_tcltk = True
 
+    if not (ns.include_alias or ns.include_alias3 or ns.include_alias3x):
+        if ns.include_freethreaded:
+            ns.include_alias3x = True
+        else:
+            ns.include_alias = True
+
     try:
         generate_source_files(ns)
         files = list(get_layout(ns))
index 8195c3dc30cdc7a4dd16d7ffa17a98e1296a271c..ae22aa16ebfa5da227a6d20a4ab76c0ad445922e 100644 (file)
@@ -39,3 +39,6 @@ PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR)
 PYTHON_CHM_NAME = "python{}{}{}{}.chm".format(
     VER_MAJOR, VER_MINOR, VER_MICRO, VER_SUFFIX
 )
+
+FREETHREADED_PYTHON_DLL_NAME = "python{}{}t.dll".format(VER_MAJOR, VER_MINOR)
+FREETHREADED_PYTHON_STABLE_DLL_NAME = "python{}t.dll".format(VER_MAJOR)
index dbcb713ef9d0c00c0153bb3b98b739a98abdc709..a87e0bea049427432e95ad72130acc693da7472e 100644 (file)
@@ -24,6 +24,10 @@ NUSPEC_PLATFORM_DATA = dict(
     amd64=("64-bit", "python", "Python"),
     arm32=("ARM", "pythonarm", "Python (ARM)"),
     arm64=("ARM64", "pythonarm64", "Python (ARM64)"),
+    win32t=("32-bit free-threaded", "pythonx86-freethreaded", "Python (32-bit, free-threaded)"),
+    amd64t=("64-bit free-threaded", "python-freethreaded", "Python (free-threaded)"),
+    arm32t=("ARM free-threaded", "pythonarm-freethreaded", "Python (ARM, free-threaded)"),
+    arm64t=("ARM64 free-threaded", "pythonarm64-freethreaded", "Python (ARM64, free-threaded)"),
 )
 
 if not NUSPEC_DATA["PYTHON_VERSION"]:
@@ -58,7 +62,10 @@ NUSPEC_TEMPLATE = r"""<?xml version="1.0"?>
 
 
 def _get_nuspec_data_overrides(ns):
-    for k, v in zip(NUSPEC_PLATFORM_DATA["_keys"], NUSPEC_PLATFORM_DATA[ns.arch]):
+    arch = ns.arch
+    if ns.include_freethreaded:
+        arch += "t"
+    for k, v in zip(NUSPEC_PLATFORM_DATA["_keys"], NUSPEC_PLATFORM_DATA[arch]):
         ev = os.getenv("PYTHON_NUSPEC_" + k)
         if ev:
             yield k, ev
index 60256fb32fe32971563e38eceb91bd8e50cfe1a9..f1a8eb0b317744900b665c483b6bf106e699aafd 100644 (file)
@@ -32,6 +32,10 @@ OPTIONS = {
     "nuspec": {"help": "a python.nuspec file"},
     "chm": {"help": "the CHM documentation"},
     "html-doc": {"help": "the HTML documentation"},
+    "freethreaded": {"help": "freethreaded binaries", "not-in-all": True},
+    "alias": {"help": "aliased python.exe entry-point binaries"},
+    "alias3": {"help": "aliased python3.exe entry-point binaries"},
+    "alias3x": {"help": "aliased python3.x.exe entry-point binaries"},
 }
 
 
@@ -47,6 +51,8 @@ PRESETS = {
             "dev",
             "launchers",
             "appxmanifest",
+            "alias",
+            "alias3x",
             # XXX: Disabled for now "precompile",
         ],
     },
@@ -59,9 +65,10 @@ PRESETS = {
             "venv",
             "props",
             "nuspec",
+            "alias",
         ],
     },
-    "iot": {"help": "Windows IoT Core", "options": ["stable", "pip"]},
+    "iot": {"help": "Windows IoT Core", "options": ["alias", "stable", "pip"]},
     "default": {
         "help": "development kit package",
         "options": [
@@ -74,11 +81,19 @@ PRESETS = {
             "dev",
             "symbols",
             "html-doc",
+            "alias",
         ],
     },
     "embed": {
         "help": "embeddable package",
-        "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"],
+        "options": [
+            "alias",
+            "stable",
+            "zip-lib",
+            "flat-dlls",
+            "underpth",
+            "precompile",
+        ],
     },
 }
 
index d8f0a6be69c21a6d9b18cbfb6a520fabc995fb7c..8bbf877a5bb5edc7c692c0e6bc2f18d52a0c56b4 100644 (file)
@@ -94,6 +94,9 @@ WIN32 is still required for the locale module.
 #endif
 #endif /* Py_BUILD_CORE || Py_BUILD_CORE_BUILTIN || Py_BUILD_CORE_MODULE */
 
+/* Define to 1 if you want to disable the GIL */
+#undef Py_GIL_DISABLED
+
 /* Compiler specific defines */
 
 /* ------------------------------------------------------------------------*/
@@ -305,8 +308,16 @@ Py_NO_ENABLE_SHARED to find out.  Also support MS_NO_COREDLL for b/w compat */
                 /* not building the core - must be an ext */
 #               if defined(_MSC_VER)
                         /* So MSVC users need not specify the .lib
-                        file in their Makefile (other compilers are
-                        generally taken care of by distutils.) */
+                        file in their Makefile */
+#                       if defined(Py_GIL_DISABLED)
+#                       if defined(_DEBUG)
+#                               pragma comment(lib,"python313t_d.lib")
+#                       elif defined(Py_LIMITED_API)
+#                               pragma comment(lib,"python3t.lib")
+#                       else
+#                               pragma comment(lib,"python313t.lib")
+#                       endif /* _DEBUG */
+#                       else /* Py_GIL_DISABLED */
 #                       if defined(_DEBUG)
 #                               pragma comment(lib,"python313_d.lib")
 #                       elif defined(Py_LIMITED_API)
@@ -314,6 +325,7 @@ Py_NO_ENABLE_SHARED to find out.  Also support MS_NO_COREDLL for b/w compat */
 #                       else
 #                               pragma comment(lib,"python313.lib")
 #                       endif /* _DEBUG */
+#                       endif /* Py_GIL_DISABLED */
 #               endif /* _MSC_VER */
 #       endif /* Py_BUILD_CORE */
 #endif /* MS_COREDLL */
@@ -739,7 +751,4 @@ Py_NO_ENABLE_SHARED to find out.  Also support MS_NO_COREDLL for b/w compat */
 /* Define if libssl has X509_VERIFY_PARAM_set1_host and related function */
 #define HAVE_X509_VERIFY_PARAM_SET1_HOST 1
 
-/* Define if you want to disable the GIL */
-#undef Py_GIL_DISABLED
-
 #endif /* !Py_CONFIG_H */
diff --git a/PC/venvlauncher.c b/PC/venvlauncher.c
new file mode 100644 (file)
index 0000000..fe97d32
--- /dev/null
@@ -0,0 +1,510 @@
+/*
+ * venv redirector for Windows
+ *
+ * This launcher looks for a nearby pyvenv.cfg to find the correct home
+ * directory, and then launches the original Python executable from it.
+ * The name of this executable is passed as argv[0].
+ */
+
+#define __STDC_WANT_LIB_EXT1__ 1
+
+#include <windows.h>
+#include <pathcch.h>
+#include <fcntl.h>
+#include <io.h>
+#include <shlobj.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <tchar.h>
+#include <assert.h>
+
+#define MS_WINDOWS
+#include "patchlevel.h"
+
+#define MAXLEN PATHCCH_MAX_CCH
+#define MSGSIZE 1024
+
+#define RC_NO_STD_HANDLES   100
+#define RC_CREATE_PROCESS   101
+#define RC_NO_PYTHON        103
+#define RC_NO_MEMORY        104
+#define RC_NO_VENV_CFG      106
+#define RC_BAD_VENV_CFG     107
+#define RC_NO_COMMANDLINE   108
+#define RC_INTERNAL_ERROR   109
+
+// This should always be defined when we build for real,
+// but it's handy to have a definition for quick testing
+#ifndef EXENAME
+#define EXENAME L"python.exe"
+#endif
+
+#ifndef CFGNAME
+#define CFGNAME L"pyvenv.cfg"
+#endif
+
+static FILE * log_fp = NULL;
+
+void
+debug(wchar_t * format, ...)
+{
+    va_list va;
+
+    if (log_fp != NULL) {
+        wchar_t buffer[MAXLEN];
+        int r = 0;
+        va_start(va, format);
+        r = vswprintf_s(buffer, MAXLEN, format, va);
+        va_end(va);
+
+        if (r <= 0) {
+            return;
+        }
+        fwprintf(log_fp, L"%ls\n", buffer);
+        while (r && isspace(buffer[r])) {
+            buffer[r--] = L'\0';
+        }
+        if (buffer[0]) {
+            OutputDebugStringW(buffer);
+        }
+    }
+}
+
+
+void
+formatWinerror(int rc, wchar_t * message, int size)
+{
+    FormatMessageW(
+        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+        NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+        message, size, NULL);
+}
+
+
+void
+winerror(int err, wchar_t * format, ... )
+{
+    va_list va;
+    wchar_t message[MSGSIZE];
+    wchar_t win_message[MSGSIZE];
+    int len;
+
+    if (err == 0) {
+        err = GetLastError();
+    }
+
+    va_start(va, format);
+    len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
+    va_end(va);
+
+    formatWinerror(err, win_message, MSGSIZE);
+    if (len >= 0) {
+        _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls",
+                     win_message);
+    }
+
+#if !defined(_WINDOWS)
+    fwprintf(stderr, L"%ls\n", message);
+#else
+    MessageBoxW(NULL, message, L"Python venv launcher is sorry to say ...",
+               MB_OK);
+#endif
+}
+
+
+void
+error(wchar_t * format, ... )
+{
+    va_list va;
+    wchar_t message[MSGSIZE];
+
+    va_start(va, format);
+    _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
+    va_end(va);
+
+#if !defined(_WINDOWS)
+    fwprintf(stderr, L"%ls\n", message);
+#else
+    MessageBoxW(NULL, message, L"Python venv launcher is sorry to say ...",
+               MB_OK);
+#endif
+}
+
+
+bool
+isEnvVarSet(const wchar_t *name)
+{
+    /* only looking for non-empty, which means at least one character
+       and the null terminator */
+    return GetEnvironmentVariableW(name, NULL, 0) >= 2;
+}
+
+
+bool
+join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment)
+{
+    if (SUCCEEDED(PathCchCombineEx(buffer, bufferLength, buffer, fragment, PATHCCH_ALLOW_LONG_PATHS))) {
+        return true;
+    }
+    return false;
+}
+
+
+bool
+split_parent(wchar_t *buffer, size_t bufferLength)
+{
+    return SUCCEEDED(PathCchRemoveFileSpec(buffer, bufferLength));
+}
+
+
+/*
+ * Path calculation
+ */
+
+int
+calculate_pyvenvcfg_path(wchar_t *pyvenvcfg_path, size_t maxlen)
+{
+    if (!pyvenvcfg_path) {
+        error(L"invalid buffer provided");
+        return RC_INTERNAL_ERROR;
+    }
+    if ((DWORD)maxlen != maxlen) {
+        error(L"path buffer is too large");
+        return RC_INTERNAL_ERROR;
+    }
+    if (!GetModuleFileNameW(NULL, pyvenvcfg_path, (DWORD)maxlen)) {
+        winerror(GetLastError(), L"failed to read executable directory");
+        return RC_NO_COMMANDLINE;
+    }
+    // Remove 'python.exe' from our path
+    if (!split_parent(pyvenvcfg_path, maxlen)) {
+        error(L"failed to remove segment from '%ls'", pyvenvcfg_path);
+        return RC_NO_COMMANDLINE;
+    }
+    // Replace with 'pyvenv.cfg'
+    if (!join(pyvenvcfg_path, maxlen, CFGNAME)) {
+        error(L"failed to append '%ls' to '%ls'", CFGNAME, pyvenvcfg_path);
+        return RC_NO_MEMORY;
+    }
+    // If it exists, return
+    if (GetFileAttributesW(pyvenvcfg_path) != INVALID_FILE_ATTRIBUTES) {
+        return 0;
+    }
+    // Otherwise, remove 'pyvenv.cfg' and (probably) 'Scripts'
+    if (!split_parent(pyvenvcfg_path, maxlen) ||
+        !split_parent(pyvenvcfg_path, maxlen)) {
+        error(L"failed to remove segments from '%ls'", pyvenvcfg_path);
+        return RC_NO_COMMANDLINE;
+    }
+    // Replace 'pyvenv.cfg'
+    if (!join(pyvenvcfg_path, maxlen, CFGNAME)) {
+        error(L"failed to append '%ls' to '%ls'", CFGNAME, pyvenvcfg_path);
+        return RC_NO_MEMORY;
+    }
+    // If it exists, return
+    if (GetFileAttributesW(pyvenvcfg_path) != INVALID_FILE_ATTRIBUTES) {
+        return 0;
+    }
+    // Otherwise, we fail
+    winerror(GetLastError(), L"failed to locate %ls", CFGNAME);
+    return RC_NO_VENV_CFG;
+}
+
+
+/*
+ * pyvenv.cfg parsing
+ */
+
+static int
+find_home_value(const char *buffer, DWORD maxlen, const char **start, DWORD *length)
+{
+    if (!buffer || !start || !length) {
+        error(L"invalid find_home_value parameters()");
+        return 0;
+    }
+    for (const char *s = strstr(buffer, "home");
+         s && ((ptrdiff_t)s - (ptrdiff_t)buffer) < maxlen;
+         s = strstr(s + 1, "\nhome")
+    ) {
+        if (*s == '\n') {
+            ++s;
+        }
+        for (int i = 4; i > 0 && *s; --i, ++s);
+
+        while (*s && iswspace(*s)) {
+            ++s;
+        }
+        if (*s != L'=') {
+            continue;
+        }
+
+        do {
+            ++s;
+        } while (*s && iswspace(*s));
+
+        *start = s;
+        char *nl = strchr(s, '\n');
+        if (nl) {
+            while (nl != s && iswspace(nl[-1])) {
+                --nl;
+            }
+            *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s);
+        } else {
+            *length = (DWORD)strlen(s);
+        }
+        return 1;
+    }
+    return 0;
+}
+
+
+int
+read_home(const wchar_t *pyvenv_cfg, wchar_t *home_path, size_t maxlen)
+{
+    HANDLE hFile = CreateFileW(pyvenv_cfg, GENERIC_READ,
+        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+        NULL, OPEN_EXISTING, 0, NULL);
+
+    if (hFile == INVALID_HANDLE_VALUE) {
+        winerror(GetLastError(), L"failed to open '%ls'", pyvenv_cfg);
+        return RC_BAD_VENV_CFG;
+    }
+
+    // 8192 characters ought to be enough for anyone
+    // (doubled compared to the old implementation!)
+    char buffer[8192];
+    DWORD len;
+    if (!ReadFile(hFile, buffer, sizeof(buffer) - 1, &len, NULL)) {
+        winerror(GetLastError(), L"failed to read '%ls'", pyvenv_cfg);
+        CloseHandle(hFile);
+        return RC_BAD_VENV_CFG;
+    }
+    CloseHandle(hFile);
+    // Ensure null termination
+    buffer[len] = '\0';
+
+    char *home;
+    DWORD home_len;
+    if (!find_home_value(buffer, sizeof(buffer), &home, &home_len)) {
+        error(L"no home= specified in '%ls'", pyvenv_cfg);
+        return RC_BAD_VENV_CFG;
+    }
+
+    if ((DWORD)maxlen != maxlen) {
+        maxlen = 8192;
+    }
+    len = MultiByteToWideChar(CP_UTF8, 0, home, home_len, home_path, (DWORD)maxlen);
+    if (!len) {
+        winerror(GetLastError(), L"failed to decode home setting in '%ls'", pyvenv_cfg);
+        return RC_BAD_VENV_CFG;
+    }
+    home_path[len] = L'\0';
+
+    return 0;
+}
+
+
+int
+locate_python(wchar_t *path, size_t maxlen)
+{
+    if (!join(path, maxlen, EXENAME)) {
+        error(L"failed to append %ls to '%ls'", EXENAME, path);
+        return RC_NO_MEMORY;
+    }
+
+    if (GetFileAttributesW(path) == INVALID_FILE_ATTRIBUTES) {
+        winerror(GetLastError(), L"did not find executable at '%ls'", path);
+        return RC_NO_PYTHON;
+    }
+
+    return 0;
+}
+
+
+int
+smuggle_path()
+{
+    wchar_t buffer[MAXLEN];
+    // We could use argv[0], but that may be wrong in certain rare cases (if the
+    // user is doing something weird like symlinks to venv redirectors), and
+    // what we _really_ want is the directory of the venv. We always copy the
+    // redirectors, so if we've made the venv, this will be correct.
+    DWORD len = GetModuleFileNameW(NULL, buffer, MAXLEN);
+    if (!len) {
+        winerror(GetLastError(), L"Failed to get own executable path");
+        return RC_INTERNAL_ERROR;
+    }
+    buffer[len] = L'\0';
+    debug(L"Setting __PYVENV_LAUNCHER__ = '%s'", buffer);
+
+    if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", buffer)) {
+        winerror(GetLastError(), L"Failed to set launcher environment");
+        return RC_INTERNAL_ERROR;
+    }
+
+    return 0;
+}
+
+/*
+ * Process creation
+ */
+
+static BOOL
+safe_duplicate_handle(HANDLE in, HANDLE * pout, const wchar_t *name)
+{
+    BOOL ok;
+    HANDLE process = GetCurrentProcess();
+    DWORD rc;
+
+    *pout = NULL;
+    ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
+                         DUPLICATE_SAME_ACCESS);
+    if (!ok) {
+        rc = GetLastError();
+        if (rc == ERROR_INVALID_HANDLE) {
+            debug(L"DuplicateHandle(%ls) returned ERROR_INVALID_HANDLE\n", name);
+            ok = TRUE;
+        }
+        else {
+            debug(L"DuplicateHandle(%ls) returned %d\n", name, rc);
+        }
+    }
+    return ok;
+}
+
+static BOOL WINAPI
+ctrl_c_handler(DWORD code)
+{
+    return TRUE;    /* We just ignore all control events. */
+}
+
+static int
+launch(const wchar_t *executable, wchar_t *cmdline)
+{
+    HANDLE job;
+    JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
+    DWORD rc;
+    BOOL ok;
+    STARTUPINFOW si;
+    PROCESS_INFORMATION pi;
+
+#if defined(_WINDOWS)
+    /*
+    When explorer launches a Windows (GUI) application, it displays
+    the "app starting" (the "pointer + hourglass") cursor for a number
+    of seconds, or until the app does something UI-ish (eg, creating a
+    window, or fetching a message).  As this launcher doesn't do this
+    directly, that cursor remains even after the child process does these
+    things.  We avoid that by doing a simple post+get message.
+    See http://bugs.python.org/issue17290
+    */
+    MSG msg;
+
+    PostMessage(0, 0, 0, 0);
+    GetMessage(&msg, 0, 0, 0);
+#endif
+
+    debug(L"run_child: about to run '%ls' with '%ls'\n", executable, cmdline);
+    job = CreateJobObject(NULL, NULL);
+    ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
+                                   &info, sizeof(info), &rc);
+    if (!ok || (rc != sizeof(info)) || !job) {
+        winerror(GetLastError(), L"Job information querying failed");
+        return RC_CREATE_PROCESS;
+    }
+    info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
+                                             JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
+    ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
+                                 sizeof(info));
+    if (!ok) {
+        winerror(GetLastError(), L"Job information setting failed");
+        return RC_CREATE_PROCESS;
+    }
+    memset(&si, 0, sizeof(si));
+    GetStartupInfoW(&si);
+    ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput, L"stdin");
+    if (!ok) {
+        return RC_NO_STD_HANDLES;
+    }
+    ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput, L"stdout");
+    if (!ok) {
+        return RC_NO_STD_HANDLES;
+    }
+    ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError, L"stderr");
+    if (!ok) {
+        return RC_NO_STD_HANDLES;
+    }
+
+    ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
+    if (!ok) {
+        winerror(GetLastError(), L"control handler setting failed");
+        return RC_CREATE_PROCESS;
+    }
+
+    si.dwFlags = STARTF_USESTDHANDLES;
+    ok = CreateProcessW(executable, cmdline, NULL, NULL, TRUE,
+                        0, NULL, NULL, &si, &pi);
+    if (!ok) {
+        winerror(GetLastError(), L"Unable to create process using '%ls'", cmdline);
+        return RC_CREATE_PROCESS;
+    }
+    AssignProcessToJobObject(job, pi.hProcess);
+    CloseHandle(pi.hThread);
+    WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
+    ok = GetExitCodeProcess(pi.hProcess, &rc);
+    if (!ok) {
+        winerror(GetLastError(), L"Failed to get exit code of process");
+        return RC_CREATE_PROCESS;
+    }
+    debug(L"child process exit code: %d", rc);
+    return rc;
+}
+
+
+int
+process(int argc, wchar_t ** argv)
+{
+    int exitCode;
+    wchar_t pyvenvcfg_path[MAXLEN];
+    wchar_t home_path[MAXLEN];
+
+    if (isEnvVarSet(L"PYLAUNCHER_DEBUG")) {
+        setvbuf(stderr, (char *)NULL, _IONBF, 0);
+        log_fp = stderr;
+    }
+
+    exitCode = calculate_pyvenvcfg_path(pyvenvcfg_path, MAXLEN);
+    if (exitCode) return exitCode;
+
+    exitCode = read_home(pyvenvcfg_path, home_path, MAXLEN);
+    if (exitCode) return exitCode;
+
+    exitCode = locate_python(home_path, MAXLEN);
+    if (exitCode) return exitCode;
+
+    // We do not update argv[0] to point at the target runtime, and so we do not
+    // pass through our original argv[0] in an environment variable.
+    //exitCode = smuggle_path();
+    //if (exitCode) return exitCode;
+
+    exitCode = launch(home_path, GetCommandLineW());
+    return exitCode;
+}
+
+
+#if defined(_WINDOWS)
+
+int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+                   LPWSTR lpstrCmd, int nShow)
+{
+    return process(__argc, __wargv);
+}
+
+#else
+
+int cdecl wmain(int argc, wchar_t ** argv)
+{
+    return process(argc, argv);
+}
+
+#endif
index ed1e1bc0a420dc3a0c85a5a581b724a1a4ca71ba..76b0ffd660dba0c6cca26e13894bf4047a20dd6f 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 3fe95fbf83993a2dca310808f8317c3cdfc350e9..e0dc6ec187a08d4b7afe95ccfee47b36760f89ce 100644 (file)
@@ -80,7 +80,7 @@
   <PropertyGroup Label="Configuration" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 253da31e9ce182abe3848de7c51ed7674253e4a6..63d5fa49cd4e17acd12ac8392e68f443b1e0f59a 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 8a01e743a4d86fc32c7d72c387ed7edb7f6f45f7..97354739c09834c244389ed164fb6e8c6055b26e 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 0916f1a2d37887f8635c219371b8260ffb6a6a6a..490d7df87eb1c63cf518a09cc761b3d3ccdd085d 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 8da5244bac0cb65791c00cb0fc93f03d7385f196..8c9c0e42f7fe3e8473649a4bb235cfa3766078d2 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 6dad8183c57ae38cf91e9354566300a9552ee6f3..2cd205224bc0891d380e7fc7ef26e7cac0bc0769 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index fe076a6fc5716807d16d1e5ba77ece7132c0b343..40107d4b76cd53b339a1acba0885e72449743f6b 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 77b6bfc8e1e483a4e5fe1e9bc3fd3161fdd9d570..a65397f532aa8638ac26cd0186ef6d4d622df826 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 9e60d3b5db336c81ccb893c5854d64010a26317d..224bf05d5303a0375dac6d735b6e461d689c9053 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 8065b2358516862cdf6ed2fdf0f8ac496b1a0932..80a1c3c6a4ad3ef673f5130f0aeac8ce529251bd 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 78fa4d6729abb993d46e9c59e0c822dc3f8e4ba8..41af0895921bbb98681573203e2078d7dc182ddb 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 57c7413671e54e7d6afbba150f1a7ecfb8b78327..9ae0a0fc3a009db7c4376c442d45e53c6a8acedb 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 226ff506f8c62bb21a3430208dd1ec0e717d6144..d4e1affab031d7524260049824b3c6cba3bb553e 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 917d7ae50feb142274fa04f2b501b0f7ad4e738e..4e721e8ce09f0c29fd5651adc09bcb5a26bc4358 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 1c15541d3ec735fd5eb827a1cee5daef402b1e6f..6911aacab29b9793ca86e967ef8c0a25536e5577 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index e319b3c0f42e0faded78b1b2da1d1584d7e9131f..ef981332c6ab03f45ffe7e502fa1019b7648a052 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>\r
\ No newline at end of file
+</Project>
index b00b2be491b423d5888070df5fdbfd7f7a81b9d1..183a55080e869385f7d6423b1c049eeaed869aaa 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 5d7e14eff10294e8f35e99ecefa7db39c64e0635..69d312b17a5a62e112dd226d455ab8b0cd57df12 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 6d80d5779f24d80d329da1866df29e70e5e61432..c35ac83c1c739f0b246932d5e2b458c3445cce2a 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 558f66ca95cd33d36ac2fcff7c090c6a89519262..a825cac9138674a02101d84800fa717c722fa69e 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 430eb528cc39277cdddf9dca17ebf9c247c79dda..e730fe308ab8356883ddced52355c61a4ab45d19 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index fb4bcd953923f860f80b32cd57dcb36e9e463d09..bf4dabf66c10408f20469f4d5e7c55d29cc5968d 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 30cedcbb43de76b74f47df32b16558b829ec8972..117488a01621cc34a84e56a795b0773117f4fd55 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 2437b7eb2d9399f40304af2ce09d43456f48773f..50d81cc7916dbdc80fcf7441597931cfcc7e945d 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index c1914a3fa5a1bff9efd78d8863a130a33bf93a24..22fa896098252882473159fb48fc50b53b82924b 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 6e6389c3773397f84af786b997455bae499498b8..47b5bfa5b8815a213ccece8488b63048a52a0bc6 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 001f8afd89b9e9fc54f11cd2ba7dc520fd3f44a7..dc9161a8b290f96908f35a0fd9601dbcab9d49db 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 16ad91ef0278c82d32ba972326b3ce3175a399ac..fd5fbc9e910eee4b7ceff6281e76d5f8a4198aa9 100644 (file)
     <LinkIncremental Condition="$(Configuration) != 'Debug'">false</LinkIncremental>
   </PropertyGroup>
 
+  <PropertyGroup Condition="$(TargetExt) != ''">
+    <TargetNameExt>$(TargetName)$(TargetExt)</TargetNameExt>
+    <_TargetNameSep>$(TargetNameExt.LastIndexOf(`.`))</_TargetNameSep>
+    <TargetName>$(TargetNameExt.Substring(0, $(_TargetNameSep)))</TargetName>
+    <TargetExt>$(TargetNameExt.Substring($(_TargetNameSep)))</TargetExt>
+  </PropertyGroup>
+
   <PropertyGroup Label="MSVC Bug Workarounds" Condition="$(VCToolsVersion) != ''">
     <_VCToolsVersion>$([System.Version]::Parse(`$(VCToolsVersion)`).Major).$([System.Version]::Parse(`$(VCToolsVersion)`).Minor)</_VCToolsVersion>
 
@@ -38,7 +45,7 @@
     <_PlatformPreprocessorDefinition>_WIN32;</_PlatformPreprocessorDefinition>
     <_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64'">_WIN64;</_PlatformPreprocessorDefinition>
     <_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64' and $(PlatformToolset) != 'ClangCL'">_M_X64;$(_PlatformPreprocessorDefinition)</_PlatformPreprocessorDefinition>
-    <_Py3NamePreprocessorDefinition>PY3_DLLNAME=L"$(Py3DllName)";</_Py3NamePreprocessorDefinition>
+    <_Py3NamePreprocessorDefinition>PY3_DLLNAME=L"$(Py3DllName)$(PyDebugExt)";</_Py3NamePreprocessorDefinition>
   </PropertyGroup>
   <ItemDefinitionGroup>
     <ClCompile>
@@ -158,8 +165,8 @@ public override bool Execute() {
   </UsingTask>
 
   <Target Name="KillPython" BeforeTargets="PrepareForBuild" Condition="'$(KillPython)' == 'true'">
-    <Message Text="Killing any running python$(PyDebugExt)$(PyTestExt).exe instances..." Importance="high" />
-    <KillPython FileName="$(OutDir)python$(PyDebugExt)$(PyTestExt).exe" />
+    <Message Text="Killing any running $(PyExeName)$(PyDebugExt)$(PyTestExt).exe instances..." Importance="high" />
+    <KillPython FileName="$(OutDir)$(PyExeName)$(PyDebugExt)$(PyTestExt).exe" />
   </Target>
 
   <!--
index 3b7a8876a707df8cdf8cb7eede79ebccf2e37223..e8796081c4eaf372c58d59c6d64043ab9398c9cf 100644 (file)
     <ArchName Condition="'$(ArchName)' == '' and $(Platform) == 'ARM'">arm32</ArchName>
     <ArchName Condition="'$(ArchName)' == '' and $(Platform) == 'ARM64'">arm64</ArchName>
     <ArchName Condition="'$(ArchName)' == ''">win32</ArchName>
-    
+
     <!-- Root directory of the repository -->
     <PySourcePath Condition="'$(PySourcePath)' == ''">$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\))</PySourcePath>
     <PySourcePath Condition="!HasTrailingSlash($(PySourcePath))">$(PySourcePath)\</PySourcePath>
-    
+
     <!-- Directory where build outputs are put -->
     <BuildPath32 Condition="'$(Py_OutDir)' == ''">$(PySourcePath)PCbuild\win32\</BuildPath32>
     <BuildPath32 Condition="'$(Py_OutDir)' != ''">$(Py_OutDir)\win32\</BuildPath32>
@@ -52,7 +52,7 @@
     <BuildPath Condition="'$(BuildPath)' == ''">$(PySourcePath)PCbuild\$(ArchName)\</BuildPath>
     <BuildPath Condition="!HasTrailingSlash($(BuildPath))">$(BuildPath)\</BuildPath>
     <BuildPath Condition="$(Configuration) == 'PGInstrument'">$(BuildPath)instrumented\</BuildPath>
-    
+
     <!-- VPATH definition (escaped) -->
     <PyVPath Condition="$(Configuration) != 'PGInstrument'">..\\..</PyVPath>
     <PyVPath Condition="$(Configuration) == 'PGInstrument'">..\\..\\..</PyVPath>
   <PropertyGroup>
     <!-- Suffix for all binaries when building for debug -->
     <PyDebugExt Condition="'$(PyDebugExt)' == '' and $(Configuration) == 'Debug'">_d</PyDebugExt>
-    
+
     <!-- Suffix for versions/keys when building with test markers -->
     <PyTestExt Condition="$(UseTestMarker) == 'true'">-test</PyTestExt>
-    
+
     <!-- Suffix for versions/keys when building for particular platforms -->
     <PyArchExt Condition="'$(ArchName)' == 'win32'">-32</PyArchExt>
     <PyArchExt Condition="'$(ArchName)' == 'arm32'">-arm32</PyArchExt>
     <PyArchExt Condition="'$(ArchName)' == 'arm64'">-arm64</PyArchExt>
-    
-    <!-- Full path of the resulting python.exe binary -->
-    <PythonExe Condition="'$(PythonExe)' == ''">$(BuildPath)python$(PyDebugExt).exe</PythonExe>
 
     <!-- Include Tkinter by default -->
     <IncludeTkinter Condition="'$(IncludeTkinter)' == ''">true</IncludeTkinter>
   </PropertyGroup>
-  
+
   <PropertyGroup Condition="'$(Platform)'=='ARM'" Label="ArmConfiguration">
     <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
   </PropertyGroup>
   <PropertyGroup Condition="'$(OverrideVersion)' == ''">
     <!--
     Read version information from Include\patchlevel.h. The following properties are set:
-    
+
         MajorVersionNumber  -   the '3' in '3.5.2a1'
         MinorVersionNumber  -   the '5' in '3.5.2a1'
         MicroVersionNumber  -   the '2' in '3.5.2a1'
     <ReleaseLevelName Condition="$(_ReleaseLevel) == 'BETA'">b$(ReleaseSerial)</ReleaseLevelName>
     <ReleaseLevelName Condition="$(_ReleaseLevel) == 'GAMMA'">rc$(ReleaseSerial)</ReleaseLevelName>
   </PropertyGroup>
-  
+
   <PropertyGroup Condition="'$(OverrideVersion)' != ''">
     <!--
     Override the version number when building by specifying OverrideVersion.
     For example:
-    
+
         PCbuild\build.bat "/p:OverrideVersion=3.5.2a1"
-    
+
     Use the -V option to check your version is valid:
-    
+
         PCbuild\build.bat -V "/p:OverrideVersion=3.5.2a1"
           PythonVersionNumber: 3.5.2
           PythonVersion:       3.5.2a1
           PythonVersionHex:    0x030502A1
           Field3Value:         2101
-    
+
     Note that this only affects the version numbers embedded in resources and
     installers, but not sys.version.
     -->
         ))
     ))</Field3Value>
     <Field3Value Condition="$(UseTestMarker) == 'true'">$([msbuild]::Add($(Field3Value), 9000))</Field3Value>
-    
+
+    <!-- Name and full path of the resulting python.exe binary -->
+    <PyExeName Condition="$(DisableGil) == 'true'">python$(MajorVersionNumber).$(MinorVersionNumber)t</PyExeName>
+    <PyExeName Condition="$(PyExeName) == ''">python</PyExeName>
+    <PythonExe Condition="'$(PythonExe)' == ''">$(BuildPath)$(PyExeName)$(PyDebugExt).exe</PythonExe>
+    <PyWExeName Condition="$(DisableGil) == 'true'">pythonw$(MajorVersionNumber).$(MinorVersionNumber)t</PyWExeName>
+    <PyWExeName Condition="$(PyWExeName) == ''">pythonw</PyWExeName>
+
     <!-- The name of the resulting pythonXY.dll (without the extension) -->
-    <PyDllName>python$(MajorVersionNumber)$(MinorVersionNumber)$(PyDebugExt)</PyDllName>
+    <PyDllName Condition="$(DisableGil) == 'true'">python$(MajorVersionNumber)$(MinorVersionNumber)t$(PyDebugExt)</PyDllName>
+    <PyDllName Condition="$(PyDllName) == ''">python$(MajorVersionNumber)$(MinorVersionNumber)$(PyDebugExt)</PyDllName>
     <!-- The name of the resulting pythonX.dll (without the extension) -->
-    <Py3DllName>python3$(PyDebugExt)</Py3DllName>
+    <Py3DllName Condition="$(DisableGil) == 'true'">python3t</Py3DllName>
+    <Py3DllName Condition="$(Py3DllName) == ''">python3</Py3DllName>
 
     <!-- The version and platform tag to include in .pyd filenames -->
     <PydTag Condition="$(ArchName) == 'win32'">.cp$(MajorVersionNumber)$(MinorVersionNumber)-win32</PydTag>
     <PydTag Condition="$(ArchName) == 'arm32'">.cp$(MajorVersionNumber)$(MinorVersionNumber)-win_arm32</PydTag>
     <PydTag Condition="$(ArchName) == 'arm64'">.cp$(MajorVersionNumber)$(MinorVersionNumber)-win_arm64</PydTag>
     <PydTag Condition="$(ArchName) == 'amd64'">.cp$(MajorVersionNumber)$(MinorVersionNumber)-win_amd64</PydTag>
-    
+
     <!-- The version number for sys.winver -->
     <SysWinVer>$(MajorVersionNumber).$(MinorVersionNumber)$(PyArchExt)$(PyTestExt)</SysWinVer>
+
+    <!-- The version and platform tag to include in .pyd filenames for freethreaded builds -->
+    <FreethreadedPydTag Condition="$(ArchName) == 'win32'">.cp$(MajorVersionNumber)$(MinorVersionNumber)t-win32</FreethreadedPydTag>
+    <FreethreadedPydTag Condition="$(ArchName) == 'arm32'">.cp$(MajorVersionNumber)$(MinorVersionNumber)t-win_arm32</FreethreadedPydTag>
+    <FreethreadedPydTag Condition="$(ArchName) == 'arm64'">.cp$(MajorVersionNumber)$(MinorVersionNumber)t-win_arm64</FreethreadedPydTag>
+    <FreethreadedPydTag Condition="$(ArchName) == 'amd64'">.cp$(MajorVersionNumber)$(MinorVersionNumber)t-win_amd64</FreethreadedPydTag>
+
+    <!-- The version number for sys.winver for freethreaded builds -->
+    <FreethreadedSysWinVer>$(MajorVersionNumber).$(MinorVersionNumber)t$(PyArchExt)$(PyTestExt)</FreethreadedSysWinVer>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="$(DisableGil) != 'true'">
+    <!-- The extension to use for standard library PYDs -->
+    <PyStdlibPydExt>.pyd</PyStdlibPydExt>
   </PropertyGroup>
-  
+
+  <PropertyGroup Condition="$(DisableGil) == 'true'">
+    <PydTag>$(FreethreadedPydTag)</PydTag>
+
+    <!-- The extension to use for standard library PYDs -->
+    <PyStdlibPydExt>$(PydTag).pyd</PyStdlibPydExt>
+
+    <!-- The version number for sys.winver -->
+    <SysWinVer>$(FreethreadedSysWinVer)</SysWinVer>
+  </PropertyGroup>
+
   <!-- Displays the calculated version info -->
   <Target Name="ShowVersionInfo">
     <Message Importance="high" Text="PythonVersionNumber: $(PythonVersionNumber)" />
index fdf573aa5bf983efe5d82530ece83f3e9662aa73..1e5ab877488e4a12e62c863c9a68fa12f87572ce 100644 (file)
@@ -72,6 +72,7 @@
   <Import Project="python.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Label="Configuration">
+    <TargetName>$(PyExeName)</TargetName>
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <CharacterSet>MultiByte</CharacterSet>
     </PropertyGroup>
     <Exec Command='setlocal
 set PYTHONPATH=$(PySourcePath)Lib
-"$(OutDir)python$(PyDebugExt).exe" "$(PySourcePath)PC\validate_ucrtbase.py" $(UcrtName)' ContinueOnError="true" />
+"$(OutDir)$(PyExeName)$(PyDebugExt).exe" "$(PySourcePath)PC\validate_ucrtbase.py" $(UcrtName)' ContinueOnError="true" />
   </Target>
   <Target Name="GeneratePythonBat" AfterTargets="AfterBuild">
     <PropertyGroup>
@@ -145,7 +146,7 @@ set PYTHONPATH=$(PySourcePath)Lib
 @echo Running $(Configuration)^|$(Platform) interpreter...
 @setlocal
 @set PYTHONHOME=$(PySourcePath)
-@"$(OutDir)python$(PyDebugExt).exe" %*
+@"$(OutDir)$(PyExeName)$(PyDebugExt).exe" %*
 </_Content>
       <_ExistingContent Condition="Exists('$(PySourcePath)python.bat')">$([System.IO.File]::ReadAllText('$(PySourcePath)python.bat'))</_ExistingContent>
     </PropertyGroup>
index ec22e6fc76e58412fc3a54ad39ad336e64a8ae42..235ea1cf9d33fb94e7e48a9dc1b67968ae7a9dc1 100644 (file)
     <ProjectGuid>{885D4898-D08D-4091-9C40-C700CFE3FC5A}</ProjectGuid>
     <RootNamespace>python3dll</RootNamespace>
     <Keyword>Win32Proj</Keyword>
-    <TargetName>python3</TargetName>
     <SupportPGO>false</SupportPGO>
   </PropertyGroup>
   <Import Project="python.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Label="Configuration">
+    <TargetName>$(Py3DllName)</TargetName>
     <ConfigurationType>DynamicLibrary</ConfigurationType>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
index 31f21308e25fb3eb6ffe7d043b1d53ffb3bbed83..d6cf0c97dedb092434ff2c4707c03dd7f6b1f860 100644 (file)
@@ -73,6 +73,7 @@
   <Import Project="python.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Label="Configuration">
+    <TargetName>$(PyWExeName)</TargetName>
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
index 332ba5edcf40820c5860f2495dfa6336678c0a50..293f99ae135faacecdc02a2a64b6775a2d125388 100644 (file)
@@ -9,6 +9,7 @@ rem      -q runs the tests just once, and without deleting .pyc files.
 rem -p <Win32|x64|ARM|ARM64> or -win32, -x64, -arm32, -arm64
 rem      Run the specified architecture of python (or python_d if -d
 rem      was specified). If omitted, uses %PREFIX% if set or 64-bit.
+rem --disable-gil Run free-threaded build.
 rem All leading instances of these switches are shifted off, and
 rem whatever remains (up to 9 arguments) is passed to regrtest.py.
 rem For example,
@@ -29,6 +30,7 @@ rem     rt -u "network,largefile"
 setlocal
 
 set pcbuild=%~dp0
+set pyname=python
 set suffix=
 set qmode=
 set dashO=
@@ -39,15 +41,18 @@ set exe=
 if "%1"=="-O" (set dashO=-O)     & shift & goto CheckOpts
 if "%1"=="-q" (set qmode=yes)    & shift & goto CheckOpts
 if "%1"=="-d" (set suffix=_d)    & shift & goto CheckOpts
+rem HACK: Need some way to infer the version number in this script
+if "%1"=="--disable-gil" (set pyname=python3.13t) & shift & goto CheckOpts
 if "%1"=="-win32" (set prefix=%pcbuild%win32) & shift & goto CheckOpts
 if "%1"=="-x64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts
+if "%1"=="-amd64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts
 if "%1"=="-arm64" (set prefix=%pcbuild%arm64) & shift & goto CheckOpts
 if "%1"=="-arm32" (set prefix=%pcbuild%arm32) & shift & goto CheckOpts
 if "%1"=="-p" (call :SetPlatform %~2) & shift & shift & goto CheckOpts
 if NOT "%1"=="" (set regrtestargs=%regrtestargs% %1) & shift & goto CheckOpts
 
 if not defined prefix set prefix=%pcbuild%amd64
-set exe=%prefix%\python%suffix%.exe
+set exe=%prefix%\%pyname%%suffix%.exe
 set cmd="%exe%" %dashO% -m test %regrtestargs%
 if defined qmode goto Qmode
 
index 750a713949919a80130e7eb6fffc91c2e0261f34..d7448fd4d72380309bb8c3d4cba128d2d601d242 100644 (file)
@@ -78,7 +78,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index c502d51833b91aa34001a062087249eea7f69d28..6bcc4e913c8e77ce748b5297ea0a376203454800 100644 (file)
   <PropertyGroup Label="Globals">
     <ProjectGuid>{A1A295E5-463C-437F-81CA-1F32367685DA}</ProjectGuid>
     <RootNamespace>sqlite3</RootNamespace>
-    <TargetExt>.pyd</TargetExt>
     <SupportPGO>false</SupportPGO>
   </PropertyGroup>
   <Import Project="python.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Label="Configuration">
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>NotSet</CharacterSet>
   </PropertyGroup>
index addef753359ed681d7ede58de18adc8c014a3a3d..781f938e2ab78e5c0d723e858624f76d91ee2338 100644 (file)
@@ -79,7 +79,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 123e84ec4e3682a7292ebf1ca9f513c331f72944..1193e032245c947e7a757478cff8c68e00d708ea 100644 (file)
   <PropertyGroup Label="Globals">
     <ProjectGuid>{494BAC80-A60C-43A9-99E7-ACB691CE2C4D}</ProjectGuid>
     <RootNamespace>venvlauncher</RootNamespace>
-    <TargetName>venvlauncher</TargetName>
     <SupportPGO>false</SupportPGO>
   </PropertyGroup>
   <Import Project="python.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Label="Configuration">
+    <TargetName>venvlauncher</TargetName>
+    <TargetName Condition="$(DisableGil) == 'true'">$(TargetName)t</TargetName>
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
   </PropertyGroup>
   <PropertyGroup Label="UserMacros" />
   <ItemDefinitionGroup>
     <ClCompile>
-      <PreprocessorDefinitions>_CONSOLE;VENV_REDIRECT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>EXENAME=L"$(PyExeName)$(PyDebugExt).exe";_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>PY_ICON;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
-      <AdditionalDependencies>version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <SubSystem>Console</SubSystem>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\PC\launcher.c" />
+    <ClCompile Include="..\PC\venvlauncher.c" />
   </ItemGroup>
   <ItemGroup>
     <None Include="..\PC\launcher.ico" />
index ec13936bf6cb7e61d7ffc9af469357766c1561b6..56a0f005a3fa2ae25e8e68b049bb9371bba181c9 100644 (file)
@@ -19,7 +19,7 @@
     </ResourceCompile>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\PC\launcher.c">
+    <ClCompile Include="..\PC\venvlauncher.c">
       <Filter>Source Files</Filter>
     </ClCompile>
   </ItemGroup>
index b8504d5d08e52f697b425add8f5b8296aa5ec5aa..1b61718201367fd996c5e032e04183f4791294c4 100644 (file)
   <PropertyGroup Label="Globals">
     <ProjectGuid>{FDB84CBB-2FB6-47C8-A2D6-091E0833239D}</ProjectGuid>
     <RootNamespace>venvwlauncher</RootNamespace>
-    <TargetName>venvwlauncher</TargetName>
     <SupportPGO>false</SupportPGO>
   </PropertyGroup>
   <Import Project="python.props" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Label="Configuration">
+    <TargetName>venvwlauncher</TargetName>
+    <TargetName Condition="$(DisableGil) == 'true'">$(TargetName)t</TargetName>
     <ConfigurationType>Application</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
   </PropertyGroup>
   <PropertyGroup Label="UserMacros" />
   <ItemDefinitionGroup>
     <ClCompile>
-      <PreprocessorDefinitions>_WINDOWS;VENV_REDIRECT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>EXENAME=L"$(PyExeName)$(PyDebugExt).exe";_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
     </ClCompile>
     <ResourceCompile>
       <PreprocessorDefinitions>PYW_ICON;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
-      <AdditionalDependencies>version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <SubSystem>Windows</SubSystem>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\PC\launcher.c" />
+    <ClCompile Include="..\PC\venvlauncher.c" />
   </ItemGroup>
   <ItemGroup>
     <None Include="..\PC\launcher.ico" />
index 8addc13e977e7a7a8e32cfc84bc540629f35c4ae..61a514395e82dc57b336659af0212162920a1b19 100644 (file)
@@ -9,7 +9,7 @@
     </Filter>
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="..\PC\launcher.c">
+    <ClCompile Include="..\PC\venvlauncher.c">
       <Filter>Source Files</Filter>
     </ClCompile>
   </ItemGroup>
index 32cedc9b4449029e1233d4d249534ee7b169a205..c26029b15a339f55f80d60d02e4c4ef625b4690b 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index 1c776fb0da3e72ab0854663035dee70dabe34188..093e6920c0b76ce5fd6f4c04a1d389d0f7cffe9e 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index dd830b3b6aaa91c3c72b6b129d3c6031ae10baff..3f4d4463f24af075e790b29917384033a042e3a5 100644 (file)
@@ -80,7 +80,7 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <PropertyGroup>
-    <TargetExt>.pyd</TargetExt>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
   </PropertyGroup>
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
index b9aab887c4939b802248f0cfce0d24dbf9f77fca..2fe8a475e7e3a30d10c22988cb37c29e192d14f4 100644 (file)
@@ -22,6 +22,9 @@ if    "%~1" EQU "--no-test-marker" (set BUILDTEST=) && shift && goto CheckOpts
 if    "%~1" EQU "--test-marker" (set BUILDTEST=--test-marker) && shift && goto CheckOpts
 if    "%~1" EQU "--pack" (set BUILDPACK=1) && shift && goto CheckOpts
 if    "%~1" EQU "-r" (set REBUILD=-r) && shift && goto CheckOpts
+rem %IncludeFreethreaded% is recognised by the MSI build, but not the regular build.
+rem We use it to build twice and then build the installer with its extra option
+if /I "%~1" EQU "--disable-gil" (set IncludeFreethreaded=true) && shift && goto CheckOpts
 
 if not defined BUILDX86 if not defined BUILDX64 if not defined BUILDARM64 (set BUILDX86=1) && (set BUILDX64=1)
 
@@ -44,6 +47,20 @@ if errorlevel 1 exit /B %ERRORLEVEL%
 if defined BUILDARM64  call "%PCBUILD%build.bat" -p ARM64 -e %REBUILD% %BUILDTEST%
 if errorlevel 1 exit /B %ERRORLEVEL%
 
+if /I "%IncludeFreethreaded%"=="true" (
+    rem Cannot "exit /B" inside an if block because %ERRORLEVEL% will be wrong.
+    rem We just skip everything after the first "errorlevel 1" and then exit after
+    if defined BUILDX86 call "%PCBUILD%build.bat" -p Win32 -d -e %REBUILD% %BUILDTEST% --disable-gil
+    if not errorlevel 1 if defined BUILDX86 call "%PCBUILD%build.bat" -p Win32 -e %REBUILD% %BUILDTEST% --disable-gil
+
+    if not errorlevel 1 if defined BUILDX64 call "%PCBUILD%build.bat" -p x64 -d -e %REBUILD% %BUILDTEST% --disable-gil
+    if not errorlevel 1 if defined BUILDX64 call "%PCBUILD%build.bat" -p x64 -e %REBUILD% %BUILDTEST% --disable-gil
+
+    if not errorlevel 1 if defined BUILDARM64 call "%PCBUILD%build.bat" -p ARM64 -d -e %REBUILD% %BUILDTEST% --disable-gil
+    if not errorlevel 1 if defined BUILDARM64  call "%PCBUILD%build.bat" -p ARM64 -e %REBUILD% %BUILDTEST% --disable-gil
+)
+if errorlevel 1 exit /B %ERRORLEVEL%
+
 if defined BUILDDOC call "%PCBUILD%..\Doc\make.bat" html
 if errorlevel 1 exit /B %ERRORLEVEL%
 
index d1b0f5bd9c1d4af4cf82a34daa2f16df4e97a6e3..471d37acc33b3d2be088196ed756db2712455311 100644 (file)
@@ -83,6 +83,7 @@
         <Checkbox Name="CompileAll" X="185" Y="151" Width="-11" Height="24" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.PrecompileLabel)</Checkbox>
         <Checkbox Name="Include_symbols" X="185" Y="176" Width="-11" Height="24" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.Include_symbolsLabel)</Checkbox>
         <Checkbox Name="Include_debug" X="185" Y="201" Width="-11" Height="24" TabStop="yes" FontId="3" HideWhenDisabled="no">#(loc.Include_debugLabel)</Checkbox>
+        <Checkbox Name="Include_freethreaded" X="185" Y="226" Width="-11" Height="24" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.Include_freethreadedLabel)</Checkbox>
 
         <Text X="185" Y="256" Width="-11" Height="17" FontId="3">#(loc.CustomLocationLabel)</Text>
         <Editbox Name="TargetDir" X="185" Y="277" Width="-101" Height="27" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
index 6f8befba3a252327806349c964e565ae6ed8820e..1540f050159a5481111d9c35fd878aa07ac5c8fe 100644 (file)
@@ -91,6 +91,7 @@ Select Customize to review current options.</String>
   <String Id="PrecompileLabel">&amp;Precompile standard library</String>
   <String Id="Include_symbolsLabel">Download debugging &amp;symbols</String>
   <String Id="Include_debugLabel">Download debu&amp;g binaries (requires VS 2017 or later)</String>
+  <String Id="Include_freethreadedLabel">Download &amp;free-threaded binaries (experimental)</String>
   
   <String Id="ProgressHeader">[ActionLikeInstallation] Progress</String>
   <String Id="ProgressLabel">[ActionLikeInstalling]:</String>
index 9c7410fe514d19cd22e8334927aeb50e937c8b7b..cb3effb4434843a2fabbc592dedfe40061db466b 100644 (file)
@@ -72,6 +72,7 @@
         <Package Include="..\tcltk\tcltk*.wixproj" />
         <Package Include="..\test\test*.wixproj" />
         <Package Include="..\ucrt\ucrt*.wixproj" Condition="$(Platform) != 'ARM64'" />
+        <Package Include="..\freethreaded\freethreaded*.wixproj" Condition="$(IncludeFreethreaded) == 'true'" />
     </ItemGroup>
 
     <PropertyGroup>
index 8b12baae31105e215483f06869e7d64c1f9db27e..9b4f072152d5c00a6ac8cefa83fc18a6915b122f 100644 (file)
     <?endif ?>
     <Variable Name="Include_symbols" Value="0" bal:Overridable="yes" />
     <Variable Name="Include_debug" Value="0" bal:Overridable="yes" />
+    <?if $(var.IncludeFreethreaded)~="true" ?>
+    <Variable Name="Include_freethreaded" Value="0" bal:Overridable="yes" />
+    <Variable Name="Include_freethreadedState" Value="enabled" />
+    <?else ?>
+    <Variable Name="Include_freethreaded" Value="0" />
+    <Variable Name="Include_freethreadedState" Value="disable" />
+    <?endif ?>
 
     <Variable Name="LauncherOnly" Value="0" bal:Overridable="yes" />
     <Variable Name="DetectedLauncher" Value="0" />
       <PackageGroupRef Id="exe" />
       <PackageGroupRef Id="dev" />
       <PackageGroupRef Id="lib" />
+      <?if $(var.IncludeFreethreaded)~="true" ?>
+      <PackageGroupRef Id="freethreaded" />
+      <?endif ?>
       <PackageGroupRef Id="test" />
       <PackageGroupRef Id="doc" />
       <PackageGroupRef Id="tcltk" />
diff --git a/Tools/msi/bundle/packagegroups/freethreaded.wxs b/Tools/msi/bundle/packagegroups/freethreaded.wxs
new file mode 100644 (file)
index 0000000..121ca34
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+    <Fragment>
+        <PackageGroup Id="freethreaded">
+            <MsiPackage Id="freethreaded_AllUsers"
+                        SourceFile="freethreaded.msi"
+                        Compressed="no"
+                        DownloadUrl="$(var.DownloadUrl)"
+                        ForcePerMachine="yes"
+                        InstallCondition="InstallAllUsers and (Include_freethreaded) and not LauncherOnly">
+                <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+                <MsiProperty Name="OPTIONALFEATURESREGISTRYKEY" Value="[OptionalFeaturesRegistryKey]" />
+            </MsiPackage>
+            <MsiPackage Id="freethreaded_AllUsers_pdb"
+                        SourceFile="freethreaded_pdb.msi"
+                        Compressed="no"
+                        DownloadUrl="$(var.DownloadUrl)"
+                        ForcePerMachine="yes"
+                        InstallCondition="InstallAllUsers and (Include_freethreaded) and Include_symbols and not LauncherOnly">
+                <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+                <MsiProperty Name="OPTIONALFEATURESREGISTRYKEY" Value="[OptionalFeaturesRegistryKey]" />
+            </MsiPackage>
+            <MsiPackage Id="freethreaded_AllUsers_d"
+                        SourceFile="freethreaded_d.msi"
+                        Compressed="no"
+                        DownloadUrl="$(var.DownloadUrl)"
+                        ForcePerMachine="yes"
+                        InstallCondition="InstallAllUsers and (Include_freethreaded) and Include_debug and not LauncherOnly">
+                <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+                <MsiProperty Name="OPTIONALFEATURESREGISTRYKEY" Value="[OptionalFeaturesRegistryKey]" />
+            </MsiPackage>
+
+            <MsiPackage Id="freethreaded_JustForMe"
+                        SourceFile="freethreaded.msi"
+                        Compressed="no"
+                        DownloadUrl="$(var.DownloadUrl)"
+                        ForcePerMachine="no"
+                        InstallCondition="not InstallAllUsers and (Include_freethreaded) and not LauncherOnly">
+                <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+                <MsiProperty Name="OPTIONALFEATURESREGISTRYKEY" Value="[OptionalFeaturesRegistryKey]" />
+            </MsiPackage>
+            <MsiPackage Id="freethreaded_JustForMe_pdb"
+                        SourceFile="freethreaded_pdb.msi"
+                        Compressed="no"
+                        DownloadUrl="$(var.DownloadUrl)"
+                        ForcePerMachine="no"
+                        InstallCondition="not InstallAllUsers and (Include_freethreaded) and Include_symbols and not LauncherOnly">
+                <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+                <MsiProperty Name="OPTIONALFEATURESREGISTRYKEY" Value="[OptionalFeaturesRegistryKey]" />
+            </MsiPackage>
+            <MsiPackage Id="freethreaded_JustForMe_d"
+                        SourceFile="freethreaded_d.msi"
+                        Compressed="no"
+                        DownloadUrl="$(var.DownloadUrl)"
+                        ForcePerMachine="no"
+                        InstallCondition="not InstallAllUsers and (Include_freethreaded) and Include_debug and not LauncherOnly">
+                <MsiProperty Name="TARGETDIR" Value="[TargetDir]" />
+                <MsiProperty Name="OPTIONALFEATURESREGISTRYKEY" Value="[OptionalFeaturesRegistryKey]" />
+            </MsiPackage>
+        </PackageGroup>
+    </Fragment>
+</Wix>
\ No newline at end of file
diff --git a/Tools/msi/freethreaded/freethreaded.wixproj b/Tools/msi/freethreaded/freethreaded.wixproj
new file mode 100644 (file)
index 0000000..0b4bd05
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{1B4502D5-B627-4F50-ABEA-4CC5A8E88265}</ProjectGuid>
+        <SchemaVersion>2.0</SchemaVersion>
+        <OutputName>freethreaded</OutputName>
+        <OutputType>Package</OutputType>
+    </PropertyGroup>
+    <Import Project="..\msi.props" />
+    <ItemGroup>
+        <Compile Include="freethreaded.wxs" />
+        <Compile Include="freethreaded_files.wxs" />
+    </ItemGroup>
+    <ItemGroup>
+        <EmbeddedResource Include="*.wxl" />
+        <WxlTemplate Include="*.wxl_template" />
+    </ItemGroup>
+
+    <Import Project="..\msi.targets" />
+</Project>
\ No newline at end of file
diff --git a/Tools/msi/freethreaded/freethreaded.wxs b/Tools/msi/freethreaded/freethreaded.wxs
new file mode 100644 (file)
index 0000000..063aa28
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+    <Product Id="*" Language="!(loc.LCID)" Name="!(loc.Title)" Version="$(var.Version)" Manufacturer="!(loc.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
+        <Package InstallerVersion="500" Compressed="yes" InstallScope="perUser" />
+        <MediaTemplate EmbedCab="yes" CompressionLevel="high" />
+
+        <PropertyRef Id="UpgradeTable" />
+        <PropertyRef Id="REGISTRYKEY" />
+
+        <Feature Id="DefaultFeature" AllowAdvertise="no" Title="!(loc.Title)" Description="!(loc.Description)">
+            <ComponentGroupRef Id="freethreaded_reg" Primary="yes" />
+            <ComponentGroupRef Id="freethreaded_exe" />
+            <ComponentGroupRef Id="freethreaded_lib_files" />
+            <ComponentGroupRef Id="freethreaded_lib_extensions" />
+            <ComponentRef Id="OptionalFeature" />
+        </Feature>
+    </Product>
+</Wix>
diff --git a/Tools/msi/freethreaded/freethreaded_d.wixproj b/Tools/msi/freethreaded/freethreaded_d.wixproj
new file mode 100644 (file)
index 0000000..e1563d4
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{D3677DCF-098A-4398-9FA5-8E74AC37E0DF}</ProjectGuid>
+        <SchemaVersion>2.0</SchemaVersion>
+        <OutputName>freethreaded_d</OutputName>
+        <OutputType>Package</OutputType>
+    </PropertyGroup>
+    <Import Project="..\msi.props" />
+    <ItemGroup>
+        <Compile Include="freethreaded_d.wxs" />
+        <Compile Include="freethreaded_files.wxs" />
+    </ItemGroup>
+    <ItemGroup>
+        <EmbeddedResource Include="*.wxl" />
+        <WxlTemplate Include="*.wxl_template" />
+    </ItemGroup>
+
+    <Import Project="..\msi.targets" />
+</Project>
\ No newline at end of file
diff --git a/Tools/msi/freethreaded/freethreaded_d.wxs b/Tools/msi/freethreaded/freethreaded_d.wxs
new file mode 100644 (file)
index 0000000..cddf22a
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+    <Product Id="*" Language="!(loc.LCID)" Name="!(loc.Title_d)" Version="$(var.Version)" Manufacturer="!(loc.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
+        <Package InstallerVersion="500" Compressed="yes" InstallScope="perUser" />
+        <MediaTemplate EmbedCab="yes" CompressionLevel="high" />
+        
+        <PropertyRef Id="UpgradeTable" />
+        
+        <Feature Id="DebugBinaries" AllowAdvertise="no" Title="!(loc.Title_d)" Description="!(loc.Description_d)">
+            <ComponentGroupRef Id="freethreaded_dll_d" />
+            <ComponentGroupRef Id="freethreaded_exe_d" />
+            <ComponentGroupRef Id="freethreaded_lib_extensions_d" />
+            <ComponentRef Id="OptionalFeature" />
+        </Feature>
+    </Product>
+</Wix>
diff --git a/Tools/msi/freethreaded/freethreaded_en-US.wxl_template b/Tools/msi/freethreaded/freethreaded_en-US.wxl_template
new file mode 100644 (file)
index 0000000..b9747eb
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
+    <String Id="Descriptor">Freethreaded Interpreter</String>
+    <String Id="ShortDescriptor">freethreaded</String>
+    <String Id="ShortcutName">Python {{ShortVersion}} ({{Bitness}}, freethreaded)</String>
+    <String Id="ShortcutDescription">Launches the !(loc.ProductName) freethreaded interpreter.</String>
+    <String Id="SupportUrl">https://www.python.org/</String>
+</WixLocalization>
diff --git a/Tools/msi/freethreaded/freethreaded_files.wxs b/Tools/msi/freethreaded/freethreaded_files.wxs
new file mode 100644 (file)
index 0000000..adaf63c
--- /dev/null
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+    <Fragment>
+        <Property Id="FREETHREADED_REGISTRYKEY" Value="Software\Python\PythonCore\$(var.ShortVersion)t$(var.PyArchExt)$(var.PyTestExt)" />
+
+        <ComponentGroup Id="freethreaded_reg">
+            <Component Id="PythonRegistration" Directory="InstallDirectory" Guid="$(var.PythonRegComponentGuid)">
+                <RegistryKey Root="HKMU" Key="[FREETHREADED_REGISTRYKEY]">
+                    <RegistryValue Name="DisplayName" Type="string" Value="!(loc.ShortcutName)" KeyPath="yes" />
+                    <RegistryValue Name="SupportUrl" Type="string" Value="!(loc.SupportUrl)" KeyPath="no" />
+                    <RegistryValue Name="Version" Type="string" Value="$(var.LongVersion)" KeyPath="no" />
+                    <RegistryValue Name="SysVersion" Type="string" Value="$(var.ShortVersion)" KeyPath="no" />
+                    <RegistryValue Name="SysArchitecture" Type="string" Value="$(var.PlatformArchitecture)" KeyPath="no" />
+                </RegistryKey>
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    <Fragment>
+        <DirectoryRef Id="Lib">
+            <Directory Id="Lib_venv__freethreaded" Name="venv">
+                <Directory Id="Lib_venv_scripts__freethreaded" Name="scripts">
+                    <Directory Id="Lib_venv_scripts_nt__freethreaded" Name="nt" />
+                </Directory>
+            </Directory>
+        </DirectoryRef>
+    </Fragment>
+    <Fragment>
+        <PropertyRef Id="FREETHREADED_REGISTRYKEY" />
+        
+        <ComponentGroup Id="freethreaded_exe">
+            <Component Id="freethreaded_python.exe" Directory="InstallDirectory" Guid="$(var.FreethreadedPythonExeComponentGuid)">
+                <File Name="python$(var.ShortVersion)t.exe" KeyPath="yes" />
+                
+                <RegistryKey Root="HKMU" Key="[FREETHREADED_REGISTRYKEY]">
+                    <RegistryValue Key="InstallPath" Type="string" Value="[InstallDirectory]" KeyPath="no" />
+                    <RegistryValue Key="InstallPath" Name="ExecutablePath" Type="string" Value="[#python$(var.ShortVersion)t.exe]" KeyPath="no" />
+                </RegistryKey>
+            </Component>
+            <Component Id="freethreaded_pythonw.exe" Directory="InstallDirectory" Guid="$(var.FreethreadedPythonwExeComponentGuid)">
+                <File Name="pythonw$(var.ShortVersion)t.exe" KeyPath="yes" />
+                <RegistryKey Root="HKMU" Key="[FREETHREADED_REGISTRYKEY]">
+                    <RegistryValue Key="InstallPath" Name="WindowedExecutablePath" Type="string" Value="[#pythonw$(var.ShortVersion)t.exe]" KeyPath="no" />
+                </RegistryKey>
+            </Component>
+            <Component Id="freethreaded_python_stable.dll" Directory="InstallDirectory" Guid="*">
+                <File Id="freethreaded_python_stable.dll" Name="python$(var.MajorVersionNumber)t.dll" KeyPath="yes" />
+            </Component>
+            <Component Id="freethreaded_python.dll" Directory="InstallDirectory" Guid="*">
+                <File Id="freethreaded_python.dll" Name="python$(var.MajorVersionNumber)$(var.MinorVersionNumber)t.dll" KeyPath="yes" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    <Fragment>
+        <ComponentGroup Id="freethreaded_symbols">
+            <Component Id="freethreaded_python_dll.pdb" Directory="InstallDirectory" Guid="*">
+                <File Name="python$(var.MajorVersionNumber)$(var.MinorVersionNumber)t.pdb" KeyPath="yes" />
+            </Component>
+            <Component Id="freethreaded_python.pdb" Directory="InstallDirectory" Guid="*">
+                <File Name="python$(var.ShortVersion)t.pdb" />
+            </Component>
+            <Component Id="freethreaded_pythonw.pdb" Directory="InstallDirectory" Guid="*">
+                <File Name="pythonw$(var.ShortVersion)t.pdb" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    <Fragment>
+        <ComponentGroup Id="freethreaded_dll_d">
+            <Component Id="freethreaded_python_stable_d.dll" Directory="InstallDirectory" Guid="*">
+                <File Id="freethreaded_python_stable_d.dll" Name="python$(var.MajorVersionNumber)t_d.dll" KeyPath="yes" />
+            </Component>
+            <Component Id="freethreaded_python_d.dll" Directory="InstallDirectory" Guid="*">
+                <File Id="freethreaded_python_d.dll" Name="python$(var.MajorVersionNumber)$(var.MinorVersionNumber)_d.dll" KeyPath="yes" />
+                <File Id="freethreaded_python_d.pdb" Name="python$(var.MajorVersionNumber)$(var.MinorVersionNumber)_d.pdb" KeyPath="no" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    <Fragment>
+        <ComponentGroup Id="freethreaded_exe_d">
+            <Component Id="freethreaded_python_d.exe" Directory="InstallDirectory" Guid="*">
+                <File Name="python$(var.ShortVersion)t_d.exe" />
+            </Component>
+            <Component Id="freethreaded_python_d.pdb" Directory="InstallDirectory" Guid="*">
+                <File Name="python$(var.ShortVersion)t_d.pdb" />
+            </Component>
+            <Component Id="freethreaded_pythonw_d.exe" Directory="InstallDirectory" Guid="*">
+                <File Name="pythonw$(var.ShortVersion)t_d.exe" />
+            </Component>
+            <Component Id="freethreaded_pythonw_d.pdb" Directory="InstallDirectory" Guid="*">
+                <File Name="pythonw$(var.ShortVersion)t_d.pdb" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+
+    <?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_uuid;_wmi;_zoneinfo;_testcapi;_ctypes_test;_testbuffer;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testinternalcapi;_testclinic;_testclinic_limited ?>
+    <Fragment>
+        <DirectoryRef Id="Lib_venv_scripts_nt__freethreaded" />
+
+        <ComponentGroup Id="freethreaded_lib_extensions">
+            <?foreach ext in $(var.exts)?>
+        
+            <Component Id="freethreaded_$(var.ext).pyd" Directory="DLLs" Guid="*">
+                <File Name="$(var.ext)$(var.FreethreadedPydTag).pyd" KeyPath="yes" />
+            </Component>
+            
+            <?endforeach ?>
+            
+            <Component Id="venvlaunchert.exe" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="venvlaunchert.exe" KeyPath="yes" />
+            </Component>
+            <Component Id="venvwlaunchert.exe" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="venvwlaunchert.exe" KeyPath="yes" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    
+    <Fragment>
+        <DirectoryRef Id="Lib_venv_scripts_nt__freethreaded" />
+
+        <ComponentGroup Id="freethreaded_lib_extensions_symbols">
+            <?foreach ext in $(var.exts)?>
+            
+            <Component Id="freethreaded_$(var.ext).pdb" Directory="DLLs" Guid="*">
+                <File Name="$(var.ext)$(var.FreethreadedPydTag).pdb" />
+            </Component>
+            
+            <?endforeach ?>
+            
+            <Component Id="venvlaunchert.pdb" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="venvlaunchert.pdb" KeyPath="yes" />
+            </Component>
+            <Component Id="venvwlaunchert.pdb" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="venvwlaunchert.pdb" KeyPath="yes" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    
+    <Fragment>
+        <ComponentGroup Id="freethreaded_lib_extensions_d">
+            <?foreach ext in $(var.exts)?>
+            
+            <Component Id="freethreaded_$(var.ext)_d.pyd" Directory="DLLs" Guid="*">
+                <File Name="$(var.ext)_d$(var.FreethreadedPydTag).pyd" />
+            </Component>
+            <Component Id="freethreaded_$(var.ext)_d.pdb" Directory="DLLs" Guid="*">
+                <File Name="$(var.ext)_d$(var.FreethreadedPydTag).pdb" />
+            </Component>
+            
+            <?endforeach ?>
+            
+            <Component Id="venvlauncher_d.exe" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="python_d.exe" Source="venvlauncher_d.exe" KeyPath="yes" />
+            </Component>
+            <Component Id="venvwlauncher_d.exe" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="pythonw_d.exe" Source="venvwlauncher_d.exe" KeyPath="yes" />
+            </Component>
+            <Component Id="venvlaunchert_d.pdb" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="venvlaunchert_d.pdb" KeyPath="yes" />
+            </Component>
+            <Component Id="venvwlaunchert_d.pdb" Directory="Lib_venv_scripts_nt__freethreaded" Guid="*">
+                <File Name="venvwlaunchert_d.pdb" KeyPath="yes" />
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+    <Fragment>
+        <PropertyRef Id="FREETHREADED_REGISTRYKEY" />
+        
+        <ComponentGroup Id="freethreaded_lib_files">
+            <Component Id="PythonPathRegistry" Directory="Lib" Guid="*">
+                <RegistryKey Root="HKMU" Key="[FREETHREADED_REGISTRYKEY]">
+                    <RegistryValue Key="PythonPath" Type="string" Value="[Lib];[DLLs]" />
+                </RegistryKey>
+            </Component>
+        </ComponentGroup>
+    </Fragment>
+</Wix>
diff --git a/Tools/msi/freethreaded/freethreaded_pdb.wixproj b/Tools/msi/freethreaded/freethreaded_pdb.wixproj
new file mode 100644 (file)
index 0000000..789a4f5
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{E98E7539-64E7-4DCE-AACD-01E3ADE40EFD}</ProjectGuid>
+        <SchemaVersion>2.0</SchemaVersion>
+        <OutputName>freethreaded_pdb</OutputName>
+        <OutputType>Package</OutputType>
+    </PropertyGroup>
+    <Import Project="..\msi.props" />
+    <ItemGroup>
+        <Compile Include="freethreaded_pdb.wxs" />
+        <Compile Include="freethreaded_files.wxs" />
+    </ItemGroup>
+    <ItemGroup>
+        <EmbeddedResource Include="*.wxl" />
+        <WxlTemplate Include="*.wxl_template" />
+    </ItemGroup>
+
+    <Import Project="..\msi.targets" />
+</Project>
\ No newline at end of file
diff --git a/Tools/msi/freethreaded/freethreaded_pdb.wxs b/Tools/msi/freethreaded/freethreaded_pdb.wxs
new file mode 100644 (file)
index 0000000..302ac41
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+    <Product Id="*" Language="!(loc.LCID)" Name="!(loc.TitlePdb)" Version="$(var.Version)" Manufacturer="!(loc.Manufacturer)" UpgradeCode="$(var.UpgradeCode)">
+        <Package InstallerVersion="500" Compressed="yes" InstallScope="perUser" />
+        <MediaTemplate EmbedCab="yes" CompressionLevel="high" />
+        
+        <PropertyRef Id="UpgradeTable" />
+        
+        <Feature Id="Symbols" AllowAdvertise="no" Title="!(loc.TitlePdb)" Description="!(loc.DescriptionPdb)">
+            <ComponentGroupRef Id="freethreaded_symbols" />
+            <ComponentGroupRef Id="freethreaded_lib_extensions_symbols" />
+            <ComponentRef Id="OptionalFeature" />
+        </Feature>
+    </Product>
+</Wix>
index a82cad596d47a6444d7bac161a19dabf04d65fc8..b8e16b5fe238a097664ec5a8555c16566cd40c3f 100644 (file)
                 <File Name="libffi-8.dll" KeyPath="yes" />
             </Component>
             <Component Id="venvlauncher.exe" Directory="Lib_venv_scripts_nt" Guid="*">
-                <File Name="python.exe" Source="venvlauncher.exe" KeyPath="yes" />
+                <File Name="venvlauncher.exe" KeyPath="yes" />
             </Component>
             <Component Id="venvwlauncher.exe" Directory="Lib_venv_scripts_nt" Guid="*">
-                <File Name="pythonw.exe" Source="venvwlauncher.exe" KeyPath="yes" />
+                <File Name="venvwlauncher.exe" KeyPath="yes" />
             </Component>
         </ComponentGroup>
     </Fragment>
                 <File Name="libssl$(var.ssltag).pdb" KeyPath="yes" />
             </Component>
             <Component Id="venvlauncher.pdb" Directory="Lib_venv_scripts_nt__pdbs" Guid="*">
-                <File Name="python.pdb" Source="venvlauncher.pdb" KeyPath="yes" />
+                <File Name="venvlauncher.pdb" KeyPath="yes" />
             </Component>
             <Component Id="venvwlauncher.pdb" Directory="Lib_venv_scripts_nt__pdbs" Guid="*">
-                <File Name="pythonw.pdb" Source="venvwlauncher.pdb" KeyPath="yes" />
+                <File Name="venvwlauncher.pdb" KeyPath="yes" />
             </Component>
         </ComponentGroup>
     </Fragment>
                 <File Name="sqlite3_d.pdb" KeyPath="yes" />
             </Component>
             <Component Id="venvlauncher_d.exe" Directory="Lib_venv_scripts_nt__d" Guid="*">
-                <File Name="python_d.exe" Source="venvlauncher_d.exe" KeyPath="yes" />
+                <File Name="venvlauncher_d.exe" KeyPath="yes" />
             </Component>
             <Component Id="venvwlauncher_d.exe" Directory="Lib_venv_scripts_nt__d" Guid="*">
-                <File Name="pythonw_d.exe" Source="venvwlauncher_d.exe" KeyPath="yes" />
+                <File Name="venvwlauncher_d.exe" KeyPath="yes" />
             </Component>
         </ComponentGroup>
     </Fragment>
index cfb3ca9e76e24c5828be71a790684af9879e47d2..372c4823bce07f30d33d5481a708381ec40515a1 100644 (file)
         This URI is used to generate the various GUIDs used by the installer.
         Installers built with the same URI will upgrade each other or block
         when attempting to downgrade.
-        
+
         By default, this is the local computer name, which will produce
         installers that do not interfere with other installers. Products
         that intend to bundle Python should rebuild these modules with their
         own URI to avoid conflicting with the official releases.
-        
+
         The official releases use "https://www.python.org/$(ArchName)"
-        
+
         This is not the same as the DownloadUrl property used in the bundle
         projects.
         -->
@@ -39,7 +39,7 @@
         <ReleaseUri Condition="!$(ReleaseUri.EndsWith(`/`))">$(ReleaseUri)/</ReleaseUri>
     </PropertyGroup>
 
-    
+
     <ItemGroup>
         <Compile Include="$(MSBuildThisFileDirectory)common.wxs" />
         <WxlTemplate Include="$(MSBuildThisFileDirectory)\*.wxl_template" Condition="$(IgnoreCommonWxlTemplates) != 'true'" />
 
         <InstallerVersion>$(MajorVersionNumber).$(MinorVersionNumber).$(Field3Value).0</InstallerVersion>
     </PropertyGroup>
-    
+
+    <PropertyGroup>
+        <IncludeFreethreaded Condition="$(IncludeFreethreaded) != 'true'">false</IncludeFreethreaded>
+    </PropertyGroup>
+
     <PropertyGroup Condition="!$(BuildForRelease)">
         <RevisionNumber Condition="'$(RevisionNumber)' == ''">$([System.Math]::Floor($([System.DateTime]::Now.Subtract($([System.DateTime]::new(2001, 1, 1))).TotalDays)))</RevisionNumber>
         <PythonVersion>$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber)dev$(RevisionNumber)</PythonVersion>
         <InstallerVersion>$(MajorVersionNumber).$(MinorVersionNumber).$(RevisionNumber).0</InstallerVersion>
     </PropertyGroup>
-    
+
     <PropertyGroup>
         <Bitness>32-bit</Bitness>
         <Bitness Condition="$(Platform) == 'x64'">64-bit</Bitness>
             PyDebugExt=$(PyDebugExt);
             PyArchExt=$(PyArchExt);
             PyTestExt=$(PyTestExt);
+            PydTag=$(PydTag);
+            FreethreadedPydTag=$(FreethreadedPydTag);
             OptionalFeatureName=$(OutputName);
             ssltag=$(OpenSSLDLLSuffix);
             Suffix32=$(PyArchExt);
+            IncludeFreethreaded=$(IncludeFreethreaded);
         </DefineConstants>
         <DefineConstants Condition="'$(CRTRedist)' != ''">
             $(DefineConstants);CRTRedist=$(CRTRedist);
         <Error Text="Platform '$(Platform)' is not supported. Use 'x86', 'x64' or 'ARM64'."
                Condition="$(Platform) != 'x86' and $(Platform) != 'x64' and $(Platform) != 'ARM64'" />
     </Target>
-    
+
     <ItemGroup>
         <_Uuid Include="CoreUpgradeCode">
             <Uri>upgradecode</Uri>
         <_Uuid Include="PythonRegComponentGuid">
             <Uri>registry/$(OutputName)</Uri>
         </_Uuid>
+        <_Uuid Include="FreethreadedPythonExeComponentGuid" Condition="$(IncludeFreethreaded)">
+            <Uri>freethreaded/python.exe</Uri>
+        </_Uuid>
+        <_Uuid Include="FreethreadedPythonwExeComponentGuid" Condition="$(IncludeFreethreaded)">
+            <Uri>freethreaded/pythonw.exe</Uri>
+        </_Uuid>
     </ItemGroup>
     <Target Name="_GenerateGuids"
             AfterTargets="PrepareForBuild"
             <_Uuids>@(_Uuid->'("%(Identity)", "$(MajorVersionNumber).$(MinorVersionNumber)/%(Uri)")',',')</_Uuids>
             <_GenerateCommand>import uuid; print('\n'.join('{}={}'.format(i, uuid.uuid5(uuid.UUID('c8d9733e-a70c-43ff-ab0c-e26456f11083'), '$(ReleaseUri.Replace(`{arch}`, `$(ArchName)`))' + j)) for i,j in [$(_Uuids.Replace(`"`,`'`))]))</_GenerateCommand>
         </PropertyGroup>
-        
+
         <Exec Command='$(PythonForBuild) -c "$(_GenerateCommand)" &gt; "$(IntermediateOutputPath)$(OutputName)guids.txt"'
               WorkingDirectory="$(MSBuildThisFileDirectory)"
               IgnoreExitCode="false" />
-        
+
         <ReadLinesFromFile File="$(IntermediateOutputPath)$(OutputName)guids.txt">
             <Output TaskParameter="Lines" ItemName="_UuidValue" />
         </ReadLinesFromFile>
-        
+
         <PropertyGroup>
             <DefineConstants>$(DefineConstants);@(_UuidValue,';');</DefineConstants>
         </PropertyGroup>