diff --git a/tools/make_dist_html.py b/tools/make_dist_html.py
new file mode 100644
index 00000000..7a19d3e1
--- /dev/null
+++ b/tools/make_dist_html.py
@@ -0,0 +1,124 @@
+#!/usr/bin/python
+
+from __future__ import print_function
+
+import itertools
+import os
+import re
+import subprocess
+
+HTML = r'''
+
+
+
+
+
+
+
+
+
+
+
+'''
+
+GROUPS = r'''
+
+ | {groups[0]} |
+ {groups[1]} |
+ {groups[2]} |
+ {groups[3]} |
+
+'''
+
+GROUP = r'''
+
+
+ | version |
+ tarball |
+ gpg |
+ windows |
+
+ {rows}
+
+'''
+
+ROW = r'''
+
+ |
+ {tag}
+ |
+
+ tarball
+ |
+ {maybe_gpg} |
+ {maybe_exe} |
+
+'''
+
+GPG = r'''
+gpg
+'''
+
+# The binaries don't have a predictable name, link to the directory instead.
+EXE = r'''
+exe
+'''
+
+def version(tag):
+ return map(int, re.match('^v(\d+)\.(\d+)\.(\d+)', tag).groups())
+
+def major_minor(tag):
+ return version(tag)[:2]
+
+def row_for(tag):
+ maybe_gpg = ''
+ maybe_exe = ''
+ # We didn't start signing releases and producing Windows installers
+ # until v1.7.0.
+ if version(tag) >= version('v1.7.0'):
+ maybe_gpg = GPG.format(**locals())
+ maybe_exe = EXE.format(**locals())
+ return ROW.format(**locals())
+
+def group_for(tags):
+ rows = ''.join(row_for(tag) for tag in tags)
+ return GROUP.format(rows=rows)
+
+# Partition in groups of |n|.
+def groups_for(groups, n=4):
+ html = ''
+ groups = groups[:] + [''] * (n - 1)
+ while len(groups) >= n:
+ html += GROUPS.format(groups=groups)
+ groups = groups[n:]
+ return html
+
+if __name__ == '__main__':
+ os.chdir(os.path.dirname(__file__))
+ tags = subprocess.check_output(['git', 'tag'])
+ tags = [tag for tag in tags.split('\n') if tag.startswith('v')]
+ tags.sort(key=version, reverse=True)
+ groups = [group_for(list(g)) for _, g in itertools.groupby(tags, major_minor)]
+ groups = groups_for(groups)
+ html = HTML.format(groups=groups).strip()
+ html = re.sub('>\\s+<', '><', html)
+ print(html)