Newer
Older
from subprocess import check_call, check_output
from weboob.tools.misc import to_unicode
WORKTREE = 'release_tmp'
def make_tarball(tag, wheel):
# Create and enter a temporary worktree
if os.path.isdir(WORKTREE):
check_call(['git', 'worktree', 'remove', '--force', WORKTREE])
check_call(['git', 'worktree', 'add', WORKTREE, tag])
assert os.path.isdir(WORKTREE)
os.chdir(WORKTREE)
check_call([sys.executable, 'setup.py'] +
['sdist',
'--keep',
'--dist-dir', '../dist'])
if wheel:
check_call([sys.executable, 'setup.py'] +
['bdist_wheel',
'--keep',
'--dist-dir', '../dist'])
# Clean up the temporary worktree
os.chdir(os.pardir)
check_call(['git', 'worktree', 'remove', '--force', WORKTREE])
assert not os.path.isdir(WORKTREE)
files = ['dist/weboob-%s.tar.gz' % tag]
if wheel:
files.append('dist/weboob-%s-py2.py3-none-any.whl' % tag)
for f in files:
if not os.path.exists(f):
raise Exception('Generated file not found at %s' % f)
else:
print('Generated file: %s' % f)
print('To upload to PyPI, run: twine upload -s %s' % ' '.join(files))
def changed_modules(changes, changetype):
for change in changes:
change = change.decode('utf-8').split()
if change[0] == changetype:
m = re.match(r'modules/([^/]+)/__init__\.py', change[1])
if m:
yield m.group(1)
def get_caps(module, config):
try:
return sorted(c for c in config[module]['capabilities'].split() if c != 'CapCollection')
except KeyError:
return ['**** FILL ME **** (running weboob update could help)']
def new_modules(start, end):
os.chdir(os.path.join(os.path.dirname(__file__), os.path.pardir))
modules_info = configparser.ConfigParser()
with open('modules/modules.list') as f:
modules_info.read_file(f)
git_cmd = ['git', 'diff', '--no-renames', '--name-status', '%s..%s' % (start, end), '--', 'modules/']
added_modules = sorted(changed_modules(check_output(git_cmd).splitlines(), 'A'))
deleted_modules = sorted(changed_modules(check_output(git_cmd).splitlines(), 'D'))
for added_module in added_modules:
yield 'New %s module (%s)' % (added_module, ', '.join(get_caps(added_module, modules_info)))
for deleted_module in deleted_modules:
yield 'Deleted %s module' % deleted_module
def changelog(start, end='HEAD'):
def sortkey(d):
"""Put the commits with multiple domains at the end"""
return (len(d), d)
commits = {}
for commithash in check_output(['git', 'rev-list', '{}..{}'.format(start, end)]).splitlines():
title, domains = commitinfo(commithash)
commits.setdefault(domains, []).append(title)
for line in new_modules(start, end):
commits.setdefault(('General',), []).append(line)
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
cl = ''
for domains in sorted(commits.keys(), key=sortkey):
cl += '\n\n\t' + '\n\t'.join(domains)
for title in commits[domains]:
cl += '\n\t* ' + title
return cl.lstrip('\n')
def domain(path):
dirs = os.path.dirname(path).split('/')
if dirs == ['']:
return 'General: Core'
if dirs[0] == 'man' or path == 'tools/py3-compatible.modules':
return None
if dirs[0] == 'weboob':
try:
if dirs[1] in ('core', 'tools'):
return 'General: Core'
elif dirs[1] == 'capabilities':
return 'Capabilities'
elif dirs[1] == 'browser':
try:
if dirs[2] == 'filters':
return 'Browser: Filters'
except IndexError:
return 'Browser'
elif dirs[1] == 'applications':
try:
return 'Applications: {}'.format(dirs[2])
except IndexError:
return 'Applications'
elif dirs[1] == 'application':
try:
return 'Applications: {}'.format(dirs[2].title())
except IndexError:
return 'Applications'
except IndexError:
return 'General: Core'
if dirs[0] in ('contrib', 'tools'):
return 'Tools'
if dirs[0] in ('docs', 'icons'):
return 'Documentation'
if dirs[0] == 'modules':
try:
return 'Modules: {}'.format(dirs[1])
except IndexError:
return 'General: Core'
return 'Unknown'
def commitinfo(commithash):
info = check_output(['git', 'show', '--format=%s', '--name-only', commithash]).decode('utf-8').splitlines()
title = to_unicode(info[0])
domains = set([domain(p) for p in info[2:] if domain(p)])
if 'Unknown' in domains and len(domains) > 1:
domains.remove('Unknown')
if not domains or len(domains) > 5:
domains = set(['Unknown'])
if 'Unknown' not in domains:
# When the domains are known, hide the title prefixes
title = re.sub(r'^(?:[\w\./\s]+:|\[[\w\./\s]+\])\s*', '', title, flags=re.UNICODE)
return title, tuple(sorted(domains))
def previous_version():
"""
Get the highest version tag
"""
for v in check_output(['git', 'tag', '-l', '*.*', '--sort=-v:refname']).splitlines():
def prepare(start, end, version):
print('Weboob %s (%s)\n' % (version, datetime.date.today().strftime('%Y-%m-%d')))
print(changelog(start, end))
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description="Prepare and export a release.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog='This is mostly meant to be called from release.sh for now.',
)
subparsers = parser.add_subparsers()
prepare_parser = subparsers.add_parser(
'prepare',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
prepare_parser.add_argument('version')
prepare_parser.add_argument('--start', default=previous_version(), help='Commit of the previous release')
prepare_parser.add_argument('--end', default='HEAD', help='Last commit before the new release')
prepare_parser.set_defaults(mode='prepare')
tarball_parser = subparsers.add_parser(
'tarball',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
tarball_parser.add_argument('tag')
tarball_parser.add_argument('--no-wheel', action='store_false', dest='wheel')
tarball_parser.set_defaults(mode='tarball')
args = parser.parse_args()
if args.mode == 'prepare':
prepare(args.start, args.end, args.version)
elif args.mode == 'tarball':
make_tarball(args.tag, args.wheel)