.. Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
   Spack Project Developers. See the top-level COPYRIGHT file for details.

   SPDX-License-Identifier: (Apache-2.0 OR MIT)

.. extensions:

=================
Custom Extensions
=================

.. warning::

   The support for extending Spack with custom commands is still experimental.
   Users should expect APIs or prescribed directory structures to
   change at any time.

*Spack extensions* permit you to extend Spack capabilities by deploying your
own custom commands or logic in an arbitrary location on your filesystem.
This might be extremely useful e.g. to develop and maintain a command whose purpose is
too specific to be considered for reintegration into the mainline or to
evolve 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 which
respects a prescribed 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
  ├── 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 named *scripting* adds an additional command (``filter``)
and unit tests to verify its behavior. The code for this example can be
obtained by cloning the corresponding git repository:

.. TODO: write an ad-hoc "hello world" extension and make it part of the spack organization

.. code-block:: console

   $ pwd
   /home/user
   $ mkdir tmp && cd tmp
   $ git clone https://github.com/alalazo/spack-scripting.git
   Cloning into 'spack-scripting'...
   remote: Counting objects: 11, done.
   remote: Compressing objects: 100% (7/7), done.
   remote: Total 11 (delta 0), reused 11 (delta 0), pack-reused 0
   Receiving objects: 100% (11/11), done.

As you can see by inspecting the sources, Python modules that are part of the extension
can import any core Spack module.

---------------------------------
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:
     - /home/user/tmp/spack-scripting

is part of your configuration file. Once this is setup 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 test``:

.. code-block:: console

   $ spack test --extension=scripting

   ============================================================== test session starts ===============================================================
   platform linux2 -- Python 2.7.15rc1, pytest-3.2.5, py-1.4.34, pluggy-0.4.0
   rootdir: /home/mculpo/tmp/spack-scripting, inifile: pytest.ini
   collected 5 items

   tests/test_filter.py ...XX
   ============================================================ short test summary info =============================================================
   XPASS tests/test_filter.py::test_filtering_specs[flags3-specs3-expected3]
   XPASS tests/test_filter.py::test_filtering_specs[flags4-specs4-expected4]

   =========================================================== slowest 20 test durations ============================================================
   3.74s setup    tests/test_filter.py::test_filtering_specs[flags0-specs0-expected0]
   0.17s call     tests/test_filter.py::test_filtering_specs[flags3-specs3-expected3]
   0.16s call     tests/test_filter.py::test_filtering_specs[flags2-specs2-expected2]
   0.15s call     tests/test_filter.py::test_filtering_specs[flags1-specs1-expected1]
   0.13s call     tests/test_filter.py::test_filtering_specs[flags4-specs4-expected4]
   0.08s call     tests/test_filter.py::test_filtering_specs[flags0-specs0-expected0]
   0.04s teardown tests/test_filter.py::test_filtering_specs[flags4-specs4-expected4]
   0.00s setup    tests/test_filter.py::test_filtering_specs[flags4-specs4-expected4]
   0.00s setup    tests/test_filter.py::test_filtering_specs[flags3-specs3-expected3]
   0.00s setup    tests/test_filter.py::test_filtering_specs[flags1-specs1-expected1]
   0.00s setup    tests/test_filter.py::test_filtering_specs[flags2-specs2-expected2]
   0.00s teardown tests/test_filter.py::test_filtering_specs[flags2-specs2-expected2]
   0.00s teardown tests/test_filter.py::test_filtering_specs[flags1-specs1-expected1]
   0.00s teardown tests/test_filter.py::test_filtering_specs[flags0-specs0-expected0]
   0.00s teardown tests/test_filter.py::test_filtering_specs[flags3-specs3-expected3]
   ====================================================== 3 passed, 2 xpassed in 4.51 seconds =======================================================
