This commit is contained in:
2025-11-12 18:16:44 +00:00
commit 944643f6bd
4 changed files with 240 additions and 0 deletions

183
mx_convert/mx_convert.py Normal file
View File

@@ -0,0 +1,183 @@
from pathlib import Path
from shutil import rmtree
from sys import argv
from argparse import ArgumentParser, Namespace
import tomllib as toml
MX_DISCARD_PATHS = [
"cmake",
".mxproject",
"CMakePresets.json",
]
#MX_MOVE_PATHS = [
# ("Core/Inc/main.h", "Core/Inc/mx-main.h"), #TODO: Force rename in includes as well
# ("Core/Src/main.c", "Core/Src/mx-main.c"),
#]
SOURCE_EXTS = [
"*.c",
"*.s"
]
HEADER_EXTS = [
"*.h"
]
CMAKE_EXCLUDE_SOURCES = [
"*_template.c"
]
CMAKE_TARGET = "" # Set from argv
ASSUME_YES = 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 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 PRIVATE ${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]
# Write CMakeLists
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 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
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"]
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)
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)
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("-H", "--header", action="append", help="Add a pattern to the headers list, single wildcards allowed. Includes *.h by default.")
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="${EXECUTABLE}", help="The CMake target to generate references to. Defaults to '${EXECUTABLE}'.")
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()