mitmproxy/release/rtool.py

440 lines
14 KiB
Python
Raw Normal View History

2015-11-29 01:46:08 +00:00
#!/usr/bin/env python
from __future__ import absolute_import, print_function, division
from os.path import join
import contextlib
import os
import shutil
import subprocess
import re
import shlex
import runpy
2015-12-03 16:55:42 +00:00
import zipfile
2015-11-29 02:38:23 +00:00
import tarfile
2015-11-29 01:46:08 +00:00
import platform
import click
2016-02-11 17:51:47 +00:00
import pysftp
2016-02-12 20:57:48 +00:00
import fnmatch
2016-02-11 17:51:47 +00:00
from six.moves import shlex_quote
2015-11-29 01:46:08 +00:00
# https://virtualenv.pypa.io/en/latest/userguide.html#windows-notes
# scripts and executables on Windows go in ENV\Scripts\ instead of ENV/bin/
if platform.system() == "Windows":
VENV_BIN = "Scripts"
else:
VENV_BIN = "bin"
if platform.system() == "Windows":
def Archive(name):
2016-02-11 17:51:47 +00:00
a = zipfile.ZipFile(name, "w")
2015-11-29 01:46:08 +00:00
a.add = a.write
return a
else:
def Archive(name):
2016-02-11 17:51:47 +00:00
return tarfile.open(name, "w:gz")
2015-11-29 01:46:08 +00:00
RELEASE_DIR = join(os.path.dirname(os.path.realpath(__file__)))
DIST_DIR = join(RELEASE_DIR, "dist")
ROOT_DIR = join(RELEASE_DIR, "..")
BUILD_DIR = join(RELEASE_DIR, "build")
PYINSTALLER_TEMP = join(BUILD_DIR, "pyinstaller")
PYINSTALLER_DIST = join(BUILD_DIR, "binaries")
VENV_DIR = join(BUILD_DIR, "venv")
VENV_PIP = join(VENV_DIR, VENV_BIN, "pip")
VENV_PYINSTALLER = join(VENV_DIR, VENV_BIN, "pyinstaller")
ALL_PROJECTS = {
"netlib": {
"tools": [],
"vfile": join(ROOT_DIR, "netlib/netlib/version.py"),
2016-02-11 17:51:47 +00:00
"dir": join(ROOT_DIR, "netlib"),
"python_version": "py2.py3" # this is the format in wheel filenames
2015-11-29 01:46:08 +00:00
},
"pathod": {
"tools": ["pathod", "pathoc"],
"vfile": join(ROOT_DIR, "pathod/libpathod/version.py"),
2016-02-11 17:51:47 +00:00
"dir": join(ROOT_DIR, "pathod"),
"python_version": "py2"
2015-11-29 01:46:08 +00:00
},
"mitmproxy": {
"tools": ["mitmproxy", "mitmdump", "mitmweb"],
"vfile": join(ROOT_DIR, "mitmproxy/libmproxy/version.py"),
2016-02-11 17:51:47 +00:00
"dir": join(ROOT_DIR, "mitmproxy"),
"python_version": "py2"
2015-11-29 01:46:08 +00:00
}
}
if platform.system() == "Windows":
ALL_PROJECTS["mitmproxy"]["tools"].remove("mitmproxy")
projects = {}
2015-12-03 16:55:42 +00:00
def get_version(project):
2015-11-29 01:46:08 +00:00
return runpy.run_path(projects[project]["vfile"])["VERSION"]
2015-12-03 16:55:42 +00:00
2016-02-11 17:51:47 +00:00
def get_snapshot_version(project):
last_tag, tag_dist, commit = subprocess.check_output(
["git", "describe", "--tags", "--long"],
cwd=projects[project]["dir"]
).strip().rsplit("-", 2)
tag_dist = int(tag_dist)
if tag_dist == 0:
return get_version(project)
else:
return "{version}dev{tag_dist:04}-{commit}".format(
version=get_version(project), # this should already be the next version
tag_dist=tag_dist,
commit=commit
)
def archive_name(project):
platform_tag = {
"Darwin": "osx",
"Windows": "win32",
"Linux": "linux"
}.get(platform.system(), platform.system())
if platform.system() == "Windows":
ext = "zip"
else:
ext = "tar.gz"
return "{project}-{version}-{platform}.{ext}".format(
project=project,
version=get_version(project),
platform=platform_tag,
ext=ext
)
2015-11-29 01:46:08 +00:00
def sdist_name(project):
2015-11-29 18:05:58 +00:00
return "{project}-{version}.tar.gz".format(
project=project,
2015-12-03 16:55:42 +00:00
version=get_version(project)
2015-11-29 18:05:58 +00:00
)
2015-12-03 16:55:42 +00:00
2015-11-29 18:05:58 +00:00
def wheel_name(project):
2016-02-11 17:51:47 +00:00
return "{project}-{version}-{py_version}-none-any.whl".format(
2015-11-29 18:05:58 +00:00
project=project,
2015-12-03 16:55:42 +00:00
version=get_version(project),
2016-02-11 17:51:47 +00:00
py_version=projects[project]["python_version"]
2015-11-29 18:05:58 +00:00
)
2015-11-29 01:46:08 +00:00
2015-12-03 16:55:42 +00:00
2015-11-29 01:46:08 +00:00
@contextlib.contextmanager
def empty_pythonpath():
"""
Make sure that the regular python installation is not on the python path,
which would give us access to modules installed outside of our virtualenv.
"""
2015-12-03 16:55:42 +00:00
pythonpath = os.environ.get("PYTHONPATH", "")
2015-11-29 01:46:08 +00:00
os.environ["PYTHONPATH"] = ""
yield
os.environ["PYTHONPATH"] = pythonpath
@contextlib.contextmanager
def chdir(path):
old_dir = os.getcwd()
os.chdir(path)
yield
os.chdir(old_dir)
@click.group(chain=True)
@click.option(
'--project', '-p',
multiple=True, type=click.Choice(ALL_PROJECTS.keys()), default=ALL_PROJECTS.keys()
)
def cli(project):
"""
mitmproxy build tool
"""
for name in project:
projects[name] = ALL_PROJECTS[name]
2015-12-03 16:55:42 +00:00
2015-11-29 01:46:08 +00:00
@cli.command("contributors")
def contributors():
"""
Update CONTRIBUTORS.md
"""
for project, conf in projects.items():
with chdir(conf["dir"]):
print("Updating %s/CONTRIBUTORS..." % project)
contributors_data = subprocess.check_output(
shlex.split("git shortlog -n -s")
)
with open("CONTRIBUTORS", "w") as f:
f.write(contributors_data)
2015-12-03 16:55:42 +00:00
2015-11-29 01:46:08 +00:00
@cli.command("set-version")
@click.argument('version')
def set_version(version):
"""
Update version information
"""
print("Update versions...")
version = ", ".join(version.split("."))
for p, conf in projects.items():
print("Update %s..." % os.path.normpath(conf["vfile"]))
with open(conf["vfile"], "rb") as f:
content = f.read()
new_content = re.sub(
r"IVERSION\s*=\s*\([\d,\s]+\)", "IVERSION = (%s)" % version,
content
)
with open(conf["vfile"], "wb") as f:
f.write(new_content)
2016-02-11 17:51:47 +00:00
def _git(project, args):
2016-02-14 16:28:25 +00:00
print("%s> %s..." % (project, " ".join(shlex_quote(a) for a in args)))
2016-02-11 17:51:47 +00:00
subprocess.check_call(
2016-02-14 16:28:25 +00:00
["git"] + list(args),
2016-02-11 17:51:47 +00:00
cwd=projects[project]["dir"]
)
2015-11-29 01:46:08 +00:00
@cli.command("git")
@click.argument('args', nargs=-1, required=True)
def git(args):
"""
Run a git command on every project
"""
for project, conf in projects.items():
2016-02-11 17:51:47 +00:00
_git(project, args)
2016-02-14 16:28:25 +00:00
print("")
2015-11-29 01:46:08 +00:00
@cli.command("sdist")
def sdist():
"""
Build a source distribution
"""
with empty_pythonpath():
print("Building release...")
if os.path.exists(DIST_DIR):
shutil.rmtree(DIST_DIR)
for project, conf in projects.items():
print("Creating %s source distribution..." % project)
subprocess.check_call(
[
2015-12-03 16:55:42 +00:00
"python", "./setup.py", "-q",
"sdist", "--dist-dir", DIST_DIR, "--formats=gztar",
2016-02-04 22:03:13 +00:00
"bdist_wheel", "--dist-dir", DIST_DIR,
2015-11-29 01:46:08 +00:00
],
cwd=conf["dir"]
)
print("Creating virtualenv for test install...")
if os.path.exists(VENV_DIR):
shutil.rmtree(VENV_DIR)
subprocess.check_call(["virtualenv", "-q", VENV_DIR])
with chdir(DIST_DIR):
for project, conf in projects.items():
print("Installing %s..." % project)
2015-12-03 17:03:59 +00:00
subprocess.check_call([VENV_PIP, "install", "-q", sdist_name(project)])
2015-11-29 01:46:08 +00:00
print("Running binaries...")
for project, conf in projects.items():
for tool in conf["tools"]:
tool = join(VENV_DIR, VENV_BIN, tool)
print("> %s --version" % tool)
print(subprocess.check_output([tool, "--version"]))
print("Virtualenv available for further testing:")
print("source %s" % os.path.normpath(join(VENV_DIR, VENV_BIN, "activate")))
@cli.command("bdist")
2015-11-29 21:11:14 +00:00
@click.option("--use-existing-sdist/--no-use-existing-sdist", default=False)
2016-02-06 00:22:27 +00:00
@click.argument("pyinstaller_version", envvar="PYINSTALLER_VERSION", default="PyInstaller~=3.1.1")
2015-11-29 01:46:08 +00:00
@click.pass_context
2015-11-29 21:11:14 +00:00
def bdist(ctx, use_existing_sdist, pyinstaller_version):
2015-11-29 01:46:08 +00:00
"""
Build a binary distribution
"""
if os.path.exists(PYINSTALLER_TEMP):
shutil.rmtree(PYINSTALLER_TEMP)
if os.path.exists(PYINSTALLER_DIST):
shutil.rmtree(PYINSTALLER_DIST)
if not use_existing_sdist:
ctx.invoke(sdist)
print("Installing PyInstaller...")
2015-11-29 21:11:14 +00:00
subprocess.check_call([VENV_PIP, "install", "-q", pyinstaller_version])
2015-11-29 01:46:08 +00:00
for p, conf in projects.items():
if conf["tools"]:
2016-02-11 17:51:47 +00:00
with Archive(join(DIST_DIR, archive_name(p))) as archive:
2015-11-29 01:46:08 +00:00
for tool in conf["tools"]:
spec = join(conf["dir"], "release", "%s.spec" % tool)
print("Building %s binary..." % tool)
subprocess.check_call(
[
VENV_PYINSTALLER,
"--clean",
"--workpath", PYINSTALLER_TEMP,
"--distpath", PYINSTALLER_DIST,
# This is PyInstaller, so setting a
# different log level obviously breaks it :-)
# "--log-level", "WARN",
spec
]
)
# Test if it works at all O:-)
executable = join(PYINSTALLER_DIST, tool)
if platform.system() == "Windows":
executable += ".exe"
2016-02-11 17:51:47 +00:00
print("> %s --version" % executable)
2015-11-29 01:46:08 +00:00
subprocess.check_call([executable, "--version"])
archive.add(executable, os.path.basename(executable))
2016-02-11 17:51:47 +00:00
print("Packed {}.".format(archive_name(p)))
2015-11-29 01:46:08 +00:00
2016-02-11 17:51:47 +00:00
@cli.command("upload-release")
2015-11-29 01:46:08 +00:00
@click.option('--username', prompt=True)
@click.password_option(confirmation_prompt=False)
@click.option('--repository', default="pypi")
2016-02-11 17:51:47 +00:00
@click.option("--sdist/--no-sdist", default=True)
@click.option("--wheel/--no-wheel", default=True)
def upload_release(username, password, repository, sdist, wheel):
2015-11-29 01:46:08 +00:00
"""
Upload source distributions to PyPI
"""
for project in projects.keys():
2016-02-11 17:51:47 +00:00
files = []
if sdist:
files.append(sdist_name(project))
if wheel:
files.append(wheel_name(project))
2015-12-03 17:03:59 +00:00
for f in files:
2015-11-29 18:05:58 +00:00
print("Uploading {} to {}...".format(f, repository))
subprocess.check_call([
"twine",
"upload",
"-u", username,
"-p", password,
"-r", repository,
join(DIST_DIR, f)
])
2015-11-29 01:46:08 +00:00
2016-02-11 17:51:47 +00:00
@cli.command("upload-snapshot")
@click.option("--host", envvar="SNAPSHOT_HOST", prompt=True)
@click.option("--port", envvar="SNAPSHOT_PORT", type=int, default=22)
@click.option("--user", envvar="SNAPSHOT_USER", prompt=True)
@click.option("--private-key", default=join(RELEASE_DIR, "rtool.pem"))
@click.option("--private-key-password", envvar="SNAPSHOT_PASS", prompt=True, hide_input=True)
2016-02-11 17:51:47 +00:00
@click.option("--sdist/--no-sdist", default=False)
@click.option("--wheel/--no-wheel", default=False)
@click.option("--bdist/--no-bdist", default=False)
def upload_snapshot(host, port, user, private_key, private_key_password, sdist, wheel, bdist):
2016-02-11 17:51:47 +00:00
"""
Upload snapshot to snapshot server
"""
with pysftp.Connection(host=host,
port=port,
username=user,
private_key=private_key,
private_key_pass=private_key_password) as sftp:
2016-02-11 17:51:47 +00:00
for project, conf in projects.items():
dir_name = "snapshots/v{}".format(get_version(project))
sftp.makedirs(dir_name)
with sftp.cd(dir_name):
files = []
if sdist:
files.append(sdist_name(project))
if wheel:
files.append(wheel_name(project))
if bdist and conf["tools"]:
files.append(archive_name(project))
for f in files:
local_path = join(DIST_DIR, f)
remote_filename = f.replace(get_version(project), get_snapshot_version(project))
symlink_path = "../{}".format(f.replace(get_version(project), "latest"))
2016-02-12 20:57:48 +00:00
old_version = f.replace(get_version(project), "*")
for f in sftp.listdir():
if fnmatch.fnmatch(f, old_version):
2016-02-12 21:18:30 +00:00
print("Removing {}...".format(f))
2016-02-12 20:57:48 +00:00
sftp.remove(f)
print("Uploading {} as {}...".format(f, remote_filename))
2016-02-11 17:51:47 +00:00
with click.progressbar(length=os.stat(local_path).st_size) as bar:
sftp.put(
local_path,
"." + remote_filename,
callback=lambda done, total: bar.update(done - bar.pos)
)
# We hide the file during upload.
sftp.rename("." + remote_filename, remote_filename)
# add symlink
if sftp.lexists(symlink_path):
2016-02-12 21:18:30 +00:00
print("Removing {}...".format(symlink_path))
2016-02-11 17:51:47 +00:00
sftp.remove(symlink_path)
sftp.symlink("v{}/{}".format(get_version(project), remote_filename), symlink_path)
2015-11-29 01:46:08 +00:00
@cli.command("wizard")
2016-02-11 17:51:47 +00:00
@click.option('--next-version', prompt=True)
2015-11-29 01:46:08 +00:00
@click.option('--username', prompt="PyPI Username")
@click.password_option(confirmation_prompt=False, prompt="PyPI Password")
@click.option('--repository', default="pypi")
@click.pass_context
2016-02-11 17:51:47 +00:00
def wizard(ctx, next_version, username, password, repository):
2015-11-29 01:46:08 +00:00
"""
Interactive Release Wizard
"""
for project, conf in projects.items():
is_dirty = subprocess.check_output(["git", "status", "--porcelain"], cwd=conf["dir"])
if is_dirty:
raise RuntimeError("%s repository is not clean." % project)
2016-02-11 17:51:47 +00:00
# update contributors file
2015-11-29 01:46:08 +00:00
ctx.invoke(contributors)
2015-12-03 16:55:42 +00:00
# Build test release
ctx.invoke(bdist)
2016-02-11 17:51:47 +00:00
try:
click.confirm("Please test the release now. Is it ok?", abort=True)
except click.Abort:
# undo changes
2016-02-14 16:28:25 +00:00
ctx.invoke(git, args=["checkout", "CONTRIBUTORS"])
2016-02-11 17:51:47 +00:00
raise
# Everything ok - let's ship it!
for p in projects.keys():
_git(p, ["tag", "v" + get_version(p)])
2016-02-14 16:28:25 +00:00
ctx.invoke(git, args=["push", "--tags"])
2015-11-29 01:46:08 +00:00
ctx.invoke(
2016-02-11 17:51:47 +00:00
upload_release,
username=username, password=password, repository=repository
2015-11-29 01:46:08 +00:00
)
click.confirm("Now please wait until CI has built binaries. Finished?")
2016-02-11 17:51:47 +00:00
# version bump commit
ctx.invoke(set_version, version=next_version)
2015-11-29 01:46:08 +00:00
ctx.invoke(
2016-02-11 17:51:47 +00:00
git, args=["commit", "-a", "-m", "bump version"]
2015-11-29 01:46:08 +00:00
)
2016-02-11 17:51:47 +00:00
ctx.invoke(git, args=["push"])
2015-11-29 01:46:08 +00:00
click.echo("All done!")
if __name__ == "__main__":
cli()