Patchwork D11081: run-test: rework the redirection script for python on windows

login
register
mail settings
Submitter phabricator
Date July 11, 2021, 9:38 p.m.
Message ID <differential-rev-PHID-DREV-4q5vf22cgyuy7fv6ku5m-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/49397/
State Superseded
Headers show

Comments

phabricator - July 11, 2021, 9:38 p.m.
marmoute created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This should get use something something overall simpler and clearer. Especially,
  we now have a `python.exe` script (even if by default Windows has
  `C:\Python27\python.exe` hardcoded in the register to open .py file)

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D11081

AFFECTED FILES
  tests/run-tests.py

CHANGE DETAILS




To: marmoute, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -3572,16 +3572,18 @@ 
     def _usecorrectpython(self):
         """Configure the environment to use the appropriate Python in tests."""
         # Tests must use the same interpreter as us or bad things will happen.
-        if sys.platform == 'win32':
+        if WINDOWS and PYTHON3:
+            pyexe_names = [b'python', b'python3', b'python.exe']
+        elif WINDOWS:
             pyexe_names = [b'python', b'python.exe']
-        elif sys.version_info[0] < 3:
+        elif PYTHON3:
+            pyexe_names = [b'python', b'python3']
+        else:
             pyexe_names = [b'python', b'python2']
-        else:
-            pyexe_names = [b'python', b'python3']
 
         # os.symlink() is a thing with py3 on Windows, but it requires
         # Administrator rights.
-        if getattr(os, 'symlink', None) and not WINDOWS:
+        if not WINDOWS and getattr(os, 'symlink', None):
             msg = "# Making python executable in test path a symlink to '%s'"
             msg %= sysexecutable
             vlog(msg)
@@ -3602,57 +3604,50 @@ 
                         # child processes may race, which is harmless
                         if err.errno != errno.EEXIST:
                             raise
+        elif WINDOWS and not os.getenv('MSYSTEM'):
+            raise AssertionError('cannot run test on Windows without MSYSTEM')
         else:
-            # Windows doesn't have `python3.exe`, and MSYS cannot understand the
-            # reparse point with that name provided by Microsoft.  Create a
-            # simple script on PATH with that name that delegates to the py3
-            # launcher so the shebang lines work.
-            if os.getenv('MSYSTEM'):
-                py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
-                with open(py3exe_name, 'wb') as f:
-                    f.write(b'#!/bin/sh\n')
-                    f.write(b'py -3.%d "$@"\n' % sys.version_info[1])
-
-                pyexe_name = os.path.join(self._custom_bin_dir, b'python')
-                with open(pyexe_name, 'wb') as f:
-                    f.write(b'#!/bin/sh\n')
-                    f.write(b'py -%d.%d "$@"\n' % sys.version_info[0:2])
-
-            exedir, exename = os.path.split(sysexecutable)
+            # Generate explicit file instead of symlink
+            #
+            # This is especially important as Windows doesn't have
+            # `python3.exe`, and MSYS cannot understand the reparse point with
+            # that name provided by Microsoft.  Create a simple script on PATH
+            # with that name that delegates to the py3 launcher so the shebang
+            # lines work.
+            esc_executable = _sys2bytes(shellquote(sysexecutable))
             for pyexename in pyexe_names:
-                msg = "# Modifying search path to find %s as %s in '%s'"
-                msg %= (exename, pyexename, exedir)
-                vlog(msg)
-            path = os.environ['PATH'].split(os.pathsep)
-            while exedir in path:
-                path.remove(exedir)
-
-            # Binaries installed by pip into the user area like pylint.exe may
-            # not be in PATH by default.
-            extra_paths = [exedir]
-            vi = sys.version_info
-            appdata = os.environ.get('APPDATA')
-            if appdata is not None:
-                scripts_dir = os.path.join(
-                    appdata,
-                    'Python',
-                    'Python%d%d' % (vi[0], vi[1]),
-                    'Scripts',
-                )
-
-                if vi.major == 2:
-                    scripts_dir = os.path.join(
-                        appdata,
-                        'Python',
-                        'Scripts',
-                    )
-
-                extra_paths.append(scripts_dir)
-
-            os.environ['PATH'] = os.pathsep.join(extra_paths + path)
-            for pyexename in pyexe_names:
-                if not self._findprogram(pyexename):
-                    print("WARNING: Cannot find %s in search path" % pyexename)
+                stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
+                with open(stub_exec_path, 'wb') as f:
+                    f.write(b'#!/bin/sh\n')
+                    f.write(b'%s "$@"\n' % esc_executable)
+
+            if WINDOWS:
+                if not PYTHON3:
+                    # lets try to build a valid python3 executable for the
+                    # scrip that requires it.
+                    py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
+                    with open(py3exe_name, 'wb') as f:
+                        f.write(b'#!/bin/sh\n')
+                        f.write(b'py -3 "$@"\n')
+
+                # adjust the path to make sur the main python finds it own dll
+                path = os.environ['PATH'].split(os.pathsep)
+                main_exec_dir = os.path.dirname(sysexecutable)
+                extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
+
+                # Binaries installed by pip into the user area like pylint.exe may
+                # not be in PATH by default.
+                appdata = os.environ.get('APPDATA')
+                vi = sys.version_info
+                if appdata is not None:
+                    python_dir = 'Python%d%d' % (vi[0], vi[1])
+                    scripts_path = [appdata, 'Python', python_dir, 'Scripts']
+                    if not PYTHON3:
+                        scripts_path = [appdata, 'Python', 'Scripts']
+                    scripts_dir = os.path.join(*scripts_path)
+                    extra_paths.append(scripts_dir)
+
+                os.environ['PATH'] = os.pathsep.join(extra_paths + path)
 
     def _use_correct_mercurial(self):
         target_exec = os.path.join(self._custom_bin_dir, b'hg')