Such CI, Very Wow. (#3182)

This commit is contained in:
Maximilian Hils 2018-06-02 21:37:44 +02:00 committed by Thomas Kriechbaumer
parent 28bd1275b2
commit 09ff5df2fb
11 changed files with 257 additions and 222 deletions

View File

@ -12,6 +12,7 @@ environment:
- PYTHON: "C:\\Python36"
TOXENV: "py36"
PYINSTALLER: "1"
WININSTALLER: "1"
install:
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
@ -19,41 +20,17 @@ install:
- "pip install -U tox"
test_script:
- ps: "tox -- --verbose --cov-report=term"
- ps: |
$Env:VERSION = $(python -m mitmproxy.version)
tox -e cibuild -- build
- ps: |
if($Env:RTOOL_KEY) {
$ibVersion = "18.2.0"
$ibSetup = "C:\projects\mitmproxy\release\installbuilder-installer.exe"
$ibCli = "C:\Program Files (x86)\BitRock InstallBuilder Enterprise $ibVersion\bin\builder-cli.exe"
if (!(Test-Path $ibSetup)) {
echo "Download InstallBuilder..."
(New-Object System.Net.WebClient).DownloadFile(
"https://installbuilder.bitrock.com/installbuilder-enterprise-$ibVersion-windows-installer.exe",
$ibSetup
)
}
echo "Install InstallBuilder..."
Start-Process $ibSetup "--mode unattended --unattendedmodeui none" -PassThru -NoNewWindow -Wait
echo "Decrypt license..."
tox -e cibuild -- decrypt release\installbuilder\license.xml.enc release\installbuilder\license.xml
echo "Run InstallBuilder..."
&$ibCli `
build `
.\release\installbuilder\mitmproxy.xml `
windows `
--license .\release\installbuilder\license.xml `
--setvars project.version=$Env:VERSION `
--verbose
while (!(Test-Path C:\projects\mitmproxy\release\dist\mitmproxy-*-windows-installer.exe)) { Start-Sleep 0.1 }
echo "Installer build completed."
if ($env:APPVEYOR_REPO_COMMIT_MESSAGE.Contains("[notest]")) {
echo "!!!! Skipping tests."
} else {
tox -- --verbose --cov-report=term
}
- ps: tox -e cibuild -- build
deploy_script:
ps: tox -e cibuild -- upload
cache:
- C:\projects\mitmproxy\release\installbuilder-installer.exe -> .appveyor.yml
- C:\projects\mitmproxy\release\installbuilder\setup -> .appveyor.yml
- C:\Users\appveyor\AppData\Local\pip\cache

View File

@ -74,7 +74,13 @@ script:
# Using the after_success block DOES NOT capture errors.
# Pull requests might break our build - we need to check this.
# Pull requests are not allowed to upload build artifacts - enforced by cibuild script.
- tox -- --verbose --cov-report=term
- |
if [[ $TRAVIS_COMMIT_MESSAGE = *"[notest]"* ]]; then
echo "!!!! Skipping tests."
else
tox -- --verbose --cov-report=term
fi
- |
if [[ $CIBUILD == "1" ]]
then

View File

@ -1,7 +1,6 @@
import gc
import os
import platform
import re
import signal
import sys
import threading
@ -13,21 +12,10 @@ from mitmproxy import version
def dump_system_info():
mitmproxy_version = version.get_version(True, True)
mitmproxy_version = re.sub(
r"-0x([0-9a-f]+)",
r" (commit \1)",
mitmproxy_version
)
# PyInstaller builds indicator, if using precompiled binary
if getattr(sys, 'frozen', False):
bin_indicator = "binary"
else:
bin_indicator = ""
mitmproxy_version = version.get_dev_version()
data = [
"Mitmproxy: {} {}".format(mitmproxy_version, bin_indicator),
"Mitmproxy: {}".format(mitmproxy_version),
"Python: {}".format(platform.python_version()),
"OpenSSL: {}".format(SSL.SSLeay_version(SSL.SSLEAY_VERSION).decode()),
"Platform: {}".format(platform.platform()),

View File

@ -1,9 +1,8 @@
import os
import subprocess
import sys
# The actual version string. For precompiled binaries, this will be changed to include the build
# tag, e.g. "3.0.0.dev0042-0xcafeabc"
VERSION = "5.0.0"
VERSION = "5.0.0.dev"
PATHOD = "pathod " + VERSION
MITMPROXY = "mitmproxy " + VERSION
@ -12,24 +11,13 @@ MITMPROXY = "mitmproxy " + VERSION
FLOW_FORMAT_VERSION = 7
def get_version(dev: bool = False, build: bool = False, refresh: bool = False) -> str:
def get_dev_version() -> str:
"""
Return a detailed version string, sourced either from a hardcoded VERSION constant
or obtained dynamically using git.
Args:
dev: If True, non-tagged releases will include a ".devXXXX" suffix, where XXXX is the number
of commits since the last tagged release.
build: If True, non-tagged releases will include a "-0xXXXXXXX" suffix, where XXXXXXX are
the first seven digits of the commit hash.
refresh: If True, always try to use git instead of a potentially hardcoded constant.
Return a detailed version string, sourced either from VERSION or obtained dynamically using git.
"""
mitmproxy_version = VERSION
if "dev" in VERSION and not refresh:
pass # There is a hardcoded build tag, so we just use what's there.
elif dev or build:
here = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
try:
git_describe = subprocess.check_output(
@ -43,19 +31,13 @@ def get_version(dev: bool = False, build: bool = False, refresh: bool = False) -
except Exception:
pass
else:
# Remove current suffix
mitmproxy_version = mitmproxy_version.split(".dev")[0]
# Add suffix for non-tagged releases
# Add commit info for non-tagged releases
if tag_dist > 0:
mitmproxy_version += ".dev{tag_dist}".format(tag_dist=tag_dist)
# The wheel build tag (we use the commit) must start with a digit, so we include "0x"
mitmproxy_version += "-0x{commit}".format(commit=commit)
mitmproxy_version += f" (+{tag_dist}, commit {commit})"
if not dev:
mitmproxy_version = mitmproxy_version.split(".dev")[0]
elif not build:
mitmproxy_version = mitmproxy_version.split("-0x")[0]
# PyInstaller build indicator, if using precompiled binary
if getattr(sys, 'frozen', False):
mitmproxy_version += " binary"
return mitmproxy_version

View File

@ -1,14 +1,16 @@
#!/usr/bin/env python3
import glob
import re
import contextlib
import glob
import os
import pathlib
import platform
import sys
import re
import shutil
import subprocess
import sys
import tarfile
import urllib.request
import zipfile
import click
@ -40,24 +42,21 @@ class BuildEnviron:
*,
system="",
root_dir="",
travis_tag="",
travis_branch="",
travis_pull_request="",
appveyor_repo_tag_name="",
appveyor_repo_branch="",
appveyor_pull_request_number="",
should_build_wheel=False,
should_build_docker=False,
should_build_pyinstaller=False,
should_build_wininstaller=False,
has_aws_creds=False,
has_twine_creds=False,
docker_username="",
docker_password="",
rtool_key="",
):
self.system = system
self.root_dir = root_dir
@ -69,6 +68,7 @@ class BuildEnviron:
self.should_build_wheel = should_build_wheel
self.should_build_docker = should_build_docker
self.should_build_pyinstaller = should_build_pyinstaller
self.should_build_wininstaller = should_build_wininstaller
self.appveyor_repo_tag_name = appveyor_repo_tag_name
self.appveyor_repo_branch = appveyor_repo_branch
@ -78,33 +78,31 @@ class BuildEnviron:
self.has_twine_creds = has_twine_creds
self.docker_username = docker_username
self.docker_password = docker_password
self.rtool_key = rtool_key
@classmethod
def from_env(klass):
return klass(
def from_env(cls):
return cls(
system=platform.system(),
root_dir=os.path.normpath(os.path.join(os.path.dirname(__file__), "..")),
travis_tag=os.environ.get("TRAVIS_TAG", ""),
travis_branch=os.environ.get("TRAVIS_BRANCH", ""),
travis_pull_request=os.environ.get("TRAVIS_PULL_REQUEST"),
appveyor_repo_tag_name=os.environ.get("APPVEYOR_REPO_TAG_NAME", ""),
appveyor_repo_branch=os.environ.get("APPVEYOR_REPO_BRANCH", ""),
appveyor_pull_request_number=os.environ.get("APPVEYOR_PULL_REQUEST_NUMBER"),
should_build_wheel="WHEEL" in os.environ,
should_build_pyinstaller="PYINSTALLER" in os.environ,
should_build_wininstaller="WININSTALLER" in os.environ,
should_build_docker="DOCKER" in os.environ,
has_aws_creds="AWS_ACCESS_KEY_ID" in os.environ,
has_twine_creds=(
"TWINE_USERNAME" in os.environ and
"TWINE_PASSWORD" in os.environ
),
docker_username=os.environ.get("DOCKER_USERNAME"),
docker_password=os.environ.get("DOCKER_PASSWORD"),
rtool_key=os.environ.get("RTOOL_KEY"),
)
def archive(self, path):
@ -177,16 +175,45 @@ class BuildEnviron:
"should_upload_pypi",
]
for attr in lst:
print("cibuild.%s=%s" % (attr, getattr(self, attr)), file=fp)
print(f"cibuild.{attr}={getattr(self, attr)}", file=fp)
def check_version(self) -> None:
"""
Check that version numbers match our conventions.
Raises a ValueError if there is a mismatch.
"""
with open(pathlib.Path(self.root_dir) / "mitmproxy" / "version.py") as f:
contents = f.read()
version = re.search(r'^VERSION = "(.+?)"', contents, re.M).group(1)
if self.tag:
# For (tagged) releases, we are strict:
# 1. The tagname must match the version in mitmproxy/version.py
# 2. The version info must be in canonical form (as recommended in PEP 440).
if version != self.tag:
raise ValueError(f"Tag is {self.tag}, but mitmproxy/version.py is {version}.")
try:
parver.Version.parse(version, strict=True)
except parver.ParseError as e:
raise ValueError(str(e)) from e
else:
# For snapshots, we only ensure that mitmproxy/version.py contains a dev release.
version_info = parver.Version.parse(version)
if not version_info.is_devrelease:
raise ValueError("Releases must be tagged.")
@property
def has_docker_creds(self) -> bool:
return self.docker_username and self.docker_password
return bool(self.docker_username and self.docker_password)
@property
def is_prod_release(self) -> bool:
if not self.tag:
return False
try:
v = parver.Version.parse(self.version)
v = parver.Version.parse(self.version, strict=True)
except (parver.ParseError, BuildError):
return False
return not v.is_prerelease
@ -212,7 +239,7 @@ class BuildEnviron:
@property
def should_upload_docker(self) -> bool:
return all([
(self.tag or self.branch == "master"),
(self.is_prod_release or self.branch == "master"),
self.should_build_docker,
self.has_docker_creds,
])
@ -220,7 +247,6 @@ class BuildEnviron:
@property
def should_upload_pypi(self) -> bool:
return all([
self.tag,
self.is_prod_release,
self.should_build_wheel,
self.has_twine_creds,
@ -242,7 +268,7 @@ class BuildEnviron:
name = self.tag or self.branch
if not name:
raise BuildError("We're on neither a tag nor a branch - could not establish version")
return re.sub('^v', "", name)
return name
def build_wheel(be: BuildEnviron): # pragma: no cover
@ -354,6 +380,56 @@ def build_pyinstaller(be: BuildEnviron): # pragma: no cover
click.echo("Packed {}.".format(be.archive_name(bdist)))
def build_wininstaller(be: BuildEnviron): # pragma: no cover
click.echo("Building wininstaller package...")
IB_VERSION = "18.5.2"
IB_DIR = pathlib.Path(be.release_dir) / "installbuilder"
IB_SETUP = IB_DIR / "setup" / f"{IB_VERSION}-installer.exe"
IB_CLI = fr"C:\Program Files (x86)\BitRock InstallBuilder Enterprise {IB_VERSION}\bin\builder-cli.exe"
IB_LICENSE = IB_DIR / "license.xml"
if True or not os.path.isfile(IB_CLI):
if not os.path.isfile(IB_SETUP):
click.echo("Downloading InstallBuilder...")
def report(block, blocksize, total):
done = block * blocksize
if round(100 * done / total) != round(100 * (done - blocksize) / total):
click.secho(f"Downloading... {round(100*done/total)}%")
urllib.request.urlretrieve(
f"https://installbuilder.bitrock.com/installbuilder-enterprise-{IB_VERSION}-windows-installer.exe",
IB_SETUP.with_suffix(".tmp"),
reporthook=report
)
shutil.move(IB_SETUP.with_suffix(".tmp"), IB_SETUP)
click.echo("Install InstallBuilder...")
subprocess.run([str(IB_SETUP), "--mode", "unattended", "--unattendedmodeui", "none"],
check=True)
assert os.path.isfile(IB_CLI)
click.echo("Decrypt InstallBuilder license...")
f = cryptography.fernet.Fernet(be.rtool_key.encode())
with open(IB_LICENSE.with_suffix(".xml.enc"), "rb") as infile, open(IB_LICENSE,
"wb") as outfile:
outfile.write(f.decrypt(infile.read()))
click.echo("Run InstallBuilder...")
subprocess.run([
IB_CLI,
"build",
str(IB_DIR / "mitmproxy.xml"),
"windows",
"--license", str(IB_LICENSE),
"--setvars", f"project.version={be.version}",
"--verbose"
], check=True)
assert os.path.isfile(
os.path.join(be.dist_dir, f"mitmproxy-{be.version}-windows-installer.exe"))
@click.group(chain=True)
def cli(): # pragma: no cover
"""
@ -370,6 +446,7 @@ def build(): # pragma: no cover
be = BuildEnviron.from_env()
be.dump_info()
be.check_version()
os.makedirs(be.dist_dir, exist_ok=True)
if be.should_build_wheel:
@ -379,6 +456,8 @@ def build(): # pragma: no cover
build_docker_image(be, whl)
if be.should_build_pyinstaller:
build_pyinstaller(be)
if be.should_build_wininstaller and be.rtool_key:
build_wininstaller(be)
@cli.command("upload")
@ -421,14 +500,5 @@ def upload(): # pragma: no cover
subprocess.check_call(["docker", "push", be.docker_tag])
@cli.command("decrypt")
@click.argument('infile', type=click.File('rb'))
@click.argument('outfile', type=click.File('wb'))
@click.argument('key', envvar='RTOOL_KEY')
def decrypt(infile, outfile, key): # pragma: no cover
f = cryptography.fernet.Fernet(key.encode())
outfile.write(f.decrypt(infile.read()))
if __name__ == "__main__": # pragma: no cover
cli()

View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -13,7 +13,7 @@ with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
long_description = f.read()
with open(os.path.join(here, "mitmproxy", "version.py")) as f:
VERSION = re.search(r'VERSION = "(.+?)(?:-0x|")', f.read()).group(1)
VERSION = re.search(r'VERSION = "(.+?)"', f.read()).group(1)
setup(
name="mitmproxy",

View File

@ -1,6 +1,7 @@
import pathlib
import runpy
import subprocess
import sys
from unittest import mock
from mitmproxy import version
@ -15,22 +16,19 @@ def test_version(capsys):
assert stdout.strip() == version.VERSION
def test_get_version_hardcoded():
version.VERSION = "3.0.0.dev123-0xcafebabe"
assert version.get_version() == "3.0.0"
assert version.get_version(True) == "3.0.0.dev123"
assert version.get_version(True, True) == "3.0.0.dev123-0xcafebabe"
def test_get_version():
version.VERSION = "3.0.0"
version.VERSION = "3.0.0rc2"
with mock.patch('subprocess.check_output') as m:
m.return_value = b"tag-0-cafecafe"
assert version.get_version(True, True) == "3.0.0"
assert version.get_dev_version() == "3.0.0rc2"
sys.frozen = True
assert version.get_dev_version() == "3.0.0rc2 binary"
sys.frozen = False
m.return_value = b"tag-2-cafecafe"
assert version.get_version(True, True) == "3.0.0.dev2-0xcafecaf"
assert version.get_dev_version() == "3.0.0rc2 (+2, commit cafecaf)"
m.side_effect = subprocess.CalledProcessError(-1, 'git describe --long')
assert version.get_version(True, True) == "3.0.0"
assert version.get_dev_version() == "3.0.0rc2"

View File

@ -1,5 +1,5 @@
import os
import io
import os
import pytest
@ -15,9 +15,7 @@ def test_buildenviron_common():
be = cibuild.BuildEnviron(
system="Linux",
root_dir="/foo",
travis_tag = "v0.0.1",
travis_branch = "v0.x",
travis_branch="master",
)
assert be.release_dir == os.path.join(be.root_dir, "release")
assert be.dist_dir == os.path.join(be.root_dir, "release", "dist")
@ -46,7 +44,6 @@ def test_buildenviron_pr():
travis_tag="",
travis_branch="master",
travis_pull_request="true",
should_build_wheel=True,
should_build_pyinstaller=True,
should_build_docker=True,
@ -67,7 +64,6 @@ def test_buildenviron_commit():
travis_tag="",
travis_branch="master",
travis_pull_request="false",
should_build_wheel=True,
should_build_pyinstaller=True,
should_build_docker=True,
@ -81,15 +77,13 @@ def test_buildenviron_commit():
assert not be.is_prod_release
def test_buildenviron_rleasetag():
def test_buildenviron_releasetag():
# Simulates a tagged release on a release branch.
be = cibuild.BuildEnviron(
system="Linux",
root_dir="/foo",
travis_tag = "v0.0.1",
travis_tag="0.0.1",
travis_branch="v0.x",
should_build_wheel=True,
should_build_docker=True,
should_build_pyinstaller=True,
@ -97,7 +91,7 @@ def test_buildenviron_rleasetag():
docker_username="foo",
docker_password="bar",
)
assert be.tag == "v0.0.1"
assert be.tag == "0.0.1"
assert be.branch == "v0.x"
assert be.version == "0.0.1"
assert be.upload_dir == "0.0.1"
@ -112,10 +106,8 @@ def test_buildenviron_branch():
be = cibuild.BuildEnviron(
system="Linux",
root_dir="/foo",
travis_tag="",
travis_branch="mybranch",
should_build_wheel=True,
should_build_docker=True,
should_build_pyinstaller=True,
@ -135,8 +127,7 @@ def test_buildenviron_osx(tmpdir):
be = cibuild.BuildEnviron(
system="Darwin",
root_dir="/foo",
travis_tag = "v0.0.1",
travis_tag="0.0.1",
travis_branch="v0.x",
)
assert be.platform_tag == "osx"
@ -155,8 +146,7 @@ def test_buildenviron_windows(tmpdir):
be = cibuild.BuildEnviron(
system="Windows",
root_dir="/foo",
travis_tag = "v0.0.1",
travis_tag="0.0.1",
travis_branch="v0.x",
)
assert be.platform_tag == "windows"
@ -169,3 +159,25 @@ def test_buildenviron_windows(tmpdir):
a = be.archive(os.path.join(tmpdir, "arch"))
assert a
a.close()
@pytest.mark.parametrize("version, tag, ok", [
("3.0.0.dev", "", True), # regular snapshot
("3.0.0.dev", "3.0.0", False), # forgot to remove ".dev" on bump
("3.0.0", "", False), # forgot to re-add ".dev"
("3.0.0", "4.0.0", False), # version mismatch
("3.0.0", "3.0.0", True), # regular release
("3.0.0.rc1", "3.0.0.rc1", False), # non-canonical.
])
def test_buildenviron_check_version(version, tag, ok, tmpdir):
tmpdir.mkdir("mitmproxy").join("version.py").write(f'VERSION = "{version}"')
be = cibuild.BuildEnviron(
root_dir=tmpdir,
travis_tag=tag
)
if ok:
be.check_version()
else:
with pytest.raises(ValueError):
be.check_version()

View File

@ -33,7 +33,7 @@ commands =
python ./test/individual_coverage.py
[testenv:cibuild]
passenv = TRAVIS_* APPVEYOR_* AWS_* TWINE_* DOCKER_* RTOOL_KEY WHEEL DOCKER PYINSTALLER
passenv = TRAVIS_* APPVEYOR_* AWS_* TWINE_* DOCKER_* RTOOL_KEY WHEEL DOCKER PYINSTALLER WININSTALLER
deps =
-rrequirements.txt
pyinstaller==3.3.1