3 """Manage site and releases.
6 manage.py release [<branch>]
9 For the release command $FMT_TOKEN should contain a GitHub personal access token
10 obtained from https://github.com/settings/tokens.
13 from __future__
import print_function
14 import datetime
, docopt
, errno
, fileinput
, json
, os
15 import re
, requests
, shutil
, sys
16 from contextlib
import contextmanager
17 from subprocess
import check_call
21 def __init__(self
, dir):
24 def call(self
, method
, args
, **kwargs
):
25 return check_call(['git', method
] + list(args
), **kwargs
)
28 return self
.call('add', args
, cwd
=self
.dir)
30 def checkout(self
, *args
):
31 return self
.call('checkout', args
, cwd
=self
.dir)
33 def clean(self
, *args
):
34 return self
.call('clean', args
, cwd
=self
.dir)
36 def clone(self
, *args
):
37 return self
.call('clone', list(args
) + [self
.dir])
39 def commit(self
, *args
):
40 return self
.call('commit', args
, cwd
=self
.dir)
42 def pull(self
, *args
):
43 return self
.call('pull', args
, cwd
=self
.dir)
45 def push(self
, *args
):
46 return self
.call('push', args
, cwd
=self
.dir)
48 def reset(self
, *args
):
49 return self
.call('reset', args
, cwd
=self
.dir)
51 def update(self
, *args
):
52 clone
= not os
.path
.exists(self
.dir)
58 def clean_checkout(repo
, branch
):
59 repo
.clean('-f', '-d')
65 def __init__(self
, cwd
):
68 def __call__(self
, *args
, **kwargs
):
69 kwargs
['cwd'] = kwargs
.get('cwd', self
.cwd
)
70 check_call(args
, **kwargs
)
73 def create_build_env():
74 """Create a build environment."""
78 env
.fmt_dir
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
79 env
.build_dir
= 'build'
80 env
.fmt_repo
= Git(os
.path
.join(env
.build_dir
, 'fmt'))
84 fmt_repo_url
= 'git@github.com:fmtlib/fmt'
88 env
.fmt_repo
.update(fmt_repo_url
)
90 doc_repo
= Git(os
.path
.join(env
.build_dir
, 'fmt.dev'))
91 doc_repo
.update('git@github.com:fmtlib/fmt.dev')
94 clean_checkout(env
.fmt_repo
, version
)
95 target_doc_dir
= os
.path
.join(env
.fmt_repo
.dir, 'doc')
98 html_dir
= os
.path
.join(env
.build_dir
, 'html')
99 if os
.path
.exists(html_dir
):
100 shutil
.rmtree(html_dir
)
101 include_dir
= env
.fmt_repo
.dir
103 build
.build_docs(version
, doc_dir
=target_doc_dir
,
104 include_dir
=include_dir
, work_dir
=env
.build_dir
)
105 shutil
.rmtree(os
.path
.join(html_dir
, '.doctrees'))
106 # Copy docs to the website.
107 version_doc_dir
= os
.path
.join(doc_repo
.dir, version
)
109 shutil
.rmtree(version_doc_dir
)
111 if e
.errno
!= errno
.ENOENT
:
113 shutil
.move(html_dir
, version_doc_dir
)
117 env
= create_build_env()
118 fmt_repo
= env
.fmt_repo
120 branch
= args
.get('<branch>')
123 if not fmt_repo
.update('-b', branch
, fmt_repo_url
):
124 clean_checkout(fmt_repo
, branch
)
126 # Update the date in the changelog and extract the version and the first
128 changelog
= 'ChangeLog.md'
129 changelog_path
= os
.path
.join(fmt_repo
.dir, changelog
)
130 is_first_section
= True
132 for i
, line
in enumerate(fileinput
.input(changelog_path
, inplace
=True)):
134 version
= re
.match(r
'# (.*) - TBD', line
).group(1)
135 line
= '# {} - {}\n'.format(
136 version
, datetime
.date
.today().isoformat())
137 elif not is_first_section
:
139 elif line
.startswith('#'):
140 is_first_section
= False
142 first_section
.append(line
)
143 sys
.stdout
.write(line
)
144 if first_section
[0] == '\n':
148 base_h_path
= os
.path
.join(fmt_repo
.dir, 'include', 'fmt', 'base.h')
149 for line
in fileinput
.input(base_h_path
):
150 m
= re
.match(r
'\s*inline namespace v(.*) .*', line
)
152 ns_version
= m
.group(1)
154 major_version
= version
.split('.')[0]
155 if not ns_version
or ns_version
!= major_version
:
156 raise Exception(f
'Version mismatch {ns_version} != {major_version}')
158 # Workaround GitHub-flavored Markdown treating newlines as <br>.
162 for line
in first_section
:
163 if re
.match(r
'^\s*```', line
):
164 code_block
= not code_block
171 if line
== '\n' or re
.match(r
'^\s*\|.*', line
):
178 line
= ' ' + line
.lstrip()
179 changes
+= line
.rstrip()
182 fmt_repo
.checkout('-B', 'release')
183 fmt_repo
.add(changelog
)
184 fmt_repo
.commit('-m', 'Update version')
186 # Build the docs and package.
187 run
= Runner(fmt_repo
.dir)
189 run('make', 'doc', 'package_source')
191 # Create a release on GitHub.
192 fmt_repo
.push('origin', 'release')
193 auth_headers
= {'Authorization': 'token ' + os
.getenv('FMT_TOKEN')}
194 r
= requests
.post('https://api.github.com/repos/fmtlib/fmt/releases',
195 headers
=auth_headers
,
196 data
=json
.dumps({'tag_name': version
,
197 'target_commitish': 'release',
198 'body': changes
, 'draft': True}))
199 if r
.status_code
!= 201:
200 raise Exception('Failed to create a release ' + str(r
))
202 uploads_url
= 'https://uploads.github.com/repos/fmtlib/fmt/releases'
203 package
= 'fmt-{}.zip'.format(version
)
205 '{}/{}/assets?name={}'.format(uploads_url
, id, package
),
206 headers
={'Content-Type': 'application/zip'} | auth_headers
,
207 data
=open('build/fmt/' + package
, 'rb'))
208 if r
.status_code
!= 201:
209 raise Exception('Failed to upload an asset ' + str(r
))
213 if __name__
== '__main__':
214 args
= docopt
.docopt(__doc__
)
215 if args
.get('release'):
217 elif args
.get('site'):
218 update_site(create_build_env())