lbkit 0.5.9__tar.gz → 0.5.10__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 (53) hide show
  1. {lbkit-0.5.9/lbkit.egg-info → lbkit-0.5.10}/PKG-INFO +2 -1
  2. lbkit-0.5.10/lbkit/__init__.py +2 -0
  3. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/cli.py +3 -0
  4. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/idf_interface.py +2 -4
  5. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/component/build.py +8 -9
  6. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/errors.py +9 -0
  7. lbkit-0.5.10/lbkit/integration/build_image.py +85 -0
  8. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/integration/build_manifest.py +27 -12
  9. lbkit-0.5.10/lbkit/integration/build_prepare.py +56 -0
  10. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/integration/build_rootfs.py +31 -47
  11. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/integration/config.py +2 -2
  12. lbkit-0.5.10/lbkit/integration/task.py +63 -0
  13. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/misc.py +2 -1
  14. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/tools.py +54 -6
  15. {lbkit-0.5.9 → lbkit-0.5.10/lbkit.egg-info}/PKG-INFO +2 -1
  16. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit.egg-info/SOURCES.txt +1 -0
  17. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit.egg-info/requires.txt +1 -0
  18. {lbkit-0.5.9 → lbkit-0.5.10}/setup.py +1 -1
  19. lbkit-0.5.9/lbkit/__init__.py +0 -2
  20. lbkit-0.5.9/lbkit/integration/build_prepare.py +0 -89
  21. lbkit-0.5.9/lbkit/integration/task.py +0 -126
  22. {lbkit-0.5.9 → lbkit-0.5.10}/AUTHORS +0 -0
  23. {lbkit-0.5.9 → lbkit-0.5.10}/LICENSE +0 -0
  24. {lbkit-0.5.9 → lbkit-0.5.10}/MANIFEST.in +0 -0
  25. {lbkit-0.5.9 → lbkit-0.5.10}/README.md +0 -0
  26. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/ci_robot/__init__.py +0 -0
  27. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/ci_robot/gitee.py +0 -0
  28. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/__init__.py +0 -0
  29. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/codegen.py +0 -0
  30. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/ctype_defination.py +0 -0
  31. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/renderer.py +0 -0
  32. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/client.c.mako +0 -0
  33. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/client.h.mako +0 -0
  34. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/interface.c.mako +0 -0
  35. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/interface.introspect.xml.mako +0 -0
  36. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/public.c.mako +0 -0
  37. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/public.h.mako +0 -0
  38. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/server.c.mako +0 -0
  39. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/codegen/template/server.h.mako +0 -0
  40. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/component/__init__.py +0 -0
  41. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/component/arg_parser.py +0 -0
  42. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/component/template/conanbase.mako +0 -0
  43. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/component/template/deploy.mako +0 -0
  44. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/component/test.py +0 -0
  45. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/helper.py +0 -0
  46. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/integration/__init__.py +0 -0
  47. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/integration/template/conanfile.py.mako +0 -0
  48. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/lbkit.py +0 -0
  49. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit/log.py +0 -0
  50. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit.egg-info/dependency_links.txt +0 -0
  51. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit.egg-info/entry_points.txt +0 -0
  52. {lbkit-0.5.9 → lbkit-0.5.10}/lbkit.egg-info/top_level.txt +0 -0
  53. {lbkit-0.5.9 → lbkit-0.5.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lbkit
3
- Version: 0.5.9
3
+ Version: 0.5.10
4
4
  Summary: Tools provided by litebmc.com
5
5
  Home-page: https://www.litebmc.com
6
6
  Author: xuhj@litebmc.com
@@ -20,5 +20,6 @@ Requires-Dist: conan==2.1
20
20
  Requires-Dist: requests
21
21
  Requires-Dist: gitpython
22
22
  Requires-Dist: inflection
23
+ Requires-Dist: meson==1.4.0
23
24
 
24
25
  build and code generate tools
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = '0.5.10'
@@ -13,6 +13,7 @@ from lbkit.component.test import TestComponent
13
13
  from lbkit.integration.build_manifest import BuildManifest
14
14
  from lbkit.integration.build_rootfs import BuildRootfs
15
15
  from lbkit.integration.build_prepare import BuildPrepare
16
+ from lbkit.integration.build_image import BuildImage
16
17
  from lbkit.component.arg_parser import ArgParser
17
18
  from lbkit.integration.config import Config
18
19
  from lbkit.ci_robot.gitee import Gitee
@@ -93,6 +94,8 @@ class Command(object):
93
94
  build.run()
94
95
  build = BuildRootfs(cfg, "create_rootfs")
95
96
  build.run()
97
+ build = BuildImage(cfg, "create_image")
98
+ build.run()
96
99
 
97
100
  def gitee(self, *args):
98
101
  """
@@ -5,7 +5,7 @@ import hashlib
5
5
  from lbkit.log import Logger
6
6
  from lbkit.codegen.renderer import Renderer
7
7
  from lbkit.codegen.ctype_defination import CTYPE_OBJS, RefObjArrayValidator, RefObjValidator
8
- from lbkit.misc import validate_yml_with_json_schema
8
+ from lbkit.misc import load_yml_with_json_schema_validate
9
9
 
10
10
  log = Logger("gen_interface")
11
11
 
@@ -786,10 +786,8 @@ class IdfInterface(IdfInterfaceBase):
786
786
  def load_elements(self):
787
787
  # 使用schema校验数据,确保IDF文件符合格式要求,减少程序处理过程中的异常处理
788
788
  # 验证失败时抛异常,此处不用处理,由外层处理
789
- validate_yml_with_json_schema(self.file, "/usr/share/litebmc/schema/idf.v1.json")
789
+ idf = load_yml_with_json_schema_validate(self.file, "/usr/share/litebmc/schema/idf.v1.json")
790
790
  log.info(f"validate {self.file} successfully")
791
- with open(self.file, "r") as fp:
792
- idf = yaml.load(fp, yaml.FullLoader)
793
791
  self.version = idf.get("version")
794
792
  self.description = idf.get("description", "")
795
793
  # 别名
@@ -10,7 +10,7 @@ from jsonschema import validate, ValidationError
10
10
  from git import Repo
11
11
  from git.exc import InvalidGitRepositoryError
12
12
  from mako.lookup import TemplateLookup
13
- from lbkit.misc import Color, validate_yml_with_json_schema, get_json_schema_file, load_json_schema
13
+ from lbkit.misc import Color, load_yml_with_json_schema_validate, get_json_schema_file, load_json_schema
14
14
  from lbkit import errors
15
15
  from lbkit.codegen.codegen import CodeGen
16
16
  from lbkit.tools import Tools
@@ -32,7 +32,7 @@ class DeployComponent():
32
32
  return
33
33
  cmd = f"conan cache path {self.package_ref}:{self.package_id}"
34
34
  package_folder = tools.run(cmd).stdout.strip()
35
- log.info(f">>copy {package_folder} to {self.rootfs_dir}")
35
+ log.info(f">>>> deploy {self.package_ref}")
36
36
  cmd = f"cp -rT {package_folder}/ {self.rootfs_dir}"
37
37
  tools.exec(cmd)
38
38
 
@@ -98,13 +98,9 @@ class BuildComponent():
98
98
  if not os.path.isfile(package_yml):
99
99
  raise FileNotFoundError("metadata/package.yml文件不存在")
100
100
  # 验证失败时抛异常,此处不用处理,由外层处理
101
- validate_yml_with_json_schema(package_yml, "/usr/share/litebmc/schema/cdf.v1.json")
101
+ pkg = load_yml_with_json_schema_validate(package_yml, "/usr/share/litebmc/schema/cdf.v1.json")
102
102
  log.info(f"validate {package_yml} successfully")
103
103
 
104
- # 加载package.yml文件
105
- fp = open(package_yml, "r")
106
- pkg = yaml.safe_load(fp)
107
- fp.close()
108
104
  self.user = pkg.get("user")
109
105
  if self.user is None:
110
106
  raise errors.PackageConfigException("metadata/package.yml未正确配置user字段")
@@ -193,13 +189,16 @@ class BuildComponent():
193
189
  os.chdir(dir)
194
190
 
195
191
  cmd = self.base_cmd.replace(self.package, "")
196
- cmd = f"conan graph info . {cmd} --build=missing -f json --filter=ref --filter=package_id"
192
+ cmd = f"conan graph info . {cmd} --build=missing -f json --filter=ref --filter=package_id --filter=context"
197
193
  res = tools.run(cmd).stdout.strip()
198
194
  graph = json.loads(res)
199
195
  nodes = graph.get("graph", {}).get("nodes", {})
200
196
  for id, info in nodes.items():
201
197
  ref = info.get("ref")
202
198
  id = info.get("package_id")
199
+ context = info.get("context")
200
+ if context != "host":
201
+ continue
203
202
  dep = DeployComponent(ref, id, self.rootfs_dir)
204
203
  dep.run()
205
204
 
@@ -264,7 +263,7 @@ class BuildComponent():
264
263
  continue
265
264
  log.info(f"start validate {file} with schema {schema_file}")
266
265
  # 验证全局odf验证
267
- validate_yml_with_json_schema(file, schema_file)
266
+ load_yml_with_json_schema_validate(file, schema_file)
268
267
  odf_ok = self._validate_odf_file(file)
269
268
  if not odf_ok:
270
269
  ok = False
@@ -79,3 +79,12 @@ class OdfValidateException(Exception):
79
79
 
80
80
  def __str__(self):
81
81
  return super(OdfValidateException, self).__str__()
82
+
83
+ class DigestNotMatchError(OSError):
84
+ """Raised when source and destination are the same file."""
85
+
86
+ class ExtractRootfsTarFileError(OSError):
87
+ """Raised when extract rootfs.tar.gz failed."""
88
+
89
+ class PermissionFormatError(OSError):
90
+ """permission.ini with format."""
@@ -0,0 +1,85 @@
1
+ """环境准备"""
2
+ import os
3
+ import tempfile
4
+ import tarfile
5
+ import shutil
6
+ from lbkit.integration.config import Config
7
+ from lbkit.integration.task import Task
8
+ from lbkit.log import Logger
9
+ from lbkit import errors
10
+
11
+ log = Logger("build_image")
12
+
13
+ src_cwd = os.path.split(os.path.realpath(__file__))[0]
14
+
15
+ class BuildImage(Task):
16
+ def download_uboot(self, img_file):
17
+ tar_file = os.path.join(self.config.download_path, "uboot.tar.gz")
18
+
19
+ url = self.get_manifest_config("components/uboot/url")
20
+ sha256 = self.get_manifest_config("components/uboot/sha256")
21
+ self.tools.download(url, tar_file, sha256)
22
+ tar = tarfile.open(tar_file)
23
+ members = tar.getmembers()
24
+ for member in members:
25
+ if not member.isfile():
26
+ continue
27
+ if member.name != "uboot.bin" and member.name != "u-boot.bin":
28
+ continue
29
+ io = tar.extractfile(member)
30
+ fp = open(img_file, "wb+")
31
+ while True:
32
+ buf = io.read(65536)
33
+ if len(buf) == 0:
34
+ break
35
+ fp.write(buf)
36
+ fp.close()
37
+ return
38
+ raise errors.ExtractRootfsTarFileError("Extract failed, the u-boot.bin can't be found in " + url)
39
+
40
+ def download_kernel(self, img_file):
41
+ tar_file = os.path.join(self.config.download_path, "kernel.tar.gz")
42
+
43
+ url = self.get_manifest_config("components/kernel/url")
44
+ sha256 = self.get_manifest_config("components/kernel/sha256")
45
+ self.tools.download(url, tar_file, sha256)
46
+ tar = tarfile.open(tar_file)
47
+ members = tar.getmembers()
48
+ for member in members:
49
+ if not member.isfile():
50
+ continue
51
+ if member.name != "Image":
52
+ continue
53
+ io = tar.extractfile(member)
54
+ fp = open(img_file, "wb+")
55
+ while True:
56
+ buf = io.read(65536)
57
+ if len(buf) == 0:
58
+ break
59
+ fp.write(buf)
60
+ fp.close()
61
+ return
62
+ raise errors.ExtractRootfsTarFileError("Extract failed, the Image can't be found in " + url)
63
+
64
+ def run(self):
65
+ """任务入口"""
66
+ """检查manifest文件是否满足schema格式描述"""
67
+ os.chdir(self.config.output_path)
68
+ self.download_uboot("u-boot.bin")
69
+ self.download_kernel("Image")
70
+ cmd = "qemu-system-aarch64 -M virt -cpu cortex-a57 -M virt,dumpdtb=virt.dtb"
71
+ self.exec(cmd)
72
+
73
+ cmd = 'lbpack_emmc.sh ./Image ./virt.dtb ./rootfs.img ./qemu.img'
74
+ self.exec(cmd)
75
+ cmd = 'cp /usr/share/litebmc/qemu.conf ./qemu.conf'
76
+ self.exec(cmd)
77
+ output_img = os.path.join(self.config.output_path, "litebmc_qemu.tar.gz")
78
+ cmd = f'tar -czf {output_img} -C . qemu.img u-boot.bin qemu.conf'
79
+ self.exec(cmd)
80
+ log.success(f"Create litebmc image {output_img} successfully")
81
+
82
+ if __name__ == "__main__":
83
+ config = Config()
84
+ build = BuildImage(config)
85
+ build.run()
@@ -1,6 +1,7 @@
1
1
  """应用构建任务"""
2
2
  import os
3
3
  import shutil
4
+ import json
4
5
  from mako.lookup import TemplateLookup
5
6
  from lbkit.integration.config import Config
6
7
  from lbkit.integration.task import Task
@@ -15,14 +16,13 @@ class ManifestValidateError(OSError):
15
16
  src_cwd = os.path.split(os.path.realpath(__file__))[0]
16
17
 
17
18
  class BuildManifest(Task):
18
- """根据产品配置构建所有app并将应用安装到self.config.conan_install目录"""
19
- def __init__(self, cfg: Config):
20
- super().__init__(cfg)
19
+ """根据产品配置构建所有app,记录待安装应用路径到self.config.conan_install路径"""
20
+ def __init__(self, cfg: Config, name: str):
21
+ super().__init__(cfg, name)
21
22
  self.conan_build = os.path.join(self.config.temp_path, "conan")
22
- shutil.rmtree(self.conan_build, ignore_errors=True)
23
+ if os.path.isdir(self.conan_build):
24
+ shutil.rmtree(self.conan_build)
23
25
  os.makedirs(self.conan_build)
24
- shutil.rmtree(self.config.conan_install, ignore_errors=True)
25
- os.makedirs(self.config.conan_install)
26
26
  if self.config.build_debug:
27
27
  self.conan_settings = " -s build_type=Debug"
28
28
  else:
@@ -30,11 +30,29 @@ class BuildManifest(Task):
30
30
  self.common_args = "-r " + self.config.remote
31
31
  self.common_args += " -pr:b {} -pr:h {}".format(self.config.profile_build, self.config.profile_host)
32
32
 
33
+ def deploy(self):
34
+ cmd = f"conan graph info . {self.common_args} --build=missing -f json --filter=ref --filter=package_id"
35
+ cmd += f" --filter=context"
36
+ res = self.tools.run(cmd).stdout.strip()
37
+ graph = json.loads(res)
38
+ nodes = graph.get("graph", {}).get("nodes", {})
39
+ for id, info in nodes.items():
40
+ ref = info.get("ref")
41
+ id = info.get("package_id")
42
+ context = info.get("context")
43
+ if context != "host":
44
+ continue
45
+ cmd = f"conan cache path {ref}:{id}"
46
+ package_folder = self.tools.run(cmd).stdout.strip()
47
+ self.config.conan_install.append(package_folder)
48
+
33
49
  def build_litebmc(self):
34
50
  """构建产品conan包"""
35
51
  log.info("build litebmc")
36
52
 
37
53
  manifest = self.load_manifest()
54
+ hook_name = "hook.prepare_manifest"
55
+ self.do_hook(hook_name)
38
56
  # 使用模板生成litebmc组件的配置
39
57
  lookup = TemplateLookup(directories=os.path.join(src_cwd, "template"))
40
58
  template = lookup.get_template("conanfile.py.mako")
@@ -49,7 +67,7 @@ class BuildManifest(Task):
49
67
 
50
68
  # 安装所有应用
51
69
  if self.config.from_source:
52
- cmd = "conan install . --build " + self.common_args
70
+ cmd = "conan install . --build=\"*\" " + self.common_args
53
71
  else:
54
72
  cmd = "conan install . --build=missing " + self.common_args
55
73
  cmd = cmd + self.conan_settings
@@ -59,10 +77,7 @@ class BuildManifest(Task):
59
77
  cmd = cmd + self.conan_settings
60
78
  self.exec(cmd, verbose=True)
61
79
  # 部署应用到self.config.conan_install
62
- log.info("install litebmc to %s", self.config.conan_install)
63
- cmd = "conan install . {} -d direct_deploy --deployer-folder={}".format(self.common_args, self.config.conan_install)
64
- cmd = cmd + self.conan_settings
65
- self.exec(cmd, verbose=self.config.verbose)
80
+ self.deploy()
66
81
 
67
82
  def run(self):
68
83
  """任务入口"""
@@ -71,4 +86,4 @@ class BuildManifest(Task):
71
86
  if __name__ == "__main__":
72
87
  config = Config()
73
88
  build = BuildManifest(config)
74
- build.run()
89
+ build.run()
@@ -0,0 +1,56 @@
1
+ """环境准备"""
2
+ import os
3
+ import shutil
4
+ import jinja2
5
+ import configparser
6
+ from lbkit.integration.config import Config
7
+ from lbkit.integration.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 BuildPrepare(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
+ dst_profile = os.path.join(profiles_dir, os.path.basename(profile))
26
+ if os.path.isdir(profiles_dir):
27
+ shutil.copyfile(profile, dst_profile, follow_symlinks=False)
28
+
29
+ with open(dst_profile, "r") as fp:
30
+ profile_data = jinja2.Template(fp.read()).render()
31
+ parser = configparser.ConfigParser()
32
+ parser.read_string(profile_data)
33
+ strip = "strip"
34
+ if parser.has_option("buildenv", "STRIP"):
35
+ strip = parser.get("buildenv", "STRIP")
36
+ path = ""
37
+ if parser.has_option("buildenv", "PATH+"):
38
+ path = parser.get("buildenv", "PATH+")
39
+ if path.startswith("(path)"):
40
+ path = path[6:]
41
+ elif parser.has_option("buildenv", "PATH"):
42
+ path = parser.get("buildenv", "PATH")
43
+ if path.startswith("(path)"):
44
+ path = path[6:]
45
+ self.config.strip = os.path.join(path, strip)
46
+
47
+ def run(self):
48
+ """任务入口"""
49
+ """检查manifest文件是否满足schema格式描述"""
50
+ self.load_manifest()
51
+ self.load_conan_profile()
52
+
53
+ if __name__ == "__main__":
54
+ config = Config()
55
+ build = BuildPrepare(config)
56
+ build.run()
@@ -3,20 +3,15 @@ import os
3
3
  import shutil
4
4
  import tarfile
5
5
  from lbkit.integration.config import Config
6
- from lbkit.integration.task import Task, DigestNotMatchError
6
+ from lbkit.integration.task import Task
7
7
  from lbkit.log import Logger
8
+ from lbkit import errors
8
9
 
9
10
  log = Logger("build_rootfs")
10
11
 
11
12
  src_cwd = os.path.split(os.path.realpath(__file__))[0]
12
13
  IMG_FILE = "rootfs.img"
13
14
 
14
- class ExtractRootfsTarFileError(OSError):
15
- """Raised when extract rootfs.tar.gz failed."""
16
-
17
- class PermissionFormatError(OSError):
18
- """permission.ini with format."""
19
-
20
15
  class BuildRootfs(Task):
21
16
  """构建rootfs镜像"""
22
17
  def do_permission(self, per_file: str):
@@ -32,14 +27,14 @@ class BuildRootfs(Task):
32
27
  log.debug("Permission line: %s", line)
33
28
  chunk = line.split()
34
29
  if len(chunk) < 5:
35
- raise PermissionFormatError(f"Permission format with error, line: {line}")
30
+ raise errors.PermissionFormatError(f"Permission format with error, line: {line}")
36
31
  if not chunk[0].startswith("/"):
37
- raise PermissionFormatError(f"Permission file error, must begin with \"/\", get: {chunk[0]}")
32
+ raise errors.PermissionFormatError(f"Permission file error, must begin with \"/\", get: {chunk[0]}")
38
33
  if not chunk[3].isnumeric() or not chunk[4].isnumeric():
39
- raise PermissionFormatError(f"Permission uid or gid error, must is numeric, uid({chunk[3]}), gid({chunk[4]})")
34
+ raise errors.PermissionFormatError(f"Permission uid or gid error, must is numeric, uid({chunk[3]}), gid({chunk[4]})")
40
35
  chunk[0] = chunk[0].lstrip("/")
41
36
  if chunk[1] != "f" and chunk[1] != "d" and chunk[1] != "s" and chunk[1] != "r":
42
- raise PermissionFormatError(f"Permission type error, only support 'f' 's' 'd' 'r', get({chunk[1]}), ignore")
37
+ raise errors.PermissionFormatError(f"Permission type error, only support 'f' 's' 'd' 'r', get({chunk[1]}), ignore")
43
38
  if chunk[1] == "d" and not os.path.isdir(chunk[0]):
44
39
  log.error("Permission error, %s is not directory", chunk[0])
45
40
  if chunk[1] == "s" and not os.path.islink(chunk[0]):
@@ -70,21 +65,7 @@ class BuildRootfs(Task):
70
65
 
71
66
  url = self.get_manifest_config("components/rootfs/url")
72
67
  sha256 = self.get_manifest_config("components/rootfs/sha256")
73
- digest = None
74
- if os.path.isfile(rootfs_tar):
75
- digest = self.file_digest_sha256(rootfs_tar)
76
- if digest != sha256:
77
- art_user = os.environ.get("ART_USER", None)
78
- art_token = os.environ.get("ART_TOKEN", None)
79
- if art_user is None or art_token is None:
80
- log.info("The environment variables ART_USER and ART_TOKEN are not set, the file is downloaded directily")
81
- cmd = f"curl -fL \"{url}\" -o {rootfs_tar}"
82
- else:
83
- cmd = f"curl -fL -u {art_user}:{art_token} \"{url}\" -o {rootfs_tar}"
84
- self.exec(cmd, verbose=True, sensitive=True)
85
- digest = self.file_digest_sha256(rootfs_tar)
86
- if digest != sha256:
87
- raise DigestNotMatchError(f"File {rootfs_tar} with sha256 error, need: {sha256}, get: {digest}")
68
+ self.tools.download(url, rootfs_tar, sha256)
88
69
  tar = tarfile.open(rootfs_tar)
89
70
  members = tar.getmembers()
90
71
  for member in members:
@@ -97,11 +78,11 @@ class BuildRootfs(Task):
97
78
  while True:
98
79
  buf = io.read(65536)
99
80
  if len(buf) == 0:
100
- break;
81
+ break
101
82
  fp.write(buf)
102
83
  fp.close()
103
84
  return
104
- raise ExtractRootfsTarFileError("Extract failed, the rootfs.img can't be found in rootfs.tar")
85
+ raise errors.ExtractRootfsTarFileError("Extract failed, the rootfs.img can't be found in rootfs.tar")
105
86
 
106
87
  def merge_rootfs(self, rootfs_img):
107
88
  """将产品依赖的所有组件安装到rootfs镜像中"""
@@ -117,13 +98,9 @@ class BuildRootfs(Task):
117
98
  # 切换到rootfs挂载目录
118
99
  os.chdir(mnt_path)
119
100
  log.info("Copy customization rootfs......")
120
- deploy_dir = os.path.join(self.config.conan_install, "direct_deploy")
121
- for component in os.listdir(deploy_dir):
122
- src_dir = os.path.join(deploy_dir, component)
123
- if not os.path.isdir(src_dir):
124
- continue
101
+ for src_dir in self.config.conan_install:
125
102
  log.info("copy %s to %s", src_dir, mnt_path)
126
- cmd = f"rsync -auHK {src_dir}/ {mnt_path}"
103
+ cmd = f"rsync -aHK {src_dir}/ {mnt_path}"
127
104
  self.exec(cmd)
128
105
  per_file = os.path.join(src_dir, "permissions")
129
106
  self.do_permission(per_file)
@@ -131,7 +108,7 @@ class BuildRootfs(Task):
131
108
  # copy product self-owned rootfs
132
109
  product_rootfs = os.path.join(self.config.work_dir, "rootfs")
133
110
  if os.path.isdir(product_rootfs):
134
- cmd = f"rsync -auKH {product_rootfs}/ {mnt_path}"
111
+ cmd = f"rsync -aKH {product_rootfs}/ {mnt_path}"
135
112
  self.exec(cmd)
136
113
  per_file = os.path.join(product_rootfs, "permissions")
137
114
  self.do_permission(per_file)
@@ -142,18 +119,21 @@ class BuildRootfs(Task):
142
119
  self.do_hook(hook_name)
143
120
 
144
121
  # 清理冗余文件
145
- cmd = "rm -rf " + os.path.join(self.config.mnt_path, "include")
146
- self.exec(cmd)
122
+ inc_dir = os.path.join(self.config.mnt_path, "include")
123
+ if os.path.isdir(inc_dir):
124
+ cmd = "rm -rf " + inc_dir
125
+ self.exec(cmd)
147
126
  cmd = "rm " + os.path.join(self.config.mnt_path, "permissions")
148
127
  self.exec(cmd)
149
128
 
150
129
  # 清理冗余静态文件
151
- cmd = "find " + os.path.join(self.config.mnt_path, "include") + " -name *.a -type f | xargs rm -f"
152
- self.exec(cmd)
130
+ cmds = ["find " + self.config.mnt_path + " -name *.a -type f", "xargs -i{} rm {} -f"]
131
+ self.pipe(cmds)
153
132
 
154
133
  # 按manifest配置的大小调整rootfs
155
134
  strip = self.get_manifest_config("metadata/strip")
156
135
  if strip:
136
+ log.info("Start strip files")
157
137
  file_list = os.path.join(self.config.temp_path, "strip.filelist")
158
138
  unstrip_regex = [
159
139
  "*.yaml$",
@@ -172,15 +152,19 @@ class BuildRootfs(Task):
172
152
  "*.conf$"]
173
153
  cmds = ["find " + self.config.mnt_path + " -type f"]
174
154
  for unstrip in unstrip_regex:
175
- cmds.append[f"grep -vE \"{unstrip}\""]
155
+ cmds.append(f"grep -vE {unstrip}")
176
156
  self.pipe(cmds, out_file=file_list)
177
157
 
178
158
  cmds = [f"cat {file_list}", "xargs file", "grep 'not stripped'", "awk -F':' '{{print $1}}'"]
179
- cmds.append[f"xargs {self.config.strip} -s"]
180
- self.pipe(cmd)
181
-
182
- cmds = [f"find {self.config.mnt_path} -name *.a", "xargs rm"]
183
- self.pipe(cmd)
159
+ cmds.append(f"xargs -i{{}} {self.config.strip} -s {{}}")
160
+ self.pipe(cmds)
161
+
162
+ log.info("remove static libraries")
163
+ cmds = [f"find {self.config.mnt_path} -name *.a", "xargs -i{} rm {}"]
164
+ self.pipe(cmds)
165
+ log.info("remove all .fuse_hiddeng* files")
166
+ cmds = [f"find {self.config.mnt_path} -name .fuse_hidden*", "xargs -i{} rm {}"]
167
+ self.pipe(cmds)
184
168
  self.exec("umount " + mnt_path)
185
169
 
186
170
  def run(self):
@@ -190,8 +174,8 @@ class BuildRootfs(Task):
190
174
  # 任务入口
191
175
  self.download_rootfs(rootfs_img)
192
176
  self.merge_rootfs(rootfs_img)
193
- tgt_img = os.path.join(self.config.output_path, IMG_FILE)
194
- os.rename(rootfs_img, tgt_img)
177
+ os.rename(rootfs_img, self.config.rootfs_img)
178
+ log.success(f"Create image {self.config.rootfs_img} successfully")
195
179
 
196
180
  if __name__ == "__main__":
197
181
  config = Config()
@@ -41,14 +41,14 @@ class Config(object):
41
41
  self.tool_path = os.path.join(self.temp_path, "tools")
42
42
  self.download_path = os.path.join(self.temp_path, "download")
43
43
  self.rootfs_path = os.path.join(self.temp_path, "rootfs")
44
- self.conan_install = os.path.join(self.temp_path, "apps")
44
+ self.conan_install = []
45
45
  self.mnt_path = os.path.join(self.temp_path, "mnt_path")
46
+ self.rootfs_img = os.path.join(self.output_path, "rootfs.img")
46
47
  os.makedirs(self.temp_path, exist_ok=True)
47
48
  os.makedirs(self.tool_path, exist_ok=True)
48
49
  os.makedirs(self.output_path, exist_ok=True)
49
50
  os.makedirs(self.download_path, exist_ok=True)
50
51
  os.makedirs(self.rootfs_path, exist_ok=True)
51
- os.makedirs(self.conan_install, exist_ok=True)
52
52
  # 制作rootfs时需要strip镜像,所以需要单独指定stip路径
53
53
  self.strip = "strip"
54
54
 
@@ -0,0 +1,63 @@
1
+ """任务基础类"""
2
+ import shutil
3
+ import importlib
4
+ import os
5
+ import hashlib
6
+ import requests
7
+ import yaml
8
+ from multiprocessing import Process
9
+ from lbkit.log import Logger
10
+ from lbkit.tools import Tools
11
+ from lbkit.misc import load_yml_with_json_schema_validate
12
+
13
+ from lbkit.integration.config import Config
14
+
15
+ class ManifestValidateError(OSError):
16
+ """Raised when validation manifest.yml failed."""
17
+
18
+ class Task(Process):
19
+ """任务基础类,提供run和install默认实现以及其它基础该当"""
20
+ def __init__(self, config: Config, name: str):
21
+ super().__init__()
22
+ self.log: Logger = Logger("task")
23
+ self.tools: Tools = Tools(name)
24
+ self.config: Config = config
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=""):
31
+ return self.tools.exec(cmd, verbose, ignore_error, sensitive, log_prefix)
32
+
33
+ def pipe(self, cmds: list[str], ignore_error=False, out_file = None):
34
+ self.tools.pipe(cmds, ignore_error, out_file)
35
+
36
+ def run(self, cmd, ignore_error=False):
37
+ return self.tools.run(cmd, ignore_error)
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)
45
+ return
46
+ self.log.info(module)
47
+ hook = module.TaskHook(self.config, "do_hook")
48
+ hook.run()
49
+
50
+ def get_manifest_config(self, key: str):
51
+ """从manifest中读取配置"""
52
+ with open(self.config.manifest, "r", encoding="utf-8") as fp:
53
+ manifest = yaml.load(fp, yaml.FullLoader)
54
+ keys = key.split("/")
55
+ for k in keys:
56
+ manifest = manifest.get(k, None)
57
+ if manifest is None:
58
+ return None
59
+ return manifest
60
+
61
+ def load_manifest(self):
62
+ """加载manifest.yml并验证schema文件"""
63
+ return load_yml_with_json_schema_validate(self.config.manifest, "/usr/share/litebmc/schema/pdf.v1.json")
@@ -73,7 +73,7 @@ def load_json_schema(schema_file):
73
73
  tmp = fp.read()
74
74
  return json.loads(tmp)
75
75
 
76
- def validate_yml_with_json_schema(yml_file, default_json_schema_file):
76
+ def load_yml_with_json_schema_validate(yml_file, default_json_schema_file):
77
77
  """使用json schema文件校验yml_file配置文件"""
78
78
  schema_file = get_json_schema_file(yml_file, default_json_schema_file)
79
79
  if schema_file is None:
@@ -85,6 +85,7 @@ def validate_yml_with_json_schema(yml_file, default_json_schema_file):
85
85
  data = yaml.safe_load(fp)
86
86
  fp.close()
87
87
  validate(data, schema)
88
+ return data
88
89
  except ValidationError as exc:
89
90
  raise PackageConfigException(f"validate {yml_file} failed, schema file is {schema_file}, "
90
91
  f"message: {exc.message}\n"
@@ -5,7 +5,9 @@ import inspect
5
5
  import os
6
6
  import sys
7
7
  import tempfile
8
+ import requests
8
9
  import shlex
10
+ import hashlib
9
11
  from lbkit.log import Logger
10
12
  from lbkit import errors
11
13
  from lbkit import misc
@@ -51,8 +53,8 @@ class Tools(object):
51
53
  show_cmd = "(" + log_prefix + ") " + show_cmd
52
54
 
53
55
  fd = os.fdopen(os.open(self.log_name, os.O_APPEND | os.O_CREAT | os.O_RDWR), "a+")
54
- self.log.debug("{}>>>>{} {}".format(Color.GREEN, Color.RESET_ALL, show_cmd))
55
- fd.write(">>>> {}".format(show_cmd))
56
+ self.log.info("{}>>>>{} {}".format(Color.GREEN, Color.RESET_ALL, show_cmd))
57
+ fd.write(">>>> {}\n".format(show_cmd))
56
58
  if os.environ.get("VERBOSE", False):
57
59
  verbose = True
58
60
  real_cmd = self._real_command(cmd)
@@ -66,16 +68,19 @@ class Tools(object):
66
68
  fd.close()
67
69
  result.communicate()
68
70
  if result is None or result.returncode != 0:
69
- self.log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
70
- msg = f"{file}:{line} > Run command failed, cmd: {show_cmd}, error code: {result.returncode}, log: {self.log_name}"
71
+ msg = f"{file}:{line} > Run command ({show_cmd}) failed but ignore, log file is {self.log_name}"
71
72
  if not ignore_error:
73
+ self.log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
72
74
  raise errors.RunCommandException(msg)
73
- self.log.error(msg)
75
+ self.log.warn(msg)
74
76
  return result
75
77
 
76
78
  def pipe(self, cmds: list[str], ignore_error=False, out_file = None):
77
79
  if not isinstance(cmds, list):
78
80
  raise errors.ArgException("Command ({}) with type error, only list[str] can be accepted".format(cmds))
81
+ if out_file and os.path.isfile(out_file):
82
+ fp = open(out_file, "w")
83
+ fp.close()
79
84
  stdin = None
80
85
  for cmd in cmds:
81
86
  self.log.debug("{}>>>>{} {}".format(Color.GREEN, Color.RESET_ALL, cmd))
@@ -85,7 +90,7 @@ class Tools(object):
85
90
  if ret is None:
86
91
  raise errors.RunCommandException(f"Run command {real_cmd[0]} failed")
87
92
  ret.communicate()
88
- if ret is None or ret.returncode != 0:
93
+ if ret.returncode != 0:
89
94
  if ignore_error:
90
95
  self.log.info("Run command ({}) failed".format(cmd))
91
96
  return
@@ -93,6 +98,8 @@ class Tools(object):
93
98
  if stdin:
94
99
  stdin.close()
95
100
  stdin = stdout
101
+ stdin.seek(0)
102
+
96
103
  if out_file:
97
104
  stdin.seek(0)
98
105
  with open(out_file, "w+b") as fp:
@@ -105,3 +112,44 @@ class Tools(object):
105
112
  check = False if ignore_error else True
106
113
  return subprocess.run(real_cmd, capture_output=True, check=check, encoding="utf-8")
107
114
 
115
+ @staticmethod
116
+ def file_digest_sha256(filename):
117
+ """计算文件的sha256值"""
118
+ sha256 = hashlib.sha256()
119
+ fp = open(filename, "rb")
120
+ while True:
121
+ data = fp.read(65536)
122
+ if len(data) == 0:
123
+ break
124
+ sha256.update(data)
125
+ fp.close()
126
+ return sha256.hexdigest()
127
+
128
+ def download(self, url, dst_file, sha256sum = None):
129
+ """下载文件"""
130
+ self.log.info("Start download %s", url)
131
+ is_local = False
132
+ if url.startswith("file://"):
133
+ path = url[7:]
134
+ shutil.copyfile(path, dst_file)
135
+ is_local = True
136
+ if os.path.isfile(dst_file):
137
+ digest = self.file_digest_sha256(dst_file)
138
+ if sha256sum is None or digest == sha256sum:
139
+ return
140
+ if is_local:
141
+ raise errors.DigestNotMatchError(f"File {dst_file} with sha256 error, need: {sha256sum}, get: {digest}")
142
+ os.unlink(dst_file)
143
+ verify = os.environ.get("HTTPS_VERIFY", True)
144
+ if verify:
145
+ response = requests.get(url, timeout=30, verify=True)
146
+ else:
147
+ response = requests.get(url, timeout=30)
148
+ fp = open(dst_file, "wb")
149
+ fp.write(response.content)
150
+ fp.close()
151
+ digest = self.file_digest_sha256(dst_file)
152
+ if sha256sum is None or digest == sha256sum:
153
+ self.log.info("Download %s successfully", url)
154
+ return
155
+ raise errors.DigestNotMatchError(f"File {dst_file} with sha256 error, need: {sha256sum}, get: {digest}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lbkit
3
- Version: 0.5.9
3
+ Version: 0.5.10
4
4
  Summary: Tools provided by litebmc.com
5
5
  Home-page: https://www.litebmc.com
6
6
  Author: xuhj@litebmc.com
@@ -20,5 +20,6 @@ Requires-Dist: conan==2.1
20
20
  Requires-Dist: requests
21
21
  Requires-Dist: gitpython
22
22
  Requires-Dist: inflection
23
+ Requires-Dist: meson==1.4.0
23
24
 
24
25
  build and code generate tools
@@ -39,6 +39,7 @@ lbkit/component/test.py
39
39
  lbkit/component/template/conanbase.mako
40
40
  lbkit/component/template/deploy.mako
41
41
  lbkit/integration/__init__.py
42
+ lbkit/integration/build_image.py
42
43
  lbkit/integration/build_manifest.py
43
44
  lbkit/integration/build_prepare.py
44
45
  lbkit/integration/build_rootfs.py
@@ -7,3 +7,4 @@ conan==2.1
7
7
  requests
8
8
  gitpython
9
9
  inflection
10
+ meson==1.4.0
@@ -19,7 +19,7 @@ setup(
19
19
  description="Tools provided by litebmc.com",
20
20
  long_description="build and code generate tools",
21
21
  long_description_content_type="text/markdown",
22
- install_requires=["pyyaml", "colorama", "mako", "node-semver==0.6.1", "jsonschema", "conan==2.1", "requests", "gitpython", "inflection"],
22
+ install_requires=["pyyaml", "colorama", "mako", "node-semver==0.6.1", "jsonschema", "conan==2.1", "requests", "gitpython", "inflection", "meson==1.4.0"],
23
23
  url="https://www.litebmc.com",
24
24
  packages=find_packages(),
25
25
  include_package_data=True,
@@ -1,2 +0,0 @@
1
-
2
- __version__ = '0.5.9'
@@ -1,89 +0,0 @@
1
- """环境准备"""
2
- import os
3
- import shutil
4
- import tarfile
5
- from lbkit.integration.config import Config
6
- from lbkit.integration.task import Task
7
- from lbkit.log import Logger
8
-
9
- log = Logger("product_prepare")
10
-
11
-
12
- class ManifestValidateError(OSError):
13
- """Raised when validation manifest.yml failed."""
14
-
15
- src_cwd = os.path.split(os.path.realpath(__file__))[0]
16
-
17
- class BuildPrepare(Task):
18
- def check_manifest(self):
19
- """检查manifest文件是否满足schema格式描述"""
20
- self.load_manifest()
21
-
22
- def install_tools(self):
23
- url = self.get_manifest_config("tools/compiler/url")
24
- sha256 = self.get_manifest_config("tools/compiler/sha256")
25
- path = self.get_manifest_config("tools/compiler/path")
26
- compiler_path = path if path else os.path.join(self.config.tool_path, "linaro")
27
-
28
- need_untar = True
29
-
30
- # 下载工具
31
- tool_file = os.path.join(self.config.download_path, "gcc.tar.bz2")
32
- # 先检查安装路径下是否存在sha256.lock文件,如果存在且值相同的不处理
33
- sha256_flag = os.path.join(compiler_path, "sha256.lock")
34
- if os.path.isfile(sha256_flag):
35
- fp = open(sha256_flag, "r", encoding="utf-8")
36
- lock_sha256 = fp.readline()
37
- fp.close
38
- if lock_sha256 == sha256:
39
- log.info("%s exist, skip untar", sha256_flag)
40
- need_untar = False
41
- # 如果需要解压值url或sha256为空时异常
42
- if need_untar and url is None or sha256 is None:
43
- raise ManifestValidateError(self.config.manifest + " with error, tools.compiler.url or tools.compiler.sha256 is None")
44
-
45
- # 解压到tools/linaro目录
46
- self.download_url(url, tool_file, sha256)
47
- tar = tarfile.open(tool_file)
48
- top_dir = tar.next().name
49
- if (need_untar):
50
- log.info("Start untar %s", tool_file)
51
- shutil.rmtree(compiler_path, ignore_errors=True)
52
- os.makedirs(compiler_path, exist_ok=True)
53
- tar.extractall(compiler_path)
54
- # 加了工具的目录名生成真实的工具目录地址
55
- compiler_path = os.path.join(compiler_path, top_dir)
56
- self.modify_profile(compiler_path)
57
- with open(sha256_flag, "w+", encoding="utf-8") as fp:
58
- fp.write(sha256)
59
- log.info("Set strip to %s", self.config.strip)
60
-
61
- def modify_profile(self, compiler_path):
62
- host = self.get_manifest_config("tools/compiler/host")
63
- # 制作rootfs时需要strip镜像,所以需要单独指定stip路径
64
- self.config.strip = os.path.join(compiler_path, "bin", host + "-strip")
65
- profile = self.get_manifest_config("tools/conan/profile")
66
- src_profile = os.path.join(self.config.work_dir, profile)
67
- log.info("Copy profile %s", src_profile)
68
- if not os.path.isfile(src_profile):
69
- raise FileNotFoundError(f"profile {src_profile} not found")
70
- profiles_dir = os.path.expanduser("~/.conan2/profiles")
71
- dst_profile = os.path.join(profiles_dir, os.path.basename(profile))
72
- if os.path.isdir(profiles_dir):
73
- shutil.copyfile(src_profile, dst_profile, follow_symlinks=False)
74
- cmd = f"sed -i 's@^toolchain=.*@toolchain={compiler_path}@g' {dst_profile}"
75
- self.exec(cmd)
76
- cmd = f"sed -i 's@^target_host=.*@target_host={host}@g' {dst_profile}"
77
- self.exec(cmd)
78
- # 重写profile配置
79
- log.info("Overwrite profile to %s", dst_profile)
80
- self.config.profile_host = os.path.basename(dst_profile)
81
-
82
- def run(self):
83
- """任务入口"""
84
- self.check_manifest()
85
- self.install_tools()
86
- if __name__ == "__main__":
87
- config = Config()
88
- build = BuildPrepare(config)
89
- build.run()
@@ -1,126 +0,0 @@
1
- """任务基础类"""
2
- import shutil
3
- import importlib
4
- import os
5
- import hashlib
6
- import requests
7
- import jsonschema
8
- import json, yaml
9
- from multiprocessing import Process
10
- from lbkit.log import Logger
11
- from lbkit.tools import Tools
12
-
13
- log = Logger("product_test")
14
-
15
- from lbkit.integration.config import Config
16
-
17
- class ManifestValidateError(OSError):
18
- """Raised when validation manifest.yml failed."""
19
-
20
- class DigestNotMatchError(OSError):
21
- """Raised when source and destination are the same file."""
22
-
23
- class Task(Process):
24
- """任务基础类,提供run和install默认实现以及其它基础该当"""
25
- def __init__(self, config: Config, name: str):
26
- super().__init__()
27
- self.tools = Tools(name)
28
- self.config = config
29
-
30
- def install(self):
31
- """安装任务"""
32
- log.info("install...........")
33
-
34
- def exec(self, cmd: str, verbose=False, ignore_error = False, sensitive=False, log_prefix=""):
35
- return self.tools.exec(cmd, verbose, ignore_error, sensitive, log_prefix)
36
-
37
- def pipe(self, cmds: list[str], ignore_error=False, out_file = None):
38
- self.tools.pipe(cmds, out_file, ignore_error)
39
-
40
- def run(self, cmd, ignore_error=False):
41
- return self.tools.run(cmd, ignore_error)
42
-
43
- def do_hook(self, path):
44
- """执行任务钓子,用于定制化"""
45
- try:
46
- module = importlib.import_module(path)
47
- except TypeError:
48
- log.info("Load module(%s) failed, skip", path)
49
- return
50
- log.info(module)
51
- hook = module.TaskHook(self.config)
52
- hook.run()
53
-
54
- def get_manifest_config(self, key: str):
55
- """从manifest中读取配置"""
56
- with open(self.config.manifest, "r", encoding="utf-8") as fp:
57
- manifest = yaml.load(fp, yaml.FullLoader)
58
- keys = key.split("/")
59
- for k in keys:
60
- manifest = manifest.get(k, None)
61
- if manifest is None:
62
- return None
63
- return manifest
64
-
65
- @staticmethod
66
- def file_digest_sha256(filename):
67
- """计算文件的sha256值"""
68
- sha256 = hashlib.sha256()
69
- fp = open(filename, "rb")
70
- while True:
71
- data = fp.read(65536)
72
- if len(data) == 0:
73
- break
74
- sha256.update(data)
75
- fp.close()
76
- return sha256.hexdigest()
77
-
78
- def download_url(self, url, dst_file, sha256sum = None):
79
- """下载文件"""
80
- log.info("Start download %s", url)
81
- is_local = False
82
- if url.startswith("file://"):
83
- path = url[7:]
84
- shutil.copyfile(path, dst_file)
85
- is_local = True
86
- if os.path.isfile(dst_file):
87
- digest = self.file_digest_sha256(dst_file)
88
- if sha256sum is None or digest == sha256sum:
89
- return
90
- if is_local:
91
- raise DigestNotMatchError(f"File {dst_file} with sha256 error, need: {sha256sum}, get: {digest}")
92
- os.unlink(dst_file)
93
- verify = os.environ.get("HTTPS_VERIFY", True)
94
- if verify:
95
- response = requests.get(url, timeout=30, verify=True)
96
- else:
97
- response = requests.get(url, timeout=30)
98
- fp = open(dst_file, "wb")
99
- fp.write(response.content)
100
- fp.close()
101
- digest = self.file_digest_sha256(dst_file)
102
- if sha256sum is None or digest == sha256sum:
103
- log.info("Download %s successfully", url)
104
- return
105
- raise DigestNotMatchError(f"File {dst_file} with sha256 error, need: {sha256sum}, get: {digest}")
106
-
107
- def load_manifest(self):
108
- """加载manifest.yml并验证schema文件"""
109
- with open(self.config.manifest, "r", encoding="utf-8") as fp:
110
- manifest = yaml.load(fp, yaml.FullLoader)
111
- schema_version = manifest.get("schema", None)
112
- if schema_version is None or not isinstance(schema_version, int):
113
- raise ManifestValidateError("Validation manifest.yml failed, Property schema not exist.")
114
- # 加载schmea文件验证文件合法性
115
- schema_url = "https://www.litebmc.com/standards/metadata/manifest.yml/schema_v" + str(schema_version)
116
- # 下载schema文件
117
- schema_file = os.path.join(self.config.temp_path, "manifest.schema.json")
118
- self.download_url(schema_url, schema_file)
119
- with open (schema_file, "rb") as fp:
120
- schema = json.load(fp)
121
- # 验证manifest.yml是否合法
122
- try:
123
- jsonschema.validate(manifest, schema)
124
- except jsonschema.exceptions.ValidationError as e:
125
- raise ManifestValidateError("manifest validate failed") from e
126
- return manifest
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