681aa9e237
Keep DEB/Docker publish keys out of the project's .env. Scripts and check.py --ready only read .env.builder; empty values count as missing.
542 lines
21 KiB
Python
Executable File
542 lines
21 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Executable form of the builder contract (references/contract.md).
|
||
|
||
Checks a project's makefile.builder against the contract by probing make
|
||
itself with dry runs (`make -f makefile.builder -n`) instead of parsing
|
||
makefile text: includes, conditionals, and variable expansion are resolved
|
||
by make, so behavior is what gets judged. The default Makefile/makefile is
|
||
not read.
|
||
|
||
Usage:
|
||
python3 -I -S check.py <project-dir> [--build] [--ready]
|
||
|
||
Exit codes: 0 = all PASS, 1 = at least one FAIL, 2 = usage/environment error.
|
||
`--ready` 额外检查轨道工具链和发布环境变量键名(只看键是否存在,永不打印值)。
|
||
无 makefile.builder 时普通模式退出 2;`--ready` 输出结构化 FAIL 并继续工具链/发布键检查。
|
||
|
||
Change the contract here first, then mirror the change into contract.md.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import hashlib
|
||
import os
|
||
import re
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ARCH_VALUES = ("amd64", "arm64")
|
||
REQUIRED_TARGETS = ("help", "version", "clean", "build")
|
||
UPLOAD_TOKENS = (
|
||
"curl ", "curl\t", "scp ", "rsync ", "aptly ", "reprepro ",
|
||
"docker push", "buildx build --push", "buildx --push", "upload_deb.sh",
|
||
"publish_docker.sh",
|
||
)
|
||
SECRET_PATTERNS = (
|
||
re.compile(r"(TOKEN|PASSWORD|SECRET|API_KEY|PASSWD)[A-Z_]*\s*[:?]?=\s*['\"]?[^\s$({\"']+", re.IGNORECASE),
|
||
re.compile(r"\b[A-Za-z0-9_]*token[A-Za-z0-9_]*\s*[:?]?=\s*['\"]?[A-Za-z0-9._\-]{16,}", re.IGNORECASE),
|
||
)
|
||
FLOATING_TAGS = (":latest", ":stable")
|
||
DEB_SHAPE = re.compile(r"^[^_\s]+_[^_\s]+_[^_\s]+\.deb$")
|
||
VALID_SCRIPT_NAMES = ("upload_deb.sh", "publish_docker.sh")
|
||
BUILDER_MAKEFILE = "makefile.builder"
|
||
BUILDER_ENV = ".env.builder"
|
||
|
||
PASS = "PASS"
|
||
FAIL = "FAIL"
|
||
SKIP = "SKIP"
|
||
|
||
|
||
class Report:
|
||
def __init__(self) -> None:
|
||
self.failures = 0
|
||
self.skips = 0
|
||
|
||
def add(self, status: str, number: int, title: str, detail: str) -> None:
|
||
print(f"[{status}] {number}. {title}")
|
||
for line in detail.splitlines():
|
||
print(f" {line}")
|
||
if status == FAIL:
|
||
self.failures += 1
|
||
elif status == SKIP:
|
||
self.skips += 0 if self.skips else 1
|
||
|
||
|
||
def run_make(
|
||
project: Path, *args: str, timeout: int = 60, dry_run: bool = True
|
||
) -> subprocess.CompletedProcess[str]:
|
||
cmd = ["make", "-C", str(project), "-f", BUILDER_MAKEFILE]
|
||
if dry_run:
|
||
cmd.append("-n")
|
||
cmd.extend(args)
|
||
return subprocess.run(
|
||
cmd, capture_output=True, text=True, timeout=timeout, check=False
|
||
)
|
||
|
||
|
||
def has_no_rule(result: subprocess.CompletedProcess[str]) -> bool:
|
||
return result.returncode != 0 and (
|
||
"No rule to make target" in result.stderr or "no rule to make target" in result.stderr.lower()
|
||
)
|
||
|
||
|
||
BANNER_RE = re.compile(r"^make(?:\[[0-9]+\])?: (进入|离开|Entering|Leaving)")
|
||
|
||
|
||
def clean_make_output(result: subprocess.CompletedProcess[str]) -> list[str]:
|
||
"""Drop make directory banners and dry-run command echoes, keep real output."""
|
||
lines = []
|
||
for line in result.stdout.splitlines():
|
||
if BANNER_RE.match(line.strip()):
|
||
continue
|
||
stripped = line.lstrip()
|
||
if stripped.startswith(("echo ", "echo\t", "printf ")):
|
||
continue
|
||
lines.append(line)
|
||
return lines
|
||
|
||
|
||
def check_required_targets(report: Report, project: Path) -> dict[str, bool]:
|
||
present: dict[str, bool] = {}
|
||
lines = []
|
||
for target in REQUIRED_TARGETS:
|
||
result = run_make(project, target)
|
||
ok = result.returncode == 0
|
||
present[target] = ok
|
||
lines.append(f"{target}: {'found' if ok else 'missing'}")
|
||
report.add(PASS if all(present.values()) else FAIL, 1, "必备目标存在(help/version/clean/build)", "\n".join(lines))
|
||
return present
|
||
|
||
|
||
def check_arch_guard(report: Report, project: Path) -> None:
|
||
bad = run_make(project, "build", "ARCH=loongarch")
|
||
guard_ok = bad.returncode != 0 and ("amd64" in bad.stderr or "arm64" in bad.stderr)
|
||
default_ok = run_make(project, "build").returncode == 0
|
||
lines = [
|
||
f"invalid ARCH rejected: {'yes' if guard_ok else 'NO'}",
|
||
f"default ARCH works: {'yes' if default_ok else 'no'}",
|
||
]
|
||
hint = "" if guard_ok else "\n Hint: add `$(error ARCH must be amd64 or arm64)` guarded by an ifneq filter."
|
||
if guard_ok and default_ok:
|
||
report.add(PASS, 2, "ARCH 守卫与缺省值", "\n".join(lines + hint.splitlines()))
|
||
else:
|
||
report.add(FAIL, 2, "ARCH 守卫与缺省值", "\n".join(lines) + hint)
|
||
|
||
|
||
def check_version_output(report: Report, project: Path) -> None:
|
||
result = run_make(project, "version")
|
||
# Dry run: the echoed `@echo <version>` line IS the would-be output.
|
||
out_lines = [ln.lstrip()[5:] for ln in result.stdout.splitlines() if ln.lstrip().startswith("echo ")]
|
||
out = "\n".join(out_lines).strip()
|
||
single = len(out.splitlines()) == 1 and out != ""
|
||
no_v = single and not out.startswith("v")
|
||
detail = f"stdout={out!r}"
|
||
if single and not no_v:
|
||
detail += "\ncanonical version must not start with 'v'"
|
||
report.add(
|
||
PASS if no_v else FAIL,
|
||
3,
|
||
"version 输出一行非空规范版本(无 v 前缀)",
|
||
detail,
|
||
)
|
||
|
||
|
||
def check_build_has_no_upload(report: Report, project: Path) -> None:
|
||
result = run_make(project, "build")
|
||
text = chr(10).join(clean_make_output(result))
|
||
hits = [token for token in UPLOAD_TOKENS if token in text]
|
||
report.add(
|
||
PASS if not hits else FAIL,
|
||
4,
|
||
"build 不含上传动作",
|
||
"clean" if not hits else "found upload commands in build recipe:\n " + ", ".join(hits),
|
||
)
|
||
|
||
|
||
def detect_deb_project(recipe_all: str, project: Path) -> bool:
|
||
return ".deb" in recipe_all or "dpkg-deb" in recipe_all or "debuild" in recipe_all or any(project.glob("debian/*"))
|
||
|
||
|
||
def check_deb_recipe(report: Report, project: Path, built_deb: Path | None) -> None:
|
||
dry = run_make(project, "deb")
|
||
text = chr(10).join(clean_make_output(dry))
|
||
problems = []
|
||
if dry.returncode != 0:
|
||
problems.append(f"`make -n deb` failed: {dry.stderr.strip() or 'unknown error'}")
|
||
else:
|
||
if "dist/" not in text and "$(DIST_DIR)" not in text:
|
||
problems.append("recipe does not reference dist/ ($(DIST_DIR)) as artifact location")
|
||
hits = [token for token in UPLOAD_TOKENS if token in text]
|
||
if hits:
|
||
problems.append("recipe contains upload commands: " + ", ".join(hits))
|
||
if "rm -rf /" in text or "rm -rf ~" in text:
|
||
problems.append("recipe contains unrestricted rm -rf")
|
||
if built_deb is not None:
|
||
shape_ok = DEB_SHAPE.match(built_deb.name) is not None
|
||
if not shape_ok:
|
||
problems.append(f"artifact name does not match <name>_<version>_<arch>.deb: {built_deb.name}")
|
||
dpkg = shutil.which("dpkg-deb")
|
||
if dpkg:
|
||
info = subprocess.run([dpkg, "--field", str(built_deb), "Package"], capture_output=True, text=True, check=False)
|
||
if info.returncode != 0 or not info.stdout.strip():
|
||
problems.append(f"dpkg-deb --info failed on {built_deb.name}")
|
||
else:
|
||
problems.append("dpkg-deb unavailable; metadata not verified (--build)")
|
||
if problems:
|
||
report.add(FAIL, 5, "deb 目标产物形状与纯构建", "\n".join(problems))
|
||
else:
|
||
extra = f"\nartifact: {built_deb.name}" if built_deb else "\n(static recipe check only; run --build to verify real artifact)"
|
||
report.add(PASS, 5, "deb 目标产物形状与纯构建", extra.lstrip("\n"))
|
||
|
||
|
||
def detect_docker_project(project: Path) -> bool:
|
||
return (project / "Dockerfile").exists() or (project / "docker-compose.yaml").exists()
|
||
|
||
|
||
def check_docker_recipe(report: Report, project: Path) -> None:
|
||
dry = run_make(project, "docker")
|
||
text = chr(10).join(clean_make_output(dry))
|
||
if has_no_rule(dry):
|
||
report.add(SKIP, 6, "docker 目标为本地单平台构建", "(no docker target)")
|
||
return
|
||
problems = []
|
||
if "--push" in text or " docker push" in text or "docker push\n" in text:
|
||
problems.append("make docker must be local-only; pushing belongs to publish_docker.sh")
|
||
if "--platform" in text and "," in text.split("--platform")[1][:80].split()[0]:
|
||
problems.append("make docker must stay single-platform; multi-platform belongs to publish_docker.sh")
|
||
report.add(FAIL if problems else PASS, 6, "docker 目标为本地单平台构建", "\n".join(problems) or "local single-platform build")
|
||
|
||
|
||
SCRIPT_RESOLVE_SNIPPETS = tuple(
|
||
f"{prefix}{name}"
|
||
for prefix in (
|
||
"$$BUILDER_SKILL_DIR",
|
||
"$BUILDER_SKILL_DIR",
|
||
"$$HOME/.pouch/skills/builder/scripts",
|
||
"$HOME/.pouch/skills/builder/scripts",
|
||
"~/.pouch/skills/builder/scripts",
|
||
"$$HOME/.skills/skills/builder/scripts",
|
||
"$HOME/.skills/skills/builder/scripts",
|
||
"~/.skills/skills/builder/scripts",
|
||
)
|
||
for name in VALID_SCRIPT_NAMES
|
||
)
|
||
|
||
|
||
def check_push_delegates(report: Report, project: Path, dual_artifact: bool) -> None:
|
||
targets = ("push-deb", "push-docker") if dual_artifact else ("push",)
|
||
missing = []
|
||
inline = []
|
||
thin = []
|
||
for target in targets:
|
||
dry = run_make(project, target)
|
||
if has_no_rule(dry):
|
||
missing.append(target)
|
||
continue
|
||
text = chr(10).join(clean_make_output(dry))
|
||
bad_tokens = [token for token in ("curl ", "scp ", "aptly ", "reprepro ") if token in text]
|
||
if bad_tokens:
|
||
inline.append(f"{target}: inline upload command ({', '.join(bad_tokens)})")
|
||
elif not any(snippet in text for snippet in SCRIPT_RESOLVE_SNIPPETS) \
|
||
and "$(BUILDER_SCRIPT)" not in text and "upload_deb.sh" not in text \
|
||
and "publish_docker.sh" not in text:
|
||
inline.append(f"{target}: does not call a builder script (expected $BUILDER_SKILL_DIR/... or ~/.pouch/... path)")
|
||
else:
|
||
thin.append(target)
|
||
problems = []
|
||
if missing:
|
||
problems.append("missing targets: " + ", ".join(missing))
|
||
problems.extend(inline)
|
||
status = PASS if not problems else FAIL
|
||
detail = "\n".join(problems) if problems else "thin wrappers: " + ", ".join(thin)
|
||
report.add(status, 7, "push 仅委托 builder 脚本(薄包装)", detail)
|
||
|
||
|
||
def check_secrets_and_tags(report: Report, project: Path) -> None:
|
||
makefile = project / BUILDER_MAKEFILE
|
||
problems = []
|
||
files = [makefile]
|
||
if makefile.exists():
|
||
for match in re.finditer(r"^include\s+(.+)$", makefile.read_text(encoding="utf-8"), re.MULTILINE):
|
||
inc = (project / match.group(1).strip()).resolve()
|
||
if inc.is_file():
|
||
files.append(inc)
|
||
for file in files:
|
||
text = file.read_text(encoding="utf-8")
|
||
rel = file.relative_to(project) if file.is_relative_to(project) else file
|
||
for pattern in SECRET_PATTERNS:
|
||
for hit in pattern.finditer(text):
|
||
problems.append(f"{rel}: possible hardcoded secret near `{hit.group(0)[:40]}...`")
|
||
for tag in FLOATING_TAGS:
|
||
for line in text.splitlines():
|
||
stripped = line.split("#", 1)[0]
|
||
if tag in stripped:
|
||
problems.append(f"{rel}: implicit floating tag `{tag}` in: {stripped.strip()[:70]}")
|
||
report.add(FAIL if problems else PASS, 8, "无内联机密、无隐式 latest/stable", "\n".join(problems) or "clean")
|
||
|
||
|
||
DEB_ENV_KEYS = ("DEB_SERVER_URL", "DEB_TOKEN", "DEB_REPOSITORY")
|
||
DOCKER_ENV_KEYS = ("DOCKER_REGISTRY",)
|
||
ENV_KEY_LINE = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)=(.*)$")
|
||
|
||
|
||
def env_file_keys(project: Path) -> set[str]:
|
||
"""Return nonempty key names in `.env.builder`. Never return or print values."""
|
||
path = project / BUILDER_ENV
|
||
keys: set[str] = set()
|
||
if not path.is_file():
|
||
return keys
|
||
try:
|
||
text = path.read_text(encoding="utf-8")
|
||
except OSError:
|
||
return keys
|
||
for raw in text.splitlines():
|
||
stripped = raw.strip()
|
||
if not stripped or stripped.startswith("#"):
|
||
continue
|
||
match = ENV_KEY_LINE.match(stripped)
|
||
if not match:
|
||
continue
|
||
value = match.group(2).strip().strip("'\"")
|
||
if value:
|
||
keys.add(match.group(1))
|
||
return keys
|
||
|
||
|
||
def key_present(key: str, env_keys: set[str]) -> bool:
|
||
return bool(os.environ.get(key)) or key in env_keys
|
||
|
||
|
||
def check_ready_toolchain(
|
||
report: Report, *, deb_project: bool, docker_project: bool
|
||
) -> None:
|
||
lines = [f"make: {'found' if shutil.which('make') else 'MISSING'}"]
|
||
problems = []
|
||
if docker_project:
|
||
docker_ok = shutil.which("docker") is not None
|
||
lines.append(f"docker: {'found' if docker_ok else 'MISSING (blocks docker track)'}")
|
||
if not docker_ok:
|
||
problems.append("install docker to build/publish images")
|
||
else:
|
||
lines.append("docker: skipped (no docker track)")
|
||
if deb_project:
|
||
dpkg_ok = shutil.which("dpkg-deb") is not None
|
||
lines.append(
|
||
f"dpkg-deb: {'found' if dpkg_ok else 'MISSING (blocks make deb / --build)'}"
|
||
)
|
||
if not dpkg_ok:
|
||
problems.append("install dpkg-dev (or equivalent) to build .deb packages")
|
||
else:
|
||
lines.append("dpkg-deb: skipped (no deb track)")
|
||
report.add(
|
||
FAIL if problems else PASS,
|
||
10,
|
||
"轨道工具链",
|
||
"\n".join(lines + ([""] + problems if problems else [])),
|
||
)
|
||
|
||
|
||
def check_ready_env_keys(
|
||
report: Report, project: Path, *, deb_project: bool, docker_project: bool
|
||
) -> None:
|
||
env_keys = env_file_keys(project)
|
||
lines = []
|
||
missing: list[str] = []
|
||
if not deb_project and not docker_project:
|
||
report.add(SKIP, 11, "发布环境变量键名(不读取值)", "no deb/docker track")
|
||
return
|
||
if deb_project:
|
||
for key in DEB_ENV_KEYS:
|
||
found = key_present(key, env_keys)
|
||
lines.append(f"{key}: {'present' if found else 'MISSING'}")
|
||
if not found:
|
||
missing.append(key)
|
||
else:
|
||
lines.append("DEB_*: skipped (no deb track)")
|
||
if docker_project:
|
||
for key in DOCKER_ENV_KEYS:
|
||
found = key_present(key, env_keys)
|
||
lines.append(f"{key}: {'present' if found else 'MISSING'}")
|
||
if not found:
|
||
missing.append(key)
|
||
else:
|
||
lines.append("DOCKER_*: skipped (no docker track)")
|
||
if missing:
|
||
lines.extend(
|
||
[
|
||
"",
|
||
f"blocks publish, not build. Put keys in the environment or {BUILDER_ENV}:",
|
||
*[f" {key}=" for key in missing],
|
||
f"Do not commit {BUILDER_ENV}. Do not put these keys in `.env`. Never print values.",
|
||
]
|
||
)
|
||
report.add(SKIP, 11, "发布环境变量键名(不读取值)", "\n".join(lines))
|
||
return
|
||
report.add(PASS, 11, "发布环境变量键名(不读取值)", "\n".join(lines))
|
||
|
||
|
||
def check_script_paths(report: Report) -> None:
|
||
candidates = []
|
||
env_dir = os.environ.get("BUILDER_SKILL_DIR")
|
||
if env_dir:
|
||
candidates.append(Path(env_dir) / "scripts")
|
||
home = Path(os.environ.get("HOME", ""))
|
||
candidates.append(home / ".pouch" / "skills" / "builder" / "scripts")
|
||
candidates.append(home / ".skills" / "skills" / "builder" / "scripts")
|
||
found = next((c for c in candidates if c.is_dir() and any((c / n).is_file() for n in VALID_SCRIPT_NAMES)), None)
|
||
if found:
|
||
report.add(PASS, 9, "builder 脚本路径可达", str(found))
|
||
else:
|
||
report.add(FAIL, 9, "builder 脚本路径可达", "\n".join([
|
||
"none of these resolve to scripts/upload_deb.sh:",
|
||
*(f" {c}" for c in candidates),
|
||
"Fix: set BUILDER_SKILL_DIR, or clone the pouch repo to ~/.pouch.",
|
||
]))
|
||
|
||
|
||
def build_project(project: Path) -> Path | None:
|
||
"""Run `make -f makefile.builder deb` for real and return the produced .deb, or None."""
|
||
result = run_make(project, "deb", timeout=1800, dry_run=False)
|
||
if result.returncode != 0:
|
||
print(
|
||
f"--build: `make -f {BUILDER_MAKEFILE} deb` failed:\n{result.stderr[-2000:]}",
|
||
file=sys.stderr,
|
||
)
|
||
return None
|
||
debs = sorted((p for p in (project / "dist").glob("*.deb") if p.is_file()), key=lambda p: p.stat().st_mtime, reverse=True)
|
||
return debs[0] if debs else None
|
||
|
||
|
||
MAKEFILE_HINT = (
|
||
f"Fix: copy <builder-skill>/templates/{BUILDER_MAKEFILE} to the project "
|
||
f"root as {BUILDER_MAKEFILE}. Do not put builder targets in Makefile or "
|
||
"makefile. Keep help/build/clean/version, include builder "
|
||
"scripts/version.mk, and enable deb/docker/push* for the tracks this "
|
||
"project actually uses. Then re-run check.py. Do not use create-makefile."
|
||
)
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
parser.add_argument(
|
||
"project",
|
||
type=Path,
|
||
help="project directory containing makefile.builder",
|
||
)
|
||
parser.add_argument(
|
||
"--build",
|
||
action="store_true",
|
||
help="actually run `make -f makefile.builder deb` and verify the artifact",
|
||
)
|
||
parser.add_argument(
|
||
"--ready",
|
||
action="store_true",
|
||
help="also check toolchain and publish env key names (init/check mode)",
|
||
)
|
||
args = parser.parse_args(argv)
|
||
|
||
project = args.project.resolve()
|
||
makefile = project / BUILDER_MAKEFILE
|
||
if not makefile.is_file():
|
||
if not args.ready:
|
||
print(f"Error: no {BUILDER_MAKEFILE} in {project}", file=sys.stderr)
|
||
return 2
|
||
report = Report()
|
||
report.add(
|
||
FAIL,
|
||
1,
|
||
f"{BUILDER_MAKEFILE} 存在",
|
||
f"no {BUILDER_MAKEFILE} in {project}\n{MAKEFILE_HINT}",
|
||
)
|
||
skip_detail = f"(no {BUILDER_MAKEFILE})"
|
||
for number, title, detail in (
|
||
(2, "ARCH 守卫与缺省值", skip_detail),
|
||
(3, "version 输出一行非空规范版本(无 v 前缀)", skip_detail),
|
||
(4, "build 不含上传动作", skip_detail),
|
||
(5, "deb 目标产物形状与纯构建", skip_detail),
|
||
(6, "docker 目标为本地单平台构建", skip_detail),
|
||
(7, "push 仅委托 builder 脚本(薄包装)", skip_detail),
|
||
(8, "无内联机密、无隐式 latest/stable", skip_detail),
|
||
(9, "builder 脚本路径可达", skip_detail),
|
||
):
|
||
report.add(SKIP, number, title, detail)
|
||
docker_project = detect_docker_project(project)
|
||
deb_project = any(project.glob("debian/*"))
|
||
check_ready_toolchain(report, deb_project=deb_project, docker_project=docker_project)
|
||
check_ready_env_keys(
|
||
report, project, deb_project=deb_project, docker_project=docker_project
|
||
)
|
||
print()
|
||
print(f"RESULT: FAILED ({report.failures} check(s) failed)")
|
||
return 1
|
||
if shutil.which("make") is None:
|
||
print("Error: make is required.", file=sys.stderr)
|
||
return 2
|
||
|
||
report = Report()
|
||
|
||
# Gather every recipe once via dry-running all known targets (best effort).
|
||
recipe_all_parts = []
|
||
for target in (*REQUIRED_TARGETS, "deb", "docker", "push", "push-deb", "push-docker"):
|
||
result = run_make(project, target)
|
||
if result.returncode == 0:
|
||
recipe_all_parts.append(result.stdout)
|
||
recipe_all = "\n".join(recipe_all_parts)
|
||
|
||
present = check_required_targets(report, project)
|
||
|
||
built_deb: Path | None = None
|
||
deb_project = detect_deb_project(recipe_all, project)
|
||
docker_project = detect_docker_project(project)
|
||
|
||
if present["build"]:
|
||
check_arch_guard(report, project)
|
||
check_version_output(report, project)
|
||
check_build_has_no_upload(report, project)
|
||
else:
|
||
report.add(SKIP, 2, "ARCH 守卫与缺省值", "(build target missing)")
|
||
report.add(SKIP, 3, "version 输出一行非空规范版本(无 v 前缀)", "(version target missing)")
|
||
report.add(SKIP, 4, "build 不含上传动作", "(build target missing)")
|
||
|
||
if deb_project:
|
||
if args.build:
|
||
print(f"--build: running `make -f {BUILDER_MAKEFILE} deb` ...")
|
||
built_deb = build_project(project)
|
||
if built_deb is None:
|
||
print("--build: no .deb produced; artifact checks degrade to recipe-only.", file=sys.stderr)
|
||
check_deb_recipe(report, project, built_deb)
|
||
else:
|
||
report.add(SKIP, 5, "deb 目标产物形状与纯构建", "(not a DEB project)")
|
||
|
||
if docker_project:
|
||
check_docker_recipe(report, project)
|
||
else:
|
||
report.add(SKIP, 6, "docker 目标为本地单平台构建", "(no Dockerfile)")
|
||
|
||
dual = deb_project and docker_project
|
||
check_push_delegates(report, project, dual)
|
||
check_secrets_and_tags(report, project)
|
||
check_script_paths(report)
|
||
if args.ready:
|
||
check_ready_toolchain(
|
||
report, deb_project=deb_project, docker_project=docker_project
|
||
)
|
||
check_ready_env_keys(
|
||
report, project, deb_project=deb_project, docker_project=docker_project
|
||
)
|
||
|
||
total_fail = report.failures
|
||
print()
|
||
if total_fail:
|
||
print(f"RESULT: FAILED ({total_fail} check(s) failed)")
|
||
return 1
|
||
print("RESULT: PASSED")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|