mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
`shfmt.ensure()` returned as soon as any `shfmt` was on PATH and tried the distribution package before the release pinned in roles/sys-lint/meta/services.yml. The Debian dev container therefore formats with its packaged 3.8.0 (/usr/bin/shfmt), Fedora with its packaged 3.7.0, the CentOS one with the pinned v3.14.1 (/usr/local/bin/shfmt), and they disagree on scripts/tests/deploy/swarm/utils/unmount/nfs_mounts.sh: every gate in a CentOS stack moved the `then` of the heredoc `if` below the terminator. ensure() now follows ruff's installer: it reads `shfmt --version`, keeps the binary only when it reports the pinned release, and otherwise installs the release binary into /usr/local/bin. It fails when PATH still resolves another version afterwards instead of formatting with it. The system package path is gone. nfs_mounts.sh is committed in the pinned release's format (byte-identical to the v3.14.1 output from the CentOS container), so the first gate on any distro leaves the tree clean. The install-lint stamp now also depends on roles/sys-lint/meta/services.yml, the pin file of actionlint, hadolint and shfmt, so a bumped pin reaches environments whose stamp predates it; until now only pyproject.toml and scripts/install/lint.sh invalidated it. Fixes #1155 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
253 lines
5.9 KiB
Python
253 lines
5.9 KiB
Python
"""CLI entry point for ``python -m utils.install.lint``.
|
|
|
|
Mirrors the prior ``scripts/install/lint.sh`` interface: no-arg / ``all``
|
|
runs every group + manages the stamp file; explicit groups bypass the
|
|
stamp; ``--force`` (as first arg) drops the stamp and mmdc's browser cache
|
|
and reinstalls.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import hashlib
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from utils.cache import PROJECT_ROOT
|
|
from utils.install.lint import (
|
|
actionlint,
|
|
eslint,
|
|
hadolint,
|
|
markdownlint_cli2,
|
|
mbake,
|
|
mermaid_cli,
|
|
packages,
|
|
php,
|
|
playwright,
|
|
ruby,
|
|
ruff,
|
|
shellcheck,
|
|
shfmt,
|
|
sqlfluff,
|
|
stylelint,
|
|
)
|
|
from utils.install.lint.ansible import collections as ansible_collections
|
|
from utils.install.lint.ansible import commands as ansible_commands
|
|
from utils.install.lint.ansible import lint as ansible_lint
|
|
from utils.install.primitives import warn
|
|
from utils.roles.mapping import ROLE_FILE_META_SERVICES
|
|
|
|
_STAMP_KEY = hashlib.sha256(sys.executable.encode("utf-8")).hexdigest()[:8]
|
|
_STAMP = f"build/install-lint-{_STAMP_KEY}.stamp"
|
|
_PINS = f"roles/sys-lint/{ROLE_FILE_META_SERVICES}"
|
|
_STAMP_DEPS = (
|
|
"scripts/install/lint.sh",
|
|
"pyproject.toml",
|
|
_PINS,
|
|
)
|
|
|
|
_STAMP_TOOLS = (
|
|
"actionlint",
|
|
"ansible-lint",
|
|
"ansible-playbook",
|
|
"eslint",
|
|
"hadolint",
|
|
"markdownlint-cli2",
|
|
"mbake",
|
|
"php",
|
|
"ruby",
|
|
"ruff",
|
|
"shellcheck",
|
|
"shfmt",
|
|
)
|
|
|
|
|
|
def _install_action_tools() -> None:
|
|
actionlint.ensure()
|
|
|
|
|
|
def _install_ansible_tools() -> None:
|
|
ansible_commands.ensure()
|
|
ansible_collections.ensure()
|
|
ansible_lint.ensure()
|
|
|
|
|
|
def _install_python_tools() -> None:
|
|
shfmt.ensure()
|
|
ruff.ensure()
|
|
|
|
|
|
def _install_php_tools() -> None:
|
|
php.ensure()
|
|
|
|
|
|
def _install_ruby_tools() -> None:
|
|
ruby.ensure()
|
|
|
|
|
|
def _install_shellcheck_tools() -> None:
|
|
shellcheck.ensure()
|
|
|
|
|
|
def _install_dockerfile_tools() -> None:
|
|
hadolint.ensure()
|
|
|
|
|
|
def _install_markdown_tools() -> None:
|
|
markdownlint_cli2.ensure()
|
|
|
|
|
|
def _install_mermaid_tools() -> None:
|
|
mermaid_cli.ensure()
|
|
|
|
|
|
def _install_makefile_tools() -> None:
|
|
mbake.ensure()
|
|
|
|
|
|
def _install_javascript_tools() -> None:
|
|
eslint.ensure()
|
|
|
|
|
|
def _install_playwright_tools() -> None:
|
|
playwright.ensure()
|
|
|
|
|
|
def _install_packages_tools() -> None:
|
|
packages.ensure()
|
|
|
|
|
|
def _install_php_tools() -> None:
|
|
php.ensure()
|
|
|
|
|
|
def _install_ruby_tools() -> None:
|
|
ruby.ensure()
|
|
|
|
|
|
def _install_sql_tools() -> None:
|
|
sqlfluff.ensure()
|
|
|
|
|
|
def _install_css_tools() -> None:
|
|
stylelint.ensure()
|
|
|
|
|
|
_GROUP_FN_NAMES = {
|
|
"action": "_install_action_tools",
|
|
"ansible": "_install_ansible_tools",
|
|
"python": "_install_python_tools",
|
|
"php": "_install_php_tools",
|
|
"ruby": "_install_ruby_tools",
|
|
"shellcheck": "_install_shellcheck_tools",
|
|
"dockerfile": "_install_dockerfile_tools",
|
|
"markdown": "_install_markdown_tools",
|
|
"mermaid": "_install_mermaid_tools",
|
|
"makefile": "_install_makefile_tools",
|
|
"javascript": "_install_javascript_tools",
|
|
"playwright": "_install_playwright_tools",
|
|
"packages": "_install_packages_tools",
|
|
"sql": "_install_sql_tools",
|
|
"css": "_install_css_tools",
|
|
}
|
|
|
|
|
|
def _install_all() -> None:
|
|
_install_action_tools()
|
|
_install_ansible_tools()
|
|
_install_python_tools()
|
|
_install_php_tools()
|
|
_install_ruby_tools()
|
|
_install_shellcheck_tools()
|
|
_install_dockerfile_tools()
|
|
_install_markdown_tools()
|
|
_install_mermaid_tools()
|
|
_install_makefile_tools()
|
|
_install_javascript_tools()
|
|
_install_playwright_tools()
|
|
|
|
|
|
def _stamp_is_fresh(repo_root: Path) -> bool:
|
|
stamp_path = repo_root / _STAMP
|
|
if not stamp_path.is_file():
|
|
return False
|
|
stamp_mtime = stamp_path.stat().st_mtime
|
|
for dep in _STAMP_DEPS:
|
|
dep_path = repo_root / dep
|
|
if not dep_path.is_file():
|
|
warn(f"[install-lint] missing dependency: {dep}")
|
|
return False
|
|
if dep_path.stat().st_mtime > stamp_mtime:
|
|
return False
|
|
return (
|
|
all(shutil.which(tool) is not None for tool in _STAMP_TOOLS)
|
|
and mermaid_cli.browser_installed()
|
|
)
|
|
|
|
|
|
def _touch_stamp(repo_root: Path) -> None:
|
|
stamp_path = repo_root / _STAMP
|
|
stamp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with contextlib.suppress(PermissionError):
|
|
stamp_path.parent.chmod(0o777)
|
|
stamp_path.touch()
|
|
|
|
|
|
def _dispatch(group: str) -> None:
|
|
if group == "all":
|
|
_install_all()
|
|
return
|
|
fn_name = _GROUP_FN_NAMES.get(group)
|
|
if fn_name is None:
|
|
raise RuntimeError(
|
|
"Usage: python -m utils.install.lint "
|
|
"[all|action|ansible|python|shellcheck|dockerfile|markdown|mermaid|makefile|"
|
|
"javascript|playwright|packages|php|ruby|sql|css]..."
|
|
)
|
|
globals()[fn_name]()
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = list(sys.argv[1:] if argv is None else argv)
|
|
repo_root = Path(PROJECT_ROOT)
|
|
|
|
force = False
|
|
if args and args[0] == "--force":
|
|
force = True
|
|
stamp_path = repo_root / _STAMP
|
|
if stamp_path.exists():
|
|
stamp_path.unlink()
|
|
shutil.rmtree(mermaid_cli.browser_cache(), ignore_errors=True)
|
|
args = args[1:]
|
|
|
|
if not args:
|
|
args = ["all"]
|
|
|
|
is_all_mode = len(args) == 1 and args[0] == "all"
|
|
|
|
if is_all_mode and not force and _stamp_is_fresh(repo_root):
|
|
return 0
|
|
|
|
# chdir for installers that resolve relative paths (eslint's npm ci
|
|
previous_cwd = Path.cwd() if Path.cwd().exists() else None
|
|
os.chdir(repo_root)
|
|
try:
|
|
for group in args:
|
|
_dispatch(group)
|
|
except RuntimeError as exc:
|
|
warn(str(exc))
|
|
return 1
|
|
finally:
|
|
if previous_cwd is not None and previous_cwd.exists():
|
|
os.chdir(previous_cwd)
|
|
|
|
if is_all_mode:
|
|
_touch_stamp(repo_root)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|