/scripts/make-release.py

http://github.com/mitsuhiko/werkzeug · Python · 153 lines · 99 code · 41 blank · 13 comment · 22 complexity · 3cec0ee26ffdb4b12ce0024470c2b9c6 MD5 · raw file

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. make-release
  5. ~~~~~~~~~~~~
  6. Helper script that performs a release. Does pretty much everything
  7. automatically for us.
  8. :copyright: (c) 2014 by Armin Ronacher.
  9. :license: BSD, see LICENSE for more details.
  10. """
  11. import sys
  12. import shutil
  13. import os
  14. import re
  15. from datetime import datetime, date
  16. from subprocess import Popen, PIPE
  17. _date_clean_re = re.compile(r'(\d+)(st|nd|rd|th)')
  18. def parse_changelog():
  19. with open('CHANGES') as f:
  20. lineiter = iter(f)
  21. for line in lineiter:
  22. match = re.search('^Version\s+(.*)', line.strip())
  23. if match is None:
  24. continue
  25. version = match.group(1).strip()
  26. if lineiter.next().count('-') != len(match.group(0)):
  27. continue
  28. while 1:
  29. change_info = lineiter.next().strip()
  30. if change_info:
  31. break
  32. match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)'
  33. r'(?:, codename (.*))?(?i)', change_info)
  34. if match is None:
  35. continue
  36. datestr, codename = match.groups()
  37. return version, parse_date(datestr), codename
  38. def bump_version(version):
  39. try:
  40. parts = map(int, version.split('.'))
  41. except ValueError:
  42. fail('Current version is not numeric')
  43. parts[-1] += 1
  44. return '.'.join(map(str, parts))
  45. def parse_date(string):
  46. string = _date_clean_re.sub(r'\1', string)
  47. return datetime.strptime(string, '%B %d %Y')
  48. def set_filename_version(filename, version_number, pattern):
  49. changed = []
  50. def inject_version(match):
  51. before, old, after = match.groups()
  52. changed.append(True)
  53. return before + version_number + after
  54. with open(filename) as f:
  55. contents = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern,
  56. inject_version, f.read())
  57. if not changed:
  58. fail('Could not find %s in %s', pattern, filename)
  59. with open(filename, 'w') as f:
  60. f.write(contents)
  61. def set_init_version(version):
  62. info('Setting __init__.py version to %s', version)
  63. set_filename_version('werkzeug/__init__.py', version, '__version__')
  64. def build_and_upload():
  65. # Work around setuptools packaging stray .pyc files
  66. if os.path.exists('werkzeug/testsuite'):
  67. shutil.rmtree('werkzeug/testsuite')
  68. Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel',
  69. 'upload']).wait()
  70. def fail(message, *args):
  71. print >> sys.stderr, 'Error:', message % args
  72. sys.exit(1)
  73. def info(message, *args):
  74. print >> sys.stderr, message % args
  75. def get_git_tags():
  76. return set(Popen(['git', 'tag'], stdout=PIPE)
  77. .communicate()[0]
  78. .splitlines())
  79. def git_is_clean():
  80. return Popen(['git', 'diff', '--quiet']).wait() == 0
  81. def make_git_commit(message, *args):
  82. message = message % args
  83. Popen(['git', 'commit', '-am', message]).wait()
  84. def make_git_tag(tag):
  85. info('Tagging "%s"', tag)
  86. Popen(['git', 'tag', tag]).wait()
  87. def main():
  88. os.chdir(os.path.join(os.path.dirname(__file__), '..'))
  89. rv = parse_changelog()
  90. if rv is None:
  91. fail('Could not parse changelog')
  92. version, release_date, codename = rv
  93. dev_version = bump_version(version) + '-dev'
  94. info('Releasing %s (codename %s, release date %s)',
  95. version, codename, release_date.strftime('%d/%m/%Y'))
  96. tags = get_git_tags()
  97. if version in tags:
  98. fail('Version "%s" is already tagged', version)
  99. if release_date.date() != date.today():
  100. fail('Release date is not today (%s != %s)',
  101. release_date.date(), date.today())
  102. if not git_is_clean():
  103. fail('You have uncommitted changes in git')
  104. set_init_version(version)
  105. make_git_commit('Bump version number to %s', version)
  106. make_git_tag(version)
  107. build_and_upload()
  108. set_init_version(dev_version)
  109. if __name__ == '__main__':
  110. main()