Skip to content

Commit 00da5a9

Browse files
szhen11acrnsi
authored andcommitted
acrn-config: web UI app for acrn-config tool
The web UI application for ACRN VM configuration tool based on Flask web framework: 1. import board info xml which is generated by acrn-config/target on target boards; or select one board from the imported list of UI. 2. load scenario settings or import scenario settings from local. 3. edit scenario settings from web UI. 4. export and save the edited scenario setting, the application will prompt error messages from UI if there are illegal configured items. 5. generate board source code, generate scenario source code. 6. load launch settings or import launch settings from local. 7. edit launch settings from web UI. 8. export and save the edited launch setting; the application will prompt error messages from UI if there are illegal configurable items 9. generate launch scripts based on current launch setting. Tracked-On: #3602 Signed-off-by: Shuang Zheng <shuang.zheng@intel.com> Reviewed-by: Victor Sun <victor.sun@intel.com> Acked-by: Terry Zou <terry.zou@intel.com>
1 parent 476e9a2 commit 00da5a9

File tree

10 files changed

+2319
-0
lines changed

10 files changed

+2319
-0
lines changed

misc/acrn-config/config_app/app.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (C) 2019 Intel Corporation.
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
4+
"""Entry for config app.
5+
6+
"""
7+
8+
import os
9+
import sys
10+
import threading
11+
import webbrowser
12+
13+
# flask: Copyright 2010 Pallets
14+
# SPDX-License-Identifier: BSD-3-Clause
15+
# Refer to https://github.com/pallets/flask/blob/master/LICENSE.rst for the permission notice.
16+
from flask import Flask
17+
18+
# flask: Copyright (c) 2013, Marc Brinkmann
19+
# SPDX-License-Identifier: BSD-3-Clause
20+
# Refer to https://pypi.org/project/Flask-Bootstrap/ for the permission notice.
21+
from flask_bootstrap import Bootstrap
22+
23+
import configs
24+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
25+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'library'))
26+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..',
27+
'board_config'))
28+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..',
29+
'scenario_config'))
30+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..',
31+
'launch_config'))
32+
from views import CONFIG_APP
33+
34+
APP = Flask(__name__)
35+
APP.config.from_object(configs)
36+
APP.register_blueprint(CONFIG_APP)
37+
APP.jinja_env.add_extension('jinja2.ext.do')
38+
Bootstrap(app=APP)
39+
40+
if __name__ == '__main__':
41+
URL = "http://127.0.0.1:5001/scenario"
42+
threading.Timer(1, lambda: webbrowser.open(URL)).start()
43+
APP.run(port=5001, debug=False)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (C) 2019 Intel Corporation.
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
4+
"""Configurations for config app.
5+
6+
"""
7+
8+
import os
9+
10+
BOARD_INFO = None
11+
BOARD_TYPE = None
12+
SCENARIO = None
13+
CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'xmls', 'config-xmls')
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# Copyright (C) 2019 Intel Corporation.
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
4+
"""Controller for config app.
5+
6+
"""
7+
8+
import os
9+
import xml.etree.ElementTree as ElementTree
10+
11+
12+
class XmlConfig:
13+
"""The core class to analyze and modify acrn config xml files"""
14+
def __init__(self, path=None, default=True):
15+
self._xml_path = path
16+
self._default = default
17+
self._curr_xml = None
18+
self._curr_xml_tree = None
19+
20+
@staticmethod
21+
def _get_xml_type(xml_file):
22+
"""
23+
get the config type by file.
24+
:param xml_file: the file path of xml file.
25+
:return: the xml type.
26+
:raises: ValueError, OSError, SyntaxError.
27+
"""
28+
xml_type = ''
29+
if os.path.splitext(xml_file)[1] != '.xml':
30+
return xml_type
31+
try:
32+
tree = ElementTree.parse(xml_file)
33+
root = tree.getroot()
34+
if 'uos_launcher' in root.attrib:
35+
xml_type = 'uos_launcher'
36+
elif 'scenario' in root.attrib:
37+
xml_type = 'scenario'
38+
elif 'board' in root.attrib:
39+
xml_type = 'board'
40+
elif 'board_setting' in root.attrib:
41+
xml_type = 'board_setting'
42+
except ValueError:
43+
print('xml parse error: {}'.format(xml_file))
44+
xml_type = ''
45+
except OSError:
46+
print('xml open error: {}'.format(xml_file))
47+
xml_type = ''
48+
except SyntaxError:
49+
print('xml syntax error: {}'.format(xml_file))
50+
xml_type = ''
51+
52+
return xml_type
53+
54+
def list_all(self, xml_type=None):
55+
"""
56+
list all xml config files by type.
57+
:param xml_type: the xml type.
58+
:return: he list of xml config files.
59+
"""
60+
xmls = []
61+
user_xmls = []
62+
63+
if self._xml_path is None or not os.path.exists(self._xml_path):
64+
return xmls, user_xmls
65+
for test_file in os.listdir(self._xml_path):
66+
test_file_path = os.path.join(self._xml_path, test_file)
67+
if os.path.isfile(test_file_path):
68+
if XmlConfig._get_xml_type(test_file_path) == xml_type:
69+
xmls.append(os.path.splitext(test_file)[0])
70+
user_path = os.path.join(self._xml_path, 'user_defined')
71+
if os.path.isdir(user_path):
72+
for test_file in os.listdir(user_path):
73+
test_file_path = os.path.join(user_path, test_file)
74+
if os.path.isfile(test_file_path):
75+
if XmlConfig._get_xml_type(test_file_path) == xml_type:
76+
user_xmls.append(os.path.splitext(test_file)[0])
77+
78+
return xmls, user_xmls
79+
80+
def set_curr(self, xml):
81+
"""
82+
set current xml file to analyze.
83+
:param xml: the xml file.
84+
:return: None.
85+
:raises: ValueError, OSError, SyntaxError.
86+
"""
87+
if self._xml_path is None:
88+
return
89+
try:
90+
self._curr_xml = xml
91+
92+
xml_path = os.path.join(self._xml_path, self._curr_xml + '.xml') \
93+
if self._default \
94+
else os.path.join(self._xml_path, 'user_defined', self._curr_xml + '.xml')
95+
96+
tree = ElementTree.parse(xml_path)
97+
self._curr_xml_tree = tree
98+
except ValueError:
99+
print('xml parse error: {}'.format(xml))
100+
self._curr_xml = None
101+
self._curr_xml_tree = None
102+
except OSError:
103+
print('xml open error: {}'.format(xml))
104+
self._curr_xml = None
105+
self._curr_xml_tree = None
106+
except SyntaxError:
107+
print('xml syntax error: {}'.format(xml))
108+
self._curr_xml = None
109+
self._curr_xml_tree = None
110+
111+
def get_curr(self):
112+
"""
113+
get current xml config file.
114+
:return: current xml config file name.
115+
"""
116+
return self._curr_xml
117+
118+
def get_curr_root(self):
119+
"""
120+
get the xml root of current xml config file.
121+
:return: the xml root of current xml config file.
122+
"""
123+
if self._curr_xml_tree is None:
124+
return None
125+
return self._curr_xml_tree.getroot()
126+
127+
def get_curr_value(self, *args):
128+
"""
129+
get the value of the element by its path.
130+
:param args: the path of the element.
131+
:return: the value of the element.
132+
"""
133+
if self._curr_xml_tree is None:
134+
return None
135+
dest_node = self._get_dest_node(*args)
136+
if dest_node is None:
137+
return None
138+
if dest_node.text is None or dest_node.text.strip() == '':
139+
return ''
140+
return dest_node.text
141+
142+
def set_curr_value(self, value, *args):
143+
"""
144+
set the value of the element by its path.
145+
:param value: the value of the element.
146+
:param args: the path of the element.
147+
:return: None.
148+
"""
149+
if self._curr_xml_tree is None:
150+
return
151+
dest_node = self._get_dest_node(*args)
152+
dest_node.text = value
153+
154+
def set_curr_list(self, values, *args):
155+
"""
156+
set a list of sub element for the element by its path.
157+
:param values: the list of values of the element.
158+
:param args: the path of the element.
159+
:return: None.
160+
"""
161+
if self._curr_xml_tree is None:
162+
return
163+
tag = args[-1]
164+
args = args[:-1]
165+
dest_node = self._get_dest_node(*args)
166+
for node in dest_node.getchildren():
167+
dest_node.remove(node)
168+
for value in values:
169+
new_node = ElementTree.SubElement(dest_node, tag)
170+
new_node.text = value
171+
172+
def set_curr_attr(self, attr_name, attr_value, *args):
173+
"""
174+
set the attribute of the element by its path.
175+
:param attr_name: the attribute name of the element.
176+
:param attr_value: the attribute value of the element.
177+
:param args: the path of the element.
178+
:return: None.
179+
"""
180+
if self._curr_xml_tree is None:
181+
return
182+
dest_node = self._get_dest_node(*args)
183+
dest_node.attrib[attr_name] = attr_value
184+
185+
def add_curr_value(self, key, desc, value, *args):
186+
"""
187+
add a sub element for the element by its path.
188+
:param key: the tag of the sub element.
189+
:param desc: the attribute desc of the sub element.
190+
:param value: the value of the sub element.
191+
:param args: the path of the element.
192+
:return: None.
193+
"""
194+
if self._curr_xml_tree is None:
195+
return
196+
197+
dest_node = self._get_dest_node(*args)
198+
199+
if key in ['vm']:
200+
ElementTree.SubElement(dest_node, key, attrib={'id': value, 'desc': desc})
201+
else:
202+
new_node = ElementTree.SubElement(dest_node, key, attrib={'desc': desc})
203+
new_node.text = value
204+
205+
def delete_curr_key(self, *args):
206+
"""
207+
delete the element by its path.
208+
:param args: the path of the element.
209+
:return: None.
210+
"""
211+
if self._curr_xml_tree is None:
212+
return
213+
dest_node = self._get_dest_node(*args)
214+
self._curr_xml_tree.getroot().remove(dest_node)
215+
216+
def _get_dest_node(self, *args):
217+
"""
218+
get the destination element by its path.
219+
:param args: the path of the element.
220+
:return: the destination element.
221+
"""
222+
if self._curr_xml_tree is None:
223+
return None
224+
dest_node = self._curr_xml_tree.getroot()
225+
path = '.'
226+
for arg in args:
227+
# tag:attr=xxx
228+
# tag:attr
229+
# tag
230+
tag = None
231+
attr_name = None
232+
attr_value = None
233+
if ':' not in arg:
234+
tag = arg
235+
elif '=' not in arg:
236+
# tag = arg.split(':')[0]
237+
# attr_name = arg.split(':')[1]
238+
raise Exception('unsupported xml path: tag:attr')
239+
else:
240+
tag = arg.split(':')[0]
241+
attr = arg.split(':')[1]
242+
attr_name = attr.split('=')[0]
243+
attr_value = attr.split('=')[1]
244+
245+
if attr_value is None:
246+
path += ("/" + tag)
247+
else:
248+
path += ("/" + tag + "[@" + attr_name + "='" + attr_value + "']")
249+
250+
dest_node = dest_node.findall(path)
251+
if dest_node is not None and dest_node != []:
252+
return dest_node[0]
253+
254+
raise Exception('can not find node by {} from xml'.format(args))
255+
256+
def save(self, xml=None):
257+
"""
258+
save current xml to file.
259+
:param xml: the file name to save; if not specified, save current xml to default names.
260+
:return: None.
261+
"""
262+
if self._curr_xml_tree is None:
263+
return
264+
if xml is None:
265+
xml = self._curr_xml
266+
267+
xml_path = os.path.join(self._xml_path, 'user_defined')
268+
if not os.path.isdir(xml_path):
269+
os.makedirs(xml_path)
270+
271+
self._format_xml(self._curr_xml_tree.getroot())
272+
self._curr_xml_tree.write(os.path.join(xml_path, xml+'.xml'), encoding='utf-8',
273+
xml_declaration=True, method='xml')
274+
275+
def _format_xml(self, element, depth=0):
276+
i = "\n" + depth * " "
277+
if element:
278+
if not element.text or not element.text.strip():
279+
element.text = i + " "
280+
if not element.tail or not element.tail.strip():
281+
element.tail = i
282+
for element in element:
283+
self._format_xml(element, depth + 1)
284+
if not element.tail or not element.tail.strip():
285+
element.tail = i
286+
else:
287+
if depth and (not element.tail or not element.tail.strip()):
288+
element.tail = i
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Flask==1.1.1
2+
flask_bootstrap==3.3.7.1
3+

0 commit comments

Comments
 (0)