@harmonyos-arkts/d2h 0.1.19 → 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.
Files changed (151) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +40 -0
  3. package/agents/android-to-hmos-00-orchestrator.md +3 -315
  4. package/agents/d2h.md +53 -38
  5. package/contracts/workflow/artifacts.md +22 -0
  6. package/contracts/workflow/delivery.md +54 -0
  7. package/contracts/workflow/dispatch.md +26 -0
  8. package/contracts/workflow/execution.md +125 -0
  9. package/contracts/workflow/generation.md +40 -0
  10. package/contracts/workflow/orchestration.md +49 -0
  11. package/contracts/workflow/overview.md +58 -0
  12. package/contracts/workflow/plan.md +119 -0
  13. package/contracts/workflow/scope.md +151 -0
  14. package/dist/bin/install-opencode.js +34 -15
  15. package/dist/src/install-codex.js +131 -0
  16. package/dist/src/install-opencode.js +5 -5
  17. package/dist/src/workflow.js +54 -0
  18. package/docs/architecture-plan-guide.zh-CN.md +70 -0
  19. package/docs/incremental-spec-evaluation-design.md +670 -0
  20. package/docs/spec-plan-guide.md +226 -0
  21. package/docs/workflow-decoupling.md +59 -0
  22. package/opencode/interactive-tools/advance-progress-demo.mjs +21 -0
  23. package/opencode/interactive-tools/browser-host.ts +110 -0
  24. package/opencode/interactive-tools/core.ts +242 -0
  25. package/opencode/interactive-tools/demo-workflow.ts +55 -0
  26. package/opencode/interactive-tools/demo.mjs +37 -0
  27. package/opencode/interactive-tools/package-lock.json +1876 -0
  28. package/opencode/interactive-tools/package.json +23 -0
  29. package/opencode/interactive-tools/panel.ts +40 -0
  30. package/opencode/interactive-tools/prepare-demo.mjs +17 -0
  31. package/opencode/interactive-tools/prepare-progress-demo.mjs +13 -0
  32. package/opencode/interactive-tools/progress.ts +118 -0
  33. package/opencode/interactive-tools/server-legacy.mjs +2 -0
  34. package/opencode/interactive-tools/server.mjs +40 -0
  35. package/opencode/interactive-tools/tui.tsx +230 -0
  36. package/package.json +9 -1
  37. package/schemas/native-libs-analysis-report.schema.json +695 -0
  38. package/schemas/native-libs-verify-report.schema.json +566 -0
  39. package/schemas/spec-native-libs-shard.schema.json +110 -0
  40. package/skills/arkts-icon-sizing/SKILL.md +0 -10
  41. package/skills/cpp/d2h-native-library-analyse/SKILL.md +91 -0
  42. package/skills/cpp/d2h-native-library-analyse/scripts/analyse-android-so.py +630 -0
  43. package/skills/cpp/d2h-native-library-build/SKILL.md +403 -0
  44. package/skills/cpp/d2h-native-library-build/references/build-config-mapping.md +180 -0
  45. package/skills/cpp/d2h-native-library-build/references/jni-to-napi-mapping.md +74 -0
  46. package/skills/cpp/d2h-native-library-build/scripts/check-android-so.py +580 -0
  47. package/skills/cpp/d2h-native-library-verify/SKILL.md +111 -0
  48. package/skills/cpp/d2h-native-library-verify/references/demo-build-sign-run.md +67 -0
  49. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/app.json5 +1 -0
  50. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/resources/base/element/string.json +1 -0
  51. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/README.md +25 -0
  52. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/build-profile.json5 +24 -0
  53. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/build-profile.json5 +1 -0
  54. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/hvigorfile.ts +2 -0
  55. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/oh-package.json5 +1 -0
  56. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/entryability/EntryAbility.ets +5 -0
  57. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/Index.ets +3 -0
  58. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/NativeVerifyDemo.ets +276 -0
  59. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/module.json5 +1 -0
  60. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/color.json +1 -0
  61. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/string.json +1 -0
  62. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/profile/main_pages.json +1 -0
  63. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/FFmpegVerify.test.ets +176 -0
  64. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/List.test.ets +2 -0
  65. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/module.json5 +1 -0
  66. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigor/hvigor-config.json5 +1 -0
  67. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigorfile.ts +2 -0
  68. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/oh-package.json5 +9 -0
  69. package/skills/cpp/d2h-native-library-verify/references/demo-template.md +176 -0
  70. package/skills/cpp/d2h-native-library-verify/references/hypium-test-template.md +163 -0
  71. package/skills/cpp/d2h-native-library-verify/scripts/__pycache__/native_common.cpython-313.pyc +0 -0
  72. package/skills/cpp/d2h-native-library-verify/scripts/device-verify.py +304 -0
  73. package/skills/cpp/d2h-native-library-verify/scripts/native_common.py +341 -0
  74. package/skills/cpp/d2h-native-library-verify/scripts/verify-ohos-native.py +1099 -0
  75. package/skills/d2h-app-solution/SKILL.md +198 -0
  76. package/skills/d2h-app-solution/agents/openai.yaml +4 -0
  77. package/skills/d2h-app-solution/references/framework-evidence.md +117 -0
  78. package/skills/d2h-app-solution/references/hierarchy-rules.md +88 -0
  79. package/skills/d2h-app-solution/references/inventory-schema.md +305 -0
  80. package/skills/d2h-app-solution/references/technical-analysis.md +101 -0
  81. package/skills/d2h-app-solution/scripts/scan_repositories.py +350 -0
  82. package/skills/d2h-app-solution/scripts/validate_inventory.py +337 -0
  83. package/skills/d2h-architecture-plan/SKILL.md +35 -0
  84. package/skills/d2h-architecture-plan/references/directory-creation.md +43 -0
  85. package/skills/d2h-architecture-plan/references/hybrid-design.md +84 -0
  86. package/skills/d2h-architecture-plan/references/layout-reference.md +83 -0
  87. package/skills/d2h-architecture-plan/references/plan-contract.md +51 -0
  88. package/skills/d2h-architecture-plan/references/source-review.md +50 -0
  89. package/skills/d2h-cloud-knowledge/SKILL.md +28 -7
  90. package/skills/d2h-cloud-knowledge/kb_query.mjs +8 -0
  91. package/skills/d2h-dashboard/SKILL.md +56 -0
  92. package/skills/d2h-product-family-migration/SKILL.md +103 -0
  93. package/skills/d2h-product-family-migration/agents/openai.yaml +3 -0
  94. package/skills/d2h-product-family-migration/assets/incremental-plan.template.md +55 -0
  95. package/skills/d2h-product-family-migration/references/advanced/behavioral-verification.md +59 -0
  96. package/skills/d2h-product-family-migration/references/advanced/existing-skill-integration.md +30 -0
  97. package/skills/d2h-product-family-migration/references/advanced/packaging-and-verification.md +43 -0
  98. package/skills/d2h-product-family-migration/references/advanced/platform-probes.md +26 -0
  99. package/skills/d2h-product-family-migration/references/advanced/product-line-design.md +40 -0
  100. package/skills/d2h-product-family-migration/references/baseline-and-delta.md +50 -0
  101. package/skills/d2h-product-family-migration/tests/markdown-cases.md +76 -0
  102. package/skills/{d2h-page-develop → d2h-spec-develop}/SKILL.md +143 -132
  103. package/skills/d2h-spec-develop/agents/openai.yaml +7 -0
  104. package/skills/d2h-spec-develop/references/worker-workflow.md +26 -0
  105. package/skills/{d2h-page-spec-generate → d2h-spec-generate}/SKILL.md +24 -13
  106. package/skills/d2h-spec-generate/assets/spec-template.md +109 -0
  107. package/skills/d2h-spec-generate/references/from-plan.md +19 -0
  108. package/skills/d2h-spec-generate/references/spec-content.md +34 -0
  109. package/skills/{d2h-page-spec-generate → d2h-spec-generate}/scripts/build_batch_report.py +35 -84
  110. package/skills/d2h-spec-plan/SKILL.md +38 -0
  111. package/skills/d2h-spec-plan/agents/openai.yaml +4 -0
  112. package/skills/d2h-spec-plan/references/architecture-handoff.md +9 -0
  113. package/skills/d2h-spec-plan/references/plan-contract.md +13 -0
  114. package/skills/d2h-spec-plan/references/planning-method.md +46 -0
  115. package/skills/d2h-spec-plan/references/round-scope-assessment.md +49 -0
  116. package/skills/d2h-spec-plan/references/work-packets.md +61 -0
  117. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +2 -2
  118. package/skills/d2h-utg-ai-generate/references/page-schema.json +2 -2
  119. package/skills/d2h-utg-ai-generate/references/utg-schema.json +2 -2
  120. package/skills/d2h-utg-static-generate/SKILL.md +2 -2
  121. package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
  122. package/skills/managing-d2h-toolchain/toolchain.json +1 -1
  123. package/skills/preparing-migration-workspace/SKILL.md +32 -2
  124. package/workflow/__pycache__/artifact_bindings.cpython-313.pyc +0 -0
  125. package/workflow/__pycache__/spec_runtime.cpython-313.pyc +0 -0
  126. package/workflow/artifact_bindings.py +224 -0
  127. package/workflow/assets/dashboard.html +88 -0
  128. package/workflow/build_batch_report.py +8 -0
  129. package/workflow/collect_code_metrics.py +75 -0
  130. package/workflow/dashboard_overview.py +109 -0
  131. package/workflow/delivery_view.py +82 -0
  132. package/workflow/feature_plan.py +234 -0
  133. package/workflow/page_batches.py +223 -0
  134. package/workflow/spec_generation.py +120 -0
  135. package/workflow/spec_runtime.py +890 -0
  136. package/workflow/work_packets.py +85 -0
  137. package/skills/d2h-page-develop/agents/openai.yaml +0 -7
  138. package/skills/harmonyos-migration-playbook/SKILL.md +0 -83
  139. package/skills/harmonyos-migration-playbook/agents/openai.yaml +0 -7
  140. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +0 -24
  141. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +0 -53
  142. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +0 -45
  143. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +0 -23
  144. package/skills/harmonyos-migration-playbook/references/ui-automation.md +0 -53
  145. package/skills/harmonyos-migration-playbook/references/windows-environment.md +0 -38
  146. package/skills/native-library-substitution/SKILL.md +0 -385
  147. package/skills/native-library-substitution/references/native-library-substitution.json +0 -56906
  148. package/skills/native-library-substitution/references/native-library-substitution.md +0 -163
  149. package/skills/porting-cmake-native-libraries/SKILL.md +0 -57
  150. package/skills/porting-cmake-native-libraries/references/platform-adaptation.md +0 -49
  151. 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())