diff --git a/bump_packaging.py b/bump_packaging.py index 8d1e0c6..e2414bd 100644 --- a/bump_packaging.py +++ b/bump_packaging.py @@ -335,6 +335,50 @@ def bump_srcinfo() -> None: say("regenerated contrib/aur/.SRCINFO") +#: Names that may appear as `resource` blocks in the formula. A Python +#: dependency without one is silently skipped at install time because Homebrew +#: installs the sdist `--no-deps`, which surfaces as an ImportError on launch +#: rather than as a packaging error. +RESOURCE_LINE = re.compile(r'(?m)^ resource "([^"]+)" do$') + + +def pypi_name(name: str) -> str: + """PEP 503 name: lowercase, runs of -_. collapsed to a single hyphen.""" + return re.sub(r"[-_.]+", "-", name).lower() + + +def runtime_dependencies() -> list[str]: + """The distribution names in `pyproject.toml`'s `[project] dependencies`.""" + block = re.search(r"(?ms)^dependencies = \[(.*?)^\]$", PYPROJECT.read_text()) + if block is None: + raise fail("no dependencies array in pyproject.toml") + return re.findall(r'^\s*"([A-Za-z0-9._-]+)', block.group(1), re.MULTILINE) + + +def formula_excluded_resources() -> set[str]: + """Dependencies the formula deliberately leaves to other formulae.""" + match = re.search(r'pypi_packages .*exclude_packages: "([^"]*)"', FORMULA.read_text()) + if match is None: + return set() + return {pypi_name(name.strip()) for name in match.group(1).split(",") if name.strip()} + + +def check_formula_resources(problems: list[str]) -> None: + """Every runtime dependency needs a `resource`, unless another formula has it.""" + resources = {pypi_name(name) for name in RESOURCE_LINE.findall(FORMULA.read_text())} + if not resources: + problems.append("formula vendors no Python resources; it would install --no-deps") + excluded = formula_excluded_resources() + for dependency in runtime_dependencies(): + name = pypi_name(dependency) + if name in excluded or name in resources: + continue + problems.append( + f"formula has no resource for {name}" + " (run `brew update-python-resources Formula/b/beefy-player.rb` in the tap)" + ) + + def bump_formula(version: str, checksum: str) -> None: text = FORMULA.read_text() url = RELEASE_ASSET.format(version=version, name=ASSET_NAME) @@ -344,6 +388,8 @@ def bump_formula(version: str, checksum: str) -> None: ) # The default branch is master, and --HEAD is only useful if it resolves. text = text.replace('branch: "main"', 'branch: "master"') + # The `resource` blocks pin versions, not the release: they only go stale + # when pyproject.toml's dependencies change, which `--check` reports. FORMULA.write_text(text) say("wrote contrib/homebrew/Formula/b/beefy-player.rb") @@ -435,6 +481,7 @@ def check(version: str) -> int: problems.append('formula head branch is "main"; this repository uses master') if re.search(r'sha256 "0{64}"', formula): problems.append("formula still has the placeholder sha256") + check_formula_resources(problems) for problem in problems: say(f"drift: {problem}")