diff --git a/release/.env b/release/.env new file mode 100644 index 000000000..69ac3f052 --- /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 000000000..c062fb3b2 --- /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 000000000..7754125d0 --- /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 000000000..097dff1a9 --- /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 000000000..136b3066c --- /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 000000000..9876af0af --- /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", + ], + }, +)