app.json 二进制依赖 depends:启动前检查(launcher)+ 安装提示(nyai)

需求(用户定案):App 复杂功能若依赖现成 Linux 二进制(板载 /usr/bin 或卡上
MeowOS/opt/bin)能效更好,声明后系统启动 App 前检查依赖是否存在。

- depends 行式声明:depends=curl,jq:optional,ffmpeg:required(无标注默认
  required;也接受 :opt/:req;未知修饰符按 required 处理并 WARN;空项/超量丢弃)
- launch_app_chain 冷启动时按序查 /usr/bin/<name> 与 <卡>/MeowOS/opt/bin/<name>
  (X_OK,两类命中即满足);缺 required → 不启动(toast「缺依赖: <name>」+ 日志,
  沿用 CANNOT START 风格);optional 缺失 → 仅 WARN 放行;只在启动时查,不做周期复查
- nyai install 对包内 app.json depends 同查并提示(不阻断安装,缺 required 标注
  「安装后启动将失败」)
- 新增 meow_dep.c(独立 TU,解析/检查/块原因),宿主测试 test_depends.c
  解析矩阵 12 用例 + 假根检查路径 12 用例全 PASS;launcher/nyai mipsel 交叉
  -Wall -Wextra 0 warning;launcher 现有宿主测试回归全过
- 文档:APPS.md §2.1 字段表 + docs/MEOWOS_SPEC_REF.md §5.5 同步;hello app.json
  加 depends:curl:optional 示例

launcher md5 899ac22c10f596909d7f35ea685744de(fw_content.conf 已更新;/usr/bin
只读,留给主会话打包固件)
This commit is contained in:
MeowOS committed 2026-09-01 18:28:07 +08:00
1 parent 9e25db77cc
commit ed2ae10320
14 files changed
+416 -12

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
/* meow_dep.c — app.json 二进制依赖声明(depends=<name>[:<mod>][,...])解析与启动前检查。
*
* 背景(用户定案):App 某些复杂功能若依赖现成 Linux 二进制程序(板载 /usr/bin 或卡上
* MeowOS/opt/bin)能效更好,故在 app.json 声明二进制依赖;launcher 在启动 App 前检查
* required 依赖是否都存在,缺 required 则不启动(toast「缺依赖: <name>」+ 日志);
* optional 缺失仅 WARN 放行。检查只在启动时做(不做周期复查)。
*
* 与 meow_parse_app_json 的 line 解析同风格:取值串后逐项切分(不引 JSON 解析器)。
* 本文件独立成 TU,由 build_launcher.sh 编入;宿主测试用「假根(env 覆写搜索目录)」验证。 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "meow_launcher_internal.h"
/* 最近一次因缺 required 依赖而阻断启动的首个缺失名(供 menu toast「缺依赖: <name>」)。
* 每次 launch_app_chain 入口清空(meow_dep_clear_reason);无缺失则 ""。 */
static char g_dep_block_reason[32] = "";
void meow_dep_clear_reason(void)
{
g_dep_block_reason[0] = 0;
}
const char *meow_dep_block_reason(void)
{
return g_dep_block_reason;
}
/* 二进制搜索目录(真机默认):板载 /usr/bin ← 卡上 <卡根>/MeowOS/opt/bin。
* 宿主测试经 MEOW_DEP_BIN / MEOW_DEP_ROOT 覆写为假根(与 nyai MEOWROOT 同思路)。 */
static const char *dep_bin_root(void)
{
const char *e = getenv("MEOW_DEP_BIN");
return (e && e[0]) ? e : "/usr/bin";
}
static const char *dep_card_root(void)
{
const char *e = getenv("MEOW_DEP_ROOT");
return (e && e[0]) ? e : "/usr/data/mnt/sd_0";
}
/* 解析 depends 值串 → 逐个填充 deps[](最多 max 项,超量丢弃;空项/纯空白丢弃)。
* <name>[:<mod>];<mod> 缺省 required;:opt/:req 等价 optional/required;
* 未知修饰符按 required 处理并 logmsg WARN。返回写入项数。 */
int meow_parse_depends(const char *val, struct meow_dep *deps, int max)
{
if (!val || !deps || max <= 0) return 0;
int n = 0;
const char *p = val;
while (*p && n < max) {
while (*p && (*p == ' ' || *p == '\t' || *p == ',')) p++;
if (!*p || *p == '"') break; /* 结束(含 JSON 值尾引号) */
const char *start = p; /* 名称起点 */
while (*p && *p != ':' && *p != ',' && *p != '"' && *p != ' ' && *p != '\t') p++;
int nl = (int)(p - start);
if (nl > 0) {
if (nl > 31) nl = 31;
char nm[32];
memcpy(nm, start, (size_t)nl);
nm[nl] = 0;
int req = 1; /* 默认 required */
if (*p == ':') { /* 修饰符 */
const char *ms = p + 1;
while (*ms == ' ' || *ms == '\t') ms++;
const char *m = ms;
while (*m && *m != ',' && *m != '"' && *m != ' ' && *m != '\t') m++;
int ml = (int)(m - ms);
if (ml == 8 && !strncmp(ms, "optional", 8)) req = 0;
else if (ml == 3 && !strncmp(ms, "opt", 3)) req = 0;
else if (ml == 8 && !strncmp(ms, "required", 8)) req = 1;
else if (ml == 3 && !strncmp(ms, "req", 3)) req = 1;
else logmsg("WARN: depends '%s' unknown mod '%.*s' (treated required)", nm, ml, ms);
p = m;
}
snprintf(deps[n].name, sizeof deps[n].name, "%s", nm);
deps[n].req = (char)(req ? '1' : '0');
n++;
}
if (*p == ',') p++; /* 消费分隔符 */
}
return n;
}
/* 依赖二进制存在性:按序 <board>/<name> → <card>/MeowOS/opt/bin/<name>(X_OK)。返回 1=存在。 */
static int dep_bin_exists(const char *name)
{
if (!name || !name[0]) return 0;
char p[320];
snprintf(p, sizeof p, "%s/%s", dep_bin_root(), name);
if (access(p, X_OK) == 0) return 1;
snprintf(p, sizeof p, "%s/MeowOS/opt/bin/%s", dep_card_root(), name);
if (access(p, X_OK) == 0) return 1;
return 0;
}
/* 启动前检查依赖。returns: 0=全部满足 / 1=缺 required(missing 回填首个缺失名,可 NULL)。 */
int meow_check_app_depends(const struct app *a, char *missing, int mlen)
{
if (!a) return 0;
for (int i = 0; i < a->ndep; i++) {
if (dep_bin_exists(a->deps[i].name)) continue;
if (a->deps[i].req == '1') {
logmsg("launch blocked: missing required dep '%s'", a->deps[i].name);
if (missing && mlen > 0) snprintf(missing, (size_t)mlen, "%s", a->deps[i].name);
snprintf(g_dep_block_reason, sizeof g_dep_block_reason, "%s", a->deps[i].name);
return 1;
}
logmsg("WARN: optional dep '%s' missing, continuing", a->deps[i].name);
}
return 0;
}