-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwhich-header
executable file
·119 lines (102 loc) · 3.32 KB
/
which-header
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python3
"""locate headers #included by the GCC C Preprocessor
SPDX-FileCopyrightText: 2024 Eli Array Minkoff
SPDX-License-Identifier: 0BSD
"""
import argparse
import subprocess
from pathlib import Path
import os
_CPP_INFO = subprocess.check_output(
# https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
["cpp", "-v", "/dev/null", "-o", "/dev/null"],
stderr=subprocess.STDOUT,
).decode()
# cut off information after search list
_CPP_INFO = _CPP_INFO.split("End of search list.\n", 1)[0]
# cut off information before search list
_CPP_INFO = _CPP_INFO.split('#include "..." search starts here:\n', 1)[1]
_quoted, _default = _CPP_INFO.split("#include <...> search starts here:\n", 1)
# path names listed are indented with single spaces.
quoted_search_dirs = [Path.cwd()] + [Path(p[1:]) for p in _quoted.splitlines()]
default_search_dirs = [Path(p[1:]) for p in _default.splitlines()]
def _get_args() -> argparse.Namespace:
"""Parse the command-line flags, return a Namespace object"""
parser = argparse.ArgumentParser(
description="Locate header included by gcc's cpp",
allow_abbrev=False,
)
# add basic arguments
parser.add_argument(
"--quoted",
"-Q",
action="store_true",
dest="quoted",
help="treat files as though they're included with quotes",
)
parser.add_argument(
"-iquoted",
action="append",
dest="quoted_includes",
type=Path,
help="Extra directory for search path for quoted includes only",
)
parser.add_argument(
"-I",
"-include",
"-isystem",
"--include",
action="append",
dest="includes",
type=Path,
help="extra directory for search path",
)
parser.add_argument(
"-a",
"--all-matches",
action="store_true",
dest="all_matches",
help="Print all matched headers, rather than the 1st",
)
parser.add_argument("headers", nargs="*", help="headers to look up")
return parser.parse_args()
def find_header(
header: str | os.PathLike,
search_dirs: list[Path],
return_all: bool = False,
) -> Path | list[Path]:
"""Return a pathlib.Path to the header
parameters:
header: the header to search for
search_dirs: the directories in the search path
if header can't be found, raises a FileNotFoundError.
"""
ret = []
for search_dir in search_dirs:
if (joined := search_dir.joinpath(header)).exists():
if not return_all:
return joined
ret.append(joined)
if not ret:
raise FileNotFoundError(f"{header} not found in search path")
return ret
def main():
"""The main function"""
args = _get_args()
search_path = []
if args.quoted:
if args.quoted_includes is not None:
search_path += args.quoted_includes
search_path += quoted_search_dirs
if args.includes is not None:
search_path += args.includes
search_path += default_search_dirs
# add extra search directories
for header in args.headers:
if args.all_matches:
for location in find_header(header, search_path, True):
print(location)
else:
print(find_header(header, search_path, False))
if __name__ == "__main__":
main()