xref: /aosp_15_r20/external/fmtlib/support/manage.py (revision 5c90c05cd622c0a81b57953a4d343e0e489f2e08)
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