openubmc-bingo 0.6.45__py3-none-any.whl → 0.6.99__py3-none-any.whl

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 (96) hide show
  1. bmcgo/__init__.py +1 -1
  2. bmcgo/bmcgo.py +9 -3
  3. bmcgo/bmcgo_config.py +16 -0
  4. bmcgo/cli/cli.py +72 -21
  5. bmcgo/codegen/__init__.py +1 -1
  6. bmcgo/codegen/lua/codegen.py +2 -2
  7. bmcgo/codegen/lua/script/check_intfs.py +1 -0
  8. bmcgo/codegen/lua/script/dto/options.py +1 -0
  9. bmcgo/codegen/lua/script/gen_db_json.py +4 -3
  10. bmcgo/codegen/lua/script/gen_rpc_msg_json.py +78 -11
  11. bmcgo/codegen/lua/script/model_consistency_check.py +1 -1
  12. bmcgo/codegen/lua/script/render_utils/db_lua.py +5 -6
  13. bmcgo/codegen/lua/script/render_utils/model_lua.py +5 -1
  14. bmcgo/codegen/lua/script/template.py +5 -0
  15. bmcgo/codegen/lua/script/utils.py +50 -8
  16. bmcgo/codegen/lua/templates/apps/Makefile +2 -2
  17. bmcgo/codegen/lua/templates/apps/client.lua.mako +1 -1
  18. bmcgo/codegen/lua/templates/apps/model.lua.mako +4 -3
  19. bmcgo/codegen/lua/templates/apps/service.lua.mako +1 -1
  20. bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +4 -0
  21. bmcgo/codegen/lua/templates/new_app_v2/CMakeLists.txt.mako +26 -0
  22. bmcgo/codegen/lua/templates/new_app_v2/conanfile.py.mako +9 -0
  23. bmcgo/codegen/lua/v1/script/render_utils/db_lua.py +5 -6
  24. bmcgo/codegen/lua/v1/script/render_utils/model_lua.py +13 -1
  25. bmcgo/codegen/lua/v1/templates/apps/client.lua.mako +1 -1
  26. bmcgo/codegen/lua/v1/templates/apps/local_db.lua.mako +0 -4
  27. bmcgo/codegen/lua/v1/templates/apps/message.lua.mako +3 -0
  28. bmcgo/codegen/lua/v1/templates/apps/model.lua.mako +3 -0
  29. bmcgo/codegen/lua/v1/templates/apps/utils/mdb_intf.lua.mako +6 -4
  30. bmcgo/component/analysis/analysis.py +9 -4
  31. bmcgo/component/analysis/dep-rules.json +20 -8
  32. bmcgo/component/analysis/dep_node.py +2 -0
  33. bmcgo/component/analysis/intf_validation.py +8 -7
  34. bmcgo/component/analysis/sr_validation.py +5 -4
  35. bmcgo/component/busctl_log_parse/busctl_log_parser.py +809 -0
  36. bmcgo/component/busctl_log_parse/mock_data_save.py +170 -0
  37. bmcgo/component/busctl_log_parse/test_data_save.py +49 -0
  38. bmcgo/component/component_helper.py +29 -0
  39. bmcgo/component/coverage/incremental_cov.py +5 -0
  40. bmcgo/component/fixture/__init__.py +29 -0
  41. bmcgo/component/fixture/auto_case_generator.py +490 -0
  42. bmcgo/component/fixture/busctl_type_converter.py +1081 -0
  43. bmcgo/component/fixture/common_config.py +15 -0
  44. bmcgo/component/fixture/dbus_gateway.py +669 -0
  45. bmcgo/component/fixture/dbus_library.py +250 -0
  46. bmcgo/component/fixture/dbus_mock_utils.py +514 -0
  47. bmcgo/component/fixture/dbus_response_handler.py +138 -0
  48. bmcgo/component/fixture/dbus_signature.py +110 -0
  49. bmcgo/component/template_v2/conanbase.py.mako +1 -5
  50. bmcgo/component/test.py +69 -10
  51. bmcgo/error_analyzer/__init__.py +0 -0
  52. bmcgo/error_analyzer/case_matcher.py +114 -0
  53. bmcgo/error_analyzer/log_parser.py +128 -0
  54. bmcgo/error_analyzer/unified_error_analyzer.py +359 -0
  55. bmcgo/error_cases/cases.yml +59 -0
  56. bmcgo/error_cases/cases_template_valid.json +71 -0
  57. bmcgo/error_cases/conanfile.py +58 -0
  58. bmcgo/frame.py +0 -4
  59. bmcgo/functional/analysis.py +18 -12
  60. bmcgo/functional/bmc_studio_action.py +21 -10
  61. bmcgo/functional/check.py +86 -42
  62. bmcgo/functional/conan_index_build.py +1 -1
  63. bmcgo/functional/config.py +22 -18
  64. bmcgo/functional/csr_build.py +63 -34
  65. bmcgo/functional/deploy.py +4 -3
  66. bmcgo/functional/diff.py +51 -34
  67. bmcgo/functional/full_component.py +16 -5
  68. bmcgo/functional/hpm_signer.py +484 -0
  69. bmcgo/functional/new.py +8 -2
  70. bmcgo/functional/schema_valid.py +111 -15
  71. bmcgo/functional/upgrade.py +6 -6
  72. bmcgo/misc.py +1 -0
  73. bmcgo/tasks/task_build_conan.py +27 -6
  74. bmcgo/tasks/task_build_rootfs_img.py +120 -83
  75. bmcgo/tasks/task_buildgppbin.py +30 -13
  76. bmcgo/tasks/task_buildhpm_ext4.py +5 -3
  77. bmcgo/tasks/task_download_buildtools.py +20 -11
  78. bmcgo/tasks/task_download_dependency.py +29 -20
  79. bmcgo/tasks/task_hpm_envir_prepare.py +32 -53
  80. bmcgo/tasks/task_packet_to_supporte.py +12 -4
  81. bmcgo/tasks/task_prepare.py +1 -1
  82. bmcgo/tasks/task_sign_and_pack_hpm.py +15 -7
  83. bmcgo/utils/component_version_check.py +4 -4
  84. bmcgo/utils/config.py +3 -0
  85. bmcgo/utils/fetch_component_code.py +148 -17
  86. bmcgo/utils/install_manager.py +2 -2
  87. bmcgo/utils/installations/base_installer.py +10 -27
  88. bmcgo/utils/installations/install_plans/studio.yml +3 -0
  89. bmcgo/utils/mapping_config_patch.py +5 -4
  90. bmcgo/utils/tools.py +49 -7
  91. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/METADATA +1 -1
  92. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/RECORD +95 -74
  93. bmcgo/tasks/download_buildtools_hm.py +0 -124
  94. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/WHEEL +0 -0
  95. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/entry_points.txt +0 -0
  96. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env python3
2
+ # encoding=utf-8
3
+ # 描述:HPM 文件重签名功能
4
+ # Copyright (c) 2025 Huawei Technologies Co., Ltd.
5
+ # openUBMC is licensed under Mulan PSL v2.
6
+ # You can use this software according to the terms and conditions of the Mulan PSL v2.
7
+ # You may obtain a copy of Mulan PSL v2 at:
8
+ # http://license.coscl.org.cn/MulanPSL2
9
+ # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
10
+ # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
11
+ # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
12
+ # See the Mulan PSL v2 for more details.
13
+
14
+ import os
15
+ import struct
16
+ import subprocess
17
+ import shutil
18
+ import argparse
19
+ from pathlib import Path
20
+
21
+ from bmcgo import misc
22
+ from bmcgo.utils.tools import Tools
23
+ from bmcgo.bmcgo_config import BmcgoConfig
24
+
25
+ tools = Tools("HPMResigner")
26
+ logger = tools.log
27
+
28
+ #========== 可调默认项 ==========
29
+ CMD = "bingo"
30
+ # DEFAULT_CA_DIR = "/root/ca" # 证书目录默认值(可被 --ca-dir 覆盖)
31
+ DEFAULT_CA_DIR = str(Path.home() / "ca") # 证书目录默认位于用户家目录下
32
+ DEFAULT_OUT_SUFFIX = "_signed.hpm" # 输出文件名默认后缀(可按需改)
33
+ TMP_DIR_NAME = "hpm_tmp" # 源文件同目录下的临时目录名
34
+
35
+ command_info: misc.CommandInfo = misc.CommandInfo(
36
+ group=misc.GRP_MISC,
37
+ name="hpm_signer",
38
+ description=["HPM 文件重签名"],
39
+ hidden=False
40
+ )
41
+
42
+
43
+ def if_available(bconfig: BmcgoConfig):
44
+ return True
45
+
46
+
47
+ def get_desc(cmd):
48
+ return f"""
49
+ HPM 文件重签名工具:
50
+
51
+ >> {cmd} hpm_resign <HPM文件> [--ca-dir /path/to/ca] [--out new_name.hpm] [--keep-tmp] [--signer-pass <签名证书加密密码>]
52
+
53
+ 说明:
54
+ 1) 请确保已安装 hpm_verify 和 hpm_signer 工具
55
+ 2) 证书目录默认位于用户家目录 ca 目录下 (可使用 --ca-dir 参数指定其他目录)
56
+ 3) 证书目录(--ca-dir)下需包含: rootca.der, rootca.crl, signer.pem, (可选) ts_signer.pem, tsa.cnf
57
+ 4) 如果签名证书已加密,需要使用 --signer-pass 参数提供密码
58
+ 5) 临时文件与输出文件默认位于源 HPM 文件同目录
59
+ """
60
+
61
+
62
+ class BmcgoCommand:
63
+ def __init__(self, bconfig: BmcgoConfig, *args):
64
+ self.bconfig = bconfig
65
+ parser = argparse.ArgumentParser(
66
+ prog=f"{CMD} HPM重签名",
67
+ description=get_desc(CMD),
68
+ add_help=True,
69
+ formatter_class=argparse.RawTextHelpFormatter
70
+ )
71
+ parser.add_argument(
72
+ "hpm_file",
73
+ type=Path,
74
+ help="HPM文件路径"
75
+ )
76
+ parser.add_argument(
77
+ "--keep-tmp",
78
+ action="store_true",
79
+ help="保留临时文件"
80
+ )
81
+ parser.add_argument(
82
+ "--ca-dir",
83
+ type=Path,
84
+ default=Path(DEFAULT_CA_DIR),
85
+ help=f"证书目录(默认: {DEFAULT_CA_DIR})"
86
+ )
87
+ parser.add_argument(
88
+ "--out",
89
+ type=str,
90
+ default=None,
91
+ help=f"输出文件名(默认: <源名>{DEFAULT_OUT_SUFFIX})"
92
+ )
93
+ parser.add_argument(
94
+ "--signer-pass",
95
+ type=str,
96
+ default=None,
97
+ help="签名证书的加密密码(如果证书已加密)"
98
+ )
99
+
100
+ self.args, self.kwargs = parser.parse_known_args(*args)
101
+ self.logger = tools.log
102
+
103
+ # 运行期路径属性(在 run/main 中初始化)
104
+ self.base_dir: Path = None
105
+ self.tmp_dir: Path = None
106
+
107
+ @staticmethod
108
+ def write_ascii_hex(fp, value: int):
109
+ """按 8 位 0 补齐十六进制,写入 ASCII 字符串"""
110
+ fp.write(f"{value:08x}".encode())
111
+
112
+ def run(self):
113
+ hpm_file = self.args.hpm_file
114
+ if not hpm_file.exists():
115
+ self.logger.error(f"ERROR: 文件 {hpm_file} 不存在")
116
+ return 1
117
+
118
+ # 源文件同目录
119
+ self.base_dir = hpm_file.parent.resolve()
120
+ # 临时目录放在源文件同目录
121
+ self.tmp_dir = (self.base_dir / TMP_DIR_NAME).resolve()
122
+
123
+ try:
124
+ # 在主要操作前进行权限检查
125
+ if not self.check_permissions(hpm_file):
126
+ self.logger.error("权限检查失败,程序退出")
127
+ return 1
128
+
129
+ self.main(hpm_file)
130
+ return 0
131
+ except Exception as e:
132
+ self.logger.error(f"处理过程中发生错误: {str(e)}")
133
+ import traceback
134
+ self.logger.debug(f"详细堆栈: {traceback.format_exc()}")
135
+ return 1
136
+ finally:
137
+ if not self.args.keep_tmp:
138
+ self.clear_temp()
139
+
140
+ def check_permissions(self, hpm_file: Path) -> bool:
141
+ """检查必要的文件和目录权限"""
142
+ self.logger.info(">>> 开始权限检查...")
143
+
144
+ # 检查源文件读取权限
145
+ if not os.access(hpm_file, os.R_OK):
146
+ self.logger.error(f"ERROR: 无读取权限: {hpm_file}")
147
+ return False
148
+ self.logger.info(f"SUCCESS: 源文件可读: {hpm_file}")
149
+
150
+ # 检查源文件所在目录的写入权限(用于创建临时目录和输出文件)
151
+ if not os.access(self.base_dir, os.W_OK):
152
+ self.logger.error(f"ERROR: 无写入权限: {self.base_dir}")
153
+ return False
154
+ self.logger.info(f"SUCCESS: 目录可写: {self.base_dir}")
155
+
156
+ # 检查证书目录权限
157
+ ca_dir = self.args.ca_dir.resolve()
158
+ if not ca_dir.exists():
159
+ self.logger.error(f"ERROR: 证书目录不存在: {ca_dir}")
160
+ return False
161
+
162
+ if not os.access(ca_dir, os.R_OK):
163
+ self.logger.error(f"ERROR: 无读取权限: {ca_dir}")
164
+ return False
165
+ self.logger.info(f"SUCCESS: 证书目录可读: {ca_dir}")
166
+
167
+ # 检查必要的证书文件
168
+ required_ca_files = [
169
+ ("rootca.der", "根证书"),
170
+ ("rootca.crl", "CRL文件"),
171
+ ("signer.pem", "签名证书")
172
+ ]
173
+
174
+ for filename, desc in required_ca_files:
175
+ file_path = ca_dir / filename
176
+ if not file_path.exists():
177
+ self.logger.error(f"ERROR: {desc}不存在: {file_path}")
178
+ return False
179
+ if not os.access(file_path, os.R_OK):
180
+ self.logger.error(f"ERROR: 无读取权限: {file_path}")
181
+ return False
182
+ self.logger.info(f"SUCCESS: {desc}可读: {filename}")
183
+
184
+ # 检查可选的时间戳证书文件
185
+ optional_files = [
186
+ ("ts_signer.pem", "时间戳证书"),
187
+ ("tsa.cnf", "时间戳配置")
188
+ ]
189
+
190
+ for filename, desc in optional_files:
191
+ file_path = ca_dir / filename
192
+ if file_path.exists():
193
+ if not os.access(file_path, os.R_OK):
194
+ self.logger.warning(f"WARNING: 无读取权限(可选): {file_path}")
195
+ else:
196
+ self.logger.info(f"SUCCESS: {desc}可读: {filename}")
197
+ else:
198
+ self.logger.warning(f"WARNING: {desc}不存在(可选): {filename}")
199
+
200
+ # 检查输出文件权限(如果文件已存在)
201
+ out_name = self.args.out if self.args.out else f"{hpm_file.stem}{DEFAULT_OUT_SUFFIX}"
202
+ new_hpm = self.base_dir / out_name
203
+ if new_hpm.exists():
204
+ if not os.access(new_hpm, os.W_OK):
205
+ self.logger.error(f"ERROR: 输出文件已存在且无写入权限: {new_hpm}")
206
+ return False
207
+ self.logger.info(f"SUCCESS: 输出文件可覆盖: {new_hpm}")
208
+
209
+ self.logger.info(">>> 权限检查通过!")
210
+ return True
211
+
212
+ def main(self, hpm_file: Path):
213
+ # 创建必要目录(源目录下)
214
+ self.tmp_dir.mkdir(parents=True, exist_ok=True)
215
+
216
+ base_name = hpm_file.stem
217
+ self.logger.info(f"源文件: {hpm_file}")
218
+
219
+ # 计算默认输出文件路径(同目录)
220
+ out_name = self.args.out if self.args.out else f"{base_name}{DEFAULT_OUT_SUFFIX}"
221
+ new_hpm = (self.base_dir / out_name).resolve()
222
+
223
+ # 1. 解析HPM文件头
224
+ self.logger.info("\n>>> 开始解析HPM文件...")
225
+ with open(hpm_file, 'rb') as f:
226
+ header_data = f.read(56)
227
+ if len(header_data) != 56:
228
+ self.logger.error(f"ERROR: 文件太小 ({len(header_data)}字节),无法解析头部信息!")
229
+ return
230
+
231
+ try:
232
+ header_bin = bytes.fromhex(header_data.decode('ascii'))
233
+ except Exception as e:
234
+ raise RuntimeError("头部数据不是合法的 ASCII Hex") from e
235
+
236
+ if len(header_bin) != 28:
237
+ raise RuntimeError(f"头部数据长度不合法 {len(header_bin)},期望 56")
238
+
239
+ header_ints = struct.unpack('>7I', header_bin)
240
+ magic, section_count, filelist_len, _, cms_len, _, crl_len = header_ints
241
+
242
+ skip1 = 56
243
+ skip2 = skip1 + filelist_len
244
+ skip3 = skip2 + cms_len
245
+ skip4 = skip3 + crl_len
246
+
247
+ self.logger.info(f"\n>>> 开始计算偏移量...")
248
+
249
+ # 2. 提取各部分(放到源目录的 tmp 下)
250
+ self.logger.info(f"\n>>> 开始提取文件...")
251
+
252
+ filelist_path = self.tmp_dir / f"{base_name}.filelist"
253
+ cms_path = self.tmp_dir / f"{base_name}.cms"
254
+ crl_path = self.tmp_dir / f"{base_name}.crl"
255
+ bin_path = self.tmp_dir / f"{base_name}.bin"
256
+
257
+ if self.extract_part(hpm_file, skip1, filelist_len, filelist_path):
258
+ self.logger.info(f"SUCCESS: 成功提取filelist到 {filelist_path}")
259
+ if self.extract_part(hpm_file, skip2, cms_len, cms_path):
260
+ self.logger.info(f"SUCCESS: 成功提取cms到 {cms_path}")
261
+ if self.extract_part(hpm_file, skip3, crl_len, crl_path):
262
+ self.logger.info(f"SUCCESS: 成功提取crl到 {crl_path}")
263
+ if self.extract_to_end(hpm_file, skip4, bin_path):
264
+ self.logger.info(f"SUCCESS: 成功提取bin到 {bin_path}")
265
+
266
+ required_files = [filelist_path, cms_path, crl_path, bin_path]
267
+ if not self.all_files_exist(required_files):
268
+ self.logger.error(f"ERROR: 部分文件提取失败!")
269
+ return
270
+
271
+ # --- 执行 openssl 证书和 CRL 转换(来自 --ca-dir)---
272
+ ca_dir: Path = self.args.ca_dir.resolve()
273
+ der_cert = ca_dir / "rootca.der"
274
+ der_crl = ca_dir / "rootca.crl"
275
+ pem_cert = self.tmp_dir / "rootca.pem"
276
+ pem_crl = self.tmp_dir / "cms.crl.pem"
277
+
278
+ # 转换 rootca.der 为 rootca.pem
279
+ if der_cert.exists():
280
+ cmd_cert = [
281
+ "openssl", "x509",
282
+ "-in", str(der_cert),
283
+ "-inform", "der",
284
+ "-outform", "pem",
285
+ "-out", str(pem_cert)
286
+ ]
287
+ self.logger.info("\n>>> " + " ".join(cmd_cert))
288
+ try:
289
+ subprocess.run(cmd_cert, check=True)
290
+ self.logger.info(f"SUCCESS: 生成 PEM 证书 {pem_cert}")
291
+ except Exception as e:
292
+ self.logger.error(f"ERROR: 证书转换失败: {e}")
293
+ else:
294
+ self.logger.error(f"ERROR: 未找到 DER 证书文件 {der_cert}")
295
+
296
+ # 转换 rootca.crl 为 cms.crl.pem
297
+ if der_crl.exists():
298
+ cmd_crl = [
299
+ "openssl", "crl",
300
+ "-in", str(der_crl),
301
+ "-inform", "der",
302
+ "-outform", "pem",
303
+ "-out", str(pem_crl)
304
+ ]
305
+ self.logger.info(">>> " + " ".join(cmd_crl))
306
+ try:
307
+ subprocess.run(cmd_crl, check=True)
308
+ self.logger.info(f"SUCCESS: 生成 PEM CRL {pem_crl}")
309
+ except Exception as e:
310
+ self.logger.error(f"ERROR: CRL转换失败: {e}")
311
+ else:
312
+ self.logger.error(f"ERROR: 未找到 DER CRL 文件 {der_crl}")
313
+
314
+ # 3. 重新签名filelist
315
+ self.logger.info(f"\n>>> 开始重新签名...")
316
+ signer_pem = ca_dir / "signer.pem"
317
+ ts_signer_pem = ca_dir / "ts_signer.pem"
318
+ tsa_cnf = ca_dir / "tsa.cnf"
319
+ cms1_path = self.tmp_dir / f"{base_name}.cms1"
320
+
321
+ if not signer_pem.exists():
322
+ self.logger.error(f"ERROR: 签名证书 {signer_pem} 不存在")
323
+ return
324
+
325
+ # 构建签名命令 - 只在时间戳证书存在时添加时间戳参数
326
+ cmd = [
327
+ "hpm_signer",
328
+ "-s", str(signer_pem),
329
+ "-i", str(filelist_path),
330
+ "-o", str(cms1_path)
331
+ ]
332
+
333
+ # 只有在时间戳证书和配置文件都存在时才添加时间戳参数
334
+ if ts_signer_pem.exists() and tsa_cnf.exists():
335
+ cmd.extend(["-t", str(ts_signer_pem), "-T", str(tsa_cnf)])
336
+ self.logger.info(">>> 使用时间戳签名")
337
+ else:
338
+ self.logger.info(">>> 使用基础签名(无时间戳)")
339
+
340
+ # 添加密码参数(如果提供了密码)
341
+ if self.args.signer_pass:
342
+ cmd.extend(["-p", self.args.signer_pass])
343
+ self.logger.info(">>> 使用加密签名证书")
344
+
345
+ self.logger.info(">>> " + " ".join(cmd))
346
+
347
+ try:
348
+ subprocess.run(cmd, check=True)
349
+ self.logger.info(f">>> SUCCESS: 重新签名成功!")
350
+ # --- 签名成功后自动验证 ---
351
+ self.logger.info(f"\n>>> 开始验证签名...")
352
+ verify_cmd = [
353
+ "hpm_verify",
354
+ "-r", str(pem_cert),
355
+ "-C", str(pem_crl),
356
+ "-c", str(filelist_path),
357
+ "-s", str(cms1_path)
358
+ ]
359
+ self.logger.info(">>> " + " ".join(verify_cmd))
360
+ try:
361
+ subprocess.run(verify_cmd, check=True)
362
+ self.logger.info(f">>> SUCCESS: 签名验证通过!")
363
+ except subprocess.CalledProcessError:
364
+ self.logger.error(f">>> ERROR: 签名验证失败!")
365
+ except FileNotFoundError:
366
+ self.logger.error(f">>> ERROR: hpm_verify 命令未找到")
367
+ except subprocess.CalledProcessError:
368
+ self.logger.error(f">>> ERROR: 重新签名失败!")
369
+ return
370
+ except FileNotFoundError:
371
+ self.logger.error(f">>> ERROR: hpm_signer 命令未找到")
372
+ return
373
+
374
+ # 4. 重新打包HPM文件(输出到源目录)
375
+ self.logger.info(f"\n>>> 开始重新打包HPM文件...")
376
+ rootca_crl = der_crl
377
+ filelist_size = filelist_path.stat().st_size
378
+ cms1_size = cms1_path.stat().st_size
379
+ crl_size = rootca_crl.stat().st_size
380
+ bin_size = bin_path.stat().st_size
381
+
382
+ # 检查输出文件写入权限
383
+ try:
384
+ with open(new_hpm, "wb") as out_f:
385
+ # 写入头部 (ASCII hex)
386
+ out_f.write(b"00000003") # magic
387
+ out_f.write(b"00000001") # section_count
388
+ self.write_ascii_hex(out_f, filelist_size) # filelist长度
389
+ out_f.write(b"00000002")
390
+ self.write_ascii_hex(out_f, cms1_size) # cms长度
391
+ out_f.write(b"00000003")
392
+ self.write_ascii_hex(out_f, crl_size) # crl长度
393
+
394
+ # 依次写入各部分内容
395
+ for part in [filelist_path, cms1_path, rootca_crl, bin_path]:
396
+ with open(part, "rb") as pf:
397
+ shutil.copyfileobj(pf, out_f)
398
+
399
+ self.logger.info(f">>> SUCCESS: 成功打包HPM文件到: {new_hpm}")
400
+ except PermissionError as e:
401
+ self.logger.error(f">>> ERROR: 无权限写入输出文件: {new_hpm}")
402
+ raise
403
+ except Exception as e:
404
+ self.logger.error(f">>> ERROR: 写入输出文件失败: {e}")
405
+ raise
406
+
407
+
408
+ def clear_temp(self):
409
+ """清理临时目录(源目录下的 tmp)"""
410
+ if self.tmp_dir and self.tmp_dir.exists():
411
+ self.logger.info(f"\n>>> 清理临时文件和目录 {self.tmp_dir} 及同目录序列文件(serial)...")
412
+ try:
413
+ # 检查临时目录删除权限
414
+ if os.access(self.tmp_dir, os.W_OK):
415
+ shutil.rmtree(self.tmp_dir)
416
+ self.logger.info(f"SUCCESS: 已删除 {self.tmp_dir} 及其所有文件")
417
+ else:
418
+ self.logger.warning(f"WARNING: 无权限删除临时目录: {self.tmp_dir}")
419
+
420
+ serial_file = (self.base_dir / "serial")
421
+ if serial_file.exists() and serial_file.is_file():
422
+ if os.access(serial_file, os.W_OK):
423
+ serial_file.unlink()
424
+ self.logger.info(f"SUCCESS: 已删除序列文件: {serial_file}")
425
+ else:
426
+ self.logger.warning(f"WARNING: 无权限删除序列文件: {serial_file}")
427
+ except Exception as e:
428
+ self.logger.error(f"ERROR: 删除临时目录和文件失败: {e}")
429
+ else:
430
+ self.logger.info("INFO: 临时目录不存在,无需清理")
431
+
432
+ def extract_part(self, source: Path, skip: int, count: int, dest: Path):
433
+ """提取文件的指定部分"""
434
+ self.logger.info(f"提取文件: {dest} (skip={skip}, count={count})")
435
+ cmd = ["dd", f"if={str(source)}", f"of={str(dest)}", "bs=1",
436
+ f"skip={skip}", f"count={count}", "status=none"]
437
+ try:
438
+ subprocess.run(cmd, check=True)
439
+ actual_size = dest.stat().st_size
440
+ if actual_size == count:
441
+ return True
442
+ else:
443
+ self.logger.error(f"ERROR:文件大小不匹配 ({actual_size} != {count}),文件名: {dest}")
444
+ return False
445
+ except subprocess.CalledProcessError:
446
+ self.logger.error(f"ERROR: 提取文件失败,文件名: {dest}")
447
+ return False
448
+ except Exception as e:
449
+ self.logger.error(f"ERROR: 提取文件异常,文件名: {dest},原因: {str(e)}")
450
+ return False
451
+
452
+ def extract_to_end(self, source: Path, skip: int, dest: Path):
453
+ """提取文件从指定位置到末尾"""
454
+ self.logger.info(f"提取文件: {dest}")
455
+ total_size = source.stat().st_size
456
+ count = total_size - skip
457
+ cmd = ["dd", f"if={str(source)}", f"of={str(dest)}", "bs=1",
458
+ f"skip={skip}", f"count={count}", "status=none"]
459
+ try:
460
+ subprocess.run(cmd, check=True)
461
+ actual_size = dest.stat().st_size
462
+ if actual_size == count:
463
+ return True
464
+ else:
465
+ self.logger.warning(f"WARNING: 文件大小不匹配 ({actual_size} != {count}),文件名: {dest}")
466
+ return True
467
+ except subprocess.CalledProcessError:
468
+ self.logger.error(f"ERROR: 提取文件失败,文件名: {dest}")
469
+ return False
470
+ except Exception as e:
471
+ self.logger.error(f"ERROR: 提取文件异常,文件名: {dest},原因: {str(e)}")
472
+ return False
473
+
474
+ def all_files_exist(self, file_list):
475
+ """检查所有文件是否存在"""
476
+ missing = [f for f in file_list if not Path(f).exists()]
477
+ if missing:
478
+ self.logger.error("\nERROR: 以下文件缺失:")
479
+ for f in missing:
480
+ self.logger.error(f" - {f}")
481
+ return False
482
+ self.logger.info("\nSUCCESS: 所有文件提取成功!")
483
+
484
+ return True
bmcgo/functional/new.py CHANGED
@@ -42,11 +42,13 @@ _DEFAULT = ""
42
42
  # 必选项:选项缩写,选项,选项的中文说明
43
43
  _REQUIRES = [["n", "name", "组件名"]]
44
44
  # 可选项:选项缩写,选项,选项的中文说明,可选值,默认值
45
- _OPTIONS = [["t", "type", "组件类型", ["application"], "application"], ["l", "language", "组件编程语言", ["lua"], "lua"]]
45
+ _OPTIONS = [["t", "type", "组件类型", ["application"], "application"],
46
+ ["l", "language", "组件编程语言", ["lua"], "lua"],
47
+ ["conan", "conan_version", "组件支持的conan版本", ["1.0", "2.0"], "1.0"]]
46
48
  # 环境中存放bmcgo自动生成工具相对的目录,组件中存放bmcgo自动生成工具的临时相对目录,模板脚本的相对路径,模板的相对目录
47
49
  _TEMPLATES = {
48
50
  _LUA : {
49
- _APP : ['codegen/lua', 'temp/lua_codegen', 'script/template.py', 'templates/new_app']
51
+ _APP: ['codegen/lua', 'temp/lua_codegen', 'script/template.py', 'templates/new_app', 'templates/new_app_v2']
50
52
  }
51
53
  }
52
54
 
@@ -66,6 +68,7 @@ class BmcgoCommand:
66
68
  self.name = parsed_args.name
67
69
  self.type = parsed_args.type
68
70
  self.language = parsed_args.language
71
+ self.conan_version = parsed_args.conan_version
69
72
  self.path = os.path.join(cwd, self.name)
70
73
 
71
74
  def run(self):
@@ -134,6 +137,9 @@ class BmcgoCommand:
134
137
 
135
138
  script_file = os.path.join(gen_tool_dir, template[2])
136
139
  template_dir = os.path.join(gen_tool_dir, template[3])
140
+ if self.conan_version == "2.0":
141
+ conan2_template_dir = os.path.join(gen_tool_dir, template[4])
142
+ shutil.copytree(conan2_template_dir, template_dir, dirs_exist_ok=True)
137
143
 
138
144
  for root, _, files in os.walk(template_dir):
139
145
  rel_dir = os.path.relpath(root, template_dir)