Commit | Line | Data |
---|---|---|
9a7ba4aa | 1 | #!/usr/bin/env python3 |
88b9d363 | 2 | # Copyright (C) 1996-2022 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 | ||
19 | """Helper to generate nltvals.def. | |
20 | ||
21 | nltvals.def is a file that describes various newlib/libgloss target values used | |
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', | |
52 | 'i960': 'libgloss/i960', | |
53 | 'mcore': 'libgloss/mcore', | |
b9249c46 | 54 | 'riscv': 'libgloss/riscv/machine', |
b7c5246b | 55 | 'sh': 'newlib/libc/sys/sh/sys', |
9a7ba4aa MF |
56 | 'v850': 'libgloss/v850/sys', |
57 | } | |
9a7ba4aa | 58 | |
9a7ba4aa MF |
59 | |
60 | # The header for the generated def file. | |
61 | FILE_HEADER = f"""\ | |
62 | /* Newlib/libgloss macro values needed by remote target support. */ | |
63 | /* This file is machine generated by {PROG}. */\ | |
64 | """ | |
65 | ||
66 | ||
67 | def gentvals(output: TextIO, cpp: str, srctype: str, srcdir: Path, | |
68 | headers: Iterable[str], | |
69 | pattern: str, | |
17d6f215 | 70 | filter: str = r'^$', |
9a7ba4aa MF |
71 | target: str = None): |
72 | """Extract constants from the specified files using a regular expression. | |
73 | ||
74 | We'll run things through the preprocessor. | |
75 | """ | |
76 | headers = tuple(headers) | |
77 | ||
78 | # Require all files exist in order to regenerate properly. | |
79 | for header in headers: | |
80 | fullpath = srcdir / header | |
81 | assert fullpath.exists(), f'{fullpath} does not exist' | |
82 | ||
bd0918c9 | 83 | if target is not None: |
9a7ba4aa | 84 | print(f'#ifdef NL_TARGET_{target}', file=output) |
bd0918c9 | 85 | print(f'#ifdef {srctype}_defs', file=output) |
9a7ba4aa MF |
86 | |
87 | print('\n'.join(f'/* from {x} */' for x in headers), file=output) | |
88 | ||
89 | if target is None: | |
90 | print(f'/* begin {srctype} target macros */', file=output) | |
91 | else: | |
92 | print(f'/* begin {target} {srctype} target macros */', file=output) | |
93 | ||
94 | # Extract all the symbols. | |
95 | srcfile = ''.join(f'#include <{x}>\n' for x in headers) | |
96 | syms = set() | |
97 | define_pattern = re.compile(r'^#\s*define\s+(' + pattern + ')') | |
17d6f215 | 98 | filter_pattern = re.compile(filter) |
9a7ba4aa MF |
99 | for header in headers: |
100 | with open(srcdir / header, 'r', encoding='utf-8') as fp: | |
101 | data = fp.read() | |
102 | for line in data.splitlines(): | |
103 | m = define_pattern.match(line) | |
17d6f215 | 104 | if m and not filter_pattern.search(line): |
9a7ba4aa MF |
105 | syms.add(m.group(1)) |
106 | for sym in sorted(syms): | |
107 | srcfile += f'#ifdef {sym}\nDEFVAL {{ "{sym}", {sym} }},\n#endif\n' | |
108 | ||
109 | result = subprocess.run( | |
110 | f'{cpp} -E -I"{srcdir}" -', shell=True, check=True, encoding='utf-8', | |
111 | input=srcfile, capture_output=True) | |
112 | for line in result.stdout.splitlines(): | |
113 | if line.startswith('DEFVAL '): | |
114 | print(line[6:].rstrip(), file=output) | |
115 | ||
bd0918c9 | 116 | print(f'#undef {srctype}_defs', file=output) |
9a7ba4aa MF |
117 | if target is None: |
118 | print(f'/* end {srctype} target macros */', file=output) | |
9a7ba4aa MF |
119 | else: |
120 | print(f'/* end {target} {srctype} target macros */', file=output) | |
121 | print('#endif', file=output) | |
bd0918c9 | 122 | print('#endif', file=output) |
9a7ba4aa MF |
123 | |
124 | ||
125 | def gen_common(output: TextIO, newlib: Path, cpp: str): | |
126 | """Generate the common C library constants. | |
127 | ||
128 | No arch should override these. | |
129 | """ | |
130 | gentvals(output, cpp, 'errno', newlib / 'newlib/libc/include', | |
131 | ('errno.h', 'sys/errno.h'), 'E[A-Z0-9]*') | |
132 | ||
133 | gentvals(output, cpp, 'signal', newlib / 'newlib/libc/include', | |
17d6f215 | 134 | ('signal.h', 'sys/signal.h'), r'SIG[A-Z0-9]*', filter=r'SIGSTKSZ') |
9a7ba4aa MF |
135 | |
136 | gentvals(output, cpp, 'open', newlib / 'newlib/libc/include', | |
137 | ('fcntl.h', 'sys/fcntl.h', 'sys/_default_fcntl.h'), r'O_[A-Z0-9]*') | |
138 | ||
139 | ||
140 | def gen_targets(output: TextIO, newlib: Path, cpp: str): | |
141 | """Generate the target-specific lists.""" | |
bd0918c9 | 142 | for target, subdir in sorted(TARGET_DIRS.items()): |
9a7ba4aa MF |
143 | gentvals(output, cpp, 'sys', newlib / subdir, ('syscall.h',), |
144 | r'SYS_[_a-zA-Z0-9]*', target=target) | |
145 | ||
bd0918c9 MF |
146 | # Then output the common syscall targets. |
147 | gentvals(output, cpp, 'sys', newlib / 'libgloss', ('syscall.h',), | |
148 | r'SYS_[_a-zA-Z0-9]*') | |
149 | ||
9a7ba4aa MF |
150 | |
151 | def gen(output: TextIO, newlib: Path, cpp: str): | |
152 | """Generate all the things!""" | |
153 | print(FILE_HEADER, file=output) | |
154 | gen_common(output, newlib, cpp) | |
155 | gen_targets(output, newlib, cpp) | |
156 | ||
157 | ||
158 | def get_parser() -> argparse.ArgumentParser: | |
159 | """Get CLI parser.""" | |
160 | parser = argparse.ArgumentParser( | |
161 | description=__doc__, | |
162 | formatter_class=argparse.RawDescriptionHelpFormatter) | |
163 | parser.add_argument( | |
164 | '-o', '--output', type=Path, | |
165 | help='write to the specified file instead of stdout') | |
166 | parser.add_argument( | |
167 | '--cpp', type=str, default='cpp', | |
168 | help='the preprocessor to use') | |
169 | parser.add_argument( | |
170 | '--srcroot', type=Path, | |
171 | help='the root of this source tree') | |
172 | parser.add_argument( | |
173 | 'newlib', nargs='?', type=Path, | |
174 | help='path to the newlib+libgloss source tree') | |
175 | return parser | |
176 | ||
177 | ||
178 | def parse_args(argv: List[str]) -> argparse.Namespace: | |
179 | """Process the command line & default options.""" | |
180 | parser = get_parser() | |
181 | opts = parser.parse_args(argv) | |
182 | ||
183 | if opts.srcroot is None: | |
184 | opts.srcroot = Path(__file__).resolve().parent.parent.parent | |
185 | ||
186 | if opts.newlib is None: | |
187 | # Try to find newlib relative to our source tree. | |
188 | if (opts.srcroot / 'newlib').is_dir(): | |
189 | # If newlib is manually in the same source tree, use it. | |
190 | if (opts.srcroot / 'libgloss').is_dir(): | |
191 | opts.newlib = opts.srcroot | |
192 | else: | |
193 | opts.newlib = opts.srcroot / 'newlib' | |
194 | elif (opts.srcroot.parent / 'newlib').is_dir(): | |
195 | # Or see if it's alongside the gdb/binutils repo. | |
196 | opts.newlib = opts.srcroot.parent / 'newlib' | |
197 | if opts.newlib is None or not opts.newlib.is_dir(): | |
198 | parser.error('unable to find newlib') | |
199 | ||
200 | return opts | |
201 | ||
202 | ||
203 | def main(argv: List[str]) -> int: | |
204 | """The main entry point for scripts.""" | |
205 | opts = parse_args(argv) | |
206 | ||
207 | if opts.output is not None: | |
208 | output = open(opts.output, 'w', encoding='utf-8') | |
209 | else: | |
210 | output = sys.stdout | |
211 | ||
212 | gen(output, opts.newlib, opts.cpp) | |
213 | return 0 | |
214 | ||
215 | ||
216 | if __name__ == '__main__': | |
217 | sys.exit(main(sys.argv[1:])) |