523 lines
14 KiB
Python
Executable File
523 lines
14 KiB
Python
Executable File
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
:copyright: 2012–2013 by OpenSlides team, see AUTHORS.
|
||
:license: GNU GPL, see LICENSE for more details.
|
||
"""
|
||
|
||
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 = {
|
||
"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-mptt": {
|
||
"copy": ["mptt"],
|
||
},
|
||
"reportlab": {
|
||
"copy": [
|
||
"reportlab",
|
||
"_renderPM.pyd",
|
||
"_rl_accel.pyd",
|
||
"pyHnj.pyd",
|
||
"sgmlop.pyd",
|
||
],
|
||
},
|
||
"pillow": {
|
||
"copy": ["PIL"],
|
||
},
|
||
"sockjs-tornado": {
|
||
"copy": ["sockjs"],
|
||
},
|
||
"tornado": {
|
||
"copy": ["tornado"],
|
||
},
|
||
"beautifulsoup4": {
|
||
"copy": ["bs4"],
|
||
},
|
||
"bleach": {
|
||
"copy": ["bleach"],
|
||
},
|
||
"html5lib": {
|
||
"copy": ["html5lib"],
|
||
},
|
||
"django-haystack": {
|
||
"copy": ["haystack"],
|
||
},
|
||
"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 <winresrc.h>
|
||
|
||
#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):
|
||
try:
|
||
dist = pkg_resources.get_distribution(pkg)
|
||
text.append("{0}-{1}\n".format(dist.project_name, dist.version))
|
||
except pkg_resources.DistributionNotFound:
|
||
# FIXME: wxpython comes from an installer and has no distribution
|
||
# see what we can do about that
|
||
text.append("{0}-???\n".format(pkg))
|
||
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()
|