summaryrefslogtreecommitdiff
path: root/lib/spack/spack/packages/__init__.py
blob: d4d1d0b7866f5be9fccd2140219b0bc6418fe564 (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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import re
import os
import sys
import string
import inspect
import glob

import spack
import spack.error
import spack.spec
import spack.tty as tty
from spack.util.filesystem import new_path
from spack.util.lang import list_modules
import spack.arch as arch

# Valid package names can contain '-' but can't start with it.
valid_package_re = r'^\w[\w-]*$'

# Don't allow consecutive [_-] in package names
invalid_package_re = r'[_-][_-]+'

instances = {}


class ProviderIndex(object):
    """This is a dict of dicts used for finding providers of particular
       virtual dependencies. The dict of dicts looks like:

       { vpkg name :
           { full vpkg spec : package providing spec } }

       Callers can use this to first find which packages provide a vpkg,
       then find a matching full spec.  e.g., in this scenario:

       { 'mpi' :
           { mpi@:1.1 : mpich,
             mpi@:2.3 : mpich2@1.9: } }

       Calling find_provider(spec) will find a package that provides a
       matching implementation of MPI.
    """
    def __init__(self, providers):
        """Takes a list of provider packagse and build an index of the virtual
           packages they provide."""
        self.providers = {}
        self.add(*providers)


    def add(self, *providers):
        """Look at the provided map on the provider packages, invert it and
           add it to this provider index."""
        for pkg in providers:
            for provided_spec, provider_spec in pkg.provided.iteritems():
                provided_name = provided_spec.name
                if provided_name not in self.providers:
                    self.providers[provided_name] = {}
                self.providers[provided_name][provided_spec] = provider_spec


    def providers_for(self, *vpkg_specs):
        """Gives names of all packages that provide virtual packages
           with the supplied names."""
        packages = set()
        for vspec in vpkg_specs:
            # Allow string names to be passed as input, as well as specs
            if type(vspec) == str:
                vspec = spack.spec.Spec(vspec)

            # Add all the packages that satisfy the vpkg spec.
            if vspec.name in self.providers:
                for provider_spec, pkg in self.providers[vspec.name].items():
                    if provider_spec.satisfies(vspec):
                        packages.add(pkg)

        # Return packages in order
        return sorted(packages)


def get(pkg_name):
    if not pkg_name in instances:
        package_class = get_class_for_package_name(pkg_name)
        instances[pkg_name] = package_class(pkg_name)

    return instances[pkg_name]


def providers_for(vpkg_spec):
    if providers_for.index is None:
        providers_for.index = ProviderIndex(all_packages())

    providers = providers_for.index.providers_for(vpkg_spec)
    if not providers:
        raise UnknownPackageError("No such virtual package: %s" % vpkg_spec)
    return providers
providers_for.index = None


def valid_package_name(pkg_name):
    return (re.match(valid_package_re, pkg_name) and
            not re.search(invalid_package_re, pkg_name))


def validate_package_name(pkg_name):
    if not valid_package_name(pkg_name):
        raise InvalidPackageNameError(pkg_name)


def filename_for_package_name(pkg_name):
    """Get the filename where a package name should be stored."""
    validate_package_name(pkg_name)
    return new_path(spack.packages_path, "%s.py" % pkg_name)


def installed_packages():
    return spack.install_layout.all_specs()


def all_package_names():
    """Generator function for all packages."""
    for module in list_modules(spack.packages_path):
        yield module


def all_packages():
    for name in all_package_names():
        yield get(name)


def class_name_for_package_name(pkg_name):
    """Get a name for the class the package file should contain.  Note that
       conflicts don't matter because the classes are in different modules.
    """
    validate_package_name(pkg_name)
    class_name = string.capwords(pkg_name.replace('_', '-'), '-')

    # If a class starts with a number, prefix it with Number_ to make it a valid
    # Python class name.
    if re.match(r'^[0-9]', class_name):
        class_name = "Num_%s" % class_name

    return class_name


def exists(pkg_name):
    """Whether a package is concrete."""
    return os.path.exists(filename_for_package_name(pkg_name))


def packages_module():
    # TODO: replace this with a proper package DB class, instead of this hackiness.
    packages_path = re.sub(spack.module_path + '\/+', 'spack.', spack.packages_path)
    packages_module = re.sub(r'\/', '.', packages_path)
    return packages_module


def get_class_for_package_name(pkg_name):
    file_name = filename_for_package_name(pkg_name)

    if os.path.exists(file_name):
        if not os.path.isfile(file_name):
            tty.die("Something's wrong. '%s' is not a file!" % file_name)
        if not os.access(file_name, os.R_OK):
            tty.die("Cannot read '%s'!" % file_name)
    else:
        raise UnknownPackageError(pkg_name)

    # Figure out pacakges module from spack.packages_path
    # This allows us to change the module path.
    if not re.match(r'%s' % spack.module_path, spack.packages_path):
        raise RuntimeError("Packages path is not a submodule of spack.")

    class_name = pkg_name.capitalize()
    try:
        module_name = "%s.%s" % (packages_module(), pkg_name)
        module = __import__(module_name, fromlist=[class_name])
    except ImportError, e:
        tty.die("Error while importing %s.%s:\n%s" % (pkg_name, class_name, e.message))

    cls = getattr(module, class_name)
    if not inspect.isclass(cls):
        tty.die("%s.%s is not a class" % (pkg_name, class_name))

    return cls


def compute_dependents():
    """Reads in all package files and sets dependence information on
       Package objects in memory.
    """
    for pkg in all_packages():
        if pkg._dependents is None:
            pkg._dependents = []

        for name, dep in pkg.dependencies.iteritems():
            dpkg = get(name)
            if dpkg._dependents is None:
                dpkg._dependents = []
            dpkg._dependents.append(pkg.name)


def graph_dependencies(out=sys.stdout):
    """Print out a graph of all the dependencies between package.
       Graph is in dot format."""
    out.write('digraph G {\n')
    out.write('  label = "Spack Dependencies"\n')
    out.write('  labelloc = "b"\n')
    out.write('  rankdir = "LR"\n')
    out.write('  ranksep = "5"\n')
    out.write('\n')

    def quote(string):
        return '"%s"' % string

    deps = []
    for pkg in all_packages():
        out.write('  %-30s [label="%s"]\n' % (quote(pkg.name), pkg.name))
        for dep_name, dep in pkg.dependencies.iteritems():
            deps.append((pkg.name, dep_name))
    out.write('\n')

    for pair in deps:
        out.write('  "%s" -> "%s"\n' % pair)
    out.write('}\n')


class InvalidPackageNameError(spack.error.SpackError):
    """Raised when we encounter a bad package name."""
    def __init__(self, name):
        super(InvalidPackageNameError, self).__init__(
            "Invalid package name: " + name)
        self.name = name


class UnknownPackageError(spack.error.SpackError):
    """Raised when we encounter a package spack doesn't have."""
    def __init__(self, name):
        super(UnknownPackageError, self).__init__("Package %s not found." % name)
        self.name = name