@harmonyos-arkts/d2h 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +40 -0
- package/agents/android-to-hmos-00-orchestrator.md +3 -315
- package/agents/d2h.md +53 -38
- package/contracts/workflow/artifacts.md +22 -0
- package/contracts/workflow/delivery.md +54 -0
- package/contracts/workflow/dispatch.md +26 -0
- package/contracts/workflow/execution.md +125 -0
- package/contracts/workflow/generation.md +40 -0
- package/contracts/workflow/orchestration.md +49 -0
- package/contracts/workflow/overview.md +58 -0
- package/contracts/workflow/plan.md +119 -0
- package/contracts/workflow/scope.md +151 -0
- package/dist/bin/install-opencode.js +34 -15
- package/dist/src/install-codex.js +131 -0
- package/dist/src/install-opencode.js +5 -5
- package/dist/src/workflow.js +54 -0
- package/docs/architecture-plan-guide.zh-CN.md +70 -0
- package/docs/incremental-spec-evaluation-design.md +670 -0
- package/docs/spec-plan-guide.md +226 -0
- package/docs/workflow-decoupling.md +59 -0
- package/opencode/interactive-tools/advance-progress-demo.mjs +21 -0
- package/opencode/interactive-tools/browser-host.ts +110 -0
- package/opencode/interactive-tools/core.ts +242 -0
- package/opencode/interactive-tools/demo-workflow.ts +55 -0
- package/opencode/interactive-tools/demo.mjs +37 -0
- package/opencode/interactive-tools/package-lock.json +1876 -0
- package/opencode/interactive-tools/package.json +23 -0
- package/opencode/interactive-tools/panel.ts +40 -0
- package/opencode/interactive-tools/prepare-demo.mjs +17 -0
- package/opencode/interactive-tools/prepare-progress-demo.mjs +13 -0
- package/opencode/interactive-tools/progress.ts +118 -0
- package/opencode/interactive-tools/server-legacy.mjs +2 -0
- package/opencode/interactive-tools/server.mjs +40 -0
- package/opencode/interactive-tools/tui.tsx +230 -0
- package/package.json +9 -1
- package/schemas/native-libs-analysis-report.schema.json +695 -0
- package/schemas/native-libs-verify-report.schema.json +566 -0
- package/schemas/spec-native-libs-shard.schema.json +110 -0
- package/skills/arkts-icon-sizing/SKILL.md +0 -10
- package/skills/cpp/d2h-native-library-analyse/SKILL.md +91 -0
- package/skills/cpp/d2h-native-library-analyse/scripts/analyse-android-so.py +630 -0
- package/skills/cpp/d2h-native-library-build/SKILL.md +403 -0
- package/skills/cpp/d2h-native-library-build/references/build-config-mapping.md +180 -0
- package/skills/cpp/d2h-native-library-build/references/jni-to-napi-mapping.md +74 -0
- package/skills/cpp/d2h-native-library-build/scripts/check-android-so.py +580 -0
- package/skills/cpp/d2h-native-library-verify/SKILL.md +111 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-build-sign-run.md +67 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/app.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/resources/base/element/string.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/README.md +25 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/build-profile.json5 +24 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/build-profile.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/hvigorfile.ts +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/oh-package.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/entryability/EntryAbility.ets +5 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/Index.ets +3 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/NativeVerifyDemo.ets +276 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/module.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/color.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/string.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/profile/main_pages.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/FFmpegVerify.test.ets +176 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/List.test.ets +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/module.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigor/hvigor-config.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigorfile.ts +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/oh-package.json5 +9 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-template.md +176 -0
- package/skills/cpp/d2h-native-library-verify/references/hypium-test-template.md +163 -0
- package/skills/cpp/d2h-native-library-verify/scripts/__pycache__/native_common.cpython-313.pyc +0 -0
- package/skills/cpp/d2h-native-library-verify/scripts/device-verify.py +304 -0
- package/skills/cpp/d2h-native-library-verify/scripts/native_common.py +341 -0
- package/skills/cpp/d2h-native-library-verify/scripts/verify-ohos-native.py +1099 -0
- package/skills/d2h-app-solution/SKILL.md +198 -0
- package/skills/d2h-app-solution/agents/openai.yaml +4 -0
- package/skills/d2h-app-solution/references/framework-evidence.md +117 -0
- package/skills/d2h-app-solution/references/hierarchy-rules.md +88 -0
- package/skills/d2h-app-solution/references/inventory-schema.md +305 -0
- package/skills/d2h-app-solution/references/technical-analysis.md +101 -0
- package/skills/d2h-app-solution/scripts/scan_repositories.py +350 -0
- package/skills/d2h-app-solution/scripts/validate_inventory.py +337 -0
- package/skills/d2h-architecture-plan/SKILL.md +35 -0
- package/skills/d2h-architecture-plan/references/directory-creation.md +43 -0
- package/skills/d2h-architecture-plan/references/hybrid-design.md +84 -0
- package/skills/d2h-architecture-plan/references/layout-reference.md +83 -0
- package/skills/d2h-architecture-plan/references/plan-contract.md +51 -0
- package/skills/d2h-architecture-plan/references/source-review.md +50 -0
- package/skills/d2h-cloud-knowledge/SKILL.md +28 -7
- package/skills/d2h-cloud-knowledge/kb_query.mjs +8 -0
- package/skills/d2h-dashboard/SKILL.md +56 -0
- package/skills/d2h-product-family-migration/SKILL.md +103 -0
- package/skills/d2h-product-family-migration/agents/openai.yaml +3 -0
- package/skills/d2h-product-family-migration/assets/incremental-plan.template.md +55 -0
- package/skills/d2h-product-family-migration/references/advanced/behavioral-verification.md +59 -0
- package/skills/d2h-product-family-migration/references/advanced/existing-skill-integration.md +30 -0
- package/skills/d2h-product-family-migration/references/advanced/packaging-and-verification.md +43 -0
- package/skills/d2h-product-family-migration/references/advanced/platform-probes.md +26 -0
- package/skills/d2h-product-family-migration/references/advanced/product-line-design.md +40 -0
- package/skills/d2h-product-family-migration/references/baseline-and-delta.md +50 -0
- package/skills/d2h-product-family-migration/tests/markdown-cases.md +76 -0
- package/skills/{d2h-page-develop → d2h-spec-develop}/SKILL.md +143 -126
- package/skills/d2h-spec-develop/agents/openai.yaml +7 -0
- package/skills/d2h-spec-develop/references/worker-workflow.md +26 -0
- package/skills/{d2h-page-spec-generate → d2h-spec-generate}/SKILL.md +24 -13
- package/skills/d2h-spec-generate/assets/spec-template.md +109 -0
- package/skills/d2h-spec-generate/references/from-plan.md +19 -0
- package/skills/d2h-spec-generate/references/spec-content.md +34 -0
- package/skills/{d2h-page-spec-generate → d2h-spec-generate}/scripts/build_batch_report.py +35 -84
- package/skills/d2h-spec-plan/SKILL.md +38 -0
- package/skills/d2h-spec-plan/agents/openai.yaml +4 -0
- package/skills/d2h-spec-plan/references/architecture-handoff.md +9 -0
- package/skills/d2h-spec-plan/references/plan-contract.md +13 -0
- package/skills/d2h-spec-plan/references/planning-method.md +46 -0
- package/skills/d2h-spec-plan/references/round-scope-assessment.md +49 -0
- package/skills/d2h-spec-plan/references/work-packets.md +61 -0
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +2 -2
- package/skills/d2h-utg-ai-generate/references/page-schema.json +2 -2
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +2 -2
- package/skills/d2h-utg-static-generate/SKILL.md +2 -2
- package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
- package/skills/managing-d2h-toolchain/toolchain.json +1 -1
- package/skills/preparing-migration-workspace/SKILL.md +32 -2
- package/workflow/__pycache__/artifact_bindings.cpython-313.pyc +0 -0
- package/workflow/__pycache__/spec_runtime.cpython-313.pyc +0 -0
- package/workflow/artifact_bindings.py +224 -0
- package/workflow/assets/dashboard.html +88 -0
- package/workflow/build_batch_report.py +8 -0
- package/workflow/collect_code_metrics.py +75 -0
- package/workflow/dashboard_overview.py +109 -0
- package/workflow/delivery_view.py +82 -0
- package/workflow/feature_plan.py +234 -0
- package/workflow/page_batches.py +223 -0
- package/workflow/spec_generation.py +120 -0
- package/workflow/spec_runtime.py +890 -0
- package/workflow/work_packets.py +85 -0
- package/skills/d2h-page-develop/agents/openai.yaml +0 -7
- package/skills/harmonyos-migration-playbook/SKILL.md +0 -83
- package/skills/harmonyos-migration-playbook/agents/openai.yaml +0 -7
- package/skills/harmonyos-migration-playbook/references/arkts-compile.md +0 -24
- package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +0 -53
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +0 -45
- package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +0 -23
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +0 -53
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +0 -38
- package/skills/native-library-substitution/SKILL.md +0 -385
- package/skills/native-library-substitution/references/native-library-substitution.json +0 -56906
- package/skills/native-library-substitution/references/native-library-substitution.md +0 -163
- package/skills/porting-cmake-native-libraries/SKILL.md +0 -57
- package/skills/porting-cmake-native-libraries/references/platform-adaptation.md +0 -49
- package/skills/porting-cmake-native-libraries/references/report-and-build.md +0 -60
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""真机验证编排:准备 demo 工程 → 编译 → 签名 → 安装 → 运行 → 采集证据。
|
|
3
|
+
|
|
4
|
+
让 verify 的静态能力判定得到真机运行证据支撑(14 个 Hypium 用例真实跑通)。
|
|
5
|
+
|
|
6
|
+
流程:
|
|
7
|
+
① 准备 demo 工程(复制模板 + 补 media 图标 + 生成测试 wav + 集成测试用例 + 替换待测 so)
|
|
8
|
+
② ohpm install(装 @sj/ffmpeg 依赖)
|
|
9
|
+
③ hvigorw 编译 app + ohosTest 两个 HAP(模板已带签名材料,自动签名)
|
|
10
|
+
④ hdc install 两个 HAP
|
|
11
|
+
⑤ aa test 运行 Hypium 用例
|
|
12
|
+
⑥ hilog 采集 NATIVE_TEST 证据 → run.log
|
|
13
|
+
|
|
14
|
+
用法:
|
|
15
|
+
python3 device-verify.py --demo <demo工程目录> --test-ets <FFmpegVerify.test.ets> \
|
|
16
|
+
--so <待测so> [--media-source <带media图标的工程>] [--device <序列号>] --output <run.log>
|
|
17
|
+
"""
|
|
18
|
+
import argparse
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import wave
|
|
25
|
+
import struct
|
|
26
|
+
import math
|
|
27
|
+
|
|
28
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
29
|
+
TEMPLATE = os.path.join(HERE, "..", "references", "demo-project-template")
|
|
30
|
+
|
|
31
|
+
TOOL_PATHS = {
|
|
32
|
+
"ohpm": ["/Applications/DevEco-Studio.app/Contents/tools/ohpm/bin/ohpm"],
|
|
33
|
+
"hvigorw": ["/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js"],
|
|
34
|
+
"hdc": ["/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc"],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def find_tool(name):
|
|
39
|
+
p = shutil.which(name)
|
|
40
|
+
if p:
|
|
41
|
+
return p
|
|
42
|
+
for c in TOOL_PATHS.get(name, []):
|
|
43
|
+
if os.path.isfile(c):
|
|
44
|
+
return c
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run(cmd, cwd=None, shell=True, env=None):
|
|
49
|
+
return subprocess.run(cmd, shell=shell, cwd=cwd, stdout=subprocess.PIPE,
|
|
50
|
+
stderr=subprocess.STDOUT, text=True, errors="replace", env=env)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def gen_test_wav(path, seconds=2, rate=44100):
|
|
54
|
+
"""生成 2 秒 440Hz 正弦波 wav(测试音频,供 FFAudioPlayer / 转码用例)。"""
|
|
55
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
56
|
+
with wave.open(path, "w") as w:
|
|
57
|
+
w.setnchannels(1)
|
|
58
|
+
w.setsampwidth(2)
|
|
59
|
+
w.setframerate(rate)
|
|
60
|
+
frames = bytearray()
|
|
61
|
+
for i in range(rate * seconds):
|
|
62
|
+
v = int(12000 * math.sin(2 * math.pi * 440 * i / rate))
|
|
63
|
+
frames += struct.pack("<h", v)
|
|
64
|
+
w.writeframes(bytes(frames))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def prepare_demo(demo_dir, test_ets, so_path, media_source):
|
|
68
|
+
"""从模板复制 demo 工程,补 media 图标、wav、测试用例、待测 so。"""
|
|
69
|
+
if not os.path.isdir(TEMPLATE):
|
|
70
|
+
print("demo 模板不存在:", TEMPLATE)
|
|
71
|
+
return False
|
|
72
|
+
print(f"① 准备 demo 工程: {demo_dir}")
|
|
73
|
+
if os.path.exists(demo_dir):
|
|
74
|
+
shutil.rmtree(demo_dir)
|
|
75
|
+
shutil.copytree(TEMPLATE, demo_dir)
|
|
76
|
+
|
|
77
|
+
# 补 media 图标(从 media_source 工程复制)
|
|
78
|
+
if media_source and os.path.isdir(media_source):
|
|
79
|
+
for rel in [
|
|
80
|
+
"AppScope/resources/base/media",
|
|
81
|
+
"entry/src/main/resources/base/media",
|
|
82
|
+
]:
|
|
83
|
+
src = os.path.join(media_source, rel)
|
|
84
|
+
dst = os.path.join(demo_dir, rel)
|
|
85
|
+
if os.path.isdir(src):
|
|
86
|
+
os.makedirs(dst, exist_ok=True)
|
|
87
|
+
for fn in os.listdir(src):
|
|
88
|
+
shutil.copy2(os.path.join(src, fn), os.path.join(dst, fn))
|
|
89
|
+
print(f" 补 media: {rel}")
|
|
90
|
+
# 修正 app.json5 的 icon 引用(模板引用 app_icon,但分层图标是 layered_image)
|
|
91
|
+
app_json = os.path.join(demo_dir, "AppScope", "app.json5")
|
|
92
|
+
if os.path.isfile(app_json):
|
|
93
|
+
s = open(app_json).read().replace("$media:app_icon", "$media:layered_image")
|
|
94
|
+
open(app_json, "w").write(s)
|
|
95
|
+
|
|
96
|
+
# 生成测试 wav
|
|
97
|
+
wav_path = os.path.join(demo_dir, "entry/src/main/resources/rawfile/ffmpeg_test.wav")
|
|
98
|
+
gen_test_wav(wav_path)
|
|
99
|
+
print(" 生成测试 wav: rawfile/ffmpeg_test.wav")
|
|
100
|
+
|
|
101
|
+
# 集成测试用例
|
|
102
|
+
dst_test = os.path.join(demo_dir, "entry/src/ohosTest/ets/test/FFmpegVerify.test.ets")
|
|
103
|
+
shutil.copy2(test_ets, dst_test)
|
|
104
|
+
print(" 集成测试用例: FFmpegVerify.test.ets")
|
|
105
|
+
|
|
106
|
+
# 自动回填签名材料(模板是占位符;从本机 ~/.ohos/config/ + 已签名工程取真实材料)
|
|
107
|
+
fill_signing_configs(demo_dir)
|
|
108
|
+
|
|
109
|
+
# 记录待测 so(编译后替换 oh_modules 里的 so)
|
|
110
|
+
demo_dir_meta = os.path.join(demo_dir, ".verify-so.txt")
|
|
111
|
+
with open(demo_dir_meta, "w") as f:
|
|
112
|
+
f.write(so_path)
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def fill_signing_configs(demo_dir):
|
|
117
|
+
"""模板签名配置是占位符;从本机 DevEco 自动签名材料(~/.ohos/config/)+ 已签名工程的
|
|
118
|
+
build-profile.json5 回填真实路径与密文密码(机制同 references/demo-build-sign-run.md)。"""
|
|
119
|
+
bp = os.path.join(demo_dir, "build-profile.json5")
|
|
120
|
+
if not os.path.isfile(bp):
|
|
121
|
+
return
|
|
122
|
+
s = open(bp, encoding="utf-8").read()
|
|
123
|
+
if "<SIGNING_DIR>" not in s:
|
|
124
|
+
return # 已有真实材料(如 --demo 指向已配置好的工程)
|
|
125
|
+
cfg_dir = os.path.expanduser("~/.ohos/config")
|
|
126
|
+
if not os.path.isdir(cfg_dir):
|
|
127
|
+
print(" ⚠ 未找到 ~/.ohos/config 签名材料,真机验证可能编译失败(参考 references/demo-build-sign-run.md 手动配置)")
|
|
128
|
+
return
|
|
129
|
+
# 找与 bundleName 匹配的最新一套 .cer/.p12/.p7b(default_<Bundle>_<hash>.xxx)
|
|
130
|
+
app_json = os.path.join(demo_dir, "AppScope", "app.json5")
|
|
131
|
+
bundle = ""
|
|
132
|
+
if os.path.isfile(app_json):
|
|
133
|
+
m = re.search(r'"bundleName"\s*:\s*"([^"]+)"', open(app_json, encoding="utf-8").read())
|
|
134
|
+
if m:
|
|
135
|
+
bundle = m.group(1)
|
|
136
|
+
stem = bundle.split(".")[-1] if bundle else ""
|
|
137
|
+
candidates = []
|
|
138
|
+
for fn in os.listdir(cfg_dir):
|
|
139
|
+
if fn.endswith(".p7b") and (not stem or stem in fn):
|
|
140
|
+
p7b = os.path.join(cfg_dir, fn)
|
|
141
|
+
base = p7b[:-4]
|
|
142
|
+
if os.path.isfile(base + ".cer") and os.path.isfile(base + ".p12"):
|
|
143
|
+
candidates.append((os.path.getmtime(p7b), base))
|
|
144
|
+
if not candidates:
|
|
145
|
+
# 兜底:不带 bundle 过滤,取最新一套
|
|
146
|
+
for fn in os.listdir(cfg_dir):
|
|
147
|
+
if fn.endswith(".p7b"):
|
|
148
|
+
p7b = os.path.join(cfg_dir, fn)
|
|
149
|
+
base = p7b[:-4]
|
|
150
|
+
if os.path.isfile(base + ".cer") and os.path.isfile(base + ".p12"):
|
|
151
|
+
candidates.append((os.path.getmtime(p7b), base))
|
|
152
|
+
if not candidates:
|
|
153
|
+
print(" ⚠ ~/.ohos/config 无完整签名三件套(.cer/.p12/.p7b),真机验证可能编译失败")
|
|
154
|
+
return
|
|
155
|
+
_, base = max(candidates)
|
|
156
|
+
# 密文密码无法自造:从本机已签名工程的 build-profile.json5 里取同材料(hash 相同)的密文
|
|
157
|
+
pw = ""
|
|
158
|
+
stem_hash = os.path.basename(base).split("_", 2)[-1] if base.count("_") >= 2 else ""
|
|
159
|
+
for dp, dns, fns in os.walk(os.path.expanduser("~/Desktop")):
|
|
160
|
+
dns[:] = [d for d in dns if d not in ("oh_modules", "build", "node_modules", ".git")]
|
|
161
|
+
if os.path.basename(dp) != "":
|
|
162
|
+
for fn in fns:
|
|
163
|
+
if fn == "build-profile.json5" and stem_hash and stem_hash in open(os.path.join(dp, fn), encoding="utf-8", errors="replace").read(1 << 16):
|
|
164
|
+
mp = re.search(r"keyPassword:\s*'([0-9a-f]+)'", open(os.path.join(dp, fn), encoding="utf-8", errors="replace").read(1 << 16))
|
|
165
|
+
if mp:
|
|
166
|
+
pw = mp.group(1)
|
|
167
|
+
break
|
|
168
|
+
if pw:
|
|
169
|
+
break
|
|
170
|
+
if not pw:
|
|
171
|
+
print(" ⚠ 未能从本机已签名工程取回密文密码,真机验证可能编译失败(参考 demo-build-sign-run.md)")
|
|
172
|
+
return
|
|
173
|
+
s = (s.replace("<SIGNING_DIR>/<project>.cer", base + ".cer")
|
|
174
|
+
.replace("<SIGNING_DIR>/<project>.p7b", base + ".p7b")
|
|
175
|
+
.replace("<SIGNING_DIR>/<project>.p12", base + ".p12")
|
|
176
|
+
.replace("<KEY_PASSWORD>", pw)
|
|
177
|
+
.replace("<STORE_PASSWORD>", pw))
|
|
178
|
+
open(bp, "w", encoding="utf-8").write(s)
|
|
179
|
+
print(f" 回填签名材料: {os.path.basename(base)}")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def ohpm_install(demo_dir):
|
|
183
|
+
ohpm = find_tool("ohpm")
|
|
184
|
+
if not ohpm:
|
|
185
|
+
print("警告:未找到 ohpm,跳过依赖安装(假设已存在)")
|
|
186
|
+
return True
|
|
187
|
+
print("② ohpm install @sj/ffmpeg ...")
|
|
188
|
+
r = run(f'"{ohpm}" install', cwd=demo_dir)
|
|
189
|
+
if r.returncode != 0:
|
|
190
|
+
print(" ohpm install 输出:", r.stdout[-500:])
|
|
191
|
+
return r.returncode == 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def replace_so(demo_dir, so_path):
|
|
195
|
+
"""把待测 so 替换到 oh_modules 的 libffmpeg.so。"""
|
|
196
|
+
if not so_path or not os.path.isfile(so_path):
|
|
197
|
+
return
|
|
198
|
+
targets = []
|
|
199
|
+
for dp, dns, fns in os.walk(os.path.join(demo_dir, "oh_modules")):
|
|
200
|
+
for fn in fns:
|
|
201
|
+
if fn == "libffmpeg.so":
|
|
202
|
+
targets.append(os.path.join(dp, fn))
|
|
203
|
+
if targets:
|
|
204
|
+
for t in targets:
|
|
205
|
+
shutil.copy2(so_path, t)
|
|
206
|
+
print(f" 替换待测 so: {len(targets)} 处")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def build(demo_dir):
|
|
210
|
+
hvigorw = find_tool("hvigorw")
|
|
211
|
+
if not hvigorw:
|
|
212
|
+
print("错误:未找到 hvigorw(需安装 DevEco Studio)")
|
|
213
|
+
return False
|
|
214
|
+
node = shutil.which("node")
|
|
215
|
+
env = dict(os.environ)
|
|
216
|
+
env["DEVECO_SDK_HOME"] = env.get("DEVECO_SDK_HOME", "/Applications/DevEco-Studio.app/Contents/sdk")
|
|
217
|
+
# 先停残留 daemon(避免上次编译的 daemon cwd 已被删导致的 ENOENT)
|
|
218
|
+
run(f'{node} "{hvigorw}" --stop-daemon', cwd=demo_dir, env=env)
|
|
219
|
+
print("③ 编译 app HAP ...")
|
|
220
|
+
r1 = run(f'{node} "{hvigorw}" --mode module -p product=default assembleHap',
|
|
221
|
+
cwd=demo_dir, env=env)
|
|
222
|
+
if r1.returncode != 0:
|
|
223
|
+
print(" app 编译失败:", r1.stdout[-800:])
|
|
224
|
+
return False
|
|
225
|
+
print(" 编译 ohosTest HAP ...")
|
|
226
|
+
r2 = run(f'{node} "{hvigorw}" --mode module -p module=entry@ohosTest -p product=default assembleHap',
|
|
227
|
+
cwd=demo_dir, env=env)
|
|
228
|
+
if r2.returncode != 0:
|
|
229
|
+
print(" ohosTest 编译失败:", r2.stdout[-800:])
|
|
230
|
+
return False
|
|
231
|
+
return True
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def install_and_run(demo_dir, device, bundle="com.example.protobufdemo", module="entry_test"):
|
|
235
|
+
hdc = find_tool("hdc")
|
|
236
|
+
if not hdc:
|
|
237
|
+
print("错误:未找到 hdc")
|
|
238
|
+
return False
|
|
239
|
+
if not device:
|
|
240
|
+
r = run(f'"{hdc}" list targets')
|
|
241
|
+
lines = [l.strip() for l in r.stdout.splitlines() if l.strip()]
|
|
242
|
+
if not lines:
|
|
243
|
+
print("错误:无连接设备")
|
|
244
|
+
return False
|
|
245
|
+
device = lines[0]
|
|
246
|
+
app_hap = os.path.join(demo_dir, "entry/build/default/outputs/default/entry-default-signed.hap")
|
|
247
|
+
test_hap = os.path.join(demo_dir, "entry/build/default/outputs/ohosTest/entry-ohosTest-signed.hap")
|
|
248
|
+
print(f"④ 安装 HAP 到设备 {device} ...")
|
|
249
|
+
run(f'"{hdc}" -t {device} install -r "{app_hap}"')
|
|
250
|
+
run(f'"{hdc}" -t {device} install -r "{test_hap}"')
|
|
251
|
+
print("⑤ 运行 Hypium 测试 ...")
|
|
252
|
+
r = run(f'"{hdc}" -t {device} shell aa test -b {bundle} -m {module} '
|
|
253
|
+
f'-s unittest OpenHarmonyTestRunner -s timeout 120000')
|
|
254
|
+
print(" 测试结果:", r.stdout.strip().splitlines()[-1] if r.stdout.strip() else "(空)")
|
|
255
|
+
return True
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def collect(device, output):
|
|
259
|
+
hdc = find_tool("hdc")
|
|
260
|
+
if not hdc or not device:
|
|
261
|
+
return False
|
|
262
|
+
r = run(f'"{hdc}" -t {device} shell "hilog -x"')
|
|
263
|
+
lines = [l for l in r.stdout.splitlines()
|
|
264
|
+
if "NATIVE_TEST" in l or "OHOS_REPORT_RESULT" in l]
|
|
265
|
+
if lines:
|
|
266
|
+
os.makedirs(os.path.dirname(output), exist_ok=True)
|
|
267
|
+
with open(output, "w") as f:
|
|
268
|
+
f.write("\n".join(lines) + "\n")
|
|
269
|
+
print(f"⑥ 采集证据: {output}({len(lines)} 行)")
|
|
270
|
+
return True
|
|
271
|
+
print("警告:未采集到 NATIVE_TEST 日志")
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def main():
|
|
276
|
+
ap = argparse.ArgumentParser(description="真机验证编排")
|
|
277
|
+
ap.add_argument("--demo", required=True, help="demo 工程目录")
|
|
278
|
+
ap.add_argument("--test-ets", required=True, help="Hypium 测试用例文件")
|
|
279
|
+
ap.add_argument("--so", required=True, help="待测 so 路径")
|
|
280
|
+
ap.add_argument("--media-source", help="提供 media 图标的工程目录")
|
|
281
|
+
ap.add_argument("--device", help="设备序列号")
|
|
282
|
+
ap.add_argument("--output", required=True, help="证据输出 run.log")
|
|
283
|
+
args = ap.parse_args()
|
|
284
|
+
|
|
285
|
+
if not prepare_demo(args.demo, args.test_ets, args.so, args.media_source):
|
|
286
|
+
return 1
|
|
287
|
+
ohpm_install(args.demo)
|
|
288
|
+
replace_so(args.demo, args.so)
|
|
289
|
+
if not build(args.demo):
|
|
290
|
+
return 1
|
|
291
|
+
# 拿到设备序列号(install_and_run 内部可能解析)
|
|
292
|
+
hdc = find_tool("hdc")
|
|
293
|
+
device = args.device
|
|
294
|
+
if not device and hdc:
|
|
295
|
+
r = run(f'"{hdc}" list targets')
|
|
296
|
+
lines = [l.strip() for l in r.stdout.splitlines() if l.strip()]
|
|
297
|
+
device = lines[0] if lines else None
|
|
298
|
+
install_and_run(args.demo, device)
|
|
299
|
+
collect(device, args.output)
|
|
300
|
+
return 0
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
sys.exit(main())
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""d2h-native-library 两个 skill 共享的工具/能力检测模块。
|
|
3
|
+
|
|
4
|
+
供 d2h-native-library-analyse 和 d2h-native-library-verify 复用:
|
|
5
|
+
- 工具发现(llvm-nm / llvm-readelf / c++filt,自动探测 DevEco SDK / Android NDK 路径)
|
|
6
|
+
- 场景分类(classify_feature,Java 类名 → 功能场景)
|
|
7
|
+
- 能力检测(CAPABILITY_RULES:从 so/a 导出符号检测真实能力)
|
|
8
|
+
- 符号 demangle(C++ mangled 名 → 可读的 FFAV::FFAudioPlayer::Play(...))
|
|
9
|
+
"""
|
|
10
|
+
import html as _html
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
|
|
16
|
+
# ==================== 工具发现 ====================
|
|
17
|
+
TOOL_INSTALL_HINTS = {
|
|
18
|
+
"llvm-nm": "① DevEco Studio(推荐)https://developer.huawei.com/consumer/cn/deveco-studio/ "
|
|
19
|
+
"② macOS: brew install llvm ③ Linux: sudo apt install binutils",
|
|
20
|
+
"llvm-readelf": "① DevEco Studio(推荐)https://developer.huawei.com/consumer/cn/deveco-studio/ "
|
|
21
|
+
"② macOS: brew install llvm ③ Linux: sudo apt install binutils",
|
|
22
|
+
"c++filt": "① DevEco Studio(自带 llvm-cxxfilt) ② macOS: 系统自带 /usr/bin/c++filt ③ Linux: sudo apt install binutils",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run(cmd):
|
|
27
|
+
try:
|
|
28
|
+
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
29
|
+
text=True, errors="replace").stdout
|
|
30
|
+
except OSError:
|
|
31
|
+
return ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def sdk_bin_dirs():
|
|
35
|
+
"""返回可能包含 llvm-nm/llvm-readelf/c++filt 的 SDK bin 目录列表。"""
|
|
36
|
+
dirs = []
|
|
37
|
+
home = os.path.expanduser("~")
|
|
38
|
+
|
|
39
|
+
def add(d):
|
|
40
|
+
if d and os.path.isdir(d):
|
|
41
|
+
dirs.append(d)
|
|
42
|
+
|
|
43
|
+
# Android NDK
|
|
44
|
+
for env in ("ANDROID_NDK_HOME", "ANDROID_NDK_ROOT"):
|
|
45
|
+
v = os.environ.get(env)
|
|
46
|
+
if v:
|
|
47
|
+
add(os.path.join(v, "toolchains/llvm/prebuilt"))
|
|
48
|
+
sdk = os.environ.get("ANDROID_HOME")
|
|
49
|
+
if sdk:
|
|
50
|
+
ndk = os.path.join(sdk, "ndk")
|
|
51
|
+
if os.path.isdir(ndk):
|
|
52
|
+
for v in os.listdir(ndk):
|
|
53
|
+
add(os.path.join(ndk, v, "toolchains/llvm/prebuilt"))
|
|
54
|
+
# DevEco / OpenHarmony SDK
|
|
55
|
+
for env in ("OHOS_SDK_HOME", "DEVECO_SDK_HOME"):
|
|
56
|
+
v = os.environ.get(env)
|
|
57
|
+
if v:
|
|
58
|
+
for sub in ("default/openharmony/native/llvm/bin",
|
|
59
|
+
"openharmony/native/llvm/bin", "native/llvm/bin"):
|
|
60
|
+
add(os.path.join(v, sub))
|
|
61
|
+
add("/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/llvm/bin")
|
|
62
|
+
add("/Applications/DevEco-Studio.app/Contents/sdk/default/hms/native/BiSheng/bin")
|
|
63
|
+
add(os.path.join(home, "Library/OpenHarmony/Sdk"))
|
|
64
|
+
return dirs
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def resolve_tool(names):
|
|
68
|
+
for n in names:
|
|
69
|
+
p = shutil.which(n)
|
|
70
|
+
if p:
|
|
71
|
+
return p
|
|
72
|
+
for d in sdk_bin_dirs():
|
|
73
|
+
for n in names:
|
|
74
|
+
c = os.path.join(d, n)
|
|
75
|
+
if os.path.isfile(c):
|
|
76
|
+
return c
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_elf_nm(nm_path):
|
|
81
|
+
"""判断 nm 能否解析 ELF:llvm-nm 可以;GNU nm 可以;macOS 旧版 /usr/bin/nm(Mach-O)不行。"""
|
|
82
|
+
if not nm_path:
|
|
83
|
+
return False
|
|
84
|
+
if "llvm-nm" in os.path.basename(nm_path):
|
|
85
|
+
return True
|
|
86
|
+
try:
|
|
87
|
+
out = subprocess.run([nm_path, "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
88
|
+
text=True, errors="replace").stdout
|
|
89
|
+
return "GNU" in out or "llvm-nm" in out
|
|
90
|
+
except Exception:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def find_elf_nm():
|
|
95
|
+
"""找一个能解析 ELF 的 nm 工具(llvm-nm 优先,GNU nm 次之)。找不到返回 None。"""
|
|
96
|
+
p = shutil.which("llvm-nm")
|
|
97
|
+
if p:
|
|
98
|
+
return p
|
|
99
|
+
for d in sdk_bin_dirs():
|
|
100
|
+
c = os.path.join(d, "llvm-nm")
|
|
101
|
+
if os.path.isfile(c):
|
|
102
|
+
return c
|
|
103
|
+
p = shutil.which("nm")
|
|
104
|
+
if p and _is_elf_nm(p):
|
|
105
|
+
return p
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def find_llvm_readelf():
|
|
110
|
+
p = shutil.which("llvm-readelf")
|
|
111
|
+
if p:
|
|
112
|
+
return p
|
|
113
|
+
for d in sdk_bin_dirs():
|
|
114
|
+
c = os.path.join(d, "llvm-readelf")
|
|
115
|
+
if os.path.isfile(c):
|
|
116
|
+
return c
|
|
117
|
+
p = shutil.which("readelf")
|
|
118
|
+
if p:
|
|
119
|
+
return p
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def find_cxxfilt():
|
|
124
|
+
"""找一个 demangle 工具(c++filt 优先,llvm-cxxfilt 次之)。找不到返回 None。"""
|
|
125
|
+
for n in ("c++filt", "llvm-cxxfilt"):
|
|
126
|
+
p = shutil.which(n)
|
|
127
|
+
if p:
|
|
128
|
+
return p
|
|
129
|
+
for d in sdk_bin_dirs():
|
|
130
|
+
for n in ("c++filt", "llvm-cxxfilt"):
|
|
131
|
+
c = os.path.join(d, n)
|
|
132
|
+
if os.path.isfile(c):
|
|
133
|
+
return c
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def find_hdc():
|
|
138
|
+
"""找 hdc(鸿蒙设备命令行工具,DevEco SDK 自带)。找不到返回 None。"""
|
|
139
|
+
p = shutil.which("hdc")
|
|
140
|
+
if p:
|
|
141
|
+
return p
|
|
142
|
+
for d in sdk_bin_dirs():
|
|
143
|
+
c = os.path.join(d, "hdc")
|
|
144
|
+
if os.path.isfile(c):
|
|
145
|
+
return c
|
|
146
|
+
for c in ["/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc",
|
|
147
|
+
"/Applications/DevEco-Studio.app/Contents/sdk/default/hms/toolchains/hdc"]:
|
|
148
|
+
if os.path.isfile(c):
|
|
149
|
+
return c
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def list_devices(hdc=None):
|
|
154
|
+
"""返回已连接的设备序列号列表。无设备返回 []。"""
|
|
155
|
+
hdc = hdc or find_hdc()
|
|
156
|
+
if not hdc:
|
|
157
|
+
return []
|
|
158
|
+
out = run([hdc, "list", "targets"])
|
|
159
|
+
return [l.strip() for l in out.splitlines() if l.strip()]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ==================== 符号 demangle ====================
|
|
163
|
+
def demangle_symbol(sym, cxxfilt=None):
|
|
164
|
+
"""把 C++ mangled 符号 demangle 成可读名字。失败返回原符号。"""
|
|
165
|
+
if not cxxfilt or not sym or not sym.startswith("_Z"):
|
|
166
|
+
return sym
|
|
167
|
+
try:
|
|
168
|
+
out = subprocess.run([cxxfilt, sym], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
|
169
|
+
text=True, errors="replace").stdout
|
|
170
|
+
d = out.strip()
|
|
171
|
+
return d if d and d != sym else sym
|
|
172
|
+
except Exception:
|
|
173
|
+
return sym
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ==================== 场景分类 ====================
|
|
177
|
+
def classify_feature(class_name):
|
|
178
|
+
"""按 Java 类名精确匹配功能场景(与 analyse/verify 共用,避免 Player 子串误伤)。"""
|
|
179
|
+
cls = (class_name or "").lower()
|
|
180
|
+
if "nextplayer" in cls:
|
|
181
|
+
return "视频播放(NextPlayer引擎)"
|
|
182
|
+
if "videoplayer" in cls:
|
|
183
|
+
return "视频滤镜预览(VideoPlayer)"
|
|
184
|
+
if "audioplayer" in cls or "musicplayer" in cls:
|
|
185
|
+
return "音频播放(AudioPlayer)"
|
|
186
|
+
if "ffmpegcmd" in cls or "transcode" in cls:
|
|
187
|
+
return "视频剪辑处理(FFmpegCmd)"
|
|
188
|
+
if "mediaretriever" in cls or "metadata" in cls:
|
|
189
|
+
return "媒体格式探测(MediaRetriever)"
|
|
190
|
+
if "livepusher" in cls or "rtmp" in cls:
|
|
191
|
+
return "直播推流(LivePusher)"
|
|
192
|
+
if "visualizer" in cls or "fft" in cls or "spectrum" in cls:
|
|
193
|
+
return "音频可视化(FrankVisualizer)"
|
|
194
|
+
if "equalizer" in cls:
|
|
195
|
+
return "均衡器(Equalizer)"
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ==================== so/a 符号提取 ====================
|
|
200
|
+
def get_lib_exports(so_path, nm_tool):
|
|
201
|
+
"""提取 .so/.a 的对外符号名集合。
|
|
202
|
+
|
|
203
|
+
- .so:动态导出符号(llvm-nm -D --defined-only --extern-only)
|
|
204
|
+
- .a :全局定义符号(llvm-nm --defined-only;ar 归档无动态符号表,
|
|
205
|
+
取类型为大写字母 T/D/B/R/C 的全局符号)
|
|
206
|
+
"""
|
|
207
|
+
if not nm_tool:
|
|
208
|
+
return set()
|
|
209
|
+
if so_path.endswith(".a"):
|
|
210
|
+
out = run([nm_tool, "--defined-only", so_path])
|
|
211
|
+
exports = set()
|
|
212
|
+
for line in out.splitlines():
|
|
213
|
+
s = line.strip()
|
|
214
|
+
if not s or s.endswith(":"):
|
|
215
|
+
continue
|
|
216
|
+
parts = s.split()
|
|
217
|
+
if len(parts) >= 3 and parts[1] and parts[1][0].isupper():
|
|
218
|
+
exports.add(parts[2])
|
|
219
|
+
return exports
|
|
220
|
+
out = run([nm_tool, "-D", "--defined-only", "--extern-only", so_path])
|
|
221
|
+
exports = set()
|
|
222
|
+
for line in out.splitlines():
|
|
223
|
+
parts = line.split()
|
|
224
|
+
if len(parts) >= 3:
|
|
225
|
+
exports.add(parts[2])
|
|
226
|
+
return exports
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ==================== 能力检测(从 so/a 导出符号判断)====================
|
|
230
|
+
# 每项:(能力名, 符号名正则, 说明)。正则匹配导出符号名,命中任一即证明 so/a 真实具备该能力。
|
|
231
|
+
CAPABILITY_RULES = [
|
|
232
|
+
# FFmpeg 核心库
|
|
233
|
+
("音视频编解码", r"^avcodec_", "avcodec 库(编解码核心)"),
|
|
234
|
+
("封装/解封装", r"^avformat_", "avformat 库(封装/解封装)"),
|
|
235
|
+
("滤镜", r"^avfilter_", "avfilter 库(滤镜)"),
|
|
236
|
+
("视频缩放", r"^swscale_", "swscale 库(像素缩放)"),
|
|
237
|
+
("音频重采样", r"^swresample_", "swresample 库(音频重采样)"),
|
|
238
|
+
("IO", r"^avio_", "avio 库(文件/网络 IO)"),
|
|
239
|
+
# 编码器
|
|
240
|
+
("H.264 编码", r"^ff_libx264_encoder$|^x264_encoder_", "H.264 编码器(so 的 ff_libx264_encoder / .a 的 x264_encoder_*)"),
|
|
241
|
+
("AAC 编码", r"^ff_aac_encoder$|^faacEnc", "AAC 编码器(so 的 ff_aac_encoder / .a 的 faacEnc*)"),
|
|
242
|
+
("AV1 编解码", r"^ff_libaom_av1_", "libaom AV1 编解码器"),
|
|
243
|
+
# 协议与封装
|
|
244
|
+
("RTMP 协议", r"^ff_rtmp_protocol$", "RTMP 推拉流协议"),
|
|
245
|
+
("RTSP 协议", r"^ff_rtsp_protocol$", "RTSP 协议"),
|
|
246
|
+
("HTTP(S) 协议", r"^ff_https?_protocol$", "HTTP/HTTPS 协议"),
|
|
247
|
+
("FLV 封装", r"^ff_flv_muxer$", "FLV 封装器"),
|
|
248
|
+
("MP4 封装", r"^ff_mp4_muxer$", "MP4 封装器"),
|
|
249
|
+
("MPEG-TS 封装", r"^ff_mpegts_muxer$", "MPEG-TS 封装器"),
|
|
250
|
+
("HLS 封装", r"^ff_hls_muxer$", "HLS 封装器"),
|
|
251
|
+
# NAPI 层(@sj/ffmpeg 的 ArkTS 接口)
|
|
252
|
+
("音频播放 NAPI", r"FFAudioPlayer", "FFAudioPlayer NAPI 类(播放/时长/位置/seek)"),
|
|
253
|
+
("音频写入 NAPI", r"FFAudioWriter", "FFAudioWriter NAPI 类(PCM 编码写入)"),
|
|
254
|
+
("任务取消 NAPI", r"FFAbortSignal", "FFAbortController/FFAbortSignal NAPI 类"),
|
|
255
|
+
("命令执行 NAPI", r"FFmpeg", "FFmpeg NAPI(执行 ffmpeg/ffprobe 命令)"),
|
|
256
|
+
# 常见非 FFmpeg 原生库(换一个 so 也能识别)
|
|
257
|
+
("AES 加密", r"^AES_", "OpenSSL AES 加解密"),
|
|
258
|
+
("RSA 非对称", r"^RSA_", "OpenSSL RSA"),
|
|
259
|
+
("X509 证书", r"^X509_", "OpenSSL X509 证书"),
|
|
260
|
+
("TLS/SSL", r"^SSL_|^TLS_", "OpenSSL TLS/SSL"),
|
|
261
|
+
("EVP 加密接口", r"^EVP_", "OpenSSL EVP 高层接口"),
|
|
262
|
+
("SHA 摘要", r"^SHA(1|224|256|384|512)_", "OpenSSL SHA 系列"),
|
|
263
|
+
("OpenSSL BN", r"^BN_", "OpenSSL BN 大数运算"),
|
|
264
|
+
("OpenSSL CRYPTO", r"^CRYPTO_", "OpenSSL CRYPTO"),
|
|
265
|
+
("OpenSSL 3.x(ossl)", r"^ossl_", "OpenSSL 3.x 内部符号"),
|
|
266
|
+
("AMR 编解码", r"^AMREncode|^AMRDecode", "AMR 音频编解码"),
|
|
267
|
+
("MP3 编码(LAME)", r"^lame_", "LAME MP3 编码器"),
|
|
268
|
+
("AV1 编码(libaom)", r"^aom_", "libaom AV1 编码器"),
|
|
269
|
+
("zlib 压缩", r"^(deflate|inflate|compress|uncompress)$", "zlib 压缩"),
|
|
270
|
+
("HTTP 客户端", r"^curl_", "libcurl HTTP 客户端"),
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def detect_capabilities(exports):
|
|
275
|
+
"""从 so/a 导出符号检测能力。返回 {能力名: 命中符号样例}(确定性:sorted 遍历)。"""
|
|
276
|
+
detected = {}
|
|
277
|
+
sorted_exports = sorted(exports)
|
|
278
|
+
for name, pattern, desc in CAPABILITY_RULES:
|
|
279
|
+
rx = re.compile(pattern)
|
|
280
|
+
for s in sorted_exports:
|
|
281
|
+
if rx.search(s):
|
|
282
|
+
detected[name] = s
|
|
283
|
+
break
|
|
284
|
+
return detected
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def cluster_unknown_symbols(exports, top=25):
|
|
288
|
+
"""对未匹配任何能力规则的符号,按前缀聚类,返回 [(前缀, 数量)]。
|
|
289
|
+
|
|
290
|
+
用于自动发现 so 里未纳入规则的库(如 aom_/ossl_/lame_/BN_ 等),
|
|
291
|
+
让未知 so 也有能力线索,不依赖硬编码规则。
|
|
292
|
+
"""
|
|
293
|
+
matched = set()
|
|
294
|
+
for name, pattern, desc in CAPABILITY_RULES:
|
|
295
|
+
rx = re.compile(pattern)
|
|
296
|
+
for s in exports:
|
|
297
|
+
if rx.search(s):
|
|
298
|
+
matched.add(s)
|
|
299
|
+
unmatched = exports - matched
|
|
300
|
+
from collections import Counter
|
|
301
|
+
prefixes = Counter()
|
|
302
|
+
for s in unmatched:
|
|
303
|
+
m = re.match(r"^([A-Za-z_]+)_", s)
|
|
304
|
+
if m:
|
|
305
|
+
prefixes[m.group(1)] += 1
|
|
306
|
+
return prefixes.most_common(top)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# 符号前缀 → 语义(让聚类结果可读;有序匹配,长前缀优先)
|
|
310
|
+
PREFIX_MEANING = [
|
|
311
|
+
("ff_vf", "FFmpeg 视频滤镜"),
|
|
312
|
+
("ff_af", "FFmpeg 音频滤镜"),
|
|
313
|
+
("ff_put", "FFmpeg 像素填充"),
|
|
314
|
+
("ff_pcm", "FFmpeg PCM 格式"),
|
|
315
|
+
("ff_avg", "FFmpeg 像素平均"),
|
|
316
|
+
("ff_tx_tab", "FFmpeg 变换表"),
|
|
317
|
+
("ff", "FFmpeg 内部符号"),
|
|
318
|
+
("cfl", "libaom 帧内预测"),
|
|
319
|
+
("aom", "libaom AV1 编码器"),
|
|
320
|
+
("av", "FFmpeg avutil"),
|
|
321
|
+
("ossl", "OpenSSL 3.x"),
|
|
322
|
+
("OPENSSL", "OpenSSL 3.x"),
|
|
323
|
+
("BN", "OpenSSL BN 大数"),
|
|
324
|
+
("CRYPTO", "OpenSSL CRYPTO"),
|
|
325
|
+
("BIO", "OpenSSL BIO"),
|
|
326
|
+
("PEM", "OpenSSL PEM"),
|
|
327
|
+
("lame", "LAME MP3"),
|
|
328
|
+
("__emutls", "线程本地存储"),
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def prefix_meaning(prefix):
|
|
333
|
+
for key, meaning in PREFIX_MEANING:
|
|
334
|
+
if prefix.startswith(key):
|
|
335
|
+
return meaning
|
|
336
|
+
return ""
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ==================== HTML 工具 ====================
|
|
340
|
+
def esc(v):
|
|
341
|
+
return _html.escape(str(v if v is not None else "-"))
|