openubmc-bingo 0.5.277__py3-none-any.whl → 0.6.0__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 (52) hide show
  1. bmcgo/__init__.py +1 -1
  2. bmcgo/bmcgo_config.py +22 -10
  3. bmcgo/cli/cli.py +86 -39
  4. bmcgo/cli/config.conan2.yaml +9 -0
  5. bmcgo/codegen/lua/codegen.py +1 -1
  6. bmcgo/codegen/lua/script/gen_intf_rpc_json.py +15 -14
  7. bmcgo/component/analysis/analysis.py +8 -8
  8. bmcgo/component/analysis/intf_validation.py +23 -12
  9. bmcgo/component/build.py +76 -14
  10. bmcgo/component/component_dt_version_parse.py +3 -2
  11. bmcgo/component/component_helper.py +43 -15
  12. bmcgo/component/coverage/incremental_cov.py +2 -2
  13. bmcgo/component/deploy.py +68 -14
  14. bmcgo/component/gen.py +1 -1
  15. bmcgo/component/package_info.py +128 -38
  16. bmcgo/component/template_v2/conanbase.py.mako +352 -0
  17. bmcgo/component/template_v2/conanfile.deploy.py.mako +26 -0
  18. bmcgo/component/test.py +53 -20
  19. bmcgo/frame.py +7 -3
  20. bmcgo/functional/analysis.py +3 -2
  21. bmcgo/functional/check.py +10 -6
  22. bmcgo/functional/conan_index_build.py +79 -20
  23. bmcgo/functional/csr_build.py +1 -1
  24. bmcgo/functional/diff.py +1 -1
  25. bmcgo/functional/fetch.py +1 -1
  26. bmcgo/functional/full_component.py +32 -24
  27. bmcgo/functional/git_history.py +220 -0
  28. bmcgo/functional/maintain.py +55 -12
  29. bmcgo/functional/new.py +1 -1
  30. bmcgo/functional/schema_valid.py +2 -2
  31. bmcgo/logger.py +2 -3
  32. bmcgo/misc.py +130 -9
  33. bmcgo/tasks/conan/__init__.py +10 -0
  34. bmcgo/tasks/conan/conanfile.py +45 -0
  35. bmcgo/tasks/task.py +27 -4
  36. bmcgo/tasks/task_build_conan.py +399 -52
  37. bmcgo/tasks/task_buildgppbin.py +8 -2
  38. bmcgo/tasks/task_download_buildtools.py +76 -0
  39. bmcgo/tasks/task_download_dependency.py +1 -0
  40. bmcgo/tasks/task_hpm_envir_prepare.py +1 -1
  41. bmcgo/utils/build_conans.py +231 -0
  42. bmcgo/utils/component_post.py +6 -4
  43. bmcgo/utils/component_version_check.py +10 -5
  44. bmcgo/utils/config.py +76 -40
  45. bmcgo/utils/fetch_component_code.py +44 -25
  46. bmcgo/utils/tools.py +239 -117
  47. bmcgo/worker.py +2 -2
  48. {openubmc_bingo-0.5.277.dist-info → openubmc_bingo-0.6.0.dist-info}/METADATA +4 -2
  49. {openubmc_bingo-0.5.277.dist-info → openubmc_bingo-0.6.0.dist-info}/RECORD +52 -45
  50. {openubmc_bingo-0.5.277.dist-info → openubmc_bingo-0.6.0.dist-info}/WHEEL +0 -0
  51. {openubmc_bingo-0.5.277.dist-info → openubmc_bingo-0.6.0.dist-info}/entry_points.txt +0 -0
  52. {openubmc_bingo-0.5.277.dist-info → openubmc_bingo-0.6.0.dist-info}/top_level.txt +0 -0
@@ -11,11 +11,9 @@
11
11
  # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
12
12
  # See the Mulan PSL v2 for more details.
13
13
  import os
14
- import pathlib
15
14
  import subprocess
16
- import shutil
17
- import stat
18
15
  import json
16
+ import re
19
17
  import yaml
20
18
 
21
19
  from bmcgo import errors
@@ -68,21 +66,31 @@ class BmcgoCommand:
68
66
  def __init__(self, bconfig: BmcgoConfig, *args):
69
67
  self.bconfig = bconfig
70
68
  parser = tool.create_common_parser("Conan Index Build")
69
+ parser.add_argument("--conan2", help="是否构建conan2.x版本的组件包", action=misc.STORE_TRUE)
71
70
  self.args, _ = parser.parse_known_args(*args)
71
+ self.conan2 = self.args.conan2
72
72
  self.path = ""
73
73
  self.version = ""
74
+ self.name = ""
75
+ self.user = ""
76
+ self.channel = ""
74
77
  self.conan_package = ""
75
78
  self.upload = False
76
79
  self.remote = None
77
80
  self.options = []
78
- self.stage = misc.StageEnum.STAGE_DEV.value
81
+ self.stage = self.args.stage
79
82
  self.enable_luajit = False
80
83
  self.from_source = False
81
84
  self.build_type = 'debug'
82
85
  self.asan = False
83
86
  self.profile = ''
87
+ self.recipe_folder = self.bconfig.conan_index.folder
88
+ if misc.conan_v2():
89
+ recipes2_folder = os.path.join(self.bconfig.conan_index.folder, "..", "recipes2")
90
+ recipes2_folder = os.path.realpath(recipes2_folder)
91
+ if os.path.isdir(recipes2_folder):
92
+ self.recipe_folder = recipes2_folder
84
93
  self.initialize()
85
- self.channel = f"@{misc.ConanUserEnum.CONAN_USER_DEV.value}/{self.stage}"
86
94
 
87
95
  @staticmethod
88
96
  def run_command(command, ignore_error=False, sudo=False, **kwargs):
@@ -109,13 +117,15 @@ class BmcgoCommand:
109
117
  os.environ["LUA_PATH"] = f"{conan_bin}/?.lua"
110
118
 
111
119
  def initialize(self):
120
+ if not self.args.conan_package:
121
+ msg = "构建参数错误,请指定有效的-cp参数,例:kmc/1.0.1 或 kmc/1.0.1@openubmc/stable"
122
+ raise errors.BmcGoException(msg)
112
123
  self.set_package(self.args.conan_package)
113
124
  self.upload = self.args.upload_package
114
125
  if self.args.remote:
115
126
  self.remote = self.args.remote
116
127
  if self.args.options:
117
128
  self.options = self.args.options
118
- self.stage = self.args.stage
119
129
  self.enable_luajit = self.args.enable_luajit
120
130
  self.from_source = self.args.from_source
121
131
  self.build_type = self.args.build_type
@@ -126,15 +136,25 @@ class BmcgoCommand:
126
136
 
127
137
  # 入参可以是huawei_secure_c/1.0.0样式
128
138
  def set_package(self, path: str):
129
- os.chdir(self.bconfig.conan_index.folder)
130
- split = path.split("/")
131
- if len(split) != 2:
132
- raise errors.BmcGoException(f"包名称({path})错误,例:kmc/1.0.1")
139
+ os.chdir(self.recipe_folder)
140
+ split = re.split('/|@', path)
141
+ if len(split) != 2 and len(split) != 4:
142
+ raise errors.BmcGoException(f"包名称({path})错误,例:kmc/1.0.1 或 kmc/1.0.1@openubmc/stable")
143
+ self.name = split[0].lower()
144
+ if len(split) == 2:
145
+ if self.stage == "dev":
146
+ self.user = misc.conan_user_dev()
147
+ else:
148
+ self.user = misc.conan_user()
149
+ self.channel = self.stage
150
+ else:
151
+ self.user = split[2]
152
+ self.channel = split[3]
133
153
 
134
154
  if not os.path.isdir(split[0]):
135
155
  raise errors.BmcGoException(f"包路径({split[0]})不存在,或不是文件夹")
136
156
 
137
- config_yaml = os.path.join(self.bconfig.conan_index.folder, split[0], "config.yml")
157
+ config_yaml = os.path.join(self.recipe_folder, split[0], "config.yml")
138
158
  with open(config_yaml) as f:
139
159
  config_data = yaml.safe_load(f)
140
160
  config_data = config_data.get('versions', None)
@@ -148,18 +168,47 @@ class BmcgoCommand:
148
168
  raise errors.BmcGoException(f"Unkown folder, config.yml path: {config_yaml}, version: {split[1]}")
149
169
  self.path = "{}/{}".format(split[0], folder)
150
170
  self.version = split[1]
171
+ if misc.conan_v2():
172
+ self.version = self.version.lower()
151
173
  if self.stage != "dev":
152
174
  self.tag_check()
153
175
 
154
- def run(self):
176
+ def run_v2(self):
177
+ log.info("Start build package")
178
+ if self.build_type == "debug":
179
+ setting = "-s build_type=Debug"
180
+ else:
181
+ setting = "-s build_type=Release"
182
+ options = " "
183
+ if self.asan:
184
+ options += f"-o {self.name}/*:asan=True"
185
+ for option in self.options:
186
+ options += f" -o {option}"
187
+
188
+ dt_stat = os.environ.get("BINGO_DT_RUN", "off")
189
+ show_log = True if dt_stat == "off" else False
190
+ pkg = self.name + "/" + self.version + "@" + self.user + "/" + self.channel
191
+ append_cmd = f"-r {self.remote}" if self.remote else ""
192
+ cmd = "conan create . --name={} --version={} -pr={} {} {} {}".format(
193
+ self.name, self.version, self.profile, setting, append_cmd, options
194
+ )
195
+
196
+ cmd += " --user={} --channel={}".format(self.user, self.channel)
197
+ if self.from_source:
198
+ cmd += " --build=*"
199
+ else:
200
+ cmd += f" --build={self.name}/* --build=missing"
201
+ self.run_command(cmd, show_log=show_log)
202
+
203
+ if not self.upload:
204
+ return
205
+
206
+ cmd = "conan upload {} -r {}".format(pkg, self.remote)
207
+ self.run_command(cmd)
208
+ log.info("===>>>Upload package successfully, pkg: {}".format(pkg))
209
+
210
+ def run_v1(self):
155
211
  self.check_luac()
156
- if self.path == "" or self.version == "":
157
- raise errors.BmcGoException(f"Path({self.path}) or version({self.version}) error")
158
- os.chdir(self.bconfig.conan_index.folder)
159
- os.chdir(self.path)
160
- if self.stage != misc.StageEnum.STAGE_DEV.value:
161
- # rc和stable模式
162
- self.channel = f"@{tool.conan_user}/{self.stage}"
163
212
  log.info("Start build package")
164
213
  if self.build_type == "dt":
165
214
  setting = "-s build_type=Dt"
@@ -177,7 +226,7 @@ class BmcgoCommand:
177
226
 
178
227
  dt_stat = os.environ.get("BMCGO_DT_RUN", "off")
179
228
  show_log = True if dt_stat == "off" else False
180
- pkg = packake_name + "/" + self.version + self.channel
229
+ pkg = self.name + "/" + self.version + "@" + self.user + "/" + self.channel
181
230
  append_cmd = f"-r {self.remote}" if self.remote else ""
182
231
  cmd = "conan create . {} -pr={} -pr:b profile.dt.ini {} {} -tf None {}".format(
183
232
  pkg, self.profile, setting, append_cmd, options
@@ -206,6 +255,16 @@ class BmcgoCommand:
206
255
  self.run_command(cmd)
207
256
  log.info("===>>>Upload package successfully, pkg: {}:{}".format(pkg, i["id"]))
208
257
 
258
+ def run(self):
259
+ if self.path == "" or self.version == "":
260
+ raise errors.BmcGoException(f"Path({self.path}) or version({self.version}) error")
261
+
262
+ os.chdir(self.path)
263
+ if misc.conan_v1():
264
+ self.run_v1()
265
+ else:
266
+ self.run_v2()
267
+
209
268
  def tag_check(self):
210
269
  yaml_file = f"{self.path}/conandata.yml"
211
270
  if os.path.exists(yaml_file) is False:
@@ -46,7 +46,7 @@ cwd = os.getcwd()
46
46
 
47
47
  SR_UPGRADE = "SRUpgrade"
48
48
  TIANCHI = "bmc.dev.Board.TianChi"
49
- HPM_PACK_PATH = "/usr/share/bmcgo/csr_packet"
49
+ HPM_PACK_PATH = "/usr/share/bingo/csr_packet"
50
50
  EEPROM_SIZE_LIMIT_CONFIG = "/usr/share/bmcgo/schema/eepromSizeLimit.json"
51
51
 
52
52
  JSON_DATA_FORMAT = 0x01
bmcgo/functional/diff.py CHANGED
@@ -105,7 +105,7 @@ class BmcgoCommand:
105
105
  tempfile = NamedTemporaryFile()
106
106
  # 过滤只包含scm的信息, 并将其生成为字典对象
107
107
  ret = tools.run_command(f"conan info {version} --json {tempfile.name} \
108
- -r {misc.CONAN_REPO}", ignore_error=True, command_echo=False, capture_output=True)
108
+ -r {misc.conan_remote()}", ignore_error=True, command_echo=False, capture_output=True)
109
109
  file_handler = open(tempfile.name, "r")
110
110
  conan_comps = json.load(file_handler)
111
111
  version_msg = ""
bmcgo/functional/fetch.py CHANGED
@@ -163,7 +163,7 @@ framework、bmc_core、security、hardware、ras、energy、om、interface、pro
163
163
  stage = self.stage
164
164
  if stage != misc.StageEnum.STAGE_STABLE.value:
165
165
  stage = misc.StageEnum.STAGE_RC.value
166
- user_channel = f"@{tools.conan_user}/{stage}"
166
+ user_channel = f"@{misc.conan_user()}/{stage}"
167
167
  com_package[index] += user_channel
168
168
 
169
169
  def __load_config_json(self, stage):
@@ -23,8 +23,6 @@ import functools
23
23
  import random
24
24
  from concurrent.futures import ProcessPoolExecutor, Future
25
25
  from typing import List
26
-
27
- from conans.model.manifest import FileTreeManifest
28
26
  import yaml
29
27
 
30
28
  from bmcgo import misc
@@ -71,7 +69,7 @@ class BuildComponent:
71
69
  build_type: str,
72
70
  options: dict,
73
71
  code_path: str,
74
- remote=misc.CONAN_REPO,
72
+ remote=misc.conan_remote(),
75
73
  service_json="mds/service.json",
76
74
  upload=False,
77
75
  ):
@@ -90,12 +88,15 @@ class BuildComponent:
90
88
  def run(self):
91
89
  os.chdir(os.path.join(self.code_path, self.comp_name))
92
90
  for key, value in self.options.items():
93
- self.option_cmd = self.option_cmd + f" -o {self.comp_name}:{key}={value}"
91
+ if misc.conan_v2():
92
+ self.option_cmd = self.option_cmd + f" -o {self.comp_name}/*:{key}={value}"
93
+ else:
94
+ self.option_cmd = self.option_cmd + f" -o {self.comp_name}:{key}={value}"
94
95
  command = (
95
96
  f"--remote {self.remote} -nc --stage {self.stage} --build_type {self.build_type.lower()}"
96
97
  f" --profile {self.profile} {self.option_cmd}"
97
98
  )
98
- log.info(f"执行构建命令bingo build {command}")
99
+ log.info(f"执行构建命令{misc.tool_name()} build {command}")
99
100
  args = command.split()
100
101
  build = BuildComp(self.bconfig, args, service_json=self.service_json)
101
102
  # 恢复工作区为clean状态, 保证conan export时scm信息准确
@@ -112,7 +113,7 @@ class BmcgoCommand:
112
113
  def __init__(self, bconfig: BmcgoConfig, *args):
113
114
  parser = argparse.ArgumentParser(description="Fetch component source code and build all binaries.")
114
115
  parser.add_argument("-comp", "--component", help="软件包名, 示例: oms/1.2.6", required=True)
115
- parser.add_argument("-r", "--remote", help="远端仓库名称", default=misc.CONAN_REPO)
116
+ parser.add_argument("-r", "--remote", help="远端仓库名称", default=misc.conan_remote())
116
117
  parser.add_argument(
117
118
  "-p",
118
119
  "--path",
@@ -136,9 +137,11 @@ class BmcgoCommand:
136
137
  self.upload = parsed_args.upload
137
138
  self.config_file = parsed_args.config_file
138
139
  self.skip_fetch = parsed_args.skip_fetch
139
- self.profile_list = ["profile.dt.ini", "profile.luajit.ini"]
140
- self.stage_list = [STAGE_STABLE]
141
- self.built_type_list = [BUILD_TYPE_DT, BUILD_TYPE_DEBUG, BUILD_TYPE_RELEASE]
140
+ self.profile_list = ["profile.dt.ini"]
141
+ if misc.conan_v1():
142
+ self.profile_list.append("profile.luajit.ini")
143
+ self.stage_list = [misc.StageEnum.STAGE_STABLE]
144
+ self.built_type_list = [misc.BuildTypeEnum.DEBUG, misc.BuildTypeEnum.RELEASE]
142
145
  if parsed_args.build_type:
143
146
  self.built_type_list = parsed_args.build_type
144
147
  self.options_dict = {}
@@ -185,7 +188,7 @@ class BmcgoCommand:
185
188
 
186
189
  # 添加module_symver选项
187
190
  module_symvers_path = os.path.join(SDK_PATH, MODULE_SYMVERS)
188
- module_symver_key, module_symver_value = tool.get_module_symver_option(module_symvers_path)
191
+ module_symver_key, module_symver_value = tool.get_171x_module_symver_option(module_symvers_path)
189
192
  if module_symver_key in options_dict:
190
193
  options_dict[module_symver_key] = [module_symver_value]
191
194
  if config_file:
@@ -237,24 +240,28 @@ class BmcgoCommand:
237
240
  @staticmethod
238
241
  def _replace_dev_version(temp_service_json: str):
239
242
  version_parse = ComponentDtVersionParse(serv_file=temp_service_json)
240
- for pkg in version_parse.conan_list:
241
- component = pkg[misc.CONAN]
242
- if "@" not in component:
243
- continue
244
- comp_version, user_channel = component.split("@")
245
- if user_channel.endswith(STAGE_DEV):
246
- pkg[misc.CONAN] = f"{comp_version}{ComponentHelper.get_user_channel(STAGE_DEV)}"
247
-
243
+ if misc.conan_v1():
244
+ for pkg in version_parse.conan_list:
245
+ component = pkg[misc.CONAN]
246
+ if "@" not in component:
247
+ continue
248
+ comp_version, user_channel = component.split("@")
249
+ if user_channel.endswith(STAGE_DEV):
250
+ pkg[misc.CONAN] = f"{comp_version}{ComponentHelper.get_user_channel(STAGE_DEV)}"
248
251
  version_parse.write_to_serv_file()
249
252
 
250
253
  @functools.cached_property
251
254
  def _full_reference(self) -> str:
252
- comp_pkg = self.comp if "@" in self.comp else f"{self.comp}@{CONAN_USER}.release/{STAGE_STABLE}"
255
+ if misc.conan_v1():
256
+ comp_pkg = self.comp if "@" in self.comp else f"{self.comp}@{CONAN_USER}.release/{STAGE_STABLE}"
257
+ else:
258
+ comp_pkg = self.comp if "@" in self.comp else f"{self.comp}@{misc.conan_user()}/{STAGE_STABLE}"
253
259
  return comp_pkg
254
260
 
255
261
  @functools.cached_property
256
262
  def _is_self_developed(self) -> bool:
257
- tool.run_command(f"conan download {self._full_reference} --recipe -r {self.remote}")
263
+ args = "--only-recipe" if misc.conan_v2() else "--recipe"
264
+ tool.run_command(f"conan download {self._full_reference} {args} -r {self.remote}")
258
265
  comp_package_path = os.path.join(tool.conan_data, self.comp_name)
259
266
  ret = tool.run_command(f"find {comp_package_path} -name 'conanbase.py'", capture_output=True)
260
267
  return bool(ret.stdout)
@@ -348,10 +355,11 @@ class BmcgoCommand:
348
355
  random.shuffle(all_build_params)
349
356
  for build_args in all_build_params:
350
357
  profile, stage, build_type, *option_values = build_args
351
- if profile == "profile.dt.ini" and build_type.lower() != BUILD_TYPE_DT.lower():
352
- continue
353
- if profile != "profile.dt.ini" and build_type.lower() == BUILD_TYPE_DT.lower():
354
- continue
358
+ if misc.conan_v1():
359
+ if profile == "profile.dt.ini" and build_type.lower() != BUILD_TYPE_DT.lower():
360
+ continue
361
+ if profile != "profile.dt.ini" and build_type.lower() == BUILD_TYPE_DT.lower():
362
+ continue
355
363
  build_options = dict(zip(options_dict.keys(), option_values))
356
364
  task = BuildComponent(
357
365
  self.comp,
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # Copyright (c) 2025 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
+ import argparse
13
+ import datetime
14
+ import json
15
+ import os
16
+ import re
17
+ import sys
18
+ from datetime import timezone, timedelta
19
+ from typing import List, Tuple, Dict, Optional, Any
20
+ from git import Repo, Commit, Diff
21
+ from git.exc import InvalidGitRepositoryError, GitCommandError
22
+ from bmcgo import misc
23
+ from bmcgo.utils.tools import Tools
24
+ from bmcgo.bmcgo_config import BmcgoConfig
25
+ from bmcgo import errors
26
+
27
+
28
+ tools = Tools()
29
+ log = tools.log
30
+ beijing_timezone = timezone(timedelta(hours=8))
31
+
32
+
33
+ command_info: misc.CommandInfo = misc.CommandInfo(
34
+ group=misc.GRP_COMP,
35
+ name="git_history",
36
+ description=["获取指定开始时间与结束时间之间提交信息,并生成release note"],
37
+ hidden=False
38
+ )
39
+
40
+
41
+ def if_available(bconfig: BmcgoConfig):
42
+ if bconfig.component is None:
43
+ return False
44
+ return True
45
+
46
+
47
+ class BmcgoCommand:
48
+ def __init__(self, bconfig: BmcgoConfig, *args):
49
+ self.bconfig = bconfig
50
+ parser = argparse.ArgumentParser(prog="bmcgo git_history", description="生成Git仓库的Release Note", add_help=True,
51
+ formatter_class=argparse.RawTextHelpFormatter)
52
+ parser.add_argument("--start_date", "-s", help="起始日期YYYY-MM-DD", required=True)
53
+ parser.add_argument("--end_date", "-e", help="结束日期YYYY-MM-DD,默认为当前日期")
54
+ args, kwargs = parser.parse_known_args(*args)
55
+ self.start_date = args.start_date
56
+ self.end_date = args.end_date
57
+ self.output_file = f"./{self.start_date}_{self.end_date}_releaseNote.log"
58
+ self.service_json_path = "mds/service.json"
59
+ self.repo = Repo(".")
60
+
61
+ @staticmethod
62
+ def validate_date(date_str: str) -> datetime.datetime:
63
+ try:
64
+ date = datetime.datetime.strptime(date_str, "%Y-%m-%d")
65
+ except ValueError as e:
66
+ raise errors.BmcGoException(f"日期格式不正确: {date_str},请使用YYYY-MM-DD格式,错误信息{e}")
67
+ current_date = datetime.datetime.now(beijing_timezone)
68
+ if date.date() > current_date.date():
69
+ raise errors.BmcGoException(f"日期 {date_str} 超过当前日期")
70
+ return date
71
+
72
+ @staticmethod
73
+ def extract_version_from_diff(self, diff: Diff) -> Optional[str]:
74
+ try:
75
+ diff_text = diff.diff.decode('utf-8') if diff.diff else str(diff)
76
+ version_pattern = r'[-+]\s*"version"\s*:\s*"([^"]+)"'
77
+ matches = re.findall(version_pattern, diff_text)
78
+ if matches:
79
+ return matches[-1]
80
+ except (UnicodeDecodeError, AttributeError):
81
+ pass
82
+ return None
83
+
84
+ @staticmethod
85
+ def is_merge_into_main(s):
86
+ pattern = r'^merge .+ into main$'
87
+ # 使用re.match进行匹配,忽略大小写
88
+ match = re.match(pattern, s, re.IGNORECASE)
89
+ return match is not None
90
+
91
+ def get_current_version(self) -> str:
92
+ try:
93
+ with open(self.service_json_path, 'r') as f:
94
+ service_data = json.load(f)
95
+ return service_data.get('version', 'unknown')
96
+ except (FileNotFoundError, json.JSONDecodeError) as e:
97
+ raise errors.BmcGoException(f"无法读取或解析 {self.service_json_path}: {e}")
98
+
99
+ def get_commits_in_range(self, start_date: str, end_date: str) -> List[Commit]:
100
+ start_dt = self.validate_date(start_date)
101
+ if end_date is None:
102
+ end_dt = datetime.datetime.now(beijing_timezone)
103
+ else:
104
+ try:
105
+ end_dt = self.validate_date(end_date)
106
+ except errors.BmcGoException as e:
107
+ if "超过当前日期" in str(e):
108
+ log.warning(f"警告: {e},将使用当前日期作为结束日期")
109
+ end_dt = datetime.datetime.now(beijing_timezone).replace(tzinfo=None)
110
+ else:
111
+ raise
112
+ if end_dt < start_dt:
113
+ raise errors.BmcGoException("结束日期不能早于起始日期")
114
+ since_str = start_dt.strftime("%Y-%m-%d")
115
+ until_str = end_dt.strftime("%Y-%m-%d")
116
+ try:
117
+ commits = list(self.repo.iter_commits(
118
+ since=since_str,
119
+ until=until_str
120
+ ))
121
+ except GitCommandError as e:
122
+ raise errors.BmcGoException(f"获取commit记录时出错: {e}")
123
+ return commits
124
+
125
+ def get_version_from_commit(self, commit_hash: str) -> Optional[str]:
126
+ try:
127
+ commit = self.repo.commit(commit_hash)
128
+ try:
129
+ file_content = (commit.tree / self.service_json_path).data_stream.read().decode('utf-8')
130
+ data = json.loads(file_content)
131
+ version = data.get('version')
132
+ return version
133
+ except (KeyError, AttributeError):
134
+ log.error(f"在commit {commit_hash} 中找不到文件: {self.service_json_path}")
135
+ return None
136
+ except json.JSONDecodeError:
137
+ log.error(f"在commit {commit_hash} 中的 {self.service_json_path} 文件不是有效的JSON格式")
138
+ return None
139
+ except Exception as e:
140
+ log.error(f"获取commit {commit_hash} 的版本信息时出错: {e}")
141
+ return None
142
+
143
+ def get_version_info_for_commits(self, commits: List[Commit]) -> Dict[str, Any]:
144
+ version_info = {
145
+ 'current_version': self.get_current_version(),
146
+ 'commit_versions': {}
147
+ }
148
+ # 按时间顺序处理commit(从旧到新)
149
+ for commit in reversed(commits):
150
+ try:
151
+ if not self.is_merge_into_main(str(commit.summary)):
152
+ continue
153
+ modified_version = self.get_version_from_commit(commit.hexsha)
154
+ message = commit.message.strip()
155
+ lines = message.splitlines()
156
+ if len(lines) >= 3:
157
+ message = lines[2]
158
+ version_info['commit_versions'][commit.hexsha] = {
159
+ 'date': commit.committed_datetime.strftime("%Y-%m-%d"),
160
+ 'message': message,
161
+ 'version': modified_version if modified_version else version_info['current_version']
162
+ }
163
+ if modified_version:
164
+ version_info['current_version'] = modified_version
165
+ except Exception as e:
166
+ log.error(f"处理commit {commit.hexsha} 时出错: {e}")
167
+ message = commit.message.strip()
168
+ lines = message.splitlines()
169
+ if len(lines) >= 3:
170
+ message = lines[2]
171
+ version_info['commit_versions'][commit.hexsha] = {
172
+ 'date': commit.committed_datetime.strftime("%Y-%m-%d"),
173
+ 'message': message,
174
+ 'version': version_info['current_version']
175
+ }
176
+ return version_info
177
+
178
+ def generate_release_note(self,
179
+ start_date: str,
180
+ end_date: str,
181
+ output_file: str) -> str:
182
+ release_note = f"从{start_date}至{end_date}特性提交如下:\n"
183
+ commits = self.get_commits_in_range(start_date, end_date)
184
+ if not commits:
185
+ try:
186
+ release_note += f"无更新\n"
187
+ with open(output_file, 'w', encoding='utf-8') as f:
188
+ f.write(release_note)
189
+ log.info(f"Release Note已保存到: {output_file}")
190
+ except IOError as e:
191
+ log.error(f"保存文件时出错: {e}")
192
+ version_info = self.get_version_info_for_commits(commits)
193
+
194
+ for commit in commits:
195
+ commit_info = version_info['commit_versions'].get(commit.hexsha, {})
196
+ if len(commit_info) == 0:
197
+ continue
198
+ release_note += f"-commit ID: {commit.hexsha}\n"
199
+ release_note += f"-修改描述: {commit_info.get('message')}\n"
200
+ release_note += f"-版本号: {commit_info.get('version')}\n"
201
+ release_note += f"-发布日期:{commit_info.get('date')}\n\n"
202
+ if output_file:
203
+ try:
204
+ with open(output_file, 'w', encoding='utf-8') as f:
205
+ f.write(release_note)
206
+ log.info(release_note)
207
+ log.info(f"Release Note已保存到: {output_file}")
208
+ except IOError as e:
209
+ log.error(f"保存文件时出错: {e}")
210
+ return release_note
211
+
212
+ def run(self):
213
+ try:
214
+ _ = self.generate_release_note(
215
+ start_date=self.start_date,
216
+ end_date=self.end_date,
217
+ output_file=self.output_file
218
+ )
219
+ except (ValueError, RuntimeError) as e:
220
+ log.error(f"错误: {e}")
@@ -26,6 +26,7 @@ from bmcgo.component.build import BuildComp
26
26
  from bmcgo import errors
27
27
  from bmcgo.bmcgo_config import BmcgoConfig
28
28
  from bmcgo import misc
29
+ from bmcgo import errors
29
30
 
30
31
  tool = Tools("maintain")
31
32
  log = tool.log
@@ -89,22 +90,33 @@ class BmcgoCommand:
89
90
  def __init__(self, bconfig: BmcgoConfig, *args):
90
91
  self.patch_file = []
91
92
  self.bconfig = bconfig
92
- parser = argparse.ArgumentParser(prog="bmcgo maintain", description="BMC组件的维护管理工具", add_help=True,
93
- formatter_class=argparse.RawTextHelpFormatter)
94
- parser.add_argument("-cp", "--conan_package", help="指定需要维护的组件版本,示例:component/1.2.3", required=True)
93
+ parser = argparse.ArgumentParser(prog=f"{misc.tool_name()} maintain", description="BMC组件的维护管理工具",
94
+ add_help=True, formatter_class=argparse.RawTextHelpFormatter)
95
+ if misc.conan_v1():
96
+ parser.add_argument("-cp", "--conan_package", help="指定需要维护的组件版本,示例:component/1.2.3", required=True)
97
+ else:
98
+ parser.add_argument("-cp", "--conan_package",
99
+ help="指定需要维护的组件版本,示例:component/1.2.3@{misc.conan_user()}/stable", required=True)
95
100
  parser.add_argument("-p", "--patch_file", help="添加patch文件,可指定多个,顺序追加到代码中", action='append')
96
101
  parser.add_argument("-b", "--branch", help="用于提取patch的分支,与commit_id配合使用", default="master")
97
102
  parser.add_argument("-c", "--commit_id", help="需要生成patch的提交节点,长度不低于8个字符,可指定多个,顺序追加到代码中",
98
103
  action='append')
99
104
  parser.add_argument("-v", "--version", help="指定需要生成的组件版本号", default=None)
100
- parser.add_argument("-r", "--remote", default=misc.CONAN_REPO,
105
+ parser.add_argument("-r", "--remote", default=misc.conan_remote(),
101
106
  help="conan远程仓,请检查conan remote list查看已配置的conan仓")
102
107
  parsed_args = parser.parse_args(*args)
103
108
  cp = parsed_args.conan_package
104
- if (not re.match("^[a-zA-Z0-9_-]+/([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+$", cp) and
105
- not re.match("^[a-zA-Z0-9_-]+/(([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+)(-build\.[0-9]+)$", cp) and
106
- not re.match("^[a-zA-Z0-9_-]+/(([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+)(.p[0-9]+)$" + cp)):
107
- raise errors.BmcGoException("-cp参数错误,不满足格式\"<name>/<version>\"要求")
109
+ if misc.conan_v1():
110
+ if (not re.match("^[a-zA-Z0-9_-]+/([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+$", cp) and
111
+ not re.match("^[a-zA-Z0-9_-]+/(([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+)(-build\.[0-9]+)$", cp) and
112
+ not re.match("^[a-zA-Z0-9_-]+/(([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+)(.p[0-9]+)$" + cp)):
113
+ raise errors.BmcGoException("-cp参数错误,不满足格式\"<name>/<version>\"要求")
114
+ else:
115
+ restr = misc.CONAN_NAME_RESTR
116
+ if (not re.match(f"^{restr}/([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+@{restr}/{restr}$", cp) and
117
+ not re.match(f"^{restr}/(([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+)(-build\.[0-9]+)@{restr}/{restr}$", cp) and
118
+ not re.match(f"^{restr}/(([1-9][0-9]*|[0-9])\.[0-9]+\.[0-9]+)(.p[0-9]+)@{restr}/{restr}$" + cp)):
119
+ raise errors.BmcGoException("-cp参数错误,不满足格式\"<name>/<version>@user/channel\"要求")
108
120
  self.conan_package = cp
109
121
  self.name = self.conan_package.split("/")[0]
110
122
  self.base_version = self.conan_package.split("/")[1]
@@ -127,7 +139,7 @@ class BmcgoCommand:
127
139
  if re.match(r"^[a-f0-9]{8,40}$", commit) is None:
128
140
  raise errors.ConfigException(f"参数错误, --commit_id({commit}) 格式错误")
129
141
  self.patch_description = ""
130
- self.dir = tempfile.TemporaryDirectory(prefix="bmcgo_maint")
142
+ self.dir = tempfile.TemporaryDirectory(prefix="bingo_maint")
131
143
  for file in self.patch_file:
132
144
  if not os.path.isfile(file):
133
145
  raise errors.BmcGoException(f"补丁文件 {file} 不存在")
@@ -182,11 +194,36 @@ class BmcgoCommand:
182
194
  log.success(f"生成维护版本 {self.name}/{self.next_version} 成功")
183
195
  return 0
184
196
 
185
- def _fetch_conan_info(self):
186
- user = tool.conan_user
197
+ def _fetch_conan_info_v2(self):
198
+ args = "--only-recipe" if misc.conan_v2() else "--recipe"
199
+ cmd = f"conan download {self.conan_package} -r {self.remote} {args}"
200
+ tool.run_command(cmd, error_log=f"组件 {self.conan_package} 未找到,请检查网络连接以及该版本是否已发布")
201
+ cmd = f"conan cache path {self.conan_package}"
202
+ export_path = tool.run_command(cmd, capture_output=True, shell=True).stdout.strip()
203
+ conanfile = os.path.join(export_path, "conanfile.py")
204
+ conandata = os.path.join(export_path, "conandata.yml")
205
+ if not os.path.isfile(conandata):
206
+ raise errors.BmcGoException(f"未找到组件 {self.conan_package} 的conandata.yml文件")
207
+ log.info(f"从 {conandata} 读取源码tag的url")
208
+ # 尝试从conandata中获取代码仓地址
209
+ with open(conandata, "r") as fp:
210
+ data = yaml.safe_load(fp)
211
+ cfg = data.get("sources", {}).get(self.base_version)
212
+ if cfg is None:
213
+ raise RuntimeError(f"${conandata}不存在组件 {self.conan_package} 配置, 请检查是否正确")
214
+ self.url = cfg.get("url")
215
+ self.tag = cfg.get("branch")
216
+ if re.match("^refs/tags/[0-9]+\\.[0-9]+\\.[0-9]+$", self.tag):
217
+ self.tag = self.tag[len("refs/tags/"):]
218
+ elif not self.tag:
219
+ self.tag = self.base_version
220
+
221
+ def _fetch_conan_info_v1(self):
222
+ user = misc.conan_user()
223
+ args = "--only-recipe" if misc.conan_v2() else "--recipe"
187
224
  export_path = os.path.join(tool.conan_data, self.conan_package, f"{user}/stable", "export")
188
225
  if not os.path.isdir(export_path):
189
- cmd = f"conan download {self.conan_package}@{user}/stable -r {self.remote} -re"
226
+ cmd = f"conan download {self.conan_package}@{user}/stable -r {self.remote} {args}"
190
227
  tool.run_command(cmd, error_log=f"组件 {self.conan_package} 未找到,请检查网络连接以及该版本是否已发布")
191
228
  conanfile = os.path.join(export_path, "conanfile.py")
192
229
  conandata = os.path.join(export_path, "conandata.yml")
@@ -216,6 +253,12 @@ class BmcgoCommand:
216
253
  else:
217
254
  raise RuntimeError("无法找到版本(revision)和地址(url)字段")
218
255
 
256
+ def _fetch_conan_info(self):
257
+ if misc.conan_v1():
258
+ self._fetch_conan_info_v1()
259
+ else:
260
+ self._fetch_conan_info_v2()
261
+
219
262
  def _update_next_version(self):
220
263
  self.next_version = self.arg_special_version
221
264
  if not self.next_version: