Skip to content

Commit 7ec59d8

Browse files
bpo-36876: [c-analyzer tool] Add a "capi" subcommand to the c-analyzer tool. (gh-23918)
This will help identify which C-API items will need to be updated for subinterpreter support. https://bugs.python.org/issue36876
1 parent b57ada9 commit 7ec59d8

File tree

10 files changed

+849
-57
lines changed

10 files changed

+849
-57
lines changed

Tools/c-analyzer/c_analyzer/__main__.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def fmt_full(analysis):
263263
def add_output_cli(parser, *, default='summary'):
264264
parser.add_argument('--format', dest='fmt', default=default, choices=tuple(FORMATS))
265265

266-
def process_args(args):
266+
def process_args(args, *, argv=None):
267267
pass
268268
return process_args
269269

@@ -280,7 +280,7 @@ def _cli_check(parser, checks=None, **kwargs):
280280
process_checks = add_checks_cli(parser)
281281
elif len(checks) == 1 and type(checks) is not dict and re.match(r'^<.*>$', checks[0]):
282282
check = checks[0][1:-1]
283-
def process_checks(args):
283+
def process_checks(args, *, argv=None):
284284
args.checks = [check]
285285
else:
286286
process_checks = add_checks_cli(parser, checks=checks)
@@ -428,9 +428,9 @@ def _cli_data(parser, filenames=None, known=None):
428428
if known is None:
429429
sub.add_argument('--known', required=True)
430430

431-
def process_args(args):
431+
def process_args(args, *, argv):
432432
if args.datacmd == 'dump':
433-
process_progress(args)
433+
process_progress(args, argv)
434434
return process_args
435435

436436

@@ -515,6 +515,7 @@ def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *, subset=None):
515515

516516
verbosity, traceback_cm = process_args_by_key(
517517
args,
518+
argv,
518519
processors[cmd],
519520
['verbosity', 'traceback_cm'],
520521
)

Tools/c-analyzer/c_common/scriptutil.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def add_verbosity_cli(parser):
192192
parser.add_argument('-q', '--quiet', action='count', default=0)
193193
parser.add_argument('-v', '--verbose', action='count', default=0)
194194

195-
def process_args(args):
195+
def process_args(args, *, argv=None):
196196
ns = vars(args)
197197
key = 'verbosity'
198198
if key in ns:
@@ -208,7 +208,7 @@ def add_traceback_cli(parser):
208208
parser.add_argument('--no-traceback', '--no-tb', dest='traceback',
209209
action='store_const', const=False)
210210

211-
def process_args(args):
211+
def process_args(args, *, argv=None):
212212
ns = vars(args)
213213
key = 'traceback_cm'
214214
if key in ns:
@@ -262,7 +262,7 @@ def add_sepval_cli(parser, opt, dest, choices, *, sep=',', **kwargs):
262262
#kwargs.setdefault('metavar', opt.upper())
263263
parser.add_argument(opt, dest=dest, action='append', **kwargs)
264264

265-
def process_args(args):
265+
def process_args(args, *, argv=None):
266266
ns = vars(args)
267267

268268
# XXX Use normalize_selection()?
@@ -293,7 +293,7 @@ def add_file_filtering_cli(parser, *, excluded=None):
293293

294294
excluded = tuple(excluded or ())
295295

296-
def process_args(args):
296+
def process_args(args, *, argv=None):
297297
ns = vars(args)
298298
key = 'iter_filenames'
299299
if key in ns:
@@ -323,7 +323,7 @@ def add_progress_cli(parser, *, threshold=VERBOSITY, **kwargs):
323323
parser.add_argument('--no-progress', dest='track_progress', action='store_false')
324324
parser.set_defaults(track_progress=True)
325325

326-
def process_args(args):
326+
def process_args(args, *, argv=None):
327327
if args.track_progress:
328328
ns = vars(args)
329329
verbosity = ns.get('verbosity', VERBOSITY)
@@ -339,7 +339,7 @@ def add_failure_filtering_cli(parser, pool, *, default=False):
339339
metavar=f'"{{all|{"|".join(sorted(pool))}}},..."')
340340
parser.add_argument('--no-fail', dest='fail', action='store_const', const=())
341341

342-
def process_args(args):
342+
def process_args(args, *, argv=None):
343343
ns = vars(args)
344344

345345
fail = ns.pop('fail')
@@ -371,7 +371,7 @@ def ignore_exc(exc):
371371
def add_kind_filtering_cli(parser, *, default=None):
372372
parser.add_argument('--kinds', action='append')
373373

374-
def process_args(args):
374+
def process_args(args, *, argv=None):
375375
ns = vars(args)
376376

377377
kinds = []
@@ -486,18 +486,18 @@ def _flatten_processors(processors):
486486
yield from _flatten_processors(proc)
487487

488488

489-
def process_args(args, processors, *, keys=None):
489+
def process_args(args, argv, processors, *, keys=None):
490490
processors = _flatten_processors(processors)
491491
ns = vars(args)
492492
extracted = {}
493493
if keys is None:
494494
for process_args in processors:
495-
for key in process_args(args):
495+
for key in process_args(args, argv=argv):
496496
extracted[key] = ns.pop(key)
497497
else:
498498
remainder = set(keys)
499499
for process_args in processors:
500-
hanging = process_args(args)
500+
hanging = process_args(args, argv=argv)
501501
if isinstance(hanging, str):
502502
hanging = [hanging]
503503
for key in hanging or ():
@@ -510,8 +510,8 @@ def process_args(args, processors, *, keys=None):
510510
return extracted
511511

512512

513-
def process_args_by_key(args, processors, keys):
514-
extracted = process_args(args, processors, keys=keys)
513+
def process_args_by_key(args, argv, processors, keys):
514+
extracted = process_args(args, argv, processors, keys=keys)
515515
return [extracted[key] for key in keys]
516516

517517

Tools/c-analyzer/c_common/tables.py

+176
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import csv
2+
import re
3+
import textwrap
24

35
from . import NOT_SET, strutil, fsutil
46

@@ -212,3 +214,177 @@ def _normalize_table_file_props(header, sep):
212214
else:
213215
sep = None
214216
return header, sep
217+
218+
219+
##################################
220+
# stdout tables
221+
222+
WIDTH = 20
223+
224+
225+
def resolve_columns(specs):
226+
if isinstance(specs, str):
227+
specs = specs.replace(',', ' ').strip().split()
228+
return _resolve_colspecs(specs)
229+
230+
231+
def build_table(specs, *, sep=' ', defaultwidth=None):
232+
columns = resolve_columns(specs)
233+
return _build_table(columns, sep=sep, defaultwidth=defaultwidth)
234+
235+
236+
_COLSPEC_RE = re.compile(textwrap.dedent(r'''
237+
^
238+
(?:
239+
[[]
240+
(
241+
(?: [^\s\]] [^\]]* )?
242+
[^\s\]]
243+
) # <label>
244+
[]]
245+
)?
246+
( \w+ ) # <field>
247+
(?:
248+
(?:
249+
:
250+
( [<^>] ) # <align>
251+
( \d+ ) # <width1>
252+
)
253+
|
254+
(?:
255+
(?:
256+
:
257+
( \d+ ) # <width2>
258+
)?
259+
(?:
260+
:
261+
( .*? ) # <fmt>
262+
)?
263+
)
264+
)?
265+
$
266+
'''), re.VERBOSE)
267+
268+
269+
def _parse_fmt(fmt):
270+
if fmt.startswith(tuple('<^>')):
271+
align = fmt[0]
272+
width = fmt[1:]
273+
if width.isdigit():
274+
return int(width), align
275+
return None, None
276+
277+
278+
def _parse_colspec(raw):
279+
m = _COLSPEC_RE.match(raw)
280+
if not m:
281+
return None
282+
label, field, align, width1, width2, fmt = m.groups()
283+
if not label:
284+
label = field
285+
if width1:
286+
width = None
287+
fmt = f'{align}{width1}'
288+
elif width2:
289+
width = int(width2)
290+
if fmt:
291+
_width, _ = _parse_fmt(fmt)
292+
if _width == width:
293+
width = None
294+
else:
295+
width = None
296+
return field, label, width, fmt
297+
298+
299+
def _normalize_colspec(spec):
300+
if len(spec) == 1:
301+
raw, = spec
302+
return _resolve_column(raw)
303+
304+
if len(spec) == 4:
305+
label, field, width, fmt = spec
306+
if width:
307+
fmt = f'{width}:{fmt}' if fmt else width
308+
elif len(raw) == 3:
309+
label, field, fmt = spec
310+
if not field:
311+
label, field = None, label
312+
elif not isinstance(field, str) or not field.isidentifier():
313+
fmt = f'{field}:{fmt}' if fmt else field
314+
label, field = None, label
315+
elif len(raw) == 2:
316+
label = None
317+
field, fmt = raw
318+
if not field:
319+
field, fmt = fmt, None
320+
elif not field.isidentifier() or fmt.isidentifier():
321+
label, field = field, fmt
322+
else:
323+
raise NotImplementedError
324+
325+
fmt = f':{fmt}' if fmt else ''
326+
if label:
327+
return _parse_colspec(f'[{label}]{field}{fmt}')
328+
else:
329+
return _parse_colspec(f'{field}{fmt}')
330+
331+
332+
def _resolve_colspec(raw):
333+
if isinstance(raw, str):
334+
spec = _parse_colspec(raw)
335+
else:
336+
spec = _normalize_colspec(raw)
337+
if spec is None:
338+
raise ValueError(f'unsupported column spec {raw!r}')
339+
return spec
340+
341+
342+
def _resolve_colspecs(columns):
343+
parsed = []
344+
for raw in columns:
345+
column = _resolve_colspec(raw)
346+
parsed.append(column)
347+
return parsed
348+
349+
350+
def _resolve_width(spec, defaultwidth):
351+
_, label, width, fmt = spec
352+
if width:
353+
if not isinstance(width, int):
354+
raise NotImplementedError
355+
return width
356+
elif width and fmt:
357+
width, _ = _parse_fmt(fmt)
358+
if width:
359+
return width
360+
361+
if not defaultwidth:
362+
return WIDTH
363+
elif not hasattr(defaultwidth, 'get'):
364+
return defaultwidth or WIDTH
365+
366+
defaultwidths = defaultwidth
367+
defaultwidth = defaultwidths.get(None) or WIDTH
368+
return defaultwidths.get(label) or defaultwidth
369+
370+
371+
def _build_table(columns, *, sep=' ', defaultwidth=None):
372+
header = []
373+
div = []
374+
rowfmt = []
375+
for spec in columns:
376+
label, field, _, colfmt = spec
377+
width = _resolve_width(spec, defaultwidth)
378+
if colfmt:
379+
colfmt = f':{colfmt}'
380+
else:
381+
colfmt = f':{width}'
382+
383+
header.append(f' {{:^{width}}} '.format(label))
384+
div.append('-' * (width + 2))
385+
rowfmt.append(f' {{{field}{colfmt}}} ')
386+
return (
387+
sep.join(header),
388+
sep.join(div),
389+
sep.join(rowfmt),
390+
)

Tools/c-analyzer/c_parser/__main__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def add_output_cli(parser):
149149
parser.add_argument('--showfwd', action='store_true', default=None)
150150
parser.add_argument('--no-showfwd', dest='showfwd', action='store_false', default=None)
151151

152-
def process_args(args):
152+
def process_args(args, *, argv=None):
153153
pass
154154
return process_args
155155

@@ -243,6 +243,7 @@ def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *, subset='parse'):
243243

244244
verbosity, traceback_cm = process_args_by_key(
245245
args,
246+
argv,
246247
processors[cmd],
247248
['verbosity', 'traceback_cm'],
248249
)

Tools/c-analyzer/c_parser/preprocessor/__main__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ def add_common_cli(parser, *, get_preprocessor=_get_preprocessor):
4040
parser.add_argument('--same', action='append')
4141
process_fail_arg = add_failure_filtering_cli(parser, FAIL)
4242

43-
def process_args(args):
43+
def process_args(args, *, argv):
4444
ns = vars(args)
4545

46-
process_fail_arg(args)
46+
process_fail_arg(args, argv)
4747
ignore_exc = ns.pop('ignore_exc')
4848
# We later pass ignore_exc to _get_preprocessor().
4949

@@ -174,6 +174,7 @@ def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *,
174174

175175
verbosity, traceback_cm = process_args_by_key(
176176
args,
177+
argv,
177178
processors[cmd],
178179
['verbosity', 'traceback_cm'],
179180
)

Tools/c-analyzer/check-c-globals.py

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def parse_args():
2222
cmd = 'check'
2323
verbosity, traceback_cm = process_args_by_key(
2424
args,
25+
argv,
2526
processors,
2627
['verbosity', 'traceback_cm'],
2728
)

0 commit comments

Comments
 (0)