lbkit 0.8.5__tar.gz → 0.8.6__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 (67) hide show
  1. {lbkit-0.8.5/lbkit.egg-info → lbkit-0.8.6}/PKG-INFO +1 -1
  2. lbkit-0.8.6/lbkit/__init__.py +2 -0
  3. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/misc.py +41 -0
  4. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/config.py +62 -3
  5. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/executor.py +2 -0
  6. lbkit-0.8.6/lbkit/tasks/task.py +132 -0
  7. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/task_build_image.py +1 -1
  8. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/task_build_manifest.py +1 -1
  9. lbkit-0.8.6/lbkit/tasks/task_build_prepare.py +98 -0
  10. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/task_build_rootfs.py +1 -1
  11. lbkit-0.8.6/lbkit/tasks/task_download.py +129 -0
  12. {lbkit-0.8.5 → lbkit-0.8.6/lbkit.egg-info}/PKG-INFO +1 -1
  13. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit.egg-info/SOURCES.txt +2 -1
  14. lbkit-0.8.6/test/test_config.py +66 -0
  15. lbkit-0.8.5/lbkit/__init__.py +0 -2
  16. lbkit-0.8.5/lbkit/tasks/task.py +0 -70
  17. lbkit-0.8.5/lbkit/tasks/task_build_prepare.py +0 -59
  18. lbkit-0.8.5/lbkit/tasks/test.py +0 -40
  19. {lbkit-0.8.5 → lbkit-0.8.6}/AUTHORS +0 -0
  20. {lbkit-0.8.5 → lbkit-0.8.6}/LICENSE +0 -0
  21. {lbkit-0.8.5 → lbkit-0.8.6}/MANIFEST.in +0 -0
  22. {lbkit-0.8.5 → lbkit-0.8.6}/README.md +0 -0
  23. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/build_conan_parallel.py +0 -0
  24. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/ci_robot/__init__.py +0 -0
  25. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/ci_robot/gitee.py +0 -0
  26. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/cli.py +0 -0
  27. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/__init__.py +0 -0
  28. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/codegen.py +0 -0
  29. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/ctype_defination.py +0 -0
  30. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/idf_interface.py +0 -0
  31. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/renderer.py +0 -0
  32. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/client.c.mako +0 -0
  33. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/client.h.mako +0 -0
  34. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/interface.c.mako +0 -0
  35. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/interface.introspect.xml.mako +0 -0
  36. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/public.c.mako +0 -0
  37. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/public.h.mako +0 -0
  38. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/server.c.mako +0 -0
  39. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/codegen/template/server.h.mako +0 -0
  40. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/component/__init__.py +0 -0
  41. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/component/arg_parser.py +0 -0
  42. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/component/build.py +0 -0
  43. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/component/template/conanbase.mako +0 -0
  44. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/component/template/deploy.mako +0 -0
  45. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/component/test.py +0 -0
  46. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/errors.py +0 -0
  47. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/helper.py +0 -0
  48. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/lbkit.py +0 -0
  49. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/log.py +0 -0
  50. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/__init__.py +0 -0
  51. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/task_test.py +0 -0
  52. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/template/conanfile.py.mako +0 -0
  53. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tasks/template/rootfs.py.mako +0 -0
  54. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/tools.py +0 -0
  55. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/utils/__init__.py +0 -0
  56. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/utils/env_detector.py +0 -0
  57. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/utils/images/__init__.py +0 -0
  58. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit/utils/images/emmc.py +0 -0
  59. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit.egg-info/dependency_links.txt +0 -0
  60. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit.egg-info/entry_points.txt +0 -0
  61. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit.egg-info/requires.txt +0 -0
  62. {lbkit-0.8.5 → lbkit-0.8.6}/lbkit.egg-info/top_level.txt +0 -0
  63. {lbkit-0.8.5 → lbkit-0.8.6}/setup.cfg +0 -0
  64. {lbkit-0.8.5 → lbkit-0.8.6}/setup.py +0 -0
  65. {lbkit-0.8.5 → lbkit-0.8.6}/test/__init__.py +0 -0
  66. {lbkit-0.8.5 → lbkit-0.8.6}/test/test_codegen.py +0 -0
  67. {lbkit-0.8.5 → lbkit-0.8.6}/test/test_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lbkit
3
- Version: 0.8.5
3
+ Version: 0.8.6
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.8.6'
@@ -5,6 +5,7 @@ import json
5
5
  import yaml
6
6
  import re
7
7
  import requests
8
+ import fcntl
8
9
  from string import Template
9
10
  from colorama import Fore, Style
10
11
  from jsonschema import validate, ValidationError
@@ -104,3 +105,43 @@ def load_yml_with_json_schema_validate(yml_file, default_json_schema_file, **kwa
104
105
  raise PackageConfigException(f"validate {yml_file} failed, schema file is {schema_file}, "
105
106
  f"message: {exc.message}\n"
106
107
  "installing redhat.vscode-yaml plugin in vscode will help you write odf files")
108
+
109
+ class DownloadFlag():
110
+ @staticmethod
111
+ def clean(filename):
112
+ """清理文件标记"""
113
+ filename += ".flag"
114
+ fp = open(filename, "a+")
115
+ fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
116
+ fp.truncate(0)
117
+ fcntl.flock(fp, fcntl.F_UNLCK)
118
+ fp.close()
119
+
120
+ @staticmethod
121
+ def create(filename, url, new_hash):
122
+ """创建文件标记"""
123
+ filename += ".flag"
124
+ fp = open(filename, "a+")
125
+ fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
126
+ fp.seek(0, 0)
127
+ fp.truncate(0)
128
+ fp.write(url + "|" + new_hash)
129
+ fcntl.flock(fp, fcntl.F_UNLCK)
130
+ fp.close()
131
+
132
+ @staticmethod
133
+ def read(filename):
134
+ """读取文件标记"""
135
+ filename += ".flag"
136
+ if not os.path.isfile(filename):
137
+ return "", ""
138
+ fp = open(filename, "a+")
139
+ fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
140
+ fp.seek(0, 0)
141
+ content = fp.read()
142
+ fcntl.flock(fp, fcntl.F_UNLCK)
143
+ fp.close()
144
+ if len(content) == 0:
145
+ return "", ""
146
+ chunk = content.split("|")
147
+ return chunk[0], chunk[1]
@@ -15,6 +15,7 @@ class Config(object):
15
15
 
16
16
  # 配置项
17
17
  self.manifest = os.path.join(os.getcwd(), args.manifest)
18
+ self.manifest = os.path.realpath(self.manifest)
18
19
  # 配置项目录
19
20
  self.work_dir = os.path.dirname(self.manifest)
20
21
  sys.path.append(self.work_dir)
@@ -32,13 +33,14 @@ class Config(object):
32
33
 
33
34
  # 编译主机配置项
34
35
  self.profile_build = args.profile_build
35
- # 编译目标配置项
36
- self.profile_host = args.profile
36
+ # 待所有参数确认后会调用refresh_profile_name设置正确的profile
37
+ self.profile_host = None
37
38
 
38
39
  # conan.lock options
39
40
  self.using_lockfile = args.lockfile
40
41
  self.update_lockfile = args.update_lockfile
41
42
  self.target = args.target
43
+ self.product = args.product
42
44
 
43
45
  # 设置并创建构建所需目录
44
46
  log.info("Work dir: %s", self.work_dir)
@@ -47,6 +49,7 @@ class Config(object):
47
49
  self.output_path = os.path.join(self.temp_path, "output")
48
50
  self.download_path = os.path.join(self.temp_path, "download")
49
51
  self.tool_path = os.path.join(self.temp_path, "tools")
52
+ self.compiler_path = os.path.join(self.tool_path, "compiler")
50
53
  # conan组件打包目录
51
54
  self.conan_install = []
52
55
  self.mnt_path = os.path.join(self.temp_path, "mnt_path")
@@ -59,8 +62,12 @@ class Config(object):
59
62
  os.makedirs(self.tool_path, exist_ok=True)
60
63
  os.makedirs(self.output_path, exist_ok=True)
61
64
  os.makedirs(self.download_path, exist_ok=True)
65
+ os.makedirs(self.compiler_path, exist_ok=True)
62
66
  # 制作rootfs时需要strip镜像,所以需要单独指定stip路径
63
67
  self.strip = "strip"
68
+ self.check_product()
69
+ # 刷新conan profile
70
+ self.refresh_profile_name()
64
71
 
65
72
  @staticmethod
66
73
  def target_list():
@@ -79,12 +86,12 @@ class Config(object):
79
86
  parser = argparse.ArgumentParser(description="Build LiteBMC", formatter_class=argparse.RawTextHelpFormatter)
80
87
  parser.add_argument("-m", "--manifest", help="Specify the manifest.yml, ignored when -l is specified.", default="./manifest.yml")
81
88
  parser.add_argument("-s", "--from_source", help="Build from source", action="store_true")
82
- parser.add_argument("-pr", "--profile", help="Apply the specified profile to the host machine", default="litebmc.ini")
83
89
  parser.add_argument("-pr:b", "--profile_build", help="Apply the specified profile to the build machine", default="default")
84
90
  parser.add_argument("-bt", "--build_type", type=str, choices=['debug', 'release', 'minsize'], help="Set the build type", default="debug")
85
91
  parser.add_argument("-r", "--remote", help="specified conan server", default="litebmc")
86
92
  parser.add_argument("-l", "--lockfile", help="using conan.lock", action="store_true")
87
93
  parser.add_argument("-ul", "--update_lockfile", help="update conan.lock", action="store_true")
94
+ parser.add_argument("-p", "--product", help="product name, default product is `default`", default="default")
88
95
  targets = Config.target_list()
89
96
  target_help = "build target:"
90
97
  for tgt, _ in targets.items():
@@ -102,6 +109,45 @@ class Config(object):
102
109
  return default
103
110
  return manifest
104
111
 
112
+ @staticmethod
113
+ def merge_cfg(dst, src):
114
+ """合并两个配置项"""
115
+ # 如果源为None,则返回dst
116
+ if src is None:
117
+ return dst
118
+ # 如果目标是空的,直接返回src
119
+ if not dst:
120
+ return src
121
+ # 如果是数组、标量的直接覆盖dst即可,所以返回src
122
+ if not isinstance(dst, dict):
123
+ return src
124
+ # 如果目标非空,但源是空,直接返回目标
125
+ if not src:
126
+ return dst
127
+ if not isinstance(src, dict):
128
+ raise Exception(f"Merge configuration failed, source config {src} is not a dictionary")
129
+ output = {}
130
+ for key, val in src.items():
131
+ dst_val = dst.get(key)
132
+ output[key] = Config.merge_cfg(dst_val, val)
133
+ for key, val in dst.items():
134
+ # 已经合并过,即目标中存在,但源不存在,直接合并
135
+ if key in output:
136
+ continue
137
+ output[key] = val
138
+ return output
139
+
140
+ def get_product_config(self, key: str, default=None):
141
+ """获取产品配置,注意,key只需要基于products/[name]/即可,如 toolchain"""
142
+ global_cfg = self.get_manifest_config(key, default)
143
+ key = f"products/{self.product}/{key}"
144
+ product_cfg = self.get_manifest_config(key, None)
145
+ if not product_cfg:
146
+ return global_cfg
147
+ if not global_cfg:
148
+ return product_cfg
149
+ return Config.merge_cfg(global_cfg, product_cfg)
150
+
105
151
  def load_manifest(self):
106
152
  """加载manifest.yml并验证schema文件"""
107
153
  template = {}
@@ -110,6 +156,19 @@ class Config(object):
110
156
  template["download_path"] = os.path.join(self.download_path)
111
157
  return load_yml_with_json_schema_validate(self.manifest, "/usr/share/litebmc/schema/pdf.v1.json", **template)
112
158
 
159
+ def check_product(self):
160
+ products = self.get_manifest_config("products", {})
161
+ if products.get(self.product):
162
+ return
163
+ log.error(f"Only the following products are supported:")
164
+ for key, _ in products.items():
165
+ log.info(" * " + key)
166
+ # todo: 待manifest.yml整改到位到删除下面注释
167
+ # raise Exception(f"Unkown product {self.product}")
168
+
169
+ def refresh_profile_name(self):
170
+ self.profile_host = self.get_product_config("toolchain/profile/name", "litebmc")
171
+
113
172
  def set_build_type(self, value):
114
173
  self.build_type = value
115
174
 
@@ -3,6 +3,7 @@
3
3
  import importlib
4
4
  import os
5
5
  import time
6
+ import traceback
6
7
  import sys
7
8
 
8
9
  from multiprocessing import Process
@@ -138,6 +139,7 @@ def task_handler(te:TaskExecutor):
138
139
  try:
139
140
  ret = te.run()
140
141
  except Exception as e:
142
+ log.error(traceback.print_exc())
141
143
  log.error(f"Task {te.status_key} exit with exceiption: {str(e)}")
142
144
  with status_lock:
143
145
  status_dict[te.status_key] = TASK_STATUS_EXCEPT
@@ -0,0 +1,132 @@
1
+ """任务基础类"""
2
+ import importlib
3
+ import os
4
+ import time
5
+ import shutil
6
+
7
+ from multiprocessing import Process
8
+ from lbkit.log import Logger
9
+ from lbkit.tools import Tools
10
+ from lbkit.misc import load_yml_with_json_schema_validate, DownloadFlag
11
+
12
+ from lbkit.tasks.config import Config
13
+
14
+ class ManifestValidateError(OSError):
15
+ """Raised when validation manifest.yml failed."""
16
+
17
+ class Task(Process):
18
+ """任务基础类,提供run和install默认实现以及其它基础该当"""
19
+ def __init__(self, config: Config, name: str):
20
+ super().__init__()
21
+ self.log: Logger = Logger("task")
22
+ self.tools: Tools = Tools(name)
23
+ self.config: Config = config
24
+ self.name = name
25
+
26
+ def install(self):
27
+ """安装任务"""
28
+ self.log.info("install...........")
29
+
30
+ def exec(self, cmd: str, verbose=False, ignore_error = False, sensitive=False, log_prefix="", **kwargs):
31
+ kwargs["uptrace"] = kwargs.get("uptrace", 0) + 1
32
+ return self.tools.exec(cmd, verbose, ignore_error, sensitive, log_prefix, **kwargs)
33
+
34
+ def pipe(self, cmds: list[str], ignore_error=False, out_file = None, **kwargs):
35
+ kwargs["uptrace"] = kwargs.get("uptrace", 0) + 1
36
+ self.tools.pipe(cmds, ignore_error, out_file, **kwargs)
37
+
38
+ def exec_easy(self, cmd, ignore_error=False, **kwargs):
39
+ kwargs["uptrace"] = kwargs.get("uptrace", 0) + 1
40
+ return self.tools.run(cmd, ignore_error, **kwargs)
41
+
42
+ def do_hook(self, path):
43
+ """执行任务钓子,用于定制化"""
44
+ try:
45
+ module = importlib.import_module(path)
46
+ except TypeError:
47
+ self.log.info("Load module(%s) failed, skip", path, uptrace=2)
48
+ return
49
+ self.log.info(f"load hook: {path}", uptrace=2)
50
+ hook = module.TaskHook(self.config, "do_hook")
51
+ hook.run()
52
+
53
+ def get_manifest_config(self, key: str, default=None):
54
+ return self.config.get_manifest_config(key, default)
55
+
56
+ def load_manifest(self):
57
+ """加载manifest.yml并验证schema文件"""
58
+ return self.config.load_manifest()
59
+
60
+ def waitfile(self, src, timeout=1):
61
+ src = os.path.realpath(src)
62
+ if os.path.islink(src):
63
+ raise FileNotFoundError(f"Source file {src} is a symlink, copying failed")
64
+ if not os.path.isfile(src):
65
+ if timeout:
66
+ # 如果有timeout的,可能是正在下载的文件,需要等待超时时间再检查一次
67
+ time.sleep(timeout)
68
+ if not os.path.isfile(src):
69
+ raise FileNotFoundError(f"Source file {src} does not exist, copying failed")
70
+ else:
71
+ raise FileNotFoundError(f"Source file {src} does not exist, copying failed")
72
+
73
+ # 检查是否由download下载器下载,如果是,需要检查是否下载完成
74
+ if src.startswith(self.config.download_path):
75
+ timeout_cnt = 0
76
+ timeout_sec = 0
77
+ while True:
78
+ _, hash = DownloadFlag.read(src)
79
+ if hash:
80
+ filehash = self.tools.file_digest_sha256(src)
81
+ if hash != filehash:
82
+ DownloadFlag.clean(src)
83
+ raise Exception(f"The hash of file {src} is {filehash}, not equal to {hash}")
84
+ break
85
+ else:
86
+ time.sleep(0.1)
87
+ timeout_sec += 0.1
88
+ timeout_cnt += 0.1
89
+ # 每10秒打印一次等等日志
90
+ if timeout_sec == 10:
91
+ self.log.info(f"Wait file {src} download success")
92
+ timeout_sec = 0
93
+ # 60秒未创建文件的,判定文件不存在,中止构建
94
+ if timeout_cnt == 60 and not os.path.isfile(src):
95
+ raise Exception(f"Check file {src} failed because source file does not exist")
96
+ if timeout_cnt == 300:
97
+ raise Exception(f"Check file {src} failed because file was not downloaded within 300 seconds")
98
+
99
+ def copyfile(self, src, dst):
100
+ try:
101
+ self.waitfile(src)
102
+ except Exception as e:
103
+ raise Exception(f"Copy file {src} to {dst} failed because source file not ready") from e
104
+
105
+ dst = os.path.realpath(dst)
106
+ if not dst.startswith(self.config.temp_path):
107
+ raise FileNotFoundError(f"Destination file {dst} is not a subpath of {self.config.temp_path}")
108
+
109
+ if os.path.isfile(dst):
110
+ os.unlink(dst)
111
+ dst_dir = os.path.dirname(dst)
112
+ if not os.path.isdir(dst_dir):
113
+ os.makedirs(dst_dir)
114
+ if os.path.isfile(dst) or os.path.islink(dst):
115
+ os.unlink(dst)
116
+ self.log.debug(f"copy {src} to {dst}")
117
+ shutil.copyfile(src, dst)
118
+
119
+ def deal_conf(self, config_dict):
120
+ """
121
+ 处理每个Task的私有配置"work_config"
122
+ 当work类有set_xxx类方法时,则可以在target文件中配置xxx
123
+ """
124
+ if not config_dict:
125
+ return
126
+ for conf in config_dict:
127
+ try:
128
+ exist = getattr(self, f"set_{conf}")
129
+ val = config_dict.get(conf)
130
+ exist(val)
131
+ except Exception as e:
132
+ raise Exception(f"无效配置: {conf}, {e}") from e
@@ -41,5 +41,5 @@ class TaskClass(Task):
41
41
 
42
42
  if __name__ == "__main__":
43
43
  config = Config()
44
- build = TaskClass(config)
44
+ build = TaskClass(config, "test")
45
45
  build.run()
@@ -159,5 +159,5 @@ class TaskClass(Task):
159
159
 
160
160
  if __name__ == "__main__":
161
161
  config = Config()
162
- build = TaskClass(config)
162
+ build = TaskClass(config, "test")
163
163
  build.run()
@@ -0,0 +1,98 @@
1
+ """环境准备"""
2
+ import os
3
+ import shutil
4
+ import jinja2
5
+ import configparser
6
+ from string import Template
7
+ from lbkit.tasks.config import Config
8
+ from lbkit.tasks.task import Task
9
+ from lbkit.log import Logger
10
+ from lbkit.misc import DownloadFlag
11
+
12
+ log = Logger("product_prepare")
13
+
14
+
15
+ class ManifestValidateError(OSError):
16
+ """Raised when validation manifest.yml failed."""
17
+
18
+ src_cwd = os.path.split(os.path.realpath(__file__))[0]
19
+
20
+ class TaskClass(Task):
21
+ def decompress_toolchain(self):
22
+ toolchain = self.config.get_product_config("toolchain")
23
+ if not toolchain:
24
+ # todo:toolchain成为强制配置项
25
+ return
26
+ file = toolchain.get("file")
27
+ strip = toolchain.get("strip_components", 0)
28
+ self.waitfile(file, 10)
29
+ compiler_file = os.path.join(self.config.compiler_path, "lbkit_cache")
30
+ _, hash = DownloadFlag.read(compiler_file)
31
+ if hash:
32
+ return
33
+ # 可能标记不匹配,所以尝试删除目录后重新创建
34
+ shutil.rmtree(self.config.compiler_path)
35
+ os.makedirs(self.config.compiler_path)
36
+ cmd = f"tar -xf {file} -C {self.config.compiler_path}"
37
+ if strip:
38
+ cmd += f" --strip-components={strip}"
39
+ self.exec(cmd)
40
+ sha256 = self.tools.file_digest_sha256(file)
41
+ DownloadFlag.create(compiler_file, file, sha256)
42
+
43
+ def get_conan_profile(self):
44
+ profile = self.config.get_product_config("toolchain/profile")
45
+ if profile:
46
+ file = profile.get("file")
47
+ name = profile.get("name")
48
+ else:
49
+ file = self.get_manifest_config("metadata/profile")
50
+ name = "litebmc"
51
+ self.waitfile(file)
52
+ return file, name
53
+
54
+
55
+ def load_conan_profile(self):
56
+ profile, name = self.get_conan_profile()
57
+ log.info("Copy profile %s", profile)
58
+ profiles_dir = os.path.expanduser("~/.conan2/profiles")
59
+ if not os.path.isdir(profiles_dir):
60
+ cmd = "conan profile detect -f"
61
+ self.exec(cmd, ignore_error=True)
62
+ dst_profile = os.path.join(profiles_dir, name)
63
+ with open(dst_profile, "w+") as dst_fp:
64
+ src_fd = open(profile, "r")
65
+ template = Template(src_fd.read())
66
+ src_fd.close()
67
+ content = template.safe_substitute(compiler_path=self.config.compiler_path)
68
+ dst_fp.write(content)
69
+
70
+ with open(dst_profile, "r") as fp:
71
+ profile_data = jinja2.Template(fp.read()).render()
72
+ parser = configparser.ConfigParser()
73
+ parser.read_string(profile_data)
74
+ strip = "strip"
75
+ if parser.has_option("buildenv", "STRIP"):
76
+ strip = parser.get("buildenv", "STRIP")
77
+ path = ""
78
+ if parser.has_option("buildenv", "PATH+"):
79
+ path = parser.get("buildenv", "PATH+")
80
+ if path.startswith("(path)"):
81
+ path = path[6:]
82
+ elif parser.has_option("buildenv", "PATH"):
83
+ path = parser.get("buildenv", "PATH")
84
+ if path.startswith("(path)"):
85
+ path = path[6:]
86
+ self.config.strip = os.path.join(path, strip)
87
+
88
+ def run(self):
89
+ """任务入口"""
90
+ self.decompress_toolchain()
91
+ """检查manifest文件是否满足schema格式描述"""
92
+ self.config.load_manifest()
93
+ self.load_conan_profile()
94
+
95
+ if __name__ == "__main__":
96
+ config = Config()
97
+ build = TaskClass(config, "test")
98
+ build.run()
@@ -157,5 +157,5 @@ class TaskClass(Task):
157
157
 
158
158
  if __name__ == "__main__":
159
159
  config = Config()
160
- build = TaskClass(config)
160
+ build = TaskClass(config, "test")
161
161
  build.run()
@@ -0,0 +1,129 @@
1
+ """环境准备"""
2
+ import os
3
+ import requests
4
+ import shutil
5
+ import time
6
+ import traceback
7
+ from functools import partial
8
+
9
+ from lbkit.tasks.config import Config
10
+ from lbkit.tasks.task import Task
11
+ from lbkit.log import Logger
12
+ from lbkit.tools import Tools
13
+ from multiprocessing.pool import Pool, ApplyResult
14
+ from lbkit.misc import DownloadFlag
15
+
16
+ log = Logger("build_image")
17
+
18
+ src_cwd = os.path.split(os.path.realpath(__file__))[0]
19
+
20
+ class DownloadTask():
21
+ def __init__(self, record, dst_dir):
22
+ self.url = record.get("url")
23
+ self.file = record.get("file")
24
+ self.dst = os.path.join(dst_dir, self.file)
25
+ self.dst = os.path.realpath(self.dst)
26
+ if not self.dst.startswith(dst_dir):
27
+ raise Exception("Download {self.file} failed because file contain relative paths")
28
+ dir = os.path.dirname(self.dst)
29
+ if not os.path.isdir(dir):
30
+ os.makedirs(dir)
31
+ self.sha256 = record.get("sha256")
32
+ self.verify = record.get("verify", True)
33
+
34
+ def start(self):
35
+ last = time.time()
36
+ if os.path.isfile(self.dst):
37
+ calc_sha = Tools.file_digest_sha256(self.dst)
38
+ # 需要校验hash且hash不一致时删除文件
39
+ if self.sha256 != "any":
40
+ if self.sha256 != calc_sha:
41
+ os.unlink(self.dst)
42
+ else:
43
+ return
44
+ else:
45
+ url, hash = DownloadFlag.read(self.dst)
46
+ # flash标志文件记录的url、hash一致时无需重复下载
47
+ if url == self.url and hash == calc_sha:
48
+ return
49
+ print(f"Start downloading {self.file} from {self.url}")
50
+ DownloadFlag.clean(self.dst)
51
+ req = requests.get(self.url, stream=True, verify=self.verify, timeout=30)
52
+ req.raise_for_status()
53
+ total_size = int(req.headers.get('content-length', 0))
54
+ total_down = 0
55
+ fp = open(self.dst, 'wb')
56
+ for chunk in req.iter_content(chunk_size=16384):
57
+ if chunk:
58
+ fp.write(chunk)
59
+ total_down += len(chunk)
60
+ now = time.time()
61
+ # 每30秒打印一次进度
62
+ if now - last > 30:
63
+ print(f"File {self.dst} is downloading, downloaded {total_down} / {total_size} ")
64
+ last = now
65
+ fp.close()
66
+ calc_sha = Tools.file_digest_sha256(self.dst)
67
+ if self.sha256 != "any" and calc_sha != self.sha256:
68
+ os.unlink(self.dst)
69
+ raise Exception(f"File {self.file} downloaded but sha256 not match, need: {self.sha256}, get: {calc_sha}")
70
+ DownloadFlag.create(self.dst, self.url, calc_sha)
71
+
72
+
73
+ def download_filed(error, pool: Pool):
74
+ print(f"download file failed, error: {str(error)}")
75
+ pool.terminate()
76
+
77
+
78
+ class TaskClass(Task):
79
+ def run(self):
80
+ records = self.config.get_manifest_config("download", [])
81
+ if not records:
82
+ return
83
+ tasks: dict[str, DownloadTask] = {}
84
+ temps: list[DownloadTask] = []
85
+ for rec in records:
86
+ task = DownloadTask(rec, self.config.download_path)
87
+ temps.append(task)
88
+ if not os.path.isfile(task.dst):
89
+ with open(task.dst, "w+") as _:
90
+ pass
91
+ for task in temps:
92
+ if not task.url.startswith("file://"):
93
+ if tasks.get(task.file):
94
+ raise Exception(f"manifest.yml configuration error, file {task.file} repeatedly")
95
+ tasks[task.file] = task
96
+ continue
97
+ DownloadFlag.clean(task.dst)
98
+ src = task.url[7:]
99
+ # 源文件不存在
100
+ if not os.path.isfile(src):
101
+ raise Exception(f"Download failed, file {task.url} not exist")
102
+ if os.path.isfile(task.dst):
103
+ os.unlink(task.dst)
104
+ shutil.copyfile(src, task.dst)
105
+ calc_hash = self.tools.file_digest_sha256(task.dst)
106
+ DownloadFlag.create(task.dst, task.url, calc_hash)
107
+ pool = Pool()
108
+ results: list[(ApplyResult, DownloadTask)] = []
109
+ for _, task in tasks.items():
110
+ ec = partial(download_filed, pool=pool)
111
+ result = pool.apply_async(task.start, error_callback=ec)
112
+ results.append((result, task))
113
+ pool.close()
114
+ pool.join()
115
+ for result in results:
116
+ if not result[0].ready():
117
+ raise Exception(f"Download file {result[1].file} failed")
118
+ try:
119
+ result[0].get()
120
+ except:
121
+ log.error("Download with exception")
122
+ log.error(traceback.print_exc)
123
+ return -1
124
+
125
+
126
+ if __name__ == "__main__":
127
+ config = Config()
128
+ build = TaskClass(config, "test")
129
+ build.run()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lbkit
3
- Version: 0.8.5
3
+ Version: 0.8.6
4
4
  Summary: Tools provided by litebmc.com
5
5
  Home-page: https://www.litebmc.com
6
6
  Author: xuhj@litebmc.com
@@ -47,8 +47,8 @@ lbkit/tasks/task_build_image.py
47
47
  lbkit/tasks/task_build_manifest.py
48
48
  lbkit/tasks/task_build_prepare.py
49
49
  lbkit/tasks/task_build_rootfs.py
50
+ lbkit/tasks/task_download.py
50
51
  lbkit/tasks/task_test.py
51
- lbkit/tasks/test.py
52
52
  lbkit/tasks/template/conanfile.py.mako
53
53
  lbkit/tasks/template/rootfs.py.mako
54
54
  lbkit/utils/__init__.py
@@ -57,4 +57,5 @@ lbkit/utils/images/__init__.py
57
57
  lbkit/utils/images/emmc.py
58
58
  test/__init__.py
59
59
  test/test_codegen.py
60
+ test/test_config.py
60
61
  test/test_helper.py
@@ -0,0 +1,66 @@
1
+ import unittest
2
+ from lbkit.tasks.config import Config
3
+
4
+ class TestConfig(unittest.TestCase):
5
+ def test_config_merge_config(self):
6
+ src = "123"
7
+ dst = "abc"
8
+ out = Config.merge_cfg(dst, src)
9
+ self.assertEqual(out, src)
10
+ src = None
11
+ dst = "abc"
12
+ out = Config.merge_cfg(dst, src)
13
+ self.assertEqual(out, dst)
14
+ src = "123"
15
+ dst = None
16
+ out = Config.merge_cfg(dst, src)
17
+ self.assertEqual(out, src)
18
+ src = ["123"]
19
+ dst = None
20
+ out = Config.merge_cfg(dst, src)
21
+ self.assertEqual(out, src)
22
+ src = ["123"]
23
+ dst = ["abc"]
24
+ out = Config.merge_cfg(dst, src)
25
+ self.assertEqual(out, src)
26
+ src = []
27
+ dst = ["abc"]
28
+ out = Config.merge_cfg(dst, src)
29
+ src = None
30
+ dst = ["abc"]
31
+ out = Config.merge_cfg(dst, src)
32
+ src = None
33
+ dst = []
34
+ out = Config.merge_cfg(dst, src)
35
+ self.assertEqual(out, dst)
36
+ src = {"key1": "val1"}
37
+ dst = {"key1": "val2"}
38
+ out = Config.merge_cfg(dst, src)
39
+ self.assertEqual(out, src)
40
+ src = {"key1": "val1", "key2": [123, 234]}
41
+ dst = {"key1": "val2"}
42
+ out = Config.merge_cfg(dst, src)
43
+ self.assertEqual(out, src)
44
+ src = {"key2": [123, 234]}
45
+ dst = {"key1": "val2"}
46
+ out = Config.merge_cfg(dst, src)
47
+ self.assertEqual(out, {"key1": "val2", "key2": [123, 234]})
48
+ src = {"key2": [123, 234]}
49
+ dst = {"key1": "val2", "key2": [456]}
50
+ out = Config.merge_cfg(dst, src)
51
+ self.assertEqual(out, {"key1": "val2", "key2": [123, 234]})
52
+ src = {"key2": [123, 234]}
53
+ dst = {"key1": "val2", "key2": []}
54
+ out = Config.merge_cfg(dst, src)
55
+ self.assertEqual(out, {"key1": "val2", "key2": [123, 234]})
56
+ src = {"key2": [123, 234]}
57
+ dst = {"key1": "val2", "key2": None}
58
+ out = Config.merge_cfg(dst, src)
59
+ self.assertEqual(out, {"key1": "val2", "key2": [123, 234]})
60
+ src = {"key2": None}
61
+ dst = {"key1": "val2", "key2": []}
62
+ out = Config.merge_cfg(dst, src)
63
+ self.assertEqual(out, {"key1": "val2", "key2": []})
64
+
65
+ if __name__ == "__main__":
66
+ unittest.main()
@@ -1,2 +0,0 @@
1
-
2
- __version__ = '0.8.5'
@@ -1,70 +0,0 @@
1
- """任务基础类"""
2
- import importlib
3
- import traceback
4
- from multiprocessing import Process
5
- from lbkit.log import Logger
6
- from lbkit.tools import Tools
7
- from lbkit.misc import load_yml_with_json_schema_validate
8
-
9
- from lbkit.tasks.config import Config
10
-
11
- class ManifestValidateError(OSError):
12
- """Raised when validation manifest.yml failed."""
13
-
14
- class Task(Process):
15
- """任务基础类,提供run和install默认实现以及其它基础该当"""
16
- def __init__(self, config: Config, name: str):
17
- super().__init__()
18
- self.log: Logger = Logger("task")
19
- self.tools: Tools = Tools(name)
20
- self.config: Config = config
21
- self.name = name
22
-
23
- def install(self):
24
- """安装任务"""
25
- self.log.info("install...........")
26
-
27
- def exec(self, cmd: str, verbose=False, ignore_error = False, sensitive=False, log_prefix="", **kwargs):
28
- kwargs["uptrace"] = kwargs.get("uptrace", 0) + 1
29
- return self.tools.exec(cmd, verbose, ignore_error, sensitive, log_prefix, **kwargs)
30
-
31
- def pipe(self, cmds: list[str], ignore_error=False, out_file = None, **kwargs):
32
- kwargs["uptrace"] = kwargs.get("uptrace", 0) + 1
33
- self.tools.pipe(cmds, ignore_error, out_file, **kwargs)
34
-
35
- def exec_easy(self, cmd, ignore_error=False, **kwargs):
36
- kwargs["uptrace"] = kwargs.get("uptrace", 0) + 1
37
- return self.tools.run(cmd, ignore_error, **kwargs)
38
-
39
- def do_hook(self, path):
40
- """执行任务钓子,用于定制化"""
41
- try:
42
- module = importlib.import_module(path)
43
- except TypeError:
44
- self.log.info("Load module(%s) failed, skip", path, uptrace=2)
45
- return
46
- self.log.info(f"load hook: {path}", uptrace=2)
47
- hook = module.TaskHook(self.config, "do_hook")
48
- hook.run()
49
-
50
- def get_manifest_config(self, key: str, default=None):
51
- return self.config.get_manifest_config(key, default)
52
-
53
- def load_manifest(self):
54
- """加载manifest.yml并验证schema文件"""
55
- return self.config.load_manifest()
56
-
57
- def deal_conf(self, config_dict):
58
- """
59
- 处理每个Task的私有配置"work_config"
60
- 当work类有set_xxx类方法时,则可以在target文件中配置xxx
61
- """
62
- if not config_dict:
63
- return
64
- for conf in config_dict:
65
- try:
66
- exist = getattr(self, f"set_{conf}")
67
- val = config_dict.get(conf)
68
- exist(val)
69
- except Exception as e:
70
- raise Exception(f"无效配置: {conf}, {e}") from e
@@ -1,59 +0,0 @@
1
- """环境准备"""
2
- import os
3
- import shutil
4
- import jinja2
5
- import configparser
6
- from lbkit.tasks.config import Config
7
- from lbkit.tasks.task import Task
8
- from lbkit.log import Logger
9
-
10
- log = Logger("product_prepare")
11
-
12
-
13
- class ManifestValidateError(OSError):
14
- """Raised when validation manifest.yml failed."""
15
-
16
- src_cwd = os.path.split(os.path.realpath(__file__))[0]
17
-
18
- class TaskClass(Task):
19
- def load_conan_profile(self):
20
- profile = self.get_manifest_config("metadata/profile")
21
- if not os.path.isfile(profile):
22
- raise FileNotFoundError(f"profile {profile} not found")
23
- log.info("Copy profile %s", profile)
24
- profiles_dir = os.path.expanduser("~/.conan2/profiles")
25
- if not os.path.isdir(profiles_dir):
26
- cmd = "conan profile detect -f"
27
- self.exec(cmd, ignore_error=True)
28
- dst_profile = os.path.join(profiles_dir, os.path.basename(profile))
29
- if os.path.isdir(profiles_dir):
30
- shutil.copyfile(profile, dst_profile, follow_symlinks=False)
31
-
32
- with open(dst_profile, "r") as fp:
33
- profile_data = jinja2.Template(fp.read()).render()
34
- parser = configparser.ConfigParser()
35
- parser.read_string(profile_data)
36
- strip = "strip"
37
- if parser.has_option("buildenv", "STRIP"):
38
- strip = parser.get("buildenv", "STRIP")
39
- path = ""
40
- if parser.has_option("buildenv", "PATH+"):
41
- path = parser.get("buildenv", "PATH+")
42
- if path.startswith("(path)"):
43
- path = path[6:]
44
- elif parser.has_option("buildenv", "PATH"):
45
- path = parser.get("buildenv", "PATH")
46
- if path.startswith("(path)"):
47
- path = path[6:]
48
- self.config.strip = os.path.join(path, strip)
49
-
50
- def run(self):
51
- """任务入口"""
52
- """检查manifest文件是否满足schema格式描述"""
53
- self.config.load_manifest()
54
- self.load_conan_profile()
55
-
56
- if __name__ == "__main__":
57
- config = Config()
58
- build = TaskClass(config)
59
- build.run()
@@ -1,40 +0,0 @@
1
- from multiprocessing import Pool
2
- from multiprocessing.pool import ApplyResult
3
- import time
4
- from functools import partial
5
-
6
- def square(number):
7
- """计算一个数的平方"""
8
- print(f"计算 {number} 的平方")
9
- time.sleep(1) # 模拟耗时操作
10
- if number % 10 == 9:
11
- raise Exception("sssssssssss")
12
- # return number * number
13
-
14
- def task_error(pool, number, error):
15
- print(f"Work {number} exist with error: {error}")
16
- pool.terminate()
17
-
18
- if __name__ == "__main__":
19
- # 创建任务并等待完成
20
- results: dict[str, ApplyResult] = {}
21
- pool = Pool(3)
22
- for work in range(100):
23
- error_cb = partial(task_error, pool, work)
24
- result = pool.apply_async(square, args=(work, ), error_callback=error_cb)
25
- results[work] = result
26
- # 检查任务结果
27
- pool.close()
28
- pool.join()
29
- # print("xxxxxxxxxxxxxxxxxx")
30
- # time.sleep(100)
31
- for number, result in results.items():
32
- try:
33
- if not result.ready():
34
- succ = False
35
- else:
36
- result.get()
37
- except Exception as e:
38
- print(f"任务{number}执行异常: {str(e)}")
39
- succ = False
40
-
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