openubmc-bingo 0.5.240__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.

Potentially problematic release.


This version of openubmc-bingo might be problematic. Click here for more details.

Files changed (242) hide show
  1. bmcgo/__init__.py +12 -0
  2. bmcgo/bmcgo.py +22 -0
  3. bmcgo/bmcgo_config.py +176 -0
  4. bmcgo/cli/__init__.py +10 -0
  5. bmcgo/cli/cli.py +584 -0
  6. bmcgo/codegen/__init__.py +14 -0
  7. bmcgo/codegen/c/__init__.py +9 -0
  8. bmcgo/codegen/c/annotation.py +52 -0
  9. bmcgo/codegen/c/argument.py +42 -0
  10. bmcgo/codegen/c/codegen.py +153 -0
  11. bmcgo/codegen/c/comment.py +22 -0
  12. bmcgo/codegen/c/ctype_defination.py +353 -0
  13. bmcgo/codegen/c/helper.py +87 -0
  14. bmcgo/codegen/c/interface.py +63 -0
  15. bmcgo/codegen/c/method.py +82 -0
  16. bmcgo/codegen/c/property.py +180 -0
  17. bmcgo/codegen/c/renderer.py +21 -0
  18. bmcgo/codegen/c/signal.py +64 -0
  19. bmcgo/codegen/c/template/client.c.mako +145 -0
  20. bmcgo/codegen/c/template/client.h.mako +36 -0
  21. bmcgo/codegen/c/template/interface.c.mako +0 -0
  22. bmcgo/codegen/c/template/interface.introspect.xml.mako +99 -0
  23. bmcgo/codegen/c/template/micro_component.c.mako +32 -0
  24. bmcgo/codegen/c/template/public.c.mako +228 -0
  25. bmcgo/codegen/c/template/public.h.mako +128 -0
  26. bmcgo/codegen/c/template/server.c.mako +104 -0
  27. bmcgo/codegen/c/template/server.h.mako +36 -0
  28. bmcgo/codegen/lua/.lua-format +7 -0
  29. bmcgo/codegen/lua/Makefile +101 -0
  30. bmcgo/codegen/lua/__init__.py +9 -0
  31. bmcgo/codegen/lua/codegen.py +171 -0
  32. bmcgo/codegen/lua/proto/Makefile +87 -0
  33. bmcgo/codegen/lua/proto/ipmi_types.proto +17 -0
  34. bmcgo/codegen/lua/proto/types.proto +52 -0
  35. bmcgo/codegen/lua/script/check_intfs.py +161 -0
  36. bmcgo/codegen/lua/script/dto/__init__.py +11 -0
  37. bmcgo/codegen/lua/script/dto/exception.py +53 -0
  38. bmcgo/codegen/lua/script/dto/kepler_abstract.py +47 -0
  39. bmcgo/codegen/lua/script/dto/options.py +33 -0
  40. bmcgo/codegen/lua/script/dto/print_simple.py +19 -0
  41. bmcgo/codegen/lua/script/dto/redfish_api.py +241 -0
  42. bmcgo/codegen/lua/script/dto/url_route.py +195 -0
  43. bmcgo/codegen/lua/script/gen_db_json.py +444 -0
  44. bmcgo/codegen/lua/script/gen_depends.py +89 -0
  45. bmcgo/codegen/lua/script/gen_entry.py +263 -0
  46. bmcgo/codegen/lua/script/gen_feature_json.py +156 -0
  47. bmcgo/codegen/lua/script/gen_historical_local_db_json.py +88 -0
  48. bmcgo/codegen/lua/script/gen_intf_json.py +261 -0
  49. bmcgo/codegen/lua/script/gen_intf_rpc_json.py +575 -0
  50. bmcgo/codegen/lua/script/gen_ipmi_json.py +485 -0
  51. bmcgo/codegen/lua/script/gen_mdb_json.py +117 -0
  52. bmcgo/codegen/lua/script/gen_rpc_msg_json.py +487 -0
  53. bmcgo/codegen/lua/script/gen_schema.py +302 -0
  54. bmcgo/codegen/lua/script/ipmi_types_pb2.py +135 -0
  55. bmcgo/codegen/lua/script/loader/__init__.py +11 -0
  56. bmcgo/codegen/lua/script/loader/file_utils.py +33 -0
  57. bmcgo/codegen/lua/script/loader/kepler_abstract_collect.py +79 -0
  58. bmcgo/codegen/lua/script/loader/kepler_abstract_loader.py +47 -0
  59. bmcgo/codegen/lua/script/loader/redfish_loader.py +127 -0
  60. bmcgo/codegen/lua/script/lua_format.py +62 -0
  61. bmcgo/codegen/lua/script/mds_util.py +385 -0
  62. bmcgo/codegen/lua/script/merge_model.py +330 -0
  63. bmcgo/codegen/lua/script/merge_proto_algo.py +85 -0
  64. bmcgo/codegen/lua/script/proto_loader.py +47 -0
  65. bmcgo/codegen/lua/script/proto_plugin.py +140 -0
  66. bmcgo/codegen/lua/script/redfish_source_tree.py +118 -0
  67. bmcgo/codegen/lua/script/render_utils/__init__.py +38 -0
  68. bmcgo/codegen/lua/script/render_utils/base.py +25 -0
  69. bmcgo/codegen/lua/script/render_utils/client_lua.py +98 -0
  70. bmcgo/codegen/lua/script/render_utils/controller_lua.py +71 -0
  71. bmcgo/codegen/lua/script/render_utils/db_lua.py +224 -0
  72. bmcgo/codegen/lua/script/render_utils/error_lua.py +185 -0
  73. bmcgo/codegen/lua/script/render_utils/factory.py +52 -0
  74. bmcgo/codegen/lua/script/render_utils/ipmi_lua.py +159 -0
  75. bmcgo/codegen/lua/script/render_utils/ipmi_message_lua.py +24 -0
  76. bmcgo/codegen/lua/script/render_utils/mdb_lua.py +177 -0
  77. bmcgo/codegen/lua/script/render_utils/mdb_register.py +215 -0
  78. bmcgo/codegen/lua/script/render_utils/message_lua.py +26 -0
  79. bmcgo/codegen/lua/script/render_utils/messages_lua.py +156 -0
  80. bmcgo/codegen/lua/script/render_utils/model_lua.py +485 -0
  81. bmcgo/codegen/lua/script/render_utils/old_model_lua.py +429 -0
  82. bmcgo/codegen/lua/script/render_utils/plugin_lua.py +38 -0
  83. bmcgo/codegen/lua/script/render_utils/redfish_proto.py +86 -0
  84. bmcgo/codegen/lua/script/render_utils/request_lua.py +76 -0
  85. bmcgo/codegen/lua/script/render_utils/service_lua.py +130 -0
  86. bmcgo/codegen/lua/script/render_utils/utils_message_lua.py +125 -0
  87. bmcgo/codegen/lua/script/render_utils/validate_lua.py +221 -0
  88. bmcgo/codegen/lua/script/sep_ipmi_message_cmds.py +217 -0
  89. bmcgo/codegen/lua/script/template.py +166 -0
  90. bmcgo/codegen/lua/script/types_pb2.py +516 -0
  91. bmcgo/codegen/lua/script/utils.py +663 -0
  92. bmcgo/codegen/lua/script/validate.py +80 -0
  93. bmcgo/codegen/lua/script/yaml_to_json.py +73 -0
  94. bmcgo/codegen/lua/templates/Makefile +114 -0
  95. bmcgo/codegen/lua/templates/apps/Makefile +261 -0
  96. bmcgo/codegen/lua/templates/apps/Makefile.mdb.mk +64 -0
  97. bmcgo/codegen/lua/templates/apps/app.lua.mako +19 -0
  98. bmcgo/codegen/lua/templates/apps/class.lua.mako +35 -0
  99. bmcgo/codegen/lua/templates/apps/client.lua.mako +429 -0
  100. bmcgo/codegen/lua/templates/apps/controller.lua.mako +276 -0
  101. bmcgo/codegen/lua/templates/apps/datas.lua.mako +8 -0
  102. bmcgo/codegen/lua/templates/apps/db.lua.mako +89 -0
  103. bmcgo/codegen/lua/templates/apps/entry.lua.mako +128 -0
  104. bmcgo/codegen/lua/templates/apps/feature.lua.mako +37 -0
  105. bmcgo/codegen/lua/templates/apps/generate_route.mako +25 -0
  106. bmcgo/codegen/lua/templates/apps/impl_feature.lua.mako +72 -0
  107. bmcgo/codegen/lua/templates/apps/ipmi.lua.mako +97 -0
  108. bmcgo/codegen/lua/templates/apps/ipmi_cmd.lua.mako +18 -0
  109. bmcgo/codegen/lua/templates/apps/ipmi_message.lua.mako +36 -0
  110. bmcgo/codegen/lua/templates/apps/local_db.lua.mako +263 -0
  111. bmcgo/codegen/lua/templates/apps/main.lua.mako +25 -0
  112. bmcgo/codegen/lua/templates/apps/mc.lua.mako +77 -0
  113. bmcgo/codegen/lua/templates/apps/mdb.lua.mako +45 -0
  114. bmcgo/codegen/lua/templates/apps/mdb_interface.lua.mako +73 -0
  115. bmcgo/codegen/lua/templates/apps/message.lua.mako +38 -0
  116. bmcgo/codegen/lua/templates/apps/model.lua.mako +239 -0
  117. bmcgo/codegen/lua/templates/apps/orm_classes.lua.mako +16 -0
  118. bmcgo/codegen/lua/templates/apps/plugin.lua.mako +8 -0
  119. bmcgo/codegen/lua/templates/apps/redfish.proto.mako +47 -0
  120. bmcgo/codegen/lua/templates/apps/service.lua.mako +440 -0
  121. bmcgo/codegen/lua/templates/apps/signal_listen.lua.mako +19 -0
  122. bmcgo/codegen/lua/templates/apps/utils/default_intf.lua.mako +41 -0
  123. bmcgo/codegen/lua/templates/apps/utils/enum.mako +10 -0
  124. bmcgo/codegen/lua/templates/apps/utils/imports.mako +13 -0
  125. bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +25 -0
  126. bmcgo/codegen/lua/templates/apps/utils/mdb_obj.lua.mako +23 -0
  127. bmcgo/codegen/lua/templates/apps/utils/message.mako +160 -0
  128. bmcgo/codegen/lua/templates/apps/utils/request.lua.mako +59 -0
  129. bmcgo/codegen/lua/templates/apps/utils/validate.mako +83 -0
  130. bmcgo/codegen/lua/templates/errors.lua.mako +36 -0
  131. bmcgo/codegen/lua/templates/messages.lua.mako +32 -0
  132. bmcgo/codegen/lua/templates/new_app/.clang-format.mako +170 -0
  133. bmcgo/codegen/lua/templates/new_app/.gitignore.mako +26 -0
  134. bmcgo/codegen/lua/templates/new_app/CHANGELOG.md.mako +0 -0
  135. bmcgo/codegen/lua/templates/new_app/CMakeLists.txt.mako +29 -0
  136. bmcgo/codegen/lua/templates/new_app/Makefile.mako +25 -0
  137. bmcgo/codegen/lua/templates/new_app/README.md.mako +0 -0
  138. bmcgo/codegen/lua/templates/new_app/conanfile.py.mako +7 -0
  139. bmcgo/codegen/lua/templates/new_app/config.cfg.mako +6 -0
  140. bmcgo/codegen/lua/templates/new_app/mds/model.json.mako +3 -0
  141. bmcgo/codegen/lua/templates/new_app/mds/service.json.mako +21 -0
  142. bmcgo/codegen/lua/templates/new_app/permissions.ini.mako +16 -0
  143. bmcgo/codegen/lua/templates/new_app/src/lualib/${project_name}_app.lua.mako +16 -0
  144. bmcgo/codegen/lua/templates/new_app/src/service/main.lua.mako +25 -0
  145. bmcgo/codegen/lua/templates/new_app/test/integration/test_${project_name}.conf.mako +9 -0
  146. bmcgo/codegen/lua/templates/new_app/test/integration/test_${project_name}.lua.mako +47 -0
  147. bmcgo/codegen/lua/templates/new_app/test/unit/test.lua.mako +23 -0
  148. bmcgo/codegen/lua/templates/new_app/user_conf/rootfs/etc/systemd/system/${project_name}.service.mako +18 -0
  149. bmcgo/codegen/lua/templates/new_app/user_conf/rootfs/etc/systemd/system/multi-user.target.wants/${project_name}.service.link +1 -0
  150. bmcgo/component/__init__.py +10 -0
  151. bmcgo/component/analysis/analysis.py +183 -0
  152. bmcgo/component/analysis/build_deps.py +165 -0
  153. bmcgo/component/analysis/data_deps.py +333 -0
  154. bmcgo/component/analysis/dep-rules.json +912 -0
  155. bmcgo/component/analysis/dep_node.py +110 -0
  156. bmcgo/component/analysis/intf_deps.py +163 -0
  157. bmcgo/component/analysis/intf_validation.py +254 -0
  158. bmcgo/component/analysis/rule.py +211 -0
  159. bmcgo/component/analysis/smc_dfx_whitelist.json +11 -0
  160. bmcgo/component/analysis/sr_validation.py +391 -0
  161. bmcgo/component/build.py +222 -0
  162. bmcgo/component/component_dt_version_parse.py +348 -0
  163. bmcgo/component/component_helper.py +114 -0
  164. bmcgo/component/coverage/__init__.py +11 -0
  165. bmcgo/component/coverage/c_incremental_cov_report.template +53 -0
  166. bmcgo/component/coverage/incremental_cov.py +464 -0
  167. bmcgo/component/deploy.py +110 -0
  168. bmcgo/component/gen.py +169 -0
  169. bmcgo/component/package_info.py +236 -0
  170. bmcgo/component/template/conanbase.py.mako +278 -0
  171. bmcgo/component/template/conanfile.deploy.py.mako +40 -0
  172. bmcgo/component/test.py +947 -0
  173. bmcgo/errors.py +119 -0
  174. bmcgo/frame.py +217 -0
  175. bmcgo/functional/__init__.py +10 -0
  176. bmcgo/functional/analysis.py +96 -0
  177. bmcgo/functional/bmc_studio_action.py +98 -0
  178. bmcgo/functional/check.py +185 -0
  179. bmcgo/functional/conan_index_build.py +251 -0
  180. bmcgo/functional/config.py +332 -0
  181. bmcgo/functional/csr_build.py +724 -0
  182. bmcgo/functional/deploy.py +263 -0
  183. bmcgo/functional/diff.py +235 -0
  184. bmcgo/functional/fetch.py +235 -0
  185. bmcgo/functional/full_component.py +391 -0
  186. bmcgo/functional/maintain.py +381 -0
  187. bmcgo/functional/new.py +166 -0
  188. bmcgo/functional/schema_valid.py +111 -0
  189. bmcgo/functional/simple_sign.py +104 -0
  190. bmcgo/functional/upgrade.py +78 -0
  191. bmcgo/ipmigen/__init__.py +13 -0
  192. bmcgo/ipmigen/ctype_defination.py +82 -0
  193. bmcgo/ipmigen/ipmigen.py +309 -0
  194. bmcgo/ipmigen/template/cmd.c.mako +366 -0
  195. bmcgo/ipmigen/template/ipmi.c.mako +25 -0
  196. bmcgo/ipmigen/template/ipmi.h.mako +51 -0
  197. bmcgo/logger.py +176 -0
  198. bmcgo/misc.py +117 -0
  199. bmcgo/target/app.yml +17 -0
  200. bmcgo/target/install_sdk.yml +15 -0
  201. bmcgo/target/personal.yml +53 -0
  202. bmcgo/target/publish.yml +45 -0
  203. bmcgo/tasks/__init__.py +11 -0
  204. bmcgo/tasks/download_buildtools_hm.py +124 -0
  205. bmcgo/tasks/misc.py +15 -0
  206. bmcgo/tasks/task.py +354 -0
  207. bmcgo/tasks/task_build_conan.py +714 -0
  208. bmcgo/tasks/task_build_rootfs_img.py +595 -0
  209. bmcgo/tasks/task_buildgppbin.py +88 -0
  210. bmcgo/tasks/task_buildhpm_ext4.py +82 -0
  211. bmcgo/tasks/task_create_interface_config.py +122 -0
  212. bmcgo/tasks/task_download_buildtools.py +99 -0
  213. bmcgo/tasks/task_download_dependency.py +72 -0
  214. bmcgo/tasks/task_hpm_envir_prepare.py +112 -0
  215. bmcgo/tasks/task_packet_to_supporte.py +87 -0
  216. bmcgo/tasks/task_prepare.py +105 -0
  217. bmcgo/tasks/task_sign_and_pack_hpm.py +42 -0
  218. bmcgo/utils/__init__.py +10 -0
  219. bmcgo/utils/buffer.py +128 -0
  220. bmcgo/utils/combine_json_schemas.py +170 -0
  221. bmcgo/utils/component_post.py +54 -0
  222. bmcgo/utils/component_version_check.py +86 -0
  223. bmcgo/utils/config.py +1067 -0
  224. bmcgo/utils/fetch_component_code.py +232 -0
  225. bmcgo/utils/install_manager.py +61 -0
  226. bmcgo/utils/installations/__init__.py +10 -0
  227. bmcgo/utils/installations/base_installer.py +70 -0
  228. bmcgo/utils/installations/install_consts.py +30 -0
  229. bmcgo/utils/installations/install_plans/bingo.yml +11 -0
  230. bmcgo/utils/installations/install_workflow.py +50 -0
  231. bmcgo/utils/installations/installers/apt_installer.py +177 -0
  232. bmcgo/utils/installations/installers/pip_installer.py +46 -0
  233. bmcgo/utils/installations/version_util.py +100 -0
  234. bmcgo/utils/mapping_config_patch.py +443 -0
  235. bmcgo/utils/perf_analysis.py +114 -0
  236. bmcgo/utils/tools.py +704 -0
  237. bmcgo/worker.py +417 -0
  238. openubmc_bingo-0.5.240.dist-info/METADATA +30 -0
  239. openubmc_bingo-0.5.240.dist-info/RECORD +242 -0
  240. openubmc_bingo-0.5.240.dist-info/WHEEL +5 -0
  241. openubmc_bingo-0.5.240.dist-info/entry_points.txt +2 -0
  242. openubmc_bingo-0.5.240.dist-info/top_level.txt +1 -0
bmcgo/utils/tools.py ADDED
@@ -0,0 +1,704 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+ # Copyright (c) 2024 Huawei Technologies Co., Ltd.
4
+ # openUBMC is licensed under Mulan PSL v2.
5
+ # You can use this software according to the terms and conditions of the Mulan PSL v2.
6
+ # You may obtain a copy of Mulan PSL v2 at:
7
+ # http://license.coscl.org.cn/MulanPSL2
8
+ # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
9
+ # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
10
+ # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
11
+ # See the Mulan PSL v2 for more details.
12
+
13
+ import os
14
+ import argparse
15
+ import json
16
+ import time
17
+ import re
18
+ import subprocess
19
+ import shutil
20
+ import tarfile
21
+ import hashlib
22
+ import shlex
23
+ import fcntl
24
+ import stat
25
+ import functools
26
+ import inspect
27
+ import configparser
28
+ from typing import Callable
29
+ from tempfile import TemporaryFile
30
+ from string import Template
31
+ from subprocess import Popen, PIPE
32
+ from collections import OrderedDict
33
+
34
+ import jsonschema
35
+ import yaml
36
+ from conans.model.profile import Profile
37
+ from conans.client.profile_loader import read_profile
38
+
39
+ from bmcgo.logger import Logger
40
+ from bmcgo.errors import BmcGoException, EnvironmentException
41
+ from bmcgo import misc
42
+ from bmcgo import errors
43
+ from bmcgo.logger import Logger
44
+
45
+
46
+ class Tools():
47
+ """
48
+ 功能描述:公共类定义全局变量,公共函数
49
+ 接口:无
50
+ """
51
+ print_cnt = 0
52
+
53
+ def __init__(self, log_name="bingo", log_file=None):
54
+ os.makedirs(misc.CACHE_DIR, exist_ok=True)
55
+ self.lock_file = os.path.join(misc.CACHE_DIR, "frame_lock")
56
+ if log_file is None:
57
+ self.log_name = os.path.join(misc.CACHE_DIR, f"{log_name}.log")
58
+ else:
59
+ self.log_name = log_file
60
+ self.log = Logger(log_name, log_file=self.log_name)
61
+ file_handle = os.fdopen(os.open(self.lock_file, os.O_WRONLY | os.O_CREAT,
62
+ stat.S_IWUSR | stat.S_IRUSR), 'a')
63
+ file_handle.close()
64
+
65
+ @property
66
+ def user_home(self):
67
+ return os.environ["HOME"]
68
+
69
+ @property
70
+ def conan_home(self):
71
+ return os.path.expanduser("~/.conan")
72
+
73
+ @property
74
+ def conan_profiles_dir(self):
75
+ return os.path.join(self.conan_home, "profiles")
76
+
77
+ @property
78
+ def conan_profiles(self):
79
+ profiles = []
80
+ for profile in os.listdir(self.conan_profiles_dir):
81
+ if os.path.isfile(os.path.join(self.conan_profiles_dir, profile)):
82
+ profiles.append(profile)
83
+ return profiles
84
+
85
+ @property
86
+ def conan_data(self):
87
+ return os.path.join(self.conan_home, "data")
88
+
89
+ @staticmethod
90
+ def format_command(command, sudo=False):
91
+ cmd_args: list[str] = []
92
+ if isinstance(command, list):
93
+ cmd_args = command
94
+ elif isinstance(command, str):
95
+ cmd_args = shlex.split(command)
96
+ cmd = shutil.which(cmd_args[0])
97
+ if cmd is None:
98
+ raise EnvironmentException(f"{cmd_args[0]}不存在, 请检查命令或环境配置")
99
+ cmd_args[0] = cmd
100
+ else:
101
+ raise BmcGoException(f"不支持的命令参数格式: {command}, 请检查命令格式")
102
+ sudo_cmd = shutil.which("sudo")
103
+ if sudo and sudo_cmd and cmd_args[0] != sudo_cmd:
104
+ cmd_args.insert(0, sudo_cmd)
105
+ return cmd_args
106
+
107
+ @staticmethod
108
+ def check_path(path):
109
+ """
110
+ 功能描述:检查目录是否存在,没有就创建
111
+ 参数:path 要检查的目录
112
+ 返回值:无
113
+ """
114
+ if not os.path.exists(path) and not os.path.islink(path):
115
+ os.makedirs(path, exist_ok=True)
116
+
117
+ @staticmethod
118
+ def copy(source, target):
119
+ """
120
+ 功能描述:拷贝文件优化,如果存在目标文件先删除后拷贝
121
+ 参数:source 源文件 target 目标文件
122
+ 返回值:无
123
+ """
124
+ if os.path.exists(source):
125
+ if os.path.exists(target):
126
+ os.unlink(target)
127
+ shutil.copy(source, target)
128
+
129
+ @staticmethod
130
+ def sha256sum(file_path: str):
131
+ # 为提高性能, 每次读取64KB数据
132
+ data_block_size = 1024 * 64
133
+ sha256hasher = hashlib.sha256()
134
+ with open(file_path, 'rb') as fp:
135
+ while True:
136
+ data = fp.read(data_block_size)
137
+ if not data:
138
+ break
139
+ sha256hasher.update(data)
140
+ sha256 = sha256hasher.hexdigest()
141
+ return sha256
142
+
143
+ @staticmethod
144
+ def find_key_in_dict(f_dict: dict, key: str):
145
+ results = []
146
+ for k, v in f_dict.items():
147
+ if k == key:
148
+ results.append(v)
149
+ elif isinstance(v, dict):
150
+ results.extend(Tools.find_key_in_dict(v, k))
151
+ return results
152
+
153
+ @staticmethod
154
+ def remove_path(path):
155
+ """
156
+ 功能描述:删除指定的目录
157
+ 参数:path 要删除的目录
158
+ 返回值:无
159
+ """
160
+ if os.path.isdir(path):
161
+ shutil.rmtree(path, ignore_errors=True)
162
+
163
+ @staticmethod
164
+ def has_kwargs(func: Callable):
165
+ """
166
+ 功能描述: 检查function是否有可变的关键字参数**kwargs
167
+ 参数: 需要检查的function
168
+ 返回值: bool
169
+ """
170
+ signature = inspect.signature(func)
171
+ parameters = signature.parameters
172
+ for param in parameters.values():
173
+ if param.kind == param.VAR_KEYWORD:
174
+ return True
175
+ return False
176
+
177
+ @staticmethod
178
+ def get_profile_arg_help():
179
+ tools = Tools()
180
+ profile_help_text = "显式指定conan构建使用的profile文件 (~/.conan/profiles目录下)别名, 默认为: 空\n可选值: "
181
+ profile_help_text += ", ".join(tools.conan_profiles)
182
+ return profile_help_text
183
+
184
+ @staticmethod
185
+ def get_conan_profile(profile: str, build_type: str, enable_luajit=False):
186
+ """
187
+ 根据参数与配置获取conan构建使用的profile文件名。
188
+ """
189
+ if profile:
190
+ return str(profile)
191
+
192
+ if build_type == "dt":
193
+ return "profile.dt.ini"
194
+
195
+ return "profile.luajit.ini"
196
+
197
+ @staticmethod
198
+ def check_product_dependencies(top, base):
199
+ for key, value in top.items():
200
+ if (
201
+ key == "dependencies"
202
+ or key == "dt_dependencies"
203
+ or key == "debug_dependencies"
204
+ ):
205
+ for com_package in value:
206
+ Tools().check_base_dependencies(base, com_package, key)
207
+
208
+ @staticmethod
209
+ def check_base_dependencies(base_manifest, com_package, dependency_type):
210
+ name = com_package.get(misc.CONAN)
211
+ action = com_package.get("action", "")
212
+ pkg_info = name.split("/")
213
+ pkg_name = pkg_info[0]
214
+ has_version = len(pkg_info) > 1
215
+ for sub in base_manifest.get(dependency_type, {}):
216
+ sub_name = sub.get(misc.CONAN)
217
+ sub_pkg_name = sub_name.split("/")[0]
218
+ # 上下层具有相同组件名的组件
219
+ if pkg_name == sub_pkg_name:
220
+ if has_version or action:
221
+ raise errors.BmcGoException(
222
+ "配置错误: 不允许删除平台组件或定制平台组件版本号{}".format(name)
223
+ )
224
+ else:
225
+ com_package[misc.CONAN] = sub_name
226
+ break
227
+
228
+ @staticmethod
229
+ def merge_dependencies(top, base):
230
+ ret = []
231
+ for conan in top:
232
+ name = conan.get(misc.CONAN)
233
+ pkg_name = name.split("/")[0]
234
+ found = False
235
+ for sub in base:
236
+ sub_name = sub.get(misc.CONAN)
237
+ sub_pkg_name = sub_name.split("/")[0]
238
+ # 上下层具有相同组件名的组件
239
+ if pkg_name == sub_pkg_name:
240
+ found = True
241
+ break
242
+ # 上下层具有相同组件名的组件
243
+ if found:
244
+ action = conan.get("action")
245
+ if action and action == "delete":
246
+ # 但如果上层配置了delete的
247
+ continue
248
+ else:
249
+ ret.append(conan)
250
+ else:
251
+ # 未找到下层组件时
252
+ ret.append(conan)
253
+ for sub in base:
254
+ sub_name = sub.get(misc.CONAN)
255
+ sub_pkg_name = sub_name.split("/")[0]
256
+ found = False
257
+ for conan in top:
258
+ name = conan.get(misc.CONAN)
259
+ pkg_name = name.split("/")[0]
260
+ # 上下层具有相同组件名的组件
261
+ if pkg_name == sub_pkg_name:
262
+ found = True
263
+ break
264
+ # 当下层组件未在上层配置时,使用下层组件
265
+ if not found:
266
+ ret.append(sub)
267
+ return ret
268
+
269
+ @staticmethod
270
+ def fix_platform(package, stage):
271
+ version_split = re.split(r"[/, @]", package[misc.CONAN])
272
+ if stage == misc.StageEnum.STAGE_DEV.value:
273
+ # 开发者测试模式
274
+ stage = misc.StageEnum.STAGE_RC.value
275
+ # else: rc和stable模式
276
+ channel = f"@{Tools().conan_user}/{stage}"
277
+
278
+ if len(version_split) == 2:
279
+ package[misc.CONAN] += channel
280
+
281
+ @staticmethod
282
+ def install_platform(platform_package, stage, remote):
283
+ Tools().fix_platform(platform_package, stage)
284
+ rtos_version = platform_package.get("options", {}).get(
285
+ "rtos_version", "rtos_v2"
286
+ )
287
+ append_cmd = f"-r {remote}" if remote else ""
288
+ Tools().run_command(
289
+ f"conan install {platform_package[misc.CONAN]} {append_cmd} -o rtos_version={rtos_version}"
290
+ )
291
+ version_split = re.split(r"[/, @]", platform_package[misc.CONAN])
292
+ package_conan_dir = os.path.join(
293
+ os.path.expanduser("~"), ".conan/data/", *version_split
294
+ )
295
+ cmd = f"conan info {platform_package[misc.CONAN]} {append_cmd} -o rtos_version={rtos_version} -j"
296
+ cmd_info = Tools().run_command(
297
+ cmd,
298
+ capture_output=True,
299
+ )
300
+ info = json.loads(cmd_info.stdout)
301
+ for i in info:
302
+ package_conan_dir = os.path.join(
303
+ package_conan_dir, "package", i["id"], rtos_version
304
+ )
305
+ return package_conan_dir
306
+
307
+ @staticmethod
308
+ def get_manifest_dependencies(manifest_build_dir, file_path, stage, remote):
309
+ with open(file_path, "r") as f_:
310
+ base_manifest = yaml.safe_load(f_)
311
+ top_dependencies = base_manifest[misc.CONAN_DEPDENCIES_KEY]
312
+ platform_package = base_manifest.get("platform", {})
313
+ if platform_package:
314
+ package_conan_dir = Tools.install_platform(platform_package, stage, remote)
315
+ top_dependencies.append(platform_package)
316
+ platform_src = os.path.join(package_conan_dir, "manifest.yml")
317
+ with open(platform_src, "r") as f_:
318
+ platform_manifest = yaml.safe_load(f_)
319
+ Tools.check_product_dependencies(base_manifest, platform_manifest)
320
+ top_dependencies = Tools.merge_dependencies(
321
+ top_dependencies, platform_manifest[misc.CONAN_DEPDENCIES_KEY]
322
+ )
323
+ inc_filename = base_manifest.get("include")
324
+ if not inc_filename:
325
+ return top_dependencies
326
+ file_path = inc_filename.replace(
327
+ "${product}", os.path.join(manifest_build_dir, "product")
328
+ )
329
+ base_dependencies = Tools.get_manifest_dependencies(manifest_build_dir, file_path, stage, remote)
330
+ dep = Tools.merge_dependencies(top_dependencies, base_dependencies)
331
+ return dep
332
+
333
+ @staticmethod
334
+ def create_common_parser(description):
335
+ parser = argparse.ArgumentParser(description=description)
336
+ parser.add_argument("-cp", "--conan_package", help="软件包名, 示例: kmc/24.0.0.B020", default="")
337
+ parser.add_argument("-uci", "--upload_package", help="是否上传软件包", action=misc.STORE_TRUE)
338
+ parser.add_argument("-o", "--options", help="组件特性配置, 示例: -o skynet:enable_luajit=True",
339
+ action='append')
340
+ parser.add_argument("-r", "--remote", help="指定conan远端")
341
+ parser.add_argument("-bt", "--build_type", help="构建类型,可选:debug(调试包), release(正式包), dt(开发者测试包)",
342
+ default="debug")
343
+ parser.add_argument("--stage", help="包类型,可选值为: dev(调试包), rc(预发布包), stable(发布包)\n默认:dev",
344
+ default="dev")
345
+ parser.add_argument("-jit", "--enable_luajit", help="Enable luajit", action=misc.STORE_TRUE)
346
+ parser.add_argument("-s", "--from_source", help="使能源码构建", action=misc.STORE_TRUE)
347
+ parser.add_argument("-as", "--asan", help="Enable address sanitizer", action=misc.STORE_TRUE)
348
+ parser.add_argument(
349
+ "-pr",
350
+ "--profile",
351
+ help=Tools.get_profile_arg_help(),
352
+ default="",
353
+ )
354
+ return parser
355
+
356
+ @staticmethod
357
+ def get_module_symver_option(module_symver_path: str):
358
+ sha256 = Tools.sha256sum(module_symver_path)
359
+ return ("module_symver", sha256)
360
+
361
+ @staticmethod
362
+ def clean_conan_bin(conan_bin):
363
+ if os.path.isdir(conan_bin):
364
+ for file in os.listdir(conan_bin):
365
+ file_path = os.path.join(conan_bin, file)
366
+ if os.path.isfile(file_path):
367
+ os.remove(file_path)
368
+ else:
369
+ os.makedirs(conan_bin)
370
+
371
+ @functools.cached_property
372
+ def is_ubuntu(self):
373
+ """
374
+ 功能描述: 判断当前环境是否为Ubuntu
375
+ 返回值: bool
376
+ """
377
+ if os.path.isfile("/etc/issue"):
378
+ fp = open("/etc/issue", "r")
379
+ issue = fp.read()
380
+ fp.close()
381
+ if issue.startswith("Ubuntu"):
382
+ return True
383
+ return False
384
+
385
+ @functools.cached_property
386
+ def conan_user(self):
387
+ if os.access(misc.GLOBAL_CFG_FILE, os.R_OK):
388
+ try:
389
+ conf = configparser.ConfigParser()
390
+ conf.read(misc.GLOBAL_CFG_FILE)
391
+ return conf.get(misc.CONAN, "user")
392
+ except (configparser.NoSectionError, configparser.NoOptionError):
393
+ pass
394
+
395
+ return misc.ConanUserEnum.CONAN_USER_RELEASE.value
396
+
397
+ def get_conan_remote_list(self, remote):
398
+ conan_remote_list = []
399
+ if remote:
400
+ conan_remote_list.append(remote)
401
+ else:
402
+ remote_list = self.run_command("conan remote list", capture_output=True).stdout.split("\n")
403
+ conan_remote_list = [remote.split(":")[0] for remote in remote_list if remote]
404
+ return conan_remote_list
405
+
406
+ def download_conan_recipes(self, conan_version, conan_remote_list):
407
+ download_flag = False
408
+ for remote in conan_remote_list:
409
+ try:
410
+ self.run_command(f"conan download {conan_version} -r {remote} -re", show_error_log=False)
411
+ download_flag = True
412
+ break
413
+ except Exception as e:
414
+ self.log.info(f"Recipe not fount in {remote}: {conan_version}")
415
+ if not download_flag:
416
+ raise BmcGoException(f"Download {conan_version} failed")
417
+
418
+ def yaml_load_template(self, yaml_name: str, template: dict, need_validate=True):
419
+ with open(yaml_name, "r", encoding="utf-8") as fp:
420
+ yaml_string = fp.read()
421
+ yaml_string = Template(yaml_string)
422
+ yaml_conf = yaml_string.safe_substitute(template)
423
+ yaml_obj = yaml.full_load(yaml_conf)
424
+ if need_validate:
425
+ schema_file = misc.get_decleared_schema_file(yaml_name)
426
+ if schema_file == "":
427
+ return yaml_obj
428
+ with open(schema_file, "rb") as fp:
429
+ schema = json.load(fp)
430
+ self.log.debug("开始校验 %s", yaml_name)
431
+ try:
432
+ jsonschema.validate(yaml_obj, schema)
433
+ except jsonschema.exceptions.ValidationError as e:
434
+ raise OSError('文件 {} 无法通过schema文件 {} 的校验\n {}'.format(yaml_name, schema_file, str(e))) from e
435
+ return yaml_obj
436
+
437
+ def get_file_permission(self, path):
438
+ with Popen(self.format_command(f"stat -c '%a' {path}", sudo=True), stdout=PIPE) as proc:
439
+ data, _ = proc.communicate(timeout=50)
440
+ return data.decode('utf-8')
441
+
442
+ def copy_all(self, source_path, target_path):
443
+ """
444
+ 功能描述:将 source_path 目录下所有文件全部复制到 target_path 下
445
+ 如果要复制 source_path 文件夹,需指定 target_path 下同名文件夹
446
+ 参数:source_path 源目录 target_path 目标目录
447
+ 返回值:无
448
+ """
449
+ if not os.path.exists(source_path):
450
+ raise OSError(f"源目录 ({source_path}) 不存在, 请检查源文件")
451
+ for root, dirs, files in os.walk(source_path, topdown=True):
452
+ for dirname in dirs:
453
+ dest_dir_t = f"{target_path}/{root[len(source_path) + 1:]}/{dirname}"
454
+ self.check_path(dest_dir_t)
455
+ for file in files:
456
+ src_file = os.path.join(root, file)
457
+ dest_dir = os.path.join(target_path, root[len(source_path) + 1:])
458
+ self.check_path(dest_dir)
459
+ dest_file = os.path.join(dest_dir, os.path.basename(src_file))
460
+ if os.path.isfile(dest_file):
461
+ os.unlink(dest_file)
462
+ if os.path.islink(src_file):
463
+ shutil.copy2(src_file, dest_dir, follow_symlinks=False)
464
+ else:
465
+ shutil.copy2(src_file, dest_dir)
466
+
467
+ def untar_to_dir(self, targz_source, dst_dir="."):
468
+ """
469
+ 功能描述:解压tar包到指定目录
470
+ 参数:targz_source tar包 ,dst_dir 目标目录
471
+ 返回值:无
472
+ """
473
+ self.log.info("解压 - 源文件: {}, 目标文件: {}".format(targz_source, dst_dir))
474
+ tar_temp = tarfile.open(targz_source)
475
+ tar_temp.extractall(dst_dir)
476
+ tar_temp.close()
477
+
478
+ def list_all_file(self, regular_exp, dir_path, recursive=False):
479
+ """
480
+ 功能描述:在 dir_path 列表中,找出符合正则表达式的值路径,并返回列表
481
+ 参数: regular_exp: 正则表达式
482
+ dir_path: 需要查找的路径
483
+ recursive: 是否递归查询(查询所有子文件夹)
484
+ 返回值:无
485
+ """
486
+ dir_match_list = []
487
+ dir_list = os.listdir(dir_path)
488
+ for element in dir_list:
489
+ if recursive and os.path.isdir(f"{dir_path}/{element}"):
490
+ dir_match_list.append(f"{dir_path}/{element}")
491
+ dir_match_list += self.list_all_file(regular_exp, f"{dir_path}/{element}", recursive)
492
+ else:
493
+ ret = re.fullmatch(regular_exp, element)
494
+ if ret is not None:
495
+ dir_match_list.append(f"{dir_path}/{element}")
496
+ return dir_match_list
497
+
498
+ def py_sed(self, src_file, regular_exp, instead_text, def_line="l"):
499
+ self.log.info(f"要被替换文件: {src_file} -- 匹配正则表达式为: {regular_exp}")
500
+ shutil.copy(src_file, f"{src_file}_bak")
501
+ with open(src_file, "r", encoding="utf-8") as fp_r:
502
+ lines = fp_r.readlines()
503
+ fp_w = os.fdopen(os.open(src_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
504
+ stat.S_IWUSR | stat.S_IRUSR), 'w')
505
+ line_num = 0
506
+ if def_line == "l":
507
+ for line in lines:
508
+ ret = re.search(regular_exp, line)
509
+ if ret is None:
510
+ fp_w.write(line)
511
+ line_num = line_num + 1
512
+ else:
513
+ self.log.info(f"字符串: {ret.group(0)}")
514
+ line = line.replace(ret.group(0), instead_text)
515
+ fp_w.write(line)
516
+ line_num = line_num + 1
517
+ break
518
+ for i in range(line_num, len(lines)):
519
+ fp_w.write(lines[i])
520
+ elif def_line == "g":
521
+ for line in lines:
522
+ ret = re.search(regular_exp, line)
523
+ if ret is not None:
524
+ line = line.replace(ret.group(0), instead_text)
525
+ fp_w.write(line)
526
+ fp_w.close()
527
+ os.remove(f"{src_file}_bak")
528
+
529
+ def create_image_and_mount_datafs(self, img_path, mnt_datafs):
530
+ """
531
+ 文件格式化为ext4分区,随后挂载到self.mnt_datafs目录
532
+ """
533
+ self.log.info(f"挂载 datafs, 目录: {mnt_datafs}, 镜像文件: {img_path}")
534
+ os.makedirs(mnt_datafs, exist_ok=True)
535
+ self.run_command(f"umount {mnt_datafs}", ignore_error=True, sudo=True)
536
+ self.run_command(f"/sbin/mkfs.ext4 -O ^64bit,^metadata_csum {img_path}")
537
+ # 直接挂载,挂载成功则直接退出
538
+ ret = self.run_command(f"mount -t ext4 {img_path} {mnt_datafs}", ignore_error=True, sudo=True)
539
+ if ret and ret.returncode == 0:
540
+ return
541
+ # 挂载设备进行16次尝试
542
+ for attempt_times in range(0, 64):
543
+ loop_id = attempt_times % 16
544
+ self.run_command(f"mknod -m 0660 /dev/loop{loop_id} b 7 {loop_id}", ignore_error=True, sudo=True)
545
+ ret = self.run_command(f"mount -o loop=/dev/loop{loop_id} -t ext4 {img_path} {mnt_datafs}",
546
+ ignore_error=True, sudo=True)
547
+ if ret is None or ret.returncode != 0:
548
+ # 创建设备,允许不成功,其他进程可能创建过相同的设备
549
+ self.log.info(f"挂载 {img_path} 失败, 尝试第 {loop_id} 次, 返回值为: {ret}")
550
+ else:
551
+ # 挂载成功退出
552
+ break
553
+ time.sleep(10)
554
+ else:
555
+ # 16次均失败,报错退出
556
+ self.run_command(f"umount {mnt_datafs}", ignore_error=True, sudo=True)
557
+ raise errors.BmcGoException(
558
+ f"挂载 {img_path} 失败, 请使用 df -hl 检查设备是否被占用"
559
+ )
560
+
561
+ def umount_datafs(self, mnt_datafs):
562
+ self.log.info(f"卸载 datafs, 目录: {mnt_datafs}")
563
+ self.run_command(f"umount {mnt_datafs}", ignore_error=True, sudo=True)
564
+
565
+ def get_studio_path(self):
566
+ ret = self.run_command("whereis bmc_studio", sudo=True, ignore_error=True,
567
+ command_echo=False, capture_output=True).stdout
568
+ if not ret:
569
+ return ""
570
+
571
+ ret = ret.replace(" ", "").replace("\n", "")
572
+ studio_split = ret.split(":")
573
+ if len(studio_split) <= 1:
574
+ return ""
575
+
576
+ return studio_split[1]
577
+
578
+ def run_command(self, command, ignore_error=False, sudo=False, **kwargs):
579
+ """
580
+ 如果ignore_error为False,命令返回码非0时则打印堆栈和日志并触发异常,中断构建
581
+ """
582
+ # 如果run_command在同一行多次调用则无法区分执行的命令,command_key用于在日志中指示当前正在执行的命令关键字
583
+ command_key = kwargs.get("command_key", None)
584
+ command_echo = kwargs.get("command_echo", True)
585
+ show_log = kwargs.get("show_log", False)
586
+ uptrace = kwargs.get("uptrace", 0) + 1
587
+ error_log = kwargs.get("error_log")
588
+ warn_log = kwargs.get("warn_log")
589
+ timeout = kwargs.get("timeout", 1200)
590
+ capture_output = kwargs.get("capture_output", False)
591
+ if command_key:
592
+ key = command_key + ":"
593
+ else:
594
+ key = ""
595
+ if command_echo and not self.log.is_debug:
596
+ self.log.info(f">> {key}{command}", uptrace=uptrace)
597
+ command = self.format_command(command, sudo)
598
+ ret = None
599
+ log_fd = os.fdopen(os.open(self.log_name, os.O_RDWR | os.O_CREAT | os.O_APPEND,
600
+ stat.S_IWUSR | stat.S_IRUSR), 'a+')
601
+ try:
602
+ check = False if ignore_error else True
603
+ if show_log:
604
+ ret = subprocess.run(command, check=check, timeout=timeout)
605
+ elif capture_output:
606
+ ret = subprocess.run(command, capture_output=capture_output, check=check, timeout=timeout, text=True)
607
+ if ret.stdout:
608
+ log_fd.write(ret.stdout)
609
+ if ret.stderr:
610
+ log_fd.write(ret.stderr)
611
+ else:
612
+ ret = subprocess.run(command, stdout=log_fd, stderr=log_fd, check=check, timeout=timeout)
613
+ except Exception as e:
614
+ if error_log:
615
+ self.log.error(error_log, uptrace=uptrace)
616
+ elif warn_log:
617
+ self.log.warning(warn_log, uptrace=uptrace)
618
+ else:
619
+ self.log.error(f"执行命令 {key}{command} 错误, 日志: {self.log_name}", uptrace=uptrace)
620
+ log_fd.flush()
621
+ log_fd.close()
622
+ raise e
623
+ log_fd.close()
624
+ return ret
625
+
626
+ def pipe_command(self, commands, out_file=None, **kwargs):
627
+ if not isinstance(commands, list):
628
+ raise BmcGoException("命令必须为列表")
629
+ # 创建一个空的文件
630
+ flag = "w+b"
631
+ if out_file:
632
+ fp = os.fdopen(os.open(out_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
633
+ stat.S_IWUSR | stat.S_IRUSR), flag)
634
+ fp.close()
635
+ stdin = None
636
+ command_echo = kwargs.get("command_echo", True)
637
+ uptrace = kwargs.get("uptrace", 0) + 1
638
+ ignore_error = kwargs.get('ignore_error', False)
639
+ capture_output = kwargs.get('capture_output', False)
640
+ if command_echo and not self.log.is_debug:
641
+ self.log.info(f">> " + ' | '.join(commands), uptrace=uptrace)
642
+ for command in commands:
643
+ stdout = TemporaryFile(flag)
644
+ cmd = self.format_command(command, False)
645
+ ret = subprocess.Popen(cmd, stdout=stdout, stdin=stdin)
646
+ ret.wait()
647
+ if ret.returncode != 0:
648
+ if ignore_error:
649
+ return None, ret.returncode
650
+ raise BmcGoException(f"运行命令 {command} 失败, 请分析执行的命令返回日志")
651
+ if stdin is not None:
652
+ stdin.close()
653
+ stdin = stdout
654
+ stdin.seek(0)
655
+ if stdin:
656
+ context = stdin.read()
657
+ if out_file:
658
+ with os.fdopen(os.open(out_file, os.O_WRONLY | os.O_CREAT,
659
+ stat.S_IWUSR | stat.S_IRUSR), flag) as fp:
660
+ fp.write(context)
661
+ stdin.close()
662
+ if capture_output:
663
+ return context.decode("utf-8"), 0
664
+ return None, ret.returncode
665
+
666
+ def sudo_passwd_check(self):
667
+ self.log.info("测试sudo是否正常执行")
668
+ try:
669
+ self.run_command("ls .", sudo=True)
670
+ except Exception as e:
671
+ self.log.error("sudo命令无法正常执行")
672
+ raise e
673
+ else:
674
+ self.log.info("sudo命令正常执行")
675
+
676
+ def clean_locks(self):
677
+ self.log.info("尝试清理conan组件锁")
678
+ if not os.path.isdir(self.conan_data):
679
+ return
680
+ cmd = [f"find {self.conan_data} -maxdepth 4 -mindepth 4 -type f -name *.count*", "xargs rm -f"]
681
+ _, _ = self.pipe_command(cmd, out_file=None)
682
+
683
+ def get_profile_config(self, profile_name) -> (Profile, OrderedDict):
684
+ """根据profile文件名获取~/.conan/profiles对应文件的配置内容"""
685
+ if not profile_name:
686
+ return None, None
687
+ profile_file = os.path.join(self.conan_profiles_dir, profile_name)
688
+ if not os.path.isfile(profile_file):
689
+ raise BmcGoException(f"{profile_file} 文件不存在")
690
+
691
+ profile, profile_vars = read_profile(profile_name, os.getcwd(), self.conan_profiles_dir)
692
+ return profile, profile_vars
693
+
694
+ def _save_tempfile_safety(self, temp_fd, target_file, show_log=False):
695
+ lock_fd = open(self.lock_file, "r")
696
+ temp_fd.seek(0)
697
+ log_conent = temp_fd.read()
698
+ if show_log:
699
+ self.log.info(log_conent, uptrace=1)
700
+ fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX)
701
+ with os.fdopen(os.open(target_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
702
+ stat.S_IWUSR | stat.S_IRUSR), 'w+') as log_fd:
703
+ log_fd.write(log_conent)
704
+ fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)