77## It is set as callback for `src/ci/docker/x86_64-gnu-tools/repo.sh` by the CI scripts
88## when a new commit lands on `master` (i.e., after it passed all checks on `auto`).
99
10+ from __future__ import print_function
11+
1012import sys
1113import re
1214import os
2022 import urllib .request as urllib2
2123
2224# List of people to ping when the status of a tool or a book changed.
25+ # These should be collaborators of the rust-lang/rust repository (with at least
26+ # read privileges on it). CI will fail otherwise.
2327MAINTAINERS = {
24- 'miri' : '@oli-obk @RalfJung @eddyb' ,
25- 'clippy-driver' : '@Manishearth @llogiq @mcarton @oli-obk @phansch @flip1995 @yaahc' ,
26- 'rls' : '@Xanewok' ,
27- 'rustfmt' : '@topecongiro' ,
28- 'book' : '@carols10cents @steveklabnik' ,
29- 'nomicon' : '@frewsxcv @Gankro' ,
30- 'reference' : '@steveklabnik @Havvy @matthewjasper @ehuss' ,
31- 'rust-by-example' : '@steveklabnik @marioidival @projektir' ,
32- 'embedded-book' : (
33- '@adamgreig @andre-richter @jamesmunns @korken89 '
34- '@ryankurte @thejpster @therealprof'
35- ),
36- 'edition-guide' : '@ehuss @Centril @steveklabnik' ,
37- 'rustc-guide' : '@mark-i-m @spastorino @amanjeev'
28+ 'miri' : {'oli-obk' , 'RalfJung' , 'eddyb' },
29+ 'clippy-driver' : {
30+ 'Manishearth' , 'llogiq' , 'mcarton' , 'oli-obk' , 'phansch' , 'flip1995' ,
31+ 'yaahc' ,
32+ },
33+ 'rls' : {'Xanewok' },
34+ 'rustfmt' : {'topecongiro' },
35+ 'book' : {'carols10cents' , 'steveklabnik' },
36+ 'nomicon' : {'frewsxcv' , 'Gankra' },
37+ 'reference' : {'steveklabnik' , 'Havvy' , 'matthewjasper' , 'ehuss' },
38+ 'rust-by-example' : {'steveklabnik' , 'marioidival' },
39+ 'embedded-book' : {
40+ 'adamgreig' , 'andre-richter' , 'jamesmunns' , 'korken89' ,
41+ 'ryankurte' , 'thejpster' , 'therealprof' ,
42+ },
43+ 'edition-guide' : {'ehuss' , 'Centril' , 'steveklabnik' },
44+ 'rustc-guide' : {'mark-i-m' , 'spastorino' , 'amanjeev' },
3845}
3946
4047REPOS = {
5259}
5360
5461
62+ def validate_maintainers (repo , github_token ):
63+ '''Ensure all maintainers are assignable on a GitHub repo'''
64+ next_link_re = re .compile (r'<([^>]+)>; rel="next"' )
65+
66+ # Load the list of assignable people in the GitHub repo
67+ assignable = []
68+ url = 'https://api.github.com/repos/%s/collaborators?per_page=100' % repo
69+ while url is not None :
70+ response = urllib2 .urlopen (urllib2 .Request (url , headers = {
71+ 'Authorization' : 'token ' + github_token ,
72+ # Properly load nested teams.
73+ 'Accept' : 'application/vnd.github.hellcat-preview+json' ,
74+ }))
75+ assignable .extend (user ['login' ] for user in json .load (response ))
76+ # Load the next page if available
77+ url = None
78+ link_header = response .headers .get ('Link' )
79+ if link_header :
80+ matches = next_link_re .match (link_header )
81+ if matches is not None :
82+ url = matches .group (1 )
83+
84+ errors = False
85+ for tool , maintainers in MAINTAINERS .items ():
86+ for maintainer in maintainers :
87+ if maintainer not in assignable :
88+ errors = True
89+ print (
90+ "error: %s maintainer @%s is not assignable in the %s repo"
91+ % (tool , maintainer , repo ),
92+ )
93+
94+ if errors :
95+ print ()
96+ print (" To be assignable, a person needs to be explicitly listed as a" )
97+ print (" collaborator in the repository settings. The simple way to" )
98+ print (" fix this is to ask someone with 'admin' privileges on the repo" )
99+ print (" to add the person or whole team as a collaborator with 'read'" )
100+ print (" privileges. Those privileges don't grant any extra permissions" )
101+ print (" so it's safe to apply them." )
102+ print ()
103+ print ("The build will fail due to this." )
104+ exit (1 )
105+
55106def read_current_status (current_commit , path ):
56107 '''Reads build status of `current_commit` from content of `history/*.tsv`
57108 '''
@@ -73,13 +124,12 @@ def maybe_delink(message):
73124def issue (
74125 tool ,
75126 status ,
76- maintainers ,
127+ assignees ,
77128 relevant_pr_number ,
78129 relevant_pr_user ,
79130 pr_reviewer ,
80131):
81132 # Open an issue about the toolstate failure.
82- assignees = [x .strip () for x in maintainers .split ('@' ) if x != '' ]
83133 if status == 'test-fail' :
84134 status_description = 'has failing tests'
85135 else :
@@ -100,7 +150,7 @@ def issue(
100150 REPOS .get (tool ), relevant_pr_user , pr_reviewer
101151 )),
102152 'title' : '`{}` no longer builds after {}' .format (tool , relevant_pr_number ),
103- 'assignees' : assignees ,
153+ 'assignees' : list ( assignees ) ,
104154 'labels' : ['T-compiler' , 'I-nominated' ],
105155 })
106156 print ("Creating issue:\n {}" .format (request ))
@@ -150,18 +200,19 @@ def update_latest(
150200 old = status [os ]
151201 new = s .get (tool , old )
152202 status [os ] = new
203+ maintainers = ' ' .join ('@' + name for name in MAINTAINERS [tool ])
153204 if new > old : # comparing the strings, but they are ordered appropriately!
154205 # things got fixed or at least the status quo improved
155206 changed = True
156207 message += '🎉 {} on {}: {} → {} (cc {}, @rust-lang/infra).\n ' \
157- .format (tool , os , old , new , MAINTAINERS . get ( tool ) )
208+ .format (tool , os , old , new , maintainers )
158209 elif new < old :
159210 # tests or builds are failing and were not failing before
160211 changed = True
161212 title = '💔 {} on {}: {} → {}' \
162213 .format (tool , os , old , new )
163214 message += '{} (cc {}, @rust-lang/infra).\n ' \
164- .format (title , MAINTAINERS . get ( tool ) )
215+ .format (title , maintainers )
165216 # Most tools only create issues for build failures.
166217 # Other failures can be spurious.
167218 if new == 'build-fail' or (tool == 'miri' and new == 'test-fail' ):
@@ -200,6 +251,16 @@ def update_latest(
200251
201252
202253if __name__ == '__main__' :
254+ repo = os .environ .get ('TOOLSTATE_VALIDATE_MAINTAINERS_REPO' )
255+ if repo :
256+ github_token = os .environ .get ('TOOLSTATE_REPO_ACCESS_TOKEN' )
257+ if github_token :
258+ validate_maintainers (repo , github_token )
259+ else :
260+ print ('skipping toolstate maintainers validation since no GitHub token is present' )
261+ # When validating maintainers don't run the full script.
262+ exit (0 )
263+
203264 cur_commit = sys .argv [1 ]
204265 cur_datetime = datetime .datetime .utcnow ().strftime ('%Y-%m-%dT%H:%M:%SZ' )
205266 cur_commit_msg = sys .argv [2 ]
0 commit comments