From 9a49104dac65a1dc7a70262791e64aec5745ce37 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Dec 2015 16:30:28 +0530 Subject: [PATCH] Refactor application layout on windows Now only the calibre executables are present in the top level directory (which is added to PATH). No third party executables and no dlls. This became necessary because the VS 2015 CRT is not manifest based, so it would have to be placed in the top level dir in the old layout. Now it can be put into the DLLs dir. --- setup/installer/windows/freeze.py | 39 +++++++------ setup/installer/windows/main.c | 94 ++++++++++++++++++++++++++----- setup/installer/windows/site.py | 9 +-- setup/installer/windows/util.c | 51 +++++++++++------ setup/installer/windows/util.h | 45 --------------- 5 files changed, 139 insertions(+), 99 deletions(-) delete mode 100644 setup/installer/windows/util.h diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index 30297b71e2..fda8fad051 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -72,11 +72,12 @@ class Win32Freeze(Command, WixMixIn): self.opts = opts self.src_root = self.d(self.SRC) self.base = self.j(self.d(self.SRC), 'build', 'winfrozen') + self.app_base = self.j(self.base, 'app') self.rc_template = self.j(self.d(self.a(__file__)), 'template.rc') self.py_ver = ''.join(map(str, sys.version_info[:2])) - self.lib_dir = self.j(self.base, 'Lib') - self.pylib = self.j(self.base, 'pylib.zip') - self.dll_dir = self.j(self.base, 'DLLs') + self.lib_dir = self.j(self.app_base, 'Lib') + self.pylib = self.j(self.app_base, 'pylib.zip') + self.dll_dir = self.j(self.app_base, 'DLLs') self.portable_base = self.j(self.d(self.base), 'Calibre Portable') self.obj_dir = self.j(self.src_root, 'build', 'launcher') @@ -96,7 +97,8 @@ class Win32Freeze(Command, WixMixIn): def initbase(self): if self.e(self.base): shutil.rmtree(self.base) - os.makedirs(self.base) + os.makedirs(self.app_base) + os.mkdir(self.dll_dir) def add_plugins(self): self.info('Adding plugins...') @@ -112,14 +114,14 @@ class Win32Freeze(Command, WixMixIn): # shutil.copytree(CRT, self.j(self.base, os.path.basename(CRT))) self.info('Adding resources...') - tgt = self.j(self.base, 'resources') + tgt = self.j(self.app_base, 'resources') if os.path.exists(tgt): shutil.rmtree(tgt) shutil.copytree(self.j(self.src_root, 'resources'), tgt) self.info('Adding Qt...') - shutil.copytree(os.path.join(self.python_base, 'DLLs') , self.dll_dir, - ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*')) + for x in glob.glob(os.path.join(self.python_base, 'DLLs', '*')): + shutil.copy2(x, self.dll_dir) for x in glob.glob(self.j(OPENSSL_DIR, 'bin', '*.dll')): shutil.copy2(x, self.dll_dir) for x in glob.glob(self.j(ICU_DIR, 'source', 'lib', '*.dll')): @@ -208,7 +210,7 @@ class Win32Freeze(Command, WixMixIn): self.info('\nAdding Qt plugins...') qt_prefix = QT_DIR plugdir = self.j(qt_prefix, 'plugins') - tdir = self.j(self.base, 'qt_plugins') + tdir = self.j(self.app_base, 'qt_plugins') for d in QT_PLUGINS: self.info('\t', d) imfd = os.path.join(plugdir, d) @@ -227,7 +229,7 @@ class Win32Freeze(Command, WixMixIn): self.info('\tAdding misc binary deps') bindir = os.path.join(SW, 'bin') for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'jpegtran-calibre', 'cjpeg-calibre', 'optipng-calibre'): - shutil.copy2(os.path.join(bindir, x+'.exe'), self.base) + shutil.copy2(os.path.join(bindir, x+'.exe'), self.dll_dir) for pat in ('*.dll',): for f in glob.glob(os.path.join(bindir, pat)): ok = True @@ -545,7 +547,7 @@ class Win32Freeze(Command, WixMixIn): ftype = '/T' + ('c' if src.endswith('.c') else 'p') cmd = [msvc.cc] + cflags + ['/Fo'+obj, ftype + src] self.run_builder(cmd, show_output=True) - exe = self.j(self.base, name) + exe = self.j(self.dll_dir, name) cmd = [msvc.linker] + ['/MACHINE:'+machine, '/SUBSYSTEM:'+subsys, '/RELEASE', '/OUT:'+exe] + [self.embed_resources(exe), obj] + libs @@ -561,14 +563,13 @@ class Win32Freeze(Command, WixMixIn): dlflags = (['/DEBUG'] if debug else ['/INCREMENTAL:NO']) base = self.j(self.src_root, 'setup', 'installer', 'windows') sources = [self.j(base, x) for x in ['util.c',]] - headers = [self.j(base, x) for x in ['util.h',]] objects = [self.j(self.obj_dir, self.b(x)+'.obj') for x in sources] - cflags = '/c /EHsc /MD /W3 /Ox /nologo /D_UNICODE'.split() + cflags = '/c /EHsc /W3 /Ox /nologo /D_UNICODE'.split() cflags += ['/DPYDLL="python%s.dll"'%self.py_ver, '/I%s/include'%self.python_base] for src, obj in zip(sources, objects): - if not self.newer(obj, headers+[src]): + if not self.newer(obj, [src]): continue - cmd = [msvc.cc] + cflags + dflags + ['/Fo'+obj, '/Tc'+src] + cmd = [msvc.cc] + cflags + dflags + ['/MD', '/Fo'+obj, '/Tc'+src] self.run_builder(cmd, show_output=True) dll = self.j(self.obj_dir, 'calibre-launcher.dll') @@ -578,37 +579,39 @@ class Win32Freeze(Command, WixMixIn): '/nologo', '/MACHINE:'+machine] + dlflags + objects + \ [self.embed_resources(dll), '/LIBPATH:%s/libs'%self.python_base, + 'delayimp.lib', 'user32.lib', 'shell32.lib', 'python%s.lib'%self.py_ver, '/delayload:python%s.dll'%self.py_ver] self.info('Linking calibre-launcher.dll') self.run_builder(cmd, show_output=True) src = self.j(base, 'main.c') - shutil.copy2(dll, self.base) + shutil.copy2(dll, self.dll_dir) for typ in ('console', 'gui', ): self.info('Processing %s launchers'%typ) subsys = 'WINDOWS' if typ == 'gui' else 'CONSOLE' for mod, bname, func in zip(modules[typ], basenames[typ], functions[typ]): - xflags = list(cflags) + xflags = list(cflags) + ['/MT'] if typ == 'gui': xflags += ['/DGUI_APP='] xflags += ['/DMODULE="%s"'%mod, '/DBASENAME="%s"'%bname, '/DFUNCTION="%s"'%func] dest = self.j(self.obj_dir, bname+'.obj') - if self.newer(dest, [src]+headers): + if self.newer(dest, [src]): self.info('Compiling', bname) cmd = [msvc.cc] + xflags + dflags + ['/Tc'+src, '/Fo'+dest] self.run_builder(cmd) exe = self.j(self.base, bname+'.exe') lib = dll.replace('.dll', '.lib') + u32 = ['user32.lib'] if typ == 'gui' else [] if self.newer(exe, [dest, lib, self.rc_template, __file__]): self.info('Linking', bname) cmd = [msvc.linker] + ['/MACHINE:'+machine, '/LIBPATH:'+self.obj_dir, '/SUBSYSTEM:'+subsys, '/LIBPATH:%s/libs'%self.python_base, '/RELEASE', - '/OUT:'+exe] + dlflags + [self.embed_resources(exe), + '/OUT:'+exe] + u32 + dlflags + [self.embed_resources(exe), dest, lib] self.run_builder(cmd) diff --git a/setup/installer/windows/main.c b/setup/installer/windows/main.c index 07b4e8ab0d..e2abedba02 100644 --- a/setup/installer/windows/main.c +++ b/setup/installer/windows/main.c @@ -2,33 +2,97 @@ * Copyright 2009 Kovid Goyal */ -#include "util.h" +#define UNICODE +#define WINDOWS_LEAN_AND_MEAN +#include +#include + +static int show_error(const wchar_t *preamble, const wchar_t *msg, const int code) { + wchar_t *buf; + buf = (wchar_t*)LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t)* + (wcslen(msg) + wcslen(preamble) + 80)); + + _snwprintf_s(buf, + LocalSize(buf) / sizeof(wchar_t), _TRUNCATE, + L"%s\r\n %s (Error Code: %d)\r\n", + preamble, msg, code); + +#ifdef GUI_APP + MessageBeep(MB_ICONERROR); + MessageBox(NULL, buf, NULL, MB_OK|MB_ICONERROR); +#else + wprintf_s(L"%s\n", buf); +#endif + LocalFree(buf); + return code; +} + +int show_last_error(wchar_t *preamble) { + wchar_t *msg = NULL; + DWORD dw = GetLastError(); + int ret; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&msg, + 0, + NULL ); + + ret = show_error(preamble, msg, (int)dw); + if (msg != NULL) LocalFree(msg); + return ret; +} + +typedef int (__cdecl *ENTRYPROC)(const char*, const char*, const char*, int); + +static ENTRYPROC load_launcher_dll() { + wchar_t buf[MAX_PATH] = {0}; + wchar_t drive[4] = L"\0\0\0"; + DWORD sz; + HMODULE dll; + ENTRYPROC entrypoint; + + if ((sz = GetModuleFileNameW(NULL, buf, MAX_PATH)) >= MAX_PATH - 30) + ExitProcess(show_error(L"Installation directory path too long", L"", 1)); + + while (sz > 0) { + if (buf[sz] == L'\\' || buf[sz] == L'/') break; + sz--; + } + if (sz <= 0) + ExitProcess(show_error(L"Executable path has no path separators", L"", 1)); + buf[sz+1] = L'a'; buf[sz+2] = L'p'; buf[sz+3] = L'p'; buf[sz+4] = L'\\'; + buf[sz+5] = L'D'; buf[sz+6] = L'L'; buf[sz+7] = L'L'; buf[sz+8] = L's'; + buf[sz+9] = 0; buf[sz+10] = 0; + if (SetDllDirectoryW(buf) == 0) { + show_last_error(L"Failed to set DLL directory"); + ExitProcess(1); + } + dll = LoadLibraryW(L"calibre-launcher.dll"); + if (!dll) ExitProcess(show_last_error(L"Failed to get the calibre-launcher dll handle")); + entrypoint = (ENTRYPROC) GetProcAddress(dll, "execute_python_entrypoint"); + if (!entrypoint) ExitProcess(show_last_error(L"Failed to get the calibre-launcher dll entry point")); + return entrypoint; +} #ifdef GUI_APP int WINAPI wWinMain(HINSTANCE Inst, HINSTANCE PrevInst, wchar_t *CmdLine, int CmdShow) { - set_gui_app((char)1); - - // Redirect stdout and stderr to NUL so that python does not fail writing to them - redirect_out_stream(stdout); - redirect_out_stream(stderr); - - execute_python_entrypoint(BASENAME, MODULE, FUNCTION); - + load_launcher_dll()(BASENAME, MODULE, FUNCTION, 1); return 0; // This should really be returning the value set in the WM_QUIT message, but I cannot be bothered figuring out how to get that. } #else - int wmain(int argc, wchar_t *argv) { - int ret = 0; - set_gui_app((char)0); - ret = execute_python_entrypoint(BASENAME, MODULE, FUNCTION); - - return ret; + return load_launcher_dll()(BASENAME, MODULE, FUNCTION, 0); } #endif diff --git a/setup/installer/windows/site.py b/setup/installer/windows/site.py index 693d7610ea..0842489aeb 100644 --- a/setup/installer/windows/site.py +++ b/setup/installer/windows/site.py @@ -19,7 +19,7 @@ class PydImporter(object): def find_module(self, fullname, path=None): if self.items is None: - dlls_dir = os.path.join(sys.app_dir, 'DLLs') + dlls_dir = os.path.join(sys.app_dir, 'app', 'DLLs') items = self.items = {} for x in os.listdir(dlls_dir): lx = x.lower() @@ -63,8 +63,9 @@ def aliasmbcs(): encodings.aliases.aliases[enc] = 'mbcs' def add_calibre_vars(): - sys.resources_location = os.path.join(sys.app_dir, 'resources') - sys.extensions_location = os.path.join(sys.app_dir, 'DLLs') + sys.new_app_layout = 1 + sys.resources_location = os.path.join(sys.app_dir, 'app', 'resources') + sys.extensions_location = os.path.join(sys.app_dir, 'app', 'DLLs') dv = os.environ.get('CALIBRE_DEVELOP_FROM', None) if dv and os.path.exists(dv): @@ -95,6 +96,6 @@ def main(): add_calibre_vars() # Needed for pywintypes to be able to load its DLL - sys.path.append(os.path.join(sys.app_dir, 'DLLs')) + sys.path.append(os.path.join(sys.app_dir, 'app', 'DLLs')) return run_entry_point() diff --git a/setup/installer/windows/util.c b/setup/installer/windows/util.c index 225de8d720..f80cb6d33d 100644 --- a/setup/installer/windows/util.c +++ b/setup/installer/windows/util.c @@ -2,18 +2,25 @@ * Copyright 2009 Kovid Goyal */ -#include "util.h" +#define UNICODE +#define _WIN32_WINNT 0x0502 +#define WINDOWS_LEAN_AND_MEAN + +#include +#include +#include +#include +#include #include #include #include -static char GUI_APP = 0; +static int GUI_APP = 0; static char python_dll[] = PYDLL; -void set_gui_app(char yes) { GUI_APP = yes; } -char is_gui_app() { return GUI_APP; } +void set_gui_app(int yes) { GUI_APP = yes; } int calibre_show_python_error(const wchar_t *preamble, int code); @@ -118,12 +125,12 @@ void load_python_dll() { size_t l; app_dir = get_app_dir(); - l = strlen(app_dir)+20; + l = strlen(app_dir)+25; dll_dir = (char*) calloc(l, sizeof(char)); qt_plugin_dir = (char*) calloc(l, sizeof(char)); if (!dll_dir || !qt_plugin_dir) ExitProcess(_show_error(L"Out of memory", L"", 1)); - _snprintf_s(dll_dir, l, _TRUNCATE, "%sDLLs", app_dir); - _snprintf_s(qt_plugin_dir, l, _TRUNCATE, "%sqt_plugins", app_dir); + _snprintf_s(dll_dir, l, _TRUNCATE, "%s\\app\\DLLs", app_dir); + _snprintf_s(qt_plugin_dir, l, _TRUNCATE, "%s\\app\\qt_plugins", app_dir); free(app_dir); _putenv_s("QT_PLUGIN_PATH", qt_plugin_dir); @@ -196,7 +203,7 @@ void initialize_interpreter(const char *basename, const char *module, const char buf[strlen(buf)-1] = '\0'; _snprintf_s(python_home, MAX_PATH, _TRUNCATE, "%s", buf); - _snprintf_s(path, MAX_PATH, _TRUNCATE, "%s\\pylib.zip", buf); + _snprintf_s(path, MAX_PATH, _TRUNCATE, "%s\\app\\pylib.zip", buf); free(buf); @@ -337,10 +344,28 @@ int calibre_show_python_error(const wchar_t *preamble, int code) { return _show_error(preamble, L"", code); } -int execute_python_entrypoint(const char *basename, const char *module, const char *function) { +void redirect_out_stream(FILE *stream) { + FILE *f = NULL; + errno_t err; + + err = freopen_s(&f, "NUL", "wt", stream); + if (err != 0) { + ExitProcess(show_last_error_crt(L"Failed to redirect stdout/stderr to NUL. This indicates a corrupted Windows install.\r\n You should contact Microsoft for assistance and/or follow the steps described here:\r\n http://bytes.com/topic/net/answers/264804-compile-error-null-device-missing")); + } +} + +__declspec(dllexport) int __cdecl +execute_python_entrypoint(const char *basename, const char *module, const char *function, int is_gui_app) { PyObject *site, *main, *res; int ret = 0; + if (is_gui_app) { + // Redirect stdout and stderr to NUL so that python does not fail writing to them + redirect_out_stream(stdout); + redirect_out_stream(stderr); + } + set_gui_app(is_gui_app); + load_python_dll(); initialize_interpreter(basename, module, function); @@ -397,12 +422,4 @@ wchar_t* get_temp_filename(const wchar_t *prefix) { return szTempName; } -void redirect_out_stream(FILE *stream) { - FILE *f = NULL; - errno_t err; - err = freopen_s(&f, "NUL", "wt", stream); - if (err != 0) { - ExitProcess(show_last_error_crt(L"Failed to redirect stdout/stderr to NUL. This indicates a corrupted Windows install.\r\n You should contact Microsoft for assistance and/or follow the steps described here:\r\n http://bytes.com/topic/net/answers/264804-compile-error-null-device-missing")); - } -} diff --git a/setup/installer/windows/util.h b/setup/installer/windows/util.h deleted file mode 100644 index ff3ee6f943..0000000000 --- a/setup/installer/windows/util.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2009 Kovid Goyal - */ - -#pragma once - -#ifndef _UNICODE -#define _UNICODE -#endif - -#ifndef UNICODE -#define UNICODE -#endif - -#define _WIN32_WINNT 0x0502 - -#include -#ifdef _DLL -# include -#endif -#include -#include -#include - -#define DllExport __declspec( dllexport ) -#define DllImport __declspec( dllimport ) - -#ifdef _DLL -# define ExIm DllExport -# pragma comment(lib, "delayimp") -# pragma comment(lib, "user32") -# pragma comment(lib, "shell32") -#else -# define ExIm DllImport -#endif - -ExIm void set_gui_app(char yes); -ExIm char is_gui_app(); - -// Redirect output streams to NUL -ExIm void redirect_out_stream(FILE *stream); - -// Execute python entry point defined by: module and function -ExIm int execute_python_entrypoint(const char *basename, const char *module, const char *function); -