from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
TEMPLATE_VERSION = 1
REPO = Path(__file__).resolve().parent.parent
FORMULA = REPO / "packaging" / "homebrew" / "retch.rb"
EXPECTED_URL = "https://github.com/l1a/retch/archive/refs/tags/v@VERSION@.tar.gz"
EXPECTED_SHA = "@SHA256@"
SHA256_RE = re.compile(r"\b[0-9a-fA-F]{64}\b")
VERSIONISH_RE = re.compile(r"\b[0-9]+\.[0-9]+\.[0-9]+\b")
class ParseError(Exception):
def _body(text: str) -> str:
start = text.find("class Retch")
if start < 0:
raise ParseError("no `class Retch` in the formula")
lines = [ln for ln in text[start:].splitlines() if not ln.lstrip().startswith("#")]
return "\n".join(lines)
def _field(body: str, name: str) -> str:
m = re.search(rf'^\s*{re.escape(name)}\s+"([^"]*)"', body, re.M)
if not m:
raise ParseError(f"no `{name}` field in the formula")
return m.group(1)
def check_template(formula_text: str) -> list[str]:
problems: list[str] = []
body = _body(formula_text)
try:
url = _field(body, "url")
sha = _field(body, "sha256")
except ParseError as exc:
return [str(exc)]
if url != EXPECTED_URL:
problems.append(
f"url is {url!r}, not {EXPECTED_URL!r} — this formula is a template; "
"scripts/render_packaging.py fills the version in at publish time"
)
if sha != EXPECTED_SHA:
problems.append(
f"sha256 is {sha!r}, not {EXPECTED_SHA!r} — a checksum cannot be computed "
"before its tag exists, which is why recording one here forced a post-tag "
"commit on every release"
)
for line in body.splitlines():
for regex, what in ((SHA256_RE, "a sha256 digest"), (VERSIONISH_RE, "a version number")):
m = regex.search(line)
if m:
problems.append(f"formula body records {what} ({m.group(0)}): {line.strip()!r}")
cargo_install = re.search(r'system\s+"cargo",\s*"install"([^\n]*)', body)
if not cargo_install:
problems.append("the formula no longer runs `cargo install`")
else:
args = cargo_install.group(1)
if "std_cargo_args" not in args:
problems.append(
"`cargo install` no longer uses *std_cargo_args — that is what supplies "
"--locked, and Cargo.lock is the only thing pinning resolution in a "
"network-enabled Homebrew build"
)
if '"--locked"' in args:
problems.append(
"`cargo install` passes an explicit --locked on top of std_cargo_args, "
"which already includes it — cargo rejects the duplicate with "
"\"the argument '--locked' cannot be used multiple times\""
)
if 'man1.install "docs/retch.1"' not in body:
problems.append(
"the formula no longer installs the committed docs/retch.1 — regenerating it "
"is how the AUR package shipped a page footed `$DATE` / `retch $pkgver`"
)
if "mandown" in body:
problems.append(
"the formula references mandown — the tarball already carries a correct man "
"page; regenerating it reintroduces the v0.7.0 footer defect"
)
return problems
_GOOD_FORMULA = '''\
# a comment mentioning mandown and v1.2.3 and
# 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef, none of which
# must trip a check -- comments are stripped before matching.
class Retch < Formula
desc "Fast, feature-rich system information fetcher"
homepage "https://github.com/l1a/retch"
url "https://github.com/l1a/retch/archive/refs/tags/v@VERSION@.tar.gz"
sha256 "@SHA256@"
license "GPL-3.0-or-later"
def install
system "cargo", "install", *std_cargo_args
man1.install "docs/retch.1"
end
end
'''
_REAL_SHA = "77ccf85843d24ac3216ab31d2584ff4a95869266c59ddb8bc83819425cfc2033"
def _self_test() -> int:
failures: list[str] = []
def check(name: str, cond: bool, detail: str = "") -> None:
if not cond:
failures.append(f"{name}: {detail}")
def expect_problem(label: str, text: str, needle: str) -> None:
probs = check_template(text)
check(label, any(needle in p for p in probs), f"got {probs}")
clean = check_template(_GOOD_FORMULA)
check("template fixture clean", clean == [], f"got {clean}")
live = Path(__file__).resolve().parent.parent / "packaging" / "homebrew" / "retch.rb"
if live.is_file():
live_problems = check_template(live.read_text(encoding="utf-8"))
check("live formula clean", live_problems == [], f"got {live_problems}")
expect_problem(
"pinned url detected",
_GOOD_FORMULA.replace("v@VERSION@.tar.gz", "v0.17.3.tar.gz"),
"url is",
)
expect_problem(
"pinned sha256 detected",
_GOOD_FORMULA.replace('sha256 "@SHA256@"', f'sha256 "{_REAL_SHA}"'),
"sha256 is",
)
expect_problem(
"branch tarball detected",
_GOOD_FORMULA.replace(
"https://github.com/l1a/retch/archive/refs/tags/v@VERSION@.tar.gz",
"https://github.com/l1a/retch/archive/refs/heads/main.tar.gz",
),
"url is",
)
expect_problem(
"smuggled digest detected",
_GOOD_FORMULA.replace(
' license "GPL-3.0-or-later"', f' license "GPL-3.0-or-later"\n version "{_REAL_SHA}"'
),
"records a sha256 digest",
)
expect_problem(
"smuggled version detected",
_GOOD_FORMULA.replace(' license "GPL-3.0-or-later"',
' license "GPL-3.0-or-later"\n version "0.17.3"'),
"records a version number",
)
expect_problem(
"dropped std_cargo_args detected",
_GOOD_FORMULA.replace('system "cargo", "install", *std_cargo_args',
'system "cargo", "install", "--root", prefix'),
"std_cargo_args",
)
expect_problem(
"duplicate --locked detected",
_GOOD_FORMULA.replace('system "cargo", "install", *std_cargo_args',
'system "cargo", "install", "--locked", *std_cargo_args'),
"multiple times",
)
expect_problem(
"no cargo install detected",
_GOOD_FORMULA.replace('system "cargo", "install", *std_cargo_args', "true"),
"cargo install",
)
expect_problem(
"regenerated man page detected",
_GOOD_FORMULA.replace('man1.install "docs/retch.1"', 'man1.install "retch.1"'),
"docs/retch.1",
)
expect_problem(
"live mandown reference detected",
_GOOD_FORMULA.replace('man1.install "docs/retch.1"',
'system "mandown", "docs/retch.1.md"'),
"mandown",
)
try:
_body("puts 1\n")
check("missing class raises", False, "_body accepted a file with no class Retch")
except ParseError:
pass
if failures:
for f in failures:
print(f" FAIL {f}", file=sys.stderr)
print(f"brew_check.py self-test FAILED ({len(failures)})", file=sys.stderr)
return 1
print(f"brew_check.py self-test passed (template v{TEMPLATE_VERSION})")
return 0
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--self-test", action="store_true", help="run the built-in assertions")
args = ap.parse_args()
if args.self_test:
return _self_test()
try:
problems = check_template(FORMULA.read_text(encoding="utf-8"))
except (ParseError, FileNotFoundError) as exc:
print(f"[brew-check] {exc}", file=sys.stderr)
return 1
if problems:
for p in problems:
print(f"[brew-check] {p}", file=sys.stderr)
print("[brew-check] the version and checksum are supplied by "
"scripts/render_packaging.py at publish time", file=sys.stderr)
return 1
print("[brew-check] retch.rb is a template (records no version, no checksum)")
return 0
if __name__ == "__main__":
sys.exit(main())