#!/usr/bin/env python # -*- coding: utf-8 -*- import errno import glob import os import re import shutil import sys import zlib zlib.Z_DEFAULT_COMPRESSION = zlib.Z_BEST_COMPRESSION import zipfile import distutils.ccompiler import distutils.sysconfig import pkg_resources import wx sys.path.insert(0, os.getcwd()) sys.path.insert(1, os.path.join(os.getcwd(), "extras")) import openslides import openslides_gui COMMON_EXCLUDE = [ r".pyc$", r".pyo$", r".po$", r".egg-info", r"\blocale/(?!de/|en/|fr/)[^/]+/" ] LIBEXCLUDE = [ r"^site-packages/", r"^test/", r"^curses/", r"^idlelib/", r"^lib2to3/", r"^lib-tk/", r"^msilib/", ] SITE_PACKAGES = { "beautifulsoup4": { "copy": ["bs4"], }, "bleach": { "copy": ["bleach"], }, "html5lib": { "copy": ["html5lib"], }, "django": { "copy": ["django"], "exclude": [ r"^django/contrib/admindocs/", r"^django/contrib/comments/", r"^django/contrib/databrowse/", r"^django/contrib/flatpages/", r"^django/contrib/formtools/", r"^django/contrib/gis/", r"^django/contrib/localflavor/", r"^django/contrib/markup/", r"^django/contrib/redirects/", r"^django/contrib/sitemaps/", r"^django/contrib/syndication/", r"^django/contrib/webdesign/", ] }, "django-haystack": { "copy": ["haystack"], }, "django-mptt": { "copy": ["mptt"], }, "jsonfield": { "copy": ["jsonfield"], }, "pillow": { "copy": ["PIL"], }, "reportlab": { "copy": [ "reportlab", "_renderPM.pyd", "_rl_accel.pyd", "pyHnj.pyd", "sgmlop.pyd", ], }, "setuptools": { "copy": ["pkg_resources.py"], }, "sockjs-tornado": { "copy": ["sockjs"], }, "tornado": { "copy": ["tornado"], }, "natsort": { "copy": ["natsort"], }, "whoosh": { "copy": ["whoosh"], }, "wx": { # NOTE: wxpython is a special case, see copy_wx "copy": [], "exclude": [ r"^wx/tools/", r"^wx/py/", r"^wx/build/", r"^wx/lib/", r"wx/_activex.pyd", r"wx/_animate.pyd", r"wx/_aui.pyd", r"wx/_calendar.pyd", r"wx/_combo.pyd", r"wx/_gizmos.pyd", r"wx/_glcanvas.pyd", r"wx/_grid.pyd", r"wx/_html.pyd", r"wx/_media.pyd", r"wx/_richtext.pyd", r"wx/_stc.pyd", r"wx/_webkit.pyd", r"wx/_wizard.pyd", r"wx/_xrc.pyd", r"wx/gdiplus.dll", r"wx/wxbase28uh_xml_vc.dll", r"wx/wxmsw28uh_aui_vc.dll", r"wx/wxmsw28uh_gizmos_vc.dll", r"wx/wxmsw28uh_gizmos_xrc_vc.dll", r"wx/wxmsw28uh_gl_vc.dll", r"wx/wxmsw28uh_media_vc.dll", r"wx/wxmsw28uh_qa_vc.dll", r"wx/wxmsw28uh_richtext_vc.dll", r"wx/wxmsw28uh_stc_vc.dll", r"wx/wxmsw28uh_xrc_vc.dll", ], }, } PY_DLLS = [ "unicodedata.pyd", "sqlite3.dll", "_sqlite3.pyd", "_socket.pyd", "select.pyd", "_ctypes.pyd", "_ssl.pyd", "_multiprocessing.pyd", "pyexpat.pyd", ] MSVCR_PUBLIC_KEY = "1fc8b3b9a1e18e3b" MSVCR_VERSION = "9.0.21022.8" MSVCR_NAME = "Microsoft.VC90.CRT" OPENSLIDES_RC_TMPL = """ #include #define ID_ICO_OPENSLIDES 1 ID_ICO_OPENSLIDES ICON "openslides.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION {version[0]},{version[1]},{version[2]},{version[4]} PRODUCTVERSION {version[0]},{version[1]},{version[2]},{version[4]} FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS {file_flags} FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "OpenSlides team\\0" VALUE "FileDescription", "OpenSlides\\0" VALUE "FileVersion", "{version_str}\\0" VALUE "InternalName", "OpenSlides\\0" VALUE "LegalCopyright", "Copyright \\251 2011-2013\\0" VALUE "OriginalFilename", "openslides.exe\\0" VALUE "ProductName", "OpenSlides\\0" VALUE "ProductVersion", "{version_str}\\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 0x4E4 END END """ def compile_re_list(patterns): expr = "|".join("(?:{0})".format(x) for x in patterns) return re.compile(expr) def relpath(base, path, addslash=False): b = os.path.normpath(os.path.abspath(base)) p = os.path.normpath(os.path.abspath(path)) if p == b: p = "." if addslash: p += "/" return p b += os.sep if not p.startswith(b): raise ValueError("{0!r} is not relative to {1!r}".format(path, base)) p = p[len(b):].replace(os.sep, "/") if addslash: p += "/" return p def filter_excluded_dirs(exclude_pattern, basedir, dirpath, dnames): i, l = 0, len(dnames) while i < l: rp = relpath(basedir, os.path.join(dirpath, dnames[i]), True) if exclude_pattern.search(rp): del dnames[i] l -= 1 else: i += 1 def copy_dir_exclude(exclude, basedir, srcdir, destdir): for dp, dnames, fnames in os.walk(srcdir): filter_excluded_dirs(exclude, basedir, dp, dnames) rp = relpath(basedir, dp) target_dir = os.path.join(destdir, rp) if not os.path.exists(target_dir): os.makedirs(target_dir) for fn in fnames: fp = os.path.join(dp, fn) rp = relpath(basedir, fp) if exclude.search(rp): continue shutil.copyfile(fp, os.path.join(destdir, rp)) def collect_lib(libdir, odir): exclude = compile_re_list(COMMON_EXCLUDE + LIBEXCLUDE) copy_dir_exclude(exclude, libdir, libdir, os.path.join(odir, "Lib")) def get_pkg_exclude(name, extra=()): exclude = COMMON_EXCLUDE[:] exclude.extend(SITE_PACKAGES.get(name, {}).get("exclude", [])) exclude.extend(extra) return compile_re_list(exclude) def copy_package(name, info, odir): copy_things = info.get("copy", []) if not copy_things: return dist = pkg_resources.get_distribution(name) exclude = get_pkg_exclude(name) site_dir = dist.location for thing in copy_things: fp = os.path.join(site_dir, thing) if not os.path.isdir(fp): rp = relpath(site_dir, fp) ofp = os.path.join(odir, rp) shutil.copyfile(fp, ofp) else: copy_dir_exclude(exclude, site_dir, fp, odir) def copy_wx(odir): base_dir = os.path.dirname(os.path.dirname(wx.__file__)) wx_dir = os.path.join(base_dir, "wx") exclude = get_pkg_exclude("wx") copy_dir_exclude(exclude, base_dir, wx_dir, odir) def collect_site_packages(sitedir, odir): if not os.path.exists(odir): os.makedirs(odir) for name, info in SITE_PACKAGES.iteritems(): copy_package(name, info, odir) assert "wx" in SITE_PACKAGES copy_wx(odir) def compile_openslides_launcher(): try: cc = distutils.ccompiler.new_compiler() if not cc.initialized: cc.initialize() except distutils.errors.DistutilsError: return False cc.add_include_dir(distutils.sysconfig.get_python_inc()) 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") shutil.copyfile( os.path.join(gui_data_dir, "openslides.ico"), "extras/win32-portable/openslides.ico") rcfile = "extras/win32-portable/openslides.rc" with open(rcfile, "w") as f: if openslides.VERSION[3] == "final": file_flags = "0" else: file_flags = "VS_FF_PRERELEASE" f.write(OPENSLIDES_RC_TMPL.format( version=openslides.VERSION, version_str=openslides.get_version(), file_flags=file_flags)) objs = cc.compile([ "extras/win32-portable/openslides.c", rcfile, ]) cc.link_executable( objs, "extras/win32-portable/openslides", 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") if not os.path.exists(dll_dest): os.makedirs(dll_dest) for dll_name in PY_DLLS: src = os.path.join(dll_src, dll_name) dest = os.path.join(dll_dest, dll_name) shutil.copyfile(src, dest) pydllname = "python{0}{1}.dll".format(*sys.version_info[:2]) src = os.path.join(os.environ["WINDIR"], "System32", pydllname) dest = os.path.join(dll_dest, pydllname) shutil.copyfile(src, dest) def copy_msvcr(odir): candidates = glob.glob("{0}/x86_*{1}_{2}*".format( os.path.join(os.environ["WINDIR"], "winsxs"), MSVCR_NAME, MSVCR_PUBLIC_KEY)) msvcr_local_name = None msvcr_dll_dir = None for dp in candidates: bn = os.path.basename(dp) if MSVCR_VERSION in bn: msvcr_local_name = bn msvcr_dll_dir = dp break else: sys.stderr.write( "Warning could not determine msvcr runtime location\n" "Private asssembly for VC runtime must be added manually\n") return msvcr_dest_dir = os.path.join(odir, MSVCR_NAME) if not os.path.exists(msvcr_dest_dir): os.makedirs(msvcr_dest_dir) for fn in os.listdir(msvcr_dll_dir): src = os.path.join(msvcr_dll_dir, fn) dest = os.path.join(msvcr_dest_dir, fn) shutil.copyfile(src, dest) src = os.path.join( os.environ["WINDIR"], "winsxs", "Manifests", "{0}.manifest".format(msvcr_local_name)) dest = os.path.join(msvcr_dest_dir, "{0}.manifest".format(MSVCR_NAME)) shutil.copyfile(src, dest) def write_package_info_content(outfile): """ Writes a list of all included packages into outfile. """ text = ['Included Packages\n', 17 * '=' + '\n', '\n'] for pkg in sorted(SITE_PACKAGES): if pkg == "wx": # wxpython comes from an installer and has no distribution # --> handle it separately text.append("wxpython-{0}\n".format(wx.__version__)) else: dist = pkg_resources.get_distribution(pkg) text.append("{0}-{1}\n".format(dist.project_name, dist.version)) with open(outfile, "w") as f: f.writelines(text) def write_metadatafile(infile, outfile): """ Writes content from metadata files like README, AUTHORS and LICENSE into outfile. """ with open(infile, "rU") as f: text = [l for l in f] with open(outfile, "w") as f: f.writelines(text) def main(): prefix = os.path.dirname(sys.executable) libdir = os.path.join(prefix, "Lib") sitedir = os.path.join(libdir, "site-packages") odir = "dist/openslides-{0}-portable".format(openslides.get_version()) try: shutil.rmtree(odir) except OSError as e: if e.errno != errno.ENOENT: raise os.makedirs(odir) out_site_packages = os.path.join(odir, "Lib", "site-packages") collect_lib(libdir, odir) collect_site_packages(sitedir, out_site_packages) exclude = get_pkg_exclude("openslides") copy_dir_exclude(exclude, ".", "openslides", out_site_packages) 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", os.path.join(odir, "openslides.exe")) shutil.copytree( "extras/openslides_gui", os.path.join(out_site_packages, "openslides_gui")) copy_dlls(odir) copy_msvcr(odir) # Info on included packages shutil.copytree( "extras/win32-portable/licenses", os.path.join(odir, "packages-info")) write_package_info_content(os.path.join(odir, 'packages-info', 'PACKAGES.txt')) # AUTHORS, LICENSE, README write_metadatafile('AUTHORS', os.path.join(odir, 'AUTHORS.txt')) write_metadatafile('LICENSE', os.path.join(odir, 'LICENSE.txt')) write_metadatafile('README.rst', os.path.join(odir, 'README.txt')) zip_fp = os.path.join( "dist", "openslides-{0}-portable.zip".format( openslides.get_version())) with zipfile.ZipFile(zip_fp, "w", zipfile.ZIP_DEFLATED) as zf: for dp, dnames, fnames in os.walk(odir): for fn in fnames: fp = os.path.join(dp, fn) rp = relpath(odir, fp) zf.write(fp, rp) print("Successfully build {0}".format(zip_fp)) if __name__ == "__main__": main()