Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

andrewi/293 cangen refactor #315

Merged
merged 12 commits into from
Nov 12, 2024
1 change: 0 additions & 1 deletion docs/includes/gen_glossary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion firmware/cmake/cangen.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 28 additions & 40 deletions scripts/cangen/cangen/can_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(".jninja2")
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
)

# 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.
Expand All @@ -220,39 +200,47 @@ 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")
with open(gitignore_path, "w") as f:
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)

AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
for bus in config.busses:
generate_code(bus, config)
_generate_code(bus, config.output_dir)
39 changes: 27 additions & 12 deletions scripts/cangen/cangen/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -15,28 +15,43 @@
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",
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
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",
"INFO": "INFO",
"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)
Loading