As of macOS 12, dladdr, NSLibraryNameForModule, and similar functions will resolve symlinks, which breaks virtualenv. This works around that issue by not using those functions if at all possible. Adapted from https://github.com/apple-oss-distributions/python/blob/python-136.120.2/2.7/fix/getpath.c.ed --- Modules/getpath.c.orig 2022-01-08 05:08:48.000000000 +1100 +++ Modules/getpath.c 2022-01-08 05:13:34.000000000 +1100 @@ -7,7 +7,7 @@ #include #ifdef __APPLE__ -#include +#include #include #endif @@ -380,9 +380,10 @@ calculate_path(void) size_t prefixsz; char *defpath = pythonpath; #ifdef WITH_NEXT_FRAMEWORK - NSModule pythonModule; + Dl_info addrinfo; + int first_pass = 1; #endif -#ifdef __APPLE__ +#if 0 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 uint32_t nsexeclength = MAXPATHLEN; #else @@ -397,7 +398,7 @@ calculate_path(void) */ if (strchr(prog, SEP)) strncpy(progpath, prog, MAXPATHLEN); -#ifdef __APPLE__ +#if 0 /* On Mac OS X, if a script uses an interpreter of the form * "#!/opt/python2.3/bin/python", the kernel only passes "python" * as argv[0], which falls through to the $PATH search below. @@ -410,7 +411,7 @@ calculate_path(void) */ else if(0 == _NSGetExecutablePath(progpath, &nsexeclength) && progpath[0] == SEP) ; -#endif /* __APPLE__ */ +#endif /* notdef */ else if (path) { while (1) { char *delim = strchr(path, DELIM); @@ -449,9 +450,18 @@ calculate_path(void) ** which is in the framework, not relative to the executable, which ma ** be outside of the framework. Except when we're in the build directory... */ - pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize")); - /* Use dylib functions to find out where the framework was loaded from */ - buf = (char *)NSLibraryNameForModule(pythonModule); + /* dladdr() now returns the real path of the dylib, instead of the + ** path of the symlink. This breaks virtualenv. To fix this, we + ** skip using dladdr() during a first pass, and if that fails, then + ** we go back and do the dladdr(). It turns out that since we moved + ** Python.app to inside the Python.framework bundle, the call to + ** search_for_prefix() will now succeed without needing dladdr(). + ** However, virtualenv copies the wrong binary when the prefix is + ** /usr, so if progpath begins with /usr/bin/, we skip the first pass. + */ + if (strncmp(progpath, "/usr/bin/", 9) == 0) first_pass = 0; +return_here_for_second_pass: + buf = first_pass ? NULL : (dladdr("_Py_Initialize", &addrinfo) ? (char *)addrinfo.dli_fname : NULL); if (buf != NULL) { /* We're in a framework. */ /* See if we might be in the build directory. The framework in the @@ -504,6 +514,12 @@ calculate_path(void) */ if (!(pfound = search_for_prefix(argv0_path, home))) { +#ifdef WITH_NEXT_FRAMEWORK + if (first_pass) { + first_pass = 0; + goto return_here_for_second_pass; + } +#endif if (!Py_FrozenFlag) fprintf(stderr, "Could not find platform independent libraries \n");