Files
E3/20250218-ETH-CAN_loadV1.3/e3_176_ref/tools/misc/project_verify.py
2025-11-14 21:41:29 +08:00

744 lines
34 KiB
Python

# -*- coding: utf-8 -*-
import argparse
import traceback
import sys
import os
import re
import xml.etree.ElementTree as ET
class ParserBase(object):
def __init__(self):
self.result = True
def __del__(self):
pass
def get_result(self):
return self.result
class ParserBaseEx(ParserBase):
def __init__(self, ssdk_dir, project):
super().__init__()
self.prj_dir, _ = os.path.split(project)
self.prj_dir = self.prj_dir.replace("\\", '/')
self.board_map = {
"e3_gateway": "E3640",
"e3_324_dev_kit": "E3640",
"e3_display": "E3340",
"e3_324_ref_display": "E3340",
"d3_gateway": "D3248",
"d3_display": "D3248",
"e3_176_ref": "E3110",
"e3_144_ref": "E3205",
}
self.skip_boards = ["e3_master_slave"]
self.boards_dir = ssdk_dir.replace('\\', '/')
if not self.boards_dir.endswith('/'):
self.boards_dir += '/'
self.boards_dir += 'boards/'
self.board_name = self.prj_dir.replace(self.boards_dir, '').split('/')[0]
def __del__(self):
pass
def is_skip_board(self):
if self.board_name.lower() in self.skip_boards:
return True
return False
class CustomArgvarsParser(ParserBaseEx):
def __init__(self, ssdk_dir, custom_argvars):
super().__init__(ssdk_dir, custom_argvars)
self.custom_argvars = custom_argvars.replace("\\", '/')
self.project_name = self.prj_dir.replace(self.boards_dir, '').split('/')[-2]
self._load_xml()
# print("project_name", self.project_name)
# print("board_name=", self.board_name)
def __del__(self):
pass
def check_part_id(self):
part_id = self.get_part_id()
if len(part_id) == 0:
print(" [Error] '%s' : PART_ID macro is undefined" % self.custom_argvars)
self.result = False
elif self.board_name in self.board_map.keys()and not (part_id == self.board_map[self.board_name]):
print(" [Error] '%s' : PART ID [%s] is invalid, it must be configured as '%s'" %
(self.custom_argvars, part_id, self.board_map[self.board_name]))
self.result = False
return True
def _load_xml(self):
data = open(self.custom_argvars).read()
index = data.find('<?xml')
if not index == -1:
data = data[index:]
self.root = ET.fromstring(data)
# self.tree = ET.parse(custom_argvars)
# self.root = self.tree.getroot()
def get_part_id(self):
for variable in self.root.find("group"):
# print(variable.find('name').text)
# print(variable.find('value').text)
if variable.find('name').text == 'PART_ID':
return variable.find('value').text
return ""
def get_board_name(self):
return self.board_name
def get_cord_name(self):
return self.project_name
class EwpParser(ParserBase):
def __init__(self, ewp, board, core, part_id):
super().__init__()
self.ewp = ewp.replace("\\", '/')
self.board = board
self.core = core
self.part_id = part_id
self.prj_dir, _ = os.path.split(self.ewp)
self.tree = ET.parse(ewp)
self.root = self.tree.getroot()
self.sfs_map = {
"e3_gateway": "sfs_s26h-hyperflash.img",
"e3_324_dev_kit": "sfs_s26h-hyperflash.img",
"e3_display": "sfs_mt35-1-1-1.img",
"e3_324_ref_display": "sfs_mt35-1-1-1.img",
"d3_display": "sfs_mt35-1-1-1.img",
"d3_gateway": "sfs_mt35-1-1-1.img",
"e3_176_ref": "sfs_mt35-1-1-1.img",
"e3_144_ref": "sfs_mt35-1-1-1.img",
}
self.out_map = {
'bootloader': 'bootloader.bin',
"sf": "sf.bin",
"sp0": "sp0.bin",
"sp1": "sp1.bin",
"sx0": "sx0.bin",
"sx1": "sx1.bin",
}
self.nor_flash_boards = [
"d3_display",
"d3_gateway",
"e3_display",
"e3_144_ref"
]
def __del__(self):
pass
def _is_sf_core(self):
if self.core not in ["sp0", "sp1", "sx0", "sx1"]:
return True
return False
def _is_xip_hyper_flash(self):
if not -1 == self.ewp.lower().find('xip/') and -1 == self.ewp.lower().find('iar_norflash'):
return True
return False
def _is_hyper_flash_driver(self):
if not -1 == self.ewp.lower().find('hyperbus/'):
return True
return False
def _is_nor_flash_driver(self):
if not -1 == self.ewp.lower().find('spi_nor/'):
return True
return False
def _is_xip_nor_flash(self):
if not -1 == self.ewp.lower().find('xip/') and not -1 == self.ewp.lower().find('iar_norflash'):
return True
return False
def check_options(self):
# project/configuration/name
_conditions = {
'IlinkAutoLibEnable': '1',
'GenLowLevelInterface': '0'
}
# 'OOCObjCopyEnable': 1 # debug
# 'OOCObjCopyEnable': 0 # flashdebug
for config in self.root.findall("configuration"):
_config = config.find('name').text
print(config.find('name').text, 'version')
for setting in config.findall("settings"):
# print(" ", setting.find('name').text)
if setting.find('name').text.upper() == 'ILINK':
for option in setting.findall("data/option"):
if option.find('name').text == 'IlinkAutoLibEnable':
'''
<option>
<name>IlinkAutoLibEnable</name>
<state>1</state>
</option>
'''
# print(" ", option.find('name').text)
# print(" ", option.find('state').text)
if not option.find('state').text == '1':
print(" [Error] '%s' : IlinkAutoLibEnable is %s, it must be configured as 1" % (self.ewp, option.find('state').text))
self.result = False
elif option.find('name').text == 'IlinkExtraOptions':
'''
<option>
<name>IlinkExtraOptions</name>
<state>--image_input $PROJ_DIR$\..\..\..\..\..\tools\sdtools\sfs\sfs_s26h-hyperflash.img,SFS_BIN,SFS_BIN,8</state>
<state>--keep SFS_BIN</state>
</option>
'''
error_des = ""
if _config.lower() == 'flashdebug':
if self.board not in self.sfs_map:
sfs_result = True # skip undefined sfs map
continue
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, it must be configured as %s" % (self.ewp, self.sfs_map[self.board])
sfs_result = False
for state in option.findall('state'):
# print(" ", state.text)
if self._is_sf_core():
if self._is_hyper_flash_driver():
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, it must be configured as sfs_s26h-hyperflash.img" % self.ewp
if state.text and len(state.text) and not -1 == state.text.find('sfs_s26h-hyperflash.img'):
sfs_result = True
error_des = ""
break
elif self._is_nor_flash_driver():
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, it must be configured as sfs_mt35-1-1-1.img" % self.ewp
if state.text and len(state.text) and not -1 == state.text.find('sfs_mt35-1-1-1.img'):
sfs_result = True
error_des = ""
break
elif self.board in self.nor_flash_boards:
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, it must be configured as sfs_mt35-1-1-1.img" % self.ewp
if state.text and len(state.text) and not -1 == state.text.find('sfs_mt35-1-1-1.img'):
sfs_result = True
error_des = ""
break
elif self._is_xip_hyper_flash():
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, it must be configured as sfs_s26h-hyperflash.img" % self.ewp
if state.text and len(state.text) and not -1 == state.text.find('sfs_s26h-hyperflash.img'):
sfs_result = True
error_des = ""
break
elif self._is_xip_nor_flash():
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, it must be configured as sfs_mt35-1-1-1.img" % self.ewp
if state.text and len(state.text) and not -1 == state.text.find('sfs_mt35-1-1-1.img'):
sfs_result = True
error_des = ""
break
else:
if state.text and len(state.text) and not -1 == state.text.find(self.sfs_map[self.board]):
sfs_result = True
error_des = ""
break
else:
sfs_result = True
if state.text and len(state.text) and not (-1 == state.text.find('SFS_BIN,')):
sfs_result = False
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, does not need to configure sfs for %s core" % (self.ewp, self.core)
break
else: # debug verison
sfs_result = True
for state in option.findall('state'):
if state.text and len(state.text) and not (-1 == state.text.find('SFS_BIN,')):
sfs_result = False
error_des = " [Error] '%s' : IlinkExtraOptions (sfs) is invalid, does not need to configure sfs for debug version" % self.ewp
break
if not sfs_result:
print(" ", option.find('name').text)
for state in option.findall('state'):
print(" ", state.text)
print(error_des)
self.result = False
elif setting.find('name').text == 'General':
for option in setting.findall("data/option"):
if option.find('name').text == 'GenLowLevelInterface':
'''
<option>
<name>GenLowLevelInterface</name>
<state>0</state>
</option>
'''
# print(" ", option.find('name').text)
# print(" ", option.find('state').text)
if not option.find('state').text == '0':
print(" [Error] '%s' : GenLowLevelInterface is %s, it must be configured as 0" % (self.ewp, option.find('state').text))
self.result = False
elif setting.find('name').text == 'OBJCOPY':
for option in setting.findall("data/option"):
if option.find('name').text == 'OOCObjCopyEnable' and _config.lower() == 'debug':
'''
<option>
<name>OOCObjCopyEnable</name>
<state>1</state>
</option>
'''
# print(" ", option.find('name').text)
# print(" ", option.find('state').text)
if not option.find('state').text == '1':
print(" [Error] '%s' : OOCObjCopyEnable is %s, it must be configured as 1 for Debug version" % (self.ewp, option.find('state').text))
self.result = False
elif option.find('name').text == 'OOCOutputFile':
'''
<option>
<name>OOCOutputFile</name>
<state>sf.bin</state>
</option>
'''
# print(" ", option.find('name').text)
# print(" ", option.find('state').text)
if not _config.lower() == 'flashdebug' and \
option.find('state').text:
if self.core in self.out_map.keys() and not option.find('state').text == self.out_map[self.core]:
print(" [Error] '%s' : OOCOutputFile is %s, it must be configured as %s" %
(self.ewp, option.find('state').text, self.out_map[self.core]))
self.result = False
elif self.core not in self.out_map.keys() and not option.find('state').text == self.out_map['sf']:
print(" [Error] '%s' : OOCOutputFile is %s, it must be configured as %s" %
(self.ewp, option.find('state').text, self.out_map['sf']))
self.result = False
def check_files(self):
for group in self.root.findall("group"):
# print(group.find('name').text)
self._enum_group(group)
def _check_file(self, file):
if -1 == file.find('$PROJ_DIR$'):
print(" [Error] \'%s\' is not a relative path" % file)
self.result = False
return False
_file = file.replace('$PROJ_DIR$', self.prj_dir)
_file = _file.replace('$PART_ID$', self.part_id)
_file = _file.replace("\\", '/')
if not os.path.exists(_file):
print(" [Error] \'%s\' : \'%s\' don't exist" % (self.ewp, file))
self.result = False
return False
match = re.search('^.+/devices/([DE]\d+)/.+$', file.replace("\\", '/'), re.I)
if match:
print(" [Error] \'%s\' - \'%s\' : The part id can not use fixed value [%s], must be used macro definition" %
(self.ewp, file, match.group(1)))
self.result = False
return False
return True
def _enum_group(self, parent):
# print(">>>", parent.find('name').text)
for file in parent.findall("file"):
# print(" ", file.find('name').text)
_file = file.find('name').text
self._check_file(_file)
for sub_group in parent.findall("group"):
self._enum_group(sub_group)
class EclipsePrjParser(ParserBaseEx):
def __init__(self, ssdk_dir, project):
super().__init__(ssdk_dir, project)
self.project = project.replace("\\", '/')
self.prj_dir, _ = os.path.split(self.project)
self.tree = ET.parse(self.project)
self.root = self.tree.getroot()
self.PART_PATH = ""
self.part_id = ""
self.macro_map = {}
def __del__(self):
pass
def check_project_name(self):
# print(self.prj_dir.split('/'))
if not self.prj_dir.split('/')[-2] == self.root.find('name').text:
print(" [Error] '%s' : Project name is incorrect, please modify '%s' to '%s'" %
(self.project, self.root.find('name').text, self.prj_dir.split('/')[-2]))
self.result = False
return False
return True
def check_part_id(self):
if self.root.find('variableList/variable') is None:
print(" [Error] '%s' : Undefined PART_ID macro" % self.project)
self.result = False
return False
has_part_id_macro = False
has_part_path_macro = False
for variable in self.root.findall("variableList/variable"):
# print(variable.find('name').text)
self.macro_map[variable.find('name').text] = variable.find('value').text
if variable.find('name').text == 'PART_ID':
has_part_id_macro = True
self.part_id = variable.find('value').text
elif variable.find('name').text == 'PART_PATH':
self.PART_PATH = variable.find('value').text
if not (-1 == variable.find('value').text.find('$%7BPART_ID%7D')):
has_part_path_macro = True
# print(self.macro_map)
if not has_part_id_macro:
print(" [Error] '%s' : PART_ID macro is undefined" % self.project)
self.result = False
return False
if not has_part_path_macro:
print(" [Error] '%s' : PART_PATH macro is undefined" % self.project)
self.result = False
return False
if len(self.part_id) == 0:
print(" [Error] '%s' : PART_ID macro is undefined" % self.project)
self.result = False
return False
elif self.board_name in self.board_map.keys() and not (self.part_id == self.board_map[self.board_name]):
print(" [Error] '%s' : PART ID [%s] is invalid, it must be configured as '%s'" %
(self.project, self.part_id, self.board_map[self.board_name]))
self.result = False
return False
return True
def get_part_id(self):
return self.part_id
def get_depth(self):
return self.PART_PATH
def check_files(self):
for link_src in self.root.findall("linkedResources/link"):
if link_src.find('type').text == '1':
# print(link_src.find('name').text)
# print(link_src.find('type').text)
if link_src.find('locationURI') is None:
# print('location', link_src.find('location').text)
self._check_file(link_src.find('location').text)
elif link_src.find('location') is None:
# print('locationURI', link_src.find('locationURI').text)
self._check_file(link_src.find('locationURI').text)
def _check_file(self, file):
match = re.search('^.+/devices/([DE]\d+)/.+$', file.replace("\\", '/'), re.I)
if match:
print(" [Error] \'%s\' - \'%s\' : The part id can not use fixed value [%s], must be used macro definition" %
(self.project, file, match.group(1)))
self.result = False
return False
return True
class EclipseCprjParser(ParserBaseEx):
def __init__(self, ssdk_dir, project):
super().__init__(ssdk_dir, project)
self.project = project.replace("\\", '/')
self.prj_dir, _ = os.path.split(self.project)
self.tree = ET.parse(self.project)
self.root = self.tree.getroot()
self.part_id = ""
self.out_map = {
'bootloader': 'bootloader',
"sf": "sf",
"sp0": "sp0",
"sp1": "sp1",
"sx0": "sx0",
"sx1": "sx1",
}
def __del__(self):
pass
def check_macro(self):
find = False
for macro in self.root.findall("storageModule/cconfiguration/storageModule/macros"):
# print(macro.find('stringMacro').attrib['name'])
# print(macro.find('stringMacro').attrib['value'])
self.part_id = macro.find('stringMacro').attrib['value']
find = True
if not find:
self.result = False
print(" [Error] '%s' : PART_ID macro is undefined" % self.project)
return False
if len(self.part_id) == 0:
print(" [Error] '%s' : PART_ID macro is undefined" % self.project)
self.result = False
return False
elif self.board_name in self.board_map.keys() and not (self.part_id == self.board_map[self.board_name]):
print(" [Error] '%s' : PART ID [%s] is invalid, it must be configured as '%s'" %
(self.project, self.part_id, self.board_map[self.board_name]))
self.result = False
return False
return True
def check_includes(self):
Assembler_tag = "GNU Arm Cross Assembler".lower()
CC_tag = "GNU Arm Cross C Compiler".lower()
CPP_tag = "GNU Arm Cross C++ Compiler".lower()
Include_tag = "Include paths (-I)".lower()
Linker_tag = "GNU Arm Cross C Linker".lower()
LD_tag = "Script files (-T)".lower()
Image_tag = "GNU Arm Cross Create Flash Image".lower()
Type_tag = "Output file format (-O)".lower()
superClass = "ilg.gnuarmeclipse.managedbuild.cross.tool.createflash".lower()
Bin_tag = 'ilg.gnuarmeclipse.managedbuild.cross.option.createflash.choice.binary'.lower()
for tool in self.root.findall("storageModule/cconfiguration/storageModule/configuration/folderInfo/toolChain/"):
if "name" not in tool.attrib.keys():
continue
if tool.attrib['name'].lower() in [Assembler_tag, CC_tag, CPP_tag, Linker_tag, Image_tag]:
_tool_name = tool.attrib['name'].lower()
if _tool_name == Image_tag:
if 'superClass' in tool.attrib.keys() and tool.attrib['superClass'].lower() == superClass:
_result = False
_error = " [Error] '%s' : artifactName is empty, it must be configured as %s" % \
(self.project, Bin_tag)
for option_item in list(tool): # tool.getchildren():
if "value" not in option_item.attrib.keys():
continue
if option_item.attrib['value'].lower() == Bin_tag:
_result = True
_error = ""
break
else:
# print("_attr_name", option_item.attrib['value'])
_error = " [Error] '%s' : artifactName is %s, it must be configured as %s" % \
(self.project, option_item.attrib['value'], Bin_tag)
_result = False
if not _result and len(_error):
print(_error)
self.result = False
continue
for option_item in list(tool): # tool.getchildren():
if "name" not in option_item.attrib.keys():
continue
_attr_name = option_item.attrib['name']
if _attr_name.lower() in [Include_tag, LD_tag]:
# print("_attr_name", _attr_name)
for listOptionValue in list(option_item): # option_item.getchildren():
# print(listOptionValue.attrib['value'])
self._check_file(listOptionValue.attrib['value'])
def check_output(self):
prj_name = self.prj_dir.split('/')[-2]
for config in self.root.findall("storageModule/cconfiguration/storageModule/configuration"):
if "artifactName" in config.attrib.keys():
if prj_name in self.out_map.keys() and not config.attrib['artifactName'] == self.out_map[prj_name] and \
not config.attrib['artifactName'] == '${ProjName}':
print(" [Error] '%s' : artifactName is %s, it must be configured as %s" %
(self.project, config.attrib['artifactName'], self.out_map[prj_name]))
self.result = False
elif prj_name not in self.out_map.keys() and not config.attrib['artifactName'] == self.out_map['sf'] and \
not config.attrib['artifactName'] == '${ProjName}':
print(" [Error] '%s' : artifactName is %s, it must be configured as %s" %
(self.project, config.attrib['artifactName'], self.out_map['sf']))
self.result = False
elif config.attrib['artifactName'] == '${ProjName}' and prj_name not in self.out_map.keys():
print(" [Error] '%s' : artifactName is %s, it must be configured as %s" %
(self.project, config.attrib['artifactName'], self.out_map['sf']))
self.result = False
def _check_file(self, file):
match = re.search('^.+/devices/([DE]\d+)/.+$', file.replace("\\", '/'), re.I)
if match:
print(" [Error] \'%s\' - \'%s\' : The part id can not use fixed value [%s], must be used macro definition" %
(self.project, file, match.group(1)))
self.result = False
return False
return True
class VerifyBase(object):
def __init__(self, ssdk_dir, board_dir=''):
self.ssdk_dir = ssdk_dir.replace("\\", '/')
self.board_dir = board_dir.replace("\\", '/')
self.ssdk_dir = self._rm_slash(self.ssdk_dir)
self.board_dir = self._rm_slash(self.board_dir)
if 0 == len(self.board_dir):
self.board_dir = self.ssdk_dir + '/boards'
self.result = True
def __del__(self):
pass
def _rm_slash(self, src): # remove /slash
if src.endswith('/'):
return src[:-1]
return src
def get_projects(self, project_type):
# project_type in ['IAR', 'IAR_Norflash', 'Eclipse', 'Eclipse_Norflash']
project_dir = []
for path, subdirs, files in os.walk(self.board_dir):
for sub_dir in subdirs:
if sub_dir in project_type:
_project = os.path.join(path, sub_dir)
_project = _project.replace("\\", '/')
project_dir.append(_project)
return project_dir
def get_result(self):
return self.result
class IarVerify(VerifyBase):
_iar_ext = [
'custom_argvars',
'ewd',
'ewp',
'eww'
]
def verify(self):
print("Start scanning and checking iar project ...")
_projects = self.get_projects(['IAR', 'IAR_Norflash'])
for proj in _projects:
_project_name = proj.split('/')[-2]
# print(proj, _project_name)
_tmp_prj_files = []
board = ""
core = ""
part_id = ""
# check *.custom_argvars
skip_check = False
for file in os.listdir(proj):
if file.endswith('custom_argvars'):
obj = CustomArgvarsParser(self.ssdk_dir, os.path.join(proj, file).replace("\\", '/'))
if obj.is_skip_board():
skip_check = True
continue
print("Check the custom_argvars file :", os.path.join(proj, file).replace("/", '\\'))
obj.check_part_id()
board = obj.get_board_name()
core = obj.get_cord_name()
part_id = obj.get_part_id()
if self.result:
self.result = obj.get_result()
# check *.ewp
# print("board = ", board)
# print("core = ", core)
# print("part_id = ", part_id)
if skip_check:
continue
for file in os.listdir(proj):
_tmp_prj_files.append(file.split('.')[-1])
if file.endswith('ewp'):
print("Check the ewp file :", os.path.join(proj, file).replace("/", '\\'))
obj = EwpParser(os.path.join(proj, file).replace("\\", '/'), board, core, part_id)
obj.check_options()
obj.check_files()
if self.result:
self.result = obj.get_result()
for item in self._iar_ext:
if item not in _tmp_prj_files:
print(" [Error] '%s' : Not find '%s' file" % (proj, item))
self.result = False
print('\n')
print("Finished scanning and checking iar project")
return self.result
class EclipseVerify(VerifyBase):
def verify(self):
print("Start scanning and checking eclipse project ...")
_projects = self.get_projects(['Eclipse', 'Eclipse_Norflash'])
for proj in _projects:
_project_name = proj.split('/')[-2]
# print(proj, _project_name)
obj = EclipsePrjParser(self.ssdk_dir, os.path.join(proj, '.project'))
if obj.is_skip_board():
continue
print("Check the .project file :", os.path.join(proj, '.project').replace("/", '\\'))
obj.check_project_name()
obj.check_part_id()
obj.check_files()
if self.result:
self.result = obj.get_result()
print("Check the .cproject file :", os.path.join(proj, '.cproject').replace("/", '\\'))
c_obj = EclipseCprjParser(self.ssdk_dir, os.path.join(proj, '.cproject'))
c_obj.check_macro()
c_obj.check_includes()
c_obj.check_output()
if self.result:
self.result = c_obj.get_result()
print("Finished scanning and checking eclipse project")
return self.result
class VerifyIdeProject(object):
"""Object for command-line tool."""
def __init__(self):
"""Initializer method."""
pass
def version(self, _):
"""Implements the 'version' sub-command."""
print('1.23.1.1')
def verify(self, args):
result = False
try:
iar = IarVerify(args.ssdk_dir, args.project_dir)
eclipse = EclipseVerify(args.ssdk_dir, args.project_dir)
result = iar.verify()
eclipse_result = eclipse.verify()
if result:
result = eclipse_result
except Exception:
result = False
print("[Error] %s" % traceback.format_exc())
finally:
if result:
print('Successfully verified project files.')
else:
print('[Error] Invalid project configuration found.')
sys.exit(1)
return result
def run(self, argv):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands')
sub_parser = subparsers.add_parser('version', help='Prints version of VerifyIdeProject.')
sub_parser.set_defaults(func=self.version)
sub_parser = subparsers.add_parser('verify', help="Verify IDE project for ssdk's boards.")
sub_parser.add_argument('--ssdk_dir',
help='Path to ssdk project root directory.',
type=str,
required=True)
sub_parser.add_argument('--project_dir',
help='Path to ide project directory.',
type=str,
default="")
sub_parser.set_defaults(func=self.verify)
args = parser.parse_args(argv[1:])
try:
func = args.func
except AttributeError:
parser.error("invalid arguments")
return func(args)
# python project_verify.py verify --ssdk_dir D:\SDRapidStudio\ide\workspace\2.1
# python project_verify.py verify --ssdk_dir D:\SDRapidStudio\ide\workspace\2.1 --project_dir D:\SDRapidStudio\ide\workspace\2.1\boards\e3_gateway
if __name__ == '__main__':
tool = VerifyIdeProject()
tool.run(sys.argv)