|
OLD | NEW |
---|---|
(Empty) | |
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 import re | |
6 | |
7 import model.app_config | |
8 import util | |
9 | |
10 | |
11 POLICY_TEMPLATES_FILE = 'chrome/app/policy/policy_templates.json' | |
12 ID_LINE_COMMENT = 'For your editing convenience: highest ID currently used:' | |
13 CONTEXT_THRESHOLD = 12 | |
iannucci
2013/08/15 03:48:05
Should we make a separate config file specifically
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
I'd expect that if we need changes, not only the c
| |
14 PROPERTY_NAME_RE = re.compile('\'(\\w+)\'\\s*:') | |
iannucci
2013/08/15 03:48:05
may be worth using a raw string to reduce the back
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Good idea, done.
| |
15 MAX_LINE = 1000000 | |
16 | |
17 REVIEW_MESSAGE = ''' | |
18 Thanks for improving Chromium enterprise features! In general, | |
19 enterprise policies should work across all platforms and need to remain | |
20 backward compatible across several Chrome versions. To help avoid common | |
21 pitfalls, policy review bot has analyzed your change and added comments | |
22 with checklists of things to watch out for. | |
23 | |
24 Please direct comments and complaints about this service to | |
25 chrome-enterprise@google.com and/or mnissler@chromium.org. | |
26 ''' | |
27 | |
28 ADDITION_COMMENT = ''' | |
29 When adding a new policy setting, please consider the following: | |
30 | |
31 - Ideally, your new policy should be expressing the intention of an | |
32 administrator and/or cover a well-scoped user-visible behavior of the | |
33 browser as opposed to an implementation-specific parameter. Consider | |
34 that your implementation may change in the future, and you might have | |
35 to re-implement your policy on top of the new implementation. | |
iannucci
2013/08/15 03:48:05
I'm assuming these strings have all been vetted by
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Yes, I've discussed them in a doc with the chrome/
| |
36 - Make sure your policy definition has proper supported_on declarations | |
37 specifying the platform and versions this policy is supported on. | |
38 - Make sure feature flags are correct. In particular: | |
39 - dynamic_refresh - whether your feature can adjust to changes in | |
40 policy at run time. You typically use PrefChangeRegistrar to do so. | |
41 - per_profile - whether your policy is browser-global or can be set | |
42 independently for each Chrome Profile. This is usually true if you | |
43 read the policy value from the Profile's PrefService and false if | |
44 you read it from the local_state PrefService. | |
45 - can_be_recommended - whether your feature supports admin-supplied | |
46 default values that the user can override. | |
47 - Make sure you put a helpful policy description: | |
48 - Describe the effect of setting the policy in a way that makes sense | |
49 to somebody who is not intimately familiar with your feature, such | |
50 as administrators and other Chromium developers. | |
51 - Do mention behavior for the cases where the policy gets ignored | |
52 (i.e. not configured, booleans only one value is effective). It's | |
53 nice for completeness and admins have been asking specifically for | |
54 this piece of information in the past. | |
55 - Write a browser_test for you new policy. The ideal test would fire up | |
56 the browser with the policy set and check whether the policy affects | |
57 user-visible behavior in the intended way. See | |
58 chrome/browser/policy/policy_browsertest.cc for examples. | |
59 - If you're adding a device policy for Chrome OS, be sure to update | |
60 chrome/browser/chromeos/policy/device_policy_decoder_chromeos.{h,cc} | |
61 so the policy shows up on the chrome://policy page. | |
iannucci
2013/08/15 03:48:05
It may make sense to actually turn these strings i
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Good point. Done.
| |
62 ''' | |
63 | |
64 MODIFICATION_COMMENT = ''' | |
65 When modifying an existing policy setting, please consider the following: | |
66 | |
67 - Make sure the policy meta data is up-to-date, in particular | |
68 supported_on, and the feature flags. | |
69 - In general, don't change policy semantics in a way that is | |
70 incompatible (as determined by user/admin-visible behavior) with | |
71 previous semantics. In particular, consider that existing policy | |
72 deployments may affect both old and new browser versions, and both | |
73 should behave according to the admin's intentions. | |
74 - It is OK to expand semantics of policy values as long as the previous | |
75 policy description is compatible with the new behavior. | |
76 - It is OK to update feature implementations and the policy description | |
77 when Chrome changes as long as the intended effect of the policy | |
78 remains intact. | |
79 - The process for removing policies is to deprecate them first, wait a | |
80 few releases (if possible) and then remove them. Make sure you put the | |
81 deprecated flag if you deprecate a policy. | |
82 ''' | |
83 | |
84 | |
85 def nmin(*args): | |
86 """Calculates the minimum of |args|, ignoring None entries.""" | |
87 try: | |
88 return min(v for v in args if v is not None) | |
89 except ValueError: | |
iannucci
2013/08/15 03:48:05
This seems sketchy. Why ValueError?
Also, I think
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
min raises ValueError if the sequence is empty. I'
| |
90 return None | |
91 | |
92 | |
93 def nmax(*args): | |
94 """Calculates the maximum of |args|, ignoring None entries.""" | |
95 try: | |
96 return max(v for v in args if v is not None) | |
97 except ValueError: | |
98 return None | |
99 | |
100 | |
101 def nsub(a, b): | |
102 """Calculates a - b, returning None if either a or b is None""" | |
103 return None if (a is None or b is None) else a - b | |
104 | |
105 | |
106 def indent(line): | |
107 """Returns the indent level (number of leading spaces) for |line|.""" | |
108 nspaces = len(line) - len(line.lstrip(' ')) | |
109 return None if nspaces == 0 else nspaces | |
iannucci
2013/08/15 03:48:05
You may want to take a look at the textwrap module
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
This actually determines the indent from a given l
| |
110 | |
111 | |
112 class PolicyChangeParser(object): | |
iannucci
2013/08/15 03:48:05
Needs docs, code is hard to read without context/e
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
I've added documentation and some illustrative uni
| |
113 def __init__(self, lines): | |
114 self.lines = lines | |
115 self.chunks_list = [] | |
116 self.reset() | |
117 | |
118 def run(self): | |
119 self.chunks_list = [] | |
120 self.last_change = [None, None] | |
121 cursor = [None, None] | |
122 self.reset() | |
123 for (a_line, b_line, line) in self.lines: | |
124 # Skip comment lines. | |
125 if line.startswith('#'): | |
126 continue | |
127 | |
128 # See whether the current line has a JSON property. | |
129 keyword = None | |
130 match = PROPERTY_NAME_RE.search(line) | |
131 if match: | |
132 keyword = match.group(1).lower() | |
133 | |
134 | |
135 # Check whether the current block closes. | |
136 line_indent = indent(line) | |
137 if (self.block_indent is not None and | |
138 line_indent is not None and | |
139 line_indent < self.block_indent): | |
140 self.block_closed = True | |
141 | |
142 if (keyword == 'name' and self.block_closed): | |
143 # If we see the 'name' property, that likely indicates the start of a | |
144 # new policy. Start a new chunk. | |
145 self.flush_chunk() | |
146 | |
147 # Update various cursors. | |
148 cursor = [nmax(a_line, cursor[0]), nmax(b_line, cursor[1])] | |
149 offset = nmin(nsub(cursor[0], self.last_change[0]), | |
150 nsub(cursor[1], self.last_change[1])) | |
151 | |
152 if a_line > 0 and b_line == 0: | |
153 self.removals = True | |
154 self.last_change[0] = a_line | |
155 elif a_line == 0 and b_line > 0: | |
156 self.additions = True | |
157 self.last_change[1] = b_line | |
158 | |
159 if (offset is not None and | |
160 (offset > CONTEXT_THRESHOLD or | |
161 (offset > 1 and self.block_closed))): | |
162 # If the last chunk is too far away, assume a new one starts. | |
163 self.flush_chunk() | |
164 | |
165 # Try to figure out block indent from properties exclusively used for | |
166 # policy definitions. | |
167 if keyword in ('id', 'schema', 'future', 'items', 'features', | |
168 'supported_on', 'example_value', 'deprecated'): | |
169 self.block_indent = line_indent | |
170 | |
171 # Prefer the comment on the policy name property if we see it fly by. | |
172 if keyword == 'name': | |
173 # Attempt to filter out name labels on enum items. | |
174 if self.block_indent is not None and self.block_indent != line_indent: | |
175 pass | |
176 elif a_line > 0 and b_line == 0: | |
177 self.comment_pos[0] = a_line | |
178 elif a_line == 0 and b_line > 0: | |
179 self.comment_pos[1] = b_line | |
180 | |
181 self.chunk_start = [nmin(self.last_change[0], self.chunk_start[0]), | |
182 nmin(self.last_change[1], self.chunk_start[1])] | |
183 | |
184 # Flush the last chunk. | |
185 if self.chunk_start != [None, None]: | |
186 self.flush_chunk() | |
187 | |
188 @property | |
189 def chunks(self): | |
190 return self.chunks_list | |
191 | |
192 def flush_chunk(self): | |
193 self.comment_pos = [nmax(self.chunk_start[0], self.comment_pos[0]), | |
194 nmax(self.chunk_start[1], self.comment_pos[1])] | |
195 self.chunks_list.append( | |
196 util.ObjectDict( | |
197 { 'start': self.chunk_start, | |
198 'end': self.last_change, | |
199 'comment_pos': self.comment_pos, | |
200 'additions': self.additions, | |
201 'removals': self.removals })) | |
202 self.reset() | |
203 | |
204 def reset(self): | |
205 # This is called from __init__. | |
206 # pylint: disable=W0201 | |
207 self.chunk_start = [None, None] | |
208 self.last_change = [None, None] | |
209 self.comment_pos = [None, None] | |
210 self.block_indent = None | |
211 self.block_closed = False | |
212 self.additions = False | |
213 self.removals = False | |
214 | |
215 | |
216 def process(addr, message, review, rietveld): | |
217 """Handles reviews for chrome/app/policy/policy_templates.json. | |
218 | |
219 This looks at the patch to identify additions/modifications to policy | |
220 definitions and posts comments with a checklist intended for the author and | |
221 reviewer to go through in order to catch common mistakes. | |
222 """ | |
223 | |
224 if POLICY_TEMPLATES_FILE not in review.latest_patchset.files: | |
225 return | |
226 | |
227 # Only process the change if the mail is directly to us or we haven't | |
228 # processed this review yet. | |
229 client_id = model.app_config.get().client_id | |
230 if (not addr in util.get_emails(getattr(message, 'to', '')) and | |
231 client_id in [m.sender for m in review.issue_data.messages]): | |
232 return | |
iannucci
2013/08/15 03:48:05
So the idea here is that you could add reviewbot a
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Correct. Once we've done so a couple times and con
| |
233 | |
234 # Don't process reverts. | |
235 if 'revert' in review.issue_data.description.lower(): | |
236 return | |
237 | |
238 # Parse the patch, look at the chunks and generate inline comments. | |
239 parser = PolicyChangeParser( | |
iannucci
2013/08/15 03:48:05
why not just make PolicyChangeParser a function re
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
It's got all this state to carry around, which is
| |
240 review.latest_patchset.files[POLICY_TEMPLATES_FILE].patch.lines) | |
241 parser.run() | |
242 for chunk in parser.chunks: | |
243 if chunk.additions and not chunk.removals: | |
244 message = ADDITION_COMMENT | |
245 else: | |
246 message = MODIFICATION_COMMENT | |
247 | |
248 if chunk.comment_pos[1] is not None: | |
249 line, side = chunk.comment_pos[1], 'b' | |
250 elif chunk.comment_pos[0] is not None: | |
251 line, side = chunk.comment_pos[0], 'a' | |
252 else: | |
253 # No suitable position? | |
254 continue | |
255 | |
256 rietveld.add_inline_comment( | |
257 review.issue_id, review.latest_patchset.patchset, | |
258 review.latest_patchset.files[POLICY_TEMPLATES_FILE].id, | |
259 line, side, message) | |
iannucci
2013/08/15 03:48:05
Maybe I'm unfamiliar with the types of changes her
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
I've tried it on GIT history and it usually posted
| |
260 | |
261 # Finally, post all inline comments. | |
262 if len(parser.chunks) > 0: | |
263 rietveld.post_comment(review.issue_id, REVIEW_MESSAGE, True) | |
OLD | NEW |