lbkit 0.5.8__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.
- {lbkit-0.5.8/lbkit.egg-info → lbkit-0.5.10}/PKG-INFO +2 -1
- lbkit-0.5.10/lbkit/__init__.py +2 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/cli.py +3 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/idf_interface.py +8 -4
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/public.c.mako +10 -2
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/server.c.mako +3 -1
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/component/build.py +8 -9
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/errors.py +9 -0
- lbkit-0.5.10/lbkit/integration/build_image.py +85 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/integration/build_manifest.py +27 -12
- lbkit-0.5.10/lbkit/integration/build_prepare.py +56 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/integration/build_rootfs.py +31 -47
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/integration/config.py +2 -2
- lbkit-0.5.10/lbkit/integration/task.py +63 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/misc.py +2 -1
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/tools.py +54 -6
- {lbkit-0.5.8 → lbkit-0.5.10/lbkit.egg-info}/PKG-INFO +2 -1
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit.egg-info/SOURCES.txt +1 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit.egg-info/requires.txt +1 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/setup.py +1 -1
- lbkit-0.5.8/lbkit/__init__.py +0 -2
- lbkit-0.5.8/lbkit/integration/build_prepare.py +0 -89
- lbkit-0.5.8/lbkit/integration/task.py +0 -126
- {lbkit-0.5.8 → lbkit-0.5.10}/AUTHORS +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/LICENSE +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/MANIFEST.in +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/README.md +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/ci_robot/__init__.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/ci_robot/gitee.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/__init__.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/codegen.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/ctype_defination.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/renderer.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/client.c.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/client.h.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/interface.c.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/interface.introspect.xml.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/public.h.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/codegen/template/server.h.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/component/__init__.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/component/arg_parser.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/component/template/conanbase.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/component/template/deploy.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/component/test.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/helper.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/integration/__init__.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/integration/template/conanfile.py.mako +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/lbkit.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit/log.py +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit.egg-info/dependency_links.txt +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit.egg-info/entry_points.txt +0 -0
- {lbkit-0.5.8 → lbkit-0.5.10}/lbkit.egg-info/top_level.txt +0 -0
- {lbkit-0.5.8 → 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.
|
|
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
|
|
@@ -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
|
|
|
@@ -48,6 +48,7 @@ ANNOTATION_MAP = {
|
|
|
48
48
|
"emits_false": IdfAnnotation("org.freedesktop.DBus.Property.EmitsChangedSignal", "false"),
|
|
49
49
|
"private": IdfAnnotation("com.litebmc.Dbus.Property.Private", "const"),
|
|
50
50
|
"refobj": IdfAnnotation("com.litebmc.Dbus.Property.RefObject", "true"),
|
|
51
|
+
"required": IdfAnnotation("com.litebmc.Dbus.Property.Required", "true"),
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
ACCESS_MAP = {
|
|
@@ -715,7 +716,10 @@ class IdfInterface(IdfInterfaceBase):
|
|
|
715
716
|
def odf_schema(self):
|
|
716
717
|
schema = {}
|
|
717
718
|
with_schema_prop_cnt = 0
|
|
719
|
+
required = []
|
|
718
720
|
for prop in self.properties:
|
|
721
|
+
if "required" in prop.flags:
|
|
722
|
+
required.append(prop.name)
|
|
719
723
|
odf = prop.odf_schema(True)
|
|
720
724
|
if odf is not None:
|
|
721
725
|
schema[prop.name] = odf
|
|
@@ -734,6 +738,8 @@ class IdfInterface(IdfInterfaceBase):
|
|
|
734
738
|
"type": "object",
|
|
735
739
|
"additionalProperties": False
|
|
736
740
|
}
|
|
741
|
+
if len(required) > 0:
|
|
742
|
+
odf["required"] = required
|
|
737
743
|
if with_schema_prop_cnt > 0:
|
|
738
744
|
odf["properties"] = schema
|
|
739
745
|
odf["$defs"] = {
|
|
@@ -780,10 +786,8 @@ class IdfInterface(IdfInterfaceBase):
|
|
|
780
786
|
def load_elements(self):
|
|
781
787
|
# 使用schema校验数据,确保IDF文件符合格式要求,减少程序处理过程中的异常处理
|
|
782
788
|
# 验证失败时抛异常,此处不用处理,由外层处理
|
|
783
|
-
|
|
789
|
+
idf = load_yml_with_json_schema_validate(self.file, "/usr/share/litebmc/schema/idf.v1.json")
|
|
784
790
|
log.info(f"validate {self.file} successfully")
|
|
785
|
-
with open(self.file, "r") as fp:
|
|
786
|
-
idf = yaml.load(fp, yaml.FullLoader)
|
|
787
791
|
self.version = idf.get("version")
|
|
788
792
|
self.description = idf.get("description", "")
|
|
789
793
|
# 别名
|
|
@@ -176,6 +176,7 @@ gboolean _validate_odf_as_struct_${name}(yaml_document_t *doc, yaml_node_t *node
|
|
|
176
176
|
g_string_truncate(prop, len);
|
|
177
177
|
}
|
|
178
178
|
% endfor
|
|
179
|
+
g_hash_table_destroy(prop_table);
|
|
179
180
|
return valid;
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -1161,8 +1162,14 @@ static gboolean _validate_odf_prop_${prop.name}(yaml_document_t *doc, GHashTable
|
|
|
1161
1162
|
gsize len = prop->len;
|
|
1162
1163
|
yaml_node_t *node = g_hash_table_lookup(prop_table, "${prop.name}");
|
|
1163
1164
|
do {
|
|
1164
|
-
if (!node)
|
|
1165
|
+
if (!node) {
|
|
1166
|
+
## 检查属性是否存在
|
|
1167
|
+
% if "required" in prop.flags:
|
|
1168
|
+
*error_list = g_slist_append(*error_list, g_error_new(ODF_ERROR, ODF_ERROR_PROP_MISSING, "Property %s is missing", prop->str));
|
|
1169
|
+
valid = FALSE;
|
|
1170
|
+
% endif
|
|
1165
1171
|
break;
|
|
1172
|
+
}
|
|
1166
1173
|
g_string_append(prop, ".${prop.name}");
|
|
1167
1174
|
% if "refobj" not in prop.flags:
|
|
1168
1175
|
if (validate_odf_as_ref_prop(doc, node, prop))
|
|
@@ -1197,13 +1204,14 @@ gboolean ${intf.name.replace(".", "_")}_validate_odf(yaml_document_t *doc, yaml_
|
|
|
1197
1204
|
"the node type of object %s is not a mapping, get %s", object_name, gcl_yaml_node_type_str(node->type)));
|
|
1198
1205
|
return FALSE;
|
|
1199
1206
|
}
|
|
1200
|
-
GString *prop = g_string_sized_new(128);
|
|
1207
|
+
cleanup_gstring GString *prop = g_string_sized_new(128);
|
|
1201
1208
|
g_string_printf(prop, "%s", object_name);
|
|
1202
1209
|
gboolean valid = TRUE;
|
|
1203
1210
|
GHashTable *prop_table = load_yaml_mapping_to_hash_table(doc, node);
|
|
1204
1211
|
% for prop in stru.values.parameters:
|
|
1205
1212
|
valid = _validate_odf_prop_${prop.name}(doc, prop_table, prop, error_list) && valid;
|
|
1206
1213
|
% endfor
|
|
1214
|
+
g_hash_table_destroy(prop_table);
|
|
1207
1215
|
return valid;
|
|
1208
1216
|
}
|
|
1209
1217
|
|
|
@@ -272,9 +272,11 @@ static void _load_odf_as_prop_${prop.name}(yaml_document_t *doc, GHashTable *pro
|
|
|
272
272
|
flags = val->data.scalar.value;
|
|
273
273
|
}
|
|
274
274
|
val = g_hash_table_lookup(prop_table, "${prop.name}");
|
|
275
|
+
## validate接口在加载odf前完成属性是否必选校验,此处如果是必选属性一定存在
|
|
275
276
|
if (!val) {
|
|
276
277
|
if (flags) {
|
|
277
|
-
|
|
278
|
+
## 属性不存在时传入的value为空,需要开发者在回调函数中完成异常(有flags无属性值)处理
|
|
279
|
+
ref_loaded(obj, &${properties}.${prop.name}, doc, NULL, user_data, flags);
|
|
278
280
|
}
|
|
279
281
|
return;
|
|
280
282
|
}
|
|
@@ -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,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
|
|
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]):
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 -
|
|
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 -
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
self.
|
|
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
|
|
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
|
|
180
|
-
self.pipe(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
self.
|
|
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
|
-
|
|
194
|
-
|
|
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 =
|
|
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
|
|
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.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
|
|
@@ -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.8/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
|