Issue #15307: On OS X framework builds, fix sys.executable to point to the stub launcher executable rather than following the symlink to the interpreter executable. This solves several issues include allowing pyvenv --symlink to work on OS X and ensuring that IDLE and tests actually execute in 32-bit mode when using python3-32 with a 64-bit/32-bit universal build. (Patch by Ronald Oussoren) 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 @@ -152,17 +152,13 @@ """ 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)) 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 #include #include +#include extern char** environ; @@ -151,16 +152,18 @@ main(int argc, char **argv) { char* exec_path = get_python_path(); static char path[PATH_MAX * 2]; - static char real_path[PATH_MAX * 2]; int status; uint32_t size = PATH_MAX * 2; /* 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" + */ + setenv("__PYVENV_LAUNCHER__", 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; + 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 @@ -628,7 +635,29 @@ wchar_t *env_cfg = L"pyvenv.cfg"; FILE * env_file = NULL; +#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__"); + } + wcsncpy(tmpbuffer, wbuf, MAXPATHLEN); + wcsncpy(progpath, wbuf, MAXPATHLEN); + PyMem_Free(wbuf); + } + else { + wcscpy(tmpbuffer, argv0_path); + } + +#else wcscpy(tmpbuffer, argv0_path); +#endif joinpath(tmpbuffer, env_cfg); env_file = _Py_wfopen(tmpbuffer, L"r"); if (env_file == NULL) {