254 lines
7.7 KiB
Python
254 lines
7.7 KiB
Python
from pathlib import Path
|
|
from shutil import rmtree
|
|
from sys import argv
|
|
from argparse import ArgumentParser, Namespace
|
|
import tomllib as toml
|
|
import re
|
|
|
|
MX_DISCARD_PATHS = [
|
|
"cmake",
|
|
".mxproject",
|
|
"CMakePresets.json",
|
|
]
|
|
|
|
SOURCE_EXTS = [
|
|
"*.c",
|
|
"*.s"
|
|
]
|
|
HEADER_EXTS = [
|
|
"*.h"
|
|
]
|
|
|
|
CMAKE_EXCLUDE_SOURCES = [
|
|
"*_template.c"
|
|
]
|
|
CMAKE_TARGET = ""
|
|
ASSUME_YES = False
|
|
INJECT_HANDLERS = []
|
|
NO_MY_MAIN = False
|
|
|
|
|
|
def mx_cleanup(path: Path):
|
|
deletions: list[Path] = []
|
|
|
|
for disc in MX_DISCARD_PATHS:
|
|
disc_path = path / disc
|
|
if disc_path.exists():
|
|
deletions.append(disc_path)
|
|
|
|
if len(deletions) == 0:
|
|
return
|
|
|
|
print("%d files selected for deletion:"%(len(deletions)))
|
|
for disc in deletions:
|
|
print(disc)
|
|
|
|
if not ASSUME_YES:
|
|
print("Accept? (y/N)")
|
|
sel = input()
|
|
if sel.lower() != "y":
|
|
print("Aborting...")
|
|
exit(1)
|
|
|
|
for disc in deletions:
|
|
disc.unlink() if disc.is_file() else rmtree(disc)
|
|
|
|
def recurse_cmakelists(path: Path):
|
|
# Detect relevant files
|
|
dirs: list[Path] = []
|
|
sources: list[Path] = []
|
|
has_headers: bool = False
|
|
|
|
for child in sorted(path.iterdir()):
|
|
if child.is_dir():
|
|
dirs.append(child)
|
|
elif any(child.match(ext) for ext in SOURCE_EXTS) and \
|
|
not any(child.match(exclude) for exclude in CMAKE_EXCLUDE_SOURCES):
|
|
sources.append(child)
|
|
elif any(child.match(ext) for ext in HEADER_EXTS):
|
|
has_headers = True
|
|
|
|
# Generate CMakeLists content
|
|
lines: list[str] = []
|
|
|
|
if has_headers:
|
|
lines.append("target_include_directories(%s PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})"%(CMAKE_TARGET))
|
|
if len(sources) != 0:
|
|
lines.append("target_sources(%s PRIVATE"%(CMAKE_TARGET))
|
|
lines += ["\t%s"%(src.name) for src in sources]
|
|
lines.append(")")
|
|
lines += ["add_subdirectory(%s)"%(directory.name) for directory in dirs]
|
|
|
|
lists_path = path / "CMakeLists.txt"
|
|
with open(lists_path, "w") as of:
|
|
of.write("\n".join(lines)+"\n")
|
|
|
|
# Recurse
|
|
for directory in dirs:
|
|
recurse_cmakelists(directory)
|
|
|
|
def root_cmakelists(path: Path):
|
|
lines: list[str] = []
|
|
lists_path = path / "CMakeLists.txt"
|
|
|
|
# Define custom libraries
|
|
lines.append("add_library(%s STATIC)"%(CMAKE_TARGET))
|
|
lines.append("target_compile_definitions(%s PRIVATE ${COMPILER_DEFINES})"%(CMAKE_TARGET))
|
|
lines.append("target_compile_options(%s PRIVATE -Os -ffunction-sections -fdata-sections -g -flto)"%(CMAKE_TARGET))
|
|
lines.append("target_link_options(%s PRIVATE -T ${LINKER_SCRIPT} -Wl,--gc-sections -flto)"%(CMAKE_TARGET))
|
|
|
|
# Read previously generated contents
|
|
with open(lists_path, "r") as _if:
|
|
lines += [line.rstrip("\n") for line in _if]
|
|
|
|
# Save final result
|
|
with open(lists_path, "w") as of:
|
|
of.write("\n".join(lines)+"\n")
|
|
|
|
def inject_handlers_file(path: Path):
|
|
with open(path, "r") as _if:
|
|
content = _if.read()
|
|
|
|
patched = content
|
|
|
|
for handler in INJECT_HANDLERS:
|
|
patched = patched.replace("void %s_Handler(void){"%(handler), "void %s_Handler(void){\nmy_%s();"%(handler, handler))
|
|
|
|
with open(path, "w") as of:
|
|
of.write(patched)
|
|
|
|
def inject_handlers(path: Path):
|
|
root = path / "Core/Src"
|
|
|
|
for child in root.iterdir():
|
|
if any(child.match(ext) for ext in SOURCE_EXTS) and \
|
|
not any(child.match(exclude) for exclude in CMAKE_EXCLUDE_SOURCES):
|
|
inject_handlers_file(child)
|
|
|
|
def call_my_main(path: Path):
|
|
main_c = path / "Core/Src/main.c"
|
|
|
|
with open(main_c, "r") as _if:
|
|
content = _if.read()
|
|
|
|
patched = content.replace("/* USER CODE BEGIN PFP */", "/* USER CODE BEGIN PFP */\nvoid my_main();").replace("/* USER CODE BEGIN WHILE */", "/* USER CODE BEGIN WHILE */\nmy_main();")
|
|
|
|
with open(main_c, "w") as of:
|
|
of.write(patched)
|
|
|
|
def load_config(path: Path):
|
|
if not path.exists():
|
|
return
|
|
if not path.is_file():
|
|
print("Config '%s' is not a file."%(path))
|
|
exit(1)
|
|
|
|
with open(path, "rb") as cfg:
|
|
config = toml.load(cfg)
|
|
|
|
overlay_config(config)
|
|
|
|
def overlay_config(config: dict):
|
|
global MX_DISCARD_PATHS
|
|
global HEADER_EXTS
|
|
global SOURCE_EXTS
|
|
global CMAKE_TARGET
|
|
global CMAKE_EXCLUDE_SOURCES
|
|
global ASSUME_YES
|
|
global INJECT_HANDLERS
|
|
global NO_MY_MAIN
|
|
|
|
if "discard" in config and config["discard"]:
|
|
if not isinstance(config["discard"], list):
|
|
print("discard must be a list of strings")
|
|
exit(1)
|
|
MX_DISCARD_PATHS += config["discard"]
|
|
if "header" in config and config["header"]:
|
|
if not isinstance(config["header"], list):
|
|
print("header must be a list of strings")
|
|
exit(1)
|
|
HEADER_EXTS += config["header"]
|
|
if "source" in config and config["source"]:
|
|
if not isinstance(config["source"], list):
|
|
print("source must be a list of strings")
|
|
exit(1)
|
|
SOURCE_EXTS += config["source"]
|
|
if "target" in config and config["target"]:
|
|
if not isinstance(config["target"], str):
|
|
print("target must be a string")
|
|
exit(1)
|
|
CMAKE_TARGET = config["target"]
|
|
if "exclude" in config and config["exclude"]:
|
|
if not isinstance(config["exclude"], list):
|
|
print("exclude must be a list of strings")
|
|
exit(1)
|
|
CMAKE_EXCLUDE_SOURCES = config["exclude"]
|
|
if "assume-yes" in config and config["assume-yes"]:
|
|
if not isinstance(config["assume-yes"], bool):
|
|
print("assume-yes must be a bool")
|
|
exit(1)
|
|
ASSUME_YES |= config["assume-yes"]
|
|
if "inject-handlers" in config and config["inject-handlers"]:
|
|
if not isinstance(config["inject-handlers"], list):
|
|
print("inject-handlers must be a list of strings")
|
|
exit(1)
|
|
INJECT_HANDLERS += config["inject-handlers"]
|
|
if "no-my-main" in config and config["no-my-main"]:
|
|
if not isinstance(config["no-my-main"], bool):
|
|
print("no-my-main must be a bool")
|
|
exit(1)
|
|
NO_MY_MAIN |= config["no-my-main"]
|
|
|
|
def print_config():
|
|
print("MX_DISCARD_PATHS", MX_DISCARD_PATHS)
|
|
print("SOURCE_EXTS", SOURCE_EXTS)
|
|
print("HEADER_EXTS", HEADER_EXTS)
|
|
print("CMAKE_EXCLUDE_SOURCES", CMAKE_EXCLUDE_SOURCES)
|
|
print("CMAKE_TARGET", CMAKE_TARGET)
|
|
print("ASSUME_YES", ASSUME_YES)
|
|
print("INJECT_HANDLERS", INJECT_HANDLERS)
|
|
print("NO_MY_MAIN", NO_MY_MAIN)
|
|
|
|
def main2(ns: Namespace):
|
|
if not ns.path.is_dir():
|
|
print("'%s' is not a directory."%(ns.path))
|
|
exit(1)
|
|
|
|
load_config(ns.config)
|
|
overlay_config(vars(ns))
|
|
|
|
if ns.show_cfg:
|
|
print_config()
|
|
exit(0)
|
|
|
|
mx_cleanup(ns.path)
|
|
recurse_cmakelists(ns.path)
|
|
root_cmakelists(ns.path)
|
|
|
|
if not INJECT_HANDLERS:
|
|
inject_handlers(ns.path)
|
|
|
|
if not NO_MY_MAIN:
|
|
call_my_main(ns.path)
|
|
|
|
def main():
|
|
parser = ArgumentParser(prog="MX Convert")
|
|
parser.add_argument("-c", "--config", action="store", type=Path, default="mx_convert.toml", help="The config file to read. If not specified, defaults to 'mx_convert.toml'.")
|
|
parser.add_argument("-d", "--discard", action="append", help="Add a file to the discard list (delete if found).")
|
|
parser.add_argument("-i", "--inject-handlers", action="store", help="Add a call to your own handler functions in MX generated code.")
|
|
parser.add_argument("-H", "--header", action="append", help="Add a pattern to the headers list, single wildcards allowed. Includes *.h by default.")
|
|
parser.add_argument("-n", "--no-my-main", action="store_true", help="Disable added call to my_main inside the generated main function.")
|
|
parser.add_argument("-s", "--source", action="append", help="Add a pattern to the sources list, single wildcards allowed. Includes *.c and *.s by default.")
|
|
parser.add_argument("-t", "--target", action="store", default="cubemx", help="The CMake target to generate references to. Defaults to 'cubemx'.")
|
|
parser.add_argument("-x", "--exclude", action="append", help="Add a file to the source exclusion list, single wildcards allowed.")
|
|
parser.add_argument("-y", "--assume-yes", action="store_true", help="Assume yes on all queries, useful for scripts.")
|
|
parser.add_argument("--show-cfg", action="store_true", help="Show configs that would be used to run and exit successfully.")
|
|
parser.add_argument("path", action="store", type=Path, help="The path to the directory to process.")
|
|
|
|
namespace = parser.parse_args(argv[1:])
|
|
|
|
main2(namespace)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|