diff --git a/.github/workflows/generate-classes.yml b/.github/workflows/generate-classes.yml index be3d42714..ec9de5c4d 100644 --- a/.github/workflows/generate-classes.yml +++ b/.github/workflows/generate-classes.yml @@ -40,10 +40,8 @@ jobs: with: python-version: '3.11' - - name: Install python script dependencies - run: | - python -m pip install --upgrade pip - pip install -r resources/utils/requirements.txt + - name: Install uv + run: brew install uv # ----------------------------------------------------------------- # 2. LabMetadataExtension pipeline @@ -52,11 +50,11 @@ jobs: run: | mkdir test_output mkdir test_output/spec - python resources/utils/generate_spec_files.py demo/labmetadata_extension_demo/spec test_output/spec + uv run resources/utils/aqnwb_utils.py generate-spec demo/labmetadata_extension_demo/spec test_output/spec - name: Run schematype_to_aqnwb.py to generate AqNWB classes for the LabMetadataExample extension run: | - python resources/utils/schematype_to_aqnwb.py --generate-test-app demo/labmetadata_extension_demo/spec/ndx-labmetadata-example.namespace.yaml test_output + uv run resources/utils/aqnwb_utils.py generate-types --generate-test-app demo/labmetadata_extension_demo/spec/ndx-labmetadata-example.namespace.yaml test_output - name: List generated files for the LabMetadataExample extension run: | @@ -67,9 +65,8 @@ jobs: cd test_output/test_app mkdir build cd build - cmake -DAQNWB_DIR="${{ github.workspace }}/build" ../ + cmake -Daqnwb_DIR="${{ github.workspace }}/prefix/lib/cmake/aqnwb" ../ make -j 2 - ./bin/schema_compilation_test - name: Run the test program for LabMetadataExample extension run: | @@ -96,11 +93,11 @@ jobs: - name: Run generate_nwb_schema_headers.sh to generated headers the nwb_schema run: | mkdir test_output_nwb_schema/spec - python resources/utils/generate_spec_files.py test_output_nwb_schema/nwb-schema/core test_output_nwb_schema/spec + uv run resources/utils/aqnwb_utils.py generate-spec test_output_nwb_schema/nwb-schema/core test_output_nwb_schema/spec - name: Run schematype_to_aqnwb.py to generate AqNWB classes for the nwb_schema run: | - python resources/utils/schematype_to_aqnwb.py --generate-test-app test_output_nwb_schema/nwb-schema/core/nwb.namespace.yaml test_output_nwb_schema + uv run resources/utils/aqnwb_utils.py generate-types --generate-test-app test_output_nwb_schema/nwb-schema/core/nwb.namespace.yaml test_output_nwb_schema - name: List generated files for the nwb-schema run: | @@ -111,12 +108,10 @@ jobs: cd test_output_nwb_schema/test_app mkdir build cd build - cmake -DAQNWB_DIR="${{ github.workspace }}/build" ../ + cmake -Daqnwb_DIR="${{ github.workspace }}/prefix/lib/cmake/aqnwb" ../ make -j 2 - name: Run the test program for the nwb_schema run: | cd test_output_nwb_schema/test_app/build ./bin/schema_compilation_test - - \ No newline at end of file diff --git a/.github/workflows/python-utils.yml b/.github/workflows/python-utils.yml index 2f2bfaffb..92d3016bd 100644 --- a/.github/workflows/python-utils.yml +++ b/.github/workflows/python-utils.yml @@ -19,10 +19,8 @@ jobs: with: python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r resources/utils/requirements.txt + - name: Install uv + run: brew install uv - name: Clone latest NWB schema release run: | @@ -37,17 +35,17 @@ jobs: echo "Checking out NWB schema at tag: $NWB_LATEST_TAG" git checkout "$NWB_LATEST_TAG" - - name: Run generate_spec_files.py + - name: Run generate_spec_files run: | - output=$(python resources/utils/generate_spec_files.py test_output/nwb-schema/core test_output 2>&1) + output=$(uv run resources/utils/aqnwb_utils.py generate-spec test_output/nwb-schema/core test_output 2>&1) echo "$output" if echo "$output" | grep -q "ERROR"; then exit 1 fi - - name: Run schematype_to_aqnwb.py + - name: Run schematype_to_aqnwb run: | - output=$(python resources/utils/schematype_to_aqnwb.py test_output/nwb-schema/core/nwb.namespace.yaml test_output 2>&1) + output=$(uv run resources/utils/aqnwb_utils.py generate-types test_output/nwb-schema/core/nwb.namespace.yaml test_output 2>&1) echo "$output" if echo "$output" | grep -q "ERROR"; then exit 1 diff --git a/.gitignore b/.gitignore index 82ce979eb..77c13515c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,23 @@ demo/cmake-build-*/ demo/inspect_electrical_series/*.nwb demo/*/Makefile demo/*/cmake_install.cmake + +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +env/ +venv/ +.ipynb_checkpoints + +# Python packaging +build/ +dist/ +*.egg-info/ +wheels/ + +# Pytest +.pytest_cache/ +htmlcov/ diff --git a/CHANGELOG.md b/CHANGELOG.md index cabd142e3..b7615a240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +* Python Utility enhancements: + * Added `aqnwb-utils` as a command-line utility to provide a common interface for aqnwb command line tools, e.g., `schematype_to_aqnwb.py` and `generate_spec_files.py`. (@oruebel, [#227](https://github.com/NeurodataWithoutBorders/aqnwb/pull/227)) + * Added inline script metadata (PEP 723) to Python utilities to enable direct execution with `uv run` without installation (@oruebel, [#229](https://github.com/NeurodataWithoutBorders/aqnwb/pull/229) + * Added `pyproject.toml` for modern Python packaging support (@oruebel, [#229](https://github.com/NeurodataWithoutBorders/aqnwb/pull/229) + ### Changed +* Updated Python utilities to use `uv` instead of `pip` for dependency management and updated docs and github workflows to use uv (@oruebel, [#227](https://github.com/NeurodataWithoutBorders/aqnwb/pull/227) +* Updated documentation to refer to the new `aqnwb-utils` command-line utility (@oruebel, [#227](https://github.com/NeurodataWithoutBorders/aqnwb/pull/227)) * Enhanced the `schematype_to_aqnwb` utility script: * Generated source files are now placed into a folder hierarchy based on the name of the namespace and schemafile of the neurodata_type (@oruebel, [#224](https://github.com/NeurodataWithoutBorders/aqnwb/pull/224)) * Added functionality to optionally create a simple example app that instantiates all generated classes to help test that all generated classes can be compiled (@oruebel, [#225](https://github.com/NeurodataWithoutBorders/aqnwb/pull/225)) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index fb25c0494..7df3209d3 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -26,7 +26,7 @@ EXPAND_ONLY_PREDEF = YES # Add sources INPUT = "@PROJECT_SOURCE_DIR@/src" "@PROJECT_SOURCE_DIR@/docs/pages" RECURSIVE = YES -EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/tests" "@PROJECT_SOURCE_DIR@/.github/CODE_OF_CONDUCT.md" "@PROJECT_SOURCE_DIR@/Legal.txt" "@PROJECT_SOURCE_DIR@/LICENSE" "@PROJECT_SOURCE_DIR@/demo/labmetadata_extension_demo/src" "@PROJECT_SOURCE_DIR@/CHANGELOG.md" +EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/tests" "@PROJECT_SOURCE_DIR@/.github/CODE_OF_CONDUCT.md" "@PROJECT_SOURCE_DIR@/Legal.txt" "@PROJECT_SOURCE_DIR@/LICENSE" "@PROJECT_SOURCE_DIR@/demo/labmetadata_extension_demo/src" "@PROJECT_SOURCE_DIR@/CHANGELOG.md" "@PROJECT_SOURCE_DIR@/resources/utils" IMAGE_PATH = "@PROJECT_SOURCE_DIR@/resources/images" EXTRACT_ALL = YES RECURSIVE = YES @@ -39,7 +39,7 @@ EXTRACT_STATIC = YES # HIDE_UNDOC_MEMBERS = YES # Enable Markdown support -MARKDOWN_SUPPORT = YES +MARKDOWN_SUPPORT = YES # Enable the call and caller graphs (this increases built time but seems reasonable for AqNWB) CALL_GRAPH = YES diff --git a/docs/pages/devdocs/install.dox b/docs/pages/devdocs/install.dox index 0965bd0fd..e361706d6 100644 --- a/docs/pages/devdocs/install.dox +++ b/docs/pages/devdocs/install.dox @@ -3,7 +3,9 @@ * * \tableofcontents * - * \section dev_requirements_sec Requirements + * \section dev_install_aqnwb_sec Installing AqNWB + * + * \subsection dev_requirements_sec Requirements * * Please ensure that the required libraries described in the * \ref user_requirements_sec "User Requirements" section are installed and @@ -24,8 +26,7 @@ * - clang-format (optional, required for ``target=format-check``, ``target=format-fix``) * - codespell (optional, required for ``target=spell-check``, ``target=spell-fix``) * - * - * \section devbuild_sec Developer Build + * \subsection devbuild_sec Developer Build * * Build system targets that are only useful for developers of AqNWB are * hidden if the `aqnwb_DEVELOPER_MODE` option is disabled. Enabling this @@ -58,7 +59,7 @@ * The use of `HDF5_ROOT` and `BOOST_ROOT` environment variables is deprecated for modern CMake * and may not work reliably with recent CMake versions. * - * \section devbuild_presets_subsec Developer Presets + * \subsubsection devbuild_presets_subsec Developer Presets * * As a developer, you can create your own dev preset by making a `CMakeUserPresets.json` file at the root of * the project: @@ -103,7 +104,7 @@ * Replace `` in the `CMakeUserPresets.json` file with the name of * the operating system you have (`win64`, `linux` or `darwin`). * - * \subsection configure_build_test Configure, Build and Test + * \subsubsection configure_build_test Configure, Build and Test * * You can configure, build and test the project respectively with the following commands from the project root on * any operating system with any build system: @@ -114,7 +115,7 @@ * ctest --preset=dev * \endcode * - * \section devbuild_dev_mode_targets_subsec Developer Mode Targets + * \subsubsection devbuild_dev_mode_targets_subsec Developer Mode Targets * * Additional targets can be invoked when in development mode using the commands below * @@ -122,10 +123,19 @@ * cmake --build --preset=dev --target= * \endcode * - * \subsection devbuild_target_options_subsubsec Target options + * \paragraph devbuild_target_options_subsubsec Target options * - `format-check`: run the `clang-format` tool on the codebase to check for formatting errors * - `format-fix` : run the `clang-format` tool on the codebase with `FIX=YES` to both check and automatically fix for formatting errors * - `spell-check`: run the `codespell` tool on the codebase to check for common spelling errors * - `spell-fix` : run the `codespell` tool on the codebase with `FIX=YES` to both check and automatically fix common spelling errors * - `docs` : builds the documentation using Doxygen. (Note: run `cmake --preset=dev -DBUILD_DOCS=ON` before building to add docs target) + * + * \section dev_install_utils_sec Installing Python Utilities + * + * AqNWB provides a set of Python utilities for developers to help with generating C++ classes from NWB schema files. + * For details on how to use the utilities, see the [README.md](https://github.com/NeurodataWithoutBorders/aqnwb/blob/main/resources/utils/README.md) + * in the `resources/utils` directory. + * + * **README.md** + * \include resources/utils/README.md */ diff --git a/docs/pages/devdocs/integrating_extensions.dox b/docs/pages/devdocs/integrating_extensions.dox index bf7592335..497870278 100644 --- a/docs/pages/devdocs/integrating_extensions.dox +++ b/docs/pages/devdocs/integrating_extensions.dox @@ -9,7 +9,10 @@ * Integrating a new schema namespace with AqNWB (e.g., to support an extension to NWB) involves generating * the necessary specification files and ensuring that the namespace is registered with the * \ref AQNWB::SPEC::NamespaceRegistry "NamespaceRegistry". This process is simplified through - * the use of the `generate_spec_files.py` script. + * the use of Python utilities that can be run with `uv`. + * + * @note + * See the \ref dev_install_utils_sec "Python Utilities" section in the developer installation guide for instructions on how to use the utilities with `uv`. * * 1. **Get the schema files**: Download or create the schema for the namespace in YAML format. * @note @@ -17,13 +20,13 @@ * [NWB Extension Tutorial](https://nwb-overview.readthedocs.io/en/latest/extensions_tutorial/extensions_tutorial_home.html) * for more information on how to create data schema for NWB. * - * 2. **Convert the schema files to C++**: Run the `resources/utils/generate_spec_files.py` script on your + * 2. **Convert the schema files to C++**: Run the `generate-spec` command on your * schema files to generate the necessary C++ header files. This script processes the schema and creates * the appropriate C++ header files that include the namespace definitions and registration. * @note * To learn more about how to use the script and its parameters, you can view the help doc by running: * @code - * python resources/utils/generate_spec_files.py --help + * uv run resources/utils/aqnwb_utils.py generate-spec --help * @endcode * * 3. **Include the Generated Header Files**: In your C++ code that uses AqNWB, include the generated header files. The @@ -34,7 +37,7 @@ * * 4. **Implement appropriate RegisteredType classes**: Follow the tutorial on * \ref registered_type_page to define appropriate interfaces for the `neurodata_type`s defined in your new namespace. - * \ref using_schematype_to_aqnwb can also provide additional help by providing a simple + * The `generate-types` command can also provide additional help by providing a simple * utility that can automatically generate skeleton AqNWB C++ classes for neurodata_types * directly from JSON/YAML schema files. * @@ -90,11 +93,11 @@ * * @subsection labmetadata_extension_cpp_generation Step 2: Convert the Schema to C++ * - * The schema files are converted to C++ using the `resources/utils/generate_spec_files.py` script: + * The schema files are converted to C++ using the `generate-spec` command: * * @code * mkdir demo/labmetadata_extension_demo/src - * python resources/utils/generate_spec_files.py demo/labmetadata_extension_demo/spec demo/labmetadata_extension_demo/src + * uv run resources/utils/aqnwb_utils.py generate-spec demo/labmetadata_extension_demo/spec demo/labmetadata_extension_demo/src * @endcode * * This generates the following new header file in the `demo/labmetadata_extension_demo/src` folder: diff --git a/docs/pages/devdocs/nwb_schema.dox b/docs/pages/devdocs/nwb_schema.dox index 7a4d6ece8..77a306552 100644 --- a/docs/pages/devdocs/nwb_schema.dox +++ b/docs/pages/devdocs/nwb_schema.dox @@ -19,7 +19,7 @@ * * This script will: * - Clone the latest NWB and HDMF schema repositories into a temporary directory - * - Run `resources/utils/generate_spec_files.py` for both the NWB core and HDMF common schemas + * - Run `uv run resources/utils/aqnwb_utils.py generate-spec` for both the NWB core and HDMF common schemas * - Copy the generated C++ header files to `src/spec` * - Clean up all temporary files automatically * @@ -33,16 +33,18 @@ * PYTHON=python3 bash resources/utils/generate_nwb_schema_headers.sh * @endcode * - * For use with extensions and other advanced or custom use cases, developers may still run `resources/utils/generate_spec_files.py` directly. + * For use with extensions and other advanced or custom use cases, developers may still run the `generate-spec` command directly: + * @code{.sh} + * uv run resources/utils/aqnwb_utils.py generate-spec + * @endcode * * * \section dev_docs_updating_nwb_schema_section Updating the schema * * Currently, the version of the schema being used for development is fixed and stored in the `/resources/schema` folder. * Updating to a newer version of the schema requires: - * - Regeneration of the `spec` header files via `resources/utils/generate_spec_files.py` + * - Regeneration of the `spec` header files via the `generate-spec` command * - Update of existing `Container` classes and unit tests in AqNWB to match changes in the new schema compared to the previous schema * - Successful completion of all unit-test and round-trip testing with PyNWB and MatNWB * */ - diff --git a/docs/pages/devdocs/registered_types.dox b/docs/pages/devdocs/registered_types.dox index dedf172ce..7ec5af29f 100644 --- a/docs/pages/devdocs/registered_types.dox +++ b/docs/pages/devdocs/registered_types.dox @@ -415,9 +415,9 @@ * in the \ref AQNWB::NWB::ElectrodesTable "ElectrodesTable" to read the `group_name` column * as `VectorData` with the data type already specified as `std::string` at compile time. * - * \section using_schematype_to_aqnwb Using the schematype_to_aqnwb.py Utility + * \section using_schematype_to_aqnwb Using the generate-types command * - * The `resources/utils/schematype_to_aqnwb.py` script, included in the + * The `generate-types` command, included in the * [AqNWB source repository](https://github.com/NeurodataWithoutBorders/aqnwb), * is a simple utility designed to create skeleton C++ source files for integrating new * neurodata_types with AqNWB. While the generated source files are only an outline and are not guaranteed to compile, @@ -427,20 +427,29 @@ * for all neurodata_types in the NWB schema: * * @code - * python resources/utils/schematype_to_aqnwb.py nwb-schema/core/nwb.namespace.yaml test_output + * uv run resources/utils/aqnwb_utils.py generate-types nwb-schema/core/nwb.namespace.yaml test_output * @endcode * * The generated files will be placed in the folder hierarchy based on the name of the namespace and * source yaml file where the type is defined. E.g, `TimeSeries` is defined in `nwb.base.yaml` in the * `core` NWB namespace, and will be generated as `core/base/TimeSeries.hpp`. + * + * We can also create a simple example app that instantiates all the generated classes to make it + * simplify testing that all the classes can be compiled. To generate the app, simply add the + * `--generate-test-app` option: + * + * @code + * uv run resources/utils/aqnwb_utils.py generate-types --generate-test-app nwb-schema/core/nwb.namespace.yaml test_output + * @endcode + * * To learn more about how to use the script and its parameters, you can view the help doc by running: * * @code - * python resources/utils/schematype_to_aqnwb.py --help + * uv run resources/utils/aqnwb_utils.py generate-types --help * @endcode * * \note - * The `schematype_to_aqnwb.py` uses `PyNWB` for parsing schema. Currently the script does not unload + * The `generate-types` command uses `PyNWB` for parsing schema. Currently the script does not unload * namespaces loaded by default by `PyNWB`. I.e., if you see a warning of the form: * \code * UserWarning: Ignoring cached namespace 'core' version 2.7.0 because version 2.8.0 is already loaded. @@ -449,6 +458,10 @@ * than the requested version. In practice, this is mainly relevant if you are generating classes for the * NWB `core` and `hdmf-common` namespaces. * + * \note + * When generating the test app via the `--generate-test-app` option we also + * need to generate the schema headers files and save them in the `/spec` folder. + * * \section implement_registered_type_unit_tests Testing RegisteredTypes * * As with all code, it is good practice to create appropriate unit tests to validate diff --git a/resources/utils/README.md b/resources/utils/README.md new file mode 100644 index 000000000..b831992d0 --- /dev/null +++ b/resources/utils/README.md @@ -0,0 +1,73 @@ +# AqNWB Utilities + +Command-line utilities for AqNWB development. + +## Installation + +### Using uv (Recommended) + +The utilities use inline script metadata (PEP 723) and can be run directly with `uv`: + +```bash +# Install uv if you haven't already +brew install uv + +# Run utilities directly without installation +uv run resources/utils/aqnwb_utils.py generate-spec +uv run resources/utils/aqnwb_utils.py generate-types +``` + +### Traditional Installation + +You can also install the package if you prefer: + +```bash +# Using uv +uv pip install ./resources/utils + +# Or using pip +pip install ./resources/utils + +# Then use the installed command +aqnwb-utils generate-spec +``` + +## Usage + +### Direct execution with uv (no installation required) + +```bash +# Generate spec files +uv run resources/utils/aqnwb_utils.py generate-spec + +# Generate neurodata types +uv run resources/utils/aqnwb_utils.py generate-types + +# Generate types with test app +uv run resources/utils/aqnwb_utils.py generate-types --generate-test-app +``` + +### After installation + +```bash +# Generate spec files +aqnwb-utils generate-spec + +# Generate neurodata types +aqnwb-utils generate-types + +# Generate types with test app +aqnwb-utils generate-types --generate-test-app +``` + +## Available Commands + +- `generate-spec`: Generate C++ header files from NWB schema files +- `generate-types`: Generate C++ classes from neurodata types defined in schema files + +## Benefits of using uv + +- **No installation required**: Run scripts directly with their dependencies automatically managed +- **Isolated environments**: Each script run uses its own isolated environment +- **Fast**: uv is significantly faster than pip for dependency resolution and installation +- **Reproducible**: Dependencies are specified in the script itself using PEP 723 metadata diff --git a/resources/utils/__init__.py b/resources/utils/__init__.py new file mode 100644 index 000000000..c441c4812 --- /dev/null +++ b/resources/utils/__init__.py @@ -0,0 +1 @@ +# This file makes the utils directory a package diff --git a/resources/utils/aqnwb_utils.py b/resources/utils/aqnwb_utils.py new file mode 100644 index 000000000..e1f026c3d --- /dev/null +++ b/resources/utils/aqnwb_utils.py @@ -0,0 +1,32 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.8" +# dependencies = ["hdmf", "pynwb", "ruamel.yaml"] +# /// + +import argparse +import generate_spec_files +import schematype_to_aqnwb + +def main(): + parser = argparse.ArgumentParser(description="AQNWB utilities") + subparsers = parser.add_subparsers(dest="command") + + # Sub-parser for generating spec files + parser_spec = subparsers.add_parser("generate-spec", help="Generate spec files") + generate_spec_files.setup_parser(parser_spec) + parser_spec.set_defaults(func=generate_spec_files.main) + + # Sub-parser for generating types + parser_types = subparsers.add_parser("generate-types", help="Generate neurodata types") + schematype_to_aqnwb.setup_parser(parser_types) + parser_types.set_defaults(func=schematype_to_aqnwb.main) + + args = parser.parse_args() + if hasattr(args, 'func'): + args.func(args) + else: + parser.print_help() + +if __name__ == "__main__": + main() diff --git a/resources/utils/generate_spec_files.py b/resources/utils/generate_spec_files.py index 15933b180..9f08aa1aa 100644 --- a/resources/utils/generate_spec_files.py +++ b/resources/utils/generate_spec_files.py @@ -1,3 +1,9 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.8" +# dependencies = ["ruamel.yaml"] +# /// + import json import argparse from pathlib import Path @@ -187,11 +193,16 @@ def process_schema_files(schema_dir: Path, output_dir: Path, chunk_size: int) -> process_namespace_file(file, output_dir, chunk_size) logger.info(f"Finished processing schema files in directory: {schema_dir}") -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Process schema files.') +def setup_parser(parser): parser.add_argument('schema_dir', type=Path, nargs='?', default=Path('./resources/schema/'), help='Directory containing the schema files') parser.add_argument('output_dir', type=Path, nargs='?', default=Path('./src/spec/'), help='Directory to output the generated header files') parser.add_argument('--chunk-size', type=int, default=16000, help='Size of the chunks for splitting large JSON strings') - args = parser.parse_args() +def main(args): process_schema_files(args.schema_dir, args.output_dir, args.chunk_size) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Process schema files.') + setup_parser(parser) + args = parser.parse_args() + main(args) diff --git a/resources/utils/pyproject.toml b/resources/utils/pyproject.toml new file mode 100644 index 000000000..fb1db67f1 --- /dev/null +++ b/resources/utils/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "aqnwb-utils" +version = "0.1.0" +description = "Command-line utilities for AqNWB development" +readme = "README.md" +requires-python = ">=3.8" +license = {text = "BSD-3-Clause"} +authors = [ + {name = "AqNWB Development Team"} +] +dependencies = [ + "hdmf", + "pynwb", + "ruamel.yaml", +] + +[project.scripts] +aqnwb-utils = "aqnwb_utils:main" + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/resources/utils/schematype_to_aqnwb.py b/resources/utils/schematype_to_aqnwb.py index 3e548d63e..b78c1b95f 100755 --- a/resources/utils/schematype_to_aqnwb.py +++ b/resources/utils/schematype_to_aqnwb.py @@ -1,4 +1,8 @@ -#!/usr/bin/env python3 +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.8" +# dependencies = ["hdmf", "pynwb", "ruamel.yaml"] +# /// """ Script to generate C++ code from NWB schema files. @@ -1010,12 +1014,6 @@ def generate_test_app_cmake(output_dir: Path, app_name: str, cpp_files: List[str # Convert file paths to use forward slashes for CMake cpp_files_cmake = [f.replace("\\", "/") for f in cpp_files] - # Calculate the path to AqNWB source directory relative to script location - # Script is in resources/utils, so AqNWB src is ../../src relative to script - aqnwb_src_dir = script_dir.parent.parent / "src" - aqnwb_build_dir = script_dir.parent.parent / "build" / "dev" - aqnwb_libs_dir = script_dir.parent.parent / "libs" - cmake_content = f"""cmake_minimum_required(VERSION 3.15) project({app_name} VERSION 0.1.0 LANGUAGES CXX) @@ -1023,19 +1021,13 @@ def generate_test_app_cmake(output_dir: Path, app_name: str, cpp_files: List[str set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Allow configuring paths to dependencies based on script location -set(AQNWB_SRC_DIR "{aqnwb_src_dir.as_posix()}" CACHE PATH "Path to aqnwb source directory") -set(AQNWB_DIR "{aqnwb_build_dir.as_posix()}" CACHE PATH "Path to aqnwb build directory") -set(HDF5_DIR "{(aqnwb_libs_dir / "hdf5_build" / "install" / "cmake").as_posix()}" CACHE PATH "Path to HDF5 build directory") -set(BOOST_ROOT "{(aqnwb_libs_dir / "boost_install").as_posix()}" CACHE PATH "Path to Boost root directory") - -# Disable compiler flags that cause issues on macOS -if(APPLE) - set(CMAKE_CXX_FLAGS "${{CMAKE_CXX_FLAGS}} -Wno-unused-command-line-argument") -endif() +# Find aqnwb package. The aqnwb_DIR must be set on the command line +# e.g. -Daqnwb_DIR=/path/to/aqnwb/install/lib/cmake/aqnwb +find_package(aqnwb REQUIRED) -# Find required dependencies +# Find HDF5 find_package(HDF5 REQUIRED COMPONENTS CXX) +# Find Boost find_package(Boost REQUIRED) # Generated source files @@ -1055,26 +1047,15 @@ def generate_test_app_cmake(output_dir: Path, app_name: str, cpp_files: List[str # Include directories target_include_directories({app_name} PRIVATE - ${{AQNWB_SRC_DIR}} - ${{CMAKE_CURRENT_SOURCE_DIR}}/.. + "${{CMAKE_CURRENT_SOURCE_DIR}}/.." + "${{CMAKE_CURRENT_SOURCE_DIR}}/../spec" ${{HDF5_INCLUDE_DIRS}} ${{Boost_INCLUDE_DIRS}} ) -# Find the aqnwb library -if(EXISTS "${{AQNWB_DIR}}/libaqnwb.a") - set(AQNWB_LIBRARY "${{AQNWB_DIR}}/libaqnwb.a") -elseif(EXISTS "${{AQNWB_DIR}}/libaqnwb.so") - set(AQNWB_LIBRARY "${{AQNWB_DIR}}/libaqnwb.so") -elseif(EXISTS "${{AQNWB_DIR}}/libaqnwb.dylib") - set(AQNWB_LIBRARY "${{AQNWB_DIR}}/libaqnwb.dylib") -else() - message(FATAL_ERROR "Could not find aqnwb library in ${{AQNWB_DIR}}. Please build the main project first or set AQNWB_DIR to the correct path.") -endif() - # Link libraries target_link_libraries({app_name} - ${{AQNWB_LIBRARY}} + aqnwb::aqnwb ${{HDF5_CXX_LIBRARIES}} ${{Boost_LIBRARIES}} ) @@ -1307,7 +1288,21 @@ def generate_test_app( logger.error(f"Failed to generate test application: {e}") -def main() -> None: +def setup_parser(parser): + """ + Set up argument parser for the script. + """ + parser.add_argument( + "schema_file", help="Path to the namespace schema file (JSON or YAML)" + ) + parser.add_argument("output_dir", help="Directory to output the generated code") + parser.add_argument( + "--generate-test-app", + action="store_true", + help="Generate a test application to verify compilation of all generated classes" + ) + +def main(args) -> None: """ Main function to parse arguments and generate code. @@ -1320,21 +1315,6 @@ def main() -> None: Returns: None """ - parser = argparse.ArgumentParser( - description="Generate C++ code from NWB schema files." - ) - parser.add_argument( - "schema_file", help="Path to the namespace schema file (JSON or YAML)" - ) - parser.add_argument("output_dir", help="Directory to output the generated code") - parser.add_argument( - "--generate-test-app", - action="store_true", - help="Generate a test application to verify compilation of all generated classes" - ) - - args = parser.parse_args() - try: logger.info(f"Parsing schema file: {args.schema_file}") namespace, neurodata_types, type_to_file_map, type_to_namespace_map = parse_schema_file(Path(args.schema_file)) @@ -1431,4 +1411,9 @@ def main() -> None: if __name__ == "__main__": - main() + parser = argparse.ArgumentParser( + description="Generate C++ code from NWB schema files." + ) + setup_parser(parser) + args = parser.parse_args() + main(args)