mirror of
https://github.com/certbot/certbot.git
synced 2026-01-27 19:42:53 +03:00
Current version of pywin32 used in certbot (225) does not have wheels available for Python 3.8. Installing certbot for development in this case requires to build from source. On Windows, this implies a Visual Studio C++ environment up and ready, which is absolutely not fun. Let's upgrade to pywin32 227, that provides these wheels for all Python versions from 3.5 up to current dev status of 3.9.
193 lines
7.1 KiB
Python
193 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
|
import contextlib
|
|
import ctypes
|
|
import struct
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import shutil
|
|
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()
|