Closes #15307: symlinks now work on OS X with framework Python builds. Patch by Ronald Oussoren.
This commit is contained in:
parent
11718620ef
commit
90db661b43
|
@ -81,7 +81,7 @@ creation according to their needs, the :class:`EnvBuilder` class.
|
||||||
* ``symlinks`` -- a Boolean value indicating whether to attempt to symlink the
|
* ``symlinks`` -- a Boolean value indicating whether to attempt to symlink the
|
||||||
Python binary (and any necessary DLLs or other binaries,
|
Python binary (and any necessary DLLs or other binaries,
|
||||||
e.g. ``pythonw.exe``), rather than copying. Defaults to ``True`` on Linux and
|
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
|
* ``upgrade`` -- a Boolean value which, if True, will upgrade an existing
|
||||||
environment with the running Python - for use when that Python has been
|
environment with the running Python - for use when that Python has been
|
||||||
|
|
|
@ -154,17 +154,47 @@ class BasicTest(BaseTest):
|
||||||
"""
|
"""
|
||||||
for usl in (False, True):
|
for usl in (False, True):
|
||||||
builder = venv.EnvBuilder(clear=True, symlinks=usl)
|
builder = venv.EnvBuilder(clear=True, symlinks=usl)
|
||||||
if (usl and sys.platform == 'darwin' and
|
builder.create(self.env_dir)
|
||||||
'__PYVENV_LAUNCHER__' in os.environ):
|
fn = self.get_env_file(self.bindir, self.exe)
|
||||||
self.assertRaises(ValueError, builder.create, self.env_dir)
|
# Don't test when False, because e.g. 'python' is always
|
||||||
else:
|
# symlinked to 'python3.3' in the env, even when symlinking in
|
||||||
builder.create(self.env_dir)
|
# general isn't wanted.
|
||||||
fn = self.get_env_file(self.bindir, self.exe)
|
if usl:
|
||||||
# Don't test when False, because e.g. 'python' is always
|
self.assertTrue(os.path.islink(fn))
|
||||||
# symlinked to 'python3.3' in the env, even when symlinking in
|
|
||||||
# general isn't wanted.
|
# If a venv is created from a source build and that venv is used to
|
||||||
if usl:
|
# run the test, the pyvenv.cfg in the venv created in the test will
|
||||||
self.assertTrue(os.path.islink(fn))
|
# 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():
|
def test_main():
|
||||||
run_unittest(BasicTest)
|
run_unittest(BasicTest)
|
||||||
|
|
|
@ -82,13 +82,6 @@ class EnvBuilder:
|
||||||
:param env_dir: The target directory to create an environment in.
|
: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)
|
env_dir = os.path.abspath(env_dir)
|
||||||
context = self.ensure_directories(env_dir)
|
context = self.ensure_directories(env_dir)
|
||||||
self.create_configuration(context)
|
self.create_configuration(context)
|
||||||
|
@ -366,8 +359,7 @@ def main(args=None):
|
||||||
action='store_true', dest='system_site',
|
action='store_true', dest='system_site',
|
||||||
help='Give the virtual environment access to the '
|
help='Give the virtual environment access to the '
|
||||||
'system site-packages dir.')
|
'system site-packages dir.')
|
||||||
if os.name == 'nt' or (sys.platform == 'darwin' and
|
if os.name == 'nt':
|
||||||
sysconfig.get_config_var('PYTHONFRAMEWORK')):
|
|
||||||
use_symlinks = False
|
use_symlinks = False
|
||||||
else:
|
else:
|
||||||
use_symlinks = True
|
use_symlinks = True
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
|
||||||
|
|
||||||
extern char** environ;
|
extern char** environ;
|
||||||
|
@ -158,9 +159,44 @@ main(int argc, char **argv) {
|
||||||
/* Set the original executable path in the environment. */
|
/* Set the original executable path in the environment. */
|
||||||
status = _NSGetExecutablePath(path, &size);
|
status = _NSGetExecutablePath(path, &size);
|
||||||
if (status == 0) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -474,6 +474,7 @@ calculate_path(void)
|
||||||
wchar_t *defpath;
|
wchar_t *defpath;
|
||||||
#ifdef WITH_NEXT_FRAMEWORK
|
#ifdef WITH_NEXT_FRAMEWORK
|
||||||
NSModule pythonModule;
|
NSModule pythonModule;
|
||||||
|
const char* modPath;
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
|
||||||
|
@ -568,8 +569,8 @@ calculate_path(void)
|
||||||
*/
|
*/
|
||||||
pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize"));
|
pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize"));
|
||||||
/* Use dylib functions to find out where the framework was loaded from */
|
/* Use dylib functions to find out where the framework was loaded from */
|
||||||
buf = (wchar_t *)NSLibraryNameForModule(pythonModule);
|
modPath = NSLibraryNameForModule(pythonModule);
|
||||||
if (buf != NULL) {
|
if (modPath != NULL) {
|
||||||
/* We're in a framework. */
|
/* We're in a framework. */
|
||||||
/* See if we might be in the build directory. The framework in the
|
/* 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
|
** build directory is incomplete, it only has the .dylib and a few
|
||||||
|
@ -578,7 +579,12 @@ calculate_path(void)
|
||||||
** be running the interpreter in the build directory, so we use the
|
** be running the interpreter in the build directory, so we use the
|
||||||
** build-directory-specific logic to find Lib and such.
|
** 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);
|
reduce(argv0_path);
|
||||||
joinpath(argv0_path, lib_python);
|
joinpath(argv0_path, lib_python);
|
||||||
joinpath(argv0_path, LANDMARK);
|
joinpath(argv0_path, LANDMARK);
|
||||||
|
@ -589,8 +595,9 @@ calculate_path(void)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* Use the location of the library as the progpath */
|
/* Use the location of the library as the progpath */
|
||||||
wcsncpy(argv0_path, buf, MAXPATHLEN);
|
wcsncpy(argv0_path, wbuf, MAXPATHLEN);
|
||||||
}
|
}
|
||||||
|
PyMem_Free(wbuf);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -629,6 +636,7 @@ calculate_path(void)
|
||||||
FILE * env_file = NULL;
|
FILE * env_file = NULL;
|
||||||
|
|
||||||
wcscpy(tmpbuffer, argv0_path);
|
wcscpy(tmpbuffer, argv0_path);
|
||||||
|
|
||||||
joinpath(tmpbuffer, env_cfg);
|
joinpath(tmpbuffer, env_cfg);
|
||||||
env_file = _Py_wfopen(tmpbuffer, L"r");
|
env_file = _Py_wfopen(tmpbuffer, L"r");
|
||||||
if (env_file == NULL) {
|
if (env_file == NULL) {
|
||||||
|
|
|
@ -616,7 +616,29 @@ Py_Main(int argc, wchar_t **argv)
|
||||||
Py_SetProgramName(buffer);
|
Py_SetProgramName(buffer);
|
||||||
/* buffer is now handed off - do not free */
|
/* buffer is now handed off - do not free */
|
||||||
} else {
|
} 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]);
|
Py_SetProgramName(argv[0]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
Py_SetProgramName(argv[0]);
|
Py_SetProgramName(argv[0]);
|
||||||
|
|
Loading…
Reference in New Issue