1*5c90c05cSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*5c90c05cSAndroid Build Coastguard Worker 3*5c90c05cSAndroid Build Coastguard Worker"""Manage site and releases. 4*5c90c05cSAndroid Build Coastguard Worker 5*5c90c05cSAndroid Build Coastguard WorkerUsage: 6*5c90c05cSAndroid Build Coastguard Worker manage.py release [<branch>] 7*5c90c05cSAndroid Build Coastguard Worker manage.py site 8*5c90c05cSAndroid Build Coastguard Worker 9*5c90c05cSAndroid Build Coastguard WorkerFor the release command $FMT_TOKEN should contain a GitHub personal access token 10*5c90c05cSAndroid Build Coastguard Workerobtained from https://github.com/settings/tokens. 11*5c90c05cSAndroid Build Coastguard Worker""" 12*5c90c05cSAndroid Build Coastguard Worker 13*5c90c05cSAndroid Build Coastguard Workerfrom __future__ import print_function 14*5c90c05cSAndroid Build Coastguard Workerimport datetime, docopt, errno, fileinput, json, os 15*5c90c05cSAndroid Build Coastguard Workerimport re, requests, shutil, sys 16*5c90c05cSAndroid Build Coastguard Workerfrom contextlib import contextmanager 17*5c90c05cSAndroid Build Coastguard Workerfrom subprocess import check_call 18*5c90c05cSAndroid Build Coastguard Worker 19*5c90c05cSAndroid Build Coastguard Worker 20*5c90c05cSAndroid Build Coastguard Workerclass Git: 21*5c90c05cSAndroid Build Coastguard Worker def __init__(self, dir): 22*5c90c05cSAndroid Build Coastguard Worker self.dir = dir 23*5c90c05cSAndroid Build Coastguard Worker 24*5c90c05cSAndroid Build Coastguard Worker def call(self, method, args, **kwargs): 25*5c90c05cSAndroid Build Coastguard Worker return check_call(['git', method] + list(args), **kwargs) 26*5c90c05cSAndroid Build Coastguard Worker 27*5c90c05cSAndroid Build Coastguard Worker def add(self, *args): 28*5c90c05cSAndroid Build Coastguard Worker return self.call('add', args, cwd=self.dir) 29*5c90c05cSAndroid Build Coastguard Worker 30*5c90c05cSAndroid Build Coastguard Worker def checkout(self, *args): 31*5c90c05cSAndroid Build Coastguard Worker return self.call('checkout', args, cwd=self.dir) 32*5c90c05cSAndroid Build Coastguard Worker 33*5c90c05cSAndroid Build Coastguard Worker def clean(self, *args): 34*5c90c05cSAndroid Build Coastguard Worker return self.call('clean', args, cwd=self.dir) 35*5c90c05cSAndroid Build Coastguard Worker 36*5c90c05cSAndroid Build Coastguard Worker def clone(self, *args): 37*5c90c05cSAndroid Build Coastguard Worker return self.call('clone', list(args) + [self.dir]) 38*5c90c05cSAndroid Build Coastguard Worker 39*5c90c05cSAndroid Build Coastguard Worker def commit(self, *args): 40*5c90c05cSAndroid Build Coastguard Worker return self.call('commit', args, cwd=self.dir) 41*5c90c05cSAndroid Build Coastguard Worker 42*5c90c05cSAndroid Build Coastguard Worker def pull(self, *args): 43*5c90c05cSAndroid Build Coastguard Worker return self.call('pull', args, cwd=self.dir) 44*5c90c05cSAndroid Build Coastguard Worker 45*5c90c05cSAndroid Build Coastguard Worker def push(self, *args): 46*5c90c05cSAndroid Build Coastguard Worker return self.call('push', args, cwd=self.dir) 47*5c90c05cSAndroid Build Coastguard Worker 48*5c90c05cSAndroid Build Coastguard Worker def reset(self, *args): 49*5c90c05cSAndroid Build Coastguard Worker return self.call('reset', args, cwd=self.dir) 50*5c90c05cSAndroid Build Coastguard Worker 51*5c90c05cSAndroid Build Coastguard Worker def update(self, *args): 52*5c90c05cSAndroid Build Coastguard Worker clone = not os.path.exists(self.dir) 53*5c90c05cSAndroid Build Coastguard Worker if clone: 54*5c90c05cSAndroid Build Coastguard Worker self.clone(*args) 55*5c90c05cSAndroid Build Coastguard Worker return clone 56*5c90c05cSAndroid Build Coastguard Worker 57*5c90c05cSAndroid Build Coastguard Worker 58*5c90c05cSAndroid Build Coastguard Workerdef clean_checkout(repo, branch): 59*5c90c05cSAndroid Build Coastguard Worker repo.clean('-f', '-d') 60*5c90c05cSAndroid Build Coastguard Worker repo.reset('--hard') 61*5c90c05cSAndroid Build Coastguard Worker repo.checkout(branch) 62*5c90c05cSAndroid Build Coastguard Worker 63*5c90c05cSAndroid Build Coastguard Worker 64*5c90c05cSAndroid Build Coastguard Workerclass Runner: 65*5c90c05cSAndroid Build Coastguard Worker def __init__(self, cwd): 66*5c90c05cSAndroid Build Coastguard Worker self.cwd = cwd 67*5c90c05cSAndroid Build Coastguard Worker 68*5c90c05cSAndroid Build Coastguard Worker def __call__(self, *args, **kwargs): 69*5c90c05cSAndroid Build Coastguard Worker kwargs['cwd'] = kwargs.get('cwd', self.cwd) 70*5c90c05cSAndroid Build Coastguard Worker check_call(args, **kwargs) 71*5c90c05cSAndroid Build Coastguard Worker 72*5c90c05cSAndroid Build Coastguard Worker 73*5c90c05cSAndroid Build Coastguard Workerdef create_build_env(): 74*5c90c05cSAndroid Build Coastguard Worker """Create a build environment.""" 75*5c90c05cSAndroid Build Coastguard Worker class Env: 76*5c90c05cSAndroid Build Coastguard Worker pass 77*5c90c05cSAndroid Build Coastguard Worker env = Env() 78*5c90c05cSAndroid Build Coastguard Worker env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 79*5c90c05cSAndroid Build Coastguard Worker env.build_dir = 'build' 80*5c90c05cSAndroid Build Coastguard Worker env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) 81*5c90c05cSAndroid Build Coastguard Worker return env 82*5c90c05cSAndroid Build Coastguard Worker 83*5c90c05cSAndroid Build Coastguard Worker 84*5c90c05cSAndroid Build Coastguard Workerfmt_repo_url = '[email protected]:fmtlib/fmt' 85*5c90c05cSAndroid Build Coastguard Worker 86*5c90c05cSAndroid Build Coastguard Worker 87*5c90c05cSAndroid Build Coastguard Workerdef update_site(env): 88*5c90c05cSAndroid Build Coastguard Worker env.fmt_repo.update(fmt_repo_url) 89*5c90c05cSAndroid Build Coastguard Worker 90*5c90c05cSAndroid Build Coastguard Worker doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev')) 91*5c90c05cSAndroid Build Coastguard Worker doc_repo.update('[email protected]:fmtlib/fmt.dev') 92*5c90c05cSAndroid Build Coastguard Worker 93*5c90c05cSAndroid Build Coastguard Worker version = '11.0.0' 94*5c90c05cSAndroid Build Coastguard Worker clean_checkout(env.fmt_repo, version) 95*5c90c05cSAndroid Build Coastguard Worker target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') 96*5c90c05cSAndroid Build Coastguard Worker 97*5c90c05cSAndroid Build Coastguard Worker # Build the docs. 98*5c90c05cSAndroid Build Coastguard Worker html_dir = os.path.join(env.build_dir, 'html') 99*5c90c05cSAndroid Build Coastguard Worker if os.path.exists(html_dir): 100*5c90c05cSAndroid Build Coastguard Worker shutil.rmtree(html_dir) 101*5c90c05cSAndroid Build Coastguard Worker include_dir = env.fmt_repo.dir 102*5c90c05cSAndroid Build Coastguard Worker import build 103*5c90c05cSAndroid Build Coastguard Worker build.build_docs(version, doc_dir=target_doc_dir, 104*5c90c05cSAndroid Build Coastguard Worker include_dir=include_dir, work_dir=env.build_dir) 105*5c90c05cSAndroid Build Coastguard Worker shutil.rmtree(os.path.join(html_dir, '.doctrees')) 106*5c90c05cSAndroid Build Coastguard Worker # Copy docs to the website. 107*5c90c05cSAndroid Build Coastguard Worker version_doc_dir = os.path.join(doc_repo.dir, version) 108*5c90c05cSAndroid Build Coastguard Worker try: 109*5c90c05cSAndroid Build Coastguard Worker shutil.rmtree(version_doc_dir) 110*5c90c05cSAndroid Build Coastguard Worker except OSError as e: 111*5c90c05cSAndroid Build Coastguard Worker if e.errno != errno.ENOENT: 112*5c90c05cSAndroid Build Coastguard Worker raise 113*5c90c05cSAndroid Build Coastguard Worker shutil.move(html_dir, version_doc_dir) 114*5c90c05cSAndroid Build Coastguard Worker 115*5c90c05cSAndroid Build Coastguard Worker 116*5c90c05cSAndroid Build Coastguard Workerdef release(args): 117*5c90c05cSAndroid Build Coastguard Worker env = create_build_env() 118*5c90c05cSAndroid Build Coastguard Worker fmt_repo = env.fmt_repo 119*5c90c05cSAndroid Build Coastguard Worker 120*5c90c05cSAndroid Build Coastguard Worker branch = args.get('<branch>') 121*5c90c05cSAndroid Build Coastguard Worker if branch is None: 122*5c90c05cSAndroid Build Coastguard Worker branch = 'master' 123*5c90c05cSAndroid Build Coastguard Worker if not fmt_repo.update('-b', branch, fmt_repo_url): 124*5c90c05cSAndroid Build Coastguard Worker clean_checkout(fmt_repo, branch) 125*5c90c05cSAndroid Build Coastguard Worker 126*5c90c05cSAndroid Build Coastguard Worker # Update the date in the changelog and extract the version and the first 127*5c90c05cSAndroid Build Coastguard Worker # section content. 128*5c90c05cSAndroid Build Coastguard Worker changelog = 'ChangeLog.md' 129*5c90c05cSAndroid Build Coastguard Worker changelog_path = os.path.join(fmt_repo.dir, changelog) 130*5c90c05cSAndroid Build Coastguard Worker is_first_section = True 131*5c90c05cSAndroid Build Coastguard Worker first_section = [] 132*5c90c05cSAndroid Build Coastguard Worker for i, line in enumerate(fileinput.input(changelog_path, inplace=True)): 133*5c90c05cSAndroid Build Coastguard Worker if i == 0: 134*5c90c05cSAndroid Build Coastguard Worker version = re.match(r'# (.*) - TBD', line).group(1) 135*5c90c05cSAndroid Build Coastguard Worker line = '# {} - {}\n'.format( 136*5c90c05cSAndroid Build Coastguard Worker version, datetime.date.today().isoformat()) 137*5c90c05cSAndroid Build Coastguard Worker elif not is_first_section: 138*5c90c05cSAndroid Build Coastguard Worker pass 139*5c90c05cSAndroid Build Coastguard Worker elif line.startswith('#'): 140*5c90c05cSAndroid Build Coastguard Worker is_first_section = False 141*5c90c05cSAndroid Build Coastguard Worker else: 142*5c90c05cSAndroid Build Coastguard Worker first_section.append(line) 143*5c90c05cSAndroid Build Coastguard Worker sys.stdout.write(line) 144*5c90c05cSAndroid Build Coastguard Worker if first_section[0] == '\n': 145*5c90c05cSAndroid Build Coastguard Worker first_section.pop(0) 146*5c90c05cSAndroid Build Coastguard Worker 147*5c90c05cSAndroid Build Coastguard Worker ns_version = None 148*5c90c05cSAndroid Build Coastguard Worker base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h') 149*5c90c05cSAndroid Build Coastguard Worker for line in fileinput.input(base_h_path): 150*5c90c05cSAndroid Build Coastguard Worker m = re.match(r'\s*inline namespace v(.*) .*', line) 151*5c90c05cSAndroid Build Coastguard Worker if m: 152*5c90c05cSAndroid Build Coastguard Worker ns_version = m.group(1) 153*5c90c05cSAndroid Build Coastguard Worker break 154*5c90c05cSAndroid Build Coastguard Worker major_version = version.split('.')[0] 155*5c90c05cSAndroid Build Coastguard Worker if not ns_version or ns_version != major_version: 156*5c90c05cSAndroid Build Coastguard Worker raise Exception(f'Version mismatch {ns_version} != {major_version}') 157*5c90c05cSAndroid Build Coastguard Worker 158*5c90c05cSAndroid Build Coastguard Worker # Workaround GitHub-flavored Markdown treating newlines as <br>. 159*5c90c05cSAndroid Build Coastguard Worker changes = '' 160*5c90c05cSAndroid Build Coastguard Worker code_block = False 161*5c90c05cSAndroid Build Coastguard Worker stripped = False 162*5c90c05cSAndroid Build Coastguard Worker for line in first_section: 163*5c90c05cSAndroid Build Coastguard Worker if re.match(r'^\s*```', line): 164*5c90c05cSAndroid Build Coastguard Worker code_block = not code_block 165*5c90c05cSAndroid Build Coastguard Worker changes += line 166*5c90c05cSAndroid Build Coastguard Worker stripped = False 167*5c90c05cSAndroid Build Coastguard Worker continue 168*5c90c05cSAndroid Build Coastguard Worker if code_block: 169*5c90c05cSAndroid Build Coastguard Worker changes += line 170*5c90c05cSAndroid Build Coastguard Worker continue 171*5c90c05cSAndroid Build Coastguard Worker if line == '\n' or re.match(r'^\s*\|.*', line): 172*5c90c05cSAndroid Build Coastguard Worker if stripped: 173*5c90c05cSAndroid Build Coastguard Worker changes += '\n' 174*5c90c05cSAndroid Build Coastguard Worker stripped = False 175*5c90c05cSAndroid Build Coastguard Worker changes += line 176*5c90c05cSAndroid Build Coastguard Worker continue 177*5c90c05cSAndroid Build Coastguard Worker if stripped: 178*5c90c05cSAndroid Build Coastguard Worker line = ' ' + line.lstrip() 179*5c90c05cSAndroid Build Coastguard Worker changes += line.rstrip() 180*5c90c05cSAndroid Build Coastguard Worker stripped = True 181*5c90c05cSAndroid Build Coastguard Worker 182*5c90c05cSAndroid Build Coastguard Worker fmt_repo.checkout('-B', 'release') 183*5c90c05cSAndroid Build Coastguard Worker fmt_repo.add(changelog) 184*5c90c05cSAndroid Build Coastguard Worker fmt_repo.commit('-m', 'Update version') 185*5c90c05cSAndroid Build Coastguard Worker 186*5c90c05cSAndroid Build Coastguard Worker # Build the docs and package. 187*5c90c05cSAndroid Build Coastguard Worker run = Runner(fmt_repo.dir) 188*5c90c05cSAndroid Build Coastguard Worker run('cmake', '.') 189*5c90c05cSAndroid Build Coastguard Worker run('make', 'doc', 'package_source') 190*5c90c05cSAndroid Build Coastguard Worker 191*5c90c05cSAndroid Build Coastguard Worker # Create a release on GitHub. 192*5c90c05cSAndroid Build Coastguard Worker fmt_repo.push('origin', 'release') 193*5c90c05cSAndroid Build Coastguard Worker auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} 194*5c90c05cSAndroid Build Coastguard Worker r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', 195*5c90c05cSAndroid Build Coastguard Worker headers=auth_headers, 196*5c90c05cSAndroid Build Coastguard Worker data=json.dumps({'tag_name': version, 197*5c90c05cSAndroid Build Coastguard Worker 'target_commitish': 'release', 198*5c90c05cSAndroid Build Coastguard Worker 'body': changes, 'draft': True})) 199*5c90c05cSAndroid Build Coastguard Worker if r.status_code != 201: 200*5c90c05cSAndroid Build Coastguard Worker raise Exception('Failed to create a release ' + str(r)) 201*5c90c05cSAndroid Build Coastguard Worker id = r.json()['id'] 202*5c90c05cSAndroid Build Coastguard Worker uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' 203*5c90c05cSAndroid Build Coastguard Worker package = 'fmt-{}.zip'.format(version) 204*5c90c05cSAndroid Build Coastguard Worker r = requests.post( 205*5c90c05cSAndroid Build Coastguard Worker '{}/{}/assets?name={}'.format(uploads_url, id, package), 206*5c90c05cSAndroid Build Coastguard Worker headers={'Content-Type': 'application/zip'} | auth_headers, 207*5c90c05cSAndroid Build Coastguard Worker data=open('build/fmt/' + package, 'rb')) 208*5c90c05cSAndroid Build Coastguard Worker if r.status_code != 201: 209*5c90c05cSAndroid Build Coastguard Worker raise Exception('Failed to upload an asset ' + str(r)) 210*5c90c05cSAndroid Build Coastguard Worker 211*5c90c05cSAndroid Build Coastguard Worker update_site(env) 212*5c90c05cSAndroid Build Coastguard Worker 213*5c90c05cSAndroid Build Coastguard Workerif __name__ == '__main__': 214*5c90c05cSAndroid Build Coastguard Worker args = docopt.docopt(__doc__) 215*5c90c05cSAndroid Build Coastguard Worker if args.get('release'): 216*5c90c05cSAndroid Build Coastguard Worker release(args) 217*5c90c05cSAndroid Build Coastguard Worker elif args.get('site'): 218*5c90c05cSAndroid Build Coastguard Worker update_site(create_build_env()) 219