1/* 2 * This wrapper program executes a python executable hidden inside an 3 * application bundle inside the Python framework. This is needed to run 4 * GUI code: some GUI API's don't work unless the program is inside an 5 * application bundle. 6 * 7 * This program uses posix_spawn rather than plain execv because we need 8 * slightly more control over how the "real" interpreter is executed. 9 * 10 * On OSX 10.4 (and earlier) this falls back to using exec because the 11 * posix_spawnv functions aren't available there. 12 */ 13 14#pragma weak_import posix_spawnattr_init 15#pragma weak_import posix_spawnattr_setbinpref_np 16#pragma weak_import posix_spawnattr_setflags 17#pragma weak_import posix_spawn 18 19#include <Python.h> 20#include <unistd.h> 21#ifdef HAVE_SPAWN_H 22#include <spawn.h> 23#endif 24#include <stdio.h> 25#include <string.h> 26#include <errno.h> 27#include <err.h> 28#include <dlfcn.h> 29#include <stdlib.h> 30#include <Python.h> 31#include <mach-o/dyld.h> 32 33 34extern char** environ; 35 36/* 37 * Locate the python framework by looking for the 38 * library that contains Py_Initialize. 39 * 40 * In a regular framework the structure is: 41 * 42 * Python.framework/Versions/2.7 43 * /Python 44 * /Resources/Python.app/Contents/MacOS/Python 45 * 46 * In a virtualenv style structure the expected 47 * structure is: 48 * 49 * ROOT 50 * /bin/pythonw 51 * /.Python <- the dylib 52 * /.Resources/Python.app/Contents/MacOS/Python 53 * 54 * NOTE: virtualenv's are not an officially supported 55 * feature, support for that structure is provided as 56 * a convenience. 57 */ 58static char* get_python_path(void) 59{ 60 size_t len; 61 Dl_info info; 62 char* end; 63 char* g_path; 64 65 if (dladdr(Py_Initialize, &info) == 0) { 66 return NULL; 67 } 68 69 len = strlen(info.dli_fname); 70 71 g_path = malloc(len+60); 72 if (g_path == NULL) { 73 return NULL; 74 } 75 76 strcpy(g_path, info.dli_fname); 77 end = g_path + len - 1; 78 while (end != g_path && *end != '/') { 79 end --; 80 } 81 end++; 82 if (*end == '.') { 83 end++; 84 } 85 strcpy(end, "Resources/Python.app/Contents/MacOS/" PYTHONFRAMEWORK); 86 87 return g_path; 88} 89 90#ifdef HAVE_SPAWN_H 91static void 92setup_spawnattr(posix_spawnattr_t* spawnattr) 93{ 94 size_t ocount; 95 size_t count; 96 cpu_type_t cpu_types[1]; 97 short flags = 0; 98 99 if ((errno = posix_spawnattr_init(spawnattr)) != 0) { 100 err(2, "posix_spawnattr_int"); 101 /* NOTREACHTED */ 102 } 103 104 count = 1; 105 106 /* Run the real python executable using the same architecture as this 107 * executable, this allows users to control the architecture using 108 * "arch -ppc python" 109 */ 110 111#if defined(__ppc64__) 112 cpu_types[0] = CPU_TYPE_POWERPC64; 113 114#elif defined(__x86_64__) 115 cpu_types[0] = CPU_TYPE_X86_64; 116 117#elif defined(__ppc__) 118 cpu_types[0] = CPU_TYPE_POWERPC; 119 120#elif defined(__i386__) 121 cpu_types[0] = CPU_TYPE_X86; 122 123#elif defined(__arm64__) 124 cpu_types[0] = CPU_TYPE_ARM64; 125 126#else 127# error "Unknown CPU" 128 129#endif 130 131 if (posix_spawnattr_setbinpref_np(spawnattr, count, 132 cpu_types, &ocount) == -1) { 133 err(1, "posix_spawnattr_setbinpref"); 134 /* NOTREACHTED */ 135 } 136 if (count != ocount) { 137 fprintf(stderr, "posix_spawnattr_setbinpref failed to copy\n"); 138 exit(1); 139 /* NOTREACHTED */ 140 } 141 142 143 /* 144 * Set flag that causes posix_spawn to behave like execv 145 */ 146 flags |= POSIX_SPAWN_SETEXEC; 147 if ((errno = posix_spawnattr_setflags(spawnattr, flags)) != 0) { 148 err(1, "posix_spawnattr_setflags"); 149 /* NOTREACHTED */ 150 } 151} 152#endif 153 154int 155main(int argc, char **argv) { 156 char* exec_path = get_python_path(); 157 static char path[PATH_MAX * 2]; 158 static char real_path[PATH_MAX * 2]; 159 int status; 160 uint32_t size = PATH_MAX * 2; 161 162 /* Set the original executable path in the environment. */ 163 status = _NSGetExecutablePath(path, &size); 164 if (status == 0) { 165 /* 166 * Note: don't call 'realpath', that will 167 * erase symlink information, and that 168 * breaks "pyvenv --symlink" 169 * 170 * It is nice to have the directory name 171 * as a cleaned up absolute path though, 172 * therefore call realpath on dirname(path) 173 */ 174 char* slash = strrchr(path, '/'); 175 if (slash) { 176 char replaced; 177 replaced = slash[1]; 178 slash[1] = 0; 179 if (realpath(path, real_path) == NULL) { 180 err(1, "realpath: %s", path); 181 } 182 slash[1] = replaced; 183 if (strlcat(real_path, slash, sizeof(real_path)) > sizeof(real_path)) { 184 errno = EINVAL; 185 err(1, "realpath: %s", path); 186 } 187 188 } else { 189 if (realpath(".", real_path) == NULL) { 190 err(1, "realpath: %s", path); 191 } 192 if (strlcat(real_path, "/", sizeof(real_path)) > sizeof(real_path)) { 193 errno = EINVAL; 194 err(1, "realpath: %s", path); 195 } 196 if (strlcat(real_path, path, sizeof(real_path)) > sizeof(real_path)) { 197 errno = EINVAL; 198 err(1, "realpath: %s", path); 199 } 200 } 201 202 /* 203 * The environment variable is used to pass the value of real_path 204 * to the actual python interpreter, and is read by code in 205 * Python/coreconfig.c. 206 * 207 * This way the real interpreter knows how the user invoked the 208 * interpreter and can behave as if this launcher is the real 209 * interpreter (looking for pyvenv configuration, ...) 210 */ 211 setenv("__PYVENV_LAUNCHER__", real_path, 1); 212 } 213 214 /* 215 * Let argv[0] refer to the new interpreter. This is needed to 216 * get the effect we want on OSX 10.5 or earlier. That is, without 217 * changing argv[0] the real interpreter won't have access to 218 * the Window Server. 219 */ 220 argv[0] = exec_path; 221 222#ifdef HAVE_SPAWN_H 223 /* We're weak-linking to posix-spawnv to ensure that 224 * an executable build on 10.5 can work on 10.4. 225 */ 226 227 if (&posix_spawn != NULL) { 228 posix_spawnattr_t spawnattr = NULL; 229 230 setup_spawnattr(&spawnattr); 231 posix_spawn(NULL, exec_path, NULL, 232 &spawnattr, argv, environ); 233 err(1, "posix_spawn: %s", exec_path); 234 } 235#endif 236 execve(exec_path, argv, environ); 237 err(1, "execve: %s", argv[0]); 238 /* NOTREACHED */ 239} 240