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
|
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")
|
2016-02-15 23:22:38 +00:00
|
|
|
ROOT_DIR = os.path.normpath(join(RELEASE_DIR, ".."))
|
|
|
|
VERSION_FILE = join(ROOT_DIR, "netlib/netlib/version.py")
|
2015-11-29 01:46:08 +00:00
|
|
|
|
|
|
|
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": [],
|
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"],
|
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"],
|
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
|
|
|
|
2016-02-15 23:22:38 +00:00
|
|
|
def get_version():
|
|
|
|
return runpy.run_path(VERSION_FILE)["VERSION"]
|
2015-11-29 01:46:08 +00:00
|
|
|
|
2015-12-03 16:55:42 +00:00
|
|
|
|
2016-02-15 23:22:38 +00:00
|
|
|
def get_snapshot_version():
|
|
|
|
last_tag, tag_dist, commit = git("describe --tags --long").strip().rsplit(b"-", 2)
|
2016-02-11 17:51:47 +00:00
|
|
|
tag_dist = int(tag_dist)
|
|
|
|
if tag_dist == 0:
|
2016-02-15 23:22:38 +00:00
|
|
|
return get_version()
|
2016-02-11 17:51:47 +00:00
|
|
|
else:
|
|
|
|
return "{version}dev{tag_dist:04}-{commit}".format(
|
2016-02-15 23:22:38 +00:00
|
|
|
version=get_version(), # this should already be the next version
|
2016-02-11 17:51:47 +00:00
|
|
|
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,
|
2016-02-15 23:22:38 +00:00
|
|
|
version=get_version(),
|
2016-02-11 17:51:47 +00:00
|
|
|
platform=platform_tag,
|
|
|
|
ext=ext
|
|
|
|
)
|
|
|
|
|
|
|
|
|
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,
|
2016-02-15 23:22:38 +00:00
|
|
|
version=get_version(),
|
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)
|
|
|
|
|
|
|
|
|
2016-02-15 23:22:38 +00:00
|
|
|
def git(args):
|
|
|
|
with chdir(ROOT_DIR):
|
|
|
|
return subprocess.check_output(["git"] + shlex.split(args))
|
|
|
|
|
|
|
|
|
2015-11-29 01:46:08 +00:00
|
|
|
@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
|
|
|
|
"""
|
2016-02-15 23:22:38 +00:00
|
|
|
with chdir(ROOT_DIR):
|
|
|
|
print("Updating CONTRIBUTORS...")
|
|
|
|
contributors_data = git("shortlog -n -s")
|
|
|
|
with open("CONTRIBUTORS", "w") as f:
|
|
|
|
f.write(contributors_data)
|
2015-11-29 01:46:08 +00:00
|
|
|
|
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("."))
|
2016-02-15 23:22:38 +00:00
|
|
|
print("Update %s..." % VERSION_FILE)
|
|
|
|
with open(VERSION_FILE, "rb") as f:
|
|
|
|
content = f.read()
|
|
|
|
new_content = re.sub(
|
|
|
|
r"IVERSION\s*=\s*\([\d,\s]+\)", "IVERSION = (%s)" % version,
|
|
|
|
content
|
2016-02-11 17:51:47 +00:00
|
|
|
)
|
2016-02-15 23:22:38 +00:00
|
|
|
with open(VERSION_FILE, "wb") as f:
|
|
|
|
f.write(new_content)
|
2015-11-29 01:46:08 +00:00
|
|
|
|
|
|
|
|
2016-02-15 23:39:07 +00:00
|
|
|
@cli.command("wheels")
|
|
|
|
def wheels():
|
2015-11-29 01:46:08 +00:00
|
|
|
"""
|
2016-02-15 23:39:07 +00:00
|
|
|
Build wheels
|
2015-11-29 01:46:08 +00:00
|
|
|
"""
|
|
|
|
with empty_pythonpath():
|
|
|
|
print("Building release...")
|
|
|
|
if os.path.exists(DIST_DIR):
|
|
|
|
shutil.rmtree(DIST_DIR)
|
|
|
|
for project, conf in projects.items():
|
2016-02-15 23:39:07 +00:00
|
|
|
print("Creating wheel for %s ..." % project)
|
2015-11-29 01:46:08 +00:00
|
|
|
subprocess.check_call(
|
|
|
|
[
|
2015-12-03 16:55:42 +00:00
|
|
|
"python", "./setup.py", "-q",
|
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)
|
2016-02-15 23:39:07 +00:00
|
|
|
subprocess.check_call([VENV_PIP, "install", "-q", wheel_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")
|
2016-02-15 23:39:07 +00:00
|
|
|
@click.option("--use-existing-wheels/--no-use-existing-wheels", 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
|
2016-02-15 23:39:07 +00:00
|
|
|
def bdist(ctx, use_existing_wheels, 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)
|
|
|
|
|
2016-02-15 23:39:07 +00:00
|
|
|
if not use_existing_wheels:
|
|
|
|
ctx.invoke(wheels)
|
2015-11-29 01:46:08 +00:00
|
|
|
|
|
|
|
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"]:
|
2016-02-18 10:25:29 +00:00
|
|
|
spec = join(RELEASE_DIR, "specs/%s.spec" % tool)
|
2015-11-29 01:46:08 +00:00
|
|
|
print("Building %s binary..." % tool)
|
|
|
|
subprocess.check_call(
|
|
|
|
[
|
|
|
|
VENV_PYINSTALLER,
|
|
|
|
"--clean",
|
|
|
|
"--workpath", PYINSTALLER_TEMP,
|
|
|
|
"--distpath", PYINSTALLER_DIST,
|
2016-02-17 20:48:37 +00:00
|
|
|
# This is PyInstaller, so setting a
|
2015-11-29 01:46:08 +00:00
|
|
|
# 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-15 23:39:07 +00:00
|
|
|
def upload_release(username, password, repository):
|
2015-11-29 01:46:08 +00:00
|
|
|
"""
|
2016-02-15 23:39:07 +00:00
|
|
|
Upload wheels to PyPI
|
2015-11-29 01:46:08 +00:00
|
|
|
"""
|
|
|
|
for project in projects.keys():
|
2016-02-15 23:39:07 +00:00
|
|
|
filename = wheel_name(project)
|
|
|
|
print("Uploading {} to {}...".format(filename, repository))
|
|
|
|
subprocess.check_call([
|
|
|
|
"twine",
|
|
|
|
"upload",
|
|
|
|
"-u", username,
|
|
|
|
"-p", password,
|
|
|
|
"-r", repository,
|
|
|
|
join(DIST_DIR, filename)
|
|
|
|
])
|
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)
|
2016-02-11 18:34:29 +00:00
|
|
|
@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("--wheel/--no-wheel", default=False)
|
|
|
|
@click.option("--bdist/--no-bdist", default=False)
|
2016-02-15 23:39:07 +00:00
|
|
|
def upload_snapshot(host, port, user, private_key, private_key_password, wheel, bdist):
|
2016-02-11 17:51:47 +00:00
|
|
|
"""
|
|
|
|
Upload snapshot to snapshot server
|
|
|
|
"""
|
2016-02-11 18:34:29 +00:00
|
|
|
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():
|
2016-02-15 23:22:38 +00:00
|
|
|
dir_name = "snapshots/v{}".format(get_version())
|
2016-02-11 17:51:47 +00:00
|
|
|
sftp.makedirs(dir_name)
|
|
|
|
with sftp.cd(dir_name):
|
|
|
|
files = []
|
|
|
|
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)
|
2016-02-15 23:22:38 +00:00
|
|
|
remote_filename = f.replace(get_version(), get_snapshot_version())
|
|
|
|
symlink_path = "../{}".format(f.replace(get_version(), "latest"))
|
2016-02-11 17:51:47 +00:00
|
|
|
|
2016-02-15 23:22:38 +00:00
|
|
|
# Delete old versions
|
|
|
|
old_version = f.replace(get_version(), "*")
|
|
|
|
for f_old in sftp.listdir():
|
|
|
|
if fnmatch.fnmatch(f_old, old_version):
|
|
|
|
print("Removing {}...".format(f_old))
|
|
|
|
sftp.remove(f_old)
|
2016-02-12 20:57:48 +00:00
|
|
|
|
2016-02-15 23:22:38 +00:00
|
|
|
# Upload new version
|
2016-02-14 00:44:54 +00:00
|
|
|
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)
|
|
|
|
|
2016-02-15 23:22:38 +00:00
|
|
|
# update symlink for the latest release
|
2016-02-11 17:51:47 +00:00
|
|
|
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)
|
2016-02-15 23:22:38 +00:00
|
|
|
sftp.symlink("v{}/{}".format(get_version(), remote_filename), symlink_path)
|
2016-02-11 17:51:47 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
2016-02-15 23:22:38 +00:00
|
|
|
is_dirty = git("status --porcelain")
|
|
|
|
if is_dirty:
|
|
|
|
raise RuntimeError("Repository is not clean.")
|
2015-11-29 01:46:08 +00:00
|
|
|
|
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-15 23:22:38 +00:00
|
|
|
git("checkout CONTRIBUTORS")
|
2016-02-11 17:51:47 +00:00
|
|
|
raise
|
|
|
|
|
|
|
|
# Everything ok - let's ship it!
|
2016-02-15 23:22:38 +00:00
|
|
|
git("tag v{}".format(get_version()))
|
|
|
|
git("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
|
|
|
)
|
|
|
|
|
2016-02-14 16:41:54 +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)
|
2016-02-15 23:22:38 +00:00
|
|
|
git("commit -a -m \"bump version\"")
|
|
|
|
git("push")
|
2016-02-11 17:51:47 +00:00
|
|
|
|
2015-11-29 01:46:08 +00:00
|
|
|
click.echo("All done!")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
cli()
|