summaryrefslogtreecommitdiff
path: root/lib/spack/spack/extensions.py
blob: 995464ea730537b96f6ad77a6de35acbdd349c16 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Copyright 2013-2018 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)
"""Service functions and classes to implement the hooks
for Spack's command extensions.
"""
import os
import re
import sys
import types

import llnl.util.lang
import llnl.util.tty as tty
import spack.config

extension_regexp = re.compile(r'spack-([\w]*)')


def extension_name(path):
    """Returns the name of the extension in the path passed as argument.

    Args:
        path (str): path where the extension resides

    Returns:
        The extension name or None if path doesn't match the format
        for Spack's extension.
    """
    regexp_match = re.search(extension_regexp, os.path.basename(path))
    if not regexp_match:
        msg = "[FOLDER NAMING]"
        msg += " {0} doesn't match the format for Spack's extensions"
        tty.warn(msg.format(path))
        return None
    return regexp_match.group(1)


def load_command_extension(command, path):
    """Loads a command extension from the path passed as argument.

    Args:
        command (str): name of the command
        path (str): base path of the command extension

    Returns:
        A valid module object if the command is found or None
    """
    extension = extension_name(path)
    if not extension:
        return None

    # Compute the name of the module we search, exit early if already imported
    cmd_package = '{0}.{1}.cmd'.format(__name__, extension)
    python_name = command.replace('-', '_')
    module_name = '{0}.{1}'.format(cmd_package, python_name)
    if module_name in sys.modules:
        return sys.modules[module_name]

    def ensure_package_creation(name):
        package_name = '{0}.{1}'.format(__name__, name)
        if package_name in sys.modules:
            return

        parts = [path] + name.split('.') + ['__init__.py']
        init_file = os.path.join(*parts)
        if os.path.exists(init_file):
            m = llnl.util.lang.load_module_from_file(package_name, init_file)
        else:
            m = types.ModuleType(package_name)

        # Setting __path__ to give spack extensions the
        # ability to import from their own tree, see:
        #
        # https://docs.python.org/3/reference/import.html#package-path-rules
        #
        m.__path__ = [os.path.dirname(init_file)]
        sys.modules[package_name] = m

    # Create a searchable package for both the root folder of the extension
    # and the subfolder containing the commands
    ensure_package_creation(extension)
    ensure_package_creation(extension + '.cmd')

    # Compute the absolute path of the file to be loaded, along with the
    # name of the python module where it will be stored
    cmd_path = os.path.join(path, extension, 'cmd', command + '.py')

    try:
        # TODO: Upon removal of support for Python 2.6 substitute the call
        # TODO: below with importlib.import_module(module_name)
        module = llnl.util.lang.load_module_from_file(module_name, cmd_path)
        sys.modules[module_name] = module
    except (ImportError, IOError):
        module = None

    return module


def get_command_paths():
    """Return the list of paths where to search for command files."""
    command_paths = []
    extension_paths = spack.config.get('config:extensions') or []

    for path in extension_paths:
        extension = extension_name(path)
        if extension:
            command_paths.append(os.path.join(path, extension, 'cmd'))

    return command_paths


def path_for_extension(target_name, *paths):
    """Return the test root dir for a given extension.

    Args:
        target_name (str): name of the extension to test
        *paths: paths where the extensions reside

    Returns:
        Root directory where tests should reside or None
    """
    for path in paths:
        name = extension_name(path)
        if name == target_name:
            return path
    else:
        raise IOError('extension "{0}" not found'.format(target_name))


def get_module(cmd_name):
    """Imports the extension module for a particular command name
    and returns it.

    Args:
        cmd_name (str): name of the command for which to get a module
            (contains ``-``, not ``_``).
    """
    # If built-in failed the import search the extension
    # directories in order
    extensions = spack.config.get('config:extensions') or []
    for folder in extensions:
        module = load_command_extension(cmd_name, folder)
        if module:
            return module
    else:
        return None


def get_template_dirs():
    """Returns the list of directories where to search for templates
    in extensions.
    """
    extension_dirs = spack.config.get('config:extensions') or []
    extensions = [os.path.join(x, 'templates') for x in extension_dirs]
    return extensions