Skip to content

Commit 69124c9

Browse files
committed
feat: 插件初始化支持自动检测依赖冲突问题
1 parent 06b8d8c commit 69124c9

4 files changed

Lines changed: 153 additions & 11 deletions

File tree

Pipfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ cymysql = "==0.9.1"
1111
flask-cors = "==2.1.0"
1212
requests = "==2.18.4"
1313
pipfile = "*"
14-
lin-cms = "==0.1.1a3"
15-
"oss2" = "*"
14+
lin-cms = "==0.1.1a4"
1615

1716
[dev-packages]
1817
pytest = "*"
1918

2019
[requires]
21-
python_version = "3.6"
20+
python_version = "3.6"

app/plugins/oss/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
oss2=="*"

plugin_init.py

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
:copyright: © 2019 by the Lin team.
33
:license: MIT, see LICENSE for more details.
44
"""
5+
import json
56
import re
67
from importlib import import_module
7-
8+
import subprocess
89
import os
910

1011
from app.app import create_app
@@ -51,6 +52,10 @@ def generate_path(self):
5152
}
5253

5354
def auto_install_rely(self):
55+
try:
56+
DependenciesResolve(app, self.path_info)
57+
except Exception as e:
58+
raise Exception('安装插件依赖时发生错误!\nError:' + str(e))
5459
from subprocess import CalledProcessError
5560
for name in self.path_info:
5661
filename = 'requirements.txt'
@@ -93,7 +98,7 @@ def auto_write_setting(self):
9398
try:
9499
info_mod = import_module(self.path_info[name]['plugin_info_path'])
95100
except ModuleNotFoundError as e:
96-
raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确')
101+
raise Exception(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确')
97102

98103
res = self._generate_setting(name, info_mod)
99104
setting_text[name] = res
@@ -109,9 +114,8 @@ def create_data(self):
109114
plugin_module = import_module(self.path_info[name]['plugin_path'] + '.app.__init__')
110115
dir_info = dir(plugin_module)
111116
except ModuleNotFoundError as e:
112-
raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确')
117+
raise Exception(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确或插件中是否有未安装的依赖包')
113118
if 'initial_data' in dir_info:
114-
# TODO 解决多次初始化数据重复添加的问题
115119
plugin_module.initial_data()
116120
print('插件初始化成功')
117121

@@ -158,7 +162,6 @@ def __get_all_plugins(self):
158162

159163
@classmethod
160164
def __execute_cmd(cls, cmd):
161-
import subprocess
162165
code = subprocess.check_call(cmd, shell=True, stdout=subprocess.PIPE)
163166
if code == 0:
164167
return True
@@ -206,6 +209,145 @@ def __cal_setting(new_setting, old_setting):
206209
return final_setting
207210

208211

212+
class DependenciesResolve:
213+
214+
def __init__(self, app_obj, path):
215+
self.app = app_obj
216+
self.path_info = path
217+
# 主项目的依赖关系列表
218+
self.root_graph = []
219+
# 所有插件的依赖关系列表
220+
self.plugin_graph = []
221+
# 生成主项目和插件依赖关系列表
222+
self.generate_graph()
223+
self.check_dependencies()
224+
225+
def generate_graph(self):
226+
try:
227+
p = subprocess.Popen(["pipenv", "graph", "--json"],
228+
stdout=subprocess.PIPE)
229+
230+
r = p.communicate()[0].decode('utf-8')
231+
self.root_graph = json.loads(r)
232+
233+
except subprocess.CalledProcessError as e:
234+
exit("pipenv指令未安装,请先使用pip安装命令\nError" + str(e))
235+
236+
except Exception as e:
237+
exit("pipenv 错误,请检测你的pipenv配置!\nError:" + str(e))
238+
239+
self.__generate_plugin_graph()
240+
241+
def check_dependencies(self):
242+
for package in self.root_graph:
243+
# 验证顶级包是否符合规范
244+
self.__check_top_dependencies(package['package'])
245+
246+
# 验证子包是否符合规范
247+
self.__check_sub_dependencies(package['dependencies'])
248+
249+
def __generate_plugin_graph(self):
250+
for name, val in self.path_info.items():
251+
# 首先去校验插件依赖于住项目的依赖是否存在冲突
252+
plugin_path = self.path_info[name]['plugin_path'].replace(
253+
'.', '/').replace('app', '')
254+
requirements_path = self.app.config.root_path + \
255+
plugin_path + '/requirements.txt'
256+
with open(requirements_path, 'r') as f:
257+
while True:
258+
259+
# 正则匹配requirements的每一行的信息
260+
line = f.readline()
261+
if not line:
262+
break
263+
264+
pattern = '(.*?)(==|<=|>=|!=|>|<)(.*)'
265+
search_obj = re.search(pattern, line)
266+
if search_obj:
267+
268+
package_name = search_obj.group(1)
269+
condition = search_obj.group(2)
270+
version = search_obj.group(3)
271+
key = search_obj.group(1).lower()
272+
273+
plugin_package = dict({'package': {}})
274+
plugin_package['package']['key'] = key
275+
plugin_package['package']['package_name'] = package_name
276+
plugin_package['package']['version'] = version
277+
plugin_package['package']['condition'] = condition
278+
plugin_package['package']['plugin_name'] = name
279+
self.plugin_graph.append(plugin_package)
280+
281+
def __check_top_dependencies(self, top_package):
282+
for plugin_package in self.plugin_graph:
283+
# top_version = top_package['installed_version']
284+
# plugin_version = plugin_package['version']
285+
if top_package['key'] == plugin_package['package']['key']:
286+
err_msg = '由于项目主目录已经存在在包' \
287+
'' + top_package['package_name'] + ',但 ' + plugin_package['package']['plugin_name']\
288+
+ ' 插件尝试重复安装不同版本,请尝试手动去掉该插件的requirements.txt中的包'
289+
raise Exception(err_msg)
290+
291+
def __check_sub_dependencies(self, dep_package):
292+
# 判断插件中要安装的依赖,是否符合主项目已安装的依赖规定的范围
293+
for dependence in dep_package:
294+
required_version = dependence['required_version']
295+
dep_name = dependence['key']
296+
297+
if required_version is not None:
298+
version_infos = required_version.split(",")
299+
for version_info in version_infos:
300+
pattern = '(>=|<=|!=|==|<|>)(.*)'
301+
search_obj = re.search(pattern, version_info)
302+
condition = search_obj.group(1)
303+
version = int(search_obj.group(2).replace('.', ''))
304+
305+
for plugin_package in self.plugin_graph:
306+
name = plugin_package['package']['key']
307+
if dep_name == name:
308+
plugin_package_version = int(plugin_package['package']['version'].replace('.', ''))
309+
err_msg = plugin_package['package']['plugin_name'] + '插件的依赖 ' + name +\
310+
' 与主项目依赖版本发生冲突! 请自行手动解决'
311+
if condition == '>=':
312+
if plugin_package_version >= version:
313+
pass
314+
else:
315+
raise Exception(err_msg)
316+
317+
elif condition == '==':
318+
if plugin_package_version == version:
319+
pass
320+
else:
321+
raise Exception(err_msg)
322+
323+
elif condition == '!=':
324+
if plugin_package_version != version:
325+
pass
326+
else:
327+
raise Exception(err_msg)
328+
329+
elif condition == '<=':
330+
if plugin_package_version <= version:
331+
pass
332+
else:
333+
raise Exception(err_msg)
334+
335+
elif condition == '<':
336+
if plugin_package_version < version:
337+
pass
338+
else:
339+
raise Exception(err_msg)
340+
341+
elif condition == '>':
342+
if plugin_package_version > version:
343+
pass
344+
else:
345+
raise Exception(err_msg)
346+
else:
347+
pass
348+
349+
209350
if __name__ == '__main__':
351+
app = create_app(register_all=False)
210352
plugin_name = input('请输入要初始化的插件名,如果多个插件请使用空格分隔插件名,输入*表示初始化所有插件:\n')
211353
PluginInit(plugin_name)

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
certifi==2018.11.29
1+
certifi==2019.3.9
22
chardet==3.0.4
33
Click==7.0
44
cymysql==0.9.1
@@ -10,8 +10,8 @@ Flask-WTF==0.14.2
1010
idna==2.6
1111
itsdangerous==1.1.0
1212
Jinja2==2.10
13-
Lin-CMS==0.1.1a3
14-
MarkupSafe==1.1.0
13+
Lin-CMS==0.1.1a4
14+
MarkupSafe==1.1.1
1515
pipfile==0.0.2
1616
PyJWT==1.7.1
1717
requests==2.18.4

0 commit comments

Comments
 (0)