]> git.lizzy.rs Git - rust.git/blob - src/tools/publish_toolstate.py
Track embedded-book in the toolstate
[rust.git] / src / tools / publish_toolstate.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import sys
5 import re
6 import json
7 import datetime
8 import collections
9 import textwrap
10 try:
11     import urllib2
12 except ImportError:
13     import urllib.request as urllib2
14
15 # List of people to ping when the status of a tool or a book changed.
16 MAINTAINERS = {
17     'miri': '@oli-obk @RalfJung @eddyb',
18     'clippy-driver': '@Manishearth @llogiq @mcarton @oli-obk @phansch',
19     'rls': '@nrc @Xanewok',
20     'rustfmt': '@nrc @topecongiro',
21     'book': '@carols10cents @steveklabnik',
22     'nomicon': '@frewsxcv @Gankro',
23     'reference': '@steveklabnik @Havvy @matthewjasper @alercah',
24     'rust-by-example': '@steveklabnik @marioidival @projektir',
25     'embedded-book': '',
26 }
27
28 REPOS = {
29     'miri': 'https://github.com/rust-lang/miri',
30     'clippy-driver': 'https://github.com/rust-lang/rust-clippy',
31     'rls': 'https://github.com/rust-lang/rls',
32     'rustfmt': 'https://github.com/rust-lang/rustfmt',
33     'book': 'https://github.com/rust-lang/book',
34     'nomicon': 'https://github.com/rust-lang-nursery/nomicon',
35     'reference': 'https://github.com/rust-lang-nursery/reference',
36     'rust-by-example': 'https://github.com/rust-lang/rust-by-example',
37     'embedded-book': 'https://github.com/rust-embedded/book',
38 }
39
40
41 def read_current_status(current_commit, path):
42     '''Reads build status of `current_commit` from content of `history/*.tsv`
43     '''
44     with open(path, 'rU') as f:
45         for line in f:
46             (commit, status) = line.split('\t', 1)
47             if commit == current_commit:
48                 return json.loads(status)
49     return {}
50
51 def issue(
52     tool,
53     maintainers,
54     relevant_pr_number,
55     relevant_pr_user,
56     pr_reviewer,
57 ):
58     # Open an issue about the toolstate failure.
59     gh_url = 'https://api.github.com/repos/rust-lang/rust/issues'
60     assignees = [x.strip() for x in maintainers.split('@') if x != '']
61     assignees.append(relevant_pr_user)
62     response = urllib2.urlopen(urllib2.Request(
63         gh_url,
64         json.dumps({
65             'body': textwrap.dedent('''\
66             Hello, this is your friendly neighborhood mergebot.
67             After merging PR {}, I observed that the tool {} no longer builds.
68             A follow-up PR to the repository {} is needed to fix the fallout.
69
70             cc @{}, do you think you would have time to do the follow-up work?
71             If so, that would be great!
72
73             cc @{}, the PR reviewer, and @rust-lang/compiler -- nominating for prioritization.
74
75             ''').format(relevant_pr_number, tool, REPOS.get(tool), relevant_pr_user, pr_reviewer),
76             'title': '`{}` no longer builds after {}'.format(tool, relevant_pr_number),
77             'assignees': assignees,
78             'labels': ['T-compiler', 'I-nominated'],
79         }),
80         {
81             'Authorization': 'token ' + github_token,
82             'Content-Type': 'application/json',
83         }
84     ))
85     response.read()
86
87 def update_latest(
88     current_commit,
89     relevant_pr_number,
90     relevant_pr_url,
91     relevant_pr_user,
92     pr_reviewer,
93     current_datetime
94 ):
95     '''Updates `_data/latest.json` to match build result of the given commit.
96     '''
97     with open('_data/latest.json', 'rb+') as f:
98         latest = json.load(f, object_pairs_hook=collections.OrderedDict)
99
100         current_status = {
101             os: read_current_status(current_commit, 'history/' + os + '.tsv')
102             for os in ['windows', 'linux']
103         }
104
105         slug = 'rust-lang/rust'
106         message = textwrap.dedent('''\
107             ðŸ“£ Toolstate changed by {}!
108
109             Tested on commit {}@{}.
110             Direct link to PR: <{}>
111
112         ''').format(relevant_pr_number, slug, current_commit, relevant_pr_url)
113         anything_changed = False
114         for status in latest:
115             tool = status['tool']
116             changed = False
117             build_failed = False
118
119             for os, s in current_status.items():
120                 old = status[os]
121                 new = s.get(tool, old)
122                 status[os] = new
123                 if new > old:
124                     # things got fixed or at least the status quo improved
125                     changed = True
126                     message += '🎉 {} on {}: {} â†’ {} (cc {}, @rust-lang/infra).\n' \
127                         .format(tool, os, old, new, MAINTAINERS.get(tool))
128                 elif new < old:
129                     # tests or builds are failing and were not failing before
130                     changed = True
131                     title = '💔 {} on {}: {} â†’ {}' \
132                         .format(tool, os, old, new)
133                     message += '{} (cc {}, @rust-lang/infra).\n' \
134                         .format(title, MAINTAINERS.get(tool))
135                     # only create issues for build failures. Other failures can be spurious
136                     if new == 'build-fail':
137                         build_failed = True
138
139             if build_failed:
140                 try:
141                     issue(
142                         tool, MAINTAINERS.get(tool, ''),
143                         relevant_pr_number, relevant_pr_user, pr_reviewer,
144                     )
145                 except IOError as e:
146                     # network errors will simply end up not creating an issue, but that's better
147                     # than failing the entire build job
148                     print("I/O error: {0}".format(e))
149                 except:
150                     print("Unexpected error: {0}".format(sys.exc_info()[0]))
151                     raise
152
153             if changed:
154                 status['commit'] = current_commit
155                 status['datetime'] = current_datetime
156                 anything_changed = True
157
158         if not anything_changed:
159             return ''
160
161         f.seek(0)
162         f.truncate(0)
163         json.dump(latest, f, indent=4, separators=(',', ': '))
164         return message
165
166
167 if __name__ == '__main__':
168     cur_commit = sys.argv[1]
169     cur_datetime = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
170     cur_commit_msg = sys.argv[2]
171     save_message_to_path = sys.argv[3]
172     github_token = sys.argv[4]
173
174     # assume that PR authors are also owners of the repo where the branch lives
175     relevant_pr_match = re.search(
176         r'Auto merge of #([0-9]+) - ([^:]+):[^,]+, r=(\S+)',
177         cur_commit_msg,
178     )
179     if relevant_pr_match:
180         number = relevant_pr_match.group(1)
181         relevant_pr_user = relevant_pr_match.group(2)
182         relevant_pr_number = 'rust-lang/rust#' + number
183         relevant_pr_url = 'https://github.com/rust-lang/rust/pull/' + number
184         pr_reviewer = relevant_pr_match.group(3)
185     else:
186         number = '-1'
187         relevant_pr_user = 'ghost'
188         relevant_pr_number = '<unknown PR>'
189         relevant_pr_url = '<unknown>'
190         pr_reviewer = 'ghost'
191
192     message = update_latest(
193         cur_commit,
194         relevant_pr_number,
195         relevant_pr_url,
196         relevant_pr_user,
197         pr_reviewer,
198         cur_datetime
199     )
200     if not message:
201         print('<Nothing changed>')
202         sys.exit(0)
203
204     print(message)
205
206     if not github_token:
207         print('Dry run only, not committing anything')
208         sys.exit(0)
209
210     with open(save_message_to_path, 'w') as f:
211         f.write(message)
212
213     # Write the toolstate comment on the PR as well.
214     gh_url = 'https://api.github.com/repos/rust-lang/rust/issues/{}/comments' \
215         .format(number)
216     response = urllib2.urlopen(urllib2.Request(
217         gh_url,
218         json.dumps({'body': message}),
219         {
220             'Authorization': 'token ' + github_token,
221             'Content-Type': 'application/json',
222         }
223     ))
224     response.read()