<feat>(tools): New project tools

The tool supports build project(currently only supported MDK) and runs menuconfig.
This commit is contained in:
MacRsh
2024-07-26 11:18:41 +08:00
parent ba3f400524
commit c9551080e5
9 changed files with 302 additions and 0 deletions

View File

@@ -0,0 +1 @@
from .builder import Builder

41
tools/builder/builder.py Normal file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@copyright (c) 2023-2024, MR Development Team
@license SPDX-License-Identifier: Apache-2.0
@date 2024-07-26 MacRsh First version
"""
from pathlib import Path
from .parsers import BaseParser
class Builder:
def __init__(self, projdir: Path):
# Choose suitable parser
self.__parser = self._get_parser(projdir)
if self.__parser is None:
raise ValueError("Suitable parser not found.")
self.projdir = projdir
self.incdirs = []
self.srcfiles = []
@staticmethod
def _get_parser(projdir: Path) -> BaseParser | None:
for parser_cls in BaseParser.__subclasses__():
parser = parser_cls(projdir)
if parser.can_handle(projdir):
return parser
return None
def add_include_dir(self, incdir: Path):
self.incdirs.append(incdir)
def add_source_file(self, srcfile: Path):
self.srcfiles.append(srcfile)
def build(self):
self.__parser.build(self.incdirs, self.srcfiles)

View File

@@ -0,0 +1,2 @@
from .base import BaseParser
from .mdk import MdkParser

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@copyright (c) 2023-2024, MR Development Team
@license SPDX-License-Identifier: Apache-2.0
@date 2024-07-26 MacRsh First version
"""
from pathlib import Path
class BaseParser:
def __init__(self, projdir: Path):
self.projdir = projdir
def can_handle(self, projdir: Path) -> bool:
"""
Determines whether the parser can handle the given project directory.
Args:
projdir (Path): The project directory to be checked.
Returns:
bool: True if the parser can handle the project directory, False otherwise.
"""
pass
def build(self, incdirs: list[Path], srcfiles: list[Path]):
"""
Builds the project using the given include directories and source files.
Args:
incdirs (list[Path]): The list of include directories.
srcfiles (list[Path]): The list of source files.
"""
pass

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@copyright (c) 2023-2024, MR Development Team
@license SPDX-License-Identifier: Apache-2.0
@date 2024-07-25 MacRsh First version
"""
import os
from lxml import etree
from pathlib import Path
from .base import BaseParser
class MdkParser(BaseParser):
def __init__(self, projdir: Path):
super().__init__(projdir)
self.projfile: Path | None = None
self.tree: etree.ElementTree = None
def can_handle(self, projdir: Path) -> bool:
# Look for ".uvprojx" files that can be parsed properly
for file in projdir.rglob('*.uvprojx'):
try:
self.projfile = file
self.tree = etree.parse(file)
return True
except:
continue
return False
def build(self, incdirs: list[Path], srcfiles: list[Path]):
self._add_incdirs(incdirs)
self._add_srcfiles(srcfiles)
self._save()
def _add_incdirs(self, incdirs: list[Path]):
projdir = self.projfile.parent
mdk_incdirs = self.tree.xpath("//Cads/VariousControls/IncludePath")
for incdir in incdirs:
incdir = Path(os.path.relpath(incdir, projdir)).as_posix()
if incdir not in mdk_incdirs[0].text.split(';'):
mdk_incdirs[0].text += f";{incdir}"
def _add_srcfiles(self, srcfiles: list[Path]):
projdir = self.projfile.parent
for srcfile in srcfiles:
group = Path(
os.path.relpath(Path(srcfile).parent, projdir)).relative_to(
"..").as_posix()
file = Path(os.path.relpath(srcfile, projdir))
# Add group if it doesn't exist
groups_node = self.tree.find('.//Groups')
group_node = groups_node.find(f"./Group[GroupName='{group}']")
if group_node is None:
group_node = etree.SubElement(groups_node, "Group")
group_name_node = etree.SubElement(group_node, "GroupName")
group_name_node.text = group
etree.SubElement(group_node, "Files")
# Add file if it doesn't exist
files_node = group_node.find("Files")
file_node = files_node.find(f"./File[FileName='{file.name}']")
if file_node is None:
file_node = etree.SubElement(files_node, "File")
file_path_node = etree.SubElement(file_node, "FilePath")
file_path_node.text = file.as_posix()
file_name_node = etree.SubElement(file_node, "FileName")
file_name_node.text = file.name
file_type_node = etree.SubElement(file_node, "FileType")
file_type_node.text = '1'
def _save(self):
self.tree.write(self.projfile, pretty_print=True, encoding="utf-8",
xml_declaration=True)

0
tools/config/__init__.py Normal file
View File

51
tools/config/kconfig.py Normal file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@copyright (c) 2023-2024, MR Development Team
@license SPDX-License-Identifier: Apache-2.0
@date 2023-12-17 MacRsh First version
"""
import re
from pathlib import Path
from kconfiglib import Kconfig
def generate_config(configfile: Path):
kconf = Kconfig("Kconfig", warn=False, warn_to_stderr=False)
# Load config file
kconf.load_config(".config")
kconf.write_config(".config")
kconf.write_autoconf(configfile)
with open(configfile, 'r+') as file:
content = file.read()
file.truncate(0)
file.seek(0)
# Writes file header
file.write("#ifndef __MR_CONFIG_H__\n")
file.write("#define __MR_CONFIG_H__\n\n")
# Writes cplusplus header
file.write("#ifdef __cplusplus\n")
file.write("extern \"C\" {\n")
file.write("#endif /* __cplusplus */\n\n")
# Writes the formatted context
content = content.replace("#define CONFIG_", "#define ")
content = re.sub(r'#define MR_USE_(\w+) (\d+)', r'#define MR_USE_\1',
content)
file.write(content)
# Writes cplusplus footer
file.write("\n#ifdef __cplusplus\n")
file.write("}\n")
file.write("#endif /* __cplusplus */\n\n")
# Writes file footer
file.write("#endif /* __MR_CONFIG_H__ */\n")

2
tools/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
kconfiglib==14.1.0
lxml==5.2.2

87
tools/tool.py Normal file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@copyright (c) 2023-2024, MR Development Team
@license SPDX-License-Identifier: Apache-2.0
@date 2024-07-26 MacRsh First version
"""
import sys
import logging
import argparse
import subprocess
from pathlib import Path
from builder import Builder
from config import kconfig
logging.basicConfig(level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
def _find_mrlib():
for dir in Path(__file__).parents:
if dir.name == 'mr-library':
return dir
return None
def _build(projdir: Path, incdirs: list[Path], srcfiles: list[Path]):
try:
builder = Builder(projdir)
for incdir in incdirs:
builder.add_include_dir(incdir)
for srcfile in srcfiles:
builder.add_source_file(srcfile)
builder.build()
logging.info("Build succeeded")
except Exception as e:
logging.error(f"Error during build: {e}")
exit(1)
def _run_menuconfig(configfile: Path):
# Check if Python 3.11 or higher is installed("kconfiglib" does not
# support later versions)
if sys.version_info.major >= 3 and sys.version_info.minor > 11:
logging.error("Python 3.11 or higher is required for menuconfig")
exit(1)
try:
subprocess.run(['menuconfig'], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
kconfig.generate_config(configfile)
logging.info("Menuconfig succeeded")
except Exception as e:
logging.error(f"Error during menuconfig: {e}")
def main():
# Find "mr-library"
mrlib = _find_mrlib()
if mrlib is None:
logging.error('mr-library not found')
return
# Parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('-b', '--build', action='store_true',
help='Build the project')
parser.add_argument('-m', '--menuconfig', action='store_true',
help='Run menuconfig')
args = parser.parse_args()
# Build the project
if args.build:
_build(mrlib.parent, [mrlib], list(mrlib.rglob('*.c')))
# Run menuconfig
if args.menuconfig:
_run_menuconfig(mrlib / 'include' / 'mr_config.h')
if __name__ == '__main__':
main()