rust/library/compiler-builtins/libm/etc/update-api-list.py
Trevor Gross ed72c4ec69 Use rustdoc output to create a list of public API
Rather than collecting a list of file names in `libm-test/build.rs`,
just use a script to parse rustdoc's JSON output.
2025-01-01 11:01:50 +00:00

118 lines
2.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Create a text file listing all public API. This can be used to ensure that all
functions are covered by our macros.
"""
import json
import subprocess as sp
import sys
import difflib
from pathlib import Path
from typing import Any
ETC_DIR = Path(__file__).parent
def get_rustdoc_json() -> dict[Any, Any]:
"""Get rustdoc's JSON output for the `libm` crate."""
librs_path = ETC_DIR.joinpath("../src/lib.rs")
j = sp.check_output(
[
"rustdoc",
librs_path,
"--edition=2021",
"--output-format=json",
"-Zunstable-options",
"-o-",
],
text=True,
)
j = json.loads(j)
return j
def list_public_functions() -> list[str]:
"""Get a list of public functions from rustdoc JSON output.
Note that this only finds functions that are reexported in `lib.rs`, this will
need to be adjusted if we need to account for functions that are defined there.
"""
names = []
index: dict[str, dict[str, Any]] = get_rustdoc_json()["index"]
for item in index.values():
# Find public items
if item["visibility"] != "public":
continue
# Find only reexports
if "use" not in item["inner"].keys():
continue
# Locate the item that is reexported
id = item["inner"]["use"]["id"]
srcitem = index.get(str(id))
# External crate
if srcitem is None:
continue
# Skip if not a function
if "function" not in srcitem["inner"].keys():
continue
names.append(srcitem["name"])
names.sort()
return names
def diff_and_exit(actual: str, expected: str):
"""If the two strings are different, print a diff between them and then exit
with an error.
"""
if actual == expected:
print("output matches expected; success")
return
a = [f"{line}\n" for line in actual.splitlines()]
b = [f"{line}\n" for line in expected.splitlines()]
diff = difflib.unified_diff(a, b, "actual", "expected")
sys.stdout.writelines(diff)
print("mismatched function list")
exit(1)
def main():
"""By default overwrite the file. If `--check` is passed, print a diff instead and
error if the files are different.
"""
match sys.argv:
case [_]:
check = False
case [_, "--check"]:
check = True
case _:
print("unrecognized arguments")
exit(1)
names = list_public_functions()
output = "# autogenerated by update-api-list.py\n"
for name in names:
output += f"{name}\n"
out_file = ETC_DIR.joinpath("function-list.txt")
if check:
with open(out_file, "r") as f:
current = f.read()
diff_and_exit(current, output)
else:
with open(out_file, "w") as f:
f.write(output)
if __name__ == "__main__":
main()