EVOLUTION-MANAGER
Edit File: construct.py
#!/usr/bin/env python3 import contextlib import ctypes import os import shutil import struct import subprocess import sys import tempfile import time PYTHON_VERSION = (3, 7, 4) PYTHON_BITNESS = 32 PYWIN32_VERSION = 227 # do not forget to edit pywin32 dependency accordingly in setup.py NSIS_VERSION = '3.04' def main(): build_path, repo_path, venv_path, venv_python = _prepare_environment() _copy_assets(build_path, repo_path) installer_cfg_path = _generate_pynsist_config(repo_path, build_path) _prepare_build_tools(venv_path, venv_python, repo_path) _compile_wheels(repo_path, build_path, venv_python) _build_installer(installer_cfg_path, venv_path) print('Done') def _build_installer(installer_cfg_path, venv_path): print('Build the installer') subprocess.check_call([os.path.join(venv_path, 'Scripts', 'pynsist.exe'), installer_cfg_path]) def _compile_wheels(repo_path, build_path, venv_python): print('Compile wheels') wheels_path = os.path.join(build_path, 'wheels') os.makedirs(wheels_path) certbot_packages = ['acme', 'certbot'] # Uncomment following line to include all DNS plugins in the installer # certbot_packages.extend([name for name in os.listdir(repo_path) if name.startswith('certbot-dns-')]) wheels_project = [os.path.join(repo_path, package) for package in certbot_packages] with _prepare_constraints(repo_path) as constraints_file_path: command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path, '--constraint', constraints_file_path] command.extend(wheels_project) subprocess.check_call(command) def _prepare_build_tools(venv_path, venv_python, repo_path): print('Prepare build tools') subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) subprocess.check_call([venv_python, os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'pipstrap.py')]) subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'pynsist']) subprocess.check_call(['choco', 'upgrade', '-y', 'nsis', '--version', NSIS_VERSION]) @contextlib.contextmanager def _prepare_constraints(repo_path): requirements = os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'dependency-requirements.txt') constraints = subprocess.check_output( [sys.executable, os.path.join(repo_path, 'tools', 'strip_hashes.py'), requirements], universal_newlines=True) workdir = tempfile.mkdtemp() try: constraints_file_path = os.path.join(workdir, 'constraints.txt') with open(constraints_file_path, 'a') as file_h: file_h.write(constraints) file_h.write('pywin32=={0}'.format(PYWIN32_VERSION)) yield constraints_file_path finally: shutil.rmtree(workdir) def _copy_assets(build_path, repo_path): print('Copy assets') if os.path.exists(build_path): os.rename(build_path, '{0}.{1}.bak'.format(build_path, int(time.time()))) os.makedirs(build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'certbot.ico'), build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'run.bat'), build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'template.nsi'), build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-up.ps1'), build_path) shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-down.ps1'), build_path) def _generate_pynsist_config(repo_path, build_path): print('Generate pynsist configuration') pywin32_paths_file = os.path.join(build_path, 'pywin32_paths.py') # Pywin32 uses non-standard folders to hold its packages. We need to instruct pynsist bootstrap # explicitly to add them into sys.path. This is done with a custom "pywin32_paths.py" that is # referred in the pynsist configuration as an "extra_preamble". # Reference example: https://github.com/takluyver/pynsist/tree/master/examples/pywebview with open(pywin32_paths_file, 'w') as file_h: file_h.write('''\ pkgdir = os.path.join(os.path.dirname(installdir), 'pkgs') sys.path.extend([ os.path.join(pkgdir, 'win32'), os.path.join(pkgdir, 'win32', 'lib'), ]) # Preload pywintypes and pythoncom pwt = os.path.join(pkgdir, 'pywin32_system32', 'pywintypes{0}{1}.dll') pcom = os.path.join(pkgdir, 'pywin32_system32', 'pythoncom{0}{1}.dll') import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") import imp imp.load_dynamic('pywintypes', pwt) imp.load_dynamic('pythoncom', pcom) '''.format(PYTHON_VERSION[0], PYTHON_VERSION[1])) installer_cfg_path = os.path.join(build_path, 'installer.cfg') certbot_pkg_path = os.path.join(repo_path, 'certbot') certbot_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'], universal_newlines=True, cwd=certbot_pkg_path).strip() with open(installer_cfg_path, 'w') as file_h: file_h.write('''\ [Application] name=Certbot version={certbot_version} icon=certbot.ico publisher=Electronic Frontier Foundation target=$INSTDIR\\run.bat [Build] directory=nsis nsi_template=template.nsi installer_name=certbot-beta-installer-{installer_suffix}.exe [Python] version={python_version} bitness={python_bitness} [Include] local_wheels=wheels\\*.whl files=run.bat renew-up.ps1 renew-down.ps1 [Command certbot] entry_point=certbot.main:main extra_preamble=pywin32_paths.py '''.format(certbot_version=certbot_version, installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32', python_bitness=PYTHON_BITNESS, python_version='.'.join([str(item) for item in PYTHON_VERSION]))) return installer_cfg_path def _prepare_environment(): print('Prepare environment') try: subprocess.check_output(['choco', '--version']) except subprocess.CalledProcessError: raise RuntimeError('Error: Chocolatey (https://chocolatey.org/) needs ' 'to be installed to run this script.') script_path = os.path.realpath(__file__) repo_path = os.path.dirname(os.path.dirname(script_path)) build_path = os.path.join(repo_path, 'windows-installer', 'build') venv_path = os.path.join(build_path, 'venv-config') venv_python = os.path.join(venv_path, 'Scripts', 'python.exe') return build_path, repo_path, venv_path, venv_python if __name__ == '__main__': if not os.name == 'nt': raise RuntimeError('This script must be run under Windows.') if ctypes.windll.shell32.IsUserAnAdmin() == 0: # Administrator privileges are required to properly install NSIS through Chocolatey raise RuntimeError('This script must be run with administrator privileges.') if sys.version_info[:2] != PYTHON_VERSION[:2]: raise RuntimeError('This script must be run with Python {0}' .format('.'.join([str(item) for item in PYTHON_VERSION[0:2]]))) if struct.calcsize('P') * 8 != PYTHON_BITNESS: raise RuntimeError('This script must be run with a {0} bit version of Python.' .format(PYTHON_BITNESS)) main()