From da9b6a1874ecb727c0136c15bc04d8942c17fb41 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Thu, 4 Jul 2024 21:28:23 -0700 Subject: [PATCH] 3.3.0 - Add more advanced SVD support --- .github/workflows/python-package.yml | 16 ++- .gitignore | 1 + .pylintrc | 3 +- LICENSE | 2 +- README.md | 5 +- config | 2 +- ifgen/__init__.py | 4 +- ifgen/commands/gen.py | 16 ++- ifgen/commands/svd.py | 28 +++- ifgen/config/svd.py | 13 ++ ifgen/data/schemas/SvdConfig.yaml | 18 +++ ifgen/data/schemas/SvdInstanceConfig.yaml | 18 +++ ifgen/data/svd.yaml | 15 +++ .../{imxrt1176_cm4.svd => mimxrt1176_cm4.svd} | 0 .../{imxrt1176_cm7.svd => mimxrt1176_cm7.svd} | 0 ifgen/dev_requirements.txt | 2 +- ifgen/environment/field.py | 2 +- ifgen/svd/group/enums.py | 4 +- ifgen/svd/group/fields.py | 125 +++++++++++++++--- ifgen/svd/model/__init__.py | 22 ++- ifgen/svd/model/field.py | 5 +- ifgen/svd/model/peripheral.py | 25 ++++ ifgen/svd/task.py | 54 ++++++-- local/configs/package.yaml | 2 +- local/configs/python.yaml | 2 +- local/variables/package.yaml | 4 +- pyproject.toml | 7 +- setup.py | 3 +- tasks/conf.py | 14 +- tests/commands/test_svd.py | 55 ++++---- 30 files changed, 367 insertions(+), 100 deletions(-) create mode 100644 ifgen/config/svd.py create mode 100644 ifgen/data/schemas/SvdConfig.yaml create mode 100644 ifgen/data/schemas/SvdInstanceConfig.yaml create mode 100644 ifgen/data/svd.yaml rename ifgen/data/svd/{imxrt1176_cm4.svd => mimxrt1176_cm4.svd} (100%) rename ifgen/data/svd/{imxrt1176_cm7.svd => mimxrt1176_cm7.svd} (100%) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 88d3214..e631600 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,6 +10,7 @@ on: env: TWINE_PASSWORD: ${{secrets.TWINE_PASSWORD}} GITHUB_API_TOKEN: ${{secrets.API_TOKEN}} + CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} jobs: build: @@ -18,7 +19,6 @@ jobs: strategy: matrix: python-version: - - "3.11" - "3.12" system: - ubuntu-latest @@ -57,29 +57,33 @@ jobs: - run: mk docs if: | - matrix.python-version == '3.11' + matrix.python-version == '3.12' && matrix.system == 'ubuntu-latest' - run: mk python-test env: PY_TEST_EXTRA_ARGS: --cov-report=xml - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v3.1.5 + with: + fail_ci_if_error: true + verbose: true + token: ${{secrets.CODECOV_TOKEN}} - run: mk pypi-upload-ci env: TWINE_USERNAME: __token__ if: | - matrix.python-version == '3.11' + matrix.python-version == '3.12' && matrix.system == 'ubuntu-latest' && env.TWINE_PASSWORD != '' && github.ref_name == 'master' - run: | mk python-release owner=vkottler \ - repo=ifgen version=3.2.1 + repo=ifgen version=3.3.0 if: | - matrix.python-version == '3.11' + matrix.python-version == '3.12' && matrix.system == 'ubuntu-latest' && env.GITHUB_API_TOKEN != '' && github.ref_name == 'master' diff --git a/.gitignore b/.gitignore index e1ff920..b775a12 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ coverage*.xml tags mklocal docs +src diff --git a/.pylintrc b/.pylintrc index 9503d8e..7dc6b19 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,7 @@ [DESIGN] -max-args=8 +max-args=9 max-attributes=8 +max-locals=17 [MESSAGES CONTROL] disable=duplicate-code diff --git a/LICENSE b/LICENSE index e97d6f9..241562d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Vaughn Kottler +Copyright (c) 2024 Vaughn Kottler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c70660b..4554e39 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.4 - hash=c432187b0744b91a3bf18c6fc28df5eb + hash=35493b15233479037424afb95e796e3e ===================================== --> -# ifgen ([3.2.1](https://pypi.org/project/ifgen/)) +# ifgen ([3.3.0](https://pypi.org/project/ifgen/)) [![python](https://img.shields.io/pypi/pyversions/ifgen.svg)](https://pypi.org/project/ifgen/) ![Build Status](https://github.com/vkottler/ifgen/workflows/Python%20Package/badge.svg) @@ -29,7 +29,6 @@ This package is tested with the following Python minor versions: -* [`python3.11`](https://docs.python.org/3.11/) * [`python3.12`](https://docs.python.org/3.12/) ## Platform Support diff --git a/config b/config index 0c555bf..2fcfcfb 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 0c555bf6565cc5d90408adbad3c162edca43a7e8 +Subproject commit 2fcfcfb2c3f3470be6e3d55f75e9d68fbd948f7f diff --git a/ifgen/__init__.py b/ifgen/__init__.py index 018afe5..0fa4156 100644 --- a/ifgen/__init__.py +++ b/ifgen/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.4 -# hash=302dd11b12a573508cf3316546a384ef +# hash=bf2c126f5c469e9b0483e934ee695f9e # ===================================== """ @@ -10,4 +10,4 @@ DESCRIPTION = "An interface generator for distributed computing." PKG_NAME = "ifgen" -VERSION = "3.2.1" +VERSION = "3.3.0" diff --git a/ifgen/commands/gen.py b/ifgen/commands/gen.py index c41da95..40c6913 100644 --- a/ifgen/commands/gen.py +++ b/ifgen/commands/gen.py @@ -5,6 +5,7 @@ # built-in from argparse import ArgumentParser as _ArgumentParser from argparse import Namespace as _Namespace +import sys # third-party from vcorelib.args import CommandFunction as _CommandFunction @@ -22,6 +23,8 @@ def gen_cmd(args: _Namespace) -> int: root = normalize(args.root) + sys.setrecursionlimit(args.recursion) + generate(root.resolve(), load(combine_if_not_absolute(root, args.config))) return 0 @@ -30,16 +33,25 @@ def gen_cmd(args: _Namespace) -> int: def add_gen_cmd(parser: _ArgumentParser) -> _CommandFunction: """Add gen-command arguments to its parser.""" + parser.add_argument( + "--recursion", + type=int, + default=10000, + help="recursion limit to set (default: '%(default)s')", + ) parser.add_argument( "-c", "--config", default=f"{PKG_NAME}.yaml", - help="configuration file to use", + help="configuration file to use (default: '%(default)s')", ) parser.add_argument( "-r", "--root", default=".", - help="root directory to use for relative paths", + help=( + "root directory to use for relative " + "paths (default: '%(default)s')" + ), ) return gen_cmd diff --git a/ifgen/commands/svd.py b/ifgen/commands/svd.py index 86ee7e5..230e9b6 100644 --- a/ifgen/commands/svd.py +++ b/ifgen/commands/svd.py @@ -14,6 +14,7 @@ # internal from ifgen import PKG_NAME +from ifgen.config.svd import SvdConfig from ifgen.svd import register_processors from ifgen.svd.group import base, enums from ifgen.svd.task import SvdProcessingTask @@ -23,23 +24,26 @@ def svd_cmd(args: _Namespace) -> int: """Execute the svd command.""" register_processors() + logger = getLogger(__name__) path = find_file( - args.svd_file, - package=PKG_NAME, - logger=getLogger(__name__), - include_cwd=True, + args.svd_file, package=PKG_NAME, logger=logger, include_cwd=True ) assert path is not None, args.svd_file - enable_pruning = path.with_suffix("").name in {"XMC4700"} + config = SvdConfig.decode( + find_file(args.config, logger=logger, include_cwd=True) + ) # Only enable certain pruning strategies for certain processors. + enable_pruning = path.with_suffix("").name in config.data.setdefault( + "enable_pruning", [] + ) enums.PRUNE_ENUMS = enable_pruning base.PRUNE_STRUCTS = enable_pruning SvdProcessingTask.svd(path, args.min_enum_width).generate_configs( - args.output + args.output, config ) return 0 @@ -56,7 +60,17 @@ def add_svd_cmd(parser: _ArgumentParser) -> _CommandFunction: "--output", type=Path, default=f"{PKG_NAME}-out", - help="output directory for configuration files", + help=( + "output directory for configuration " + "files (default '%(default)s')" + ), + ) + + parser.add_argument( + "-c", + "--config", + default=f"package://{PKG_NAME}/svd.yaml", + help="configuration rules to use (default: '%(default)s')", ) parser.add_argument( diff --git a/ifgen/config/svd.py b/ifgen/config/svd.py new file mode 100644 index 0000000..3d81d86 --- /dev/null +++ b/ifgen/config/svd.py @@ -0,0 +1,13 @@ +""" +A module implementing an SVD-command configuration interface for the package. +""" + +# third-party +from vcorelib.dict.codec import BasicDictCodec as _BasicDictCodec + +# internal +from ifgen.schemas import IfgenDictCodec + + +class SvdConfig(IfgenDictCodec, _BasicDictCodec): + """The top-level configuration object for the package.""" diff --git a/ifgen/data/schemas/SvdConfig.yaml b/ifgen/data/schemas/SvdConfig.yaml new file mode 100644 index 0000000..5cb3ba4 --- /dev/null +++ b/ifgen/data/schemas/SvdConfig.yaml @@ -0,0 +1,18 @@ +--- +type: object +additionalProperties: false +required: [] + +properties: + enable_pruning: + type: array + default: [XMC4700] + items: + type: string + + devices: + type: object + additionalProperties: false + patternProperties: + "^[a-zA-Z0-9-_.]+$": + $ref: package://ifgen/schemas/SvdInstanceConfig.yaml diff --git a/ifgen/data/schemas/SvdInstanceConfig.yaml b/ifgen/data/schemas/SvdInstanceConfig.yaml new file mode 100644 index 0000000..aad5d74 --- /dev/null +++ b/ifgen/data/schemas/SvdInstanceConfig.yaml @@ -0,0 +1,18 @@ +--- +type: object +additionalProperties: false +required: [] + +properties: + ignore_peripherals: + type: array + items: + type: object + additionalProperties: false + required: [name, reason] + + properties: + name: + type: string + reason: + type: string diff --git a/ifgen/data/svd.yaml b/ifgen/data/svd.yaml new file mode 100644 index 0000000..a2e83b6 --- /dev/null +++ b/ifgen/data/svd.yaml @@ -0,0 +1,15 @@ +--- + +# Note: ignored peripherals still get generated YAML outputs, they're just +# not included in the generated top-level ifgen.yaml. + +devices: + mimxrt1176_cm4: &rt1176 + ignore_peripherals: + - name: caam + reason: Register map not in reference manual / (#64). + - name: dcic1 + reason: (#64) dimIncrement not supported. + - name: rdc + reason: (#64) dimIncrement not supported. + mimxrt1176_cm7: *rt1176 diff --git a/ifgen/data/svd/imxrt1176_cm4.svd b/ifgen/data/svd/mimxrt1176_cm4.svd similarity index 100% rename from ifgen/data/svd/imxrt1176_cm4.svd rename to ifgen/data/svd/mimxrt1176_cm4.svd diff --git a/ifgen/data/svd/imxrt1176_cm7.svd b/ifgen/data/svd/mimxrt1176_cm7.svd similarity index 100% rename from ifgen/data/svd/imxrt1176_cm7.svd rename to ifgen/data/svd/mimxrt1176_cm7.svd diff --git a/ifgen/dev_requirements.txt b/ifgen/dev_requirements.txt index 3619e4e..62697c7 100644 --- a/ifgen/dev_requirements.txt +++ b/ifgen/dev_requirements.txt @@ -12,4 +12,4 @@ sphinx-book-theme pytest-cov setuptools-wrapper types-setuptools -yambs +yambs>=3.0.4 diff --git a/ifgen/environment/field.py b/ifgen/environment/field.py index 1df8660..953458c 100644 --- a/ifgen/environment/field.py +++ b/ifgen/environment/field.py @@ -31,7 +31,7 @@ def process_field( difference = expected - size assert difference >= 0, ( - f"({struct_name}.{field_name}) current={size} " + f"{difference} ({struct_name}.{field_name}) current={size} " f"!= expected={expected}" ) diff --git a/ifgen/svd/group/enums.py b/ifgen/svd/group/enums.py index 8c96b04..3374292 100644 --- a/ifgen/svd/group/enums.py +++ b/ifgen/svd/group/enums.py @@ -144,7 +144,7 @@ def translate_enums(enum: EnumeratedValues) -> EnumValues: enum_data: dict[str, Any] = {} value.handle_description(enum_data) - value_str: str = value.raw_data["value"] + value_str: str = value.raw_data["value"].lower() prefix = "" for possible_prefix in ("#", "0b", "0x"): @@ -154,7 +154,7 @@ def translate_enums(enum: EnumeratedValues) -> EnumValues: if prefix in ("#", "0b"): enum_data["value"] = int( - value_str[len(prefix) :].replace("X", "1"), 2 + value_str[len(prefix) :].replace("x", "1"), 2 ) elif prefix == "0x": enum_data["value"] = int(value_str[len(prefix) :], 16) diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index 4b887a4..ea95fe0 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -3,11 +3,16 @@ """ # built-in -from typing import Any, Iterable +from typing import Any, Iterable, Optional # internal from ifgen.svd.group.enums import ENUM_DEFAULTS, get_enum_name, translate_enums -from ifgen.svd.model.peripheral import Cluster, Register, RegisterData +from ifgen.svd.model.peripheral import ( + Cluster, + Register, + RegisterData, + register_groups, +) StructMap = dict[str, Any] StructField = dict[str, Any] @@ -58,12 +63,13 @@ def handle_cluster( cluster.children, structs, enums, peripheral, min_enum_members ) - # Too difficult due to padding (may need to comment out). - cluster_struct["expected_size"] = size + # Too difficult due to padding (may need to comment out). Padding within + # a cluster isn't currently handled. + # cluster_struct["expected_size"] = size cluster_struct.update(DEFAULT_STRUCT) - raw_name = cluster.name.replace("[%s]", "") + raw_name = sanitize_name(cluster.name) cluster_name = cluster.raw_data.get( "headerStructName", f"{raw_name}_instance" @@ -71,14 +77,16 @@ def handle_cluster( structs[cluster_name] = cluster_struct # This needs to be an array element somehow. Use a namespace? + # dimIncrement not handled. array_dim = int(cluster.raw_data.get("dim", 1)) + size *= array_dim result: StructField = { "name": raw_name, "type": cluster_name, # Too difficult due to padding (may need to comment out). - "expected_size": size, - "expected_offset": parse_offset(cluster.raw_data), + # "expected_size": size, + # "expected_offset": parse_offset(cluster.raw_data), } if array_dim > 1: result["array_length"] = array_dim @@ -125,7 +133,7 @@ def process_bit_fields( # Check if enum is unique. enum_name = get_enum_name( - f"{peripheral}_{register.name}_{name}".replace("[%s]", ""), + sanitize_name(f"{peripheral}_{register.name}_{name}"), peripheral, raw, ) @@ -137,6 +145,12 @@ def process_bit_fields( output["fields"] = result +def sanitize_name(name: str) -> str: + """Remove special characters from a name.""" + + return name.replace("[%s]", "").replace("%s", "") + + def handle_register( register: Register, register_map: RegisterMap, @@ -151,14 +165,15 @@ def handle_register( if "alternateRegister" in register.raw_data: return 0, {} - # Ensure that a correct result will be produced. - check_not_handled_fields(register.raw_data, ["alternateGroup"]) + # Currently provided in metadata output. + # check_not_handled_fields(register.raw_data, ["alternateGroup"]) + # dimIncrement not handled. array_dim = int(register.raw_data.get("dim", 1)) size = register.size * array_dim data = { - "name": register.name.replace("[%s]", ""), + "name": sanitize_name(register.name), "type": register.c_type, "expected_size": size, "expected_offset": parse_offset(register.raw_data), @@ -207,6 +222,68 @@ def handle_register( return size, data +def handle_register_group( + name: str, + registers: list[Register], + register_map: RegisterMap, + enums: EnumMap, + peripheral: str, + min_enum_width: int, +) -> tuple[int, StructField]: + """Handle creating a struct field for a group of registers.""" + + # Handle melding register data at some point (aggregate more bit fields). + del name + + return handle_register( + registers[0], register_map, enums, peripheral, min_enum_width + ) + + +def handle_item( + item: Register | Cluster, + structs: StructMap, + enums: EnumMap, + peripheral: str, + min_enum_width: int, + groups_handled: set[str], + register_map: RegisterMap, + groups: dict[str, list[Register]], + by_name: dict[str, str], +) -> tuple[int, Optional[StructField]]: + """Handle creating a struct field from a register or cluster.""" + + inst_size = 0 + field = None + + if isinstance(item, Cluster): + inst_size, field = handle_cluster( + item, structs, enums, peripheral, min_enum_width + ) + else: + # Handle register groups. + if item.name in by_name: + group = by_name[item.name] + if group not in groups_handled: + inst_size, field = handle_register_group( + group, + groups[group], + register_map, + enums, + peripheral, + min_enum_width, + ) + groups_handled.add(group) + + # Handle normal registers. + else: + inst_size, field = handle_register( + item, register_map, enums, peripheral, min_enum_width + ) + + return inst_size, field + + def struct_fields( registers: RegisterData, structs: StructMap, @@ -228,15 +305,27 @@ def struct_fields( if isinstance(item, Register): register_map[item.name] = item + groups = register_groups(registers) + by_name = {} + for group, regs in groups.items(): + for reg in regs: + by_name[reg.name] = group + groups_handled: set[str] = set() + for item in registers: - inst_size, field = ( - handle_cluster(item, structs, enums, peripheral, min_enum_width) - if isinstance(item, Cluster) - else handle_register( - item, register_map, enums, peripheral, min_enum_width - ) + inst_size, field = handle_item( + item, + structs, + enums, + peripheral, + min_enum_width, + groups_handled, + register_map, + groups, + by_name, ) - if inst_size > 0: + + if inst_size > 0 and field is not None: fields.append(field) size += inst_size diff --git a/ifgen/svd/model/__init__.py b/ifgen/svd/model/__init__.py index 5b559ba..7df4fad 100644 --- a/ifgen/svd/model/__init__.py +++ b/ifgen/svd/model/__init__.py @@ -21,6 +21,16 @@ class SvdModel: device: Optional[Device] = None cpu: Optional[Cpu] = None + @property + def device_name(self) -> str: + """Get the SVD device name.""" + assert self.device is not None + return self.device.raw_data["name"] + + def namespace(self) -> list[str]: + """Get a namespace for this SVD device.""" + return [x.upper() for x in self.device_name.split("_")] + def metadata(self) -> dict[str, Any]: """Get device and CPU metadata.""" @@ -32,13 +42,23 @@ def metadata(self) -> dict[str, Any]: result["cpu"] = self.cpu.raw_data for name, peripheral in self.peripherals.items(): - result[name] = { + data = { "interrupts": [x.raw_data for x in peripheral.interrupts], "address_blocks": [ x.raw_data for x in peripheral.address_blocks ], } + # Add alternate group metadata. + groups = peripheral.register_groups() + if groups: + data["register_groups"] = { # type: ignore + key: [x.name for x in value] + for key, value in groups.items() + } + + result[name] = data + return result def assign_device(self, device: Device) -> None: diff --git a/ifgen/svd/model/field.py b/ifgen/svd/model/field.py index e0e1220..30ebee2 100644 --- a/ifgen/svd/model/field.py +++ b/ifgen/svd/model/field.py @@ -91,8 +91,11 @@ def ifgen_data(self) -> dict[str, Any]: elif "lsb" in self.raw_data: lsb = int(self.raw_data["lsb"]) msb = int(self.raw_data["msb"]) + elif "bitOffset" in self.raw_data: + lsb = int(self.raw_data["bitOffset"]) + msb = lsb + (int(self.raw_data["bitWidth"]) - 1) - assert lsb != -1 and msb != -1 + assert lsb != -1 and msb != -1, self.raw_data output["index"] = lsb diff --git a/ifgen/svd/model/peripheral.py b/ifgen/svd/model/peripheral.py index 2b90d44..b8ba24e 100644 --- a/ifgen/svd/model/peripheral.py +++ b/ifgen/svd/model/peripheral.py @@ -91,6 +91,11 @@ def size(self) -> int: """Get the size of this register in bytes.""" return self.bits // 8 + @property + def alternate_group(self) -> Optional[str]: + """Get this register's possible alternate group.""" + return self.raw_data.get("alternateGroup") + @property def access(self) -> str: """Get the access setting for this register.""" @@ -143,6 +148,22 @@ def string_keys(cls) -> Iterable[StringKeyVal]: ) +def register_groups(registers: RegisterData) -> dict[str, list[Register]]: + """Get groups of registers.""" + + result: dict[str, list[Register]] = {} + + for item in registers: + if isinstance(item, Register): + alt = item.alternate_group + if alt: + if alt not in result: + result[alt] = [] + result[alt].append(item) + + return result + + @dataclass class Peripheral(DerivedMixin): """A container for peripheral information.""" @@ -155,6 +176,10 @@ class Peripheral(DerivedMixin): registers: RegisterData + def register_groups(self) -> dict[str, list[Register]]: + """Get register groups.""" + return register_groups(self.registers) + def __eq__(self, other) -> bool: """Determine if two peripherals are equivalent.""" diff --git a/ifgen/svd/task.py b/ifgen/svd/task.py index a2b271f..400e6e5 100644 --- a/ifgen/svd/task.py +++ b/ifgen/svd/task.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from logging import getLogger from pathlib import Path -from typing import Callable +from typing import Callable, Iterable, Iterator from xml.etree import ElementTree # third-party @@ -15,6 +15,7 @@ from vcorelib.paths import rel # internal +from ifgen.config.svd import SvdConfig from ifgen.svd.group import handle_group, peripheral_groups from ifgen.svd.model import SvdModel @@ -25,6 +26,33 @@ TAG_PROCESSORS: TagProcessorMap = {} +def filter_includes( + config: SvdConfig, + model: SvdModel, + paths: Iterable[Path], + filtered: dict[str, str], +) -> Iterator[str]: + """Filter includes based on configuration.""" + + config_data = config.data.setdefault("devices", {}).setdefault( + model.device_name.lower(), {"ignore_peripherals": []} + ) + + for path in paths: + item = str(path) + reason = None + + # Determine a possible reason to filter this include. + for ignore in config_data["ignore_peripherals"]: + if path.parts[0] == ignore["name"]: + reason = ignore["reason"] + + if reason is not None: + filtered[item] = reason + else: + yield item + + @dataclass class SvdProcessingTask: """A container for SVD-processing state.""" @@ -44,16 +72,11 @@ def svd(path: Path, min_enum_width: int) -> "SvdProcessingTask": task.process(ElementTree.parse(path).getroot()) return task - def generate_configs(self, path: Path) -> None: + def generate_configs(self, path: Path, config: SvdConfig) -> None: """Generate output configuration files.""" path.mkdir(exist_ok=True, parents=True) - meta = self.model.metadata() - - # Write metadata that doesn't currently get used for generation. - ARBITER.encode(path.joinpath("metadata.json"), meta) - includes: set[Path] = set() # Organize peripherals into groups based on ones derived from others @@ -63,13 +86,25 @@ def generate_configs(self, path: Path) -> None: output_dir.mkdir(exist_ok=True) handle_group(output_dir, group, includes, self.min_enum_width) + # Write metadata that doesn't currently get used for generation. + meta = self.model.metadata() + + # Indicate includes that were filtered out in metadata. + filtered: dict[str, str] = {} + meta["filtered_includes"] = filtered + ARBITER.encode( path.joinpath("ifgen.yaml"), { "includes": sorted( # type: ignore - str(rel(x.resolve(), base=path)) for x in includes + filter_includes( + config, + self.model, + (rel(x.resolve(), base=path) for x in includes), + filtered, + ) ), - "namespace": [meta["device"]["name"]], + "namespace": self.model.namespace(), # type: ignore "struct": { "stream": False, "codec": False, @@ -80,3 +115,4 @@ def generate_configs(self, path: Path) -> None: "enum": {"use_map": False, "identifier": False}, }, ) + ARBITER.encode(path.joinpath("metadata.json"), meta) diff --git a/local/configs/package.yaml b/local/configs/package.yaml index f3b3ebe..708c3b3 100644 --- a/local/configs/package.yaml +++ b/local/configs/package.yaml @@ -22,7 +22,7 @@ dev_requirements: - pytest-cov - setuptools-wrapper - types-setuptools - - yambs + - yambs>=3.0.4 commands: - name: gen diff --git a/local/configs/python.yaml b/local/configs/python.yaml index c7712f0..d48f2a8 100644 --- a/local/configs/python.yaml +++ b/local/configs/python.yaml @@ -3,7 +3,7 @@ author_info: name: Vaughn Kottler email: vaughnkottler@gmail.com username: vkottler -versions: ["3.11", "3.12"] +versions: ["3.12"] systems: - macos-latest diff --git a/local/variables/package.yaml b/local/variables/package.yaml index a346345..14870c4 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 3 -minor: 2 -patch: 1 +minor: 3 +patch: 0 entry: ig diff --git a/pyproject.toml b/pyproject.toml index f6541f9..da4ade7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "ifgen" -version = "3.2.1" +version = "3.3.0" description = "An interface generator for distributed computing." readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.12" authors = [ {name = "Vaughn Kottler", email = "vaughnkottler@gmail.com"} ] @@ -15,7 +15,6 @@ maintainers = [ {name = "Vaughn Kottler", email = "vaughnkottler@gmail.com"} ] classifiers = [ - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS", @@ -42,7 +41,7 @@ test = [ "pytest-cov", "setuptools-wrapper", "types-setuptools", - "yambs" + "yambs>=3.0.4" ] [project.scripts] diff --git a/setup.py b/setup.py index ae89d6c..2167d3a 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.4 -# hash=8c4d99f635c21da173b8be87123faba3 +# hash=3bac2a3bc0b9200d68535b5383b9886e # ===================================== """ @@ -28,7 +28,6 @@ "version": VERSION, "description": DESCRIPTION, "versions": [ - "3.11", "3.12", ], } diff --git a/tasks/conf.py b/tasks/conf.py index 7356deb..0aebbed 100644 --- a/tasks/conf.py +++ b/tasks/conf.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.4 -# hash=9f62028523c3b5a953733ca89dcc3018 +# hash=7d378a1752611508007a77d4ca39a5af # ===================================== """ A module for project-specific task registration. @@ -20,14 +20,9 @@ def audit_local_tasks() -> None: """Ensure that shared task infrastructure is present.""" local = Path(__file__).parent.joinpath("mklocal") - - # Also link a top-level file. top_level = local.parent.parent.joinpath("mklocal") - if not top_level.is_symlink(): - assert not top_level.exists() - top_level.symlink_to(local) - if local.is_symlink(): + if local.is_symlink() and top_level.is_symlink(): return # If it's not a symlink, it shouldn't be any other kind of file. @@ -48,6 +43,11 @@ def audit_local_tasks() -> None: # Create the link. local.symlink_to(vmklib) + # Also link a top-level file. + if not top_level.is_symlink(): + assert not top_level.exists() + top_level.symlink_to(local) + def register( manager: TaskManager, diff --git a/tests/commands/test_svd.py b/tests/commands/test_svd.py index 8dc9d25..290dd1b 100644 --- a/tests/commands/test_svd.py +++ b/tests/commands/test_svd.py @@ -10,34 +10,35 @@ from ifgen.entry import main as ifgen_main -def test_svd_command_basic(): - """Test the 'svd' command.""" +def handle_proc(proc: str) -> None: + """Test that we can generate code from an SVD processor.""" with TemporaryDirectory() as tmpdir: - for svd in ["XMC4700", "rp2040"]: - # Generate configurations. - assert ( - ifgen_main( - [ - PKG_NAME, - "svd", - "-o", - str(tmpdir), - f"package://{PKG_NAME}/svd/{svd}.svd", - ] - ) - == 0 + # Generate configurations. + assert ( + ifgen_main( + [ + PKG_NAME, + "svd", + "-o", + str(tmpdir), + f"package://{PKG_NAME}/svd/{proc}.svd", + ] ) + == 0 + ) - # Generate code. - assert ( - ifgen_main( - [ - PKG_NAME, - "-C", - str(tmpdir), - "gen", - ] - ) - == 0 - ) + # Generate code. + assert ifgen_main([PKG_NAME, "-C", str(tmpdir), "gen"]) == 0 + + +def test_svd_command_basic(): + """Test the 'svd' command.""" + + # Coverage not working with this. + # procs = ["XMC4700", "rp2040", "mimxrt1176_cm7", "mimxrt1176_cm4"] + # with Pool(len(procs)) as pool: + # pool.map(handle_proc, procs) + + for proc in ["mimxrt1176_cm7", "XMC4700", "rp2040"]: + handle_proc(proc)