Mercurial > minori
diff dep/fmt/support/manage.py @ 343:1faa72660932
*: transfer back to cmake from autotools
autotools just made lots of things more complicated than
they should have and many things broke (i.e. translations)
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Thu, 20 Jun 2024 05:56:06 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/fmt/support/manage.py Thu Jun 20 05:56:06 2024 -0400 @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 + +"""Manage site and releases. + +Usage: + manage.py release [<branch>] + manage.py site + +For the release command $FMT_TOKEN should contain a GitHub personal access token +obtained from https://github.com/settings/tokens. +""" + +from __future__ import print_function +import datetime, docopt, errno, fileinput, json, os +import re, requests, shutil, sys +from contextlib import contextmanager +from distutils.version import LooseVersion +from subprocess import check_call + + +class Git: + def __init__(self, dir): + self.dir = dir + + def call(self, method, args, **kwargs): + return check_call(['git', method] + list(args), **kwargs) + + def add(self, *args): + return self.call('add', args, cwd=self.dir) + + def checkout(self, *args): + return self.call('checkout', args, cwd=self.dir) + + def clean(self, *args): + return self.call('clean', args, cwd=self.dir) + + def clone(self, *args): + return self.call('clone', list(args) + [self.dir]) + + def commit(self, *args): + return self.call('commit', args, cwd=self.dir) + + def pull(self, *args): + return self.call('pull', args, cwd=self.dir) + + def push(self, *args): + return self.call('push', args, cwd=self.dir) + + def reset(self, *args): + return self.call('reset', args, cwd=self.dir) + + def update(self, *args): + clone = not os.path.exists(self.dir) + if clone: + self.clone(*args) + return clone + + +def clean_checkout(repo, branch): + repo.clean('-f', '-d') + repo.reset('--hard') + repo.checkout(branch) + + +class Runner: + def __init__(self, cwd): + self.cwd = cwd + + def __call__(self, *args, **kwargs): + kwargs['cwd'] = kwargs.get('cwd', self.cwd) + check_call(args, **kwargs) + + +def create_build_env(): + """Create a build environment.""" + class Env: + pass + env = Env() + + # Import the documentation build module. + env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.join(env.fmt_dir, 'doc')) + import build + + env.build_dir = 'build' + env.versions = build.versions + + # Virtualenv and repos are cached to speed up builds. + build.create_build_env(os.path.join(env.build_dir, 'virtualenv')) + + env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) + return env + + +@contextmanager +def rewrite(filename): + class Buffer: + pass + buffer = Buffer() + if not os.path.exists(filename): + buffer.data = '' + yield buffer + return + with open(filename) as f: + buffer.data = f.read() + yield buffer + with open(filename, 'w') as f: + f.write(buffer.data) + + +fmt_repo_url = 'git@github.com:fmtlib/fmt' + + +def update_site(env): + env.fmt_repo.update(fmt_repo_url) + + doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io')) + doc_repo.update('git@github.com:fmtlib/fmtlib.github.io') + + for version in env.versions: + clean_checkout(env.fmt_repo, version) + target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') + # Remove the old theme. + for entry in os.listdir(target_doc_dir): + path = os.path.join(target_doc_dir, entry) + if os.path.isdir(path): + shutil.rmtree(path) + # Copy the new theme. + for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap', + 'conf.py', 'fmt.less']: + src = os.path.join(env.fmt_dir, 'doc', entry) + dst = os.path.join(target_doc_dir, entry) + copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile + copy(src, dst) + # Rename index to contents. + contents = os.path.join(target_doc_dir, 'contents.rst') + if not os.path.exists(contents): + os.rename(os.path.join(target_doc_dir, 'index.rst'), contents) + # Fix issues in reference.rst/api.rst. + for filename in ['reference.rst', 'api.rst', 'index.rst']: + pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M) + with rewrite(os.path.join(target_doc_dir, filename)) as b: + b.data = b.data.replace('std::ostream &', 'std::ostream&') + b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data) + b.data = b.data.replace('std::FILE*', 'std::FILE *') + b.data = b.data.replace('unsigned int', 'unsigned') + #b.data = b.data.replace('operator""_', 'operator"" _') + b.data = b.data.replace( + 'format_to_n(OutputIt, size_t, string_view, Args&&', + 'format_to_n(OutputIt, size_t, const S&, const Args&') + b.data = b.data.replace( + 'format_to_n(OutputIt, std::size_t, string_view, Args&&', + 'format_to_n(OutputIt, std::size_t, const S&, const Args&') + if version == ('3.0.2'): + b.data = b.data.replace( + 'fprintf(std::ostream&', 'fprintf(std::ostream &') + if version == ('5.3.0'): + b.data = b.data.replace( + 'format_to(OutputIt, const S&, const Args&...)', + 'format_to(OutputIt, const S &, const Args &...)') + if version.startswith('5.') or version.startswith('6.'): + b.data = b.data.replace(', size_t', ', std::size_t') + if version.startswith('7.'): + b.data = b.data.replace(', std::size_t', ', size_t') + b.data = b.data.replace('join(It, It', 'join(It, Sentinel') + if version.startswith('7.1.'): + b.data = b.data.replace(', std::size_t', ', size_t') + b.data = b.data.replace('join(It, It', 'join(It, Sentinel') + b.data = b.data.replace( + 'fmt::format_to(OutputIt, const S&, Args&&...)', + 'fmt::format_to(OutputIt, const S&, Args&&...) -> ' + + 'typename std::enable_if<enable, OutputIt>::type') + b.data = b.data.replace('aa long', 'a long') + b.data = b.data.replace('serveral', 'several') + if version.startswith('6.2.'): + b.data = b.data.replace( + 'vformat(const S&, basic_format_args<' + + 'buffer_context<Char>>)', + 'vformat(const S&, basic_format_args<' + + 'buffer_context<type_identity_t<Char>>>)') + # Fix a broken link in index.rst. + index = os.path.join(target_doc_dir, 'index.rst') + with rewrite(index) as b: + b.data = b.data.replace( + 'doc/latest/index.html#format-string-syntax', 'syntax.html') + # Fix issues in syntax.rst. + index = os.path.join(target_doc_dir, 'syntax.rst') + with rewrite(index) as b: + b.data = b.data.replace( + '..productionlist:: sf\n', '.. productionlist:: sf\n ') + b.data = b.data.replace('Examples:\n', 'Examples::\n') + # Build the docs. + html_dir = os.path.join(env.build_dir, 'html') + if os.path.exists(html_dir): + shutil.rmtree(html_dir) + include_dir = env.fmt_repo.dir + if LooseVersion(version) >= LooseVersion('5.0.0'): + include_dir = os.path.join(include_dir, 'include', 'fmt') + elif LooseVersion(version) >= LooseVersion('3.0.0'): + include_dir = os.path.join(include_dir, 'fmt') + import build + build.build_docs(version, doc_dir=target_doc_dir, + include_dir=include_dir, work_dir=env.build_dir) + shutil.rmtree(os.path.join(html_dir, '.doctrees')) + # Create symlinks for older versions. + for link, target in {'index': 'contents', 'api': 'reference'}.items(): + link = os.path.join(html_dir, link) + '.html' + target += '.html' + if os.path.exists(os.path.join(html_dir, target)) and \ + not os.path.exists(link): + os.symlink(target, link) + # Copy docs to the website. + version_doc_dir = os.path.join(doc_repo.dir, version) + try: + shutil.rmtree(version_doc_dir) + except OSError as e: + if e.errno != errno.ENOENT: + raise + shutil.move(html_dir, version_doc_dir) + + +def release(args): + env = create_build_env() + fmt_repo = env.fmt_repo + + branch = args.get('<branch>') + if branch is None: + branch = 'master' + if not fmt_repo.update('-b', branch, fmt_repo_url): + clean_checkout(fmt_repo, branch) + + # Update the date in the changelog and extract the version and the first + # section content. + changelog = 'ChangeLog.md' + changelog_path = os.path.join(fmt_repo.dir, changelog) + is_first_section = True + first_section = [] + for i, line in enumerate(fileinput.input(changelog_path, inplace=True)): + if i == 0: + version = re.match(r'# (.*) - TBD', line).group(1) + line = '# {} - {}\n'.format( + version, datetime.date.today().isoformat()) + elif not is_first_section: + pass + elif line.startswith('#'): + is_first_section = False + else: + first_section.append(line) + sys.stdout.write(line) + if first_section[0] == '\n': + first_section.pop(0) + + changes = '' + code_block = False + stripped = False + for line in first_section: + if re.match(r'^\s*```', line): + code_block = not code_block + changes += line + stripped = False + continue + if code_block: + changes += line + continue + if line == '\n': + changes += line + if stripped: + changes += line + stripped = False + continue + if stripped: + line = ' ' + line.lstrip() + changes += line.rstrip() + stripped = True + + cmakelists = 'CMakeLists.txt' + for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists), + inplace=True): + prefix = 'set(FMT_VERSION ' + if line.startswith(prefix): + line = prefix + version + ')\n' + sys.stdout.write(line) + + # Add the version to the build script. + script = os.path.join('doc', 'build.py') + script_path = os.path.join(fmt_repo.dir, script) + for line in fileinput.input(script_path, inplace=True): + m = re.match(r'( *versions \+= )\[(.+)\]', line) + if m: + line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version) + sys.stdout.write(line) + + fmt_repo.checkout('-B', 'release') + fmt_repo.add(changelog, cmakelists, script) + fmt_repo.commit('-m', 'Update version') + + # Build the docs and package. + run = Runner(fmt_repo.dir) + run('cmake', '.') + run('make', 'doc', 'package_source') + update_site(env) + + # Create a release on GitHub. + fmt_repo.push('origin', 'release') + auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} + r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', + headers=auth_headers, + data=json.dumps({'tag_name': version, + 'target_commitish': 'release', + 'body': changes, 'draft': True})) + if r.status_code != 201: + raise Exception('Failed to create a release ' + str(r)) + id = r.json()['id'] + uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' + package = 'fmt-{}.zip'.format(version) + r = requests.post( + '{}/{}/assets?name={}'.format(uploads_url, id, package), + headers={'Content-Type': 'application/zip'} | auth_headers, + data=open('build/fmt/' + package, 'rb')) + if r.status_code != 201: + raise Exception('Failed to upload an asset ' + str(r)) + + +if __name__ == '__main__': + args = docopt.docopt(__doc__) + if args.get('release'): + release(args) + elif args.get('site'): + update_site(create_build_env())