/* * Written by Thomas Heller, May 2000 * * $Id: install.c,v 1.8 2000/10/12 19:25:58 theller Exp $ */ /* * Windows Installer program for distutils. * * (a kind of self-extracting zip-file) * * At runtime, the exefile has appended: * - compressed setup-data in ini-format, containing the following sections: * [metadata] * author=Greg Ward * author_email=gward@python.net * description=Python Distribution Utilities * licence=Python * name=Distutils * url=http://www.python.org/sigs/distutils-sig/ * version=0.9pre * * [Setup] * info= text to be displayed in the edit-box * title= to be displayed by this program * target_version = if present, python version required * pyc_compile = if 0, do not compile py to pyc * pyo_compile = if 0, do not compile py to pyo * * - a struct meta_data_hdr, describing the above * - a zip-file, containing the modules to be installed. * for the format see http://www.pkware.com/appnote.html * * What does this program do? * - the setup-data is uncompressed and written to a temporary file. * - setup-data is queried with GetPrivateProfile... calls * - [metadata] - info is displayed in the dialog box * - The registry is searched for installations of python * - The user can select the python version to use. * - The python-installation directory (sys.prefix) is displayed * - When the start-button is pressed, files from the zip-archive * are extracted to the file system. All .py filenames are stored * in a list. */ /* * To Do: * - install a help-button, which will display the above * text to the user * - should there be a possibility to display a README file * before starting the installation (if one is present in the archive) * - think about uninstaller * - more comments about what the code does(?) * * - think about an uninstaller (?) * - evolve this into a full blown installer (???) */ #include #include #include "resource.h" #include #include #include #include #include "archive.h" /* Bah: global variables */ HWND hwndMain; HWND hDialog; char *ini_file; /* Full pathname of ini-file */ /* From ini-file */ char info[4096]; /* [Setup] info= */ char title[80]; /* [Setup] title= */ char target_version[10]; /* [Setup] target_version= */ char build_info[80]; /* [Setup] build_info= */ char meta_name[80]; char *arc_data; /* memory mapped archive */ DWORD arc_size; /* number of bytes in archive */ char install_dir[MAX_PATH]; char pythondll[MAX_PATH]; BOOL pyc_compile, pyo_compile; BOOL success; /* Installation successfull? */ #define WM_NUMFILES WM_USER+1 /* wParam: 0, lParam: total number of files */ #define WM_NEXTFILE WM_USER+2 /* wParam: number of this file */ /* lParam: points to pathname */ enum { UNSPECIFIED, ASK, ALWAYS, NEVER } allow_overwrite = UNSPECIFIED; static void unescape (char *str) { char *dst = str; char *src = str; char *eon; char ch; while (src && *src) { if (*src == '\\') { switch (*++src) { case 'n': *dst++ = '\n'; *dst++ = '\r'; break; case 'r': *dst++ = '\r'; break; case '0': case '1': case '2': case '3': ch = (char)strtol (src, &eon, 8); if (ch == '\n') *dst++ = '\r'; *dst++ = ch; src = eon; } } else *dst++ = *src++; } *dst = '\0'; } static struct tagFile { char *path; struct tagFile *next; } *file_list = NULL; static void add_to_filelist (char *path) { struct tagFile *p; p = (struct tagFile *)malloc (sizeof (struct tagFile)); p->path = strdup (path); p->next = file_list; file_list = p; } static int do_compile_files (int (__cdecl * PyRun_SimpleString)(char *)) { struct tagFile *p; int total, n; char Buffer[MAX_PATH + 64]; int errors = 0; total = 0; p = file_list; while (p) { ++total; p = p->next; } SendDlgItemMessage (hDialog, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM (0, total)); SendDlgItemMessage (hDialog, IDC_PROGRESS, PBM_SETPOS, 0, 0); n = 0; p = file_list; while (p) { ++n; wsprintf (Buffer, "import py_compile; py_compile.compile (r'%s')", p->path); if (PyRun_SimpleString (Buffer)) ++errors; SendDlgItemMessage (hDialog, IDC_PROGRESS, PBM_SETPOS, n, 0); SetDlgItemText (hDialog, IDC_INFO, p->path); p = p->next; } return errors; } static int compile_filelist (BOOL optimize_flag) { void (__cdecl * Py_Initialize)(void); void (__cdecl * Py_Finalize)(void); int (__cdecl * PyRun_SimpleString)(char *); int *Py_OptimizeFlag; int errors = 0; HINSTANCE hPython; struct tagFile *p = file_list; if (!p) return 0; SetDlgItemText (hDialog, IDC_INFO, "Loading python..."); hPython = LoadLibrary (pythondll); if (!hPython) return 1; Py_Initialize = (void (*)(void))GetProcAddress (hPython,"Py_Initialize"); Py_Finalize = (void (*)(void))GetProcAddress (hPython, "Py_Finalize"); PyRun_SimpleString = (int (*)(char *))GetProcAddress ( hPython, "PyRun_SimpleString"); Py_OptimizeFlag = (int *)GetProcAddress (hPython, "Py_OptimizeFlag"); *Py_OptimizeFlag = optimize_flag; Py_Initialize (); errors += do_compile_files (PyRun_SimpleString); Py_Finalize (); FreeLibrary (hPython); return errors; } static BOOL SystemError (int error, char *msg) { char Buffer[1024]; int n; if (error) { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL ); strncpy (Buffer, lpMsgBuf, sizeof (Buffer)); LocalFree (lpMsgBuf); } else Buffer[0] = '\0'; n = lstrlen (Buffer); _snprintf (Buffer+n, sizeof (Buffer)-n, msg); MessageBox (hwndMain, Buffer, "Runtime Error", MB_OK | MB_ICONSTOP); return FALSE; } static BOOL AskOverwrite (char *filename) { int result; again: if (allow_overwrite == ALWAYS) return TRUE; if (allow_overwrite == NEVER) return FALSE; if (allow_overwrite == ASK) return (IDYES == MessageBox (hwndMain, filename, "Overwrite existing file?", MB_YESNO | MB_ICONQUESTION)); result = MessageBox (hwndMain, "Overwrite existing files?\n" "\n" "Press YES to ALWAYS overwrite existing files,\n" "press NO to NEVER overwrite existing files,\n" "press CANCEL to ASK individually.", "Overwrite options", MB_YESNOCANCEL | MB_ICONQUESTION); if (result == IDYES) allow_overwrite = ALWAYS; else if (result == IDNO) allow_overwrite = NEVER; else allow_overwrite = ASK; goto again; } static BOOL notify (int code, char *fmt, ...) { char Buffer[1024]; va_list marker; BOOL result = TRUE; int a, b; char *cp; va_start (marker, fmt); _vsnprintf (Buffer, sizeof (Buffer), fmt, marker); switch (code) { /* Questions */ case CAN_OVERWRITE: result = AskOverwrite (Buffer); break; /* Information notification */ case DIR_CREATED: break; case FILE_CREATED: case FILE_OVERWRITTEN: if ((cp = strrchr(fmt, '.')) && (0 == strcmp (cp, ".py"))) add_to_filelist (fmt); break; /* Error Messages */ case ZLIB_ERROR: MessageBox (GetFocus(), Buffer, "Error", MB_OK | MB_ICONWARNING); break; case SYSTEM_ERROR: SystemError (GetLastError(), Buffer); break; case NUM_FILES: a = va_arg (marker, int); b = va_arg (marker, int); SendMessage (hDialog, WM_NUMFILES, 0, MAKELPARAM (0, a)); SendMessage (hDialog, WM_NEXTFILE, b, (LPARAM)fmt); } va_end (marker); return result; } static char *MapExistingFile (char *pathname, DWORD *psize) { HANDLE hFile, hFileMapping; DWORD nSizeLow, nSizeHigh; char *data; hFile = CreateFile (pathname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return NULL; nSizeLow = GetFileSize (hFile, &nSizeHigh); hFileMapping = CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL); CloseHandle (hFile); if (hFileMapping == INVALID_HANDLE_VALUE) return NULL; data = MapViewOfFile (hFileMapping, FILE_MAP_READ, 0, 0, 0); CloseHandle (hFileMapping); *psize = nSizeLow; return data; } static char *ExtractIniFile (char *data, DWORD size) { /* read the end of central directory record */ struct eof_cdir *pe = (struct eof_cdir *)&data[size - sizeof (struct eof_cdir)]; int arc_start = size - sizeof (struct eof_cdir) - pe->nBytesCDir - pe->ofsCDir; int ofs = arc_start - sizeof (struct meta_data_hdr); /* read meta_data info */ struct meta_data_hdr *pmd = (struct meta_data_hdr *)&data[ofs]; char *src, *dst; char *ini_file; char tempdir[MAX_PATH]; if (pe->tag != 0x06054b50) { SystemError (0, "Setup program invalid or damaged"); return NULL; } if (pmd->tag != 0x12345679 || ofs < 0) { SystemError (0, "Setup program invalid or damaged"); return NULL; } src = ((char *)pmd) - pmd->uncomp_size; ini_file = malloc (MAX_PATH); /* will be returned, so do not free it */ if (!ini_file) return NULL; if (!GetTempPath (sizeof (tempdir), tempdir) || !GetTempFileName (tempdir, "~du", 0, ini_file)) { SystemError (GetLastError(), "Could not create temporary file"); return NULL; } dst = map_new_file (CREATE_ALWAYS, ini_file, NULL, pmd->uncomp_size, 0, 0, notify); if (!dst) return NULL; memcpy (dst, src, pmd->uncomp_size); UnmapViewOfFile(dst); return ini_file; } static void PumpMessages (void) { MSG msg; while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); } } LRESULT CALLBACK WindowProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; HFONT hFont; int h; PAINTSTRUCT ps; switch (msg) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps); h = GetSystemMetrics (SM_CYSCREEN) / 10; hFont = CreateFont (h, 0, 0, 0, 700, TRUE, 0, 0, 0, 0, 0, 0, 0, "Times Roman"); hFont = SelectObject (hdc, hFont); SetBkMode (hdc, TRANSPARENT); TextOut (hdc, 15, 15, title, strlen (title)); SetTextColor (hdc, RGB (255, 255, 255)); TextOut (hdc, 10, 10, title, strlen (title)); DeleteObject (SelectObject (hdc, hFont)); EndPaint (hwnd, &ps); return 0; } return DefWindowProc (hwnd, msg, wParam, lParam); } static HWND CreateBackground (char *title) { WNDCLASS wc; HWND hwnd; char buffer[4096]; wc.style = CS_VREDRAW | CS_HREDRAW; wc.lpfnWndProc = WindowProc; wc.cbWndExtra = 0; wc.cbClsExtra = 0; wc.hInstance = GetModuleHandle (NULL); wc.hIcon = NULL; wc.hCursor = LoadCursor (NULL, IDC_ARROW); wc.hbrBackground = CreateSolidBrush (RGB (0, 0, 128)); wc.lpszMenuName = NULL; wc.lpszClassName = "SetupWindowClass"; if (!RegisterClass (&wc)) MessageBox (hwndMain, "Could not register window class", "Setup.exe", MB_OK); wsprintf (buffer, "Setup %s", title); hwnd = CreateWindow ("SetupWindowClass", buffer, 0, 0, 0, GetSystemMetrics (SM_CXFULLSCREEN), GetSystemMetrics (SM_CYFULLSCREEN), NULL, NULL, GetModuleHandle (NULL), NULL); ShowWindow (hwnd, SW_SHOWMAXIMIZED); UpdateWindow (hwnd); return hwnd; } /* * Center a window on the screen */ static void CenterWindow (HWND hwnd) { RECT rc; int w, h; GetWindowRect (hwnd, &rc); w = GetSystemMetrics (SM_CXSCREEN); h = GetSystemMetrics (SM_CYSCREEN); MoveWindow (hwnd, (w - (rc.right-rc.left))/2, (h - (rc.bottom-rc.top))/2, rc.right-rc.left, rc.bottom-rc.top, FALSE); } #include BOOL CALLBACK IntroDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { LPNMHDR lpnm; char Buffer[4096]; switch (msg) { case WM_INITDIALOG: CenterWindow (GetParent (hwnd)); wsprintf (Buffer, "This Wizard will install %s on your computer. " "Click Next to continue or Cancel to exit the Setup Wizard.", meta_name); SetDlgItemText (hwnd, IDC_TITLE, Buffer); SetDlgItemText (hwnd, IDC_INTRO_TEXT, info); SetDlgItemText (hwnd, IDC_BUILD_INFO, build_info); return FALSE; case WM_NOTIFY: lpnm = (LPNMHDR) lParam; switch (lpnm->code) { case PSN_SETACTIVE: PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_NEXT); break; case PSN_WIZNEXT: break; case PSN_RESET: break; default: break; } } return FALSE; } /* * Fill the listbox specified by hwnd with all python versions found * in the registry. version, if not NULL or empty, is the version * required. */ static BOOL GetPythonVersions (HWND hwnd, HKEY hkRoot, LPSTR version) { DWORD index = 0; char core_version[80]; HKEY hKey; BOOL result = TRUE; DWORD bufsize; if (ERROR_SUCCESS != RegOpenKeyEx (hkRoot, "Software\\Python\\PythonCore", 0, KEY_READ, &hKey)) return FALSE; bufsize = sizeof (core_version); while (ERROR_SUCCESS == RegEnumKeyEx (hKey, index, core_version, &bufsize, NULL, NULL, NULL, NULL)) { char subkey_name[80], vers_name[80], prefix_buf[MAX_PATH+1]; int itemindex; DWORD value_size; HKEY hk; bufsize = sizeof (core_version); ++index; if (version && *version && strcmp (version, core_version)) continue; wsprintf (vers_name, "Python Version %s (found in registry)", core_version); itemindex = SendMessage (hwnd, LB_ADDSTRING, 0, (LPARAM)(LPSTR)vers_name); wsprintf (subkey_name, "Software\\Python\\PythonCore\\%s\\InstallPath", core_version); value_size = sizeof (subkey_name); RegOpenKeyEx (hkRoot, subkey_name, 0, KEY_READ, &hk); RegQueryValueEx (hk, NULL, NULL, NULL, prefix_buf, &value_size); RegCloseKey (hk); SendMessage (hwnd, LB_SETITEMDATA, itemindex, (LPARAM)(LPSTR)strdup (prefix_buf)); } RegCloseKey (hKey); return result; } BOOL CALLBACK SelectPythonDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { LPNMHDR lpnm; switch (msg) { case WM_INITDIALOG: GetPythonVersions (GetDlgItem (hwnd, IDC_VERSIONS_LIST), HKEY_LOCAL_MACHINE, target_version); GetPythonVersions (GetDlgItem (hwnd, IDC_VERSIONS_LIST), HKEY_CURRENT_USER, target_version); { /* select the last entry which is the highest python version found */ int count; count = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_GETCOUNT, 0, 0); if (count && count != LB_ERR) SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_SETCURSEL, count-1, 0); } goto UpdateInstallDir; break; case WM_COMMAND: switch (LOWORD (wParam)) { case IDC_VERSIONS_LIST: switch (HIWORD (wParam)) { int id; char *cp; case LBN_SELCHANGE: UpdateInstallDir: PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT); id = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_GETCURSEL, 0, 0); if (id == LB_ERR) { PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK); SetDlgItemText (hwnd, IDC_PATH, ""); strcpy (install_dir, ""); strcpy (pythondll, ""); } else { char *pbuf; int result; PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT); cp = (LPSTR)SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_GETITEMDATA, id, 0); strcpy (install_dir, cp); SetDlgItemText (hwnd, IDC_PATH, install_dir); result = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_GETTEXTLEN, (WPARAM)id, 0); pbuf = (char *)malloc (result + 1); if (pbuf) { /* guess the name of the python-dll */ int major, minor; SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_GETTEXT, (WPARAM)id, (LPARAM)pbuf); result = sscanf (pbuf, "Python Version %d.%d", &major, &minor); if (result == 2) wsprintf (pythondll, "python%d%d.dll", major, minor); free (pbuf); } else strcpy (pythondll, ""); } } break; } return 0; case WM_NOTIFY: lpnm = (LPNMHDR) lParam; switch (lpnm->code) { int id; case PSN_SETACTIVE: id = SendDlgItemMessage (hwnd, IDC_VERSIONS_LIST, LB_GETCURSEL, 0, 0); if (id == LB_ERR) PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK); else PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT); break; case PSN_WIZNEXT: break; case PSN_WIZFINISH: break; case PSN_RESET: break; default: break; } } return 0; } BOOL CALLBACK InstallFilesDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { LPNMHDR lpnm; char Buffer[4096]; switch (msg) { case WM_INITDIALOG: wsprintf (Buffer, "Click Next to begin the installation of %s. " "If you want to review or change any of your " " installation settings, click Back. " "Click Cancel to exit the wizard.", meta_name); SetDlgItemText (hwnd, IDC_TITLE, Buffer); break; case WM_NUMFILES: SendDlgItemMessage (hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, lParam); PumpMessages (); return TRUE; case WM_NEXTFILE: SendDlgItemMessage (hwnd, IDC_PROGRESS, PBM_SETPOS, wParam, 0); SetDlgItemText (hwnd, IDC_INFO, (LPSTR)lParam); PumpMessages (); return TRUE; case WM_NOTIFY: lpnm = (LPNMHDR) lParam; switch (lpnm->code) { case PSN_SETACTIVE: PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT); break; case PSN_WIZFINISH: break; case PSN_WIZNEXT: /* Handle a Next button click here */ hDialog = hwnd; /* Make sure the installation directory name ends in a */ /* backslash */ if (install_dir[strlen(install_dir)-1] != '\\') strcat (install_dir, "\\"); /* Strip the trailing backslash again */ install_dir[strlen(install_dir)-1] = '\0'; /* Extract all files from the archive */ SetDlgItemText (hwnd, IDC_TITLE, "Installing files..."); success = unzip_archive (install_dir, arc_data, arc_size, notify); /* Compile the py-files */ if (pyc_compile) { SetDlgItemText (hwnd, IDC_TITLE, "Compiling files to .pyc..."); compile_filelist (FALSE); } if (pyo_compile) { SetDlgItemText (hwnd, IDC_TITLE, "Compiling files to .pyo..."); compile_filelist (TRUE); } break; case PSN_RESET: break; default: break; } } return 0; } BOOL CALLBACK FinishedDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { LPNMHDR lpnm; switch (msg) { case WM_INITDIALOG: if (!success) SetDlgItemText (hwnd, IDC_INFO, "Installation failed."); break; case WM_NOTIFY: lpnm = (LPNMHDR) lParam; switch (lpnm->code) { case PSN_SETACTIVE: /* Enable the Finish button */ PropSheet_SetWizButtons(GetParent(hwnd), PSWIZB_FINISH); break; case PSN_WIZNEXT: break; case PSN_WIZFINISH: break; case PSN_RESET: break; default: break; } } return 0; } void RunWizard (HWND hwnd) { PROPSHEETPAGE psp = {0}; HPROPSHEETPAGE ahpsp[4] = {0}; PROPSHEETHEADER psh = {0}; /* Display module information */ psp.dwSize = sizeof(psp); psp.dwFlags = PSP_DEFAULT|PSP_HIDEHEADER; psp.hInstance = GetModuleHandle (NULL); psp.lParam = 0; psp.pfnDlgProc = IntroDlgProc; psp.pszTemplate = MAKEINTRESOURCE(IDD_INTRO); ahpsp[0] = CreatePropertySheetPage(&psp); /* Select python version to use */ psp.dwFlags = PSP_DEFAULT|PSP_HIDEHEADER; psp.pszTemplate = MAKEINTRESOURCE(IDD_SELECTPYTHON); psp.pfnDlgProc = SelectPythonDlgProc; ahpsp[1] = CreatePropertySheetPage(&psp); /* Install the files */ psp.dwFlags = PSP_DEFAULT|PSP_HIDEHEADER; psp.pszTemplate = MAKEINTRESOURCE(IDD_INSTALLFILES); psp.pfnDlgProc = InstallFilesDlgProc; ahpsp[2] = CreatePropertySheetPage(&psp); /* Show success or failure */ psp.dwFlags = PSP_DEFAULT|PSP_HIDEHEADER; psp.pszTemplate = MAKEINTRESOURCE(IDD_FINISHED); psp.pfnDlgProc = FinishedDlgProc; ahpsp[3] = CreatePropertySheetPage(&psp); /* Create the property sheet */ psh.dwSize = sizeof(psh); psh.hInstance = GetModuleHandle (NULL); psh.hwndParent = hwnd; psh.phpage = ahpsp; psh.dwFlags = PSH_WIZARD/*97*//*|PSH_WATERMARK|PSH_HEADER*/; psh.pszbmWatermark = NULL; psh.pszbmHeader = NULL; psh.nStartPage = 0; psh.nPages = 4; PropertySheet(&psh); } int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, INT nCmdShow) { char modulename[MAX_PATH]; GetModuleFileName (NULL, modulename, sizeof (modulename)); /* Map the executable file to memory */ arc_data = MapExistingFile (modulename, &arc_size); if (!arc_data) { SystemError (GetLastError(), "Could not open archive"); return 1; } /* Extract the configuration data into a temporary file */ ini_file = ExtractIniFile (arc_data, arc_size); if (!ini_file) { return 1; } /* Read installation information */ GetPrivateProfileString ("Setup", "title", "", title, sizeof (title), ini_file); unescape (title); GetPrivateProfileString ("Setup", "info", "", info, sizeof (info), ini_file); unescape (info); GetPrivateProfileString ("Setup", "build_info", "", build_info, sizeof (build_info), ini_file); pyc_compile = GetPrivateProfileInt ("Setup", "target_compile", 1, ini_file); pyo_compile = GetPrivateProfileInt ("Setup", "target_optimize", 1, ini_file); GetPrivateProfileString ("Setup", "target_version", "", target_version, sizeof (target_version), ini_file); GetPrivateProfileString ("metadata", "name", "", meta_name, sizeof (meta_name), ini_file); hwndMain = CreateBackground (title); RunWizard (hwndMain); /* Clean up */ UnmapViewOfFile (arc_data); if (ini_file) DeleteFile (ini_file); return 0; }