8
8
import os
9
9
import time
10
10
import datetime
11
+ import json
11
12
from github import Github , GithubException
12
13
from github .GithubException import UnknownObjectException
13
14
from collections import defaultdict
14
15
from west .manifest import Manifest
15
16
from west .manifest import ManifestProject
17
+ from git import Repo
18
+ from pathlib import Path
16
19
17
20
TOP_DIR = os .path .join (os .path .dirname (__file__ ))
18
21
sys .path .insert (0 , os .path .join (TOP_DIR , "scripts" ))
19
22
from get_maintainer import Maintainers
20
23
24
+ zephyr_base = os .getenv ('ZEPHYR_BASE' , os .path .join (TOP_DIR , '..' ))
25
+
21
26
def log (s ):
22
27
if args .verbose > 0 :
23
28
print (s , file = sys .stdout )
@@ -50,11 +55,45 @@ def parse_args():
50
55
parser .add_argument ("-r" , "--repo" , default = "zephyr" ,
51
56
help = "Github repository" )
52
57
58
+ parser .add_argument ( "--updated-manifest" , default = None ,
59
+ help = "Updated manifest file to compare against current west.yml" )
60
+
53
61
parser .add_argument ("-v" , "--verbose" , action = "count" , default = 0 ,
54
62
help = "Verbose Output" )
55
63
56
64
args = parser .parse_args ()
57
65
66
+
67
+ def process_manifest (old_manifest_file ):
68
+ log ("Processing manifest changes" )
69
+ if not os .path .isfile ("west.yml" ) or not os .path .isfile (old_manifest_file ):
70
+ log ("No west.yml found, skipping..." )
71
+ return []
72
+ old_manifest = Manifest .from_file (old_manifest_file )
73
+ new_manifest = Manifest .from_file ("west.yml" )
74
+ old_projs = set ((p .name , p .revision ) for p in old_manifest .projects )
75
+ new_projs = set ((p .name , p .revision ) for p in new_manifest .projects )
76
+ # Removed projects
77
+ rprojs = set (filter (lambda p : p [0 ] not in list (p [0 ] for p in new_projs ),
78
+ old_projs - new_projs ))
79
+ # Updated projects
80
+ uprojs = set (filter (lambda p : p [0 ] in list (p [0 ] for p in old_projs ),
81
+ new_projs - old_projs ))
82
+ # Added projects
83
+ aprojs = new_projs - old_projs - uprojs
84
+
85
+ # All projs
86
+ projs = rprojs | uprojs | aprojs
87
+ projs_names = [name for name , rev in projs ]
88
+
89
+ log (f"found modified projects: { projs_names } " )
90
+ areas = []
91
+ for p in projs_names :
92
+ areas .append (f'West project: { p } ' )
93
+
94
+ log (f'manifest areas: { areas } ' )
95
+ return areas
96
+
58
97
def process_pr (gh , maintainer_file , number ):
59
98
60
99
gh_repo = gh .get_repo (f"{ args .org } /{ args .repo } " )
@@ -67,35 +106,59 @@ def process_pr(gh, maintainer_file, number):
67
106
found_maintainers = defaultdict (int )
68
107
69
108
num_files = 0
70
- all_areas = set ()
71
109
fn = list (pr .get_files ())
72
110
73
- for changed_file in fn :
74
- if changed_file .filename in ['west.yml' ,'submanifests/optional.yaml' ]:
75
- break
76
-
77
111
if pr .commits == 1 and (pr .additions <= 1 and pr .deletions <= 1 ):
78
112
labels = {'size: XS' }
79
113
80
114
if len (fn ) > 500 :
81
115
log (f"Too many files changed ({ len (fn )} ), skipping...." )
82
116
return
83
117
118
+ # areas where assignment happens if only area is affected
119
+ meta_areas = [
120
+ 'Release Notes' ,
121
+ 'Documentation' ,
122
+ 'Samples'
123
+ ]
124
+
84
125
for changed_file in fn :
126
+
85
127
num_files += 1
86
128
log (f"file: { changed_file .filename } " )
87
- areas = maintainer_file .path2areas (changed_file .filename )
129
+
130
+ areas = []
131
+ if changed_file .filename in ['west.yml' ,'submanifests/optional.yaml' ]:
132
+ if not args .updated_manifest :
133
+ log ("No updated manifest file provided, cannot process west.yml changes, skipping..." )
134
+ continue
135
+ parsed_areas = process_manifest (old_manifest_file = args .updated_manifest )
136
+ for _area in parsed_areas :
137
+ area_match = maintainer_file .name2areas (_area )
138
+ if area_match :
139
+ areas .extend (area_match )
140
+ else :
141
+ areas = maintainer_file .path2areas (changed_file .filename )
142
+
143
+ print (f"areas for { changed_file } : { areas } " )
88
144
89
145
if not areas :
90
146
continue
91
147
92
- all_areas . update ( areas )
148
+ # instance of an area, for example a driver or a board, not APIs or subsys code.
93
149
is_instance = False
94
150
sorted_areas = sorted (areas , key = lambda x : 'Platform' in x .name , reverse = True )
95
151
for area in sorted_areas :
96
- c = 1 if not is_instance else 0
152
+ # do not count cmake file changes, i.e. when there are changes to
153
+ # instances of an area listed in both the subsystem and the
154
+ # platform implementing it
155
+ if 'CMakeLists.txt' in changed_file .filename or area .name in meta_areas :
156
+ c = 0
157
+ else :
158
+ c = 1 if not is_instance else 0
97
159
98
160
area_counter [area ] += c
161
+ print (f"area counter: { area_counter } " )
99
162
labels .update (area .labels )
100
163
# FIXME: Here we count the same file multiple times if it exists in
101
164
# multiple areas with same maintainer
@@ -122,22 +185,26 @@ def process_pr(gh, maintainer_file, number):
122
185
log (f"Submitted by: { pr .user .login } " )
123
186
log (f"candidate maintainers: { _all_maintainers } " )
124
187
125
- assignees = []
126
- tmp_assignees = []
188
+ ranked_assignees = []
189
+ assignees = None
127
190
128
191
# we start with areas with most files changed and pick the maintainer from the first one.
129
192
# if the first area is an implementation, i.e. driver or platform, we
130
193
# continue searching for any other areas involved
131
194
for area , count in area_counter .items ():
132
- if count == 0 :
195
+ # if only meta area is affected, assign one of the maintainers of that area
196
+ if area .name in meta_areas and len (area_counter ) == 1 :
197
+ assignees = area .maintainers
198
+ break
199
+ # if no maintainers, skip
200
+ if count == 0 or len (area .maintainers ) == 0 :
133
201
continue
202
+ # if there are maintainers, but no assignees yet, set them
134
203
if len (area .maintainers ) > 0 :
135
- tmp_assignees = area .maintainers
136
204
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
205
+ # If submitter = assignee, try to pick next area and assign
206
+ # someone else other than the submitter, otherwise when there
207
+ # are other maintainers for the area, assign them.
141
208
if len (area .maintainers ) > 1 :
142
209
assignees = area .maintainers .copy ()
143
210
assignees .remove (pr .user .login )
@@ -146,16 +213,25 @@ def process_pr(gh, maintainer_file, number):
146
213
else :
147
214
assignees = area .maintainers
148
215
149
- if 'Platform' not in area .name :
150
- break
216
+ # found a non-platform area that was changed, pick assignee from this
217
+ # area and put them on top of the list, otherwise just append.
218
+ if 'Platform' not in area .name :
219
+ ranked_assignees .insert (0 , area .maintainers )
220
+ break
221
+ else :
222
+ ranked_assignees .append (area .maintainers )
151
223
152
- if tmp_assignees and not assignees :
153
- assignees = tmp_assignees
224
+ if ranked_assignees :
225
+ assignees = ranked_assignees [ 0 ]
154
226
155
227
if assignees :
156
228
prop = (found_maintainers [assignees [0 ]] / num_files ) * 100
157
229
log (f"Picked assignees: { assignees } ({ prop :.2f} % ownership)" )
158
230
log ("+++++++++++++++++++++++++" )
231
+ elif len (_all_maintainers ) > 0 :
232
+ # if we have maintainers found, but could not pick one based on area,
233
+ # then pick the one with most changes
234
+ assignees = [next (iter (_all_maintainers ))]
159
235
160
236
# Set labels
161
237
if labels :
@@ -206,21 +282,24 @@ def process_pr(gh, maintainer_file, number):
206
282
if len (existing_reviewers ) < 15 :
207
283
reviewer_vacancy = 15 - len (existing_reviewers )
208
284
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" )
217
285
else :
218
286
log ("not adding reviewers because the existing reviewer count is greater than or "
219
- "equal to 15" )
287
+ "equal to 15. Adding maintainers of all areas as reviewers instead." )
288
+ # FIXME: Here we could also add collaborators of the areas most
289
+ # affected, i.e. the one with the final assigne.
290
+ reviewers = list (_all_maintainers .keys ())
291
+
292
+ if reviewers :
293
+ try :
294
+ log (f"adding reviewers { reviewers } ..." )
295
+ if not args .dry_run :
296
+ pr .create_review_request (reviewers = reviewers )
297
+ except GithubException :
298
+ log ("can't add reviewer" )
220
299
221
300
ms = []
222
301
# assignees
223
- if assignees and not pr .assignee :
302
+ if assignees and ( not pr .assignee or args . dry_run ) :
224
303
try :
225
304
for assignee in assignees :
226
305
u = gh .get_user (assignee )
0 commit comments