Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_depends.c
T
MeowOS ed2ae10320 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
只读,留给主会话打包固件)
2026-09-01 18:28:07 +08:00

184 lines
7.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* test_depends.c — 宿主单测:app.json 二进制依赖(depends)解析矩阵 + 启动前检查路径命中/缺失。
*
* 编译运行(在 patch_v2/meow_launcherd 下,需 -I../sdk 因内部头含 SDK 声明):
* gcc -Wall -Wextra -Os -I../sdk test_depends.c meow_dep.c -o test_depends && ./test_depends
* 全 PASS -> exit 0。logmsg 打桩(解析 WARN 不干扰断言);检查路径用「假根」(MEOW_DEP_BIN/
* MEOW_DEP_ROOT 覆写搜索目录,与 nyai MEOWROOT 同思路)。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "meow_launcher_internal.h"
/* logmsg 打桩:meow_dep.c 经内部头取到声明,此处给定义(宿主无 util.c)。 */
void logmsg(const char *fmt, ...)
{
(void)fmt;
}
static int g_pass = 0, g_fail = 0;
static void check(const char *name, int ok)
{
if (ok) { g_pass++; printf("PASS %s\n", name); }
else { g_fail++; printf("FAIL %s\n", name); }
}
/* ---- 解析矩阵:直接喂 depends 值串给 meow_parse_depends ---- */
static void test_parse(void)
{
struct meow_dep d[MEOW_MAX_DEPS];
int n;
/* T0 空串 → 0 项 */
memset(d, 0, sizeof d);
n = meow_parse_depends("", d, MEOW_MAX_DEPS);
check("T0 empty -> 0", n == 0);
/* T1 单 required(无修饰符) */
memset(d, 0, sizeof d);
n = meow_parse_depends("curl", d, MEOW_MAX_DEPS);
check("T1 single required count", n == 1);
check("T1 name", n == 1 && !strcmp(d[0].name, "curl"));
check("T1 req default", n == 1 && d[0].req == '1');
/* T2 混合:无标注(req) + optional + required */
memset(d, 0, sizeof d);
n = meow_parse_depends("curl,jq:optional,ffmpeg:required", d, MEOW_MAX_DEPS);
check("T2 mixed count", n == 3);
check("T2 curl req", n == 3 && !strcmp(d[0].name, "curl") && d[0].req == '1');
check("T2 jq optional", n == 3 && !strcmp(d[1].name, "jq") && d[1].req == '0');
check("T2 ffmpeg required", n == 3 && !strcmp(d[2].name, "ffmpeg") && d[2].req == '1');
/* T3 缩写 :opt / :req */
memset(d, 0, sizeof d);
n = meow_parse_depends("jq:opt,ffmpeg:req", d, MEOW_MAX_DEPS);
check("T3 abbrev count", n == 2);
check("T3 jq opt", n == 2 && !strcmp(d[0].name, "jq") && d[0].req == '0');
check("T3 ffmpeg req", n == 2 && !strcmp(d[1].name, "ffmpeg") && d[1].req == '1');
/* T4 空项跳过 + 尾部引号(JSON 值剥开引号后的形态) */
memset(d, 0, sizeof d);
n = meow_parse_depends("a:opt,,b\"", d, MEOW_MAX_DEPS);
check("T4 empty item skipped + trailing quote", n == 2);
check("T4 a opt", n == 2 && !strcmp(d[0].name, "a") && d[0].req == '0');
check("T4 b req(默认)", n == 2 && !strcmp(d[1].name, "b") && d[1].req == '1');
/* T5 未知修饰符 → 按 required 处理(WARN 由 logmsg 桩吞掉) */
memset(d, 0, sizeof d);
n = meow_parse_depends("a:bogus", d, MEOW_MAX_DEPS);
check("T5 unknown mod treated required", n == 1 && !strcmp(d[0].name, "a") && d[0].req == '1');
/* T6 超量丢弃(最多 MEOW_MAX_DEPS 项) */
memset(d, 0, sizeof d);
n = meow_parse_depends("a,b,c,d,e,f,g,h,i,j", d, MEOW_MAX_DEPS);
check("T6 cap at MEOW_MAX_DEPS", n == MEOW_MAX_DEPS);
}
/* ---- 假根检查路径:临时目录造 /usr/bin 与 <card>/MeowOS/opt/bin 桩,验证命中/缺失 ---- */
static char g_bin[128], g_root[64];
/* mkdir -p 的极简实现(按 '/' 逐级建) */
static void mkdir_p(const char *path)
{
char buf[512];
snprintf(buf, sizeof buf, "%s", path);
for (char *p = buf + 1; *p; p++)
if (*p == '/') { *p = 0; mkdir(buf, 0755); *p = '/'; }
mkdir(buf, 0755);
}
static void make_exec(const char *path)
{
mkdir_p(path);
/* 建到父目录后在末尾文件名上 chmod:先写再 chmod */
FILE *f = fopen(path, "w");
if (f) { fputs("#!/bin/sh\n", f); fclose(f); }
chmod(path, 0755);
}
static void test_check(void)
{
mkdir_p(g_bin);
char cardbin[128];
snprintf(cardbin, sizeof cardbin, "%s/MeowOS/opt/bin", g_root);
mkdir_p(cardbin);
/* 板载 /usr/bin/curl(命中第一类)+ 卡上 MeowOS/opt/bin/ffmpeg(命中第二类) */
char p[160];
snprintf(p, sizeof p, "%s/curl", g_bin); make_exec(p);
snprintf(p, sizeof p, "%s/ffmpeg", cardbin); make_exec(p);
/* 同名板载/卡上并存 → 板载优先验证(都 make_exec,命中即真) */
snprintf(p, sizeof p, "%s/tool", g_bin); make_exec(p);
snprintf(p, sizeof p, "%s/tool", cardbin); make_exec(p);
/* vim / jq 不存在(缺失) */
struct app a;
char missing[32];
/* T7 全部满足(curl 板载 + ffmpeg 卡上)= 0 */
memset(&a, 0, sizeof a);
a.ndep = 2;
snprintf(a.deps[0].name, sizeof a.deps[0].name, "curl"); a.deps[0].req = '1';
snprintf(a.deps[1].name, sizeof a.deps[1].name, "ffmpeg"); a.deps[1].req = '1';
missing[0] = 0;
check("T7 all present -> ok", meow_check_app_depends(&a, missing, sizeof missing) == 0);
/* T8 optional 缺失 → 放行(返回 0) */
memset(&a, 0, sizeof a);
a.ndep = 2;
snprintf(a.deps[0].name, sizeof a.deps[0].name, "curl"); a.deps[0].req = '1';
snprintf(a.deps[1].name, sizeof a.deps[1].name, "nosuch"); a.deps[1].req = '0';
missing[0] = 0;
meow_dep_clear_reason();
check("T8 optional missing -> ok", meow_check_app_depends(&a, missing, sizeof missing) == 0);
check("T8 optional no block reason", meow_dep_block_reason()[0] == 0);
/* T9 required 缺失 → 返回 1 且回填首个缺失名 + block reason */
memset(&a, 0, sizeof a);
a.ndep = 3;
snprintf(a.deps[0].name, sizeof a.deps[0].name, "curl"); a.deps[0].req = '1';
snprintf(a.deps[1].name, sizeof a.deps[1].name, "vim"); a.deps[1].req = '1';
snprintf(a.deps[2].name, sizeof a.deps[2].name, "jq"); a.deps[2].req = '1';
missing[0] = 0;
meow_dep_clear_reason();
int rc = meow_check_app_depends(&a, missing, sizeof missing);
check("T9 required missing -> block", rc == 1);
check("T9 missing name vim", !strcmp(missing, "vim"));
check("T9 block reason", !strcmp(meow_dep_block_reason(), "vim"));
/* T10 required 缺失但 optional 先前置缺失 → 仍报 required 缺失(order 保持) */
memset(&a, 0, sizeof a);
a.ndep = 2;
snprintf(a.deps[0].name, sizeof a.deps[0].name, "nosuchopt"); a.deps[0].req = '0';
snprintf(a.deps[1].name, sizeof a.deps[1].name, "curl"); a.deps[1].req = '1';
meow_dep_clear_reason();
rc = meow_check_app_depends(&a, missing, sizeof missing);
check("T10 optional-first then required present -> ok", rc == 0);
/* T11 同名板载/卡上并存 → 命中(不判卡上缺失) */
memset(&a, 0, sizeof a);
a.ndep = 1;
snprintf(a.deps[0].name, sizeof a.deps[0].name, "tool"); a.deps[0].req = '1';
meow_dep_clear_reason();
check("T11 both-places dup -> ok", meow_check_app_depends(&a, missing, sizeof missing) == 0);
}
int main(void)
{
/* 假根:唯一临时目录(随 pid) */
snprintf(g_root, sizeof g_root, "/tmp/test_depends_root_%d", (int)getpid());
snprintf(g_bin, sizeof g_bin, "%s/usr/bin", g_root);
/* 经环境变量覆写搜索目录:MEOW_DEP_BIN=假usr/bin、MEOW_DEP_ROOT=假卡根 */
setenv("MEOW_DEP_BIN", g_bin, 1);
setenv("MEOW_DEP_ROOT", g_root, 1);
test_parse();
test_check();
printf("\n%d pass, %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}