267 lines
8.3 KiB
Python
267 lines
8.3 KiB
Python
"""
|
|
Script to upload 32 bits and 64 bits wheel packages for Python 3.3 on Windows.
|
|
|
|
Usage: "python release.py HG_TAG" where HG_TAG is a Mercurial tag, usually
|
|
a version number like "3.4.2".
|
|
|
|
Modify manually the dry_run attribute to upload files.
|
|
|
|
It requires the Windows SDK 7.1 on Windows 64 bits and the aiotest module.
|
|
"""
|
|
import contextlib
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import textwrap
|
|
|
|
PY3 = (sys.version_info >= (3,))
|
|
HG = 'hg'
|
|
_PYTHON_VERSIONS = [(3, 3)]
|
|
PYTHON_VERSIONS = []
|
|
for pyver in _PYTHON_VERSIONS:
|
|
PYTHON_VERSIONS.append((pyver, 32))
|
|
PYTHON_VERSIONS.append((pyver, 64))
|
|
SDK_ROOT = r"C:\Program Files\Microsoft SDKs\Windows"
|
|
BATCH_FAIL_ON_ERROR = "@IF %errorlevel% neq 0 exit /b %errorlevel%"
|
|
|
|
class Release(object):
|
|
def __init__(self):
|
|
root = os.path.dirname(__file__)
|
|
self.root = os.path.realpath(root)
|
|
# Set these attributes to True to run also register sdist upload
|
|
self.register = False
|
|
self.sdist = False
|
|
self.dry_run = True
|
|
self.test = True
|
|
self.aiotest = True
|
|
|
|
@contextlib.contextmanager
|
|
def _popen(self, args, **kw):
|
|
env2 = kw.pop('env', {})
|
|
env = dict(os.environ)
|
|
# Force the POSIX locale
|
|
env['LC_ALL'] = 'C'
|
|
env.update(env2)
|
|
print('+ ' + ' '.join(args))
|
|
if PY3:
|
|
kw['universal_newlines'] = True
|
|
proc = subprocess.Popen(args, env=env, **kw)
|
|
with proc:
|
|
yield proc
|
|
|
|
def get_output(self, *args, **kw):
|
|
with self._popen(args, stdout=subprocess.PIPE, **kw) as proc:
|
|
stdout, stderr = proc.communicate()
|
|
return stdout
|
|
|
|
def run_command(self, *args, **kw):
|
|
with self._popen(args, **kw) as proc:
|
|
exitcode = proc.wait()
|
|
if exitcode:
|
|
sys.exit(exitcode)
|
|
|
|
def get_local_changes(self):
|
|
status = self.get_output(HG, 'status')
|
|
return [line for line in status.splitlines()
|
|
if not line.startswith("?")]
|
|
|
|
def remove_directory(self, name):
|
|
path = os.path.join(self.root, name)
|
|
if os.path.exists(path):
|
|
print("Remove directory: %s" % name)
|
|
shutil.rmtree(path)
|
|
|
|
def remove_file(self, name):
|
|
path = os.path.join(self.root, name)
|
|
if os.path.exists(path):
|
|
print("Remove file: %s" % name)
|
|
os.unlink(path)
|
|
|
|
def windows_sdk_setenv(self, pyver, bits):
|
|
if pyver >= (3, 3):
|
|
sdkver = "v7.1"
|
|
else:
|
|
sdkver = "v7.0"
|
|
setenv = os.path.join(SDK_ROOT, sdkver, 'Bin', 'SetEnv.cmd')
|
|
if not os.path.exists(setenv):
|
|
print("Unable to find Windows SDK %s for Python %s.%s"
|
|
% (sdkver, pyver[0], pyver[1]))
|
|
print("Please download and install it")
|
|
print("%s does not exists" % setenv)
|
|
sys.exit(1)
|
|
if bits == 64:
|
|
arch = '/x64'
|
|
else:
|
|
arch = '/x86'
|
|
return ["CALL", setenv, "/release", arch]
|
|
|
|
def get_python(self, version, bits):
|
|
if bits == 32:
|
|
python = 'c:\\Python%s%s_32bit\\python.exe' % version
|
|
else:
|
|
python = 'c:\\Python%s%s\\python.exe' % version
|
|
if not os.path.exists(python):
|
|
print("Unable to find python%s.%s" % version)
|
|
print("%s does not exists" % python)
|
|
sys.exit(1)
|
|
code = (
|
|
'import platform, sys; '
|
|
'print("{ver.major}.{ver.minor} {bits}".format('
|
|
'ver=sys.version_info, '
|
|
'bits=platform.architecture()[0]))'
|
|
)
|
|
stdout = self.get_output(python, '-c', code)
|
|
stdout = stdout.rstrip()
|
|
expected = "%s.%s %sbit" % (version[0], version[1], bits)
|
|
if stdout != expected:
|
|
print("Python version or architecture doesn't match")
|
|
print("got %r, expected %r" % (stdout, expected))
|
|
print(python)
|
|
sys.exit(1)
|
|
return python
|
|
|
|
def quote(self, arg):
|
|
if not re.search("[ '\"]", arg):
|
|
return arg
|
|
# FIXME: should we escape "?
|
|
return '"%s"' % arg
|
|
|
|
def quote_args(self, args):
|
|
return ' '.join(self.quote(arg) for arg in args)
|
|
|
|
def cleanup(self):
|
|
self.remove_directory('build')
|
|
self.remove_directory('dist')
|
|
self.remove_file('_overlapped.pyd')
|
|
self.remove_file(os.path.join('asyncio', '_overlapped.pyd'))
|
|
|
|
def sdist_upload(self):
|
|
self.cleanup()
|
|
self.run_command(sys.executable, 'setup.py', 'sdist', 'upload')
|
|
|
|
def runtests(self, pyver, bits):
|
|
pythonstr = "%s.%s (%s bits)" % (pyver[0], pyver[1], bits)
|
|
python = self.get_python(pyver, bits)
|
|
dbg_env = {'PYTHONASYNCIODEBUG': '1'}
|
|
|
|
self.build(pyver, bits, 'build')
|
|
if bits == 64:
|
|
arch = 'win-amd64'
|
|
else:
|
|
arch = 'win32'
|
|
build_dir = 'lib.%s-%s.%s' % (arch, pyver[0], pyver[1])
|
|
src = os.path.join(self.root, 'build', build_dir, 'asyncio', '_overlapped.pyd')
|
|
dst = os.path.join(self.root, 'asyncio', '_overlapped.pyd')
|
|
shutil.copyfile(src, dst)
|
|
|
|
args = (python, 'runtests.py', '-r')
|
|
print("Run runtests.py in release mode with %s" % pythonstr)
|
|
self.run_command(*args)
|
|
|
|
print("Run runtests.py in debug mode with %s" % pythonstr)
|
|
self.run_command(*args, env=dbg_env)
|
|
|
|
if self.aiotest:
|
|
args = (python, 'run_aiotest.py')
|
|
print("Run aiotest in release mode with %s" % pythonstr)
|
|
self.run_command(*args)
|
|
|
|
print("Run aiotest in debug mode with %s" % pythonstr)
|
|
self.run_command(*args, env=dbg_env)
|
|
|
|
def build(self, pyver, bits, *cmds):
|
|
self.cleanup()
|
|
|
|
setenv = self.windows_sdk_setenv(pyver, bits)
|
|
|
|
python = self.get_python(pyver, bits)
|
|
|
|
cmd = [python, 'setup.py'] + list(cmds)
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".bat", delete=False) as temp:
|
|
print("CD %s" % self.quote(self.root), file=temp)
|
|
print(self.quote_args(setenv), file=temp)
|
|
print(BATCH_FAIL_ON_ERROR, file=temp)
|
|
print("", file=temp)
|
|
print("SET DISTUTILS_USE_SDK=1", file=temp)
|
|
print("SET MSSDK=1", file=temp)
|
|
print(self.quote_args(cmd), file=temp)
|
|
print(BATCH_FAIL_ON_ERROR, file=temp)
|
|
|
|
try:
|
|
self.run_command(temp.name)
|
|
finally:
|
|
os.unlink(temp.name)
|
|
|
|
def test_wheel(self, pyver, bits):
|
|
self.build(pyver, bits, 'bdist_wheel')
|
|
|
|
def publish_wheel(self, pyver, bits):
|
|
self.build(pyver, bits, 'bdist_wheel', 'upload')
|
|
|
|
def main(self):
|
|
try:
|
|
pos = sys.argv[1:].index('--ignore')
|
|
except ValueError:
|
|
ignore = False
|
|
else:
|
|
ignore = True
|
|
del sys.argv[1+pos]
|
|
if len(sys.argv) != 2:
|
|
print("usage: %s hg_tag" % sys.argv[0])
|
|
sys.exit(1)
|
|
|
|
print("Directory: %s" % self.root)
|
|
os.chdir(self.root)
|
|
|
|
if not ignore:
|
|
lines = self.get_local_changes()
|
|
else:
|
|
lines = ()
|
|
if lines:
|
|
print("ERROR: Found local changes")
|
|
for line in lines:
|
|
print(line)
|
|
print("")
|
|
print("Revert local changes")
|
|
print("or use the --ignore command line option")
|
|
sys.exit(1)
|
|
|
|
hg_tag = sys.argv[1]
|
|
self.run_command(HG, 'up', hg_tag)
|
|
|
|
if self.test:
|
|
for pyver, bits in PYTHON_VERSIONS:
|
|
self.runtests(pyver, bits)
|
|
|
|
for pyver, bits in PYTHON_VERSIONS:
|
|
self.test_wheel(pyver, bits)
|
|
|
|
if self.dry_run:
|
|
sys.exit(0)
|
|
|
|
if self.register:
|
|
self.run_command(sys.executable, 'setup.py', 'register')
|
|
|
|
if self.sdist:
|
|
self.sdist_upload()
|
|
|
|
for pyver, bits in PYTHON_VERSIONS:
|
|
self.publish_wheel(pyver, bits)
|
|
|
|
print("")
|
|
if self.register:
|
|
print("Publish version %s" % hg_tag)
|
|
print("Uploaded:")
|
|
if self.sdist:
|
|
print("- sdist")
|
|
for pyver, bits in PYTHON_VERSIONS:
|
|
print("- Windows wheel %s bits package for Python %s.%s"
|
|
% (bits, pyver[0], pyver[1]))
|
|
|
|
if __name__ == "__main__":
|
|
Release().main()
|