blob: 3825a0271d3adafed992478d22133aba938d3928 [file] [log] [blame]
Hector Dearman534765e2017-11-01 11:17:38 +00001# Copyright (C) 2017 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Hector Dearman2cc71f62021-07-14 10:28:58 +010015from __future__ import print_function
Florian Mayer640b7ae2018-05-09 15:28:32 +010016import itertools
Sami Kyostilab27619f2017-12-13 19:22:16 +000017import subprocess
Hector Dearman2cc71f62021-07-14 10:28:58 +010018import time
19
Lalit Maganti86a053e2022-11-09 23:35:20 +000020USE_PYTHON3 = True
21
22
Hector Dearman2cc71f62021-07-14 10:28:58 +010023def RunAndReportIfLong(func, *args, **kargs):
24 start = time.time()
25 results = func(*args, **kargs)
26 end = time.time()
27 limit = 0.5 # seconds
28 name = func.__name__
29 runtime = end - start
30 if runtime > limit:
31 print("{} took >{:.2}s ({:.2}s)".format(name, limit, runtime))
32 return results
Sami Kyostilab27619f2017-12-13 19:22:16 +000033
Primiano Tuccibf3b19c2019-06-01 08:40:26 +010034
Hector Dearman534765e2017-11-01 11:17:38 +000035def CheckChange(input, output):
Primiano Tuccibf3b19c2019-06-01 08:40:26 +010036 # There apparently is no way to wrap strings in blueprints, so ignore long
37 # lines in them.
Primiano Tucci834fdc72019-10-04 11:33:44 +010038 def long_line_sources(x):
39 return input.FilterSourceFile(
40 x,
Ryan Savitski135038e2020-11-12 20:57:57 +000041 files_to_check='.*',
42 files_to_skip=[
Lalit Maganti3dc0ffe2021-06-09 13:27:52 +010043 'Android[.]bp',
Lalit Magantibf99dd32023-02-07 23:50:48 +000044 "buildtools/grpc/BUILD.gn",
Lalit Maganti3dc0ffe2021-06-09 13:27:52 +010045 '.*[.]json$',
46 '.*[.]sql$',
47 '.*[.]out$',
Anna Mayzner6988df82023-01-23 10:37:16 +000048 'test/trace_processor/.*/tests.*$',
Lalit Maganti3dc0ffe2021-06-09 13:27:52 +010049 '(.*/)?BUILD$',
50 'WORKSPACE',
51 '.*/Makefile$',
52 '/perfetto_build_flags.h$',
53 "infra/luci/.*",
Primiano Tucci834fdc72019-10-04 11:33:44 +010054 ])
55
Primiano Tuccibf3b19c2019-06-01 08:40:26 +010056 results = []
Hector Dearman2cc71f62021-07-14 10:28:58 +010057 results += RunAndReportIfLong(input.canned_checks.CheckDoNotSubmit, input,
58 output)
59 results += RunAndReportIfLong(input.canned_checks.CheckChangeHasNoTabs, input,
60 output)
61 results += RunAndReportIfLong(
62 input.canned_checks.CheckLongLines,
63 input,
64 output,
65 80,
66 source_file_filter=long_line_sources)
67 results += RunAndReportIfLong(
68 input.canned_checks.CheckPatchFormatted, input, output, check_js=True)
69 results += RunAndReportIfLong(input.canned_checks.CheckGNFormatted, input,
70 output)
71 results += RunAndReportIfLong(CheckIncludeGuards, input, output)
72 results += RunAndReportIfLong(CheckIncludeViolations, input, output)
73 results += RunAndReportIfLong(CheckProtoComments, input, output)
74 results += RunAndReportIfLong(CheckBuild, input, output)
75 results += RunAndReportIfLong(CheckAndroidBlueprint, input, output)
76 results += RunAndReportIfLong(CheckBinaryDescriptors, input, output)
77 results += RunAndReportIfLong(CheckMergedTraceConfigProto, input, output)
78 results += RunAndReportIfLong(CheckProtoEventList, input, output)
79 results += RunAndReportIfLong(CheckBannedCpp, input, output)
Primiano Tucci0fd92a92023-07-17 11:47:38 -070080 results += RunAndReportIfLong(CheckBadCppPatterns, input, output)
Anna Mayzner412e0562022-11-25 09:55:51 +000081 results += RunAndReportIfLong(CheckSqlModules, input, output)
Hector Dearman2cc71f62021-07-14 10:28:58 +010082 results += RunAndReportIfLong(CheckSqlMetrics, input, output)
Primiano Tucci3d4217d2021-11-05 11:11:51 +000083 results += RunAndReportIfLong(CheckTestData, input, output)
Primiano Tucci11d94e12022-08-02 17:44:33 +010084 results += RunAndReportIfLong(CheckAmalgamatedPythonTools, input, output)
Primiano Tuccibf3b19c2019-06-01 08:40:26 +010085 return results
Hector Dearman534765e2017-11-01 11:17:38 +000086
Sami Kyostilab27619f2017-12-13 19:22:16 +000087
Hector Dearman534765e2017-11-01 11:17:38 +000088def CheckChangeOnUpload(input_api, output_api):
Primiano Tuccibf3b19c2019-06-01 08:40:26 +010089 return CheckChange(input_api, output_api)
Hector Dearman534765e2017-11-01 11:17:38 +000090
Sami Kyostilab27619f2017-12-13 19:22:16 +000091
Hector Dearman534765e2017-11-01 11:17:38 +000092def CheckChangeOnCommit(input_api, output_api):
Primiano Tuccibf3b19c2019-06-01 08:40:26 +010093 return CheckChange(input_api, output_api)
Hector Dearman534765e2017-11-01 11:17:38 +000094
Sami Kyostilab27619f2017-12-13 19:22:16 +000095
Lalit Maganti279ecde2019-04-01 16:57:12 +010096def CheckBuild(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -070097 # The script invocation doesn't work on Windows.
98 if input_api.is_windows:
99 return []
100
Primiano Tucci9c411652019-08-27 07:13:59 +0200101 tool = 'tools/gen_bazel'
Primiano Tucci834fdc72019-10-04 11:33:44 +0100102
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100103 # If no GN files were modified, bail out.
Primiano Tucci834fdc72019-10-04 11:33:44 +0100104 def build_file_filter(x):
105 return input_api.FilterSourceFile(
Ryan Savitski135038e2020-11-12 20:57:57 +0000106 x, files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', 'BUILD\.extras', tool))
Primiano Tucci834fdc72019-10-04 11:33:44 +0100107
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100108 if not input_api.AffectedSourceFiles(build_file_filter):
Lalit Maganti279ecde2019-04-01 16:57:12 +0100109 return []
Primiano Tucci9c411652019-08-27 07:13:59 +0200110 if subprocess.call([tool, '--check-only']):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100111 return [
112 output_api.PresubmitError('Bazel BUILD(s) are out of date. Run ' +
113 tool + ' to update them.')
114 ]
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100115 return []
116
Lalit Maganti279ecde2019-04-01 16:57:12 +0100117
Sami Kyostilab27619f2017-12-13 19:22:16 +0000118def CheckAndroidBlueprint(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700119 # The script invocation doesn't work on Windows.
120 if input_api.is_windows:
121 return []
122
Primiano Tucci9c411652019-08-27 07:13:59 +0200123 tool = 'tools/gen_android_bp'
Primiano Tucci834fdc72019-10-04 11:33:44 +0100124
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100125 # If no GN files were modified, bail out.
Primiano Tucci834fdc72019-10-04 11:33:44 +0100126 def build_file_filter(x):
127 return input_api.FilterSourceFile(
Rasika Navarange4c6a6a62023-11-12 13:39:54 +0000128 x,
129 files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', tool),
130 # Do not require Android.bp to be regenerated for chrome
131 # stdlib changes.
132 files_to_skip=(
133 'src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn'))
Primiano Tucci834fdc72019-10-04 11:33:44 +0100134
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100135 if not input_api.AffectedSourceFiles(build_file_filter):
Sami Kyostilab27619f2017-12-13 19:22:16 +0000136 return []
Primiano Tucci9c411652019-08-27 07:13:59 +0200137 if subprocess.call([tool, '--check-only']):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100138 return [
139 output_api.PresubmitError('Android build files are out of date. ' +
140 'Run ' + tool + ' to update them.')
141 ]
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100142 return []
143
Primiano Tuccic5010802018-01-19 17:13:21 +0000144
Primiano Tucci40e98722018-02-16 11:50:17 +0000145def CheckIncludeGuards(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700146 # The script invocation doesn't work on Windows.
147 if input_api.is_windows:
148 return []
149
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100150 tool = 'tools/fix_include_guards'
151
Primiano Tucci834fdc72019-10-04 11:33:44 +0100152 def file_filter(x):
153 return input_api.FilterSourceFile(
Ryan Savitski135038e2020-11-12 20:57:57 +0000154 x, files_to_check=['.*[.]cc$', '.*[.]h$', tool])
Primiano Tucci834fdc72019-10-04 11:33:44 +0100155
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100156 if not input_api.AffectedSourceFiles(file_filter):
Primiano Tucci40e98722018-02-16 11:50:17 +0000157 return []
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100158 if subprocess.call([tool, '--check-only']):
159 return [
Primiano Tucci834fdc72019-10-04 11:33:44 +0100160 output_api.PresubmitError('Please run ' + tool +
161 ' to fix include guards.')
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100162 ]
163 return []
164
165
Ryan704bc822020-03-31 02:31:13 +0100166def CheckBannedCpp(input_api, output_api):
Hector Dearman53d91b92019-12-13 17:07:52 +0000167 bad_cpp = [
Hector Dearman53d91b92019-12-13 17:07:52 +0000168 (r'\bstd::stoi\b',
169 'std::stoi throws exceptions prefer base::StringToInt32()'),
170 (r'\bstd::stol\b',
171 'std::stoull throws exceptions prefer base::StringToInt32()'),
172 (r'\bstd::stoul\b',
173 'std::stoull throws exceptions prefer base::StringToUint32()'),
174 (r'\bstd::stoll\b',
175 'std::stoull throws exceptions prefer base::StringToInt64()'),
176 (r'\bstd::stoull\b',
177 'std::stoull throws exceptions prefer base::StringToUint64()'),
178 (r'\bstd::stof\b',
179 'std::stof throws exceptions prefer base::StringToDouble()'),
180 (r'\bstd::stod\b',
181 'std::stod throws exceptions prefer base::StringToDouble()'),
182 (r'\bstd::stold\b',
183 'std::stold throws exceptions prefer base::StringToDouble()'),
Primiano Tucci78cd82b2021-10-13 13:50:27 +0100184 (r'\bstrncpy\b',
185 'strncpy does not null-terminate if src > dst. Use base::StringCopy'),
186 (r'[(=]\s*snprintf\(',
187 'snprintf can return > dst_size. Use base::SprintfTrunc'),
Primiano Tucci997ab092021-10-18 20:53:35 +0100188 (r'//.*\bDNS\b',
189 '// DNS (Do Not Ship) found. Did you mean to remove some testing code?'),
Ryan704bc822020-03-31 02:31:13 +0100190 (r'\bPERFETTO_EINTR\(close\(',
191 'close(2) must not be retried on EINTR on Linux and other OSes '
192 'that we run on, as the fd will be closed.'),
Primiano Tucci58d2dc62021-06-24 16:03:24 +0100193 (r'^#include <inttypes.h>', 'Use <cinttypes> rather than <inttypes.h>. ' +
194 'See https://github.com/google/perfetto/issues/146'),
Hector Dearman53d91b92019-12-13 17:07:52 +0000195 ]
196
197 def file_filter(x):
Ryan Savitski135038e2020-11-12 20:57:57 +0000198 return input_api.FilterSourceFile(x, files_to_check=[r'.*\.h$', r'.*\.cc$'])
Hector Dearman53d91b92019-12-13 17:07:52 +0000199
200 errors = []
201 for f in input_api.AffectedSourceFiles(file_filter):
202 for line_number, line in f.ChangedContents():
Primiano Tucci78cd82b2021-10-13 13:50:27 +0100203 if input_api.re.search(r'^\s*//', line):
204 continue # Skip comments
Hector Dearman53d91b92019-12-13 17:07:52 +0000205 for regex, message in bad_cpp:
206 if input_api.re.search(regex, line):
207 errors.append(
Ryan704bc822020-03-31 02:31:13 +0100208 output_api.PresubmitError('Banned pattern:\n {}:{} {}'.format(
Hector Dearman53d91b92019-12-13 17:07:52 +0000209 f.LocalPath(), line_number, message)))
210 return errors
211
212
Primiano Tucci0fd92a92023-07-17 11:47:38 -0700213def CheckBadCppPatterns(input_api, output_api):
214 bad_patterns = [
215 (r'.*/tracing_service_impl[.]cc$', r'\btrigger_config\(\)',
216 'Use GetTriggerMode(session->config) rather than .trigger_config()'),
217 ]
218 errors = []
219 for file_regex, code_regex, message in bad_patterns:
220 filt = lambda x: input_api.FilterSourceFile(x, files_to_check=[file_regex])
221 for f in input_api.AffectedSourceFiles(filt):
222 for line_number, line in f.ChangedContents():
223 if input_api.re.search(r'^\s*//', line):
224 continue # Skip comments
225 if input_api.re.search(code_regex, line):
226 errors.append(
227 output_api.PresubmitError('{}:{} {}'.format(
228 f.LocalPath(), line_number, message)))
229 return errors
230
231
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100232def CheckIncludeViolations(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700233 # The script invocation doesn't work on Windows.
234 if input_api.is_windows:
235 return []
236
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100237 tool = 'tools/check_include_violations'
238
Primiano Tucci834fdc72019-10-04 11:33:44 +0100239 def file_filter(x):
Ryan Savitski135038e2020-11-12 20:57:57 +0000240 return input_api.FilterSourceFile(
241 x, files_to_check=['include/.*[.]h$', tool])
Primiano Tucci834fdc72019-10-04 11:33:44 +0100242
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100243 if not input_api.AffectedSourceFiles(file_filter):
244 return []
245 if subprocess.call([tool]):
246 return [output_api.PresubmitError(tool + ' failed.')]
247 return []
Primiano Tucci40e98722018-02-16 11:50:17 +0000248
249
Hector Dearmanb7fa5442018-11-08 18:39:32 +0000250def CheckBinaryDescriptors(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700251 # The script invocation doesn't work on Windows.
252 if input_api.is_windows:
253 return []
254
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100255 tool = 'tools/gen_binary_descriptors'
256
Primiano Tucci834fdc72019-10-04 11:33:44 +0100257 def file_filter(x):
258 return input_api.FilterSourceFile(
Ryan Savitski135038e2020-11-12 20:57:57 +0000259 x, files_to_check=['protos/perfetto/.*[.]proto$', '.*[.]h', tool])
Primiano Tucci834fdc72019-10-04 11:33:44 +0100260
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100261 if not input_api.AffectedSourceFiles(file_filter):
Hector Dearmanb7fa5442018-11-08 18:39:32 +0000262 return []
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100263 if subprocess.call([tool, '--check-only']):
264 return [
Primiano Tucci834fdc72019-10-04 11:33:44 +0100265 output_api.PresubmitError('Please run ' + tool +
266 ' to update binary descriptors.')
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100267 ]
268 return []
Hector Dearmanb7fa5442018-11-08 18:39:32 +0000269
270
Primiano Tuccic5010802018-01-19 17:13:21 +0000271def CheckMergedTraceConfigProto(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700272 # The script invocation doesn't work on Windows.
273 if input_api.is_windows:
274 return []
275
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100276 tool = 'tools/gen_merged_protos'
277
Primiano Tucci834fdc72019-10-04 11:33:44 +0100278 def build_file_filter(x):
279 return input_api.FilterSourceFile(
Ryan Savitski135038e2020-11-12 20:57:57 +0000280 x, files_to_check=['protos/perfetto/.*[.]proto$', tool])
Primiano Tucci834fdc72019-10-04 11:33:44 +0100281
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100282 if not input_api.AffectedSourceFiles(build_file_filter):
Primiano Tuccic5010802018-01-19 17:13:21 +0000283 return []
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100284 if subprocess.call([tool, '--check-only']):
285 return [
286 output_api.PresubmitError(
287 'perfetto_config.proto or perfetto_trace.proto is out of ' +
288 'date. Please run ' + tool + ' to update it.')
289 ]
290 return []
Florian Mayer640b7ae2018-05-09 15:28:32 +0100291
292
Primiano Tuccia3645202020-08-03 16:28:18 +0200293# Prevent removing or changing lines in event_list.
294def CheckProtoEventList(input_api, output_api):
Florian Mayer640b7ae2018-05-09 15:28:32 +0100295 for f in input_api.AffectedFiles():
Hector Dearman7ea83c92022-05-12 15:21:49 +0100296 if f.LocalPath() != 'src/tools/ftrace_proto_gen/event_list':
Florian Mayer640b7ae2018-05-09 15:28:32 +0100297 continue
Primiano Tucci834fdc72019-10-04 11:33:44 +0100298 if any((not new_line.startswith('removed')) and new_line != old_line
Hector Dearmand88945e2021-12-07 18:56:40 +0000299 for old_line, new_line in zip(f.OldContents(), f.NewContents())):
Florian Mayer640b7ae2018-05-09 15:28:32 +0100300 return [
Primiano Tuccibf3b19c2019-06-01 08:40:26 +0100301 output_api.PresubmitError(
Primiano Tuccia3645202020-08-03 16:28:18 +0200302 'event_list only has two supported changes: '
Primiano Tucci834fdc72019-10-04 11:33:44 +0100303 'appending a new line, and replacing a line with removed.')
Florian Mayer640b7ae2018-05-09 15:28:32 +0100304 ]
305 return []
Anindita Ghosh2aa4e422020-06-26 15:48:32 +0100306
307
308def CheckProtoComments(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700309 # The script invocation doesn't work on Windows.
310 if input_api.is_windows:
311 return []
312
Anindita Ghosh2aa4e422020-06-26 15:48:32 +0100313 tool = 'tools/check_proto_comments'
314
315 def file_filter(x):
316 return input_api.FilterSourceFile(
Ryan Savitski135038e2020-11-12 20:57:57 +0000317 x, files_to_check=['protos/perfetto/.*[.]proto$', tool])
Anindita Ghosh2aa4e422020-06-26 15:48:32 +0100318
319 if not input_api.AffectedSourceFiles(file_filter):
320 return []
321 if subprocess.call([tool]):
322 return [output_api.PresubmitError(tool + ' failed')]
323 return []
Lalit Magantie0e8bdb2021-01-11 19:43:55 +0000324
325
Anna Mayzner412e0562022-11-25 09:55:51 +0000326def CheckSqlModules(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700327 # The script invocation doesn't work on Windows.
328 if input_api.is_windows:
329 return []
330
Anna Mayzner412e0562022-11-25 09:55:51 +0000331 tool = 'tools/check_sql_modules.py'
332
333 def file_filter(x):
334 return input_api.FilterSourceFile(
Alexander Timin8e1c5e82023-11-06 14:21:12 +0000335 x,
336 files_to_check=[
337 'src/trace_processor/perfetto_sql/stdlib/.*[.]sql$', tool
338 ])
Anna Mayzner412e0562022-11-25 09:55:51 +0000339
340 if not input_api.AffectedSourceFiles(file_filter):
341 return []
342 if subprocess.call([tool]):
343 return [output_api.PresubmitError(tool + ' failed')]
344 return []
345
346
Lalit Magantie0e8bdb2021-01-11 19:43:55 +0000347def CheckSqlMetrics(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700348 # The script invocation doesn't work on Windows.
349 if input_api.is_windows:
350 return []
351
Lalit Magantie0e8bdb2021-01-11 19:43:55 +0000352 tool = 'tools/check_sql_metrics.py'
353
354 def file_filter(x):
355 return input_api.FilterSourceFile(
356 x, files_to_check=['src/trace_processor/metrics/.*[.]sql$', tool])
357
358 if not input_api.AffectedSourceFiles(file_filter):
359 return []
360 if subprocess.call([tool]):
361 return [output_api.PresubmitError(tool + ' failed')]
362 return []
Primiano Tucci3d4217d2021-11-05 11:11:51 +0000363
364
365def CheckTestData(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700366 # The script invocation doesn't work on Windows.
367 if input_api.is_windows:
368 return []
369
Primiano Tucci3d4217d2021-11-05 11:11:51 +0000370 tool = 'tools/test_data'
371 if subprocess.call([tool, 'status', '--quiet']):
372 return [
373 output_api.PresubmitError(
Mohit Saini8abc5242022-06-24 11:16:03 +0100374 '//test/data is out of sync. Run ' + tool + ' status for more. \n'
375 'If you rebaselined UI tests or added a new test trace, run:'
376 '`tools/test_data upload`. Otherwise run `tools/install-build-deps`'
Andrew Shulaevd85d05f2022-07-04 11:54:42 +0100377 ' or `tools/test_data download --overwrite` to sync local test_data'
378 )
Primiano Tucci3d4217d2021-11-05 11:11:51 +0000379 ]
380 return []
Primiano Tucci11d94e12022-08-02 17:44:33 +0100381
382
383def CheckAmalgamatedPythonTools(input_api, output_api):
Bruce Dawson7627d042023-07-17 10:08:24 -0700384 # The script invocation doesn't work on Windows.
385 if input_api.is_windows:
386 return []
387
Primiano Tucci11d94e12022-08-02 17:44:33 +0100388 tool = 'tools/gen_amalgamated_python_tools'
389
390 # If no GN files were modified, bail out.
391 def build_file_filter(x):
392 return input_api.FilterSourceFile(x, files_to_check=('python/.*$', tool))
393
394 if not input_api.AffectedSourceFiles(build_file_filter):
395 return []
396 if subprocess.call([tool, '--check-only']):
397 return [
398 output_api.PresubmitError(
399 'amalgamated python tools/ are out of date. ' + 'Run ' + tool +
400 ' to update them.')
401 ]
402 return []