diff --git a/extras/win32-portable/create_portable.txt b/extras/win32-portable/create_portable.txt index 4cad67913..dda2949a4 100644 --- a/extras/win32-portable/create_portable.txt +++ b/extras/win32-portable/create_portable.txt @@ -6,11 +6,18 @@ How to create a new portable Windows distribution of OpenSlides: easy_install -Z django django-mptt beautifulsoup4 bleach pillow qrcode reportlab tornado -2.) Run in the main directory of the OpenSlides checkout: +2.) To update the version resource of the prebuild openslides.exe + pywin32 should be installed (it is not strictly required but at + least for releases that are to be published it is highly advisable) + + To install it just grab the binary installer from: + http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download + +3.) Run in the main directory of the OpenSlides checkout: python extras\win32-portable\prepare_portable.py -3.) The portable OpenSlides distribution is now ready as a zip archive +4.) The portable OpenSlides distribution is now ready as a zip archive in the 'dist' directory diff --git a/extras/win32-portable/openslides.c b/extras/win32-portable/openslides.c index 7e6abdcc4..e69d9824b 100644 --- a/extras/win32-portable/openslides.c +++ b/extras/win32-portable/openslides.c @@ -12,6 +12,17 @@ #include +#define PYTHON_DLL_PATH "\\Dlls\\python27.dll" + +static void (*py_initialize)(void) = 0; +static void (*py_finalize)(void) = 0; +static void (*py_set_program_name)(char *) = 0; +static void (*py_set_python_home)(char *) = 0; +static int (*py_run_simple_string_flags)(const char *, PyCompilerFlags *) = 0; +static void (*py_sys_set_argv_ex)(int, char **, int) = 0; +static int (*py_main)(int, char **) = 0; +static int *py_ignore_environment_flag = 0; + static const char *run_openslides_code = "import openslides_gui.gui;" "openslides_gui.gui.main()"; @@ -71,14 +82,94 @@ _get_module_name() return NULL; } +static void +_fatal_error(const char *text) +{ + MessageBoxA(NULL, text, "Fatal error", MB_OK | MB_ICONERROR); + exit(1); +} + +static void +_fatal_error_fmt(const char *fmt, ...) +{ + int size = 512; + char *buf = malloc(size); + va_list args; + int bytes_written; + + if (!buf) + abort(); + + va_start(args, fmt); + for (;;) + { + bytes_written = vsnprintf(buf, size, fmt, args); + if (bytes_written > -1 && bytes_written < size) + break; + else if (bytes_written > size) + size = bytes_written + 1; + else + size *= 2; + + buf = realloc(buf, size); + if (!buf) + abort(); + } + va_end(args); + + _fatal_error(buf); +} + +static void * +_load_func(HMODULE module, const char *name) +{ + void *address = GetProcAddress(module, name); + if (!address) + _fatal_error_fmt("Failed to look up symbol %s", name); + return address; +} + +static void +_load_python(const char *pyhome) +{ + size_t pyhome_len = strlen(pyhome); + size_t size = pyhome_len + strlen(PYTHON_DLL_PATH) + 1; + char *buf = malloc(size); + HMODULE py_dll; + + if (!buf) + abort(); + memcpy(buf, pyhome, pyhome_len); + memcpy(buf + pyhome_len, PYTHON_DLL_PATH, sizeof(PYTHON_DLL_PATH)); + buf[size - 1] = '\0'; + + py_dll = LoadLibrary(buf); + if (!py_dll) + { + DWORD error = GetLastError(); + _fatal_error_fmt("Failed to load %s (error %d)", buf, error); + } + + py_initialize = (void (*)(void))_load_func(py_dll, "Py_Initialize"); + py_finalize = (void (*)(void))_load_func(py_dll, "Py_Finalize"); + py_set_program_name = (void (*)(char *)) + _load_func(py_dll, "Py_SetProgramName"); + py_set_python_home = (void (*)(char *)) + _load_func(py_dll, "Py_SetPythonHome"); + py_run_simple_string_flags = (int (*)(const char *, PyCompilerFlags *)) + _load_func(py_dll, "PyRun_SimpleStringFlags"); + py_sys_set_argv_ex = (void (*)(int, char **, int)) + _load_func(py_dll, "PySys_SetArgvEx"); + py_main = (int (*)(int, char **))_load_func(py_dll, "Py_Main"); + py_ignore_environment_flag = (int *) + _load_func(py_dll, "Py_IgnoreEnvironmentFlag"); +} + static int _run() { - if (PyRun_SimpleString(run_openslides_code) != 0) - { - fprintf(stderr, "ERROR: failed to execute openslides\n"); - return 1; - } + if (py_run_simple_string_flags(run_openslides_code, NULL) != 0) + _fatal_error("Failed to execute openslides"); return 0; } @@ -91,33 +182,33 @@ WinMain(HINSTANCE inst, HINSTANCE prev_inst, LPSTR cmdline, int show) int run_py_main = __argc > 1; char *py_home, *sep = NULL; - Py_SetProgramName(__argv[0]); - py_home = _get_module_name(); + if (!py_home) + _fatal_error("Could not determine portable root directory"); - if (py_home) - sep = strrchr(py_home, '\\'); + sep = strrchr(py_home, '\\'); /* should always be the true */ if (sep) - { *sep = '\0'; - Py_SetPythonHome(py_home); - Py_IgnoreEnvironmentFlag = 1; - } + + _load_python(py_home); + py_set_program_name(__argv[0]); + py_set_python_home(py_home); + *py_ignore_environment_flag = 1; if (run_py_main) { /* we where given extra arguments, behave like python.exe */ - returncode = Py_Main(__argc, __argv); + returncode = py_main(__argc, __argv); } else { /* no arguments given => start openslides gui */ - Py_Initialize(); - PySys_SetArgvEx(__argc, __argv, 0); + py_initialize(); + py_sys_set_argv_ex(__argc, __argv, 0); returncode = _run(); - Py_Finalize(); + py_finalize(); } free(py_home); diff --git a/extras/win32-portable/openslides.exe b/extras/win32-portable/openslides.exe index 812eabdc0..7d1bd450f 100755 Binary files a/extras/win32-portable/openslides.exe and b/extras/win32-portable/openslides.exe differ diff --git a/extras/win32-portable/prepare_portable.py b/extras/win32-portable/prepare_portable.py old mode 100644 new mode 100755 index 770fef517..593e5b051 --- a/extras/win32-portable/prepare_portable.py +++ b/extras/win32-portable/prepare_portable.py @@ -318,7 +318,7 @@ def compile_openslides_launcher(): return False cc.add_include_dir(distutils.sysconfig.get_python_inc()) - cc.add_library_dir(os.path.join(sys.exec_prefix, "Libs")) + cc.define_macro("_CRT_SECURE_NO_WARNINGS") gui_data_dir = os.path.dirname(openslides_gui.__file__) gui_data_dir = os.path.join(gui_data_dir, "data") @@ -343,11 +343,52 @@ def compile_openslides_launcher(): ]) cc.link_executable( objs, "extras/win32-portable/openslides", - extra_preargs=["/subsystem:windows"], + extra_preargs=["/subsystem:windows", "/nodefaultlib:python27.lib"], + libraries=["user32"] ) return True +def openslides_launcher_update_version_resource(): + try: + import win32api + import win32verstamp + except ImportError: + sys.stderr.write( + "Using precompiled executable and pywin32 is not available - " + "version resource may be out of date!\n") + return False + import struct + + sys.stdout.write("Updating version resource") + # code based on win32verstamp.stamp() with some minor differences in + # version handling + major, minor, sub = openslides.VERSION[:3] + build = openslides.VERSION[4] + pre_release = openslides.VERSION[3] != "final" + version_str = openslides.get_version() + + sdata = { + "CompanyName": "OpenSlides team", + "FileDescription": "OpenSlides", + "FileVersion": version_str, + "InternalName": "OpenSlides", + "LegalCopyright": u"Copyright \xa9 2011-2013", + "OriginalFilename": "openslides.exe", + "ProductName": "OpenSlides", + "ProductVersion": version_str, + } + vdata = { + "Translation": struct.pack("hh", 0x409, 0x4e4), + } + + vs = win32verstamp.VS_VERSION_INFO( + major, minor, sub, build, sdata, vdata, pre_release, False) + h = win32api.BeginUpdateResource("extras/win32-portable/openslides.exe", 0) + win32api.UpdateResource(h, 16, 1, vs) + win32api.EndUpdateResource(h, 0) + + def copy_dlls(odir): dll_src = os.path.join(sys.exec_prefix, "DLLs") dll_dest = os.path.join(odir, "DLLs") @@ -361,7 +402,7 @@ def copy_dlls(odir): pydllname = "python{0}{1}.dll".format(*sys.version_info[:2]) src = os.path.join(os.environ["WINDIR"], "System32", pydllname) - dest = os.path.join(odir, pydllname) + dest = os.path.join(dll_dest, pydllname) shutil.copyfile(src, dest) @@ -442,6 +483,7 @@ def main(): if not compile_openslides_launcher(): sys.stdout.write("Using prebuild openslides.exe\n") + openslides_launcher_update_version_resource() shutil.copyfile( "extras/win32-portable/openslides.exe", diff --git a/openslides/main.py b/openslides/main.py index e4cd32b61..0d609b641 100644 --- a/openslides/main.py +++ b/openslides/main.py @@ -349,6 +349,14 @@ def get_user_data_path(*args): def is_portable(): + """Return True if openslides is run as portable version""" + + # NOTE: sys.executable is the path of the *interpreter* + # the portable version embeds python so it *is* the interpreter. + # The wrappers generated by pip and co. will spawn + # the usual python(w).exe, so there is no danger of mistaking + # them for the portable even though they may also be called + # openslides.exe exename = os.path.basename(sys.executable).lower() return exename == "openslides.exe"