aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--release/.env6
-rw-r--r--release/.gitignore9
-rw-r--r--release/README.mkd83
-rw-r--r--release/rtool.pem68
-rw-r--r--release/rtool.py439
-rw-r--r--release/setup.py20
6 files changed, 625 insertions, 0 deletions
diff --git a/release/.env b/release/.env
new file mode 100644
index 00000000..69ac3f05
--- /dev/null
+++ b/release/.env
@@ -0,0 +1,6 @@
+DIR="$( dirname "${BASH_SOURCE[0]}" )"
+ACTIVATE_DIR="$(if [ -f "$DIR/../venv.mitmproxy/bin/activate" ]; then echo 'bin'; else echo 'Scripts'; fi;)"
+if [ -z "$VIRTUAL_ENV" ] && [ -f "$DIR/../venv.mitmproxy/$ACTIVATE_DIR/activate" ]; then
+ echo "Activating mitmproxy virtualenv..."
+ source "$DIR/../venv.mitmproxy/$ACTIVATE_DIR/activate"
+fi
diff --git a/release/.gitignore b/release/.gitignore
new file mode 100644
index 00000000..c062fb3b
--- /dev/null
+++ b/release/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+MANIFEST
+*.py[cdo]
+*.swp
+*.swo
+
+/build
+/dist
+/mitmproxy_rtool.egg-info \ No newline at end of file
diff --git a/release/README.mkd b/release/README.mkd
new file mode 100644
index 00000000..7754125d
--- /dev/null
+++ b/release/README.mkd
@@ -0,0 +1,83 @@
+
+General build and release utilities for the mitmproxy, netlib and pathod
+projects. These tools assume a directory structure with all repositories at the
+same level, for example:
+
+ /src
+ /mitmproxy
+ /netlib
+ /pathod
+ /release
+
+
+# Release policies
+
+ - By default, every release is a new minor (`0.x`) release and it will be
+ pushed for all three projects.
+
+ - Only if an emergency bugfix is needed, we push a new `0.x.y` bugfix release
+ for a single project. This matches with what we do in `setup.py`:
+
+ "netlib>=%s, <%s" % (version.MINORVERSION, version.NEXT_MINORVERSION)
+
+
+
+# Release Checklist
+
+## Check out release versions
+
+ - Check out the versions of pathod, netlib and mitmproxy due to be released
+
+ - Verify that repositories are in a clean state:
+
+ `./build git status`
+
+ - Ensure that the website style assets have been compiled for production, and
+ synced to the docs.
+
+ - Render the docs, update CONTRIBUTORS file:
+
+ ./build docs contributors
+
+
+## Test
+
+ - Test the source distributions:
+
+ ./build test
+
+ This does the following:
+ - creates a venv in release/venv
+ - creates source distributions in release/release
+ - installs the source distributions in the venv
+ - and runs all installed tools
+
+
+## Release
+
+ - Make a release commit for all projects, tag and push it:
+
+ ./build git commit -am "Release v0.13"
+ ./build git tag v0.13
+ ./build git push --tags
+
+ - Build the OSX binaries
+ - Follow instructions in osx-binaries
+ - Move to download dir:
+
+ mv ./tmp/osx-mitmproxy-VERSION.tar.gz ~/mitmproxy/www.mitmproxy.org/src/download
+
+ - Move all source distributions from `./dist` to the server:
+
+ mv ./dist/* ~/mitmproxy/www.mitmproxy.org/src/download
+
+ - Upload distributions in `./dist` to PyPI:
+
+ ./build upload
+
+ You can test with [testpypi.python.org](https://testpypi.python.org/pypi) by passing `--repository test`.
+ ([more info](https://tom-christie.github.io/articles/pypi/))
+
+ - Now bump the version number to be ready for the next cycle
+
+ `./build set-version 0.13`
diff --git a/release/rtool.pem b/release/rtool.pem
new file mode 100644
index 00000000..097dff1a
--- /dev/null
+++ b/release/rtool.pem
@@ -0,0 +1,68 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,7A4E07094E1AC0B74BB3D172A73A62DB
+
+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
+-----END RSA PRIVATE KEY-----
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs0xeGx+bc+NCLGRJDr6y
+qv825685l1Tb6lhLvYVYUFJfcxL1V79ML13CO09TPxGTbmKw7iI33ef9N2mUHHVe
+24EjcSnYZ2Zx5zYIY4ejnDKCzM0md9XA3RpfTI9F3zGsJPt72veyHy6alCbF4NgX
+FgAvp2UJmWwTLnJj3oIEwfGJeEcQRKjCFjSJlHGxbB6L0MSCgRtDxwlBfinzkriZ
+0C8YyVv6u+DidHtobbZvwk/4uV3+KhoDfkpT2VE57a2KXr4c9Fekzr6szduFMkcI
+KPqKouOOnaiNjOoKofbZ5I6cB6qoMkh+ebJvfPuEYBwhHmDDhpdjdk5v3LkeD5kt
+0GtOaF5A3LbdZhgsC+1v1dHrL6Vndrb9KhO8ATHvlzJOonV73AoJFygp6upCvIt+
+tPUuy3VE0kjc2mJDDJr5V28pSBJ5r9uz1rXZWP+9aduR3M+RUGzHS7cUdsl+lHyQ
+TFQAQIDhWXyokMGqVZBm9xJ2KcDiQAEr5+kP0BrhPmBtfHSnLA1tIXlFcOqakvyQ
+q1cACMvwbkZpupTqMhCr2S4V9dKxnlO5EIWYJmme9moU/zu1KA0mcwqfnNeDOR2J
+6c3/x3E+AqBoFzDQb6LQ2Iw+9bkKWKgDml4paLPu/ot8vA2ZB9/mujFUvp/1MLuD
+5I1jQDXFbjAgAnaSyqyW368CAwEAAQ==
+-----END PUBLIC KEY----- \ No newline at end of file
diff --git a/release/rtool.py b/release/rtool.py
new file mode 100644
index 00000000..136b3066
--- /dev/null
+++ b/release/rtool.py
@@ -0,0 +1,439 @@
+#!/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
+import zipfile
+import tarfile
+import platform
+import click
+import pysftp
+import fnmatch
+from six.moves import shlex_quote
+
+# 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):
+ a = zipfile.ZipFile(name, "w")
+ a.add = a.write
+ return a
+else:
+ def Archive(name):
+ return tarfile.open(name, "w:gz")
+
+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"),
+ "dir": join(ROOT_DIR, "netlib"),
+ "python_version": "py2.py3" # this is the format in wheel filenames
+ },
+ "pathod": {
+ "tools": ["pathod", "pathoc"],
+ "vfile": join(ROOT_DIR, "pathod/libpathod/version.py"),
+ "dir": join(ROOT_DIR, "pathod"),
+ "python_version": "py2"
+ },
+ "mitmproxy": {
+ "tools": ["mitmproxy", "mitmdump", "mitmweb"],
+ "vfile": join(ROOT_DIR, "mitmproxy/libmproxy/version.py"),
+ "dir": join(ROOT_DIR, "mitmproxy"),
+ "python_version": "py2"
+ }
+}
+if platform.system() == "Windows":
+ ALL_PROJECTS["mitmproxy"]["tools"].remove("mitmproxy")
+
+projects = {}
+
+
+def get_version(project):
+ return runpy.run_path(projects[project]["vfile"])["VERSION"]
+
+
+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
+ )
+
+
+def sdist_name(project):
+ return "{project}-{version}.tar.gz".format(
+ project=project,
+ version=get_version(project)
+ )
+
+
+def wheel_name(project):
+ return "{project}-{version}-{py_version}-none-any.whl".format(
+ project=project,
+ version=get_version(project),
+ py_version=projects[project]["python_version"]
+ )
+
+
+@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.
+ """
+ pythonpath = os.environ.get("PYTHONPATH", "")
+ 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]
+
+
+@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)
+
+
+@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)
+
+
+def _git(project, args):
+ print("%s> %s..." % (project, " ".join(shlex_quote(a) for a in args)))
+ subprocess.check_call(
+ ["git"] + list(args),
+ cwd=projects[project]["dir"]
+ )
+
+
+@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():
+ _git(project, args)
+ print("")
+
+
+@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(
+ [
+ "python", "./setup.py", "-q",
+ "sdist", "--dist-dir", DIST_DIR, "--formats=gztar",
+ "bdist_wheel", "--dist-dir", DIST_DIR,
+ ],
+ 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)
+ subprocess.check_call([VENV_PIP, "install", "-q", sdist_name(project)])
+
+ 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")
+@click.option("--use-existing-sdist/--no-use-existing-sdist", default=False)
+@click.argument("pyinstaller_version", envvar="PYINSTALLER_VERSION", default="PyInstaller~=3.1.1")
+@click.pass_context
+def bdist(ctx, use_existing_sdist, pyinstaller_version):
+ """
+ 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...")
+ subprocess.check_call([VENV_PIP, "install", "-q", pyinstaller_version])
+
+ for p, conf in projects.items():
+ if conf["tools"]:
+ with Archive(join(DIST_DIR, archive_name(p))) as archive:
+ 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"
+ print("> %s --version" % executable)
+ subprocess.check_call([executable, "--version"])
+
+ archive.add(executable, os.path.basename(executable))
+ print("Packed {}.".format(archive_name(p)))
+
+
+@cli.command("upload-release")
+@click.option('--username', prompt=True)
+@click.password_option(confirmation_prompt=False)
+@click.option('--repository', default="pypi")
+@click.option("--sdist/--no-sdist", default=True)
+@click.option("--wheel/--no-wheel", default=True)
+def upload_release(username, password, repository, sdist, wheel):
+ """
+ Upload source distributions to PyPI
+ """
+ for project in projects.keys():
+ files = []
+ if sdist:
+ files.append(sdist_name(project))
+ if wheel:
+ files.append(wheel_name(project))
+ for f in files:
+ print("Uploading {} to {}...".format(f, repository))
+ subprocess.check_call([
+ "twine",
+ "upload",
+ "-u", username,
+ "-p", password,
+ "-r", repository,
+ join(DIST_DIR, f)
+ ])
+
+
+@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)
+@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):
+ """
+ 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:
+ 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"))
+
+ old_version = f.replace(get_version(project), "*")
+ for f in sftp.listdir():
+ if fnmatch.fnmatch(f, old_version):
+ print("Removing {}...".format(f))
+ sftp.remove(f)
+
+ print("Uploading {} as {}...".format(f, remote_filename))
+ 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):
+ print("Removing {}...".format(symlink_path))
+ sftp.remove(symlink_path)
+ sftp.symlink("v{}/{}".format(get_version(project), remote_filename), symlink_path)
+
+
+@cli.command("wizard")
+@click.option('--next-version', prompt=True)
+@click.option('--username', prompt="PyPI Username")
+@click.password_option(confirmation_prompt=False, prompt="PyPI Password")
+@click.option('--repository', default="pypi")
+@click.pass_context
+def wizard(ctx, next_version, username, password, repository):
+ """
+ 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)
+
+ # update contributors file
+ ctx.invoke(contributors)
+
+ # Build test release
+ ctx.invoke(bdist)
+
+ try:
+ click.confirm("Please test the release now. Is it ok?", abort=True)
+ except click.Abort:
+ # undo changes
+ ctx.invoke(git, args=["checkout", "CONTRIBUTORS"])
+ raise
+
+ # Everything ok - let's ship it!
+ for p in projects.keys():
+ _git(p, ["tag", "v" + get_version(p)])
+ ctx.invoke(git, args=["push", "--tags"])
+ ctx.invoke(
+ upload_release,
+ username=username, password=password, repository=repository
+ )
+
+ click.confirm("Now please wait until CI has built binaries. Finished?")
+
+ # version bump commit
+ ctx.invoke(set_version, version=next_version)
+ ctx.invoke(
+ git, args=["commit", "-a", "-m", "bump version"]
+ )
+ ctx.invoke(git, args=["push"])
+
+ click.echo("All done!")
+
+
+if __name__ == "__main__":
+ cli()
diff --git a/release/setup.py b/release/setup.py
new file mode 100644
index 00000000..9876af0a
--- /dev/null
+++ b/release/setup.py
@@ -0,0 +1,20 @@
+from setuptools import setup
+
+setup(
+ name='mitmproxy-rtool',
+ version="1.0",
+ py_modules=["rtool"],
+ install_requires=[
+ "click>=6.2, <7.0",
+ "twine>=1.6.5, <1.7",
+ "virtualenv>=14.0.5, <14.1",
+ "wheel>=0.29.0, <0.30",
+ "six>=1.10.0, <1.11",
+ "pysftp>=0.2.8, <0.3",
+ ],
+ entry_points={
+ "console_scripts": [
+ "rtool=rtool:cli",
+ ],
+ },
+)