88import os
99import time
1010import datetime
11+ import json
1112from github import Github , GithubException
1213from github .GithubException import UnknownObjectException
1314from collections import defaultdict
1819sys .path .insert (0 , os .path .join (TOP_DIR , "scripts" ))
1920from get_maintainer import Maintainers
2021
22+ zephyr_base = os .getenv ('ZEPHYR_BASE' , os .path .join (TOP_DIR , '..' ))
23+
2124def log (s ):
2225 if args .verbose > 0 :
2326 print (s , file = sys .stdout )
@@ -50,11 +53,45 @@ def parse_args():
5053 parser .add_argument ("-r" , "--repo" , default = "zephyr" ,
5154 help = "Github repository" )
5255
56+ parser .add_argument ( "--updated-manifest" , default = None ,
57+ help = "Updated manifest file to compare against current west.yml" )
58+
5359 parser .add_argument ("-v" , "--verbose" , action = "count" , default = 0 ,
5460 help = "Verbose Output" )
5561
5662 args = parser .parse_args ()
5763
64+
65+ def process_manifest (old_manifest_file ):
66+ log ("Processing manifest changes" )
67+ if not os .path .isfile ("west.yml" ) or not os .path .isfile (old_manifest_file ):
68+ log ("No west.yml found, skipping..." )
69+ return []
70+ old_manifest = Manifest .from_file (old_manifest_file )
71+ new_manifest = Manifest .from_file ("west.yml" )
72+ old_projs = set ((p .name , p .revision ) for p in old_manifest .projects )
73+ new_projs = set ((p .name , p .revision ) for p in new_manifest .projects )
74+ # Removed projects
75+ rprojs = set (filter (lambda p : p [0 ] not in list (p [0 ] for p in new_projs ),
76+ old_projs - new_projs ))
77+ # Updated projects
78+ uprojs = set (filter (lambda p : p [0 ] in list (p [0 ] for p in old_projs ),
79+ new_projs - old_projs ))
80+ # Added projects
81+ aprojs = new_projs - old_projs - uprojs
82+
83+ # All projs
84+ projs = rprojs | uprojs | aprojs
85+ projs_names = [name for name , rev in projs ]
86+
87+ log (f"found modified projects: { projs_names } " )
88+ areas = []
89+ for p in projs_names :
90+ areas .append (f'West project: { p } ' )
91+
92+ log (f'manifest areas: { areas } ' )
93+ return areas
94+
5895def process_pr (gh , maintainer_file , number ):
5996
6097 gh_repo = gh .get_repo (f"{ args .org } /{ args .repo } " )
@@ -67,35 +104,59 @@ def process_pr(gh, maintainer_file, number):
67104 found_maintainers = defaultdict (int )
68105
69106 num_files = 0
70- all_areas = set ()
71107 fn = list (pr .get_files ())
72108
73- for changed_file in fn :
74- if changed_file .filename in ['west.yml' ,'submanifests/optional.yaml' ]:
75- break
76-
77109 if pr .commits == 1 and (pr .additions <= 1 and pr .deletions <= 1 ):
78110 labels = {'size: XS' }
79111
80112 if len (fn ) > 500 :
81113 log (f"Too many files changed ({ len (fn )} ), skipping...." )
82114 return
83115
116+ # areas where assignment happens if only area is affected
117+ meta_areas = [
118+ 'Release Notes' ,
119+ 'Documentation' ,
120+ 'Samples'
121+ ]
122+
84123 for changed_file in fn :
124+
85125 num_files += 1
86126 log (f"file: { changed_file .filename } " )
87- areas = maintainer_file .path2areas (changed_file .filename )
127+
128+ areas = []
129+ if changed_file .filename in ['west.yml' ,'submanifests/optional.yaml' ]:
130+ if not args .updated_manifest :
131+ log ("No updated manifest file provided, cannot process west.yml changes, skipping..." )
132+ continue
133+ parsed_areas = process_manifest (old_manifest_file = args .updated_manifest )
134+ for _area in parsed_areas :
135+ area_match = maintainer_file .name2areas (_area )
136+ if area_match :
137+ areas .extend (area_match )
138+ else :
139+ areas = maintainer_file .path2areas (changed_file .filename )
140+
141+ print (f"areas for { changed_file } : { areas } " )
88142
89143 if not areas :
90144 continue
91145
92- all_areas . update ( areas )
146+ # instance of an area, for example a driver or a board, not APIs or subsys code.
93147 is_instance = False
94148 sorted_areas = sorted (areas , key = lambda x : 'Platform' in x .name , reverse = True )
95149 for area in sorted_areas :
96- c = 1 if not is_instance else 0
150+ # do not count cmake file changes, i.e. when there are changes to
151+ # instances of an area listed in both the subsystem and the
152+ # platform implementing it
153+ if 'CMakeLists.txt' in changed_file .filename or area .name in meta_areas :
154+ c = 0
155+ else :
156+ c = 1 if not is_instance else 0
97157
98158 area_counter [area ] += c
159+ print (f"area counter: { area_counter } " )
99160 labels .update (area .labels )
100161 # FIXME: Here we count the same file multiple times if it exists in
101162 # multiple areas with same maintainer
@@ -122,22 +183,26 @@ def process_pr(gh, maintainer_file, number):
122183 log (f"Submitted by: { pr .user .login } " )
123184 log (f"candidate maintainers: { _all_maintainers } " )
124185
125- assignees = []
126- tmp_assignees = []
186+ ranked_assignees = []
187+ assignees = None
127188
128189 # we start with areas with most files changed and pick the maintainer from the first one.
129190 # if the first area is an implementation, i.e. driver or platform, we
130191 # continue searching for any other areas involved
131192 for area , count in area_counter .items ():
132- if count == 0 :
193+ # if only meta area is affected, assign one of the maintainers of that area
194+ if area .name in meta_areas and len (area_counter ) == 1 :
195+ assignees = area .maintainers
196+ break
197+ # if no maintainers, skip
198+ if count == 0 or len (area .maintainers ) == 0 :
133199 continue
200+ # if there are maintainers, but no assignees yet, set them
134201 if len (area .maintainers ) > 0 :
135- tmp_assignees = area .maintainers
136202 if pr .user .login in area .maintainers :
137- # submitter = assignee, try to pick next area and
138- # assign someone else other than the submitter
139- # when there also other maintainers for the area
140- # assign them
203+ # If submitter = assignee, try to pick next area and assign
204+ # someone else other than the submitter, otherwise when there
205+ # are other maintainers for the area, assign them.
141206 if len (area .maintainers ) > 1 :
142207 assignees = area .maintainers .copy ()
143208 assignees .remove (pr .user .login )
@@ -146,16 +211,25 @@ def process_pr(gh, maintainer_file, number):
146211 else :
147212 assignees = area .maintainers
148213
149- if 'Platform' not in area .name :
150- break
214+ # found a non-platform area that was changed, pick assignee from this
215+ # area and put them on top of the list, otherwise just append.
216+ if 'Platform' not in area .name :
217+ ranked_assignees .insert (0 , area .maintainers )
218+ break
219+ else :
220+ ranked_assignees .append (area .maintainers )
151221
152- if tmp_assignees and not assignees :
153- assignees = tmp_assignees
222+ if ranked_assignees :
223+ assignees = ranked_assignees [ 0 ]
154224
155225 if assignees :
156226 prop = (found_maintainers [assignees [0 ]] / num_files ) * 100
157227 log (f"Picked assignees: { assignees } ({ prop :.2f} % ownership)" )
158228 log ("+++++++++++++++++++++++++" )
229+ elif len (_all_maintainers ) > 0 :
230+ # if we have maintainers found, but could not pick one based on area,
231+ # then pick the one with most changes
232+ assignees = [next (iter (_all_maintainers ))]
159233
160234 # Set labels
161235 if labels :
@@ -206,21 +280,24 @@ def process_pr(gh, maintainer_file, number):
206280 if len (existing_reviewers ) < 15 :
207281 reviewer_vacancy = 15 - len (existing_reviewers )
208282 reviewers = reviewers [:reviewer_vacancy ]
209-
210- if reviewers :
211- try :
212- log (f"adding reviewers { reviewers } ..." )
213- if not args .dry_run :
214- pr .create_review_request (reviewers = reviewers )
215- except GithubException :
216- log ("cant add reviewer" )
217283 else :
218284 log ("not adding reviewers because the existing reviewer count is greater than or "
219- "equal to 15" )
285+ "equal to 15. Adding maintainers of all areas as reviewers instead." )
286+ # FIXME: Here we could also add collaborators of the areas most
287+ # affected, i.e. the one with the final assigne.
288+ reviewers = list (_all_maintainers .keys ())
289+
290+ if reviewers :
291+ try :
292+ log (f"adding reviewers { reviewers } ..." )
293+ if not args .dry_run :
294+ pr .create_review_request (reviewers = reviewers )
295+ except GithubException :
296+ log ("can't add reviewer" )
220297
221298 ms = []
222299 # assignees
223- if assignees and not pr .assignee :
300+ if assignees and ( not pr .assignee or args . dry_run ) :
224301 try :
225302 for assignee in assignees :
226303 u = gh .get_user (assignee )
0 commit comments