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