-
Notifications
You must be signed in to change notification settings - Fork 4
/
deploy.py
122 lines (91 loc) · 4.35 KB
/
deploy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env python3
"""
Deploys the package to PyPI and uploads docs. This script is called in circle.yml.
"""
import os
import shlex
import subprocess
import sys
CIRCLECI_ENV_VAR = 'CIRCLECI'
def _shell(cmd, check=True, stdin=None, stdout=None, stderr=None): # pragma: no cover
"""Runs a subprocess shell with check=True by default"""
return subprocess.run(cmd, shell=True, check=check, stdin=stdin, stdout=stdout, stderr=stderr)
def _pypi_push(dist):
"""Push created package to PyPI.
Requires the following defined environment variables:
- TWINE_USERNAME: The PyPI username to upload this package under
- TWINE_PASSWORD: The password to the user's account
Args:
dist (str):
The distribution to push. Must be a valid directory; shell globs are
NOT allowed.
"""
_shell('twine upload ' + shlex.quote(dist + '/*'))
def deploy(target):
"""Deploys the package and documentation.
Proceeds in the following steps:
1. Ensures proper environment variables are set and checks that we are on Circle CI
2. Tags the repository with the new version
3. Creates a standard distribution and a wheel
4. Updates version.py to have the proper version
5. Commits the ChangeLog, AUTHORS, and version.py file
6. Pushes to PyPI
7. Pushes the tags and newly committed files
Raises:
`EnvironmentError`:
- Not running on CircleCI
- `*_PYPI_USERNAME` and/or `*_PYPI_PASSWORD` environment variables
are missing
- Attempting to deploy to production from a branch that isn't master
"""
# Ensure proper environment
if not os.getenv(CIRCLECI_ENV_VAR): # pragma: no cover
raise EnvironmentError('Must be on CircleCI to run this script')
current_branch = os.getenv('CIRCLE_BRANCH')
if (target == 'PROD') and (current_branch != 'master'):
raise EnvironmentError(
f'Refusing to deploy to production from branch {current_branch!r}. '
f'Production deploys can only be made from master.')
if target in ('PROD', 'TEST'):
pypi_username = os.getenv(f'{target}_PYPI_USERNAME')
pypi_password = os.getenv(f'{target}_PYPI_PASSWORD')
else:
raise ValueError(f"Deploy target must be 'PROD' or 'TEST', got {target!r}.")
if not (pypi_username and pypi_password): # pragma: no cover
raise EnvironmentError(
f"Missing '{target}_PYPI_USERNAME' and/or '{target}_PYPI_PASSWORD' "
f"environment variables. These are required to push to PyPI.")
# Twine requires these environment variables to be set. Subprocesses will
# inherit these when we invoke them, so no need to pass them on the command
# line. We want to avoid that in case something's logging each command run.
os.environ['TWINE_USERNAME'] = pypi_username
os.environ['TWINE_PASSWORD'] = pypi_password
# Set up git on circle to push to the current branch
_shell('git config --global user.email "[email protected]"')
_shell('git config --global user.name "Circle CI"')
_shell('git config push.default current')
# Obtain the version to deploy
ret = _shell('make version', stdout=subprocess.PIPE)
version = ret.stdout.decode('utf-8').strip()
print(f'Deploying version {version!r}...')
# Tag the version
_shell(f'git tag -f -a {version} -m "Version {version}"')
# Update the version
_shell(
f'sed -i.bak "s/^__version__ = .*/__version__ = {version!r}/" */version.py')
# Create a standard distribution and a wheel
_shell('python setup.py sdist bdist_wheel')
# Add the updated ChangeLog and AUTHORS
_shell('git add ChangeLog AUTHORS */version.py')
# Start the commit message with "Merge" so that PBR will ignore it in the
# ChangeLog. Use [skip ci] to ensure CircleCI doesn't recursively deploy.
_shell('git commit --no-verify -m "Merge autogenerated files [skip ci]"')
# Push the distributions to PyPI.
_pypi_push('dist')
# Push the tag and AUTHORS / ChangeLog after successful PyPI deploy
_shell('git push --follow-tags')
print(f'Deployment complete. Latest version is {version}.')
if __name__ == '__main__': # pragma: no cover
if len(sys.argv) != 2:
raise RuntimeError('Require one argument indicating deploy target: `prod` or `test`.')
deploy(sys.argv[1].upper())