diff --git a/docs/includes/gen_glossary.py b/docs/includes/gen_glossary.py index ccd3f5c17..205b85563 100644 --- a/docs/includes/gen_glossary.py +++ b/docs/includes/gen_glossary.py @@ -22,7 +22,6 @@ glossary: list[dict] = [] with open(HERE / file_glossary_input, "r") as f: - for idx, line in enumerate(f.readlines()): match = re.match(term_pattern, line) if match: diff --git a/firmware/cmake/cangen.cmake b/firmware/cmake/cangen.cmake index 6f308158b..c881fdcfc 100644 --- a/firmware/cmake/cangen.cmake +++ b/firmware/cmake/cangen.cmake @@ -5,7 +5,7 @@ cmake_language(GET_MESSAGE_LOG_LEVEL LOG_LEVEL) # cangen is provided by racecar/scripts/cangen add_custom_target( generated_can - COMMAND "cangen" ${CMAKE_CURRENT_SOURCE_DIR} "--log-level=${LOG_LEVEL}" + COMMAND "cangen" ${CMAKE_CURRENT_SOURCE_DIR} "--cmake-log-level=${LOG_LEVEL}" DEPENDS ${CAN_DEPENDENCIES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT Generating CAN code from DBCs diff --git a/scripts/cangen/cangen/can_generator.py b/scripts/cangen/cangen/can_generator.py index 2b8348ed3..a84e36f07 100644 --- a/scripts/cangen/cangen/can_generator.py +++ b/scripts/cangen/cangen/can_generator.py @@ -22,11 +22,8 @@ EIGHT_BITS = 8 EIGHT_BYTES = 8 TOTAL_BITS = EIGHT_BITS * EIGHT_BYTES -MSG_REGISTRY_FILE_NAME = "_msg_registry.h" -CAN_MESSAGES_FILE_NAME = "_can_messages.h" -CAN_MESSAGES_TEMPLATE_FILENAME = "can_messages.h.jinja2" -MSG_REGISTRY_TEMPLATE_FILENAME = "msg_registry.h.jinja2" +TEMPLATE_FILE_NAMES = ["can_messages.h.jinja2", "msg_registry.h.jinja2"] def _parse_dbc_files(dbc_file: str) -> Database: @@ -171,29 +168,12 @@ def _camel_to_snake(text): return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -def _generate_from_jinja2_template( - template_path: str, output_path: str, context_dict: dict -): - # Create the environment with trim_blocks and lstrip_blocks settings - env = Environment( - loader=PackageLoader("cangen"), trim_blocks=True, lstrip_blocks=True +def _create_output_file_name(output_dir: str, bus_name: str, template_file_name: str) -> str: + return os.path.join( + output_dir, bus_name.lower() + "_" + template_file_name.removesuffix(".jinja2") ) - # Register the camel_to_snake filter - env.filters["camel_to_snake"] = _camel_to_snake - env.filters["decimal_to_hex"] = hex - - # Load and render template - template = env.get_template(template_path) - rendered_code = template.render(**context_dict) - - with open(output_path, "w") as output_file: - output_file.write(rendered_code) - - logger.info(f"Rendered code written to '{os.path.abspath(output_path)}'") - - -def generate_code(bus: Bus, config: Config): +def _generate_code(bus: Bus, output_dir: str): """ Parses DBC files, extracts information, and generates code using Jinja2 templates. @@ -220,27 +200,33 @@ def generate_code(bus: Bus, config: Config): "bus_name": bus.bus_name, } - logger.debug("Generating code for can messages") - _generate_from_jinja2_template( - CAN_MESSAGES_TEMPLATE_FILENAME, - os.path.join(config.output_dir, bus.bus_name.lower() + CAN_MESSAGES_FILE_NAME), - context, - ) + logger.debug("Generating code for can messages and msg registry.") - logger.debug("Generating code for msg registry") - _generate_from_jinja2_template( - MSG_REGISTRY_TEMPLATE_FILENAME, - os.path.join(config.output_dir, bus.bus_name.lower() + MSG_REGISTRY_FILE_NAME), - context, + env = Environment( + loader=PackageLoader(__package__), trim_blocks=True, lstrip_blocks=True ) + env.filters["decimal_to_hex"] = hex + env.filters["camel_to_snake"] = _camel_to_snake - logger.info("Code generation complete") + for template_file_name in TEMPLATE_FILE_NAMES: + template = env.get_template(template_file_name) + rendered_code = template.render(**context) + output_file_name = _create_output_file_name(output_dir, bus.bus_name, template_file_name) + with open(output_file_name, "w") as output_file: + output_file.write(rendered_code) + logger.info(f"Rendered code written to '{os.path.abspath(output_file_name)}'") -def _prepare_output_directory(output_dir): + logger.info("Code generation complete") + + +def _prepare_output_directory(output_dir: str): """Deletes previously generated files and creates a gitignore for the directory""" if os.path.exists(output_dir): + logger.info("Deleting previously generated code") shutil.rmtree(output_dir) + + logger.info("Creating generated/can folder") os.makedirs(output_dir, exist_ok=True) gitignore_path = os.path.join(output_dir, ".gitignore") @@ -248,11 +234,13 @@ def _prepare_output_directory(output_dir): f.write("*") -def generate_can_from_dbc(project_folder_name: str): +def generate_can_for_project(project_folder_name: str): + """Generates C code for a given project. + Ensure the project folder contains a config.yaml file which specifies the relative path to its corresponding dbc file.""" os.chdir(project_folder_name) config = Config.from_yaml("config.yaml") _prepare_output_directory(config.output_dir) for bus in config.busses: - generate_code(bus, config) + _generate_code(bus, config.output_dir) diff --git a/scripts/cangen/cangen/main.py b/scripts/cangen/cangen/main.py index f2912e7fe..c867ac563 100644 --- a/scripts/cangen/cangen/main.py +++ b/scripts/cangen/cangen/main.py @@ -6,7 +6,7 @@ import argparse import logging -from .can_generator import generate_can_from_dbc +from .can_generator import generate_can_for_project logging.basicConfig(level="INFO", format="%(levelname)-8s| (%(name)s) %(message)s") logger = logging.getLogger(__name__) @@ -15,21 +15,24 @@ def parse(): parser = argparse.ArgumentParser(description="DBC to C code generator") parser.add_argument("project", type=str, help="Name of the project") - parser.add_argument( - "--log-level", + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-v", + "--verbose", + dest="is_verbose", + action="store_true", + help="Enable verbose output", + ) + group.add_argument( + "--cmake-log-level", dest="level", choices=["STATUS", "INFO", "VERBOSE", "DEBUG"], default="INFO", help="Log verbosity threshold", ) - # If parsing fails (ex incorrect or no arguments provided) then this exits with - # code 2. - return parser.parse_args() - - -def main(): - args = parse() + # If parsing fails (ex incorrect or no arguments provided) then this code will be 2 + args = parser.parse_args() cmake_to_python_level = { "STATUS": "INFO", @@ -37,6 +40,18 @@ def main(): "VERBOSE": "DEBUG", "DEBUG": "DEBUG", } - logger.setLevel(cmake_to_python_level[args.level]) - generate_can_from_dbc(args.project) + args.level = ( + cmake_to_python_level["VERBOSE"] + if args.is_verbose + else cmake_to_python_level[args.level] + ) + return args + + +def main(): + args = parse() + + logger.setLevel(args.level) + + generate_can_for_project(args.project)