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:
1 parent
9e25db77cc
commit
ed2ae10320
14 files changed
+416
-12
No files matched your search
@@ -74,10 +74,11 @@
|
|||||||
| `author` | — | 保留字段 |
|
| `author` | — | 保留字段 |
|
||||||
| `fullscreen` | — | app 级全屏:`1`=进入全屏(状态栏**收合至提示线** + **保留左缘返回/顶部提示线/下滑通知中心**,见 MEOWOS_SPEC_REF §5.1);activity 级用它 `route.<id>.fullscreen`。**app.json `fullscreen` 字段此前未解析/已定未做 → 本次实现**(launcher 启动/切 activity 据声明预设 SDK 全屏,env `MEOW_FULLSCREEN`)[源:用户 2026-08-30] |
|
| `fullscreen` | — | app 级全屏:`1`=进入全屏(状态栏**收合至提示线** + **保留左缘返回/顶部提示线/下滑通知中心**,见 MEOWOS_SPEC_REF §5.1);activity 级用它 `route.<id>.fullscreen`。**app.json `fullscreen` 字段此前未解析/已定未做 → 本次实现**(launcher 启动/切 activity 据声明预设 SDK 全屏,env `MEOW_FULLSCREEN`)[源:用户 2026-08-30] |
|
||||||
| `fonts` | — | 字库档位声明:`"full"` / `"gbk"` / `"common"`(app 需大字库时可声明 `"full"`,默认小字库 `common`≈4k;launcher 据此为该 app 加载大字库 `gbk`≈20k,从已扫描的 `MeowOS/fonts/<family>/<weight>/` 里按同 family(其次同 weight)选大字库,M2.6)。**注意**:与 `"fonts":["<handle>"]`(应用自带字库句柄数组)**同名二义**,见 MEOWOS_SPEC_REF §7 A-14 [源:用户 2026-08-30] |
|
| `fonts` | — | 字库档位声明:`"full"` / `"gbk"` / `"common"`(app 需大字库时可声明 `"full"`,默认小字库 `common`≈4k;launcher 据此为该 app 加载大字库 `gbk`≈20k,从已扫描的 `MeowOS/fonts/<family>/<weight>/` 里按同 family(其次同 weight)选大字库,M2.6)。**注意**:与 `"fonts":["<handle>"]`(应用自带字库句柄数组)**同名二义**,见 MEOWOS_SPEC_REF §7 A-14 [源:用户 2026-08-30] |
|
||||||
|
| `depends` | — | **二进制依赖声明**(launcher/nyai 启动前检查):`"curl,jq:optional,ffmpeg:required"`(JSON 值串)或行式 `depends=curl,jq:optional,ffmpeg:required`。逗号分隔多项,每项 `<name>[:<mod>]`;**无标注默认 required**,也接受 `:opt`/`:req` 缩写(=optional/required),未知修饰符按 required 处理并 WARN。App 某些复杂功能若依赖现成 Linux 二进制(**板载 `/usr/bin/<name>` 或卡上 `/usr/data/mnt/sd_0/MeowOS/opt/bin/<name>`,均按 X_OK 查**,两类任命中即满足)能效更好时声明。**launcher 启动前检查(仅启动时,不做周期复查)**:required 缺失 → **不启动**(toast「缺依赖: <name>」+ 日志,沿用 CANNOT START 风格);optional 缺失 → 仅 WARN 放行。[源:用户 2026-09-01] |
|
||||||
|
|
||||||
- 目录扫描深度 1 级:只枚举 `MeowAPPs/` 下的直接子目录
|
- 目录扫描深度 1 级:只枚举 `MeowAPPs/` 下的直接子目录
|
||||||
- 应用计数上限 **8**(超出忽略;单屏 8 行)
|
- 应用计数上限 **8**(超出忽略;单屏 8 行)
|
||||||
- app.json 解析器极简(launcher 内实现):仅匹配 `"name":`、`"exec":`、`"package":` 后的引号字符串(及扩展 icon/framework/immersive/route./`awake.<id>`(keep_awake)/service.),**`fullscreen`(本次实现)与 `fonts`(档位串) 已解析**,不做 JSON 校验(多行/单行均可)[源:审计 2026-08-30][源:用户 2026-08-30]
|
- app.json 解析器极简(launcher 内实现):仅匹配 `"name":`、`"exec":`、`"package":` 后的引号字符串(及扩展 icon/framework/immersive/route./`awake.<id>`(keep_awake)/service./**`depends`**(二进制依赖,line 解析)),**`fullscreen`(本次实现)与 `fonts`(档位串) 已解析**,不做 JSON 校验(多行/单行均可)[源:审计 2026-08-30][源:用户 2026-08-30]
|
||||||
- 列表刷新时机:每次菜单打开时重新扫描(TF 卡热插拔后立即可见)
|
- 列表刷新时机:每次菜单打开时重新扫描(TF 卡热插拔后立即可见)
|
||||||
|
|
||||||
### 2.2 边界情况行为(launcher 实现,全部真机验证)
|
### 2.2 边界情况行为(launcher 实现,全部真机验证)
|
||||||
|
|||||||
@@ -215,7 +215,7 @@
|
|||||||
- `<pkg>/` = 程序主体+app.json+资源;`MeowData/<pkg>/{data,cache}` = 数据;首次启动自动建 data/cache 并经 env 注入,exFAT 无软链 `[APPS 附录 §1/§2]`
|
- `<pkg>/` = 程序主体+app.json+资源;`MeowData/<pkg>/{data,cache}` = 数据;首次启动自动建 data/cache 并经 env 注入,exFAT 无软链 `[APPS 附录 §1/§2]`
|
||||||
- `.mpk` = tar.gz(app.json + meow 二进制 + 资源),**不含 data/cache**;导入校验 app.json/哈希→解压→数据 init→登入列表 `[APPS 附录 §3]`
|
- `.mpk` = tar.gz(app.json + meow 二进制 + 资源),**不含 data/cache**;导入校验 app.json/哈希→解压→数据 init→登入列表 `[APPS 附录 §3]`
|
||||||
- 包管理 **CLI `nyai`**(静态 MIPS,部署 `MeowOS/opt/bin/nyai`):install/remove/list/info/search/index/export/upgrade/scan;源 `MeowOS/opt/nyai/sources.list`;索引 `Packages`(Package/Version/Filename/Size/SHA256/Depends/Description)`[APPS 附录 §5]`
|
- 包管理 **CLI `nyai`**(静态 MIPS,部署 `MeowOS/opt/bin/nyai`):install/remove/list/info/search/index/export/upgrade/scan;源 `MeowOS/opt/nyai/sources.list`;索引 `Packages`(Package/Version/Filename/Size/SHA256/Depends/Description)`[APPS 附录 §5]`
|
||||||
- 应用数上限 **8**(单屏 8 行);扫描深度 1 级;`app.json` 字段:name(仅 ASCII)/exec(静态 MIPS32r2 ELF)/version/author + **`package`(反域名权威 app_id;==目录名;缺省回退目录名)** + 扩展 `framework`/`immersive`/`icon`/`route.<id>`/`awake.<id>`(keep_awake)/`service.<svc>`(M4 起服务二进制=App 同目录 `<svc>` 文件)/**`background`/`background_audio`(M4 已落地:无头服务注册权限/后台播放保活)**/`fullscreen`(app 级;`route.<id>.fullscreen`=activity 级;**本次实现**:launcher 启动/切 activity 据声明预设 SDK 全屏,见 §5.1/§3.1/§A-12)/`fonts`(字体档位 `"full"|"gbk"|"common"`,需大字库时声明,见 §5.7.8) `[APPS §2][MEOW_APP_SDK §9][FRAMEWORK_DESIGN §2][源:审计 2026-08-30][源:用户 2026-08-30]`
|
- 应用数上限 **8**(单屏 8 行);扫描深度 1 级;`app.json` 字段:name(仅 ASCII)/exec(静态 MIPS32r2 ELF)/version/author + **`package`(反域名权威 app_id;==目录名;缺省回退目录名)** + 扩展 `framework`/`immersive`/`icon`/`route.<id>`/`awake.<id>`(keep_awake)/`service.<svc>`(M4 起服务二进制=App 同目录 `<svc>` 文件)/**`background`/`background_audio`(M4 已落地:无头服务注册权限/后台播放保活)**/`fullscreen`(app 级;`route.<id>.fullscreen`=activity 级;**本次实现**:launcher 启动/切 activity 据声明预设 SDK 全屏,见 §5.1/§3.1/§A-12)/`fonts`(字体档位 `"full"|"gbk"|"common"`,需大字库时声明,见 §5.7.8)/`depends`(**二进制依赖**声明:`"curl,jq:optional"` 或行式 `depends=curl,jq:optional,ffmpeg:required`;comma 分项 `<name>[:<mod>]`,mod=required|req(默认)/optional|opt;launcher 启动前查 `/usr/bin/<name>` 与 `/usr/data/mnt/sd_0/MeowOS/opt/bin/<name>`(X_OK,命一即可),required 缺失阻断启动(「缺依赖」toast)、optional 缺失仅 WARN,**仅启动时检查**;nyai install 同查并提示) `[APPS §2][MEOW_APP_SDK §9][FRAMEWORK_DESIGN §2][源:审计 2026-08-30][源:用户 2026-08-30]`
|
||||||
|
|
||||||
### 5.6 冷启动充电屏
|
### 5.6 冷启动充电屏
|
||||||
- 触发(**三者同时**):冷启动(`poweroff` 写 `/data/meow_cold_boot=1`;`reboot` 写 0)+ 在充电(AC online=1 或 battery status=Charging)+(开机瞬间一次性快照,判定后不回头)`[CHARGING_SCREEN §0]`
|
- 触发(**三者同时**):冷启动(`poweroff` 写 `/data/meow_cold_boot=1`;`reboot` 写 0)+ 在充电(AC online=1 或 battery status=Charging)+(开机瞬间一次性快照,判定后不回头)`[CHARGING_SCREEN §0]`
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"name":"Hello App","exec":"/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello","package":"os.meow.hello","version":"0.1","framework":1,"immersive":1}
|
{"name":"Hello App","exec":"/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello","package":"os.meow.hello","version":"0.1","framework":1,"immersive":1,"depends":"curl:optional"}
|
||||||
@@ -807,6 +807,26 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
|||||||
tmp.nsvc++;
|
tmp.nsvc++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* depends=<name>[:<mod>][,...]:二进制依赖声明(meow_dep.c 解析)。
|
||||||
|
* 与行式解析同风格(JSON "depends":"..." 或扁平 depends=... 均可);不引 JSON 解析器。
|
||||||
|
* <mod> 缺省 required;:opt/:req 等价 optional/required;未知修饰符按 required 处理并 WARN。 */
|
||||||
|
char *dp = strstr(buf, "depends");
|
||||||
|
if (dp) {
|
||||||
|
char *after = dp + 7; /* strlen("depends") */
|
||||||
|
while (*after == ' ' || *after == '\t') after++;
|
||||||
|
/* 紧跟 '=' / ':' / '"' 才算 depends 键(防 description 等文本误命中) */
|
||||||
|
if ((*after == '=' || *after == ':' || *after == '"') &&
|
||||||
|
tmp.ndep < MEOW_MAX_DEPS) {
|
||||||
|
char *vp = after;
|
||||||
|
while (*vp && *vp != ':' && *vp != '=') vp++; /* 跳到值分隔符 */
|
||||||
|
if (*vp == ':' || *vp == '=') vp++;
|
||||||
|
while (*vp == ' ' || *vp == '\t') vp++;
|
||||||
|
if (*vp == '"') vp++; /* 剥 JSON 值开引号 */
|
||||||
|
int got = meow_parse_depends(vp, tmp.deps + tmp.ndep,
|
||||||
|
MEOW_MAX_DEPS - tmp.ndep);
|
||||||
|
tmp.ndep += got;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
strncpy(a->name, name, 16);
|
strncpy(a->name, name, 16);
|
||||||
strncpy(a->exec, exec, 128);
|
strncpy(a->exec, exec, 128);
|
||||||
@@ -822,6 +842,8 @@ void meow_parse_app_json(FILE *f, struct app *a)
|
|||||||
if (tmp.nroute) memcpy(a->routes, tmp.routes, sizeof(a->routes[0]) * (size_t)tmp.nroute);
|
if (tmp.nroute) memcpy(a->routes, tmp.routes, sizeof(a->routes[0]) * (size_t)tmp.nroute);
|
||||||
a->nsvc = tmp.nsvc;
|
a->nsvc = tmp.nsvc;
|
||||||
if (tmp.nsvc) memcpy(a->svcs, tmp.svcs, sizeof(a->svcs[0]) * (size_t)tmp.nsvc);
|
if (tmp.nsvc) memcpy(a->svcs, tmp.svcs, sizeof(a->svcs[0]) * (size_t)tmp.nsvc);
|
||||||
|
a->ndep = tmp.ndep;
|
||||||
|
if (tmp.ndep) memcpy(a->deps, tmp.deps, sizeof(a->deps[0]) * (size_t)tmp.ndep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1442,6 +1464,7 @@ static int launch_app_chain(struct app *a, const char *chain)
|
|||||||
g_ams_pick_armed = 0;
|
g_ams_pick_armed = 0;
|
||||||
}
|
}
|
||||||
if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; }
|
if (!a->ok) { logmsg("launch blocked: exec not ok"); return -1; }
|
||||||
|
meow_dep_clear_reason(); /* 每次 launch 入口清 stale 依赖阻断原因(防上一 app 残留) */
|
||||||
|
|
||||||
/* M4 后台保活恢复:该 App 驻留后台(播放中经 nav-exit 转过)→ 原进程回前台,
|
/* M4 后台保活恢复:该 App 驻留后台(播放中经 nav-exit 转过)→ 原进程回前台,
|
||||||
* 不 fork/exec(进程仍存无法注入新 env:chain/picker 上下文忽略,记日志)。
|
* 不 fork/exec(进程仍存无法注入新 env:chain/picker 上下文忽略,记日志)。
|
||||||
@@ -1461,6 +1484,15 @@ static int launch_app_chain(struct app *a, const char *chain)
|
|||||||
meow_nav_begin(a->id, NULL); /* 转后台时导航栈已清;回前台从裸态重建 */
|
meow_nav_begin(a->id, NULL); /* 转后台时导航栈已清;回前台从裸态重建 */
|
||||||
}
|
}
|
||||||
if (!resumed) {
|
if (!resumed) {
|
||||||
|
/* 二进制依赖启动前检查(app.json depends):required 缺失阻断启动,optional 缺失放行。
|
||||||
|
* 只在冷启动时做(不做周期复查);resumed 进程不重查(依赖无变化、无需再 fork)。 */
|
||||||
|
{
|
||||||
|
char dep_missing[32] = "";
|
||||||
|
if (meow_check_app_depends(a, dep_missing, sizeof dep_missing) != 0) {
|
||||||
|
logmsg("launch blocked: dep '%s' missing (CANNOT START: %s)", dep_missing, a->name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
meow_nav_begin(a->id, chain); /* M3:冷启动/裸启动都以当前链深初始化导航栈(chain 空→depth 0) */
|
meow_nav_begin(a->id, chain); /* M3:冷启动/裸启动都以当前链深初始化导航栈(chain 空→depth 0) */
|
||||||
|
|
||||||
/* M2.12:启动前准备 MeowData/<pkg>/{data,cache}(幂等;卡不在/失败只 log 不崩)。
|
/* M2.12:启动前准备 MeowData/<pkg>/{data,cache}(幂等;卡不在/失败只 log 不崩)。
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
|
|||||||
main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \
|
main_menu.c statusbar.c fb_compositor.c settings_page.c power_menu.c \
|
||||||
notif_center.c app_launcher.c audio.c backlight.c net.c about.c \
|
notif_center.c app_launcher.c audio.c backlight.c net.c about.c \
|
||||||
subpage.c wifi_page.c bt_page.c now_playing.c \
|
subpage.c wifi_page.c bt_page.c now_playing.c \
|
||||||
font_theme.c keys.c util.c \
|
font_theme.c keys.c util.c meow_dep.c \
|
||||||
meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
|
meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
|
||||||
"../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd
|
"../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd
|
||||||
echo "== meow_launcherd =="
|
echo "== meow_launcherd =="
|
||||||
|
|||||||
@@ -13,6 +13,16 @@
|
|||||||
int menu_opened_blank = 0;
|
int menu_opened_blank = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/* launch_app/launch_activity 返回 -1 时的 toast:缺二进制依赖(app.json depends)→ 「缺依赖: <name>」;
|
||||||
|
* 其余启动失败沿用「CANNOT START: <name>」。原因由 meow_dep.c 的 meow_dep_block_reason() 提供。 */
|
||||||
|
static void launch_fail_toast(char *toast, size_t n, const char *name)
|
||||||
|
{
|
||||||
|
const char *bd = meow_dep_block_reason();
|
||||||
|
if (bd && bd[0]) snprintf(toast, n, "缺依赖: %s", bd);
|
||||||
|
else snprintf(toast, n, "CANNOT START: %s", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---- config (MeowAPPs/meow.conf): pure black background by default ---- */
|
/* ---- config (MeowAPPs/meow.conf): pure black background by default ---- */
|
||||||
uint32_t menu_bg = 0x000000;
|
uint32_t menu_bg = 0x000000;
|
||||||
|
|
||||||
@@ -904,7 +914,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
|||||||
ret = launch_app(&apps[idx]);
|
ret = launch_app(&apps[idx]);
|
||||||
}
|
}
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
snprintf(toast, sizeof toast, "CANNOT START: %s", apps[idx].name);
|
launch_fail_toast(toast, sizeof toast, apps[idx].name);
|
||||||
toast_ms = TOAST_MS;
|
toast_ms = TOAST_MS;
|
||||||
}
|
}
|
||||||
menu_after_app_exit(ret == 0);
|
menu_after_app_exit(ret == 0);
|
||||||
@@ -938,7 +948,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
|||||||
last_input_ms = now_ms(); /* M2.17:回主界面即视为输入,重置自动息屏计时 */
|
last_input_ms = now_ms(); /* M2.17:回主界面即视为输入,重置自动息屏计时 */
|
||||||
dirty = 1; /* app wrote buffer0 during its run -> redraw menu */
|
dirty = 1; /* app wrote buffer0 during its run -> redraw menu */
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
snprintf(toast, sizeof toast, "CANNOT START: %s", apps[0].name);
|
launch_fail_toast(toast, sizeof toast, apps[0].name);
|
||||||
toast_ms = TOAST_MS;
|
toast_ms = TOAST_MS;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1297,8 +1307,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
|||||||
last_input_ms = now_ms();
|
last_input_ms = now_ms();
|
||||||
dirty = 1;
|
dirty = 1;
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
snprintf(toast, sizeof toast, "CANNOT START: %s",
|
launch_fail_toast(toast, sizeof toast, apps[click.app_idx].name);
|
||||||
apps[click.app_idx].name);
|
|
||||||
toast_ms = TOAST_MS;
|
toast_ms = TOAST_MS;
|
||||||
}
|
}
|
||||||
} else if (click.status == NOTIF_CLICK_STATUS_NOROUTE) {
|
} else if (click.status == NOTIF_CLICK_STATUS_NOROUTE) {
|
||||||
@@ -1392,8 +1401,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
|||||||
last_input_ms = now_ms();
|
last_input_ms = now_ms();
|
||||||
dirty = 1;
|
dirty = 1;
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
snprintf(toast, sizeof toast, "CANNOT START: %s",
|
launch_fail_toast(toast, sizeof toast, apps[click.app_idx].name);
|
||||||
apps[click.app_idx].name);
|
|
||||||
toast_ms = TOAST_MS;
|
toast_ms = TOAST_MS;
|
||||||
}
|
}
|
||||||
} else if (click.status == NOTIF_CLICK_STATUS_NOROUTE) {
|
} else if (click.status == NOTIF_CLICK_STATUS_NOROUTE) {
|
||||||
@@ -1497,7 +1505,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
|||||||
last_input_ms = now_ms(); /* M2.17:回主界面即视为输入,重置自动息屏计时 */
|
last_input_ms = now_ms(); /* M2.17:回主界面即视为输入,重置自动息屏计时 */
|
||||||
dirty = 1; /* app wrote buffer0 -> redraw menu */
|
dirty = 1; /* app wrote buffer0 -> redraw menu */
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
snprintf(toast, sizeof toast, "CANNOT START: %s", apps[idx].name);
|
launch_fail_toast(toast, sizeof toast, apps[idx].name);
|
||||||
toast_ms = TOAST_MS;
|
toast_ms = TOAST_MS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -295,6 +295,15 @@ struct meow_svc {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* 二进制依赖行:depends=<name>[:<mod>][,...]。<name>=二进制名;<mod>=required|req(默认)/optional|opt。
|
||||||
|
* launcher 启动前检查(required 缺失阻断,optional 缺失放行),nyai 安装时提示。 */
|
||||||
|
#define MEOW_MAX_DEPS 8
|
||||||
|
struct meow_dep {
|
||||||
|
char name[32]; /* 二进制名(/usr/bin/<name> 或 <卡>/MeowOS/opt/bin/<name>) */
|
||||||
|
char req; /* '1'=required(缺失阻断启动) '0'=optional(缺失仅 WARN 放行) */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct app {
|
struct app {
|
||||||
char id[MEOW_APP_ID_MAX]; /* M2.52:app_id=app.json 的 package(权威,缺省回退卡上目录名 de->d_name),供 MEOW_LOG_APP 注入 */
|
char id[MEOW_APP_ID_MAX]; /* M2.52:app_id=app.json 的 package(权威,缺省回退卡上目录名 de->d_name),供 MEOW_LOG_APP 注入 */
|
||||||
char package[MEOW_APP_ID_MAX]; /* 安卓化:app.json 的 package 字段(反域名权威标识;目录名=package),scan_apps 据此定 app_id */
|
char package[MEOW_APP_ID_MAX]; /* 安卓化:app.json 的 package 字段(反域名权威标识;目录名=package),scan_apps 据此定 app_id */
|
||||||
@@ -313,6 +322,8 @@ struct app {
|
|||||||
struct meow_route routes[MEOW_MAX_ROUTES];
|
struct meow_route routes[MEOW_MAX_ROUTES];
|
||||||
int nsvc;
|
int nsvc;
|
||||||
struct meow_svc svcs[MEOW_MAX_SVCS];
|
struct meow_svc svcs[MEOW_MAX_SVCS];
|
||||||
|
int ndep; /* app.json depends 二进制依赖项数(meow_dep.c) */
|
||||||
|
struct meow_dep deps[MEOW_MAX_DEPS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -634,6 +645,11 @@ int kp_open(struct keypoll *kp, const char *dev);
|
|||||||
int kp_poll(struct keypoll *kp, int (*cb)(int code, int down, void *ctx),
|
int kp_poll(struct keypoll *kp, int (*cb)(int code, int down, void *ctx),
|
||||||
void *ctx);
|
void *ctx);
|
||||||
int launch_app(struct app *a);
|
int launch_app(struct app *a);
|
||||||
|
/* ---- app.json 二进制依赖(depends)解析与启动前检查(meow_dep.c)---- */
|
||||||
|
int meow_parse_depends(const char *val, struct meow_dep *deps, int max);
|
||||||
|
int meow_check_app_depends(const struct app *a, char *missing, int mlen);
|
||||||
|
void meow_dep_clear_reason(void);
|
||||||
|
const char *meow_dep_block_reason(void);
|
||||||
/* M3:以指定 activity 链路冷启动某 app(注入 MEOW_ACTIVITY/MEOW_CHAIN + 设 nav_stack 栈深)。
|
/* M3:以指定 activity 链路冷启动某 app(注入 MEOW_ACTIVITY/MEOW_CHAIN + 设 nav_stack 栈深)。
|
||||||
* chain=NULL 或空 = 裸启动(进 App 主屏/首页,不注入 activity env)。 */
|
* chain=NULL 或空 = 裸启动(进 App 主屏/首页,不注入 activity env)。 */
|
||||||
int launch_activity(struct app *a, const char *chain);
|
int launch_activity(struct app *a, const char *chain);
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,183 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -810,6 +810,51 @@ static int export_preserve_sig(const char *pkg, const char *dst)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------- 二进制依赖检查(app.json depends,新语义) ----------------
|
||||||
|
* app.json "depends" 现为二进制依赖声明:<name>[:<mod>][,...],<mod>=required|req(默认)/optional|opt。
|
||||||
|
* 与 launcher meow_parse_depends 同语义;按序查 /usr/bin/<name> 与 <root>/MeowOS/opt/bin/<name>(X_OK)。
|
||||||
|
* 安装时**不阻断**:缺 required 输出 WARN 标注「安装后启动将失败」;缺 optional 仅提示。 */
|
||||||
|
static int bin_dep_exists(const char *name)
|
||||||
|
{
|
||||||
|
char p[300];
|
||||||
|
snprintf(p, sizeof p, "/usr/bin/%s", name);
|
||||||
|
if (access(p, X_OK) == 0) return 1;
|
||||||
|
snprintf(p, sizeof p, "%s/MeowOS/opt/bin/%s", meow_root(), name);
|
||||||
|
if (access(p, X_OK) == 0) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nyai_check_bin_depends(const appmeta *m)
|
||||||
|
{
|
||||||
|
if (!m || !m->depends[0]) return;
|
||||||
|
char d[256];
|
||||||
|
snprintf(d, sizeof d, "%s", m->depends);
|
||||||
|
char *save = NULL, *tok = strtok_r(d, ",", &save);
|
||||||
|
while (tok) {
|
||||||
|
tok = trim(tok);
|
||||||
|
if (!*tok) { tok = strtok_r(NULL, ",", &save); continue; }
|
||||||
|
char nm[64] = ""; int req = 1;
|
||||||
|
char *colon = strchr(tok, ':');
|
||||||
|
size_t nl = colon ? (size_t)(colon - tok) : strlen(tok);
|
||||||
|
if (nl >= sizeof nm) nl = sizeof nm - 1;
|
||||||
|
memcpy(nm, tok, nl); nm[nl] = 0;
|
||||||
|
if (colon) {
|
||||||
|
char *mod = trim(colon + 1);
|
||||||
|
if (!strcmp(mod, "optional") || !strcmp(mod, "opt")) req = 0;
|
||||||
|
else if (!strcmp(mod, "required") || !strcmp(mod, "req")) req = 1;
|
||||||
|
/* 未知修饰符按 required 处理 */
|
||||||
|
}
|
||||||
|
if (!*nm) { tok = strtok_r(NULL, ",", &save); continue; }
|
||||||
|
if (!bin_dep_exists(nm)) {
|
||||||
|
if (req)
|
||||||
|
printf("nyai: 警告: 依赖 %s 缺失(required),安装后启动将失败\n", nm);
|
||||||
|
else
|
||||||
|
printf("nyai: 提示: 可选依赖 %s 缺失\n", nm);
|
||||||
|
}
|
||||||
|
tok = strtok_r(NULL, ",", &save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- install(文件或名称) ---------------- */
|
/* ---------------- install(文件或名称) ---------------- */
|
||||||
static int install_mpk_file(const char *mpk_path)
|
static int install_mpk_file(const char *mpk_path)
|
||||||
{
|
{
|
||||||
@@ -852,6 +897,8 @@ static int install_mpk_file(const char *mpk_path)
|
|||||||
fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n");
|
fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n");
|
||||||
rm_rf(stg); return 1;
|
rm_rf(stg); return 1;
|
||||||
}
|
}
|
||||||
|
/* 二进制依赖检查(app.json depends):不阻断安装,缺 required 标注「安装后启动将失败」。 */
|
||||||
|
nyai_check_bin_depends(&m);
|
||||||
/* ①.5 签名分级 + 三重绑定(MPK_SIGNATURE_DESIGN §3.3/§5.2 失败行为表) */
|
/* ①.5 签名分级 + 三重绑定(MPK_SIGNATURE_DESIGN §3.3/§5.2 失败行为表) */
|
||||||
int vr = verify_mpk_signature(mpk_path, pkg, NULL, 0);
|
int vr = verify_mpk_signature(mpk_path, pkg, NULL, 0);
|
||||||
int system_pkg = is_system_pkg(pkg);
|
int system_pkg = is_system_pkg(pkg);
|
||||||
|
|||||||
Binary file not shown.
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
|
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
|
||||||
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M2.85(sysui让位 agent-216)
|
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M2.85(sysui让位 agent-216)
|
||||||
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|c07d011c0769fca14d3bfb752d735463|主屏 launcher M4(后台服务保活+后台播放:MSG_REGISTER/SERVICE_STOP/MSG_SYS 退避熔断;nav-exit 播放中转后台保活,SDK present 自抑非 SIGSTOP)
|
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|899ac22c10f596909d7f35ea685744de|主屏 launcher M4(后台服务保活+后台播放:MSG_REGISTER/SERVICE_STOP/MSG_SYS 退避熔断;nav-exit 播放中转后台保活,SDK present 自抑非 SIGSTOP;app.json depends 二进制依赖启动前检查:缺 required 阻断/optional 放行)
|
||||||
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|553840cdc79d8d7bed766a5e3981ac57|应用看门狗 M2.95(+音量反转修复)
|
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|553840cdc79d8d7bed766a5e3981ac57|应用看门狗 M2.95(+音量反转修复)
|
||||||
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
|
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
|
||||||
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
|
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
|
||||||
|
|||||||
Reference in new issue
Block a user