[Python-checkins] cpython: Closes #15307: symlinks now work on OS X with framework Python builds. Patch

vinay.sajip python-checkins at python.org
Tue Jul 17 18:34:10 CEST 2012


http://hg.python.org/cpython/rev/b79d276041a8
changeset:   78160:b79d276041a8
user:        Vinay Sajip <vinay_sajip at yahoo.co.uk>
date:        Tue Jul 17 17:33:46 2012 +0100
summary:
  Closes #15307: symlinks now work on  OS X with framework Python builds. Patch by Ronald Oussoren.

files:
  Doc/library/venv.rst  |   2 +-
  Lib/test/test_venv.py |  52 ++++++++++++++++++++++++------
  Lib/venv/__init__.py  |  10 +-----
  Mac/Tools/pythonw.c   |  40 ++++++++++++++++++++++-
  Modules/getpath.c     |  16 +++++++--
  Modules/main.c        |  22 +++++++++++++
  6 files changed, 115 insertions(+), 27 deletions(-)


diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
--- a/Doc/library/venv.rst
+++ b/Doc/library/venv.rst
@@ -81,7 +81,7 @@
     * ``symlinks`` -- a Boolean value indicating whether to attempt to symlink the
       Python binary (and any necessary DLLs or other binaries,
       e.g. ``pythonw.exe``), rather than copying. Defaults to ``True`` on Linux and
-      Unix systems, but ``False`` on Windows and Mac OS X.
+      Unix systems, but ``False`` on Windows.
 
     * ``upgrade`` -- a Boolean value which, if True, will upgrade an existing
       environment with the running Python - for use when that Python has been
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -154,17 +154,47 @@
         """
         for usl in (False, True):
             builder = venv.EnvBuilder(clear=True, symlinks=usl)
-            if (usl and sys.platform == 'darwin' and
-                '__PYVENV_LAUNCHER__' in os.environ):
-                self.assertRaises(ValueError, builder.create, self.env_dir)
-            else:
-                builder.create(self.env_dir)
-                fn = self.get_env_file(self.bindir, self.exe)
-                # Don't test when False, because e.g. 'python' is always
-                # symlinked to 'python3.3' in the env, even when symlinking in
-                # general isn't wanted.
-                if usl:
-                    self.assertTrue(os.path.islink(fn))
+            builder.create(self.env_dir)
+            fn = self.get_env_file(self.bindir, self.exe)
+            # Don't test when False, because e.g. 'python' is always
+            # symlinked to 'python3.3' in the env, even when symlinking in
+            # general isn't wanted.
+            if usl:
+                self.assertTrue(os.path.islink(fn))
+
+    # If a venv is created from a source build and that venv is used to
+    # run the test, the pyvenv.cfg in the venv created in the test will
+    # point to the venv being used to run the test, and we lose the link
+    # to the source build - so Python can't initialise properly.
+    @unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
+                     'in a venv')
+    def test_executable(self):
+        """
+        Test that the sys.executable value is as expected.
+        """
+        shutil.rmtree(self.env_dir)
+        self.run_with_capture(venv.create, self.env_dir)
+        envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+        cmd = [envpy, '-c', 'import sys; print(sys.executable)']
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        self.assertEqual(out[:-1], envpy.encode())
+
+    @unittest.skipUnless(can_symlink(), 'Needs symlinks')
+    def test_executable_symlinks(self):
+        """
+        Test that the sys.executable value is as expected.
+        """
+        shutil.rmtree(self.env_dir)
+        builder = venv.EnvBuilder(clear=True, symlinks=True)
+        builder.create(self.env_dir)
+        envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
+        cmd = [envpy, '-c', 'import sys; print(sys.executable)']
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        self.assertEqual(out[:-1], envpy.encode())
 
 def test_main():
     run_unittest(BasicTest)
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -82,13 +82,6 @@
         :param env_dir: The target directory to create an environment in.
 
         """
-        if (self.symlinks and
-            sys.platform == 'darwin' and
-            sysconfig.get_config_var('PYTHONFRAMEWORK')):
-            # Symlinking the stub executable in an OSX framework build will
-            # result in a broken virtual environment.
-            raise ValueError(
-                'Symlinking is not supported on OSX framework Python.')
         env_dir = os.path.abspath(env_dir)
         context = self.ensure_directories(env_dir)
         self.create_configuration(context)
@@ -366,8 +359,7 @@
                             action='store_true', dest='system_site',
                             help='Give the virtual environment access to the '
                                  'system site-packages dir.')
-        if os.name == 'nt' or (sys.platform == 'darwin' and
-                               sysconfig.get_config_var('PYTHONFRAMEWORK')):
+        if os.name == 'nt':
             use_symlinks = False
         else:
             use_symlinks = True
diff --git a/Mac/Tools/pythonw.c b/Mac/Tools/pythonw.c
--- a/Mac/Tools/pythonw.c
+++ b/Mac/Tools/pythonw.c
@@ -28,6 +28,7 @@
 #include <dlfcn.h>
 #include <stdlib.h>
 #include <Python.h>
+#include <mach-o/dyld.h>
 
 
 extern char** environ;
@@ -158,9 +159,44 @@
     /* Set the original executable path in the environment. */
     status = _NSGetExecutablePath(path, &size);
     if (status == 0) {
-        if (realpath(path, real_path) != NULL) {
-            setenv("__PYVENV_LAUNCHER__", real_path, 1);
+        /*
+         * Note: don't call 'realpath', that will
+         * erase symlink information, and that
+         * breaks "pyvenv --symlink"
+         *
+         * It is nice to have the directory name
+         * as a cleaned up absolute path though,
+         * therefore call realpath on dirname(path)
+         */
+        char* slash = strrchr(path, '/');
+        if (slash) {
+            char  replaced;
+            replaced = slash[1];
+            slash[1] = 0;
+            if (realpath(path, real_path) == NULL) {
+                err(1, "realpath: %s", path);
+            }
+            slash[1] = replaced;
+            if (strlcat(real_path, slash, sizeof(real_path)) > sizeof(real_path)) {
+                errno = EINVAL;
+                err(1, "realpath: %s", path);
+            }
+
+        } else {
+            if (realpath(".", real_path) == NULL) {
+                err(1, "realpath: %s", path);
+            }
+            if (strlcat(real_path, "/", sizeof(real_path)) > sizeof(real_path)) {
+                errno = EINVAL;
+                err(1, "realpath: %s", path);
+            }
+            if (strlcat(real_path, path, sizeof(real_path)) > sizeof(real_path)) {
+                errno = EINVAL;
+                err(1, "realpath: %s", path);
+            }
         }
+
+        setenv("__PYVENV_LAUNCHER__", real_path, 1);
     }
 
     /*
diff --git a/Modules/getpath.c b/Modules/getpath.c
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -474,6 +474,7 @@
     wchar_t *defpath;
 #ifdef WITH_NEXT_FRAMEWORK
     NSModule pythonModule;
+    const char*    modPath;
 #endif
 #ifdef __APPLE__
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
@@ -568,8 +569,8 @@
     */
     pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize"));
     /* Use dylib functions to find out where the framework was loaded from */
-    buf = (wchar_t *)NSLibraryNameForModule(pythonModule);
-    if (buf != NULL) {
+    modPath = NSLibraryNameForModule(pythonModule);
+    if (modPath != NULL) {
         /* We're in a framework. */
         /* See if we might be in the build directory. The framework in the
         ** build directory is incomplete, it only has the .dylib and a few
@@ -578,7 +579,12 @@
         ** be running the interpreter in the build directory, so we use the
         ** build-directory-specific logic to find Lib and such.
         */
-        wcsncpy(argv0_path, buf, MAXPATHLEN);
+        wchar_t* wbuf = _Py_char2wchar(modPath, NULL);
+        if (wbuf == NULL) {
+            Py_FatalError("Cannot decode framework location");
+        }
+
+        wcsncpy(argv0_path, wbuf, MAXPATHLEN);
         reduce(argv0_path);
         joinpath(argv0_path, lib_python);
         joinpath(argv0_path, LANDMARK);
@@ -589,8 +595,9 @@
         }
         else {
             /* Use the location of the library as the progpath */
-            wcsncpy(argv0_path, buf, MAXPATHLEN);
+            wcsncpy(argv0_path, wbuf, MAXPATHLEN);
         }
+        PyMem_Free(wbuf);
     }
 #endif
 
@@ -629,6 +636,7 @@
         FILE * env_file = NULL;
 
         wcscpy(tmpbuffer, argv0_path);
+
         joinpath(tmpbuffer, env_cfg);
         env_file = _Py_wfopen(tmpbuffer, L"r");
         if (env_file == NULL) {
diff --git a/Modules/main.c b/Modules/main.c
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -616,7 +616,29 @@
         Py_SetProgramName(buffer);
         /* buffer is now handed off - do not free */
     } else {
+#ifdef WITH_NEXT_FRAMEWORK
+        char* pyvenv_launcher = getenv("__PYVENV_LAUNCHER__");
+
+        if (pyvenv_launcher && *pyvenv_launcher) {
+            /* Used by Mac/Tools/pythonw.c to forward
+             * the argv0 of the stub executable
+             */
+            wchar_t* wbuf = _Py_char2wchar(pyvenv_launcher, NULL);
+
+            if (wbuf == NULL) {
+                Py_FatalError("Cannot decode __PYVENV_LAUNCHER__");
+            }
+            Py_SetProgramName(wbuf);
+
+            /* Don't free wbuf, the argument to Py_SetProgramName
+             * must remain valid until the Py_Finalize is called.
+             */
+        } else {
+            Py_SetProgramName(argv[0]);
+        }
+#else
         Py_SetProgramName(argv[0]);
+#endif
     }
 #else
     Py_SetProgramName(argv[0]);

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list