Files
jupyterhub/tools/tasks.py
2016-03-08 09:55:44 +01:00

243 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""
invoke script for releasing jupyterhub
usage:
invoke release 1.2.3 [--upload]
This does:
- clone into /tmp/jupyterhub-repo
- patches version.py with release version
- creates tag (push if uploading)
- makes a virtualenv with python3.4 (PYTHON_EXE env to override)
- builds an sdist (optionally uploads)
- patches version.py with post-release version (X.Y+1.Z.dev) (push if uploading)
- unpacks sdist to /tmp/jupyterhub-release
- builds bdist_wheel from sdist (optional upload)
"""
# derived from PyZMQ release/tasks.py (used under BSD)
# Copyright (c) Jupyter Developers
# Distributed under the terms of the Modified BSD License.
import glob
import os
import pipes
import shutil
from contextlib import contextmanager
from distutils.version import LooseVersion as V
from invoke import task, run as invoke_run
pjoin = os.path.join
here = os.path.dirname(__file__)
repo = "git@github.com:jupyter/jupyterhub"
pkg = repo.rsplit('/', 1)[-1]
py_exe = os.environ.get('PYTHON_EXE', 'python3.4')
tmp = "/tmp"
env_root = os.path.join(tmp, 'envs')
repo_root = pjoin(tmp, '%s-repo' % pkg)
sdist_root = pjoin(tmp, '%s-release' % pkg)
def run(cmd, **kwargs):
"""wrapper around invoke.run that accepts a Popen list"""
if isinstance(cmd, list):
cmd = " ".join(pipes.quote(s) for s in cmd)
kwargs.setdefault('echo', True)
return invoke_run(cmd, **kwargs)
@contextmanager
def cd(path):
"""Context manager for temporary CWD"""
cwd = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(cwd)
@task
def clone_repo(reset=False):
"""Clone the repo"""
if os.path.exists(repo_root) and reset:
shutil.rmtree(repo_root)
if os.path.exists(repo_root):
with cd(repo_root):
run("git pull")
else:
run("git clone %s %s" % (repo, repo_root))
@task
def patch_version(vs, path=pjoin(here, '..')):
"""Patch zmq/sugar/version.py for the current release"""
v = parse_vs(vs)
version_py = pjoin(path, 'jupyterhub', 'version.py')
print("patching %s with %s" % (version_py, vs))
# read version.py, minus version parts
with open(version_py) as f:
pre_lines = []
post_lines = []
for line in f:
pre_lines.append(line)
if line.startswith("version_info"):
break
for line in f:
if line.startswith(')'):
post_lines.append(line)
break
for line in f:
post_lines.append(line)
# write new version.py
with open(version_py, 'w') as f:
for line in pre_lines:
f.write(line)
for part in v:
f.write(' %r,\n' % part)
for line in post_lines:
f.write(line)
# verify result
ns = {}
with open(version_py) as f:
exec(f.read(), {}, ns)
assert ns['__version__'] == vs, "%r != %r" % (ns['__version__'], vs)
@task
def tag(vs, push=False):
"""Make the tagged release commit"""
patch_version(vs, repo_root)
with cd(repo_root):
run('git commit -a -m "release {}"'.format(vs))
run('git tag -a -m "release {0}" {0}'.format(vs))
if push:
run('git push')
run('git push --tags')
@task
def untag(vs, push=False):
"""Make the post-tag 'back to dev' commit"""
v2 = parse_vs(vs)
v2.append('dev')
v2[1] += 1
v2[2] = 0
vs2 = unparse_vs(v2)
patch_version(vs2, repo_root)
with cd(repo_root):
run('git commit -a -m "back to dev"')
if push:
run('git push')
def make_env(*packages):
"""Make a virtualenv
Assumes `which python` has the `virtualenv` package
"""
if not os.path.exists(env_root):
os.makedirs(env_root)
env = os.path.join(env_root, os.path.basename(py_exe))
py = pjoin(env, 'bin', 'python')
# new env
if not os.path.exists(py):
run('python -m virtualenv {} -p {}'.format(
pipes.quote(env),
pipes.quote(py_exe),
))
py = pjoin(env, 'bin', 'python')
run([py, '-V'])
install(py, 'pip', 'setuptools')
if packages:
install(py, *packages)
return py
def build_sdist(py):
"""Build sdists
Returns the path to the tarball
"""
with cd(repo_root):
cmd = [py, 'setup.py', 'sdist', '--formats=zip,gztar']
run(cmd)
return glob.glob(pjoin(repo_root, 'dist', '*.tar.gz'))[0]
@task
def sdist(vs, upload=False):
clone_repo()
tag(vs, push=upload)
py = make_env()
tarball = build_sdist(py)
if upload:
with cd(repo_root):
install(py, 'twine')
run([py, '-m', 'twine', 'upload', 'dist/*'])
untag(vs, push=upload)
return untar(tarball)
def install(py, *packages):
run([py, '-m', 'pip', 'install', '--upgrade'] + list(packages))
def parse_vs(vs):
"""version string to list"""
return V(vs).version
def unparse_vs(tup):
"""version list to string"""
return '.'.join(map(str, tup))
def untar(tarball):
"""extract sdist, returning path to unpacked package directory"""
if os.path.exists(sdist_root):
shutil.rmtree(sdist_root)
os.makedirs(sdist_root)
with cd(sdist_root):
run(['tar', '-xzf', tarball])
return glob.glob(pjoin(sdist_root, '*'))[0]
def bdist():
"""build a wheel, optionally uploading it"""
py = make_env('wheel')
run([py, 'setup.py', 'bdist_wheel'])
@task
def release(vs, upload=False):
"""Release the package"""
# start from scrach with clone and envs
clone_repo(reset=True)
if os.path.exists(env_root):
shutil.rmtree(env_root)
path = sdist(vs, upload=upload)
print("Working in %r" % path)
with cd(path):
bdist()
if upload:
py = make_env('twine')
run([py, '-m', 'twine', 'upload', 'dist/*'])