]>
Commit | Line | Data |
---|---|---|
9a7ba4aa | 1 | #!/usr/bin/env python3 |
1d506c26 | 2 | # Copyright (C) 1996-2024 Free Software Foundation, Inc. |
9a7ba4aa MF |
3 | # |
4 | # This file is part of the GNU simulators. | |
5 | # | |
6 | # This program is free software; you can redistribute it and/or modify | |
7 | # it under the terms of the GNU General Public License as published by | |
8 | # the Free Software Foundation; either version 3 of the License, or | |
9 | # (at your option) any later version. | |
10 | # | |
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. | |
15 | # | |
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | ||
1e42d017 | 19 | """Helper to generate target-newlib-* files. |
9a7ba4aa | 20 | |
1e42d017 | 21 | target-newlib-* are files that describe various newlib/libgloss values used |
9a7ba4aa MF |
22 | by the host/target interface. This needs to be rerun whenever the newlib source |
23 | changes. Developers manually run it. | |
24 | ||
25 | If the path to newlib is not specified, it will be searched for in: | |
26 | - the root of this source tree | |
27 | - alongside this source tree | |
28 | """ | |
29 | ||
30 | import argparse | |
31 | from pathlib import Path | |
32 | import re | |
33 | import subprocess | |
34 | import sys | |
35 | from typing import Iterable, List, TextIO | |
36 | ||
37 | ||
38 | PROG = Path(__file__).name | |
39 | ||
bd0918c9 | 40 | # Unfortunately, many newlib/libgloss ports have seen fit to define their own |
9a7ba4aa MF |
41 | # syscall.h file. This means that system call numbers can vary for each port. |
42 | # Support for all this crud is kept here, rather than trying to get too fancy. | |
43 | # If you want to try to improve this, please do, but don't break anything. | |
bd0918c9 MF |
44 | # |
45 | # If a target isn't listed here, it gets the standard syscall.h file (see | |
46 | # libgloss/syscall.h) which hopefully new targets will use. | |
9a7ba4aa MF |
47 | # |
48 | # NB: New ports should use libgloss, not newlib. | |
49 | TARGET_DIRS = { | |
50 | 'cr16': 'libgloss/cr16/sys', | |
51 | 'd10v': 'newlib/libc/sys/d10v/sys', | |
d485f8c7 MF |
52 | # Port removed from the tree years ago. |
53 | #'i960': 'libgloss/i960', | |
9a7ba4aa | 54 | 'mcore': 'libgloss/mcore', |
b9249c46 | 55 | 'riscv': 'libgloss/riscv/machine', |
b7c5246b | 56 | 'sh': 'newlib/libc/sys/sh/sys', |
9a7ba4aa MF |
57 | 'v850': 'libgloss/v850/sys', |
58 | } | |
9a7ba4aa | 59 | |
9a7ba4aa MF |
60 | |
61 | # The header for the generated def file. | |
62 | FILE_HEADER = f"""\ | |
63 | /* Newlib/libgloss macro values needed by remote target support. */ | |
64 | /* This file is machine generated by {PROG}. */\ | |
65 | """ | |
66 | ||
a7e40a99 MF |
67 | # Used to update sections of files. |
68 | START_MARKER = 'gennltvals: START' | |
69 | END_MARKER = 'gennltvals: END' | |
9a7ba4aa | 70 | |
a7e40a99 MF |
71 | |
72 | def extract_syms(cpp: str, srcdir: Path, | |
73 | headers: Iterable[str], | |
74 | pattern: str, | |
75 | filter: str = r'^$') -> dict: | |
76 | """Extract all the symbols from |headers| matching |pattern| using |cpp|.""" | |
77 | srcfile = ''.join(f'#include <{x}>\n' for x in headers) | |
78 | syms = set() | |
79 | define_pattern = re.compile(r'^#\s*define\s+(' + pattern + ')') | |
80 | filter_pattern = re.compile(filter) | |
81 | for header in headers: | |
82 | with open(srcdir / header, 'r', encoding='utf-8') as fp: | |
83 | data = fp.read() | |
84 | for line in data.splitlines(): | |
85 | m = define_pattern.match(line) | |
86 | if m and not filter_pattern.search(line): | |
87 | syms.add(m.group(1)) | |
88 | for sym in syms: | |
89 | srcfile += f'#ifdef {sym}\nDEFVAL "{sym}" {sym}\n#endif\n' | |
90 | ||
91 | result = subprocess.run( | |
92 | f'{cpp} -E -I"{srcdir}" -', shell=True, check=True, encoding='utf-8', | |
93 | input=srcfile, capture_output=True) | |
94 | ret = {} | |
95 | for line in result.stdout.splitlines(): | |
96 | if line.startswith('DEFVAL '): | |
97 | _, sym, val = line.split() | |
98 | ret[sym.strip('"')] = val | |
99 | return ret | |
100 | ||
101 | ||
1e42d017 | 102 | def gentvals(output_dir: Path, |
a7e40a99 | 103 | cpp: str, srctype: str, srcdir: Path, |
9a7ba4aa MF |
104 | headers: Iterable[str], |
105 | pattern: str, | |
39d53d04 | 106 | filter: str = r'^$', |
9a7ba4aa MF |
107 | target: str = None): |
108 | """Extract constants from the specified files using a regular expression. | |
109 | ||
110 | We'll run things through the preprocessor. | |
111 | """ | |
112 | headers = tuple(headers) | |
113 | ||
114 | # Require all files exist in order to regenerate properly. | |
115 | for header in headers: | |
116 | fullpath = srcdir / header | |
117 | assert fullpath.exists(), f'{fullpath} does not exist' | |
118 | ||
a7e40a99 MF |
119 | syms = extract_syms(cpp, srcdir, headers, pattern, filter) |
120 | ||
a7e40a99 | 121 | target_map = output_dir / f'target-newlib-{srctype}.c' |
1e42d017 MF |
122 | assert target_map.exists(), f'{target_map}: Missing skeleton' |
123 | old_lines = target_map.read_text().splitlines() | |
124 | start_i = end_i = None | |
125 | for i, line in enumerate(old_lines): | |
126 | if START_MARKER in line: | |
127 | start_i = i | |
128 | if END_MARKER in line: | |
129 | end_i = i | |
130 | assert start_i and end_i | |
131 | new_lines = old_lines[0:start_i + 1] | |
132 | new_lines.extend( | |
133 | f'#ifdef {sym}\n' | |
134 | f' {{ "{sym}", {sym}, {val} }},\n' | |
135 | f'#endif' for sym, val in sorted(syms.items())) | |
136 | new_lines.extend(old_lines[end_i:]) | |
137 | target_map.write_text('\n'.join(new_lines) + '\n') | |
9a7ba4aa MF |
138 | |
139 | ||
1e42d017 | 140 | def gen_common(output_dir: Path, newlib: Path, cpp: str): |
9a7ba4aa MF |
141 | """Generate the common C library constants. |
142 | ||
143 | No arch should override these. | |
144 | """ | |
a70dcdeb MF |
145 | # Enable Linux errno extensions since the newlib values are designed to |
146 | # not conflict with each other. | |
147 | gentvals(output_dir, | |
148 | cpp + ' -D__LINUX_ERRNO_EXTENSIONS__', | |
149 | 'errno', newlib / 'newlib/libc/include', | |
9a7ba4aa MF |
150 | ('errno.h', 'sys/errno.h'), 'E[A-Z0-9]*') |
151 | ||
1e42d017 | 152 | gentvals(output_dir, cpp, 'signal', newlib / 'newlib/libc/include', |
39d53d04 | 153 | ('signal.h', 'sys/signal.h'), r'SIG[A-Z0-9]*', filter=r'SIGSTKSZ') |
9a7ba4aa | 154 | |
1e42d017 | 155 | gentvals(output_dir, cpp, 'open', newlib / 'newlib/libc/include', |
9a7ba4aa MF |
156 | ('fcntl.h', 'sys/fcntl.h', 'sys/_default_fcntl.h'), r'O_[A-Z0-9]*') |
157 | ||
158 | ||
64ae70dd MF |
159 | def gen_target_syscall(output_dir: Path, newlib: Path, cpp: str): |
160 | """Generate the target-specific syscall lists.""" | |
161 | target_map_c = output_dir / 'target-newlib-syscall.c' | |
162 | old_lines_c = target_map_c.read_text().splitlines() | |
163 | start_i = end_i = None | |
164 | for i, line in enumerate(old_lines_c): | |
165 | if START_MARKER in line: | |
166 | start_i = i | |
167 | if END_MARKER in line: | |
168 | end_i = i | |
169 | assert start_i and end_i, f'{target_map_c}: Unable to find markers' | |
170 | new_lines_c = old_lines_c[0:start_i + 1] | |
171 | new_lines_c_end = old_lines_c[end_i:] | |
172 | ||
173 | target_map_h = output_dir / 'target-newlib-syscall.h' | |
174 | old_lines_h = target_map_h.read_text().splitlines() | |
175 | start_i = end_i = None | |
176 | for i, line in enumerate(old_lines_h): | |
177 | if START_MARKER in line: | |
178 | start_i = i | |
179 | if END_MARKER in line: | |
180 | end_i = i | |
181 | assert start_i and end_i, f'{target_map_h}: Unable to find markers' | |
182 | new_lines_h = old_lines_h[0:start_i + 1] | |
183 | new_lines_h_end = old_lines_h[end_i:] | |
184 | ||
185 | headers = ('syscall.h',) | |
186 | pattern = r'SYS_[_a-zA-Z0-9]*' | |
187 | ||
188 | # Output the target-specific syscalls. | |
bd0918c9 | 189 | for target, subdir in sorted(TARGET_DIRS.items()): |
64ae70dd MF |
190 | syms = extract_syms(cpp, newlib / subdir, headers, pattern) |
191 | new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_{target}_syscall_map[] = {{') | |
192 | new_lines_c.extend( | |
193 | f'#ifdef CB_{sym}\n' | |
194 | ' { ' | |
195 | f'"{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{target.upper()}_{sym}' | |
196 | ' },\n' | |
197 | '#endif' for sym in sorted(syms)) | |
198 | new_lines_c.append(' {NULL, -1, -1},') | |
199 | new_lines_c.append('};\n') | |
200 | ||
201 | new_lines_h.append( | |
202 | f'extern CB_TARGET_DEFS_MAP cb_{target}_syscall_map[];') | |
203 | new_lines_h.extend( | |
204 | f'#define TARGET_NEWLIB_{target.upper()}_{sym} {val}' | |
205 | for sym, val in sorted(syms.items())) | |
206 | new_lines_h.append('') | |
9a7ba4aa | 207 | |
bd0918c9 | 208 | # Then output the common syscall targets. |
64ae70dd MF |
209 | syms = extract_syms(cpp, newlib / 'libgloss', headers, pattern) |
210 | new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_init_syscall_map[] = {{') | |
211 | new_lines_c.extend( | |
212 | f'#ifdef CB_{sym}\n' | |
213 | f' {{ "{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{sym} }},\n' | |
214 | f'#endif' for sym in sorted(syms)) | |
215 | new_lines_c.append(' {NULL, -1, -1},') | |
216 | new_lines_c.append('};') | |
217 | ||
218 | new_lines_h.append('extern CB_TARGET_DEFS_MAP cb_init_syscall_map[];') | |
219 | new_lines_h.extend( | |
220 | f'#define TARGET_NEWLIB_{sym} {val}' | |
221 | for sym, val in sorted(syms.items())) | |
222 | ||
223 | new_lines_c.extend(new_lines_c_end) | |
224 | target_map_c.write_text('\n'.join(new_lines_c) + '\n') | |
225 | ||
226 | new_lines_h.extend(new_lines_h_end) | |
227 | target_map_h.write_text('\n'.join(new_lines_h) + '\n') | |
228 | ||
229 | ||
1e42d017 | 230 | def gen_targets(output_dir: Path, newlib: Path, cpp: str): |
64ae70dd MF |
231 | """Generate the target-specific lists.""" |
232 | gen_target_syscall(output_dir, newlib, cpp) | |
bd0918c9 | 233 | |
9a7ba4aa | 234 | |
1e42d017 | 235 | def gen(output_dir: Path, newlib: Path, cpp: str): |
9a7ba4aa | 236 | """Generate all the things!""" |
1e42d017 MF |
237 | gen_common(output_dir, newlib, cpp) |
238 | gen_targets(output_dir, newlib, cpp) | |
9a7ba4aa MF |
239 | |
240 | ||
241 | def get_parser() -> argparse.ArgumentParser: | |
242 | """Get CLI parser.""" | |
243 | parser = argparse.ArgumentParser( | |
244 | description=__doc__, | |
245 | formatter_class=argparse.RawDescriptionHelpFormatter) | |
246 | parser.add_argument( | |
247 | '-o', '--output', type=Path, | |
082cf694 | 248 | help='write to the specified directory') |
9a7ba4aa MF |
249 | parser.add_argument( |
250 | '--cpp', type=str, default='cpp', | |
251 | help='the preprocessor to use') | |
252 | parser.add_argument( | |
253 | '--srcroot', type=Path, | |
254 | help='the root of this source tree') | |
255 | parser.add_argument( | |
256 | 'newlib', nargs='?', type=Path, | |
257 | help='path to the newlib+libgloss source tree') | |
258 | return parser | |
259 | ||
260 | ||
261 | def parse_args(argv: List[str]) -> argparse.Namespace: | |
262 | """Process the command line & default options.""" | |
263 | parser = get_parser() | |
264 | opts = parser.parse_args(argv) | |
265 | ||
082cf694 MF |
266 | if opts.output is None: |
267 | # Default to where the script lives. | |
268 | opts.output = Path(__file__).resolve().parent | |
269 | ||
9a7ba4aa MF |
270 | if opts.srcroot is None: |
271 | opts.srcroot = Path(__file__).resolve().parent.parent.parent | |
082cf694 MF |
272 | else: |
273 | opts.srcroot = opts.srcroot.resolve() | |
9a7ba4aa MF |
274 | |
275 | if opts.newlib is None: | |
276 | # Try to find newlib relative to our source tree. | |
277 | if (opts.srcroot / 'newlib').is_dir(): | |
278 | # If newlib is manually in the same source tree, use it. | |
279 | if (opts.srcroot / 'libgloss').is_dir(): | |
280 | opts.newlib = opts.srcroot | |
281 | else: | |
282 | opts.newlib = opts.srcroot / 'newlib' | |
283 | elif (opts.srcroot.parent / 'newlib').is_dir(): | |
284 | # Or see if it's alongside the gdb/binutils repo. | |
285 | opts.newlib = opts.srcroot.parent / 'newlib' | |
286 | if opts.newlib is None or not opts.newlib.is_dir(): | |
287 | parser.error('unable to find newlib') | |
288 | ||
289 | return opts | |
290 | ||
291 | ||
292 | def main(argv: List[str]) -> int: | |
293 | """The main entry point for scripts.""" | |
294 | opts = parse_args(argv) | |
295 | ||
1e42d017 | 296 | gen(opts.output, opts.newlib, opts.cpp) |
9a7ba4aa MF |
297 | return 0 |
298 | ||
299 | ||
300 | if __name__ == '__main__': | |
301 | sys.exit(main(sys.argv[1:])) |