需求(用户定案):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 只读,留给主会话打包固件)
118 lines
4.8 KiB
C
118 lines
4.8 KiB
C
/* 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;
|
||
}
|