From a19cdad6dc2815f6044c56601e8dd81d9c219631 Mon Sep 17 00:00:00 2001 From: Thomas Heller Date: Fri, 20 Feb 2004 14:43:21 +0000 Subject: [PATCH] Patch #892660 from Mark Hammond, for distutils bdist_wininst command. install.c: support for a 'pre-install-script', run before anything has been installed. Provides a 'message_box' module function for use by either the pre-install or post-install scripts. bdist_wininst.py: support for pre-install script. Typo (build->built), fixes so that --target-version can still work, even when the distribution has extension modules - in this case, we insist on --skip-build, as we still can't actually build other versions. --- Lib/distutils/command/bdist_wininst.py | 32 +++- PC/bdist_wininst/install.c | 227 +++++++++++++++++++------ 2 files changed, 206 insertions(+), 53 deletions(-) diff --git a/Lib/distutils/command/bdist_wininst.py b/Lib/distutils/command/bdist_wininst.py index 3c4c8939200..76b1762edef 100644 --- a/Lib/distutils/command/bdist_wininst.py +++ b/Lib/distutils/command/bdist_wininst.py @@ -43,6 +43,10 @@ class bdist_wininst (Command): ('install-script=', None, "basename of installation script to be run after" "installation or before deinstallation"), + ('pre-install-script=', None, + "Fully qualified filename of a script to be run before " + "any files are installed. This script need not be in the " + "distribution"), ] boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', @@ -59,6 +63,7 @@ class bdist_wininst (Command): self.title = None self.skip_build = 0 self.install_script = None + self.pre_install_script = None # initialize_options() @@ -69,11 +74,12 @@ class bdist_wininst (Command): self.bdist_dir = os.path.join(bdist_base, 'wininst') if not self.target_version: self.target_version = "" - if self.distribution.has_ext_modules(): + if not self.skip_build and self.distribution.has_ext_modules(): short_version = get_python_version() if self.target_version and self.target_version != short_version: raise DistutilsOptionError, \ - "target version can only be" + short_version + "target version can only be %s, or the '--skip_build'" \ + " option must be specified" % (short_version,) self.target_version = short_version self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) @@ -109,6 +115,21 @@ class bdist_wininst (Command): # we do not want to include pyc or pyo files install_lib.compile = 0 install_lib.optimize = 0 + + # If we are building an installer for a Python version other + # than the one we are currently running, then we need to ensure + # our build_lib reflects the other Python version rather than ours. + # Note that for target_version!=sys.version, we must have skipped the + # build step, so there is no issue with enforcing the build of this + # version. + target_version = self.target_version + if not target_version: + assert self.skip_build, "Should have already checked this" + target_version = sys.version[0:3] + plat_specifier = ".%s-%s" % (get_platform(), target_version) + build = self.get_finalized_command('build') + build.build_lib = os.path.join(build.build_base, + 'lib' + plat_specifier) # Use a custom scheme for the zip-file, because we have to decide # at installation time which scheme to use. @@ -187,7 +208,7 @@ class bdist_wininst (Command): lines.append("title=%s" % repr(title)[1:-1]) import time import distutils - build_info = "Build %s with distutils-%s" % \ + build_info = "Built %s with distutils-%s" % \ (time.ctime(time.time()), distutils.__version__) lines.append("build_info=%s" % build_info) return string.join(lines, "\n") @@ -223,6 +244,11 @@ class bdist_wininst (Command): if bitmap: file.write(bitmapdata) + # Append the pre-install script + cfgdata = cfgdata + "\0" + if self.pre_install_script: + script_data = open(self.pre_install_script, "r").read() + cfgdata = cfgdata + script_data + "\n\0" file.write(cfgdata) header = struct.pack("lpVtbl->Save(pPf, wszFilename, TRUE); if (FAILED(hr)) { g_PyErr_Format(g_PyExc_OSError, - "Save() failed, error 0x%x", hr); + "Failed to create shortcut '%s' - error 0x%x", filename, hr); goto error; } @@ -553,6 +569,17 @@ static PyObject *CreateShortcut(PyObject *self, PyObject *args) return NULL; } +static PyObject *PyMessageBox(PyObject *self, PyObject *args) +{ + int rc; + char *text, *caption; + int flags; + if (!g_PyArg_ParseTuple(args, "ssi", &text, &caption, &flags)) + return NULL; + rc = MessageBox(GetFocus(), text, caption, flags); + return g_Py_BuildValue("i", rc); +} + #define METH_VARARGS 0x0001 PyMethodDef meth[] = { @@ -560,8 +587,42 @@ PyMethodDef meth[] = { {"get_special_folder_path", GetSpecialFolderPath, METH_VARARGS, NULL}, {"file_created", FileCreated, METH_VARARGS, NULL}, {"directory_created", DirectoryCreated, METH_VARARGS, NULL}, + {"message_box", PyMessageBox, METH_VARARGS, NULL}, }; +static int prepare_script_environment(HINSTANCE hPython) +{ + PyObject *mod; + DECLPROC(hPython, PyObject *, PyImport_ImportModule, (char *)); + DECLPROC(hPython, int, PyObject_SetAttrString, (PyObject *, char *, PyObject *)); + DECLPROC(hPython, PyObject *, PyObject_GetAttrString, (PyObject *, char *)); + DECLPROC(hPython, PyObject *, PyCFunction_New, (PyMethodDef *, PyObject *)); + DECLPROC(hPython, PyObject *, Py_BuildValue, (char *, ...)); + DECLPROC(hPython, int, PyArg_ParseTuple, (PyObject *, char *, ...)); + DECLPROC(hPython, PyObject *, PyErr_Format, (PyObject *, char *)); + if (!PyImport_ImportModule || !PyObject_GetAttrString || + !PyObject_SetAttrString || !PyCFunction_New) + return 1; + if (!Py_BuildValue || !PyArg_ParseTuple || !PyErr_Format) + return 1; + + mod = PyImport_ImportModule("__builtin__"); + if (mod) { + int i; + g_PyExc_ValueError = PyObject_GetAttrString(mod, "ValueError"); + g_PyExc_OSError = PyObject_GetAttrString(mod, "OSError"); + for (i = 0; i < DIM(meth); ++i) { + PyObject_SetAttrString(mod, meth[i].ml_name, + PyCFunction_New(&meth[i], NULL)); + } + } + g_Py_BuildValue = Py_BuildValue; + g_PyArg_ParseTuple = PyArg_ParseTuple; + g_PyErr_Format = PyErr_Format; + + return 0; +} + /* * This function returns one of the following error codes: * 1 if the Python-dll does not export the functions we need @@ -579,19 +640,12 @@ run_installscript(HINSTANCE hPython, char *pathname, int argc, char **argv) DECLPROC(hPython, int, PySys_SetArgv, (int, char **)); DECLPROC(hPython, int, PyRun_SimpleFile, (FILE *, char *)); DECLPROC(hPython, void, Py_Finalize, (void)); - DECLPROC(hPython, PyObject *, PyImport_ImportModule, (char *)); - DECLPROC(hPython, int, PyObject_SetAttrString, - (PyObject *, char *, PyObject *)); - DECLPROC(hPython, PyObject *, PyObject_GetAttrString, - (PyObject *, char *)); DECLPROC(hPython, PyObject *, Py_BuildValue, (char *, ...)); DECLPROC(hPython, PyObject *, PyCFunction_New, (PyMethodDef *, PyObject *)); DECLPROC(hPython, int, PyArg_ParseTuple, (PyObject *, char *, ...)); DECLPROC(hPython, PyObject *, PyErr_Format, (PyObject *, char *)); - PyObject *mod; - int result = 0; FILE *fp; @@ -599,20 +653,12 @@ run_installscript(HINSTANCE hPython, char *pathname, int argc, char **argv) || !PyRun_SimpleFile || !Py_Finalize) return 1; - if (!PyImport_ImportModule || !PyObject_SetAttrString - || !Py_BuildValue) + if (!Py_BuildValue || !PyArg_ParseTuple || !PyErr_Format) return 1; if (!PyCFunction_New || !PyArg_ParseTuple || !PyErr_Format) return 1; - if (!PyObject_GetAttrString) - return 1; - - g_Py_BuildValue = Py_BuildValue; - g_PyArg_ParseTuple = PyArg_ParseTuple; - g_PyErr_Format = PyErr_Format; - if (pathname == NULL || pathname[0] == '\0') return 2; @@ -627,19 +673,7 @@ run_installscript(HINSTANCE hPython, char *pathname, int argc, char **argv) Py_Initialize(); - mod = PyImport_ImportModule("__builtin__"); - if (mod) { - int i; - - g_PyExc_ValueError = PyObject_GetAttrString(mod, - "ValueError"); - g_PyExc_OSError = PyObject_GetAttrString(mod, "OSError"); - for (i = 0; i < DIM(meth); ++i) { - PyObject_SetAttrString(mod, meth[i].ml_name, - PyCFunction_New(&meth[i], NULL)); - } - } - + prepare_script_environment(hPython); PySys_SetArgv(argc, argv); result = PyRun_SimpleFile(fp, pathname); Py_Finalize(); @@ -649,6 +683,77 @@ run_installscript(HINSTANCE hPython, char *pathname, int argc, char **argv) return result; } +static int do_run_simple_script(HINSTANCE hPython, char *script) +{ + int rc; + DECLPROC(hPython, void, Py_Initialize, (void)); + DECLPROC(hPython, void, Py_SetProgramName, (char *)); + DECLPROC(hPython, void, Py_Finalize, (void)); + DECLPROC(hPython, int, PyRun_SimpleString, (char *)); + DECLPROC(hPython, void, PyErr_Print, (void)); + + if (!Py_Initialize || !Py_SetProgramName || !Py_Finalize || + !PyRun_SimpleString || !PyErr_Print) + return -1; + + Py_SetProgramName(modulename); + Py_Initialize(); + prepare_script_environment(hPython); + rc = PyRun_SimpleString(script); + if (rc) + PyErr_Print(); + Py_Finalize(); + return rc; +} + +static int run_simple_script(char *script) +{ + int rc; + char *tempname; + HINSTANCE hPython; + tempname = tmpnam(NULL); + freopen(tempname, "a", stderr); + freopen(tempname, "a", stdout); + + hPython = LoadLibrary (pythondll); + if (!hPython) { + set_failure_reason("Can't load Python for pre-install script"); + return -1; + } + rc = do_run_simple_script(hPython, script); + FreeLibrary(hPython); + fflush(stderr); + fflush(stdout); + /* We only care about the output when we fail. If the script works + OK, then we discard it + */ + if (rc) { + int err_buf_size; + char *err_buf; + const char *prefix = "Running the pre-installation script failed\r\n"; + int prefix_len = strlen(prefix); + FILE *fp = fopen(tempname, "rb"); + fseek(fp, 0, SEEK_END); + err_buf_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + err_buf = malloc(prefix_len + err_buf_size + 1); + if (err_buf) { + int n; + strcpy(err_buf, prefix); + n = fread(err_buf+prefix_len, 1, err_buf_size, fp); + err_buf[prefix_len+n] = '\0'; + fclose(fp); + set_failure_reason(err_buf); + free(err_buf); + } else { + set_failure_reason("Out of memory!"); + } + } + remove(tempname); + return rc; +} + + static BOOL SystemError(int error, char *msg) { char Buffer[1024]; @@ -811,7 +916,11 @@ static void create_bitmap(HWND hwnd) ReleaseDC(hwnd, hdc); } -static char *ExtractIniFile(char *data, DWORD size, int *pexe_size) +/* Extract everything we need to begin the installation. Currently this + is the INI filename with install data, and the raw pre-install script +*/ +static BOOL ExtractInstallData(char *data, DWORD size, int *pexe_size, + char **out_ini_file, char **out_preinstall_script) { /* read the end of central directory record */ struct eof_cdir *pe = (struct eof_cdir *)&data[size - sizeof @@ -828,12 +937,15 @@ static char *ExtractIniFile(char *data, DWORD size, int *pexe_size) char *ini_file; char tempdir[MAX_PATH]; + /* ensure that if we fail, we don't have garbage out pointers */ + *out_ini_file = *out_preinstall_script = NULL; + if (pe->tag != 0x06054b50) { - return NULL; + return FALSE; } if (pmd->tag != 0x1234567A || ofs < 0) { - return NULL; + return FALSE; } if (pmd->bitmap_size) { @@ -846,21 +958,26 @@ static char *ExtractIniFile(char *data, DWORD size, int *pexe_size) src = ((char *)pmd) - pmd->uncomp_size; ini_file = malloc(MAX_PATH); /* will be returned, so do not free it */ if (!ini_file) - return NULL; + return FALSE; if (!GetTempPath(sizeof(tempdir), tempdir) || !GetTempFileName(tempdir, "~du", 0, ini_file)) { SystemError(GetLastError(), "Could not create temporary file"); - return NULL; + return FALSE; } dst = map_new_file(CREATE_ALWAYS, ini_file, NULL, pmd->uncomp_size, 0, 0, NULL/*notify*/); if (!dst) - return NULL; - memcpy(dst, src, pmd->uncomp_size); + return FALSE; + /* Up to the first \0 is the INI file data. */ + strncpy(dst, src, pmd->uncomp_size); + src += strlen(dst) + 1; + /* Up to next \0 is the pre-install script */ + *out_preinstall_script = strdup(src); + *out_ini_file = ini_file; UnmapViewOfFile(dst); - return ini_file; + return TRUE; } static void PumpMessages(void) @@ -1354,7 +1471,7 @@ SelectPythonDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) &py_major, &py_minor); if (result == 2) #ifdef _DEBUG - wsprintf(pythondll, "c:\\python22\\PCBuild\\python%d%d_d.dll", + wsprintf(pythondll, "python%d%d_d.dll", py_major, py_minor); #else wsprintf(pythondll, "python%d%d.dll", @@ -1542,6 +1659,7 @@ InstallFilesDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) "Click Cancel to exit the wizard.", meta_name); SetDlgItemText(hwnd, IDC_TITLE, Buffer); + SetDlgItemText(hwnd, IDC_INFO, "Ready to install"); break; case WM_NUMFILES: @@ -1571,6 +1689,7 @@ InstallFilesDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case PSN_WIZNEXT: /* Handle a Next button click here */ hDialog = hwnd; + success = TRUE; /* Make sure the installation directory name ends in a */ /* backslash */ @@ -1602,14 +1721,23 @@ InstallFilesDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } */ scheme = GetScheme(py_major, py_minor); - + /* Run the pre-install script. */ + if (pre_install_script && *pre_install_script) { + SetDlgItemText (hwnd, IDC_TITLE, + "Running pre-installation script"); + run_simple_script(pre_install_script); + } + if (!success) { + break; + } /* Extract all files from the archive */ SetDlgItemText(hwnd, IDC_TITLE, "Installing files..."); - success = unzip_archive(scheme, - python_dir, arc_data, - arc_size, notify); + if (!unzip_archive (scheme, + python_dir, arc_data, + arc_size, notify)) + set_failure_reason("Failed to unzip installation files"); /* Compile the py-files */ - if (pyc_compile) { + if (success && pyc_compile) { int errors; HINSTANCE hPython; SetDlgItemText(hwnd, IDC_TITLE, @@ -1628,7 +1756,7 @@ InstallFilesDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) * confuse the user. */ } - if (pyo_compile) { + if (success && pyo_compile) { int errors; HINSTANCE hPython; SetDlgItemText(hwnd, IDC_TITLE, @@ -1668,7 +1796,7 @@ FinishedDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) SendDlgItemMessage(hwnd, IDC_BITMAP, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBitmap); if (!success) - SetDlgItemText(hwnd, IDC_INFO, "Installation failed."); + SetDlgItemText(hwnd, IDC_INFO, get_failure_reason()); /* async delay: will show the dialog box completely before the install_script is started */ @@ -1677,7 +1805,7 @@ FinishedDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_USER: - if (install_script && install_script[0]) { + if (success && install_script && install_script[0]) { char fname[MAX_PATH]; char *tempname; FILE *fp; @@ -2271,9 +2399,8 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, */ /* Try to extract the configuration data into a temporary file */ - ini_file = ExtractIniFile(arc_data, arc_size, &exe_size); - - if (ini_file) + if (ExtractInstallData(arc_data, arc_size, &exe_size, + &ini_file, &pre_install_script)) return DoInstall(); if (!ini_file && __argc > 1) {