Skip to content

Commit 86f0e6d

Browse files
authored
[libc] Add --json mode for hdrgen (#127847)
This adds a feature to hdrgen to emit JSON summaries of header files for build system integration. For now the summaries have only the basic information about each header that is relevant for build and testing purposes: the standards and includes lists.
1 parent 9107ad4 commit 86f0e6d

File tree

4 files changed

+87
-32
lines changed

4 files changed

+87
-32
lines changed

libc/utils/hdrgen/header.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,12 @@ def relpath(file):
233233
content.append("__END_C_DECLS")
234234

235235
return "\n".join(content)
236+
237+
def json_data(self):
238+
return {
239+
"name": self.name,
240+
"standards": self.standards,
241+
"includes": [
242+
str(file) for file in sorted({COMMON_HEADER} | self.includes())
243+
],
244+
}

libc/utils/hdrgen/main.py

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# ==------------------------------------------------------------------------==#
1010

1111
import argparse
12+
import json
1213
import sys
1314
from pathlib import Path
1415

@@ -23,7 +24,7 @@ def main():
2324
help="Path to the YAML file containing header specification",
2425
metavar="FILE",
2526
type=Path,
26-
nargs=1,
27+
nargs="+",
2728
)
2829
parser.add_argument(
2930
"-o",
@@ -32,6 +33,11 @@ def main():
3233
type=Path,
3334
required=True,
3435
)
36+
parser.add_argument(
37+
"--json",
38+
help="Write JSON instead of a header, can use multiple YAML files",
39+
action="store_true",
40+
)
3541
parser.add_argument(
3642
"--depfile",
3743
help="Path to write a depfile",
@@ -52,6 +58,11 @@ def main():
5258
)
5359
args = parser.parse_args()
5460

61+
if not args.json and len(args.yaml_file) != 1:
62+
print("Only one YAML file at a time without --json", file=sys.stderr)
63+
parser.print_usage(sys.stderr)
64+
return 2
65+
5566
files_read = set()
5667

5768
def write_depfile():
@@ -66,35 +77,47 @@ def load_yaml(path):
6677
files_read.add(path)
6778
return load_yaml_file(path, HeaderFile, args.entry_point)
6879

69-
merge_from_files = dict()
70-
71-
def merge_from(paths):
72-
for path in paths:
73-
# Load each file exactly once, in case of redundant merges.
74-
if path in merge_from_files:
75-
continue
76-
header = load_yaml(path)
77-
merge_from_files[path] = header
78-
merge_from(path.parent / f for f in header.merge_yaml_files)
79-
80-
# Load the main file first.
81-
[yaml_file] = args.yaml_file
82-
header = load_yaml(yaml_file)
83-
84-
# Now load all the merge_yaml_files, and any transitive merge_yaml_files.
85-
merge_from(yaml_file.parent / f for f in header.merge_yaml_files)
86-
87-
# Merge in all those files' contents.
88-
for merge_from_path, merge_from_header in merge_from_files.items():
89-
if merge_from_header.name is not None:
90-
print(f"{merge_from_path!s}: Merge file cannot have header field", stderr)
91-
return 2
92-
header.merge(merge_from_header)
93-
94-
# The header_template path is relative to the containing YAML file.
95-
template = header.template(yaml_file.parent, files_read)
96-
97-
contents = fill_public_api(header.public_api(), template)
80+
def load_header(yaml_file):
81+
merge_from_files = dict()
82+
83+
def merge_from(paths):
84+
for path in paths:
85+
# Load each file exactly once, in case of redundant merges.
86+
if path in merge_from_files:
87+
continue
88+
header = load_yaml(path)
89+
merge_from_files[path] = header
90+
merge_from(path.parent / f for f in header.merge_yaml_files)
91+
92+
# Load the main file first.
93+
header = load_yaml(yaml_file)
94+
95+
# Now load all the merge_yaml_files, and transitive merge_yaml_files.
96+
merge_from(yaml_file.parent / f for f in header.merge_yaml_files)
97+
98+
# Merge in all those files' contents.
99+
for merge_from_path, merge_from_header in merge_from_files.items():
100+
if merge_from_header.name is not None:
101+
print(
102+
f"{merge_from_path!s}: Merge file cannot have header field",
103+
file=sys.stderr,
104+
)
105+
return 2
106+
header.merge(merge_from_header)
107+
108+
return header
109+
110+
if args.json:
111+
contents = json.dumps(
112+
[load_header(file).json_data() for file in args.yaml_file],
113+
indent=2,
114+
)
115+
else:
116+
[yaml_file] = args.yaml_file
117+
header = load_header(yaml_file)
118+
# The header_template path is relative to the containing YAML file.
119+
template = header.template(yaml_file.parent, files_read)
120+
contents = fill_public_api(header.public_api(), template)
98121

99122
write_depfile()
100123

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"name": "test_small.h",
4+
"standards": [],
5+
"includes": [
6+
"__llvm-libc-common.h",
7+
"llvm-libc-macros/test_more-macros.h",
8+
"llvm-libc-macros/test_small-macros.h",
9+
"llvm-libc-types/float128.h",
10+
"llvm-libc-types/type_a.h",
11+
"llvm-libc-types/type_b.h"
12+
]
13+
}
14+
]

libc/utils/hdrgen/tests/test_integration.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ def setUp(self):
1212
self.main_script = self.source_dir.parent / "main.py"
1313
self.maxDiff = 80 * 100
1414

15-
def run_script(self, yaml_file, output_file, entry_points=[]):
15+
def run_script(self, yaml_file, output_file, entry_points=[], switches=[]):
1616
command = [
1717
"python3",
1818
str(self.main_script),
1919
str(yaml_file),
2020
"--output",
2121
str(output_file),
22-
]
22+
] + switches
2323

2424
for entry_point in entry_points:
2525
command.extend(["--entry-point", entry_point])
@@ -59,6 +59,15 @@ def test_generate_subdir_header(self):
5959
self.run_script(yaml_file, output_file)
6060
self.compare_files(output_file, expected_output_file)
6161

62+
def test_generate_json(self):
63+
yaml_file = self.source_dir / "input/test_small.yaml"
64+
expected_output_file = self.source_dir / "expected_output/test_small.json"
65+
output_file = self.output_dir / "test_small.json"
66+
67+
self.run_script(yaml_file, output_file, switches=["--json"])
68+
69+
self.compare_files(output_file, expected_output_file)
70+
6271

6372
def main():
6473
parser = argparse.ArgumentParser(description="TestHeaderGenIntegration arguments")

0 commit comments

Comments
 (0)