.. Copyright Spack Project Developers. See COPYRIGHT file for details. SPDX-License-Identifier: (Apache-2.0 OR MIT) .. meta:: :description lang=en: Discover how to extend Spack's core functionality by creating custom commands and plugins. Custom Extensions ================= *Spack extensions* allow you to add custom subcommands to the ``spack`` command. This is extremely useful when developing and maintaining a command whose purpose is too specific to be included in the Spack codebase. It's also useful for evolving a command through its early stages before starting a discussion to merge it upstream. From Spack's point of view, an extension is any path in your filesystem that respects the following naming and layout for files: .. code-block:: console spack-scripting/ # The top level directory must match the format 'spack-{extension_name}' ├── pytest.ini # Optional file if the extension ships its own tests ├── scripting # Folder that may contain modules that are needed for the extension commands │ ├── cmd # Folder containing extension commands │ │ └── filter.py # A new command that will be available │ └── functions.py # Module with internal details ├── tests # Tests for this extension │ ├── conftest.py │ └── test_filter.py └── templates # Templates that may be needed by the extension In the example above, the extension is named *scripting*. It adds an additional command (``spack filter``) and unit tests to verify its behavior. The extension can import any core Spack module in its implementation. When loaded by the ``spack`` command, the extension itself is imported as a Python package in the ``spack.extensions`` namespace. In the example above, since the extension is named "scripting", the corresponding Python module is ``spack.extensions.scripting``. The code for this example extension can be obtained by cloning the corresponding git repository: .. code-block:: console $ git -C /tmp clone https://github.com/spack/spack-scripting.git Configure Spack to Use Extensions --------------------------------- To make your current Spack instance aware of extensions you should add their root paths to ``config.yaml``. In the case of our example, this means ensuring that: .. code-block:: yaml config: extensions: - /tmp/spack-scripting is part of your configuration file. Once this is set up, any command that the extension provides will be available from the command line: .. code-block:: console $ spack filter --help usage: spack filter [-h] [--installed | --not-installed] [--explicit | --implicit] [--output OUTPUT] ... filter specs based on their properties positional arguments: specs specs to be filtered optional arguments: -h, --help show this help message and exit --installed select installed specs --not-installed select specs that are not yet installed --explicit select specs that were installed explicitly --implicit select specs that are not installed or were installed implicitly --output OUTPUT where to dump the result The corresponding unit tests can be run giving the appropriate options to ``spack unit-test``: .. code-block:: console $ spack unit-test --extension=scripting ========================================== test session starts =========================================== platform linux -- Python 3.11.5, pytest-7.4.3, pluggy-1.3.0 rootdir: /home/culpo/github/spack-scripting configfile: pytest.ini testpaths: tests plugins: xdist-3.5.0 collected 5 items tests/test_filter.py ..... [100%] ========================================== slowest 30 durations ========================================== 2.31s setup tests/test_filter.py::test_filtering_specs[kwargs0-specs0-expected0] 0.57s call tests/test_filter.py::test_filtering_specs[kwargs2-specs2-expected2] 0.56s call tests/test_filter.py::test_filtering_specs[kwargs4-specs4-expected4] 0.54s call tests/test_filter.py::test_filtering_specs[kwargs3-specs3-expected3] 0.54s call tests/test_filter.py::test_filtering_specs[kwargs1-specs1-expected1] 0.48s call tests/test_filter.py::test_filtering_specs[kwargs0-specs0-expected0] 0.01s setup tests/test_filter.py::test_filtering_specs[kwargs4-specs4-expected4] 0.01s setup tests/test_filter.py::test_filtering_specs[kwargs2-specs2-expected2] 0.01s setup tests/test_filter.py::test_filtering_specs[kwargs1-specs1-expected1] 0.01s setup tests/test_filter.py::test_filtering_specs[kwargs3-specs3-expected3] (5 durations < 0.005s hidden. Use -vv to show these durations.) =========================================== 5 passed in 5.06s ============================================ Registering Extensions via Entry Points --------------------------------------- .. note:: Python version >= 3.8 is required to register extensions via entry points. Spack can be made aware of extensions that are installed as part of a Python package. To do so, register a function that returns the extension path, or paths, to the ``"spack.extensions"`` entry point. Consider the Python package ``my_package`` that includes a Spack extension: .. code-block:: console my-package/ ├── src │ ├── my_package │ │ └── __init__.py │ └── spack-scripting/ # the spack extensions └── pyproject.toml adding the following to ``my_package``'s ``pyproject.toml`` will make the ``spack-scripting`` extension visible to Spack when ``my_package`` is installed: .. code-block:: toml [project.entry_points."spack.extensions"] my_package = "my_package:get_extension_path" The function ``my_package.get_extension_path`` in ``my_package/__init__.py`` might look like .. code-block:: python import importlib.resources def get_extension_path(): dirname = importlib.resources.files("my_package").joinpath("spack-scripting") if dirname.exists(): return str(dirname)