lbkit 0.9.3__tar.gz → 0.9.4__tar.gz

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 (70) hide show
  1. {lbkit-0.9.3 → lbkit-0.9.4}/AUTHORS +1 -1
  2. {lbkit-0.9.3/lbkit.egg-info → lbkit-0.9.4}/PKG-INFO +1 -1
  3. lbkit-0.9.4/lbkit/__init__.py +2 -0
  4. lbkit-0.9.4/lbkit/build_conan_parallel.py +61 -0
  5. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/component/build.py +18 -15
  6. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/component/test.py +3 -3
  7. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/log.py +29 -19
  8. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/config.py +0 -2
  9. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/task_build_manifest.py +27 -40
  10. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/task_build_prepare.py +4 -2
  11. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tools.py +54 -28
  12. {lbkit-0.9.3 → lbkit-0.9.4/lbkit.egg-info}/PKG-INFO +1 -1
  13. lbkit-0.9.3/lbkit/__init__.py +0 -2
  14. lbkit-0.9.3/lbkit/build_conan_parallel.py +0 -206
  15. {lbkit-0.9.3 → lbkit-0.9.4}/LICENSE +0 -0
  16. {lbkit-0.9.3 → lbkit-0.9.4}/MANIFEST.in +0 -0
  17. {lbkit-0.9.3 → lbkit-0.9.4}/README.md +0 -0
  18. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/ci_robot/__init__.py +0 -0
  19. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/ci_robot/gitee.py +0 -0
  20. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/cli.py +0 -0
  21. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/__init__.py +0 -0
  22. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/codegen.py +0 -0
  23. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/ctype_defination.py +0 -0
  24. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/idf_interface.py +0 -0
  25. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/renderer.py +0 -0
  26. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/client.c.mako +0 -0
  27. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/client.h.mako +0 -0
  28. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/interface.c.mako +0 -0
  29. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/interface.introspect.xml.mako +0 -0
  30. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/public.c.mako +0 -0
  31. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/public.h.mako +0 -0
  32. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/server.c.mako +0 -0
  33. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/codegen/template/server.h.mako +0 -0
  34. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/component/__init__.py +0 -0
  35. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/component/arg_parser.py +0 -0
  36. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/component/template/conanbase.mako +0 -0
  37. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/component/template/deploy.mako +0 -0
  38. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/errors.py +0 -0
  39. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/helper.py +0 -0
  40. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/lbkit.py +0 -0
  41. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/misc.py +0 -0
  42. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/__init__.py +0 -0
  43. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/executor.py +0 -0
  44. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/image_maker/__init__.py +0 -0
  45. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/image_maker/make_image.py +0 -0
  46. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/image_maker/make_qemu_image.py +0 -0
  47. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/image_maker/make_rockchip_image.py +0 -0
  48. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/task.py +0 -0
  49. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/task_build_image.py +0 -0
  50. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/task_build_rootfs.py +0 -0
  51. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/task_download.py +0 -0
  52. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/template/conanfile.py.mako +0 -0
  53. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/tasks/template/rootfs.py.mako +0 -0
  54. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/ukr/__init__.py +0 -0
  55. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/ukr/build.py +0 -0
  56. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/utils/__init__.py +0 -0
  57. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/utils/env_detector.py +0 -0
  58. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/utils/images/__init__.py +0 -0
  59. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit/utils/images/emmc.py +0 -0
  60. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit.egg-info/SOURCES.txt +0 -0
  61. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit.egg-info/dependency_links.txt +0 -0
  62. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit.egg-info/entry_points.txt +0 -0
  63. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit.egg-info/requires.txt +0 -0
  64. {lbkit-0.9.3 → lbkit-0.9.4}/lbkit.egg-info/top_level.txt +0 -0
  65. {lbkit-0.9.3 → lbkit-0.9.4}/setup.cfg +0 -0
  66. {lbkit-0.9.3 → lbkit-0.9.4}/setup.py +0 -0
  67. {lbkit-0.9.3 → lbkit-0.9.4}/test/__init__.py +0 -0
  68. {lbkit-0.9.3 → lbkit-0.9.4}/test/test_codegen.py +0 -0
  69. {lbkit-0.9.3 → lbkit-0.9.4}/test/test_config.py +0 -0
  70. {lbkit-0.9.3 → lbkit-0.9.4}/test/test_helper.py +0 -0
@@ -1,4 +1,4 @@
1
- Below are just a few of the people who have contributed to litebmc/toolkit.
1
+ Below are just a few of the people who have contributed to litebmc/lbkit.
2
2
 
3
3
  ----------
4
4
  xuhaijun <xuhj@litebmc.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lbkit
3
- Version: 0.9.3
3
+ Version: 0.9.4
4
4
  Summary: Tools provided by litebmc.com
5
5
  Home-page: https://www.litebmc.com
6
6
  Author: xuhj@litebmc.com
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = '0.9.4'
@@ -0,0 +1,61 @@
1
+ """任务基础类"""
2
+ import os
3
+ import json
4
+ import tempfile
5
+ from lbkit.log import Logger
6
+ from concurrent.futures import ThreadPoolExecutor, Future, as_completed
7
+ from lbkit import misc
8
+ from lbkit.tools import Tools
9
+ from lbkit.misc import Color
10
+
11
+ tools = Tools("comp_build")
12
+ log = Logger("comp_build")
13
+
14
+
15
+ class BuildConanParallel(object):
16
+ def __init__(self, orderinfo, remote):
17
+ self.orderinfo = orderinfo
18
+ self.remote = remote
19
+
20
+ def _build_package(self, logfile, build_args, public_args):
21
+ cmd = f"conan install {build_args} {public_args}"
22
+ log.info("{}build start: '{}".format(Color.GREEN, Color.RESET_ALL) + cmd + ' log file: ' + logfile)
23
+ tools.exec(cmd, ignore_error=True, log_name=logfile, echo_cmd=False)
24
+ log.info("{}build finished: '{}".format(Color.GREEN, Color.RESET_ALL) + cmd + ' log file: ' + logfile)
25
+
26
+ def _build(self):
27
+ with open(self.orderinfo, "r") as fp:
28
+ order_info = json.load(fp)
29
+ public_args = order_info.get("profiles", {}).get("self", {}).get("args", "")
30
+ for orders in order_info.get("order", []):
31
+ threadPool = ThreadPoolExecutor(max_workers=(os.cpu_count() + 1) // 2)
32
+ results: list[Future] = []
33
+ for order in orders:
34
+ ref = order.get("ref")
35
+ pkgname = ref.split("/")[0]
36
+ logfile = f"{misc.LOG_DIR}/conan_{pkgname}.log"
37
+ packages = order.get("packages", [])
38
+ for package in packages:
39
+ for pkg in package:
40
+ binary = pkg.get("binary", "")
41
+ if binary == 'Download':
42
+ pkgid = pkg.get("package_id")
43
+ cmd = f'conan download {ref}:{pkgid}'
44
+ if self.remote:
45
+ cmd += f' -r {self.remote}'
46
+ tools.exec(cmd)
47
+ continue
48
+ if binary != "Build":
49
+ continue
50
+ build_args = pkg.get("build_args", "")
51
+ result = threadPool.submit(self._build_package, logfile, build_args, public_args)
52
+ results.append(result)
53
+ for result in as_completed(results):
54
+ result.result()
55
+
56
+ def build(self):
57
+ cwd = os.getcwd()
58
+ tmp = tempfile.TemporaryDirectory()
59
+ os.chdir(tmp.name)
60
+ self._build()
61
+ os.chdir(cwd)
@@ -4,6 +4,7 @@ import yaml
4
4
  import shutil
5
5
  import re
6
6
  import json
7
+ import sys
7
8
  from multiprocessing import Pool
8
9
  import traceback
9
10
  from argparse import ArgumentParser
@@ -206,23 +207,25 @@ class BuildComponent():
206
207
  export_cmd = "conan export . "
207
208
  if self.user != "litebmc" or self.channel != "release":
208
209
  export_cmd += f"--user={self.user} --channel={self.channel}"
209
- tools.run(export_cmd, capture_output=False)
210
- # import sys
211
- # sys.exit(-1)
210
+ tools.run(export_cmd, capture_output=False, stdout=sys.stdout, stderr=sys.stdout)
212
211
 
213
212
  lockfile = os.path.join(self.cwd, ".temp", "conan.lock")
213
+ orderfile = os.path.join(self.cwd, ".temp", "graph.order")
214
+ graph_cmd = f"conan graph build-order . {self.base_cmd} --order-by=recipe -f json --out-file={orderfile}"
215
+ if self.from_source:
216
+ graph_cmd += " --build='*'"
217
+ else:
218
+ graph_cmd += " --build=missing"
219
+ graph_cmd += f" --lockfile-out={lockfile}"
220
+ tools.run(graph_cmd)
221
+ bcp = BuildConanParallel(orderfile, self.options.remote)
222
+ bcp.build()
223
+ cmd = f"conan create {self.base_cmd} --build='{self.name}/*'"
224
+ tools.run(cmd, capture_output=False)
225
+
214
226
  graphfile = os.path.join(self.cwd, ".temp", "graph.info")
215
- lock_cmd = f"conan lock create . {self.base_cmd} --lockfile-out={lockfile}"
216
- tools.run(lock_cmd, capture_output=False)
217
227
  graph_cmd = f"conan graph info . {self.base_cmd} -f json --lockfile={lockfile}"
218
228
  tools.pipe([graph_cmd], out_file=graphfile)
219
- log.success(f"start build dependency packages of {self.package}")
220
- bcp = BuildConanParallel(graphfile, lockfile, self.base_cmd, self.from_source)
221
- bcp.build()
222
-
223
- cmd = f"conan create . {self.base_cmd} --build=missing"
224
- log.info(f"start build {self.package}: {cmd}")
225
- tools.run(cmd, capture_output=False)
226
229
 
227
230
  log.success(f"start deploy {self.package} and is's dependency packages")
228
231
  self.deploy(graphfile)
@@ -315,7 +318,7 @@ class BuildComponent():
315
318
  @property
316
319
  def package_id(self):
317
320
  cmd = f"conan graph info . {self.base_cmd} --filter=package_id"
318
- res = tools.run(cmd)
321
+ res = tools.run(cmd, capture_output=True)
319
322
  match = re.search(r"package_id: ([a-f0-9]{40})", res.stdout)
320
323
  if match is None:
321
324
  raise errors.LiteBmcException(f"Get package if of {self.package} failed")
@@ -324,13 +327,13 @@ class BuildComponent():
324
327
  @staticmethod
325
328
  def package_folder(self):
326
329
  cmd = f"conan cache path {self.package}#latest:{self.package_id}"
327
- res = tools.run(cmd)
330
+ res = tools.run(cmd, capture_output=True)
328
331
  return res.stdout.strip()
329
332
 
330
333
  @property
331
334
  def build_folder(self):
332
335
  cmd = f"conan cache path {self.package}#latest:{self.package_id} --folder=build"
333
- res = tools.run(cmd)
336
+ res = tools.run(cmd, capture_output=True)
334
337
  return res.stdout.strip()
335
338
 
336
339
  def test(self):
@@ -52,12 +52,12 @@ class TestComponent():
52
52
  shutil.rmtree(coverage_dir, ignore_errors=True)
53
53
  os.makedirs(coverage_dir)
54
54
  # 覆盖率数据路径由conanbase.make在build配置cflags时定义
55
- cmd = f"lcov -c -q -d {build_folder} -o {coverage_dir}/cover.info"
55
+ cmd = f"lcov --compat-libtool -c -q -d {build_folder} -o {coverage_dir}/cover.info"
56
56
  tool.exec(cmd)
57
57
  for dir in test_src_folder:
58
- cmd = f"lcov -r {coverage_dir}/cover.info \"{build_folder}/{dir}/*\" -o {coverage_dir}/cover.info"
58
+ cmd = f"lcov --compat-libtool -r {coverage_dir}/cover.info \"{build_folder}/{dir}/*\" -o {coverage_dir}/cover.info"
59
59
  tool.exec(cmd)
60
- cmd = f"lcov -r {coverage_dir}/cover.info \"*/include/*\" -o {coverage_dir}/cover.info"
60
+ cmd = f"lcov --compat-libtool -r {coverage_dir}/cover.info \"*/include/*\" -o {coverage_dir}/cover.info"
61
61
  tool.exec(cmd)
62
62
  cmd = f"genhtml -o {coverage_dir}/html --legend {coverage_dir}/cover.info"
63
63
  tool.exec(cmd)
@@ -1,41 +1,47 @@
1
1
  import logging
2
2
  import inspect
3
3
  import os
4
+ import sys
4
5
  from lbkit.misc import Color
5
6
 
6
7
  class Logger(logging.getLoggerClass()):
7
8
 
8
9
  def __init__(self, *args, **kwargs):
9
10
  super(Logger, self).__init__(*args, **kwargs)
10
- self.setLevel(logging.INFO)
11
- handler = logging.StreamHandler()
12
11
  self.logenv = os.environ.get("LOG")
13
12
  if self.logenv is None:
14
13
  formatter = logging.Formatter('%(message)s')
15
- self.setLevel(logging.INFO)
14
+ self._set_handler(logging.INFO, formatter)
16
15
  else:
17
- formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
16
+ formatter = logging.Formatter('%(asctime)s %(message)s')
18
17
  if self.logenv == "info":
19
- self.setLevel(logging.INFO)
18
+ self._set_handler(logging.INFO, formatter)
20
19
  elif self.logenv == "warn":
21
- self.setLevel(logging.WARNING)
22
- elif self.logenv == "error":
23
- self.setLevel(logging.ERROR)
24
- else:
25
- self.setLevel(logging.DEBUG)
20
+ self._set_handler(logging.WARNING, formatter)
21
+ elif self.logenv != "error":
22
+ self._set_handler(logging.DEBUG, formatter)
23
+ self._set_handler(logging.ERROR, formatter)
24
+
25
+ def _set_handler(self, level, formatter):
26
+ if level == logging.ERROR:
27
+ handler = logging.StreamHandler()
28
+ else:
29
+ handler = logging.StreamHandler(sys.stdout)
30
+ handler.addFilter(lambda record: record.levelno < logging.ERROR)
31
+ handler.setLevel(level)
26
32
  handler.setFormatter(formatter)
27
- self.handlers = []
28
33
  self.addHandler(handler)
29
34
 
35
+
30
36
  def error(self, msg, *args, **kwargs):
31
37
  if self.logenv:
32
38
  uptrace = kwargs.get("uptrace", 0)
33
39
  uptrace += 1
34
40
  stack = inspect.stack()[uptrace]
35
41
  filename = os.path.basename(stack.filename)
36
- msg = f"[{filename}:{stack.lineno}] " + Color.RED + msg + Color.RESET_ALL
42
+ msg = "ERROR: " + f"[{filename}:{stack.lineno}] " + Color.RED + msg + Color.RESET_ALL
37
43
  else:
38
- msg = Color.RED + msg + Color.RESET_ALL
44
+ msg = "ERROR: " + Color.RED + msg + Color.RESET_ALL
39
45
  kwargs.pop("uptrace", None)
40
46
  super(Logger, self).error(msg, *args, **kwargs)
41
47
 
@@ -45,7 +51,9 @@ class Logger(logging.getLoggerClass()):
45
51
  uptrace += 1
46
52
  stack = inspect.stack()[uptrace]
47
53
  filename = os.path.basename(stack.filename)
48
- msg = f"[{filename}:{stack.lineno}] " + msg
54
+ msg = "DEBUG: " + f"[{filename}:{stack.lineno}] " + msg
55
+ else:
56
+ msg = "DEBUG: " + msg
49
57
  kwargs.pop("uptrace", None)
50
58
  super(Logger, self).debug(msg, *args, **kwargs)
51
59
 
@@ -55,7 +63,9 @@ class Logger(logging.getLoggerClass()):
55
63
  uptrace += 1
56
64
  stack = inspect.stack()[uptrace]
57
65
  filename = os.path.basename(stack.filename)
58
- msg = f"[{filename}:{stack.lineno}] " + msg
66
+ msg = "INFO: " + f"[{filename}:{stack.lineno}] " + msg
67
+ else:
68
+ msg = "INFO: " + msg
59
69
  kwargs.pop("uptrace", None)
60
70
  super(Logger, self).info(msg, *args, **kwargs)
61
71
 
@@ -65,9 +75,9 @@ class Logger(logging.getLoggerClass()):
65
75
  uptrace += 1
66
76
  stack = inspect.stack()[uptrace]
67
77
  filename = os.path.basename(stack.filename)
68
- msg = f"[{filename}:{stack.lineno}] " + Color.YELLOW + msg + Color.RESET_ALL
78
+ msg = "WARN: " + f"[{filename}:{stack.lineno}] " + Color.YELLOW + msg + Color.RESET_ALL
69
79
  else:
70
- msg = Color.YELLOW + msg + Color.RESET_ALL
80
+ msg = "WARN: " + Color.YELLOW + msg + Color.RESET_ALL
71
81
  kwargs.pop("uptrace", None)
72
82
  super(Logger, self).warning(msg, *args, **kwargs)
73
83
 
@@ -77,9 +87,9 @@ class Logger(logging.getLoggerClass()):
77
87
  uptrace += 1
78
88
  stack = inspect.stack()[uptrace]
79
89
  filename = os.path.basename(stack.filename)
80
- msg = f"[{filename}:{stack.lineno}] " + Color.GREEN + msg + Color.RESET_ALL
90
+ msg = "SUCC: " + f"[{filename}:{stack.lineno}] " + Color.GREEN + msg + Color.RESET_ALL
81
91
  else:
82
- msg = Color.GREEN + msg + Color.RESET_ALL
92
+ msg = "SUCC: " + Color.GREEN + msg + Color.RESET_ALL
83
93
  kwargs.pop("uptrace", None)
84
94
  super(Logger, self).info(msg, *args, **kwargs)
85
95
 
@@ -37,7 +37,6 @@ class Config(object):
37
37
  self.profile_host = None
38
38
 
39
39
  # conan.lock options
40
- self.using_lockfile = args.lockfile
41
40
  self.update_lockfile = args.update_lockfile
42
41
  self.target = args.target
43
42
  self.product = args.product
@@ -86,7 +85,6 @@ class Config(object):
86
85
  parser.add_argument("-pr:b", "--profile_build", help="Apply the specified profile to the build machine", default="default")
87
86
  parser.add_argument("-bt", "--build_type", type=str, choices=['debug', 'release', 'minsize'], help="Set the build type", default="debug")
88
87
  parser.add_argument("-r", "--remote", help="specified conan server", default="litebmc")
89
- parser.add_argument("-l", "--lockfile", help="using conan.lock", action="store_true")
90
88
  parser.add_argument("-ul", "--update_lockfile", help="update conan.lock", action="store_true")
91
89
  parser.add_argument("-p", "--product", help="product name, default product is `default`", default="default")
92
90
  targets = Config.target_list()
@@ -43,24 +43,20 @@ class TaskClass(Task):
43
43
 
44
44
  def deploy(self, graph_file):
45
45
  with open(graph_file, "r") as fp:
46
- graph = json.load(fp)
47
- nodes = graph.get("graph", {}).get("nodes", {})
48
- for id, info in nodes.items():
49
- ref = info.get("ref")
50
- id = info.get("package_id")
51
- context = info.get("context")
52
- if context != "host" or ref.startswith("litebmc/"):
53
- continue
54
- cmd = f"conan cache path {ref}:{id}"
55
- package_folder = self.tools.run(cmd).stdout.strip()
56
- self.config.conan_install.append(package_folder)
57
-
58
- def download_recipe(self, pkg):
59
- cmd = f"conan cache path {pkg}"
60
- ret = self.exec_easy(cmd, ignore_error=True)
61
- if ret is None or ret.returncode != 0:
62
- cmd = f"conan download {pkg} -r {self.config.remote} --only-recipe"
63
- self.exec(cmd, ignore_error=True)
46
+ order_info = json.load(fp)
47
+ for orders in order_info.get("order", []):
48
+ for order in orders:
49
+ ref = order.get("ref")
50
+ packages = order.get("packages", [])
51
+ for package in packages:
52
+ for pkg in package:
53
+ binary = pkg.get("context", "")
54
+ if binary != "host":
55
+ continue
56
+ id = pkg.get("package_id", "")
57
+ cmd = f"conan cache path {ref}:{id}"
58
+ package_folder = self.tools.run(cmd).stdout.strip()
59
+ self.config.conan_install.append(package_folder)
64
60
 
65
61
  def build_rootfs(self):
66
62
  """构建产品rootfs包"""
@@ -101,33 +97,24 @@ class TaskClass(Task):
101
97
  fp.close()
102
98
 
103
99
  base_cmd = f"{self.common_args} {self.conan_settings}"
104
- threadPool = ThreadPoolExecutor(max_workers=16)
105
- if self.config.using_lockfile:
106
- lockfile = os.path.join(self.config.code_path, "conan.lock")
100
+ lockfile = os.path.join(self.config.code_path, "conan.lock")
101
+ orderfile = os.path.join(self.config.temp_path, "build-order.json")
102
+ graph_cmd = f"conan graph build-order . {base_cmd} --order-by=recipe -f json --out-file={orderfile}"
103
+ if self.config.from_source:
104
+ graph_cmd += " --build='*'"
107
105
  else:
108
- lockfile = os.path.join(self.config.temp_path, "conan.lock")
109
- # 创建新的conan.lock文件
110
- if not self.config.using_lockfile or self.config.update_lockfile:
111
- lock_cmd = f"conan lock create . {base_cmd} --lockfile-out={lockfile}"
112
- self.exec(lock_cmd, verbose=True)
113
- if not os.path.isfile(lockfile):
114
- raise FileNotFoundError("lockfile ./conan.lock was not found")
115
- with open(lockfile, "r") as fp:
116
- lock = json.load(fp)
117
- for key in ["requires", "build_requires", "python_requires", "config_requires"]:
118
- requires = lock.get(key, [])
119
- for require in requires:
120
- threadPool.submit(self.download_recipe, require)
121
- threadPool.shutdown(wait=True)
122
- graph_cmd = f"conan graph info . {base_cmd} -f json --lockfile={lockfile}"
123
- graphfile = os.path.join(self.config.temp_path, "graph.info")
124
- self.pipe([graph_cmd], out_file=graphfile)
125
- bcp = BuildConanParallel(graphfile, lockfile, self.common_args, self.config.from_source)
106
+ graph_cmd += " --build=missing"
107
+ if self.config.update_lockfile or not os.path.isfile(lockfile):
108
+ graph_cmd += f" --lockfile-out={lockfile}"
109
+ else:
110
+ graph_cmd += f" --lockfile={lockfile} --lockfile-partial"
111
+ self.exec(graph_cmd, verbose=True)
112
+ bcp = BuildConanParallel(orderfile, self.config.remote)
126
113
  bcp.build()
127
114
 
128
115
  self.exec(f"sed -i 's@rootfs_df190c/0.0.1#.*\"@rootfs_df190c/0.0.1\"@g' {lockfile}")
129
116
  # 部署应用到self.config.conan_install
130
- self.deploy(graphfile)
117
+ self.deploy(orderfile)
131
118
 
132
119
  def run(self):
133
120
  """任务入口"""
@@ -74,12 +74,14 @@ class TaskClass(Task):
74
74
  src_fd = open(profile, "r")
75
75
  template = Template(src_fd.read())
76
76
  src_fd.close()
77
- content = template.safe_substitute(compiler_path=self.compiler_path, sysroot_path=self.sysroot_path)
77
+ content = template.safe_substitute(compiler_path=self.compiler_path,
78
+ sysroot_path=self.sysroot_path,
79
+ code_path=self.config.code_path)
78
80
  dst_fp.write(content)
79
81
 
80
82
  with open(dst_profile, "r") as fp:
81
83
  profile_data = jinja2.Template(fp.read()).render()
82
- parser = configparser.ConfigParser()
84
+ parser = configparser.ConfigParser(allow_no_value=True, delimiters=('=',))
83
85
  parser.read_string(profile_data)
84
86
  strip = "strip"
85
87
  if parser.has_option("buildenv", "STRIP"):
@@ -22,6 +22,33 @@ class Tools(object):
22
22
  self.log_name = os.path.join(self.log_dir, f"{log_name}.log")
23
23
  self.log: Logger = Logger(log_name)
24
24
 
25
+ class _Tee:
26
+ """同时写入文件和终端的类"""
27
+ def __init__(self, file_obj, verbose: bool):
28
+ self.file = file_obj
29
+ self.verbose = verbose
30
+ self.memory = ""
31
+ self.stdout = sys.stdout
32
+ self.stderr = sys.stderr
33
+
34
+ def writef(self, data):
35
+ self.file.write(data)
36
+
37
+ def write(self, data):
38
+ # 同时写入文件和终端
39
+ self.memory += data
40
+ self.file.write(data)
41
+ if self.verbose:
42
+ self.stdout.write(data)
43
+
44
+ def fileno(self):
45
+ return self.file.fileno()
46
+
47
+ def flush(self):
48
+ self.file.flush()
49
+ if self.verbose:
50
+ self.stdout.flush()
51
+
25
52
  @staticmethod
26
53
  def _real_command(shell: str):
27
54
  if not isinstance(shell, str):
@@ -44,50 +71,46 @@ class Tools(object):
44
71
  return cmd
45
72
 
46
73
  def exec(self, cmd: str, verbose=False, ignore_error = False, sensitive=False, log_prefix="", **kwargs):
47
- """执行命令"""
74
+ """执行命令,输出同时写入日志文件和终端"""
48
75
  stack = inspect.stack()[1]
49
76
  file = os.path.basename(stack.filename)
50
77
  line = stack.lineno
51
78
  echo_cmd = kwargs.get("echo_cmd", True)
52
79
  log_name = kwargs.get("log_name", self.log_name)
53
80
  uptrace = kwargs.get("uptrace", 0) + 1
54
-
55
81
  show_cmd = "***" if sensitive else cmd
56
82
  if log_prefix:
57
83
  show_cmd = "(" + log_prefix + ") " + show_cmd
58
84
 
59
- fd = os.fdopen(os.open(log_name, os.O_APPEND | os.O_CREAT | os.O_RDWR), "a+")
60
- if echo_cmd:
61
- self.log.info("{}>>>>{} {}".format(Color.GREEN, Color.RESET_ALL, show_cmd), uptrace=uptrace)
62
- last_pos = fd.tell()
63
- fd.write(">>>> {}\n".format(show_cmd))
64
85
  if os.environ.get("VERBOSE", False):
65
86
  verbose = True
87
+ fd = os.fdopen(os.open(log_name, os.O_APPEND | os.O_CREAT | os.O_RDWR), "a+")
88
+ # 使用 _Tee 类同时写入文件和终端
89
+ msg = "{}>>>>{} {}\n".format(Color.GREEN, Color.RESET_ALL, show_cmd)
90
+ tee = self._Tee(fd, verbose)
91
+ if echo_cmd:
92
+ sys.stdout.write(msg)
93
+ tee.writef(msg)
66
94
  real_cmd = self._real_command(cmd)
67
- result = subprocess.Popen(real_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
95
+ result = subprocess.Popen(real_cmd, stdout=tee, stderr=tee, universal_newlines=True)
96
+ tee.flush()
97
+ fd.close()
68
98
  if result is None:
69
99
  raise errors.RunCommandException(f"Run command {show_cmd} failed")
70
- for msg in result.stdout:
71
- fd.write(msg)
72
- if verbose:
73
- sys.stdout.write(msg)
74
- fd.close()
75
100
  result.communicate()
76
- if result is None or result.returncode != 0:
77
- if not ignore_error:
78
- msg = f"{file}:{line} > Run command ({show_cmd}) failed, log file is {log_name}"
101
+ if result.returncode == 0:
102
+ return result
103
+ if not ignore_error:
104
+ msg = f"{file}:{line} > Run command ({show_cmd}) failed, log file is {log_name}"
105
+ if not verbose:
79
106
  self.log.warn(f">>>>>>>>>> '{show_cmd}' LOG START", uptrace=uptrace)
80
- fd = open(log_name, "r")
81
- fd.seek(last_pos, 0)
82
- logs = fd.readlines()
83
- for log in logs:
84
- print(log, end="")
85
- self.log.warn(f"<<<<<<<<<< '{show_cmd}' LOG END", uptrace=uptrace)
107
+ sys.stdout.write(tee.memory)
86
108
  sys.stdout.flush()
87
- raise errors.RunCommandException(msg)
88
- else:
89
- msg = f"{file}:{line} > Run command ({show_cmd}) failed but ignore error"
90
- self.log.warn(msg, uptrace=uptrace)
109
+ self.log.warn(f"<<<<<<<<<< '{show_cmd}' LOG END", uptrace=uptrace)
110
+ raise errors.RunCommandException(msg)
111
+ else:
112
+ msg = f"{file}:{line} > Run command ({show_cmd}) failed but ignore error"
113
+ self.log.warn(msg, uptrace=uptrace)
91
114
  return result
92
115
 
93
116
  def pipe(self, cmds: list[str], ignore_error=False, out_file = None, **kwargs):
@@ -127,13 +150,16 @@ class Tools(object):
127
150
  stdin.close()
128
151
  return output
129
152
 
130
- def run(self, cmd, ignore_error=False, **kwargs):
153
+ def run(self, cmd, ignore_error=False, stdout=None, stderr=None, **kwargs):
131
154
  uptrace = kwargs.get("uptrace", 0) + 1
132
155
  capture_output = kwargs.get("capture_output", True)
133
156
  self.log.debug("{}>>>>{} {}".format(Color.GREEN, Color.RESET_ALL, cmd), uptrace=uptrace)
134
157
  real_cmd = self._real_command(cmd)
135
158
  check = False if ignore_error else True
136
- return subprocess.run(real_cmd, capture_output=capture_output, check=check, encoding="utf-8")
159
+ if stdout or stderr:
160
+ return subprocess.run(real_cmd, check=check, stdout=stdout, stderr=stderr, encoding="utf-8")
161
+ else:
162
+ return subprocess.run(real_cmd, capture_output=capture_output, check=check, encoding="utf-8")
137
163
 
138
164
  @staticmethod
139
165
  def file_digest_sha256(filename):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lbkit
3
- Version: 0.9.3
3
+ Version: 0.9.4
4
4
  Summary: Tools provided by litebmc.com
5
5
  Home-page: https://www.litebmc.com
6
6
  Author: xuhj@litebmc.com
@@ -1,2 +0,0 @@
1
-
2
- __version__ = '0.9.3'
@@ -1,206 +0,0 @@
1
- """任务基础类"""
2
- import os
3
- import json
4
- import tempfile
5
- from queue import Queue
6
- from threading import Thread
7
- from lbkit.log import Logger
8
- from lbkit.errors import LiteBmcException
9
- from lbkit import misc
10
- from lbkit.tools import Tools
11
-
12
- tools = Tools("comp_build")
13
- log = Logger("comp_build")
14
-
15
- class ConanPackage():
16
- def __init__(self, node):
17
- self.node = node
18
- self.deps: dict[str, ConanPackage] = {}
19
- self.build_deps: dict[str, ConanPackage] = {}
20
- self.ref = node.get("ref")
21
- self.pkg = self.ref.split("#")[0]
22
- self.name = self.pkg.split("/")[0]
23
- self.context = node.get("context")
24
- self.building = False
25
- self.is_host = self.context == "host"
26
- binary = node.get("binary")
27
- self.binary_exist = (binary in ["Cache"])
28
-
29
- def append_dep(self, dep):
30
- self.deps[dep.pkg] = dep
31
-
32
- def append_build_dep(self, dep):
33
- self.build_deps[dep.pkg] = dep
34
-
35
- # 间接构建依赖更新为直接依赖
36
- def update_indirect_build_dep(self):
37
- deps = {}
38
- for _, dep in self.build_deps.items():
39
- sub_deps = dep.update_indirect_build_dep()
40
- deps.update(sub_deps)
41
- self.build_deps.update(deps)
42
- return self.build_deps
43
-
44
- @property
45
- def options(self):
46
- return self.node.get("options", {})
47
-
48
- @property
49
- def settings(self):
50
- return self.node.get("settings", {})
51
-
52
- @property
53
- def default_options(self):
54
- return self.node.get("default_options", {})
55
-
56
-
57
- class BuildConanParallel(object):
58
- def __init__(self, graphinfo, lockfile, cmd, force_build):
59
- self.queue = Queue()
60
- self.cmd = ""
61
- chunks = cmd.split()
62
- skip = False
63
- for chunk in chunks:
64
- if skip:
65
- skip = False
66
- continue
67
- if chunk in ["--user", "--channel", "--version", "--name"]:
68
- skip = True
69
- continue
70
- self.cmd += f"{chunk} "
71
- self.graphinfo = os.path.realpath(graphinfo)
72
- self.lockfile = os.path.realpath(lockfile)
73
- if not os.path.isfile(graphinfo):
74
- raise LiteBmcException(f"graph file {graphinfo} not exist")
75
- self.exception = None
76
- self.force_build = force_build
77
-
78
- def build_dep(self, cp: ConanPackage, options):
79
- for name, value in cp.settings.items():
80
- options += f" -s {name}={value}"
81
-
82
- cmd = f"conan install --requires={cp.ref} {self.cmd} {options}"
83
- if self.force_build:
84
- cmd += f" --build=\"{cp.name}/*\""
85
- cmd += " --build=missing"
86
- cmd += f" --lockfile={self.lockfile}"
87
- try:
88
- logfile = f"{misc.LOG_DIR}/conan_{cp.name}.log"
89
- log.info(f">>>> build {cp.ref} start, logfile: {logfile}")
90
- log.debug(f">>>> {cmd}")
91
- tools.exec(cmd, echo_cmd=False, log_name=logfile)
92
- log.success(f"<<<< build {cp.ref} finished")
93
- except Exception as e:
94
- self.exception = e
95
- self.queue.put(cp)
96
-
97
- def _build(self):
98
- with open(self.graphinfo, "r") as fp:
99
- grapth = json.load(fp)
100
- nodes = grapth.get("graph", {}).get("nodes", {})
101
- packages: dict[str, ConanPackage] = {}
102
-
103
- build_works = {}
104
- for id, node in nodes.items():
105
- if id == "0":
106
- continue
107
- cp = ConanPackage(node)
108
- packages[cp.pkg] = cp
109
- build_works[cp.name] = False
110
- for id, node in nodes.items():
111
- if id == "0":
112
- continue
113
- ref = node.get("ref")
114
- if ref.startswith("litebmc/"):
115
- continue
116
- cp = packages[ref.split("#")[0]]
117
- deps = node.get("dependencies", {})
118
- for _, dep in deps.items():
119
- dep_ref = dep.get("ref")
120
- direct = dep.get("direct")
121
- if not direct:
122
- continue
123
- dep_cp = packages[dep_ref]
124
- if dep_cp.is_host:
125
- cp.append_dep(dep_cp)
126
- else:
127
- cp.append_build_dep(dep_cp)
128
- options = ""
129
- for _, cp in packages.items():
130
- for name, value in cp.options.items():
131
- if value is None:
132
- continue
133
- def_val = cp.default_options.get(name)
134
- conan_name = cp.pkg.split("@")[0]
135
- if isinstance(def_val, bool):
136
- if (def_val and "False" == value) or (not def_val and "True" == value):
137
- options += f" -o {conan_name}:{name}={value}"
138
- elif def_val != value:
139
- options += f" -o {conan_name}:{name}={value}"
140
- # 工具间接依赖变更为直接依赖
141
- for name, pkg in packages.items():
142
- pkg.update_indirect_build_dep()
143
- wait_finished = 0
144
- while True:
145
- for _, cp in packages.items():
146
- if wait_finished >= 4:
147
- continue
148
- if cp.ref.startswith("litebmc/"):
149
- continue
150
- # 如果是构建工具,不参与构建
151
- if not cp.is_host:
152
- continue
153
- # 如果还有依赖未构建完成,不参与构建
154
- if len(cp.deps) != 0:
155
- continue
156
- # 如果正在构建
157
- if cp.building:
158
- continue
159
- # 相同名称的组件正在构建时不启动新的构建
160
- if build_works[cp.name]:
161
- continue
162
- if not cp.binary_exist or self.force_build:
163
- # 当依赖的构建工具存在正在构建的组件时不能构建
164
- need_build = True
165
- for _, dep in cp.build_deps.items():
166
- # 正在构建且未构建出制品时
167
- if not dep.binary_exist and dep.building:
168
- need_build = False
169
- break
170
- if not need_build:
171
- continue
172
- cp.building = True
173
- # 启动构建前将其依赖的构建工具置为正在构建
174
- for _, dep in cp.build_deps.items():
175
- dep.building = True
176
- build_works[cp.name] = True
177
- wait_finished += 1
178
- thread = Thread(target=self.build_dep, args=(cp, options,))
179
- thread.start()
180
- else:
181
- cp.building = True
182
- build_works[cp.name] = True
183
- wait_finished += 1
184
- self.queue.put(cp)
185
- if not wait_finished:
186
- return
187
- cp = self.queue.get()
188
- if self.exception:
189
- raise self.exception
190
- build_works[cp.name] = False
191
- wait_finished -= 1
192
- for _, sub_cp in packages.items():
193
- if sub_cp.deps.get(cp.pkg):
194
- sub_cp.deps.pop(cp.pkg)
195
- # 构建完成后,组件的构建依赖工具一定构建完成且制品存在
196
- for _, dep in cp.build_deps.items():
197
- dep.building = False
198
- dep.binary_exist = True
199
-
200
-
201
- def build(self):
202
- cwd = os.getcwd()
203
- tmp = tempfile.TemporaryDirectory()
204
- os.chdir(tmp.name)
205
- self._build()
206
- os.chdir(cwd)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes