blob: 8f305d98c8f253434c6c13887c7077722619e4d9 [file] [log] [blame]
Mike Frysingera23e81f2019-07-01 11:55:50 -04001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Print signal table as markdown."""
8
9from __future__ import print_function
10
11import argparse
12import ctypes
13import ctypes.util
Mike Frysinger65de7442022-01-06 02:51:42 -050014from pathlib import Path
Mike Frysingera23e81f2019-07-01 11:55:50 -040015import re
16import subprocess
17import sys
Mike Frysingera535ecc2022-01-06 03:13:29 -050018from typing import Dict, List, Optional, Tuple
Mike Frysingera23e81f2019-07-01 11:55:50 -040019
20import constants
21
22
Mike Frysinger65de7442022-01-06 02:51:42 -050023# The directory where all the code & markdown files live.
24TOPDIR = Path(__file__).resolve().parent
25
Mike Frysingera23e81f2019-07-01 11:55:50 -040026# The C library header to find symbols.
27HEADER = 'signal.h'
28
29# The markdown file where we store this table.
30MARKDOWN = 'signals.md'
31
32# The string we use to start the table header.
33START_OF_TABLE = '| number |'
34
35
Mike Frysingera535ecc2022-01-06 03:13:29 -050036def strsignal(num: int) -> str:
Mike Frysingera23e81f2019-07-01 11:55:50 -040037 """Until Python supports this, do it ourselves."""
38 # Handle internal glibc details.
39 if num == 32 or num == 33:
40 return ('Real-time signal reserved by the C library for NPTL; '
41 'see [signal(7)]')
42
43 libc = ctypes.CDLL(ctypes.util.find_library('c'))
44 proto = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_int)
45 func = proto(('strsignal', libc), ((1,),))
46 return func(num).decode('utf-8')
47
48
Mike Frysingera535ecc2022-01-06 03:13:29 -050049def find_symbols(target: str) -> Dict[str, str]:
Mike Frysingera23e81f2019-07-01 11:55:50 -040050 """Find all the symbols using |target|."""
51 cc = '%s-clang' % (target,)
52 source = '#include <%s>\n' % (HEADER,)
53 ret = subprocess.run([cc, '-E', '-dD', '-P', '-'],
Mike Frysinger22a4f202022-01-06 03:17:41 -050054 check=True,
Mike Frysingera23e81f2019-07-01 11:55:50 -040055 input=source,
56 stdout=subprocess.PIPE,
57 encoding='utf-8')
58
59 table = {}
60
61 # Find all the symbols that are known. We have to do two passes as the
62 # headers like to #undef & redefine names.
63 matcher = re.compile(r'^#define\s+(SIG[^_]+)\s+[0-9]')
64 symbols = set()
65 for line in ret.stdout.splitlines():
66 m = matcher.match(line)
67 if m:
68 sym = m.group(1)
69 if sym not in ('SIGSTKSZ',):
70 symbols.add(sym)
71
72 source += '\n'.join('_%s %s' % (x, x) for x in symbols)
73
74 # Pull out any aliases.
75 matcher = re.compile(r'#define\s+(SIG[^_]+)\s(SIG[A-Z]+)')
76 for line in ret.stdout.splitlines():
77 m = matcher.match(line)
78 if m:
79 table[m.group(1)] = m.group(2)
80
81 # Parse our custom code and extract the symbols.
82 ret = subprocess.run([cc, '-E', '-P', '-'],
Mike Frysinger22a4f202022-01-06 03:17:41 -050083 check=True,
Mike Frysingera23e81f2019-07-01 11:55:50 -040084 input=source,
85 stdout=subprocess.PIPE,
86 encoding='utf-8')
87
88 for line in ret.stdout.splitlines():
89 if line.startswith('_SIG'):
90 sym, val = line.strip().split()
91 sym = sym[1:]
92 assert sym not in table, 'sym %s already found' % (sym,)
93 table[sym] = val
94 return table
95
96
Mike Frysingera535ecc2022-01-06 03:13:29 -050097def load_table() -> Tuple[Dict[str, str], Dict[str, List[str]]]:
Mike Frysingera23e81f2019-07-01 11:55:50 -040098 """Return a table of all the symbol values (and aliases)."""
99 all_tables = {}
100 for target in constants.TARGETS:
101 all_tables[target] = find_symbols(target)
102
Mike Frysinger2db7b852020-09-10 04:37:44 -0400103 # Check that all the tables are the same.
Mike Frysingera23e81f2019-07-01 11:55:50 -0400104 basetarget = constants.TARGETS[0]
105 baseline = all_tables[basetarget]
106 for target, table in all_tables.items():
107 assert baseline == table
108
109 # Sometimes values have multiple names.
110 aliases = {}
111 for sym, val in baseline.items():
112 try:
113 int(val)
114 except ValueError:
115 aliases.setdefault(val, []).append(sym)
116
117 # Deal with dynamic realtime signals.
118 baseline['SIGRTMIN-2'] = '32'
119 baseline['SIGRTMIN-1'] = '33'
120 assert 'SIGRTMIN' not in baseline
121 baseline['SIGRTMIN'] = '34'
122 assert 'SIGRTMAX' not in baseline
123 baseline['SIGRTMAX'] = '64'
124 for i in range(1, 16):
125 num = 34 + i
126 baseline['SIGRTMIN+%i' % (i,)] = str(num)
127 for i in range(1, 15):
128 num = 64 - i
129 baseline['SIGRTMAX-%i' % (i,)] = str(num)
130
131 return (baseline, aliases)
132
133
Mike Frysingera535ecc2022-01-06 03:13:29 -0500134def sort_table(table: Dict[str, str]) -> List[Tuple[str, str]]:
Mike Frysingera23e81f2019-07-01 11:55:50 -0400135 """Return a sorted table."""
136 def sorter(element):
137 try:
138 num = int(element[1])
139 except ValueError:
140 num = 0
141 return (num, element[0])
142 return sorted(table.items(), key=sorter)
143
144
Mike Frysingera535ecc2022-01-06 03:13:29 -0500145def get_md_table(table: Dict[str, str],
146 aliases: Dict[str, List[str]]) -> List[str]:
Mike Frysingera23e81f2019-07-01 11:55:50 -0400147 """Return the table in markdown format."""
148 ret = []
149 last_num = 0
150 for sym, val in sort_table(table):
151 try:
152 num = int(val)
153 except ValueError:
154 continue
155
156 # Fill in holes in the table so it's obvious to the user when searching.
157 for stub in range(last_num + 1, num):
158 ret.append('| %i | 0x%02x | | *not implemented* ||' % (stub, stub))
159 last_num = num
160
161 desc = strsignal(num)
162 ret.append('| %i | 0x%02x | %s | %s |' % (num, num, sym, desc))
163 for alias in aliases.get(sym, []):
164 ret.append('| %i | 0x%02x | %s | *(Same value as %s)* %s |' %
165 (num, num, alias, sym, desc))
166 return ret
167
168
Mike Frysingera535ecc2022-01-06 03:13:29 -0500169def get_parser() -> argparse.ArgumentParser:
Mike Frysingera23e81f2019-07-01 11:55:50 -0400170 """Return a command line parser."""
171 parser = argparse.ArgumentParser(description=__doc__)
172 parser.add_argument('-i', '--inplace', action='store_true',
173 help='Update the markdown file directly.')
174 return parser
175
176
Mike Frysingera535ecc2022-01-06 03:13:29 -0500177def main(argv: List[str]) -> Optional[int]:
Mike Frysingera23e81f2019-07-01 11:55:50 -0400178 """The main func!"""
179 parser = get_parser()
180 opts = parser.parse_args(argv)
181
182 baseline, aliases = load_table()
183 md_data = get_md_table(baseline, aliases)
184
185 if opts.inplace:
Mike Frysinger65de7442022-01-06 02:51:42 -0500186 md_file = TOPDIR / MARKDOWN
187 old_data = md_file.read_text().splitlines(keepends=True)
Mike Frysingera23e81f2019-07-01 11:55:50 -0400188
189 i = None
190 for i, line in enumerate(old_data):
191 if line.startswith(START_OF_TABLE):
192 break
193 else:
194 print('ERROR: could not find table in %s' % (md_file,),
195 file=sys.stderr)
196 sys.exit(1)
197
198 old_data = old_data[0:i + 2]
Mike Frysinger65de7442022-01-06 02:51:42 -0500199 with md_file.open('w') as fp:
Mike Frysingera23e81f2019-07-01 11:55:50 -0400200 fp.writelines(old_data)
201 fp.write('\n'.join(md_data) + '\n')
202 else:
203 print('\n'.join(md_data))
204
205
206if __name__ == '__main__':
207 sys.exit(main(sys.argv[1:]))