Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0



## Upcoming (~June 2026)
## Upcoming [0.4.0] (~August 2026)

### Added
* Added `ElectricalSeries::writeAllChannels` method and `IO::writeElectricalSeriesData` overload to simplify zero-copy interleaved multichannel writes. (@copilot, @oruebel, [#293](https://github.com/NeurodataWithoutBorders/aqnwb/pull/293))
Expand All @@ -16,11 +16,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
* **[BREAKING]** Moved `disableSWMRMode` option from `HDF5IO` constructor to a new `HDF5IO::startRecording(bool disableSWMRMode)` overload. The `BaseIO`-compliant `startRecording()` override is preserved and defaults to SWMR enabled.
* **Migration Note**: Code using `HDF5IO(path, true)` must be updated to `HDF5IO(path)` followed by `startRecording(true)`. When the `HDF5IO` object is held as a `std::shared_ptr<BaseIO>` (e.g., from `createIO`), downcast with `std::dynamic_pointer_cast<HDF5IO>` to access the overload. (@oruebel [#297](https://github.com/NeurodataWithoutBorders/aqnwb/pull/297))
* Updated schema in the `spec/` module to the latest NWB releases: (i) NWB 2.10, (ii) HDMF Common 1.9, and (iii) HDMF Experimental 0.6 (@oruebel, [#300](https://github.com/NeurodataWithoutBorders/aqnwb/pull/300))

### Fixed
* Updated nwbinspector validation tests in the CI to: 1) `--ignore=check_subject_exists` and 2) remove dependency on `sanitizer` tests to speed up CI (@oruebel, [#289](https://github.com/NeurodataWithoutBorders/aqnwb/pull/289))
* Fixed `get_utc_offset_seconds` to correctly account for daylight saving time using platform-specific APIs (`tm_gmtoff` on Unix/macOS; `_get_timezone` + `_get_dstbias` on Windows), preventing `session_start_time` from being written ~1 hour ahead of UTC during DST (@cboulay, [#295](https://github.com/NeurodataWithoutBorders/aqnwb/pull/295))

* Fixed `resources/utils/schematype_to_aqnwb.py` to emit forward declarations rather than full header includes for referenced generated types, avoiding circular header dependencies. This issue surfaced due to the update to the latest HDMF Common schema types where `DynamicTable` references `MeaningsTable`, which is itself a `DynamicTable` (@copilot, @oruebel, [#300](https://github.com/NeurodataWithoutBorders/aqnwb/pull/300))
* Fixed Windows CI by updating the CMake generator in `CMakePresets.json` from `"Visual Studio 17 2022"` to `"Visual Studio 18 2026"` to match the updated `windows-latest` runner (@copilot, [#300](https://github.com/NeurodataWithoutBorders/aqnwb/pull/300))

## [0.3.0] - 2026-02-23

Expand Down
2 changes: 1 addition & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
{
"name": "ci-win64",
"inherits": ["flags-msvc", "ci-std"],
"generator": "Visual Studio 17 2022",
"generator": "Visual Studio 18 2026",
"architecture": "x64",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
Expand Down
85 changes: 74 additions & 11 deletions resources/utils/schematype_to_aqnwb.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,51 @@ def get_referenced_types(neurodata_type: Spec, type_to_namespace_map: Dict[str,
return set(referenced_types) # Return unique types only


def render_forward_declarations(
referenced_types: set[str],
type_to_namespace_map: Dict[str, str],
current_type_name: str,
) -> str:
"""
Render forward declarations for referenced neurodata types.

Forward declarations avoid circular includes when a generated parent type
references one of its generated subtypes while that subtype inherits from
the parent (e.g., DynamicTable <-> MeaningsTable in newer HDMF Common
schemas).

Parameters:
referenced_types (set[str]): Referenced neurodata type names.
type_to_namespace_map (Dict[str, str]): Mapping of types to their namespaces.
current_type_name (str): Name of the type currently being generated.

Returns:
str: Forward declaration blocks grouped by C++ namespace.
"""
declarations_by_namespace = {}
for referenced_type in sorted(referenced_types):
if referenced_type == current_type_name:
continue
referenced_namespace = to_cpp_namespace_name(
type_to_namespace_map.get(referenced_type, "")
)
declarations_by_namespace.setdefault(referenced_namespace, []).append(
referenced_type
)

if not declarations_by_namespace:
return ""

forward_declarations = "// Forward declarations for referenced types\n"
for referenced_namespace in sorted(declarations_by_namespace):
forward_declarations += f"namespace {referenced_namespace} {{\n"
for referenced_type in declarations_by_namespace[referenced_namespace]:
forward_declarations += f"class {referenced_type};\n"
forward_declarations += f"}} // namespace {referenced_namespace}\n"

return forward_declarations


def parse_schema_file(file_path: Path) -> Tuple[SpecNamespace, Dict[str, Spec], Dict[str, Path], Dict[str, str]]:
"""
Parse a schema file and return the namespace and data types using PyNWB.
Expand Down Expand Up @@ -1187,17 +1232,17 @@ def generate_header_file(
else:
header += '#include "nwb/hdmf/base/Container.hpp"\n'

# Determine additional includes required for DEFINE_REGISTERED_FIELD macro definitions
# for groups and datasets, and links to other neurodata_types referenced via attributes
# Add forward declarations for referenced types used in generated shared_ptr
# accessors and initialize signatures. This avoids circular includes for
# schema patterns where a parent type references a child type that inherits
# from the same parent.
referenced_types = get_referenced_types(neurodata_type, type_to_namespace_map)
if len(referenced_types) > 0:
header += "// Includes for types that are referenced and used\n"
# Add includes for all referenced types
for ref_type in referenced_types:
ref_namespace = type_to_namespace_map.get(ref_type, namespace.name)
ref_subfolder = get_schema_subfolder_name(type_to_file_map.get(ref_type, None))
ref_include_path = f"{ref_namespace}/{ref_subfolder}/{ref_type}.hpp"
header += f'#include "{ref_include_path}"\n'
header += render_forward_declarations(
referenced_types=referenced_types,
type_to_namespace_map=type_to_namespace_map,
current_type_name=type_name,
)

# Include the namespace header
header += "// Include for the namespace schema header\n"
Expand Down Expand Up @@ -1367,7 +1412,11 @@ def is_commented_field_def(input_field: str) -> bool:


def generate_implementation_file(
namespace: SpecNamespace, neurodata_type: Spec, all_types: Dict[str, Spec], type_to_namespace_map: Dict[str, str]
namespace: SpecNamespace,
neurodata_type: Spec,
all_types: Dict[str, Spec],
type_to_file_map: Dict[str, Path],
type_to_namespace_map: Dict[str, str],
) -> str:
"""
Generate C++ implementation file for a neurodata type.
Expand All @@ -1376,6 +1425,7 @@ def generate_implementation_file(
namespace (SpecNamespace): The namespace object.
neurodata_type (Spec): The neurodata type spec.
all_types (Dict[str, Spec]): A dictionary of all neurodata types.
type_to_file_map (Dict[str, Path]): Mapping of types to their source schema files.
type_to_namespace_map (Dict[str, str]): Mapping of types to their namespaces.

Returns:
Expand Down Expand Up @@ -1415,9 +1465,22 @@ def generate_implementation_file(
parent_neurodata_type=parent_neurodata_type_spec
)

referenced_type_includes = ""
referenced_types = get_referenced_types(neurodata_type, type_to_namespace_map)
if referenced_types:
referenced_type_includes += "// Includes for referenced types\n"
for ref_type in sorted(referenced_types):
if ref_type == class_name:
continue
ref_namespace = type_to_namespace_map.get(ref_type, namespace.name)
ref_subfolder = get_schema_subfolder_name(type_to_file_map.get(ref_type, None))
ref_include_path = f"{ref_namespace}/{ref_subfolder}/{ref_type}.hpp"
referenced_type_includes += f'#include "{ref_include_path}"\n'

# Start building the implementation file
impl = f"""#include "{class_name}.hpp"
#include "Utils.hpp"
{referenced_type_includes}

using namespace {cpp_namespace_name};
using namespace AQNWB::IO;
Expand Down Expand Up @@ -1816,7 +1879,7 @@ def main(args) -> None:
cpp_file_name = f"{class_name}.cpp"
logger.info(f" Generating implementation file: {cpp_file_name}")
impl_file = generate_implementation_file(
namespace, neurodata_type, neurodata_types, type_to_namespace_map
namespace, neurodata_type, neurodata_types, type_to_file_map, type_to_namespace_map
)
impl_path = type_output_dir / cpp_file_name
with open(impl_path, "w") as f:
Expand Down
28 changes: 16 additions & 12 deletions src/spec/core.hpp

Large diffs are not rendered by default.

Loading
Loading