import os
import re
import subprocess as sp
import sys
from difflib import unified_diff
from glob import iglob
from pathlib import Path
FMT_DIRS = ["src", "ci"]
IGNORE_FILES = [
"src/macros.rs"
]
def main():
check_only = os.getenv("CI") is not None
run(["rustfmt", "-V"])
fmt_files = []
for dir in FMT_DIRS:
fmt_files.extend(iglob(f"{dir}/**/*.rs", recursive=True))
for file in fmt_files:
if file in IGNORE_FILES:
continue
fmt_one(Path(file), check_only)
if check_only:
run(["cargo", "fmt", "--check"])
else:
run(["cargo", "fmt"])
for file in iglob("libc-test/semver/*.txt"):
check_semver_file(Path(file))
run(
[
"cargo",
"test",
"--manifest-path=libc-test/Cargo.toml",
"--test=style",
"--",
"--nocapture",
]
)
try:
run(["shellcheck", "--version"])
except sp.CalledProcessError:
eprint("ERROR: shellcheck not found")
exit(1)
for file in iglob("**/*.sh", recursive=True):
run(["shellcheck", file])
def fmt_one(fpath: Path, check_only: bool):
eprint(f"Formatting {fpath}")
text = fpath.read_text()
text = re.sub(r"(?!macro_rules)\b(\w+)!\s*\{", r"fn \1_fmt_tmp() {", text)
text = re.sub(r"if #\[cfg\((.*?)\)\]", r"if cfg_tmp!([\1])", text, flags=re.DOTALL)
text = re.sub(r"enum #anon\b", r"enum _fmt_anon", text)
def enum_sub(m: re.Match) -> str:
enum_body = m.group(0)
rep = re.sub(
r"^(.*)\b(pub\s*?(\(.*?\))?)\s*",
r"\1/* FMT-VIS \2 END-FMT-VIS */\n\1",
enum_body,
flags=re.MULTILINE,
)
return rep
text = re.sub(r"\benum.*\{\n?(?:\s*[^}]*\n)+\s*\}", enum_sub, text)
cmd = ["rustfmt", "--config-path=.rustfmt.toml"]
if check_only:
res = check_output(cmd + ["--check"], input=text)
if len(res) == 0:
return
eprint(f"ERROR: File {fpath} is not properly formatted")
print(res)
exit(1)
else:
text = check_output(cmd, input=text)
text = re.sub(r"fn (\w+)_fmt_tmp\(\)", r"\1!", text)
text = re.sub(r"cfg_tmp!\(\[(.*?)\]\)", r"#[cfg(\1)]", text, flags=re.DOTALL)
text = re.sub(r"enum _fmt_anon", r"enum #anon", text)
text = re.sub(r"/\* FMT-VIS (.*) END-FMT-VIS \*/\n\s*", r"\1 ", text)
fpath.write_text(text)
def check_semver_file(fpath: Path):
if "TODO" in str(fpath):
eprint(f"Skipping semver file {fpath}")
return
eprint(f"Checking semver file {fpath}")
text = fpath.read_text()
lines = text.splitlines()
sort = sorted(lines)
if lines != sort:
eprint(f"ERROR: Unsorted semver file {fpath}")
eprint("\n".join(unified_diff(lines, sort, lineterm="")))
exit(1)
duplicates = []
seen = set()
for line in lines:
if line in seen:
duplicates.append(line)
seen.add(line)
if len(duplicates) > 0:
eprint(f"ERROR: Duplicates in semver file {fpath}")
eprint(duplicates)
exit(1)
def check_output(args: list[str], **kw) -> str:
xtrace(args)
return sp.check_output(args, encoding="utf8", text=True, **kw)
def run(args: list[str], **kw) -> sp.CompletedProcess:
xtrace(args)
return sp.run(args, check=True, text=True, **kw)
def xtrace(args: list[str]):
astr = " ".join(args)
eprint(f"+ {astr}")
def eprint(*args, **kw):
print(*args, file=sys.stderr, **kw)
if __name__ == "__main__":
main()