fix(builder): keep project script config in makefile
This commit is contained in:
@@ -48,9 +48,9 @@ pouch add builder -g # 全局
|
|||||||
多架构清单是 `ARCHES`,见下方「多架构」。
|
多架构清单是 `ARCHES`,见下方「多架构」。
|
||||||
2. 运行 `python3 -I -S <builder>/scripts/check.py . --ready` 直到构建项 PASS。
|
2. 运行 `python3 -I -S <builder>/scripts/check.py . --ready` 直到构建项 PASS。
|
||||||
缺发布键只挡住上传,不挡住构建。
|
缺发布键只挡住上传,不挡住构建。
|
||||||
3. 在项目根 `.env.builder` 配置发布环境变量(不要写进 `.env`)。APK/IPA 可配置
|
3. 在项目根 `.env.builder` 配置发布环境变量(不要写进 `.env`)。项目内 APK/IPA
|
||||||
`COPY_SCRIPT=.pouch/builder/publish-apk.sh`,它接收产物路径作为位置参数;未配置时使用
|
分发脚本的路径写在 `makefile.builder` 的 `COPY_SCRIPT`,脚本接收产物路径作为位置参数;
|
||||||
`COPY_DEST` 的本地目录或容器目录后备逻辑:
|
未配置项目脚本时使用 `COPY_DEST` 的本地目录或容器目录后备逻辑:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DEB_SERVER_URL=https://deb.example.com
|
DEB_SERVER_URL=https://deb.example.com
|
||||||
@@ -59,7 +59,7 @@ DEB_TOKEN=<token> # 只放 .env.builder 或密钥系统,不进 git
|
|||||||
DOCKER_REGISTRY=registry.example.com
|
DOCKER_REGISTRY=registry.example.com
|
||||||
COPY_DEST=file-server:/srv
|
COPY_DEST=file-server:/srv
|
||||||
COPY_AS=latest.apk
|
COPY_AS=latest.apk
|
||||||
# 或:COPY_SCRIPT=.pouch/builder/publish-apk.sh
|
# COPY_SCRIPT= # 仅外部/临时脚本覆盖;项目内脚本写 makefile.builder
|
||||||
```
|
```
|
||||||
|
|
||||||
4. 日常发布就是两条命令:
|
4. 日常发布就是两条命令:
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ description: >-
|
|||||||
submodule,submodule 是独立 builder 项目:它的 `makefile.builder`、`.env.builder` 和
|
submodule,submodule 是独立 builder 项目:它的 `makefile.builder`、`.env.builder` 和
|
||||||
`.pouch/builder/` 必须放在 submodule 内,父仓库只负责调用或固定 submodule commit。
|
`.pouch/builder/` 必须放在 submodule 内,父仓库只负责调用或固定 submodule commit。
|
||||||
|
|
||||||
|
配置来源原则:项目要构建什么、发布哪些轨道、使用哪个已提交的分发脚本,放在
|
||||||
|
`makefile.builder`;本机路径和工具链覆盖放在被忽略的 `.pouch/builder/local.mk`;凭据、
|
||||||
|
发布目标和本轮别名放在被忽略的 `.env.builder`。`COPY_SCRIPT` 若指向项目内
|
||||||
|
`.pouch/builder/` 的脚本,优先写入 `makefile.builder`,这样迁移项目时配置与脚本一起进入 Git。
|
||||||
|
|
||||||
开始时解析当前 `SKILL.md` 所在目录,记为 `<skill-dir>`。优先
|
开始时解析当前 `SKILL.md` 所在目录,记为 `<skill-dir>`。优先
|
||||||
`git rev-parse --show-toplevel` 解析项目根。
|
`git rev-parse --show-toplevel` 解析项目根。
|
||||||
|
|
||||||
@@ -52,7 +57,7 @@ DEB_TOKEN=
|
|||||||
DOCKER_REGISTRY=registry.example.com
|
DOCKER_REGISTRY=registry.example.com
|
||||||
COPY_DEST=file-server:/srv
|
COPY_DEST=file-server:/srv
|
||||||
COPY_AS=latest.apk
|
COPY_AS=latest.apk
|
||||||
# 或:COPY_SCRIPT=.pouch/builder/publish-apk.sh
|
# 外部/临时脚本才放这里;项目内脚本优先在 makefile.builder 设置 COPY_SCRIPT
|
||||||
```
|
```
|
||||||
|
|
||||||
## 检查
|
## 检查
|
||||||
@@ -67,5 +72,5 @@ COPY_AS=latest.apk
|
|||||||
2. 仅当用户明确要求发布、上传或提交时才上传。不猜测生产端点;不把 token 写入输出、文件、提交或回复;不用 `set -x` 跑含凭据的命令。可能覆盖同版本时先问。脏工作树默认拒绝发布;用户明确接受时设 `ALLOW_UNCOMMITTED=1` 并注明未提交修改。
|
2. 仅当用户明确要求发布、上传或提交时才上传。不猜测生产端点;不把 token 写入输出、文件、提交或回复;不用 `set -x` 跑含凭据的命令。可能覆盖同版本时先问。脏工作树默认拒绝发布;用户明确接受时设 `ALLOW_UNCOMMITTED=1` 并注明未提交修改。
|
||||||
3. 构建:用户指定了架构才传 `ARCH=`;否则不要传,让 makefile 按 `ARCHES` 展开(`make -s -f makefile.builder arches`)。`make -f makefile.builder build` 始终单架构;DEB 用 `make -f makefile.builder deb`。版本按契约从 Git 祖先稳定 tag 推导,不要调用 releaser,不要用全仓库最新 tag。仅获构建授权时不要走会自动上传的目标。脚本含 `rm -rf` 时先确认为受限构建目录。
|
3. 构建:用户指定了架构才传 `ARCH=`;否则不要传,让 makefile 按 `ARCHES` 展开(`make -s -f makefile.builder arches`)。`make -f makefile.builder build` 始终单架构;DEB 用 `make -f makefile.builder deb`。版本按契约从 Git 祖先稳定 tag 推导,不要调用 releaser,不要用全仓库最新 tag。仅获构建授权时不要走会自动上传的目标。脚本含 `rm -rf` 时先确认为受限构建目录。
|
||||||
4. DEB 上传前:对每个包 `verify_deb.sh <exact-package-path.deb> <version> <arch>`。多个包时不凭文件时间猜测。镜像轨道由 `publish_docker.sh` 自带远端 inspect。
|
4. DEB 上传前:对每个包 `verify_deb.sh <exact-package-path.deb> <version> <arch>`。多个包时不凭文件时间猜测。镜像轨道由 `publish_docker.sh` 自带远端 inspect。
|
||||||
5. 发布优先 `make -f makefile.builder push[-deb|-docker|-copy]`,或直接调 `upload_deb.sh` / `publish_docker.sh` / `copy_artifact.sh`。未指定 `ARCH=` 时 `push-deb` 上传清单里每一个 `.deb`。拷贝轨道至少配置 `COPY_DEST`(本地目录或 `container:/dir`)或 `COPY_SCRIPT`(推荐 `.pouch/builder/` 下的可执行脚本;同时配置时后者优先)。自定义脚本按 `FILE [FILE ...]` 接收产物路径,自行完成重命名、上传、远端校验和通知;相对路径按项目根解析,`BUILDER_PROJECT_ROOT` 提供项目根。`COPY_AS` 只由内置 `COPY_DEST` 路径处理。脚本缺环境变量时加载 `.env.builder`(shell 显式值优先),不读 `.env`。不要把 token 当命令行参数;脚本可随项目放在 `.pouch/builder/` 下。多平台镜像只能走 `publish_docker.sh`,不能拆进 make;不要把项目架构清单写进 `.env.builder`。`build` 里禁止 `docker cp`。
|
5. 发布优先 `make -f makefile.builder push[-deb|-docker|-copy]`,或直接调 `upload_deb.sh` / `publish_docker.sh` / `copy_artifact.sh`。未指定 `ARCH=` 时 `push-deb` 上传清单里每一个 `.deb`。拷贝轨道至少配置 `COPY_DEST`(本地目录或 `container:/dir`)或 `COPY_SCRIPT`(项目内脚本优先在 `makefile.builder` 设置;同时配置时命令行/环境值优先)。自定义脚本按 `FILE [FILE ...]` 接收产物路径,自行完成重命名、上传、远端校验和通知;相对路径按项目根解析,`BUILDER_PROJECT_ROOT` 提供项目根。`COPY_AS` 只由内置 `COPY_DEST` 路径处理。脚本缺环境变量时加载 `.env.builder`(shell 显式值优先),不读 `.env`。不要把 token 当命令行参数;脚本可随项目放在 `.pouch/builder/` 下。多平台镜像只能走 `publish_docker.sh`,不能拆进 make;不要把项目架构清单写进 `.env.builder`。`build` 里禁止 `docker cp`。
|
||||||
6. 发布成功不能只看「curl/push 已执行」。要有退出码、HTTP 成功或远端 digest;索引异步时报告「上传已接受,索引尚待更新」。最终回复给出包名/镜像引用、版本、架构、SHA-256 或 digest、源 commit、工作区状态和未完成项。
|
6. 发布成功不能只看「curl/push 已执行」。要有退出码、HTTP 成功或远端 digest;索引异步时报告「上传已接受,索引尚待更新」。最终回复给出包名/镜像引用、版本、架构、SHA-256 或 digest、源 commit、工作区状态和未完成项。
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ make -f makefile.builder <target>
|
|||||||
|
|
||||||
## 2. 变量
|
## 2. 变量
|
||||||
|
|
||||||
|
配置来源按变量的稳定性分层:`makefile.builder` 是项目构建与发布轨道的可迁移事实,
|
||||||
|
包括 `ARCHES`、产物命名、目标和项目内 `COPY_SCRIPT`;`.pouch/builder/local.mk` 是
|
||||||
|
不提交的机器工具链覆盖;`.env.builder` 是不提交的凭据、发布端点、目标目录和本轮别名。
|
||||||
|
命令行/环境变量可以覆盖前两者,但不应把项目契约依赖在本机环境上。
|
||||||
|
|
||||||
| 变量 | 默认 | 说明 |
|
| 变量 | 默认 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `ARCHES` | `amd64` | 本项目要发布的架构清单,空格分隔,子集只能是 `amd64` `arm64`。写在 `makefile.builder` 里并提交;不要放进 `.env.builder`。缺省或无 `arches` 目标时视为单架构 `amd64`。不要默认写成双架构 |
|
| `ARCHES` | `amd64` | 本项目要发布的架构清单,空格分隔,子集只能是 `amd64` `arm64`。写在 `makefile.builder` 里并提交;不要放进 `.env.builder`。缺省或无 `arches` 目标时视为单架构 `amd64`。不要默认写成双架构 |
|
||||||
@@ -145,14 +150,14 @@ include $(HOME)/.pouch/skills/builder/scripts/version.mk
|
|||||||
| 变量 | 必填 | 说明 |
|
| 变量 | 必填 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `COPY_DEST` | 与 `COPY_SCRIPT` 至少配置一个 | 内置后备目标:本地目录,或 Docker 目标 `container:/absolute/dir`(`docker cp`) |
|
| `COPY_DEST` | 与 `COPY_SCRIPT` 至少配置一个 | 内置后备目标:本地目录,或 Docker 目标 `container:/absolute/dir`(`docker cp`) |
|
||||||
| `COPY_SCRIPT` | 与 `COPY_DEST` 至少配置一个 | 推荐 `.pouch/builder/` 下的项目相对路径,也可用绝对路径;必须是可执行文件,同时配置时优先于 `COPY_DEST` |
|
| `COPY_SCRIPT` | 与 `COPY_DEST` 至少配置一个 | 项目内 `.pouch/builder/` 脚本的路径优先写在 `makefile.builder` 并提交;也可用绝对路径或环境/.env.builder 覆盖;必须是可执行文件,同时配置时优先于 `COPY_DEST` |
|
||||||
| `COPY_AS` | 否 | 第一份文件的额外 basename,逗号或空格分隔 |
|
| `COPY_AS` | 否 | 第一份文件的额外 basename,逗号或空格分隔 |
|
||||||
|
|
||||||
`copy_artifact.sh FILE [FILE ...]` 是 APK/IPA 等产物的统一分发入口。不读 `.env`,脏工作树默认拒绝,
|
`copy_artifact.sh FILE [FILE ...]` 是 APK/IPA 等产物的统一分发入口。不读 `.env`,脏工作树默认拒绝,
|
||||||
`ALLOW_UNCOMMITTED=1` 放行。
|
`ALLOW_UNCOMMITTED=1` 放行。
|
||||||
|
|
||||||
未配置 `COPY_SCRIPT` 时,入口把文件拷到 `COPY_DEST`,校验目标存在后打印 SHA-256。
|
未配置 `COPY_SCRIPT` 时,入口把文件拷到 `COPY_DEST`,校验目标存在后打印 SHA-256。
|
||||||
配置 `COPY_SCRIPT=.pouch/builder/publish-apk.sh` 时,入口按项目根解析、先校验每个文件存在,然后直接执行:
|
配置 `COPY_SCRIPT=.pouch/builder/publish-apk.sh` 时(项目内脚本通常由 `makefile.builder` 传入),入口按项目根解析、先校验每个文件存在,然后直接执行:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/absolute/path/to/publish-script FILE [FILE ...]
|
/absolute/path/to/publish-script FILE [FILE ...]
|
||||||
|
|||||||
@@ -492,13 +492,44 @@ def env_file_values(project: Path) -> dict[str, str]:
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
def key_present(key: str, env_keys: set[str]) -> bool:
|
MAKE_ASSIGNMENT = re.compile(
|
||||||
return bool(os.environ.get(key)) or key in env_keys
|
r"^([A-Za-z_][A-Za-z0-9_]*)\s*(?:\?=|:=|=)\s*(.*?)\s*$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def configured_value(key: str, env_values: dict[str, str]) -> str:
|
def makefile_values(project: Path) -> dict[str, str]:
|
||||||
|
"""Return nonempty top-level make variable values for contract checks."""
|
||||||
|
path = project / BUILDER_MAKEFILE
|
||||||
|
values: dict[str, str] = {}
|
||||||
|
if not path.is_file():
|
||||||
|
return values
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
except OSError:
|
||||||
|
return values
|
||||||
|
for raw in text.splitlines():
|
||||||
|
match = MAKE_ASSIGNMENT.match(raw)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
value = re.split(r"\s+#", match.group(2), maxsplit=1)[0].strip()
|
||||||
|
if value:
|
||||||
|
values[match.group(1)] = value
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def key_present(
|
||||||
|
key: str, env_keys: set[str], make_values: dict[str, str] | None = None
|
||||||
|
) -> bool:
|
||||||
|
return bool(os.environ.get(key)) or key in env_keys or bool((make_values or {}).get(key))
|
||||||
|
|
||||||
|
|
||||||
|
def configured_value(
|
||||||
|
key: str,
|
||||||
|
env_values: dict[str, str],
|
||||||
|
make_values: dict[str, str] | None = None,
|
||||||
|
) -> str:
|
||||||
"""Read a configured value without ever printing it."""
|
"""Read a configured value without ever printing it."""
|
||||||
return os.environ.get(key, "") or env_values.get(key, "")
|
return os.environ.get(key, "") or env_values.get(key, "") or (make_values or {}).get(key, "")
|
||||||
|
|
||||||
|
|
||||||
def resolve_copy_script(project: Path, script: str) -> tuple[Path | None, str | None]:
|
def resolve_copy_script(project: Path, script: str) -> tuple[Path | None, str | None]:
|
||||||
@@ -515,8 +546,13 @@ def resolve_copy_script(project: Path, script: str) -> tuple[Path | None, str |
|
|||||||
return resolved, None
|
return resolved, None
|
||||||
|
|
||||||
|
|
||||||
def check_copy_script(report: Report, project: Path, env_values: dict[str, str]) -> None:
|
def check_copy_script(
|
||||||
script = configured_value("COPY_SCRIPT", env_values)
|
report: Report,
|
||||||
|
project: Path,
|
||||||
|
env_values: dict[str, str],
|
||||||
|
make_values: dict[str, str],
|
||||||
|
) -> None:
|
||||||
|
script = configured_value("COPY_SCRIPT", env_values, make_values)
|
||||||
if not script:
|
if not script:
|
||||||
return
|
return
|
||||||
path, resolve_error = resolve_copy_script(project, script)
|
path, resolve_error = resolve_copy_script(project, script)
|
||||||
@@ -579,6 +615,7 @@ def check_ready_env_keys(
|
|||||||
) -> None:
|
) -> None:
|
||||||
env_keys = env_file_keys(project)
|
env_keys = env_file_keys(project)
|
||||||
env_values = env_file_values(project)
|
env_values = env_file_values(project)
|
||||||
|
make_values = makefile_values(project)
|
||||||
lines = []
|
lines = []
|
||||||
missing: list[str] = []
|
missing: list[str] = []
|
||||||
if not deb_project and not docker_project and not copy_project:
|
if not deb_project and not docker_project and not copy_project:
|
||||||
@@ -594,7 +631,7 @@ def check_ready_env_keys(
|
|||||||
lines.append("DEB_*: skipped (no deb track)")
|
lines.append("DEB_*: skipped (no deb track)")
|
||||||
if docker_project:
|
if docker_project:
|
||||||
for key in DOCKER_ENV_KEYS:
|
for key in DOCKER_ENV_KEYS:
|
||||||
found = key_present(key, env_keys)
|
found = key_present(key, env_keys, make_values)
|
||||||
lines.append(f"{key}: {'present' if found else 'MISSING'}")
|
lines.append(f"{key}: {'present' if found else 'MISSING'}")
|
||||||
if not found:
|
if not found:
|
||||||
missing.append(key)
|
missing.append(key)
|
||||||
@@ -602,11 +639,13 @@ def check_ready_env_keys(
|
|||||||
lines.append("DOCKER_*: skipped (no docker track)")
|
lines.append("DOCKER_*: skipped (no docker track)")
|
||||||
if copy_project:
|
if copy_project:
|
||||||
for key in COPY_ENV_KEYS:
|
for key in COPY_ENV_KEYS:
|
||||||
found = key_present(key, env_keys)
|
found = key_present(key, env_keys, make_values)
|
||||||
lines.append(f"{key}: {'present' if found else 'MISSING'}")
|
lines.append(f"{key}: {'present' if found else 'MISSING'}")
|
||||||
if not any(key_present(key, env_keys) for key in COPY_ENV_KEYS):
|
if "COPY_SCRIPT" in make_values and "COPY_SCRIPT" not in env_keys and not os.environ.get("COPY_SCRIPT"):
|
||||||
|
lines[-1] += " (makefile.builder)"
|
||||||
|
if not any(key_present(key, env_keys, make_values) for key in COPY_ENV_KEYS):
|
||||||
missing.append("COPY_DEST or COPY_SCRIPT")
|
missing.append("COPY_DEST or COPY_SCRIPT")
|
||||||
check_copy_script(report, project, env_values)
|
check_copy_script(report, project, env_values, make_values)
|
||||||
else:
|
else:
|
||||||
lines.append("COPY_*: skipped (no copy track)")
|
lines.append("COPY_*: skipped (no copy track)")
|
||||||
if missing:
|
if missing:
|
||||||
|
|||||||
@@ -12,5 +12,5 @@
|
|||||||
# IMAGE_TAG=
|
# IMAGE_TAG=
|
||||||
# PLATFORMS= # optional override; project ARCHES lives in makefile.builder
|
# PLATFORMS= # optional override; project ARCHES lives in makefile.builder
|
||||||
# COPY_DEST= # fallback: local dir, or docker dest container:/absolute/dir
|
# COPY_DEST= # fallback: local dir, or docker dest container:/absolute/dir
|
||||||
# COPY_SCRIPT=.pouch/builder/publish-apk.sh # project-local executable; overrides COPY_DEST
|
# COPY_SCRIPT= # external/temporary executable override; project-local paths belong in makefile.builder
|
||||||
# COPY_AS= # optional extra basenames for the first file
|
# COPY_AS= # optional extra basenames for the first file
|
||||||
|
|||||||
Reference in New Issue
Block a user