mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
This PR defines pipelines that can be run on Azure Pipelines. Currently there are two:
* `.azure-pipelines/main.yml` is the main one, executed on PRs for master, and pushes to master,
* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for master.
These two pipelines covers all existing stuff done by AppVeyor currently, and so AppVeyor can be decommissioned once Azure Pipelines is operational.
You can see working pipeline in my fork:
* a PR for `master` (so using main pipeline): https://github.com/adferrand/certbot/pull/65
* a PR for `test-something` (so using advanced pipeline): https://github.com/adferrand/certbot/pull/66
* uploaded coverage from Azure Pipelines: 499aa2cbf2/build
Once this PR is merged, we need to enable Azure Pipelines for Certbot. Instructions are written in `azure-pipelines/INSTALL.md`. This document also references all access rights required to Azure Pipelines onto GitHub to make the CI process work.
Future work for future PRs:
* create a CD pipeline for the releases that will push the installer to GitHub releases
* implement a solution to generate notification on IRC or Mattermost when a nightly build fails
* Define pipelines
* Update locations
* Update nightly
* Use x86
* Update nightly.yml for Azure Pipelines
* Run script
* Use script
* Update install
* Use local installation
* Register warnings
* Fix pywin32 loading
* Clean context
* Enable coverage publication
* Consume codecov token
* Document installation
* Update tool to upload coverage
* Prepare pipeline artifacts
* Update artifact ignore
* Protect against codecov failures
* Add a comment about codecov
* Add a comment on RW access asked by Azure
* Add instructions
* Rename pipeline file
* Update instructions
* Update .azure-pipelines/templates/tests-suite.yml
Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>
* Update .azure-pipelines/INSTALL.md
Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>
* Modified scheduled pipeline
* Add comment
* Remove dynamic version-based installer name
171 lines
6.0 KiB
Python
171 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
import ctypes
|
|
import struct
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import time
|
|
|
|
|
|
PYTHON_VERSION = (3, 7, 4)
|
|
PYTHON_BITNESS = 32
|
|
|
|
|
|
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', '.']
|
|
# 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]
|
|
|
|
command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_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(['choco', 'upgrade', '-y', 'nsis'])
|
|
subprocess.check_call([venv_python, '-m', 'pip', 'install', '--upgrade', 'pip'])
|
|
subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'wheel', 'pynsist'])
|
|
|
|
|
|
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_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'],
|
|
universal_newlines=True, cwd=repo_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-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()
|