@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,580 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""迁移前检查:Android 工程中已有的 .so / .a 能否直接用于 HarmonyOS。
|
|
3
|
+
|
|
4
|
+
跨平台(macOS / Linux / Windows):
|
|
5
|
+
- 不依赖 `file`,文件类型用魔数判断(ELF / ar archive),架构用 llvm-readelf/readelf。
|
|
6
|
+
- 依赖工具优先找 `llvm-readelf` / `llvm-nm` / `llvm-objdump`(Android NDK 与
|
|
7
|
+
DevEco/OpenHarmony SDK 均自带,且三平台同名同输出格式);缺省时自动从常见
|
|
8
|
+
SDK 安装目录(ANDROID_NDK_HOME / ANDROID_HOME / OHOS_SDK_HOME / DevEco 内置 SDK)发现。
|
|
9
|
+
|
|
10
|
+
用法
|
|
11
|
+
----
|
|
12
|
+
python3 check-android-so.py [项目目录] [--json] [--no-build-dirs]
|
|
13
|
+
python3 check-android-so.py <项目目录> --from-facts <facts/native-libs.json> [--json]
|
|
14
|
+
|
|
15
|
+
`--from-facts` 按 AppGraph `facts/native-libs.json` 分片的产物清单只深度检查
|
|
16
|
+
其中列出的 .so/.a(路径相对工程目录),避免与分片扫描器各自重扫导致清单分叉;
|
|
17
|
+
分片的 ①②③ 初分类(usageCase/facts_usage)会一并写入报告供对账。
|
|
18
|
+
|
|
19
|
+
退出码
|
|
20
|
+
------
|
|
21
|
+
0 正常完成(含“存在不可直接用”的库)
|
|
22
|
+
2 缺少必要的工具(llvm-readelf/readelf 与 llvm-nm/nm)或分片不可读
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
import shutil
|
|
30
|
+
import subprocess
|
|
31
|
+
import sys
|
|
32
|
+
|
|
33
|
+
ANDROID_ONLY_NEEDED = {
|
|
34
|
+
"liblog.so",
|
|
35
|
+
"libandroid.so",
|
|
36
|
+
"libnativehelper.so",
|
|
37
|
+
"libjnigraphics.so",
|
|
38
|
+
"libcamera2ndk.so",
|
|
39
|
+
"libmediandk.so",
|
|
40
|
+
"libOpenSLES.so",
|
|
41
|
+
"libaaudio.so",
|
|
42
|
+
"libamidi.so",
|
|
43
|
+
"libbinder_ndk.so",
|
|
44
|
+
"libneuralnetworks.so",
|
|
45
|
+
"libEGL.so",
|
|
46
|
+
"libGLESv1_CM.so",
|
|
47
|
+
"libGLESv2.so",
|
|
48
|
+
"libGLESv3.so",
|
|
49
|
+
"libandroid_runtime.so",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
ANDROID_SYMBOL_PREFIXES = (
|
|
53
|
+
"__android_log",
|
|
54
|
+
"AAssetManager",
|
|
55
|
+
"ANativeWindow",
|
|
56
|
+
"ANativeActivity",
|
|
57
|
+
"AInputQueue",
|
|
58
|
+
"AChoreographer",
|
|
59
|
+
"AHardwareBuffer",
|
|
60
|
+
"ACamera",
|
|
61
|
+
"AMedia",
|
|
62
|
+
"ASensor",
|
|
63
|
+
"AImage",
|
|
64
|
+
"android_",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
EXCLUDE_DIRS = {
|
|
68
|
+
".git", ".hg", ".svn", "node_modules", "oh_modules", ".hvigor",
|
|
69
|
+
".gradle", "build", ".idea", ".cxx", ".externalNativeBuild",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
NATIVE_BUILD_FILES = {"CMakeLists.txt", "Android.mk", "Application.mk", "Makefile", "configure.ac"}
|
|
73
|
+
NATIVE_SOURCE_EXTS = {".c", ".cpp", ".cc", ".cxx", ".h", ".hpp"}
|
|
74
|
+
PATCH_EXTS = {".patch", ".diff"}
|
|
75
|
+
|
|
76
|
+
TOOL_NAMES = {
|
|
77
|
+
"readelf": ["llvm-readelf", "readelf", "greadelf", "readelf.exe", "llvm-readelf.exe"],
|
|
78
|
+
"objdump": ["llvm-objdump", "objdump", "gobjdump", "objdump.exe", "llvm-objdump.exe"],
|
|
79
|
+
"nm": ["llvm-nm", "nm", "gnm", "nm.exe", "llvm-nm.exe"],
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run(cmd):
|
|
84
|
+
try:
|
|
85
|
+
return subprocess.run(
|
|
86
|
+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
87
|
+
text=True, errors="replace",
|
|
88
|
+
).stdout
|
|
89
|
+
except OSError:
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def is_windows():
|
|
94
|
+
return os.name == "nt"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def expand_env(*names):
|
|
98
|
+
for n in names:
|
|
99
|
+
v = os.environ.get(n)
|
|
100
|
+
if v:
|
|
101
|
+
yield v
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def sdk_bin_dirs():
|
|
105
|
+
dirs = []
|
|
106
|
+
|
|
107
|
+
def add_ndk_bin(ndk_root):
|
|
108
|
+
if not ndk_root or not os.path.isdir(ndk_root):
|
|
109
|
+
return
|
|
110
|
+
prebuilt = os.path.join(ndk_root, "toolchains", "llvm", "prebuilt")
|
|
111
|
+
if os.path.isdir(prebuilt):
|
|
112
|
+
for host in os.listdir(prebuilt):
|
|
113
|
+
bin_dir = os.path.join(prebuilt, host, "bin")
|
|
114
|
+
if os.path.isdir(bin_dir):
|
|
115
|
+
dirs.append(bin_dir)
|
|
116
|
+
|
|
117
|
+
for ndk in expand_env("ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "NDK_HOME"):
|
|
118
|
+
add_ndk_bin(ndk)
|
|
119
|
+
|
|
120
|
+
for sdk in expand_env("ANDROID_HOME", "ANDROID_SDK_ROOT"):
|
|
121
|
+
ndk_root = os.path.join(sdk, "ndk")
|
|
122
|
+
if os.path.isdir(ndk_root):
|
|
123
|
+
for ndk in os.listdir(ndk_root):
|
|
124
|
+
add_ndk_bin(os.path.join(ndk_root, ndk))
|
|
125
|
+
|
|
126
|
+
for sdk in expand_env("OHOS_SDK_HOME", "DEVECO_SDK_HOME", "OHOS_NDK_HOME", "OHOS_NATIVE_HOME"):
|
|
127
|
+
for sub in ("llvm/bin", os.path.join("native", "llvm", "bin")):
|
|
128
|
+
d = os.path.join(sdk, sub)
|
|
129
|
+
if os.path.isdir(d):
|
|
130
|
+
dirs.append(d)
|
|
131
|
+
|
|
132
|
+
home = os.path.expanduser("~")
|
|
133
|
+
for base in (
|
|
134
|
+
os.path.join(home, "Library", "OpenHarmony", "Sdk"), # macOS
|
|
135
|
+
os.path.join(home, "AppData", "Local", "OpenHarmony", "Sdk"), # Windows
|
|
136
|
+
os.path.join(home, ".local", "share", "OpenHarmony", "Sdk"), # Linux
|
|
137
|
+
os.path.join(home, "Huawei", "Sdk"),
|
|
138
|
+
):
|
|
139
|
+
if os.path.isdir(base):
|
|
140
|
+
for sub in ("native/llvm/bin", "llvm/bin"):
|
|
141
|
+
d = os.path.join(base, sub)
|
|
142
|
+
if os.path.isdir(d):
|
|
143
|
+
dirs.append(d)
|
|
144
|
+
return dirs
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def resolve_tools():
|
|
148
|
+
tools = {}
|
|
149
|
+
bin_dirs = sdk_bin_dirs()
|
|
150
|
+
for role, names in TOOL_NAMES.items():
|
|
151
|
+
path = None
|
|
152
|
+
for n in names:
|
|
153
|
+
p = shutil.which(n)
|
|
154
|
+
if p:
|
|
155
|
+
path = p
|
|
156
|
+
break
|
|
157
|
+
if not path:
|
|
158
|
+
for d in bin_dirs:
|
|
159
|
+
for n in names:
|
|
160
|
+
candidate = os.path.join(d, n)
|
|
161
|
+
if os.path.isfile(candidate):
|
|
162
|
+
path = candidate
|
|
163
|
+
break
|
|
164
|
+
if path:
|
|
165
|
+
break
|
|
166
|
+
tools[role] = path
|
|
167
|
+
return tools
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def file_kind(path):
|
|
171
|
+
with open(path, "rb") as f:
|
|
172
|
+
head = f.read(8)
|
|
173
|
+
if head.startswith(b"\x7fELF"):
|
|
174
|
+
return "elf"
|
|
175
|
+
if head.startswith(b"!<arch>\n"):
|
|
176
|
+
return "ar"
|
|
177
|
+
return "unknown"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def map_machine(machine):
|
|
181
|
+
machine = machine.strip()
|
|
182
|
+
if machine == "AArch64":
|
|
183
|
+
return "arm64-v8a", 64
|
|
184
|
+
if machine == "ARM":
|
|
185
|
+
return "armeabi-v7a", 32
|
|
186
|
+
if machine in ("X86-64", "Advanced Micro Devices X86-64", "AMD x86-64"):
|
|
187
|
+
return "x86_64", 64
|
|
188
|
+
if machine in ("Intel 80386", "i386", "80386"):
|
|
189
|
+
return "x86", 32
|
|
190
|
+
if "LoongArch" in machine:
|
|
191
|
+
return "loongarch64", 64
|
|
192
|
+
if "RISC-V" in machine:
|
|
193
|
+
return "riscv64", 64
|
|
194
|
+
return "unknown", None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def read_arch_readelf(out):
|
|
198
|
+
bits = None
|
|
199
|
+
m = re.search(r"Class:\s*(ELF64|ELF32)", out)
|
|
200
|
+
if m:
|
|
201
|
+
bits = 64 if m.group(1) == "ELF64" else 32
|
|
202
|
+
machine = ""
|
|
203
|
+
m = re.search(r"Machine:\s*(.+)", out)
|
|
204
|
+
if m:
|
|
205
|
+
machine = m.group(1)
|
|
206
|
+
arch, arch_bits = map_machine(machine)
|
|
207
|
+
if arch != "unknown":
|
|
208
|
+
return arch, bits if bits is not None else arch_bits
|
|
209
|
+
return arch, bits
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def read_arch_objdump(out):
|
|
213
|
+
m = re.search(r"architecture:\s*([^\s,]+)", out)
|
|
214
|
+
if m:
|
|
215
|
+
arch, bits = map_machine(m.group(1))
|
|
216
|
+
if arch != "unknown":
|
|
217
|
+
return arch, bits
|
|
218
|
+
return "unknown", None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def inspect_elf(path, tools):
|
|
222
|
+
info = {
|
|
223
|
+
"path": path,
|
|
224
|
+
"kind": "so",
|
|
225
|
+
"arch": "unknown",
|
|
226
|
+
"bits": None,
|
|
227
|
+
"soname": None,
|
|
228
|
+
"needed": [],
|
|
229
|
+
"android_needed": [],
|
|
230
|
+
"jni": False,
|
|
231
|
+
"jni_symbols": [],
|
|
232
|
+
"bionic_versioned": False,
|
|
233
|
+
"android_symbols": [],
|
|
234
|
+
"android_signals": [],
|
|
235
|
+
"direct_usable": None,
|
|
236
|
+
"reason": "",
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
arch_out = run([tools["readelf"], "-h", path]) if tools["readelf"] else ""
|
|
240
|
+
if arch_out and "ELF Header" in arch_out:
|
|
241
|
+
info["arch"], info["bits"] = read_arch_readelf(arch_out)
|
|
242
|
+
elif tools["objdump"]:
|
|
243
|
+
info["arch"], info["bits"] = read_arch_objdump(run([tools["objdump"], "-f", path]))
|
|
244
|
+
|
|
245
|
+
dyn_out = run([tools["readelf"], "-d", path]) if tools["readelf"] else ""
|
|
246
|
+
needed = re.findall(r"\(NEEDED\)\s+Shared library:\s*\[([^\]]+)\]", dyn_out)
|
|
247
|
+
if not needed and tools["objdump"]:
|
|
248
|
+
objdump_out = run([tools["objdump"], "-p", path])
|
|
249
|
+
needed = re.findall(r"^\s*NEEDED\s+(\S+)", objdump_out, re.MULTILINE)
|
|
250
|
+
for lib in needed:
|
|
251
|
+
info["needed"].append(lib)
|
|
252
|
+
if lib in ANDROID_ONLY_NEEDED:
|
|
253
|
+
info["android_needed"].append(lib)
|
|
254
|
+
|
|
255
|
+
m = re.search(r"\(SONAME\)\s+Library soname:\s*\[([^\]]+)\]", dyn_out)
|
|
256
|
+
if m:
|
|
257
|
+
info["soname"] = m.group(1)
|
|
258
|
+
elif tools["objdump"]:
|
|
259
|
+
objdump_out = run([tools["objdump"], "-p", path])
|
|
260
|
+
m = re.search(r"^\s*SONAME\s+(\S+)", objdump_out, re.MULTILINE)
|
|
261
|
+
if m:
|
|
262
|
+
info["soname"] = m.group(1)
|
|
263
|
+
|
|
264
|
+
if tools["nm"]:
|
|
265
|
+
defined = run([tools["nm"], "-D", "--defined-only", "--extern-only", "-j", path])
|
|
266
|
+
for sym in defined.splitlines():
|
|
267
|
+
sym = sym.strip()
|
|
268
|
+
if sym.startswith("Java_") or sym in ("JNI_OnLoad", "JNI_OnUnload"):
|
|
269
|
+
info["jni"] = True
|
|
270
|
+
info["jni_symbols"].append(sym)
|
|
271
|
+
|
|
272
|
+
undefined = run([tools["nm"], "-D", "-u", "-P", path])
|
|
273
|
+
for line in undefined.splitlines():
|
|
274
|
+
parts = line.split()
|
|
275
|
+
sym = parts[0] if parts else ""
|
|
276
|
+
if "@" in sym:
|
|
277
|
+
info["bionic_versioned"] = True
|
|
278
|
+
if any(sym.startswith(p) for p in ANDROID_SYMBOL_PREFIXES):
|
|
279
|
+
info["android_symbols"].append(sym)
|
|
280
|
+
|
|
281
|
+
if info["jni"]:
|
|
282
|
+
info["android_signals"].append("JNI 桥接(Java_* / JNI_OnLoad)")
|
|
283
|
+
if info["android_needed"]:
|
|
284
|
+
info["android_signals"].append("依赖 Android 系统库:" + ", ".join(info["android_needed"]))
|
|
285
|
+
if info["bionic_versioned"]:
|
|
286
|
+
info["android_signals"].append("符号带 Bionic 版本(@LIBC/@LIBLOG…)")
|
|
287
|
+
if info["android_symbols"]:
|
|
288
|
+
info["android_signals"].append("引用 Android NDK 专用符号:" + ", ".join(sorted(set(info["android_symbols"]))[:5]))
|
|
289
|
+
|
|
290
|
+
is_android = bool(info["android_signals"])
|
|
291
|
+
if is_android:
|
|
292
|
+
info["direct_usable"] = False
|
|
293
|
+
info["reason"] = ";".join(info["android_signals"])
|
|
294
|
+
else:
|
|
295
|
+
info["direct_usable"] = True
|
|
296
|
+
info["reason"] = "未检测到 Android 特征(无 JNI、无 Android 系统库依赖、符号无 Bionic 版本),可能已是鸿蒙/musl 编译产物"
|
|
297
|
+
return info
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def inspect_archive(path, tools):
|
|
301
|
+
info = {
|
|
302
|
+
"path": path,
|
|
303
|
+
"kind": "a",
|
|
304
|
+
"arch": "unknown",
|
|
305
|
+
"bits": None,
|
|
306
|
+
"soname": None,
|
|
307
|
+
"needed": [],
|
|
308
|
+
"android_needed": [],
|
|
309
|
+
"jni": False,
|
|
310
|
+
"jni_symbols": [],
|
|
311
|
+
"bionic_versioned": False,
|
|
312
|
+
"android_symbols": [],
|
|
313
|
+
"android_signals": ["静态库(.a)"],
|
|
314
|
+
"direct_usable": False,
|
|
315
|
+
"reason": "静态库(.a):对象文件以 aarch64-linux-android 三元组编译,链接进鸿蒙会失败,必须用 OHOS 工具链重编",
|
|
316
|
+
}
|
|
317
|
+
if tools["nm"]:
|
|
318
|
+
syms = run([tools["nm"], "-P", path])
|
|
319
|
+
for line in syms.splitlines():
|
|
320
|
+
parts = line.split()
|
|
321
|
+
sym = parts[0] if parts else ""
|
|
322
|
+
if "@" in sym:
|
|
323
|
+
info["bionic_versioned"] = True
|
|
324
|
+
if any(sym.startswith(p) for p in ANDROID_SYMBOL_PREFIXES):
|
|
325
|
+
info["android_symbols"].append(sym)
|
|
326
|
+
if info["bionic_versioned"]:
|
|
327
|
+
info["android_signals"].append("对象符号带 Bionic 版本")
|
|
328
|
+
if info["android_symbols"]:
|
|
329
|
+
info["android_signals"].append("引用 Android NDK 专用符号:" + ", ".join(sorted(set(info["android_symbols"]))[:5]))
|
|
330
|
+
info["reason"] = ";".join(info["android_signals"])
|
|
331
|
+
return info
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def classify_case(info, evidence):
|
|
335
|
+
if not info["direct_usable"]:
|
|
336
|
+
if evidence["has_patch"] and evidence["has_source"]:
|
|
337
|
+
return 2
|
|
338
|
+
if evidence["has_source"]:
|
|
339
|
+
return 1
|
|
340
|
+
return 3
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def build_solution(info, case):
|
|
345
|
+
if info["direct_usable"]:
|
|
346
|
+
return ["可直接放入鸿蒙工程 libs/<abi>/ 并通过 NAPI 或 ArkTS import 使用;仍建议核对 ABI(arm64-v8a)是否齐全。"]
|
|
347
|
+
|
|
348
|
+
if case == 1:
|
|
349
|
+
return [
|
|
350
|
+
"1) 保留源码,将构建脚本改写为鸿蒙 CMake(目标三元组 aarch64-linux-ohos,日志库换 libhilog_ndk.z.so)。",
|
|
351
|
+
"2) JNI 桥接层改写为 NAPI(参考 references/jni-to-napi-mapping.md)。",
|
|
352
|
+
"3) 在 build-profile.json5 配 externalNativeOptions + abiFilters 编进 HAP。",
|
|
353
|
+
]
|
|
354
|
+
if case == 2:
|
|
355
|
+
return [
|
|
356
|
+
"1) 在 OHOS 工具链下重放修改脚本/patch;逐条适配 patch 中的 Android 特有代码(__ANDROID__ 宏、__android_log_print、AAssetManager 等)。",
|
|
357
|
+
"2) JNI 桥接层改写为 NAPI。",
|
|
358
|
+
"3) 重新交叉编译(--target=aarch64-linux-ohos)并编进 HAP。",
|
|
359
|
+
]
|
|
360
|
+
return [
|
|
361
|
+
"1) 无源码,预编译 .so 无法在鸿蒙直接加载,需先查鸿蒙替代:用 native-library-substitution(ohpm 2678 映射)或厂商官方鸿蒙 SDK。",
|
|
362
|
+
"2) 若上游开源,则退回情况一/二,获取源码后用 OHOS 工具链重编。",
|
|
363
|
+
"3) 无源码且无替代:用鸿蒙系统 API 重实现或功能裁剪,并在风险清单标记 high。",
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def scan_evidence(root):
|
|
368
|
+
evidence = {"has_source": False, "has_patch": False, "has_build_files": False, "native_dirs": []}
|
|
369
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
370
|
+
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
|
|
371
|
+
base = os.path.basename(dirpath)
|
|
372
|
+
if base in ("jni", "cpp", "native") or "native" in base:
|
|
373
|
+
evidence["native_dirs"].append(dirpath)
|
|
374
|
+
for fn in filenames:
|
|
375
|
+
if fn in NATIVE_BUILD_FILES:
|
|
376
|
+
evidence["has_build_files"] = True
|
|
377
|
+
ext = os.path.splitext(fn)[1].lower()
|
|
378
|
+
if ext in NATIVE_SOURCE_EXTS:
|
|
379
|
+
evidence["has_source"] = True
|
|
380
|
+
if ext in PATCH_EXTS:
|
|
381
|
+
evidence["has_patch"] = True
|
|
382
|
+
if fn.endswith((".sh", ".mk")) and fn not in ("Android.mk", "Application.mk"):
|
|
383
|
+
evidence["has_patch"] = True
|
|
384
|
+
return evidence
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def find_native_libs(root):
|
|
388
|
+
results = []
|
|
389
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
390
|
+
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
|
|
391
|
+
for fn in filenames:
|
|
392
|
+
if fn.endswith(".so") or fn.endswith(".a"):
|
|
393
|
+
results.append(os.path.join(dirpath, fn))
|
|
394
|
+
return sorted(results)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def load_facts(path):
|
|
398
|
+
try:
|
|
399
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
400
|
+
shard = json.load(f)
|
|
401
|
+
except (OSError, ValueError) as e:
|
|
402
|
+
print("无法读取 facts 分片: %s (%s)" % (path, e))
|
|
403
|
+
return None
|
|
404
|
+
if shard.get("kind") != "native-lib-facts" or not isinstance(shard.get("nativeLibs"), dict):
|
|
405
|
+
print("分片不是合法的 native-lib-facts: %s" % path)
|
|
406
|
+
return None
|
|
407
|
+
return shard["nativeLibs"]
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def libs_from_facts(root, native_libs):
|
|
411
|
+
"""把分片 libraries[].artifacts[].path 解析为绝对路径清单,返回 (排序路径, 路径→usage 映射)。"""
|
|
412
|
+
usage_by_path = {}
|
|
413
|
+
for lib in native_libs.get("libraries", []):
|
|
414
|
+
for artifact in lib.get("artifacts", []):
|
|
415
|
+
rel = artifact.get("path")
|
|
416
|
+
if not rel:
|
|
417
|
+
continue
|
|
418
|
+
absolute = os.path.normpath(os.path.join(root, *rel.split("/")))
|
|
419
|
+
usage_by_path[absolute] = lib.get("usage")
|
|
420
|
+
return sorted(usage_by_path.keys()), usage_by_path
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def main():
|
|
424
|
+
parser = argparse.ArgumentParser(description="检查 Android .so/.a 能否直接用于鸿蒙")
|
|
425
|
+
parser.add_argument("target", nargs="?", default=".", help="Android 工程目录(默认当前目录)")
|
|
426
|
+
parser.add_argument("--json", action="store_true", help="输出 JSON")
|
|
427
|
+
parser.add_argument("--no-build-dirs", action="store_true", help="跳过 build 目录扫描")
|
|
428
|
+
parser.add_argument("--from-facts", metavar="PATH", help="按 facts/native-libs.json 分片清单只检查列出的产物(路径相对工程目录)")
|
|
429
|
+
args = parser.parse_args()
|
|
430
|
+
|
|
431
|
+
tools = resolve_tools()
|
|
432
|
+
if not tools["readelf"] and not tools["objdump"]:
|
|
433
|
+
print("缺少 ELF 解析工具(llvm-readelf / readelf / objdump)。")
|
|
434
|
+
print("Android NDK 与 DevEco/OpenHarmony SDK 均自带 llvm-readelf/llvm-nm/llvm-objdump,")
|
|
435
|
+
print("请将其加入 PATH,或设置 ANDROID_NDK_HOME / OHOS_SDK_HOME 后重试。")
|
|
436
|
+
return 2
|
|
437
|
+
if not tools["nm"]:
|
|
438
|
+
print("缺少符号解析工具(llvm-nm / nm),将跳过 JNI/Bionic 符号检测,仅做依赖检查。")
|
|
439
|
+
|
|
440
|
+
root = os.path.abspath(args.target)
|
|
441
|
+
if args.no_build_dirs:
|
|
442
|
+
EXCLUDE_DIRS.add("build")
|
|
443
|
+
|
|
444
|
+
native_libs = None
|
|
445
|
+
if args.from_facts:
|
|
446
|
+
native_libs = load_facts(os.path.abspath(args.from_facts))
|
|
447
|
+
if native_libs is None:
|
|
448
|
+
return 2
|
|
449
|
+
|
|
450
|
+
if native_libs is not None:
|
|
451
|
+
libs, usage_by_path = libs_from_facts(root, native_libs)
|
|
452
|
+
else:
|
|
453
|
+
libs, usage_by_path = find_native_libs(root), {}
|
|
454
|
+
evidence = scan_evidence(root)
|
|
455
|
+
|
|
456
|
+
report = []
|
|
457
|
+
for path in libs:
|
|
458
|
+
if not os.path.isfile(path):
|
|
459
|
+
info = {
|
|
460
|
+
"path": path, "kind": "unknown", "arch": "unknown", "bits": None,
|
|
461
|
+
"soname": None, "needed": [], "android_needed": [], "jni": False,
|
|
462
|
+
"jni_symbols": [], "bionic_versioned": False, "android_symbols": [],
|
|
463
|
+
"android_signals": [], "direct_usable": False,
|
|
464
|
+
"reason": "分片列出但工程中不存在该文件",
|
|
465
|
+
}
|
|
466
|
+
else:
|
|
467
|
+
kind = file_kind(path)
|
|
468
|
+
if kind == "ar":
|
|
469
|
+
info = inspect_archive(path, tools)
|
|
470
|
+
elif kind == "elf":
|
|
471
|
+
info = inspect_elf(path, tools)
|
|
472
|
+
else:
|
|
473
|
+
info = {
|
|
474
|
+
"path": path, "kind": "unknown", "arch": "unknown", "bits": None,
|
|
475
|
+
"soname": None, "needed": [], "android_needed": [], "jni": False,
|
|
476
|
+
"jni_symbols": [], "bionic_versioned": False, "android_symbols": [],
|
|
477
|
+
"android_signals": [], "direct_usable": False,
|
|
478
|
+
"reason": "无法识别的文件类型(非 ELF 非 ar archive)",
|
|
479
|
+
}
|
|
480
|
+
case = classify_case(info, evidence)
|
|
481
|
+
info["usage_case"] = case
|
|
482
|
+
info["solution"] = build_solution(info, case)
|
|
483
|
+
if path in usage_by_path:
|
|
484
|
+
info["facts_usage"] = usage_by_path[path]
|
|
485
|
+
report.append(info)
|
|
486
|
+
|
|
487
|
+
jni_reconciliation = None
|
|
488
|
+
if native_libs is not None:
|
|
489
|
+
exported = set()
|
|
490
|
+
for info in report:
|
|
491
|
+
for sym in info.get("jni_symbols", []):
|
|
492
|
+
if sym.startswith("Java_"):
|
|
493
|
+
exported.add(sym)
|
|
494
|
+
declared = native_libs.get("jniMethods", [])
|
|
495
|
+
declared_names = [m.get("name") for m in declared if m.get("name")]
|
|
496
|
+
unmatched_declared = [
|
|
497
|
+
m for m in declared
|
|
498
|
+
if m.get("name") and not any(s.endswith("_" + m["name"]) for s in exported)
|
|
499
|
+
]
|
|
500
|
+
unmatched_exports = [
|
|
501
|
+
s for s in sorted(exported)
|
|
502
|
+
if not any(s.endswith("_" + n) for n in declared_names)
|
|
503
|
+
]
|
|
504
|
+
jni_reconciliation = {
|
|
505
|
+
"declared": len(declared),
|
|
506
|
+
"exported": len(exported),
|
|
507
|
+
"unmatched_declared": unmatched_declared,
|
|
508
|
+
"unmatched_exports": unmatched_exports,
|
|
509
|
+
"note": "按 JNI 符号方法名后缀对账(Java_<类>_<方法>)。Kotlin 文件级 external fun 编译为 <文件>Kt 类,重载/名称混淆/动态注册需人工复核;无 nm 工具时 exported 为空。",
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if args.json:
|
|
513
|
+
payload = {"target": root, "evidence": evidence, "libraries": report}
|
|
514
|
+
if native_libs is not None:
|
|
515
|
+
payload["from_facts"] = os.path.abspath(args.from_facts)
|
|
516
|
+
payload["facts_usage_case"] = native_libs.get("usageCase")
|
|
517
|
+
payload["load_calls"] = native_libs.get("loadCalls", [])
|
|
518
|
+
payload["jni_reconciliation"] = jni_reconciliation
|
|
519
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
520
|
+
return 0
|
|
521
|
+
|
|
522
|
+
print("=" * 72)
|
|
523
|
+
print("Android C/C++ 库迁移前检查报告")
|
|
524
|
+
print("工程目录:", root)
|
|
525
|
+
if native_libs is not None:
|
|
526
|
+
print("检查范围: facts/native-libs.json 分片清单(%d 个产物,初分类 %s)" % (len(libs), native_libs.get("usageCase")))
|
|
527
|
+
print("发现库文件:", len(libs), "个")
|
|
528
|
+
print("源码证据:", "有源码" if evidence["has_source"] else "无源码",
|
|
529
|
+
"| 构建脚本:", "有" if evidence["has_build_files"] else "无",
|
|
530
|
+
"| patch/修改脚本:", "有" if evidence["has_patch"] else "无")
|
|
531
|
+
print("=" * 72)
|
|
532
|
+
|
|
533
|
+
if not libs:
|
|
534
|
+
print("未发现 .so / .a 文件。")
|
|
535
|
+
return 0
|
|
536
|
+
|
|
537
|
+
for info in report:
|
|
538
|
+
print()
|
|
539
|
+
print("文件:", info["path"])
|
|
540
|
+
kind_label = {"so": "动态库(.so)", "a": "静态库(.a)", "unknown": "未知"}.get(info["kind"], info["kind"])
|
|
541
|
+
print(" 类型:", kind_label, "| 架构:", info["arch"], "|", str(info["bits"]) + "位" if info["bits"] else "")
|
|
542
|
+
if info["soname"]:
|
|
543
|
+
print(" SONAME:", info["soname"])
|
|
544
|
+
if info["needed"]:
|
|
545
|
+
print(" 动态依赖:", ", ".join(info["needed"]))
|
|
546
|
+
verdict = "可直接使用" if info["direct_usable"] else "不能直接使用"
|
|
547
|
+
print(" 结论:", verdict)
|
|
548
|
+
print(" 原因:", info["reason"] or "-")
|
|
549
|
+
if info["usage_case"]:
|
|
550
|
+
print(" 使用方式分类: 情况%d" % info["usage_case"])
|
|
551
|
+
if info.get("facts_usage"):
|
|
552
|
+
print(" 分片初分类:", info["facts_usage"])
|
|
553
|
+
for step in info["solution"]:
|
|
554
|
+
print(" ", step)
|
|
555
|
+
|
|
556
|
+
print()
|
|
557
|
+
if jni_reconciliation is not None:
|
|
558
|
+
print("JNI 桥接对账(分片 jniMethods ↔ .so 导出 Java_* 符号):")
|
|
559
|
+
print(" 声明 external/native 方法:", jni_reconciliation["declared"], "个")
|
|
560
|
+
print(" .so 导出 Java_* 符号:", jni_reconciliation["exported"], "个")
|
|
561
|
+
if jni_reconciliation["unmatched_declared"]:
|
|
562
|
+
print(" ⚠ 声明了方法但无对应导出符号(可能桥接缺失/动态注册/名称混淆):")
|
|
563
|
+
for m in jni_reconciliation["unmatched_declared"]:
|
|
564
|
+
print(" - %s:%s %s(%s)" % (m.get("file"), m.get("line"), m.get("name"), m.get("language")))
|
|
565
|
+
if jni_reconciliation["unmatched_exports"]:
|
|
566
|
+
print(" ⚠ 导出了符号但未在源码声明(可能重载/额外 JNI 函数):")
|
|
567
|
+
for s in jni_reconciliation["unmatched_exports"][:10]:
|
|
568
|
+
print(" -", s)
|
|
569
|
+
print()
|
|
570
|
+
|
|
571
|
+
print("=" * 72)
|
|
572
|
+
n_ok = sum(1 for r in report if r["direct_usable"])
|
|
573
|
+
n_bad = len(report) - n_ok
|
|
574
|
+
print("汇总: 可直接使用 %d 个,需处理 %d 个(全部需在鸿蒙侧重编或替代后才能使用)。" % (n_ok, n_bad))
|
|
575
|
+
print("=" * 72)
|
|
576
|
+
return 0
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
if __name__ == "__main__":
|
|
580
|
+
sys.exit(main())
|