|  | #!/usr/bin/env python3 | 
|  | # Copyright (C) 1996-2024 Free Software Foundation, Inc. | 
|  | # | 
|  | # This file is part of the GNU simulators. | 
|  | # | 
|  | # This program is free software; you can redistribute it and/or modify | 
|  | # it under the terms of the GNU General Public License as published by | 
|  | # the Free Software Foundation; either version 3 of the License, or | 
|  | # (at your option) any later version. | 
|  | # | 
|  | # This program is distributed in the hope that it will be useful, | 
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | # GNU General Public License for more details. | 
|  | # | 
|  | # You should have received a copy of the GNU General Public License | 
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
|  |  | 
|  | """Helper to generate target-newlib-* files. | 
|  |  | 
|  | target-newlib-* are files that describe various newlib/libgloss values used | 
|  | by the host/target interface.  This needs to be rerun whenever the newlib source | 
|  | changes.  Developers manually run it. | 
|  |  | 
|  | If the path to newlib is not specified, it will be searched for in: | 
|  | - the root of this source tree | 
|  | - alongside this source tree | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | from pathlib import Path | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  | from typing import Iterable, List, TextIO | 
|  |  | 
|  |  | 
|  | PROG = Path(__file__).name | 
|  |  | 
|  | # Unfortunately, many newlib/libgloss ports have seen fit to define their own | 
|  | # syscall.h file.  This means that system call numbers can vary for each port. | 
|  | # Support for all this crud is kept here, rather than trying to get too fancy. | 
|  | # If you want to try to improve this, please do, but don't break anything. | 
|  | # | 
|  | # If a target isn't listed here, it gets the standard syscall.h file (see | 
|  | # libgloss/syscall.h) which hopefully new targets will use. | 
|  | # | 
|  | # NB: New ports should use libgloss, not newlib. | 
|  | TARGET_DIRS = { | 
|  | 'cr16': 'libgloss/cr16/sys', | 
|  | 'd10v': 'newlib/libc/sys/d10v/sys', | 
|  | # Port removed from the tree years ago. | 
|  | #'i960': 'libgloss/i960', | 
|  | 'mcore': 'libgloss/mcore', | 
|  | 'riscv': 'libgloss/riscv/machine', | 
|  | 'sh': 'newlib/libc/sys/sh/sys', | 
|  | 'v850': 'libgloss/v850/sys', | 
|  | } | 
|  |  | 
|  |  | 
|  | # The header for the generated def file. | 
|  | FILE_HEADER = f"""\ | 
|  | /* Newlib/libgloss macro values needed by remote target support.  */ | 
|  | /* This file is machine generated by {PROG}.  */\ | 
|  | """ | 
|  |  | 
|  | # Used to update sections of files. | 
|  | START_MARKER = 'gennltvals: START' | 
|  | END_MARKER = 'gennltvals: END' | 
|  |  | 
|  |  | 
|  | def extract_syms(cpp: str, srcdir: Path, | 
|  | headers: Iterable[str], | 
|  | pattern: str, | 
|  | filter: str = r'^$') -> dict: | 
|  | """Extract all the symbols from |headers| matching |pattern| using |cpp|.""" | 
|  | srcfile = ''.join(f'#include <{x}>\n' for x in headers) | 
|  | syms = set() | 
|  | define_pattern = re.compile(r'^#\s*define\s+(' + pattern + ')') | 
|  | filter_pattern = re.compile(filter) | 
|  | for header in headers: | 
|  | with open(srcdir / header, 'r', encoding='utf-8') as fp: | 
|  | data = fp.read() | 
|  | for line in data.splitlines(): | 
|  | m = define_pattern.match(line) | 
|  | if m and not filter_pattern.search(line): | 
|  | syms.add(m.group(1)) | 
|  | for sym in syms: | 
|  | srcfile += f'#ifdef {sym}\nDEFVAL "{sym}" {sym}\n#endif\n' | 
|  |  | 
|  | result = subprocess.run( | 
|  | f'{cpp} -E -I"{srcdir}" -', shell=True, check=True, encoding='utf-8', | 
|  | input=srcfile, capture_output=True) | 
|  | ret = {} | 
|  | for line in result.stdout.splitlines(): | 
|  | if line.startswith('DEFVAL '): | 
|  | _, sym, val = line.split() | 
|  | ret[sym.strip('"')] = val | 
|  | return ret | 
|  |  | 
|  |  | 
|  | def gentvals(output_dir: Path, | 
|  | cpp: str, srctype: str, srcdir: Path, | 
|  | headers: Iterable[str], | 
|  | pattern: str, | 
|  | filter: str = r'^$', | 
|  | target: str = None): | 
|  | """Extract constants from the specified files using a regular expression. | 
|  |  | 
|  | We'll run things through the preprocessor. | 
|  | """ | 
|  | headers = tuple(headers) | 
|  |  | 
|  | # Require all files exist in order to regenerate properly. | 
|  | for header in headers: | 
|  | fullpath = srcdir / header | 
|  | assert fullpath.exists(), f'{fullpath} does not exist' | 
|  |  | 
|  | syms = extract_syms(cpp, srcdir, headers, pattern, filter) | 
|  |  | 
|  | target_map = output_dir / f'target-newlib-{srctype}.c' | 
|  | assert target_map.exists(), f'{target_map}: Missing skeleton' | 
|  | old_lines = target_map.read_text().splitlines() | 
|  | start_i = end_i = None | 
|  | for i, line in enumerate(old_lines): | 
|  | if START_MARKER in line: | 
|  | start_i = i | 
|  | if END_MARKER in line: | 
|  | end_i = i | 
|  | assert start_i and end_i | 
|  | new_lines = old_lines[0:start_i + 1] | 
|  | new_lines.extend( | 
|  | f'#ifdef {sym}\n' | 
|  | f'  {{ "{sym}", {sym}, {val} }},\n' | 
|  | f'#endif' for sym, val in sorted(syms.items())) | 
|  | new_lines.extend(old_lines[end_i:]) | 
|  | target_map.write_text('\n'.join(new_lines) + '\n') | 
|  |  | 
|  |  | 
|  | def gen_common(output_dir: Path, newlib: Path, cpp: str): | 
|  | """Generate the common C library constants. | 
|  |  | 
|  | No arch should override these. | 
|  | """ | 
|  | # Enable Linux errno extensions since the newlib values are designed to | 
|  | # not conflict with each other. | 
|  | gentvals(output_dir, | 
|  | cpp + ' -D__LINUX_ERRNO_EXTENSIONS__', | 
|  | 'errno', newlib / 'newlib/libc/include', | 
|  | ('errno.h', 'sys/errno.h'), 'E[A-Z0-9]*') | 
|  |  | 
|  | gentvals(output_dir, cpp, 'signal', newlib / 'newlib/libc/include', | 
|  | ('signal.h', 'sys/signal.h'), r'SIG[A-Z0-9]*', filter=r'SIGSTKSZ') | 
|  |  | 
|  | gentvals(output_dir, cpp, 'open', newlib / 'newlib/libc/include', | 
|  | ('fcntl.h', 'sys/fcntl.h', 'sys/_default_fcntl.h'), r'O_[A-Z0-9]*') | 
|  |  | 
|  |  | 
|  | def gen_target_syscall(output_dir: Path, newlib: Path, cpp: str): | 
|  | """Generate the target-specific syscall lists.""" | 
|  | target_map_c = output_dir / 'target-newlib-syscall.c' | 
|  | old_lines_c = target_map_c.read_text().splitlines() | 
|  | start_i = end_i = None | 
|  | for i, line in enumerate(old_lines_c): | 
|  | if START_MARKER in line: | 
|  | start_i = i | 
|  | if END_MARKER in line: | 
|  | end_i = i | 
|  | assert start_i and end_i, f'{target_map_c}: Unable to find markers' | 
|  | new_lines_c = old_lines_c[0:start_i + 1] | 
|  | new_lines_c_end = old_lines_c[end_i:] | 
|  |  | 
|  | target_map_h = output_dir / 'target-newlib-syscall.h' | 
|  | old_lines_h = target_map_h.read_text().splitlines() | 
|  | start_i = end_i = None | 
|  | for i, line in enumerate(old_lines_h): | 
|  | if START_MARKER in line: | 
|  | start_i = i | 
|  | if END_MARKER in line: | 
|  | end_i = i | 
|  | assert start_i and end_i, f'{target_map_h}: Unable to find markers' | 
|  | new_lines_h = old_lines_h[0:start_i + 1] | 
|  | new_lines_h_end = old_lines_h[end_i:] | 
|  |  | 
|  | headers = ('syscall.h',) | 
|  | pattern = r'SYS_[_a-zA-Z0-9]*' | 
|  |  | 
|  | # Output the target-specific syscalls. | 
|  | for target, subdir in sorted(TARGET_DIRS.items()): | 
|  | syms = extract_syms(cpp, newlib / subdir, headers, pattern) | 
|  | new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_{target}_syscall_map[] = {{') | 
|  | new_lines_c.extend( | 
|  | f'#ifdef CB_{sym}\n' | 
|  | '  { ' | 
|  | f'"{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{target.upper()}_{sym}' | 
|  | ' },\n' | 
|  | '#endif' for sym in sorted(syms)) | 
|  | new_lines_c.append('  {NULL, -1, -1},') | 
|  | new_lines_c.append('};\n') | 
|  |  | 
|  | new_lines_h.append( | 
|  | f'extern CB_TARGET_DEFS_MAP cb_{target}_syscall_map[];') | 
|  | new_lines_h.extend( | 
|  | f'#define TARGET_NEWLIB_{target.upper()}_{sym} {val}' | 
|  | for sym, val in sorted(syms.items())) | 
|  | new_lines_h.append('') | 
|  |  | 
|  | # Then output the common syscall targets. | 
|  | syms = extract_syms(cpp, newlib / 'libgloss', headers, pattern) | 
|  | new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_init_syscall_map[] = {{') | 
|  | new_lines_c.extend( | 
|  | f'#ifdef CB_{sym}\n' | 
|  | f'  {{ "{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{sym} }},\n' | 
|  | f'#endif' for sym in sorted(syms)) | 
|  | new_lines_c.append('  {NULL, -1, -1},') | 
|  | new_lines_c.append('};') | 
|  |  | 
|  | new_lines_h.append('extern CB_TARGET_DEFS_MAP cb_init_syscall_map[];') | 
|  | new_lines_h.extend( | 
|  | f'#define TARGET_NEWLIB_{sym} {val}' | 
|  | for sym, val in sorted(syms.items())) | 
|  |  | 
|  | new_lines_c.extend(new_lines_c_end) | 
|  | target_map_c.write_text('\n'.join(new_lines_c) + '\n') | 
|  |  | 
|  | new_lines_h.extend(new_lines_h_end) | 
|  | target_map_h.write_text('\n'.join(new_lines_h) + '\n') | 
|  |  | 
|  |  | 
|  | def gen_targets(output_dir: Path, newlib: Path, cpp: str): | 
|  | """Generate the target-specific lists.""" | 
|  | gen_target_syscall(output_dir, newlib, cpp) | 
|  |  | 
|  |  | 
|  | def gen(output_dir: Path, newlib: Path, cpp: str): | 
|  | """Generate all the things!""" | 
|  | gen_common(output_dir, newlib, cpp) | 
|  | gen_targets(output_dir, newlib, cpp) | 
|  |  | 
|  |  | 
|  | def get_parser() -> argparse.ArgumentParser: | 
|  | """Get CLI parser.""" | 
|  | parser = argparse.ArgumentParser( | 
|  | description=__doc__, | 
|  | formatter_class=argparse.RawDescriptionHelpFormatter) | 
|  | parser.add_argument( | 
|  | '-o', '--output', type=Path, | 
|  | help='write to the specified directory') | 
|  | parser.add_argument( | 
|  | '--cpp', type=str, default='cpp', | 
|  | help='the preprocessor to use') | 
|  | parser.add_argument( | 
|  | '--srcroot', type=Path, | 
|  | help='the root of this source tree') | 
|  | parser.add_argument( | 
|  | 'newlib', nargs='?', type=Path, | 
|  | help='path to the newlib+libgloss source tree') | 
|  | return parser | 
|  |  | 
|  |  | 
|  | def parse_args(argv: List[str]) -> argparse.Namespace: | 
|  | """Process the command line & default options.""" | 
|  | parser = get_parser() | 
|  | opts = parser.parse_args(argv) | 
|  |  | 
|  | if opts.output is None: | 
|  | # Default to where the script lives. | 
|  | opts.output = Path(__file__).resolve().parent | 
|  |  | 
|  | if opts.srcroot is None: | 
|  | opts.srcroot = Path(__file__).resolve().parent.parent.parent | 
|  | else: | 
|  | opts.srcroot = opts.srcroot.resolve() | 
|  |  | 
|  | if opts.newlib is None: | 
|  | # Try to find newlib relative to our source tree. | 
|  | if (opts.srcroot / 'newlib').is_dir(): | 
|  | # If newlib is manually in the same source tree, use it. | 
|  | if (opts.srcroot / 'libgloss').is_dir(): | 
|  | opts.newlib = opts.srcroot | 
|  | else: | 
|  | opts.newlib = opts.srcroot / 'newlib' | 
|  | elif (opts.srcroot.parent / 'newlib').is_dir(): | 
|  | # Or see if it's alongside the gdb/binutils repo. | 
|  | opts.newlib = opts.srcroot.parent / 'newlib' | 
|  | if opts.newlib is None or not opts.newlib.is_dir(): | 
|  | parser.error('unable to find newlib') | 
|  |  | 
|  | return opts | 
|  |  | 
|  |  | 
|  | def main(argv: List[str]) -> int: | 
|  | """The main entry point for scripts.""" | 
|  | opts = parse_args(argv) | 
|  |  | 
|  | gen(opts.output, opts.newlib, opts.cpp) | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main(sys.argv[1:])) |