lbkit 0.8.4__tar.gz → 0.8.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lbkit-0.8.6/MANIFEST.in +3 -0
- {lbkit-0.8.4/lbkit.egg-info → lbkit-0.8.6}/PKG-INFO +1 -1
- lbkit-0.8.6/lbkit/__init__.py +2 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/cli.py +11 -26
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/server.c.mako +1 -1
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/log.py +1 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/misc.py +42 -0
- lbkit-0.8.6/lbkit/tasks/config.py +187 -0
- lbkit-0.8.6/lbkit/tasks/executor.py +258 -0
- lbkit-0.8.6/lbkit/tasks/task.py +132 -0
- lbkit-0.8.4/lbkit/integration/build_image.py → lbkit-0.8.6/lbkit/tasks/task_build_image.py +4 -6
- lbkit-0.8.4/lbkit/integration/build_manifest.py → lbkit-0.8.6/lbkit/tasks/task_build_manifest.py +4 -4
- lbkit-0.8.6/lbkit/tasks/task_build_prepare.py +98 -0
- lbkit-0.8.4/lbkit/integration/build_rootfs.py → lbkit-0.8.6/lbkit/tasks/task_build_rootfs.py +4 -5
- lbkit-0.8.6/lbkit/tasks/task_download.py +129 -0
- lbkit-0.8.6/lbkit/tasks/task_test.py +29 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/tools.py +0 -1
- lbkit-0.8.6/lbkit/utils/env_detector.py +38 -0
- {lbkit-0.8.4 → lbkit-0.8.6/lbkit.egg-info}/PKG-INFO +1 -1
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit.egg-info/SOURCES.txt +14 -9
- lbkit-0.8.6/test/test_config.py +66 -0
- lbkit-0.8.4/MANIFEST.in +0 -3
- lbkit-0.8.4/lbkit/__init__.py +0 -2
- lbkit-0.8.4/lbkit/integration/build_prepare.py +0 -59
- lbkit-0.8.4/lbkit/integration/config.py +0 -91
- lbkit-0.8.4/lbkit/integration/task.py +0 -58
- {lbkit-0.8.4 → lbkit-0.8.6}/AUTHORS +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/LICENSE +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/README.md +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/build_conan_parallel.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/ci_robot/__init__.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/ci_robot/gitee.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/__init__.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/codegen.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/ctype_defination.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/idf_interface.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/renderer.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/client.c.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/client.h.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/interface.c.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/interface.introspect.xml.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/public.c.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/public.h.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/codegen/template/server.h.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/component/__init__.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/component/arg_parser.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/component/build.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/component/template/conanbase.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/component/template/deploy.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/component/test.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/errors.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/helper.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/lbkit.py +0 -0
- {lbkit-0.8.4/lbkit/integration → lbkit-0.8.6/lbkit/tasks}/__init__.py +0 -0
- {lbkit-0.8.4/lbkit/integration → lbkit-0.8.6/lbkit/tasks}/template/conanfile.py.mako +0 -0
- {lbkit-0.8.4/lbkit/integration → lbkit-0.8.6/lbkit/tasks}/template/rootfs.py.mako +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/utils/__init__.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/utils/images/__init__.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit/utils/images/emmc.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit.egg-info/dependency_links.txt +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit.egg-info/entry_points.txt +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit.egg-info/requires.txt +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/lbkit.egg-info/top_level.txt +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/setup.cfg +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/setup.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/test/__init__.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/test/test_codegen.py +0 -0
- {lbkit-0.8.4 → lbkit-0.8.6}/test/test_helper.py +0 -0
lbkit-0.8.6/MANIFEST.in
ADDED
|
@@ -10,16 +10,13 @@ from lbkit import __version__ as client_version
|
|
|
10
10
|
from lbkit.codegen.codegen import CodeGen
|
|
11
11
|
from lbkit.component.build import BuildComponent
|
|
12
12
|
from lbkit.component.test import TestComponent
|
|
13
|
-
from lbkit.
|
|
14
|
-
from lbkit.integration.build_rootfs import BuildRootfs
|
|
15
|
-
from lbkit.integration.build_prepare import BuildPrepare
|
|
16
|
-
from lbkit.integration.build_image import BuildImage
|
|
13
|
+
from lbkit.tasks.executor import Executor
|
|
17
14
|
from lbkit.component.arg_parser import ArgParser
|
|
18
|
-
from lbkit.integration.config import Config
|
|
19
15
|
from lbkit.ci_robot.gitee import Gitee
|
|
20
16
|
from lbkit.log import Logger
|
|
21
17
|
from lbkit import misc
|
|
22
18
|
from lbkit import errors
|
|
19
|
+
from lbkit.utils.env_detector import EnvDetector
|
|
23
20
|
|
|
24
21
|
log = Logger("cli")
|
|
25
22
|
|
|
@@ -80,26 +77,14 @@ class Command(object):
|
|
|
80
77
|
|
|
81
78
|
组件需要支持多种跨平台构建场景,典型的包括DT(X86-64)、交叉编译(arm64)
|
|
82
79
|
"""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
组件需要支持多种跨平台构建场景,典型的包括DT(X86-64)、交叉编译(arm64)
|
|
93
|
-
"""
|
|
94
|
-
cfg = Config(sys.argv[2:])
|
|
95
|
-
build = BuildPrepare(cfg, "create_pre")
|
|
96
|
-
build.run()
|
|
97
|
-
build = BuildManifest(cfg, "create_run")
|
|
98
|
-
build.run()
|
|
99
|
-
build = BuildRootfs(cfg, "create_rootfs")
|
|
100
|
-
build.run()
|
|
101
|
-
build = BuildImage(cfg, "create_image")
|
|
102
|
-
build.run()
|
|
80
|
+
env = EnvDetector()
|
|
81
|
+
if env.component:
|
|
82
|
+
arg_parser = ArgParser.new()
|
|
83
|
+
build = BuildComponent(arg_parser, sys.argv[2:])
|
|
84
|
+
build.run()
|
|
85
|
+
elif env.manifest:
|
|
86
|
+
exe = Executor(env)
|
|
87
|
+
exe.run()
|
|
103
88
|
|
|
104
89
|
def gitee(self, *args):
|
|
105
90
|
"""
|
|
@@ -126,7 +111,7 @@ class Command(object):
|
|
|
126
111
|
"""
|
|
127
112
|
grps = [("Code Generate commands", ["gen"]),
|
|
128
113
|
("Build Component commands", ["new", "build", "test"]),
|
|
129
|
-
("Build Product commands", ["
|
|
114
|
+
("Build Product commands", ["build"]),
|
|
130
115
|
("Misc commands", ["help"]),
|
|
131
116
|
("CI Robot commands", ["gitee"])
|
|
132
117
|
]
|
|
@@ -12,6 +12,7 @@ class Logger(logging.getLoggerClass()):
|
|
|
12
12
|
self.logenv = os.environ.get("LOG")
|
|
13
13
|
if self.logenv is None:
|
|
14
14
|
formatter = logging.Formatter('%(message)s')
|
|
15
|
+
self.setLevel(logging.INFO)
|
|
15
16
|
else:
|
|
16
17
|
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
|
17
18
|
if self.logenv == "info":
|
|
@@ -5,12 +5,14 @@ import json
|
|
|
5
5
|
import yaml
|
|
6
6
|
import re
|
|
7
7
|
import requests
|
|
8
|
+
import fcntl
|
|
8
9
|
from string import Template
|
|
9
10
|
from colorama import Fore, Style
|
|
10
11
|
from jsonschema import validate, ValidationError
|
|
11
12
|
from lbkit.errors import PackageConfigException, HttpRequestException
|
|
12
13
|
|
|
13
14
|
LOG_DIR = "/tmp/lbkit/log"
|
|
15
|
+
TARGETS_DIR = "/usr/share/litebmc/targets"
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class Color(object):
|
|
@@ -103,3 +105,43 @@ def load_yml_with_json_schema_validate(yml_file, default_json_schema_file, **kwa
|
|
|
103
105
|
raise PackageConfigException(f"validate {yml_file} failed, schema file is {schema_file}, "
|
|
104
106
|
f"message: {exc.message}\n"
|
|
105
107
|
"installing redhat.vscode-yaml plugin in vscode will help you write odf files")
|
|
108
|
+
|
|
109
|
+
class DownloadFlag():
|
|
110
|
+
@staticmethod
|
|
111
|
+
def clean(filename):
|
|
112
|
+
"""清理文件标记"""
|
|
113
|
+
filename += ".flag"
|
|
114
|
+
fp = open(filename, "a+")
|
|
115
|
+
fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
116
|
+
fp.truncate(0)
|
|
117
|
+
fcntl.flock(fp, fcntl.F_UNLCK)
|
|
118
|
+
fp.close()
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def create(filename, url, new_hash):
|
|
122
|
+
"""创建文件标记"""
|
|
123
|
+
filename += ".flag"
|
|
124
|
+
fp = open(filename, "a+")
|
|
125
|
+
fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
126
|
+
fp.seek(0, 0)
|
|
127
|
+
fp.truncate(0)
|
|
128
|
+
fp.write(url + "|" + new_hash)
|
|
129
|
+
fcntl.flock(fp, fcntl.F_UNLCK)
|
|
130
|
+
fp.close()
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def read(filename):
|
|
134
|
+
"""读取文件标记"""
|
|
135
|
+
filename += ".flag"
|
|
136
|
+
if not os.path.isfile(filename):
|
|
137
|
+
return "", ""
|
|
138
|
+
fp = open(filename, "a+")
|
|
139
|
+
fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
140
|
+
fp.seek(0, 0)
|
|
141
|
+
content = fp.read()
|
|
142
|
+
fcntl.flock(fp, fcntl.F_UNLCK)
|
|
143
|
+
fp.close()
|
|
144
|
+
if len(content) == 0:
|
|
145
|
+
return "", ""
|
|
146
|
+
chunk = content.split("|")
|
|
147
|
+
return chunk[0], chunk[1]
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""集成构建配置项"""
|
|
2
|
+
import argparse
|
|
3
|
+
import os, sys
|
|
4
|
+
from lbkit.log import Logger
|
|
5
|
+
from lbkit.misc import load_yml_with_json_schema_validate, TARGETS_DIR
|
|
6
|
+
|
|
7
|
+
log = Logger("build_config")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Config(object):
|
|
11
|
+
"""集成构建的配置项"""
|
|
12
|
+
def __init__(self, args = None):
|
|
13
|
+
parser = self.arg_parser()
|
|
14
|
+
args = parser.parse_args(args)
|
|
15
|
+
|
|
16
|
+
# 配置项
|
|
17
|
+
self.manifest = os.path.join(os.getcwd(), args.manifest)
|
|
18
|
+
self.manifest = os.path.realpath(self.manifest)
|
|
19
|
+
# 配置项目录
|
|
20
|
+
self.work_dir = os.path.dirname(self.manifest)
|
|
21
|
+
sys.path.append(self.work_dir)
|
|
22
|
+
# 是否从源码构建
|
|
23
|
+
self.from_source = args.from_source
|
|
24
|
+
# 是否打印详细信息
|
|
25
|
+
self.verbose = True if os.environ.get("VERBOSE", False) else False
|
|
26
|
+
# 编译类型
|
|
27
|
+
self.build_type = args.build_type
|
|
28
|
+
# conan中心仓
|
|
29
|
+
self.remote = args.remote
|
|
30
|
+
|
|
31
|
+
if not os.path.isfile(self.manifest):
|
|
32
|
+
raise FileNotFoundError(f"File {args.manifest} not exist")
|
|
33
|
+
|
|
34
|
+
# 编译主机配置项
|
|
35
|
+
self.profile_build = args.profile_build
|
|
36
|
+
# 待所有参数确认后会调用refresh_profile_name设置正确的profile
|
|
37
|
+
self.profile_host = None
|
|
38
|
+
|
|
39
|
+
# conan.lock options
|
|
40
|
+
self.using_lockfile = args.lockfile
|
|
41
|
+
self.update_lockfile = args.update_lockfile
|
|
42
|
+
self.target = args.target
|
|
43
|
+
self.product = args.product
|
|
44
|
+
|
|
45
|
+
# 设置并创建构建所需目录
|
|
46
|
+
log.info("Work dir: %s", self.work_dir)
|
|
47
|
+
self.code_path = os.getcwd()
|
|
48
|
+
self.temp_path = os.path.join(self.code_path, ".temp")
|
|
49
|
+
self.output_path = os.path.join(self.temp_path, "output")
|
|
50
|
+
self.download_path = os.path.join(self.temp_path, "download")
|
|
51
|
+
self.tool_path = os.path.join(self.temp_path, "tools")
|
|
52
|
+
self.compiler_path = os.path.join(self.tool_path, "compiler")
|
|
53
|
+
# conan组件打包目录
|
|
54
|
+
self.conan_install = []
|
|
55
|
+
self.mnt_path = os.path.join(self.temp_path, "mnt_path")
|
|
56
|
+
self.rootfs_img = os.path.join(self.output_path, "rootfs.img")
|
|
57
|
+
# rootfs、uboot和kernel关键文件路径
|
|
58
|
+
self.uboot_bin = None
|
|
59
|
+
self.linux_bin = None
|
|
60
|
+
self.rootfs_tar = None
|
|
61
|
+
os.makedirs(self.temp_path, exist_ok=True)
|
|
62
|
+
os.makedirs(self.tool_path, exist_ok=True)
|
|
63
|
+
os.makedirs(self.output_path, exist_ok=True)
|
|
64
|
+
os.makedirs(self.download_path, exist_ok=True)
|
|
65
|
+
os.makedirs(self.compiler_path, exist_ok=True)
|
|
66
|
+
# 制作rootfs时需要strip镜像,所以需要单独指定stip路径
|
|
67
|
+
self.strip = "strip"
|
|
68
|
+
self.check_product()
|
|
69
|
+
# 刷新conan profile
|
|
70
|
+
self.refresh_profile_name()
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def target_list():
|
|
74
|
+
targets = {}
|
|
75
|
+
dirname = os.path.join(TARGETS_DIR)
|
|
76
|
+
for file in os.listdir(dirname):
|
|
77
|
+
if not file.endswith(".yml"):
|
|
78
|
+
continue
|
|
79
|
+
tgt_file = os.path.join(TARGETS_DIR, file)
|
|
80
|
+
targets[file] = tgt_file
|
|
81
|
+
return targets
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def arg_parser():
|
|
85
|
+
"""返回配置项支持的参数"""
|
|
86
|
+
parser = argparse.ArgumentParser(description="Build LiteBMC", formatter_class=argparse.RawTextHelpFormatter)
|
|
87
|
+
parser.add_argument("-m", "--manifest", help="Specify the manifest.yml, ignored when -l is specified.", default="./manifest.yml")
|
|
88
|
+
parser.add_argument("-s", "--from_source", help="Build from source", action="store_true")
|
|
89
|
+
parser.add_argument("-pr:b", "--profile_build", help="Apply the specified profile to the build machine", default="default")
|
|
90
|
+
parser.add_argument("-bt", "--build_type", type=str, choices=['debug', 'release', 'minsize'], help="Set the build type", default="debug")
|
|
91
|
+
parser.add_argument("-r", "--remote", help="specified conan server", default="litebmc")
|
|
92
|
+
parser.add_argument("-l", "--lockfile", help="using conan.lock", action="store_true")
|
|
93
|
+
parser.add_argument("-ul", "--update_lockfile", help="update conan.lock", action="store_true")
|
|
94
|
+
parser.add_argument("-p", "--product", help="product name, default product is `default`", default="default")
|
|
95
|
+
targets = Config.target_list()
|
|
96
|
+
target_help = "build target:"
|
|
97
|
+
for tgt, _ in targets.items():
|
|
98
|
+
target_help += "\n* " + tgt[:-4]
|
|
99
|
+
parser.add_argument("-t", "--target", help=target_help, default="default")
|
|
100
|
+
return parser
|
|
101
|
+
|
|
102
|
+
def get_manifest_config(self, key: str, default=None):
|
|
103
|
+
"""从manifest中读取配置"""
|
|
104
|
+
manifest = self.load_manifest()
|
|
105
|
+
keys = key.split("/")
|
|
106
|
+
for k in keys:
|
|
107
|
+
manifest = manifest.get(k, None)
|
|
108
|
+
if manifest is None:
|
|
109
|
+
return default
|
|
110
|
+
return manifest
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def merge_cfg(dst, src):
|
|
114
|
+
"""合并两个配置项"""
|
|
115
|
+
# 如果源为None,则返回dst
|
|
116
|
+
if src is None:
|
|
117
|
+
return dst
|
|
118
|
+
# 如果目标是空的,直接返回src
|
|
119
|
+
if not dst:
|
|
120
|
+
return src
|
|
121
|
+
# 如果是数组、标量的直接覆盖dst即可,所以返回src
|
|
122
|
+
if not isinstance(dst, dict):
|
|
123
|
+
return src
|
|
124
|
+
# 如果目标非空,但源是空,直接返回目标
|
|
125
|
+
if not src:
|
|
126
|
+
return dst
|
|
127
|
+
if not isinstance(src, dict):
|
|
128
|
+
raise Exception(f"Merge configuration failed, source config {src} is not a dictionary")
|
|
129
|
+
output = {}
|
|
130
|
+
for key, val in src.items():
|
|
131
|
+
dst_val = dst.get(key)
|
|
132
|
+
output[key] = Config.merge_cfg(dst_val, val)
|
|
133
|
+
for key, val in dst.items():
|
|
134
|
+
# 已经合并过,即目标中存在,但源不存在,直接合并
|
|
135
|
+
if key in output:
|
|
136
|
+
continue
|
|
137
|
+
output[key] = val
|
|
138
|
+
return output
|
|
139
|
+
|
|
140
|
+
def get_product_config(self, key: str, default=None):
|
|
141
|
+
"""获取产品配置,注意,key只需要基于products/[name]/即可,如 toolchain"""
|
|
142
|
+
global_cfg = self.get_manifest_config(key, default)
|
|
143
|
+
key = f"products/{self.product}/{key}"
|
|
144
|
+
product_cfg = self.get_manifest_config(key, None)
|
|
145
|
+
if not product_cfg:
|
|
146
|
+
return global_cfg
|
|
147
|
+
if not global_cfg:
|
|
148
|
+
return product_cfg
|
|
149
|
+
return Config.merge_cfg(global_cfg, product_cfg)
|
|
150
|
+
|
|
151
|
+
def load_manifest(self):
|
|
152
|
+
"""加载manifest.yml并验证schema文件"""
|
|
153
|
+
template = {}
|
|
154
|
+
template["code_path"] = self.code_path
|
|
155
|
+
template["temp_path"] = self.temp_path
|
|
156
|
+
template["download_path"] = os.path.join(self.download_path)
|
|
157
|
+
return load_yml_with_json_schema_validate(self.manifest, "/usr/share/litebmc/schema/pdf.v1.json", **template)
|
|
158
|
+
|
|
159
|
+
def check_product(self):
|
|
160
|
+
products = self.get_manifest_config("products", {})
|
|
161
|
+
if products.get(self.product):
|
|
162
|
+
return
|
|
163
|
+
log.error(f"Only the following products are supported:")
|
|
164
|
+
for key, _ in products.items():
|
|
165
|
+
log.info(" * " + key)
|
|
166
|
+
# todo: 待manifest.yml整改到位到删除下面注释
|
|
167
|
+
# raise Exception(f"Unkown product {self.product}")
|
|
168
|
+
|
|
169
|
+
def refresh_profile_name(self):
|
|
170
|
+
self.profile_host = self.get_product_config("toolchain/profile/name", "litebmc")
|
|
171
|
+
|
|
172
|
+
def set_build_type(self, value):
|
|
173
|
+
self.build_type = value
|
|
174
|
+
|
|
175
|
+
def deal_conf(self, config_dict):
|
|
176
|
+
"""
|
|
177
|
+
处理Target级别的配置"target_config"
|
|
178
|
+
当Config类有set_xxx类方法时,则可以在target文件中配置xxx
|
|
179
|
+
"""
|
|
180
|
+
if not config_dict or not isinstance(config_dict, dict):
|
|
181
|
+
return
|
|
182
|
+
for key, conf in config_dict.items():
|
|
183
|
+
try:
|
|
184
|
+
method = getattr(self, f"set_{key}")
|
|
185
|
+
method(conf)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
raise Exception(f"目标 config 无效配置: {key}") from e
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
import traceback
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from multiprocessing import Process
|
|
10
|
+
from multiprocessing import Manager
|
|
11
|
+
from lbkit.errors import LiteBmcException
|
|
12
|
+
from lbkit.tools import Tools
|
|
13
|
+
from lbkit.tasks.config import Config
|
|
14
|
+
from lbkit.utils.env_detector import EnvDetector
|
|
15
|
+
import lbkit.misc as misc
|
|
16
|
+
from lbkit.misc import load_yml_with_json_schema_validate
|
|
17
|
+
|
|
18
|
+
# 任务失败状态
|
|
19
|
+
TASK_STATUS_FAILED = "Failed"
|
|
20
|
+
TASK_STATUS_SUCCED = "succed"
|
|
21
|
+
TASK_STATUS_EXCEPT = "Except"
|
|
22
|
+
TASK_STATUS_RUNNING = "Runing"
|
|
23
|
+
tool = Tools("executor")
|
|
24
|
+
log = tool.log
|
|
25
|
+
manager = Manager()
|
|
26
|
+
status_dict = manager.dict()
|
|
27
|
+
status_lock = manager.Lock()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def wait_finish(target_name, wait_list):
|
|
31
|
+
"""
|
|
32
|
+
等待任务结束
|
|
33
|
+
"""
|
|
34
|
+
if not wait_list:
|
|
35
|
+
return True
|
|
36
|
+
start_time = time.time()
|
|
37
|
+
cnt = 0
|
|
38
|
+
while True:
|
|
39
|
+
finish = True
|
|
40
|
+
time.sleep(0.1)
|
|
41
|
+
for work_name in wait_list:
|
|
42
|
+
cur_time = time.time()
|
|
43
|
+
key = target_name + "/" + work_name
|
|
44
|
+
status = status_dict.get(key)
|
|
45
|
+
if status is None:
|
|
46
|
+
log.warn(f"等待不存在的任务{key}。如果要等待一个任务,这个任务必须在当前任务之前运行,否则触发异常")
|
|
47
|
+
return False
|
|
48
|
+
if status == TASK_STATUS_SUCCED:
|
|
49
|
+
continue
|
|
50
|
+
if status == TASK_STATUS_FAILED or status == TASK_STATUS_EXCEPT:
|
|
51
|
+
return False
|
|
52
|
+
finish = False
|
|
53
|
+
# 每等待60s打印一次日志
|
|
54
|
+
if int(cur_time - start_time) >= 60:
|
|
55
|
+
start_time = time.time()
|
|
56
|
+
cnt += 60
|
|
57
|
+
log.info("目标 {} 正在等待任务: {}, 当前已等待 {} 秒".format(target_name, work_name, cnt))
|
|
58
|
+
break
|
|
59
|
+
if finish:
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TaskExecutor():
|
|
64
|
+
'''
|
|
65
|
+
'''
|
|
66
|
+
def __init__(self, target_name, work, config: Config):
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.work = work
|
|
69
|
+
self.config: Config = config
|
|
70
|
+
self.target_name = target_name
|
|
71
|
+
self.work_name = self.work.get("task", "")
|
|
72
|
+
self.status_key = target_name + "/" + self.work_name
|
|
73
|
+
chunks = self.work_name.split(".", -1)
|
|
74
|
+
if len(chunks) == 1:
|
|
75
|
+
self.task_path = ""
|
|
76
|
+
self.work_name = chunks[1]
|
|
77
|
+
else:
|
|
78
|
+
self.task_path = "lbkit.tasks." + chunks[0]
|
|
79
|
+
self.work_name = chunks[1]
|
|
80
|
+
self.exception = None
|
|
81
|
+
|
|
82
|
+
def load_class(self):
|
|
83
|
+
if not self.task_path:
|
|
84
|
+
return None
|
|
85
|
+
log.debug("工作路径: {}".format(self.task_path))
|
|
86
|
+
work_py_file = importlib.import_module(self.task_path)
|
|
87
|
+
return getattr(work_py_file, "TaskClass")
|
|
88
|
+
|
|
89
|
+
def run(self):
|
|
90
|
+
'''
|
|
91
|
+
功能描述:执行任务
|
|
92
|
+
'''
|
|
93
|
+
work_name = self.work_name
|
|
94
|
+
log.debug(f"任务{self.status_key}已就绪")
|
|
95
|
+
ret = wait_finish(self.target_name, self.work.get("wait"))
|
|
96
|
+
if not ret:
|
|
97
|
+
log.debug(f"任务{self.status_key}等待的其它任务发生错误")
|
|
98
|
+
return -1
|
|
99
|
+
work_class = self.load_class()
|
|
100
|
+
# 如果未指定类时,不需要执行
|
|
101
|
+
if work_class is not None:
|
|
102
|
+
work_x = work_class(self.config, work_name)
|
|
103
|
+
# work配置项和target配置项
|
|
104
|
+
work_config = self.work.get("config")
|
|
105
|
+
work_x.deal_conf(work_config)
|
|
106
|
+
with status_lock:
|
|
107
|
+
status = status_dict.get(self.status_key)
|
|
108
|
+
if status is None:
|
|
109
|
+
status_dict[self.status_key] = TASK_STATUS_RUNNING
|
|
110
|
+
if status is None:
|
|
111
|
+
# 创建进程并且等待完成或超时
|
|
112
|
+
ret = work_x.run()
|
|
113
|
+
if ret is not None and ret != 0:
|
|
114
|
+
return -1
|
|
115
|
+
else:
|
|
116
|
+
# 不需要创建进程,等待任务执行完成即可
|
|
117
|
+
wait_list = []
|
|
118
|
+
wait_list.append(work_name)
|
|
119
|
+
ret = wait_finish(self.target_name, wait_list)
|
|
120
|
+
if not ret:
|
|
121
|
+
log.debug(f"任务{self.status_key}等待的其它任务发生错误")
|
|
122
|
+
return -1
|
|
123
|
+
|
|
124
|
+
log.debug(f"任务 {work_name} 开始安装步骤")
|
|
125
|
+
|
|
126
|
+
# 创建子任务
|
|
127
|
+
ret = exec_works(self.work.get("subtasks", []), self.config, os.cpu_count())
|
|
128
|
+
ret = ret and exec_works(self.work.get("seqtasks", []), self.config, 1)
|
|
129
|
+
if not ret:
|
|
130
|
+
status_dict[self.status_key] = TASK_STATUS_FAILED
|
|
131
|
+
return -1
|
|
132
|
+
|
|
133
|
+
log.success(f"任务 {work_name} 完成")
|
|
134
|
+
with status_lock:
|
|
135
|
+
status_dict[self.status_key] = TASK_STATUS_SUCCED
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
def task_handler(te:TaskExecutor):
|
|
139
|
+
try:
|
|
140
|
+
ret = te.run()
|
|
141
|
+
except Exception as e:
|
|
142
|
+
log.error(traceback.print_exc())
|
|
143
|
+
log.error(f"Task {te.status_key} exit with exceiption: {str(e)}")
|
|
144
|
+
with status_lock:
|
|
145
|
+
status_dict[te.status_key] = TASK_STATUS_EXCEPT
|
|
146
|
+
return -1
|
|
147
|
+
if ret != 0:
|
|
148
|
+
with status_lock:
|
|
149
|
+
status_dict[te.status_key] = TASK_STATUS_FAILED
|
|
150
|
+
return -1
|
|
151
|
+
else:
|
|
152
|
+
with status_lock:
|
|
153
|
+
status_dict[te.status_key] = TASK_STATUS_SUCCED
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TaskInfo():
|
|
158
|
+
def __init__(self, te: TaskExecutor, proc: Process):
|
|
159
|
+
self.te = te
|
|
160
|
+
self.proc = proc
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def wait_tasks(tasks: dict[str, TaskInfo], alow_processes_alive=0):
|
|
164
|
+
while True:
|
|
165
|
+
new_results = {}
|
|
166
|
+
cnt = 0
|
|
167
|
+
killall = False
|
|
168
|
+
for key, ti in tasks.items():
|
|
169
|
+
if ti.proc.is_alive():
|
|
170
|
+
new_results[key] = ti
|
|
171
|
+
cnt += 1
|
|
172
|
+
continue
|
|
173
|
+
with status_lock:
|
|
174
|
+
status = status_dict.get(ti.te.status_key)
|
|
175
|
+
if status == TASK_STATUS_EXCEPT:
|
|
176
|
+
killall = True
|
|
177
|
+
break
|
|
178
|
+
if killall:
|
|
179
|
+
for _, ti in tasks.items():
|
|
180
|
+
if ti.proc.is_alive():
|
|
181
|
+
ti.proc.terminate()
|
|
182
|
+
return None, False
|
|
183
|
+
tasks = new_results
|
|
184
|
+
if cnt > 0 and cnt >= alow_processes_alive:
|
|
185
|
+
time.sleep(0.1)
|
|
186
|
+
else:
|
|
187
|
+
return new_results, True
|
|
188
|
+
|
|
189
|
+
def exec_works(work_list, config, processes):
|
|
190
|
+
if not work_list:
|
|
191
|
+
return True
|
|
192
|
+
# 创建任务并等待完成
|
|
193
|
+
results: dict[str, TaskInfo] = {}
|
|
194
|
+
for work in work_list:
|
|
195
|
+
te = TaskExecutor(config.target, work, config)
|
|
196
|
+
result = Process(target=task_handler, args=(te, ))
|
|
197
|
+
results[te.status_key] = TaskInfo(te, result)
|
|
198
|
+
result.start()
|
|
199
|
+
results, ok = wait_tasks(results, processes)
|
|
200
|
+
if not ok:
|
|
201
|
+
return False
|
|
202
|
+
_, ok = wait_tasks(results, 0)
|
|
203
|
+
return ok
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def target_executor(config):
|
|
207
|
+
log.info(f"创建新目标 {config.target} 构建计划表")
|
|
208
|
+
manifest_target = f"{config.code_path}/targets/{config.target}.yml"
|
|
209
|
+
lbkit_target = os.path.join(misc.TARGETS_DIR, config.target + ".yml")
|
|
210
|
+
if os.path.isfile(manifest_target):
|
|
211
|
+
target_file = manifest_target
|
|
212
|
+
elif os.path.isfile(lbkit_target):
|
|
213
|
+
target_file = lbkit_target
|
|
214
|
+
else:
|
|
215
|
+
raise Exception(f"构建目标文件 [target_]{config.target}.yml 不存在")
|
|
216
|
+
|
|
217
|
+
# 读取配置
|
|
218
|
+
work_list = load_yml_with_json_schema_validate(target_file, os.path.join(misc.TARGETS_DIR, "tdf.v1.json"))
|
|
219
|
+
target_cfg = work_list.get("config", {})
|
|
220
|
+
config.deal_conf(target_cfg)
|
|
221
|
+
environments = work_list.get("env", {})
|
|
222
|
+
for key, value in environments.items():
|
|
223
|
+
log.success(f"配置环境变量 {key}: {value}")
|
|
224
|
+
os.environ[key] = value
|
|
225
|
+
# 打印任务清单
|
|
226
|
+
log.debug(f"任务列表:{work_list}")
|
|
227
|
+
# 创建任务调度器
|
|
228
|
+
ret = exec_works(work_list.get("subtasks", []), config, os.cpu_count())
|
|
229
|
+
ret = ret and exec_works(work_list.get("seqtasks", []), config, 1)
|
|
230
|
+
return ret
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class Executor(object):
|
|
234
|
+
def __init__(self, env: EnvDetector):
|
|
235
|
+
if not env.manifest:
|
|
236
|
+
raise LiteBmcException("未找到manifest.yml配置文件,当前目录不是一个合法的产品配置仓")
|
|
237
|
+
os.chdir(env.manifest.folder)
|
|
238
|
+
|
|
239
|
+
def run(self):
|
|
240
|
+
target = ""
|
|
241
|
+
succ = False
|
|
242
|
+
try:
|
|
243
|
+
config = Config(sys.argv[2:])
|
|
244
|
+
target = config.target
|
|
245
|
+
succ = target_executor(config)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
log.error(str(e))
|
|
248
|
+
raise Exception(f"任务 {target} 执行失败")
|
|
249
|
+
if succ:
|
|
250
|
+
log.success(f"任务 {target} 执行成功")
|
|
251
|
+
return 0
|
|
252
|
+
else:
|
|
253
|
+
raise Exception(f"任务 {target} 执行失败")
|
|
254
|
+
|
|
255
|
+
if __name__ == "__main__":
|
|
256
|
+
env = EnvDetector()
|
|
257
|
+
exec = Executor(env)
|
|
258
|
+
exec.run()
|