-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script to build Windows MSI installers
- Loading branch information
Showing
3 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
#!/usr/bin/env python2.7 | ||
""" Build Windows MSI distributions. | ||
Requirements: | ||
- A 'windeps' folder with all of the *exe installers listed in | ||
BINARY_PACKAGES, available at http://www.lfd.uci.edu/~gohlke/pythonlibs/ | ||
in both 32 and 64 bit. | ||
- 'pywin32' installers for 32 and 64 bit in 'windeps' folder, , available | ||
at http://sourceforge.net/projects/pywin32/files/pywin32/ | ||
- spreads and all of its dependencies installed in the present Python | ||
environment (*not* with pip's '-e' flag!) | ||
- 'pynsist' package must be installed in the present Python environment, | ||
currently (2014/04/11) from GitHub master, not from PyPi. | ||
- 'nsis' executable must be on $PATH ('apt-get install nsis' on Debian | ||
systems) | ||
Run: | ||
$ python buildmsi.py | ||
When complete, MSIs can be found under 'build/msi{32,64}/spreads_{version}.exe' | ||
""" | ||
|
||
import os | ||
import shutil | ||
import sys | ||
import tempfile | ||
import zipfile | ||
from collections import namedtuple | ||
|
||
import nsist | ||
import pkg_resources | ||
from spreads.vendor.pathlib import Path | ||
|
||
import spreads | ||
|
||
BINARY_PACKAGES = { | ||
"cffi": "cffi-0.8.2.{arch}-py2.7.exe", | ||
"MarkupSafe": "MarkupSafe-0.19.{arch}-py2.7.exe", | ||
"PIL": "Pillow-2.4.0.{arch}-py2.7.exe", | ||
"psutil": "psutil-2.1.0.{arch}-py2.7.exe", | ||
"pyexiv2": "pyexiv2-0.3.2.{arch}-py2.7.exe", | ||
"PySide": "PySide-1.2.1.{arch}-py2.7.exe", | ||
"PyYAML": "PyYAML-3.11.{arch}-py2.7.exe", | ||
"tornado": "tornado-3.2.{arch}-py2.7.exe", | ||
"setuptools": "setuptools-3.4.1.{arch}-py2.7.exe" | ||
} | ||
|
||
SourceDep = namedtuple("SourceDep", ("project_name", "module_name")) | ||
SOURCE_PACKAGES = [ | ||
# project module | ||
SourceDep(*("spreads",)*2), | ||
SourceDep(*(None, "spreadsplug")), | ||
SourceDep(*("Flask", "flask")), | ||
SourceDep(*("Jinja2", "jinja2")), | ||
SourceDep(*("Werkzeug", "werkzeug")), | ||
SourceDep(*("backports.ssl-match-hostname", "backports")), | ||
SourceDep(*("blinker",)*2), | ||
SourceDep(*("colorama",)*2), | ||
SourceDep(*("futures", "concurrent")), | ||
SourceDep(*("itsdangerous",)*2), | ||
SourceDep(*("pyusb", "usb")), | ||
SourceDep(*("requests",)*2), | ||
SourceDep(*("waitress",)*2), | ||
SourceDep(*("zipstream",)*2), | ||
] | ||
|
||
|
||
def extract_native_pkg(fname, pkg_dir): | ||
zf = zipfile.ZipFile(unicode(Path('win_deps')/fname)) | ||
tmpdir = Path(tempfile.mkdtemp()) | ||
zf.extractall(unicode(tmpdir)) | ||
fpaths = [] | ||
if (tmpdir/'PLATLIB').exists(): | ||
fpaths += [p for p in (tmpdir/'PLATLIB').iterdir()] | ||
if (tmpdir/'PURELIB').exists(): | ||
fpaths += [p for p in (tmpdir/'PURELIB').iterdir()] | ||
for path in fpaths: | ||
if path.is_dir(): | ||
shutil.copytree(unicode(path), unicode(pkg_dir/path.name)) | ||
else: | ||
shutil.copy2(unicode(path), unicode(pkg_dir/path.name)) | ||
shutil.rmtree(unicode(tmpdir)) | ||
|
||
|
||
def copy_info(pkg, pkg_dir): | ||
try: | ||
dist = pkg_resources.get_distribution(pkg) | ||
except pkg_resources.DistributionNotFound: | ||
raise IOError("No distribution could be found for {0}!".format(pkg)) | ||
if dist.location == os.getcwd(): | ||
egg_name = dist.project_name | ||
else: | ||
egg_name = dist.egg_name() | ||
|
||
egg_path = Path(dist.location)/(egg_name + ".egg-info") | ||
dist_path = Path(dist.location)/(dist.project_name + "-" + dist.version | ||
+ ".dist-info") | ||
if egg_path.exists(): | ||
src_path = egg_path | ||
elif dist_path.exists(): | ||
src_path = dist_path | ||
else: | ||
raise IOError("No egg-info or dist-info could be found for {0}!" | ||
.format(pkg)) | ||
if src_path.is_dir(): | ||
shutil.copytree(unicode(src_path), unicode(pkg_dir/src_path.name)) | ||
else: | ||
shutil.copy2(unicode(src_path), unicode(pkg_dir/src_path.name)) | ||
|
||
|
||
def build_msi(bitness=32): | ||
build_path = Path('build') | ||
if not build_path.exists(): | ||
build_path.mkdir() | ||
pkg_dir = build_path/'pynsist_pkgs' | ||
if pkg_dir.exists(): | ||
shutil.rmtree(unicode(pkg_dir)) | ||
pkg_dir.mkdir() | ||
for pkg in BINARY_PACKAGES.itervalues(): | ||
arch = 'win32' if bitness == 32 else 'win-amd64' | ||
extract_native_pkg(pkg.format(arch=arch), pkg_dir) | ||
|
||
for pkg in (x.project_name for x in SOURCE_PACKAGES | ||
if x.project_name is not None): | ||
copy_info(pkg, pkg_dir) | ||
|
||
icon = os.path.abspath("spreads.ico") | ||
extra_files = [ | ||
os.path.join(os.path.abspath('win_deps'), | ||
'pywin32-2.7.6{0}.exe' | ||
.format('.amd64' if bitness == 64 else '') | ||
)] | ||
nsi_template = os.path.abspath("template.nsi") | ||
|
||
# NOTE: We need to remove the working directory from sys.path to force | ||
# pynsist to copy all of our modules, including 'spreads' and 'spreadsplug' | ||
# from the site-packages. Additionally, we need to change into the | ||
# build directory. | ||
if os.getcwd() in sys.path: | ||
sys.path.remove(os.getcwd()) | ||
os.chdir(unicode(build_path)) | ||
nsist.all_steps( | ||
appname="spreads", | ||
version=spreads.__version__, | ||
script=None, | ||
entry_point="spreads.main:main", | ||
icon=icon, | ||
console=False, | ||
packages=[x.module_name for x in SOURCE_PACKAGES], | ||
extra_files=extra_files, | ||
py_version="2.7.6", | ||
py_bitness=bitness, | ||
build_dir='msi{0}'.format(bitness), | ||
installer_name=None, | ||
nsi_template=nsi_template | ||
) | ||
os.chdir('..') | ||
|
||
if __name__ == '__main__': | ||
if os.path.exists('spreads.egg-info'): | ||
shutil.rmtree('spreads.egg-info') | ||
for bitness in (32, 64): | ||
build_msi(bitness) |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
|
||
; Definitions will be added above | ||
|
||
SetCompressor lzma | ||
|
||
; Modern UI installer stuff | ||
!include "MUI2.nsh" | ||
!define MUI_ABORTWARNING | ||
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" | ||
|
||
; UI pages | ||
!insertmacro MUI_PAGE_WELCOME | ||
!insertmacro MUI_PAGE_COMPONENTS | ||
!insertmacro MUI_PAGE_DIRECTORY | ||
!insertmacro MUI_PAGE_INSTFILES | ||
!insertmacro MUI_PAGE_FINISH | ||
!insertmacro MUI_LANGUAGE "English" | ||
; MUI end ------ | ||
|
||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" | ||
OutFile "${INSTALLER_NAME}" | ||
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}" | ||
ShowInstDetails show | ||
|
||
Section -SETTINGS | ||
SetOutPath "$INSTDIR" | ||
SetOverwrite ifnewer | ||
SectionEnd | ||
|
||
Section "Python ${PY_VERSION}" sec_py | ||
File "python-${PY_VERSION}${ARCH_TAG}.msi" | ||
ExecWait 'msiexec /i "$INSTDIR\python-${PY_VERSION}${ARCH_TAG}.msi" /qb ALLUSERS=1' | ||
Delete $INSTDIR\python-${PY_VERSION}.msi | ||
SectionEnd | ||
|
||
;PYLAUNCHER_INSTALL | ||
;------------------ | ||
|
||
Section "pywin32" sec_pywin32 | ||
File "pywin32-${PY_VERSION}${ARCH_TAG}.exe" | ||
ExecWait "$INSTDIR\pywin32-${PY_VERSION}${ARCH_TAG}.exe" | ||
Delete $INSTDIR\pywin32-${PY_VERSION}${ARCH_TAG}.exe | ||
SectionEnd | ||
|
||
Section "!${PRODUCT_NAME}" sec_app | ||
SectionIn RO | ||
File ${SCRIPT} | ||
File ${PRODUCT_ICON} | ||
SetOutPath "$INSTDIR\pkgs" | ||
File /r "pkgs\*.*" | ||
SetOutPath "$INSTDIR" | ||
;EXTRA_FILES_INSTALL | ||
;------------------- | ||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "${PY_EXE}" '"$INSTDIR\${SCRIPT}"' \ | ||
"$INSTDIR\${PRODUCT_ICON}" | ||
WriteUninstaller $INSTDIR\uninstall.exe | ||
; Add ourselves to Add/remove programs | ||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ | ||
"DisplayName" "${PRODUCT_NAME}" | ||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ | ||
"UninstallString" '"$INSTDIR\uninstall.exe"' | ||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ | ||
"InstallLocation" "$INSTDIR" | ||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ | ||
"DisplayIcon" "$INSTDIR\${PRODUCT_ICON}" | ||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ | ||
"NoModify" 1 | ||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ | ||
"NoRepair" 1 | ||
SectionEnd | ||
|
||
Section "Uninstall" | ||
Delete $INSTDIR\uninstall.exe | ||
Delete "$INSTDIR\${SCRIPT}" | ||
Delete "$INSTDIR\${PRODUCT_ICON}" | ||
RMDir /r "$INSTDIR\pkgs" | ||
;EXTRA_FILES_UNINSTALL | ||
;--------------------- | ||
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk" | ||
RMDir $INSTDIR | ||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" | ||
SectionEnd | ||
|
||
; Functions | ||
|
||
Function .onMouseOverSection | ||
; Find which section the mouse is over, and set the corresponding description. | ||
FindWindow $R0 "#32770" "" $HWNDPARENT | ||
GetDlgItem $R0 $R0 1043 ; description item (must be added to the UI) | ||
|
||
StrCmp $0 ${sec_py} 0 +2 | ||
SendMessage $R0 ${WM_SETTEXT} 0 "STR:The Python interpreter. \ | ||
This is required for ${PRODUCT_NAME} to run." | ||
; | ||
;PYLAUNCHER_HELP | ||
;------------------ | ||
|
||
StrCmp $0 ${sec_pywin32} 0 +2 | ||
SendMessage $R0 ${WM_SETTEXT} 0 "STR:The pywin32 library. \ | ||
This is required for ${PRODUCT_NAME} to run." | ||
|
||
StrCmp $0 ${sec_app} "" +2 | ||
SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}" | ||
FunctionEnd |