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