lbkit 0.5.9__tar.gz → 0.5.11__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.
- {lbkit-0.5.9/lbkit.egg-info → lbkit-0.5.11}/PKG-INFO +2 -1
- lbkit-0.5.11/lbkit/__init__.py +2 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/cli.py +3 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/idf_interface.py +2 -4
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/component/build.py +8 -9
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/errors.py +9 -0
- lbkit-0.5.11/lbkit/integration/build_image.py +89 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/integration/build_manifest.py +27 -12
- lbkit-0.5.11/lbkit/integration/build_prepare.py +56 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/integration/build_rootfs.py +34 -48
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/integration/config.py +4 -2
- lbkit-0.5.11/lbkit/integration/task.py +63 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/misc.py +2 -1
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/tools.py +54 -6
- {lbkit-0.5.9 → lbkit-0.5.11/lbkit.egg-info}/PKG-INFO +2 -1
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit.egg-info/SOURCES.txt +1 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit.egg-info/requires.txt +1 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/setup.py +1 -1
- lbkit-0.5.9/lbkit/__init__.py +0 -2
- lbkit-0.5.9/lbkit/integration/build_prepare.py +0 -89
- lbkit-0.5.9/lbkit/integration/task.py +0 -126
- {lbkit-0.5.9 → lbkit-0.5.11}/AUTHORS +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/LICENSE +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/MANIFEST.in +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/README.md +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/ci_robot/__init__.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/ci_robot/gitee.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/__init__.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/codegen.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/ctype_defination.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/renderer.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/client.c.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/client.h.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/interface.c.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/interface.introspect.xml.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/public.c.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/public.h.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/server.c.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/codegen/template/server.h.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/component/__init__.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/component/arg_parser.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/component/template/conanbase.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/component/template/deploy.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/component/test.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/helper.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/integration/__init__.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/integration/template/conanfile.py.mako +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/lbkit.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit/log.py +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit.egg-info/dependency_links.txt +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit.egg-info/entry_points.txt +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/lbkit.egg-info/top_level.txt +0 -0
- {lbkit-0.5.9 → lbkit-0.5.11}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lbkit
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.11
|
|
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
|
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
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,89 @@
|
|
|
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 = None
|
|
21
|
+
if not self.config.not_check_download_sha:
|
|
22
|
+
sha256 = self.get_manifest_config("components/uboot/sha256")
|
|
23
|
+
self.tools.download(url, tar_file, sha256)
|
|
24
|
+
tar = tarfile.open(tar_file)
|
|
25
|
+
members = tar.getmembers()
|
|
26
|
+
for member in members:
|
|
27
|
+
if not member.isfile():
|
|
28
|
+
continue
|
|
29
|
+
if member.name != "uboot.bin" and member.name != "u-boot.bin":
|
|
30
|
+
continue
|
|
31
|
+
io = tar.extractfile(member)
|
|
32
|
+
fp = open(img_file, "wb+")
|
|
33
|
+
while True:
|
|
34
|
+
buf = io.read(65536)
|
|
35
|
+
if len(buf) == 0:
|
|
36
|
+
break
|
|
37
|
+
fp.write(buf)
|
|
38
|
+
fp.close()
|
|
39
|
+
return
|
|
40
|
+
raise errors.ExtractRootfsTarFileError("Extract failed, the u-boot.bin can't be found in " + url)
|
|
41
|
+
|
|
42
|
+
def download_kernel(self, img_file):
|
|
43
|
+
tar_file = os.path.join(self.config.download_path, "kernel.tar.gz")
|
|
44
|
+
|
|
45
|
+
url = self.get_manifest_config("components/kernel/url")
|
|
46
|
+
sha256 = None
|
|
47
|
+
if not self.config.not_check_download_sha:
|
|
48
|
+
sha256 = self.get_manifest_config("components/kernel/sha256")
|
|
49
|
+
self.tools.download(url, tar_file, sha256)
|
|
50
|
+
tar = tarfile.open(tar_file)
|
|
51
|
+
members = tar.getmembers()
|
|
52
|
+
for member in members:
|
|
53
|
+
if not member.isfile():
|
|
54
|
+
continue
|
|
55
|
+
if member.name != "Image":
|
|
56
|
+
continue
|
|
57
|
+
io = tar.extractfile(member)
|
|
58
|
+
fp = open(img_file, "wb+")
|
|
59
|
+
while True:
|
|
60
|
+
buf = io.read(65536)
|
|
61
|
+
if len(buf) == 0:
|
|
62
|
+
break
|
|
63
|
+
fp.write(buf)
|
|
64
|
+
fp.close()
|
|
65
|
+
return
|
|
66
|
+
raise errors.ExtractRootfsTarFileError("Extract failed, the Image can't be found in " + url)
|
|
67
|
+
|
|
68
|
+
def run(self):
|
|
69
|
+
"""任务入口"""
|
|
70
|
+
"""检查manifest文件是否满足schema格式描述"""
|
|
71
|
+
os.chdir(self.config.output_path)
|
|
72
|
+
self.download_uboot("u-boot.bin")
|
|
73
|
+
self.download_kernel("Image")
|
|
74
|
+
cmd = "qemu-system-aarch64 -M virt -cpu cortex-a57 -M virt,dumpdtb=virt.dtb"
|
|
75
|
+
self.exec(cmd)
|
|
76
|
+
|
|
77
|
+
cmd = 'lbpack_emmc.sh ./Image ./virt.dtb ./rootfs.img ./qemu.img'
|
|
78
|
+
self.exec(cmd)
|
|
79
|
+
cmd = 'cp /usr/share/litebmc/qemu.conf ./qemu.conf'
|
|
80
|
+
self.exec(cmd)
|
|
81
|
+
output_img = os.path.join(self.config.output_path, "litebmc_qemu.tar.gz")
|
|
82
|
+
cmd = f'tar -czf {output_img} -C . qemu.img u-boot.bin qemu.conf'
|
|
83
|
+
self.exec(cmd)
|
|
84
|
+
log.success(f"Create litebmc image {output_img} successfully")
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
config = Config()
|
|
88
|
+
build = BuildImage(config)
|
|
89
|
+
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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]):
|
|
@@ -69,22 +64,10 @@ class BuildRootfs(Task):
|
|
|
69
64
|
rootfs_tar = "rootfs.tar.gz"
|
|
70
65
|
|
|
71
66
|
url = self.get_manifest_config("components/rootfs/url")
|
|
72
|
-
sha256 =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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}")
|
|
67
|
+
sha256 = None
|
|
68
|
+
if not self.config.not_check_download_sha:
|
|
69
|
+
sha256 = self.get_manifest_config("components/rootfs/sha256")
|
|
70
|
+
self.tools.download(url, rootfs_tar, sha256)
|
|
88
71
|
tar = tarfile.open(rootfs_tar)
|
|
89
72
|
members = tar.getmembers()
|
|
90
73
|
for member in members:
|
|
@@ -97,11 +80,11 @@ class BuildRootfs(Task):
|
|
|
97
80
|
while True:
|
|
98
81
|
buf = io.read(65536)
|
|
99
82
|
if len(buf) == 0:
|
|
100
|
-
break
|
|
83
|
+
break
|
|
101
84
|
fp.write(buf)
|
|
102
85
|
fp.close()
|
|
103
86
|
return
|
|
104
|
-
raise ExtractRootfsTarFileError("Extract failed, the rootfs.img can't be found in rootfs.tar")
|
|
87
|
+
raise errors.ExtractRootfsTarFileError("Extract failed, the rootfs.img can't be found in rootfs.tar")
|
|
105
88
|
|
|
106
89
|
def merge_rootfs(self, rootfs_img):
|
|
107
90
|
"""将产品依赖的所有组件安装到rootfs镜像中"""
|
|
@@ -117,13 +100,9 @@ class BuildRootfs(Task):
|
|
|
117
100
|
# 切换到rootfs挂载目录
|
|
118
101
|
os.chdir(mnt_path)
|
|
119
102
|
log.info("Copy customization rootfs......")
|
|
120
|
-
|
|
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
|
|
103
|
+
for src_dir in self.config.conan_install:
|
|
125
104
|
log.info("copy %s to %s", src_dir, mnt_path)
|
|
126
|
-
cmd = f"rsync -
|
|
105
|
+
cmd = f"rsync -aHK {src_dir}/ {mnt_path}"
|
|
127
106
|
self.exec(cmd)
|
|
128
107
|
per_file = os.path.join(src_dir, "permissions")
|
|
129
108
|
self.do_permission(per_file)
|
|
@@ -131,7 +110,7 @@ class BuildRootfs(Task):
|
|
|
131
110
|
# copy product self-owned rootfs
|
|
132
111
|
product_rootfs = os.path.join(self.config.work_dir, "rootfs")
|
|
133
112
|
if os.path.isdir(product_rootfs):
|
|
134
|
-
cmd = f"rsync -
|
|
113
|
+
cmd = f"rsync -aKH {product_rootfs}/ {mnt_path}"
|
|
135
114
|
self.exec(cmd)
|
|
136
115
|
per_file = os.path.join(product_rootfs, "permissions")
|
|
137
116
|
self.do_permission(per_file)
|
|
@@ -142,18 +121,21 @@ class BuildRootfs(Task):
|
|
|
142
121
|
self.do_hook(hook_name)
|
|
143
122
|
|
|
144
123
|
# 清理冗余文件
|
|
145
|
-
|
|
146
|
-
|
|
124
|
+
inc_dir = os.path.join(self.config.mnt_path, "include")
|
|
125
|
+
if os.path.isdir(inc_dir):
|
|
126
|
+
cmd = "rm -rf " + inc_dir
|
|
127
|
+
self.exec(cmd)
|
|
147
128
|
cmd = "rm " + os.path.join(self.config.mnt_path, "permissions")
|
|
148
129
|
self.exec(cmd)
|
|
149
130
|
|
|
150
131
|
# 清理冗余静态文件
|
|
151
|
-
|
|
152
|
-
self.
|
|
132
|
+
cmds = ["find " + self.config.mnt_path + " -name *.a -type f", "xargs -i{} rm {} -f"]
|
|
133
|
+
self.pipe(cmds)
|
|
153
134
|
|
|
154
135
|
# 按manifest配置的大小调整rootfs
|
|
155
136
|
strip = self.get_manifest_config("metadata/strip")
|
|
156
137
|
if strip:
|
|
138
|
+
log.info("Start strip files")
|
|
157
139
|
file_list = os.path.join(self.config.temp_path, "strip.filelist")
|
|
158
140
|
unstrip_regex = [
|
|
159
141
|
"*.yaml$",
|
|
@@ -172,15 +154,19 @@ class BuildRootfs(Task):
|
|
|
172
154
|
"*.conf$"]
|
|
173
155
|
cmds = ["find " + self.config.mnt_path + " -type f"]
|
|
174
156
|
for unstrip in unstrip_regex:
|
|
175
|
-
cmds.append
|
|
157
|
+
cmds.append(f"grep -vE {unstrip}")
|
|
176
158
|
self.pipe(cmds, out_file=file_list)
|
|
177
159
|
|
|
178
160
|
cmds = [f"cat {file_list}", "xargs file", "grep 'not stripped'", "awk -F':' '{{print $1}}'"]
|
|
179
|
-
cmds.append
|
|
180
|
-
self.pipe(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
self.
|
|
161
|
+
cmds.append(f"xargs -i{{}} {self.config.strip} -s {{}}")
|
|
162
|
+
self.pipe(cmds)
|
|
163
|
+
|
|
164
|
+
log.info("remove static libraries")
|
|
165
|
+
cmds = [f"find {self.config.mnt_path} -name *.a", "xargs -i{} rm {}"]
|
|
166
|
+
self.pipe(cmds)
|
|
167
|
+
log.info("remove all .fuse_hiddeng* files")
|
|
168
|
+
cmds = [f"find {self.config.mnt_path} -name .fuse_hidden*", "xargs -i{} rm {}"]
|
|
169
|
+
self.pipe(cmds)
|
|
184
170
|
self.exec("umount " + mnt_path)
|
|
185
171
|
|
|
186
172
|
def run(self):
|
|
@@ -190,8 +176,8 @@ class BuildRootfs(Task):
|
|
|
190
176
|
# 任务入口
|
|
191
177
|
self.download_rootfs(rootfs_img)
|
|
192
178
|
self.merge_rootfs(rootfs_img)
|
|
193
|
-
|
|
194
|
-
|
|
179
|
+
os.rename(rootfs_img, self.config.rootfs_img)
|
|
180
|
+
log.success(f"Create image {self.config.rootfs_img} successfully")
|
|
195
181
|
|
|
196
182
|
if __name__ == "__main__":
|
|
197
183
|
config = Config()
|
|
@@ -25,6 +25,7 @@ class Config(object):
|
|
|
25
25
|
self.build_debug = args.debug
|
|
26
26
|
# conan中心仓
|
|
27
27
|
self.remote = args.remote
|
|
28
|
+
self.not_check_download_sha = args.not_check_download_sha
|
|
28
29
|
|
|
29
30
|
if not os.path.isfile(self.manifest):
|
|
30
31
|
raise FileNotFoundError(f"File {args.manifest} not exist")
|
|
@@ -41,14 +42,14 @@ class Config(object):
|
|
|
41
42
|
self.tool_path = os.path.join(self.temp_path, "tools")
|
|
42
43
|
self.download_path = os.path.join(self.temp_path, "download")
|
|
43
44
|
self.rootfs_path = os.path.join(self.temp_path, "rootfs")
|
|
44
|
-
self.conan_install =
|
|
45
|
+
self.conan_install = []
|
|
45
46
|
self.mnt_path = os.path.join(self.temp_path, "mnt_path")
|
|
47
|
+
self.rootfs_img = os.path.join(self.output_path, "rootfs.img")
|
|
46
48
|
os.makedirs(self.temp_path, exist_ok=True)
|
|
47
49
|
os.makedirs(self.tool_path, exist_ok=True)
|
|
48
50
|
os.makedirs(self.output_path, exist_ok=True)
|
|
49
51
|
os.makedirs(self.download_path, exist_ok=True)
|
|
50
52
|
os.makedirs(self.rootfs_path, exist_ok=True)
|
|
51
|
-
os.makedirs(self.conan_install, exist_ok=True)
|
|
52
53
|
# 制作rootfs时需要strip镜像,所以需要单独指定stip路径
|
|
53
54
|
self.strip = "strip"
|
|
54
55
|
|
|
@@ -61,5 +62,6 @@ class Config(object):
|
|
|
61
62
|
parser.add_argument("-pr", "--profile", help="Apply the specified profile to the host machine", default="litebmc.ini")
|
|
62
63
|
parser.add_argument("-pr:b", "--profile_build", help="Apply the specified profile to the build machine", default="default")
|
|
63
64
|
parser.add_argument("-d", "--debug", help="Set the build type to debug", action="store_false")
|
|
65
|
+
parser.add_argument("--not_check_download_sha", help="don't check sha256 of download file", action="store_true")
|
|
64
66
|
parser.add_argument("-r", "--remote", help="specified conan server", default="litebmc")
|
|
65
67
|
return parser
|
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
3
|
+
Version: 0.5.11
|
|
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
|
|
@@ -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,
|
lbkit-0.5.9/lbkit/__init__.py
DELETED
|
@@ -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
|
|
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
|