| #!/usr/bin/env python3 |
| # Copyright (C) 1996-2021 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 nltvals.def. |
| |
| nltvals.def is a file that describes various newlib/libgloss target 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', |
| '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}. */\ |
| """ |
| |
| |
| def gentvals(output: TextIO, 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' |
| |
| if target is not None: |
| print(f'#ifdef NL_TARGET_{target}', file=output) |
| print(f'#ifdef {srctype}_defs', file=output) |
| |
| print('\n'.join(f'/* from {x} */' for x in headers), file=output) |
| |
| if target is None: |
| print(f'/* begin {srctype} target macros */', file=output) |
| else: |
| print(f'/* begin {target} {srctype} target macros */', file=output) |
| |
| # Extract all the symbols. |
| 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 sorted(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) |
| for line in result.stdout.splitlines(): |
| if line.startswith('DEFVAL '): |
| print(line[6:].rstrip(), file=output) |
| |
| print(f'#undef {srctype}_defs', file=output) |
| if target is None: |
| print(f'/* end {srctype} target macros */', file=output) |
| else: |
| print(f'/* end {target} {srctype} target macros */', file=output) |
| print('#endif', file=output) |
| print('#endif', file=output) |
| |
| |
| def gen_common(output: TextIO, newlib: Path, cpp: str): |
| """Generate the common C library constants. |
| |
| No arch should override these. |
| """ |
| gentvals(output, cpp, 'errno', newlib / 'newlib/libc/include', |
| ('errno.h', 'sys/errno.h'), 'E[A-Z0-9]*') |
| |
| gentvals(output, cpp, 'signal', newlib / 'newlib/libc/include', |
| ('signal.h', 'sys/signal.h'), r'SIG[A-Z0-9]*', filter=r'SIGSTKSZ') |
| |
| gentvals(output, cpp, 'open', newlib / 'newlib/libc/include', |
| ('fcntl.h', 'sys/fcntl.h', 'sys/_default_fcntl.h'), r'O_[A-Z0-9]*') |
| |
| |
| def gen_targets(output: TextIO, newlib: Path, cpp: str): |
| """Generate the target-specific lists.""" |
| for target, subdir in sorted(TARGET_DIRS.items()): |
| gentvals(output, cpp, 'sys', newlib / subdir, ('syscall.h',), |
| r'SYS_[_a-zA-Z0-9]*', target=target) |
| |
| # Then output the common syscall targets. |
| gentvals(output, cpp, 'sys', newlib / 'libgloss', ('syscall.h',), |
| r'SYS_[_a-zA-Z0-9]*') |
| |
| |
| def gen(output: TextIO, newlib: Path, cpp: str): |
| """Generate all the things!""" |
| print(FILE_HEADER, file=output) |
| gen_common(output, newlib, cpp) |
| gen_targets(output, 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) |
| |
| output = (opts.output / 'nltvals.def').open('w', encoding='utf-8') |
| |
| gen(output, opts.newlib, opts.cpp) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |