v0.1.0
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.vscode/
|
||||
test/
|
||||
**__pycache__/
|
||||
mx_convert.egg-info/
|
||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# MX Converter
|
||||
|
||||
MX Converter is a tool to convert Cube MX generated projects to the ISTSAT-2 repository structure.
|
||||
|
||||
## Notice
|
||||
|
||||
This guide and the MX Converter were built with the goal of cleanly separating STM generated code from application code. As such, files produced by Cube MX are treated as disposable, and any code in said files is likely to be lost. This applies to C files, header files, CMakeLists and possibly more. Do not include any code of your own in the MX files.
|
||||
|
||||
When adapting existing code for use with MX Converter, make sure to back up the original code and to double check all code is present in the final version of your project.
|
||||
|
||||
## Cube MX Configuration
|
||||
|
||||
For simplicity of development of this tool and for uniformity across different firmware projects, the same configuration should be used when creating the projects with Cube MX.
|
||||
|
||||
All the relevant settings are under the 'Project Manager' tab and should be set as listed below. Any unspecified settings can be set to whatever is most convinient.
|
||||
|
||||
Setting | Value
|
||||
-- | --
|
||||
Project > Do not generate the main() | SET
|
||||
Project > Toolchain / IDE | 'CMake'
|
||||
Code Generator > STM32Cube MCU packages and embedded software packages | 'Copy only necessary library files'
|
||||
Code Generator > Generate peripheral initialization as a pair of '.c/.h' files per peripheral | UNSET
|
||||
Code Generator > Backup previously generated files when re-generating | UNSET
|
||||
Code Generator > Keep User Code when re-generating | UNSET
|
||||
Code Generator > Delete previously generated files when not re-generated | SET
|
||||
|
||||
## Project Creation
|
||||
|
||||
The project creation process differs somewhat from creating a standard MX project.
|
||||
|
||||
1. Start with the creation of a project folder, from now on called ROOT. This will NOT be the destination of the code generation nor the `.ioc` file.
|
||||
1. Open the CubeMX program, select the board or MCU, setup the hardware as needed and configure the project as described before. Save the project, navigating to the ROOT folder, and name the project 'CubeMX'. This will create a new directory where generated code and the ioc will reside.
|
||||
1. Follow the Code generation / IOC Update guide.
|
||||
|
||||
## Code generation / IOC Update
|
||||
|
||||
After project creation of after modifying the IOC, code generation needs to be invoked. Assuming Cube MX is open with the relevant IOC:
|
||||
|
||||
1. Save the project and press 'Generate Code'. Any existing code will be **deleted**, and the new code will be placed in the previously created CubeMX folder.
|
||||
1. TODO: Define how to use the CMakeLists generator
|
||||
183
mx_convert/mx_convert.py
Normal file
183
mx_convert/mx_convert.py
Normal 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()
|
||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "mx_convert"
|
||||
version = "0.1.0"
|
||||
description = "A Cube MX projct conversion helper"
|
||||
authors = [{ name = "Didas72" }]
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
mx-convert = "mx_convert.mx_convert:main"
|
||||
Reference in New Issue
Block a user