forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathupload-pypi.py
160 lines (133 loc) · 6.02 KB
/
upload-pypi.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
"""Build and upload mypy packages for Linux and macOS to PyPI.
Note: This should be run on macOS using offical python.org Python 3.6 or
later, as this is the only tested configuration. Use --force to
run anyway.
This uses a fresh repo clone and a fresh virtualenv to avoid depending on
local state.
Ideas for improvements:
- also upload Windows wheels
- try installing the generated packages and running mypy
- try installing the uploaded packages and running mypy
- run tests
- verify that there is a green travis build
"""
import argparse
import getpass
import os
import os.path
import re
import subprocess
import sys
import tempfile
from typing import Any
class Builder:
def __init__(self, version: str, force: bool, no_upload: bool) -> None:
if not re.match(r'0\.[0-9]{3}$', version):
sys.exit('Invalid version {!r} (expected form 0.123)'.format(version))
self.version = version
self.force = force
self.no_upload = no_upload
self.target_dir = tempfile.mkdtemp()
self.repo_dir = os.path.join(self.target_dir, 'mypy')
def build_and_upload(self) -> None:
self.prompt()
self.run_sanity_checks()
print('Temporary target directory: {}'.format(self.target_dir))
self.git_clone_repo()
self.git_check_out_tag()
self.verify_version()
self.make_virtualenv()
self.install_dependencies()
self.make_wheel()
self.make_sdist()
if not self.no_upload:
self.upload_wheel()
self.upload_sdist()
self.heading('Successfully uploaded wheel and sdist for mypy {}'.format(self.version))
print("<< Don't forget to upload Windows wheels! >>")
else:
self.heading('Successfully built wheel and sdist for mypy {}'.format(self.version))
dist_dir = os.path.join(self.repo_dir, 'dist')
print('Generated packages:'.format(dist_dir))
for fnam in sorted(os.listdir(dist_dir)):
print(' {}'.format(os.path.join(dist_dir, fnam)))
def prompt(self) -> None:
if self.force:
return
extra = '' if self.no_upload else ' and upload'
print('This will build{} PyPI packages for mypy {}.'.format(extra, self.version))
response = input('Proceeed? [yN] '.format(self.version))
if response.lower() != 'y':
sys.exit('Exiting')
def verify_version(self) -> None:
version_path = os.path.join(self.repo_dir, 'mypy', 'version.py')
with open(version_path) as f:
contents = f.read()
if "'{}'".format(self.version) not in contents:
sys.stderr.write(
'\nError: Version {} does not match {}/mypy/version.py\n'.format(
self.version, self.repo_dir))
sys.exit(2)
def run_sanity_checks(self) -> None:
if not sys.version_info >= (3, 6):
sys.exit('You must use Python 3.6 or later to build mypy')
if sys.platform != 'darwin' and not self.force:
sys.exit('You should run this on macOS; use --force to go ahead anyway')
os_file = os.path.realpath(os.__file__)
if not os_file.startswith('/Library/Frameworks') and not self.force:
# Be defensive -- Python from brew may produce bad packages, for example.
sys.exit('Error -- run this script using an official Python build from python.org')
if getpass.getuser() == 'root':
sys.exit('This script must not be run as root')
def git_clone_repo(self) -> None:
self.heading('Cloning mypy git repository')
self.run('git clone https://github.com/python/mypy')
def git_check_out_tag(self) -> None:
tag = 'v{}'.format(self.version)
self.heading('Check out {}'.format(tag))
self.run('cd mypy && git checkout {}'.format(tag))
self.run('cd mypy && git submodule update --init typeshed'.format(tag))
def make_virtualenv(self) -> None:
self.heading('Creating a fresh virtualenv')
self.run('virtualenv -p {} mypy-venv'.format(sys.executable))
def install_dependencies(self) -> None:
self.heading('Installing build dependencies')
self.run_in_virtualenv('pip3 install wheel twine && pip3 install -U setuptools')
def make_wheel(self) -> None:
self.heading('Building wheel')
self.run_in_virtualenv('python3 setup.py bdist_wheel')
def make_sdist(self) -> None:
self.heading('Building sdist')
self.run_in_virtualenv('python3 setup.py sdist')
def upload_wheel(self) -> None:
self.heading('Uploading wheel')
self.run_in_virtualenv('twine upload dist/mypy-{}-py3-none-any.whl'.format(self.version))
def upload_sdist(self) -> None:
self.heading('Uploading sdist')
self.run_in_virtualenv('twine upload dist/mypy-{}.tar.gz'.format(self.version))
def run(self, cmd: str) -> None:
try:
subprocess.check_call(cmd, shell=True, cwd=self.target_dir)
except subprocess.CalledProcessError:
sys.stderr.write('Error: Command {!r} failed\n'.format(cmd))
sys.exit(1)
def run_in_virtualenv(self, cmd: str) -> None:
self.run('source mypy-venv/bin/activate && cd mypy &&' + cmd)
def heading(self, heading: str) -> None:
print()
print('==== {} ===='.format(heading))
print()
def parse_args() -> Any:
parser = argparse.ArgumentParser(
description='PyPI mypy package uploader (for non-Windows packages only)')
parser.add_argument('--force', action='store_true', default=False,
help='Skip prompts and sanity checks (be careful!)')
parser.add_argument('--no-upload', action='store_true', default=False,
help="Only build packages but don't upload")
parser.add_argument('version', help='Mypy version to release')
return parser.parse_args()
if __name__ == '__main__':
args = parse_args()
builder = Builder(args.version, args.force, args.no_upload)
builder.build_and_upload()