// Copyright (c) Facebook, Inc. and its affiliates. #define WIN32_LEAN_AND_MEAN #include #include #include #define PATH_SIZE 32768 typedef int (*Py_Main)(int, wchar_t**); // Add the given path to Windows's DLL search path. // For Windows DLL search path resolution, see: // https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order void add_search_path(const wchar_t* path) { wchar_t buffer[PATH_SIZE]; wchar_t** lppPart = NULL; if (!GetFullPathNameW(path, PATH_SIZE, buffer, lppPart)) { fwprintf( stderr, L"warning: %d unable to expand path %s\n", GetLastError(), path); return; } if (!AddDllDirectory(buffer)) { DWORD error = GetLastError(); if (error != ERROR_FILE_NOT_FOUND) { fwprintf( stderr, L"warning: %d unable to set DLL search path for %s\n", GetLastError(), path); } } } int locate_py_main(int argc, wchar_t** argv) { /* * We have to dynamically locate Python3.dll because we may be loading a * Python native module while running. If that module is built with a * different Python version, we will end up a DLL import error. To resolve * this, we can either ship an embedded version of Python with us or * dynamically look up existing Python distribution installed on user's * machine. This way, we should be able to get a consistent version of * Python3.dll and .pyd modules. */ HINSTANCE python_dll; Py_Main pymain; // last added directory has highest priority add_search_path(L"C:\\Python36\\"); add_search_path(L"C:\\tools\\fb-python\\fb-python36\\"); add_search_path(L"C:\\Python37\\"); add_search_path(L"C:\\tools\\fb-python\\fb-python37\\"); add_search_path(L"C:\\Python38\\"); add_search_path(L"C:\\tools\\fb-python\\fb-python38\\"); // TODO(T123615656): Re-enable Python 3.9 after the fix // add_search_path(L"C:\\tools\\fb-python\\fb-python39\\"); python_dll = LoadLibraryExW(L"python3.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); int returncode = 0; if (python_dll != NULL) { pymain = (Py_Main)GetProcAddress(python_dll, "Py_Main"); if (pymain != NULL) { returncode = (pymain)(argc, argv); } else { fprintf(stderr, "error: %d unable to load Py_Main\n", GetLastError()); } FreeLibrary(python_dll); } else { fprintf(stderr, "error: %d unable to locate python3.dll\n", GetLastError()); return 1; } return returncode; } int wmain() { /* * This executable will be prepended to the start of a Python ZIP archive. * Python will be able to directly execute the ZIP archive, so we simply * need to tell Py_Main() to run our own file. Duplicate the argument list * and add our file name to the beginning to tell Python what file to invoke. */ wchar_t** pyargv = malloc(sizeof(wchar_t*) * (__argc + 1)); if (!pyargv) { fprintf(stderr, "error: failed to allocate argument vector\n"); return 1; } /* Py_Main wants the wide character version of the argv so we pull those * values from the global __wargv array that has been prepared by MSVCRT. * * In order for the zipapp to run we need to insert an extra argument in * the front of the argument vector that points to ourselves. * * An additional complication is that, depending on who prepared the argument * string used to start our process, the computed __wargv[0] can be a simple * shell word like `watchman-wait` which is normally resolved together with * the PATH by the shell. * That unresolved path isn't sufficient to start the zipapp on windows; * we need the fully qualified path. * * Given: * __wargv == {"watchman-wait", "-h"} * * we want to pass the following to Py_Main: * * { * "z:\build\watchman\python\watchman-wait.exe", * "z:\build\watchman\python\watchman-wait.exe", * "-h" * } */ wchar_t full_path_to_argv0[PATH_SIZE]; DWORD len = GetModuleFileNameW(NULL, full_path_to_argv0, PATH_SIZE); if (len == 0 || len == PATH_SIZE && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { fprintf( stderr, "error: %d while retrieving full path to this executable\n", GetLastError()); return 1; } for (int n = 1; n < __argc; ++n) { pyargv[n + 1] = __wargv[n]; } pyargv[0] = full_path_to_argv0; pyargv[1] = full_path_to_argv0; return locate_py_main(__argc + 1, pyargv); }