blob: af68d6fb4d22934adb5d3f0052fdb214d63ce2dc [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
18
19import constants
20
21
Mike Frysinger65de7442022-01-06 02:51:42 -050022# The directory where all the code & markdown files live.
23TOPDIR = Path(__file__).resolve().parent
24
Mike Frysingera23e81f2019-07-01 11:55:50 -040025# The C library header to find symbols.
26HEADER = 'signal.h'
27
28# The markdown file where we store this table.
29MARKDOWN = 'signals.md'
30
31# The string we use to start the table header.
32START_OF_TABLE = '| number |'
33
34
35def strsignal(num):
36 """Until Python supports this, do it ourselves."""
37 # Handle internal glibc details.
38 if num == 32 or num == 33:
39 return ('Real-time signal reserved by the C library for NPTL; '
40 'see [signal(7)]')
41
42 libc = ctypes.CDLL(ctypes.util.find_library('c'))
43 proto = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_int)
44 func = proto(('strsignal', libc), ((1,),))
45 return func(num).decode('utf-8')
46
47
48def find_symbols(target):
49 """Find all the symbols using |target|."""
50 cc = '%s-clang' % (target,)
51 source = '#include <%s>\n' % (HEADER,)
52 ret = subprocess.run([cc, '-E', '-dD', '-P', '-'],
Mike Frysinger22a4f202022-01-06 03:17:41 -050053 check=True,
Mike Frysingera23e81f2019-07-01 11:55:50 -040054 input=source,
55 stdout=subprocess.PIPE,
56 encoding='utf-8')
57
58 table = {}
59
60 # Find all the symbols that are known. We have to do two passes as the
61 # headers like to #undef & redefine names.
62 matcher = re.compile(r'^#define\s+(SIG[^_]+)\s+[0-9]')
63 symbols = set()
64 for line in ret.stdout.splitlines():
65 m = matcher.match(line)
66 if m:
67 sym = m.group(1)
68 if sym not in ('SIGSTKSZ',):
69 symbols.add(sym)
70
71 source += '\n'.join('_%s %s' % (x, x) for x in symbols)
72
73 # Pull out any aliases.
74 matcher = re.compile(r'#define\s+(SIG[^_]+)\s(SIG[A-Z]+)')
75 for line in ret.stdout.splitlines():
76 m = matcher.match(line)
77 if m:
78 table[m.group(1)] = m.group(2)
79
80 # Parse our custom code and extract the symbols.
81 ret = subprocess.run([cc, '-E', '-P', '-'],
Mike Frysinger22a4f202022-01-06 03:17:41 -050082 check=True,
Mike Frysingera23e81f2019-07-01 11:55:50 -040083 input=source,
84 stdout=subprocess.PIPE,
85 encoding='utf-8')
86
87 for line in ret.stdout.splitlines():
88 if line.startswith('_SIG'):
89 sym, val = line.strip().split()
90 sym = sym[1:]
91 assert sym not in table, 'sym %s already found' % (sym,)
92 table[sym] = val
93 return table
94
95
96def load_table():
97 """Return a table of all the symbol values (and aliases)."""
98 all_tables = {}
99 for target in constants.TARGETS:
100 all_tables[target] = find_symbols(target)
101
Mike Frysinger2db7b852020-09-10 04:37:44 -0400102 # Check that all the tables are the same.
Mike Frysingera23e81f2019-07-01 11:55:50 -0400103 basetarget = constants.TARGETS[0]
104 baseline = all_tables[basetarget]
105 for target, table in all_tables.items():
106 assert baseline == table
107
108 # Sometimes values have multiple names.
109 aliases = {}
110 for sym, val in baseline.items():
111 try:
112 int(val)
113 except ValueError:
114 aliases.setdefault(val, []).append(sym)
115
116 # Deal with dynamic realtime signals.
117 baseline['SIGRTMIN-2'] = '32'
118 baseline['SIGRTMIN-1'] = '33'
119 assert 'SIGRTMIN' not in baseline
120 baseline['SIGRTMIN'] = '34'
121 assert 'SIGRTMAX' not in baseline
122 baseline['SIGRTMAX'] = '64'
123 for i in range(1, 16):
124 num = 34 + i
125 baseline['SIGRTMIN+%i' % (i,)] = str(num)
126 for i in range(1, 15):
127 num = 64 - i
128 baseline['SIGRTMAX-%i' % (i,)] = str(num)
129
130 return (baseline, aliases)
131
132
133def sort_table(table):
134 """Return a sorted table."""
135 def sorter(element):
136 try:
137 num = int(element[1])
138 except ValueError:
139 num = 0
140 return (num, element[0])
141 return sorted(table.items(), key=sorter)
142
143
144def get_md_table(table, aliases):
145 """Return the table in markdown format."""
146 ret = []
147 last_num = 0
148 for sym, val in sort_table(table):
149 try:
150 num = int(val)
151 except ValueError:
152 continue
153
154 # Fill in holes in the table so it's obvious to the user when searching.
155 for stub in range(last_num + 1, num):
156 ret.append('| %i | 0x%02x | | *not implemented* ||' % (stub, stub))
157 last_num = num
158
159 desc = strsignal(num)
160 ret.append('| %i | 0x%02x | %s | %s |' % (num, num, sym, desc))
161 for alias in aliases.get(sym, []):
162 ret.append('| %i | 0x%02x | %s | *(Same value as %s)* %s |' %
163 (num, num, alias, sym, desc))
164 return ret
165
166
167def get_parser():
168 """Return a command line parser."""
169 parser = argparse.ArgumentParser(description=__doc__)
170 parser.add_argument('-i', '--inplace', action='store_true',
171 help='Update the markdown file directly.')
172 return parser
173
174
175def main(argv):
176 """The main func!"""
177 parser = get_parser()
178 opts = parser.parse_args(argv)
179
180 baseline, aliases = load_table()
181 md_data = get_md_table(baseline, aliases)
182
183 if opts.inplace:
Mike Frysinger65de7442022-01-06 02:51:42 -0500184 md_file = TOPDIR / MARKDOWN
185 old_data = md_file.read_text().splitlines(keepends=True)
Mike Frysingera23e81f2019-07-01 11:55:50 -0400186
187 i = None
188 for i, line in enumerate(old_data):
189 if line.startswith(START_OF_TABLE):
190 break
191 else:
192 print('ERROR: could not find table in %s' % (md_file,),
193 file=sys.stderr)
194 sys.exit(1)
195
196 old_data = old_data[0:i + 2]
Mike Frysinger65de7442022-01-06 02:51:42 -0500197 with md_file.open('w') as fp:
Mike Frysingera23e81f2019-07-01 11:55:50 -0400198 fp.writelines(old_data)
199 fp.write('\n'.join(md_data) + '\n')
200 else:
201 print('\n'.join(md_data))
202
203
204if __name__ == '__main__':
205 sys.exit(main(sys.argv[1:]))