__init__.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import logging
  2. import os
  3. import shutil
  4. from subprocess import check_output, call
  5. import sys
  6. from urllib.request import urlretrieve
  7. from .copymodules import copy_modules
  8. from .nsiswriter import NSISFileWriter
  9. pjoin = os.path.join
  10. logger = logging.getLogger(__name__)
  11. _PKGDIR = os.path.dirname(__file__)
  12. DEFAULT_PY_VERSION = '3.3.2'
  13. DEFAULT_BUILD_DIR = pjoin('build', 'nsis')
  14. DEFAULT_ICON = pjoin(_PKGDIR, 'glossyorb.ico')
  15. if os.name == 'nt' and sys.maxsize == (2**63)-1:
  16. DEFAULT_BITNESS = 64
  17. else:
  18. DEFAULT_BITNESS = 32
  19. def fetch_python(version=DEFAULT_PY_VERSION, bitness=DEFAULT_BITNESS,
  20. destination=DEFAULT_BUILD_DIR):
  21. """Fetch the MSI for the specified version of Python.
  22. It will be placed in the destination directory, and validated using GPG
  23. if possible.
  24. """
  25. arch_tag = '.amd64' if (bitness==64) else ''
  26. url = 'http://python.org/ftp/python/{0}/python-{0}{1}.msi'.format(version, arch_tag)
  27. target = pjoin(destination, 'python-{0}{1}.msi'.format(version, arch_tag))
  28. if os.path.isfile(target):
  29. logger.info('Python MSI already in build directory.')
  30. return
  31. logger.info('Downloading Python MSI...')
  32. urlretrieve(url, target)
  33. urlretrieve(url+'.asc', target+'.asc')
  34. try:
  35. keys_file = os.path.join(_PKGDIR, 'python-pubkeys.txt')
  36. check_output(['gpg', '--import', keys_file])
  37. check_output(['gpg', '--verify', target+'.asc'])
  38. except FileNotFoundError:
  39. logger.warn("GPG not available - could not check signature of {0}".format(target))
  40. def copy_extra_files(filelist, build_dir):
  41. results = [] # name, is_directory
  42. for file in filelist:
  43. file = file.rstrip('/\\')
  44. basename = os.path.basename(file)
  45. if os.path.isdir(file):
  46. target_name = pjoin(build_dir, basename)
  47. if os.path.isdir(target_name):
  48. shutil.rmtree(target_name)
  49. elif os.path.exists(target_name):
  50. os.unlink(target_name)
  51. shutil.copytree(file, target_name)
  52. results.append((basename, True))
  53. else:
  54. shutil.copy2(file, build_dir)
  55. results.append((basename, False))
  56. return results
  57. def make_installer_name(appname, version):
  58. s = appname + '_' + version + '.exe'
  59. return s.replace(' ', '_')
  60. def run_nsis(nsi_file):
  61. return call(['makensis', nsi_file])
  62. def all_steps(appname, version, script, icon=DEFAULT_ICON, console=False,
  63. packages=None, extra_files=None, py_version=DEFAULT_PY_VERSION,
  64. py_bitness=DEFAULT_BITNESS, build_dir=DEFAULT_BUILD_DIR,
  65. installer_name=None):
  66. installer_name = installer_name or make_installer_name(appname, version)
  67. os.makedirs(build_dir, exist_ok=True)
  68. fetch_python(version=py_version, bitness=py_bitness, destination=build_dir)
  69. shutil.copy2(script, build_dir)
  70. shutil.copy2(icon, build_dir)
  71. # Packages
  72. build_pkg_dir = pjoin(build_dir, 'pkgs')
  73. if os.path.isdir(build_pkg_dir):
  74. shutil.rmtree(build_pkg_dir)
  75. if os.path.isdir('pynsist_pkgs'):
  76. shutil.copytree('pynsist_pkgs', build_pkg_dir)
  77. else:
  78. os.mkdir(build_pkg_dir)
  79. copy_modules(packages or [], build_pkg_dir)
  80. nsis_writer = NSISFileWriter(pjoin(_PKGDIR, 'template.nsi'),
  81. definitions = {'PRODUCT_NAME': appname,
  82. 'PRODUCT_VERSION': version,
  83. 'PY_VERSION': py_version,
  84. 'SCRIPT': os.path.basename(script),
  85. 'PRODUCT_ICON': os.path.basename(icon),
  86. 'INSTALLER_NAME': installer_name,
  87. 'ARCH_TAG': '.amd64' if (py_bitness==64) else '',
  88. 'PY_EXE': 'py' if console else 'pyw',
  89. }
  90. )
  91. # Extra files
  92. nsis_writer.extra_files = copy_extra_files(extra_files or [], build_dir)
  93. nsi_file = pjoin(build_dir, 'installer.nsi')
  94. nsis_writer.write(nsi_file)
  95. exitcode = run_nsis(nsi_file)
  96. if not exitcode:
  97. logger.info('Installer written to %s', pjoin(build_dir, installer_name))
  98. def main(argv=None):
  99. logger.setLevel(logging.INFO)
  100. logger.addHandler(logging.StreamHandler())
  101. import argparse
  102. argp = argparse.ArgumentParser(prog='pynsist')
  103. argp.add_argument('config_file')
  104. options = argp.parse_args(argv)
  105. dirname, config_file = os.path.split(options.config_file)
  106. if dirname:
  107. os.chdir(dirname)
  108. import configparser
  109. cfg = configparser.ConfigParser()
  110. cfg.read(config_file)
  111. appcfg = cfg['Application']
  112. all_steps(
  113. appname = appcfg['name'],
  114. version = appcfg['version'],
  115. script = appcfg['script'],
  116. icon = appcfg.get('icon', DEFAULT_ICON),
  117. console = appcfg.getboolean('console', fallback=False),
  118. packages = cfg.get('Include', 'packages', fallback='').splitlines(),
  119. extra_files = cfg.get('Include', 'files', fallback='').splitlines(),
  120. py_version = cfg.get('Python', 'version', fallback=DEFAULT_PY_VERSION),
  121. py_bitness = cfg.getint('Python', 'bitness', fallback=DEFAULT_BITNESS),
  122. build_dir = cfg.get('Build', 'directory', fallback=DEFAULT_BUILD_DIR),
  123. installer_name = cfg.get('Build', 'installer_name', fallback=None),
  124. )